Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 24 Jul 2013 18:22:45 -0400
changeset 139883 22f051d8f93136db61cdec5726473442dfbfb551
parent 139882 4ededcd9b11f44de709829202b70526611393c59 (current diff)
parent 139791 d7f60ad11f4851a08be6e54840f8469eb2f4118c (diff)
child 139884 0a8a8ee6daab2096898ccc6e79cfcd7eae31507f
push idunknown
push userunknown
push dateunknown
milestone25.0a1
Merge m-c to inbound.
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/newtab/dropTargetShim.js
+++ b/browser/base/content/newtab/dropTargetShim.js
@@ -18,59 +18,141 @@ let gDropTargetShim = {
   /**
    * The last drop target that was hovered.
    */
   _lastDropTarget: null,
 
   /**
    * Initializes the drop target shim.
    */
-  init: function DropTargetShim_init() {
-    let node = gGrid.node;
+  init: function () {
+    gGrid.node.addEventListener("dragstart", this, true);
+  },
+
+  /**
+   * Add all event listeners needed during a drag operation.
+   */
+  _addEventListeners: function () {
+    gGrid.node.addEventListener("dragend", this);
 
-    // Add drag event handlers.
-    node.addEventListener("dragstart", this, true);
-    node.addEventListener("dragend", this, true);
+    let docElement = document.documentElement;
+    docElement.addEventListener("dragover", this);
+    docElement.addEventListener("dragenter", this);
+    docElement.addEventListener("drop", this);
+  },
+
+  /**
+   * Remove all event listeners that were needed during a drag operation.
+   */
+  _removeEventListeners: function () {
+    gGrid.node.removeEventListener("dragend", this);
+
+    let docElement = document.documentElement;
+    docElement.removeEventListener("dragover", this);
+    docElement.removeEventListener("dragenter", this);
+    docElement.removeEventListener("drop", this);
   },
 
   /**
    * Handles all shim events.
    */
-  handleEvent: function DropTargetShim_handleEvent(aEvent) {
+  handleEvent: function (aEvent) {
     switch (aEvent.type) {
       case "dragstart":
-        this._start(aEvent);
+        this._dragstart(aEvent);
+        break;
+      case "dragenter":
+        aEvent.preventDefault();
         break;
       case "dragover":
         this._dragover(aEvent);
         break;
+      case "drop":
+        this._drop(aEvent);
+        break;
       case "dragend":
-        this._end(aEvent);
+        this._dragend(aEvent);
         break;
     }
   },
 
   /**
    * Handles the 'dragstart' event.
    * @param aEvent The 'dragstart' event.
    */
-  _start: function DropTargetShim_start(aEvent) {
+  _dragstart: function (aEvent) {
     if (aEvent.target.classList.contains("newtab-link")) {
       gGrid.lock();
+      this._addEventListeners();
+    }
+  },
 
-      // XXX bug 505521 - Listen for dragover on the document.
-      document.documentElement.addEventListener("dragover", this, false);
+  /**
+   * Handles the 'dragover' event.
+   * @param aEvent The 'dragover' event.
+   */
+  _dragover: function (aEvent) {
+    // XXX bug 505521 - Use the dragover event to retrieve the
+    //                  current mouse coordinates while dragging.
+    let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
+    gDrag.drag(sourceNode._newtabSite, aEvent);
+
+    // Find the current drop target, if there's one.
+    this._updateDropTarget(aEvent);
+
+    // If we have a valid drop target,
+    // let the drag-and-drop service know.
+    if (this._lastDropTarget) {
+      aEvent.preventDefault();
     }
   },
 
   /**
-   * Handles the 'drag' event and determines the current drop target.
-   * @param aEvent The 'drag' event.
+   * Handles the 'drop' event.
+   * @param aEvent The 'drop' event.
+   */
+  _drop: function (aEvent) {
+    // We're accepting all drops.
+    aEvent.preventDefault();
+
+    // Make sure to determine the current drop target
+    // in case the dragover event hasn't been fired.
+    this._updateDropTarget(aEvent);
+
+    // A site was successfully dropped.
+    this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
+  },
+
+  /**
+   * Handles the 'dragend' event.
+   * @param aEvent The 'dragend' event.
    */
-  _drag: function DropTargetShim_drag(aEvent) {
+  _dragend: function (aEvent) {
+    if (this._lastDropTarget) {
+      if (aEvent.dataTransfer.mozUserCancelled) {
+        // The drag operation was cancelled.
+        this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+        this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+      }
+
+      // Clean up.
+      this._lastDropTarget = null;
+      this._cellPositions = null;
+    }
+
+    gGrid.unlock();
+    this._removeEventListeners();
+  },
+
+  /**
+   * Tries to find the current drop target and will fire
+   * appropriate dragenter, dragexit, and dragleave events.
+   * @param aEvent The current drag event.
+   */
+  _updateDropTarget: function (aEvent) {
     // Let's see if we find a drop target.
     let target = this._findDropTarget(aEvent);
 
     if (target != this._lastDropTarget) {
       if (this._lastDropTarget)
         // We left the last drop target.
         this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
 
@@ -82,63 +164,21 @@ let gDropTargetShim = {
         // We left the last drop target.
         this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
 
       this._lastDropTarget = target;
     }
   },
 
   /**
-   * Handles the 'dragover' event as long as bug 505521 isn't fixed to get
-   * current mouse cursor coordinates while dragging.
-   * @param aEvent The 'dragover' event.
-   */
-  _dragover: function DropTargetShim_dragover(aEvent) {
-    let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
-    gDrag.drag(sourceNode._newtabSite, aEvent);
-
-    this._drag(aEvent);
-  },
-
-  /**
-   * Handles the 'dragend' event.
-   * @param aEvent The 'dragend' event.
-   */
-  _end: function DropTargetShim_end(aEvent) {
-    // Make sure to determine the current drop target in case the dragenter
-    // event hasn't been fired.
-    this._drag(aEvent);
-
-    if (this._lastDropTarget) {
-      if (aEvent.dataTransfer.mozUserCancelled) {
-        // The drag operation was cancelled.
-        this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
-        this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
-      } else {
-        // A site was successfully dropped.
-        this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
-      }
-
-      // Clean up.
-      this._lastDropTarget = null;
-      this._cellPositions = null;
-    }
-
-    gGrid.unlock();
-
-    // XXX bug 505521 - Remove the document's dragover listener.
-    document.documentElement.removeEventListener("dragover", this, false);
-  },
-
-  /**
    * Determines the current drop target by matching the dragged site's position
    * against all cells in the grid.
    * @return The currently hovered drop target or null.
    */
-  _findDropTarget: function DropTargetShim_findDropTarget() {
+  _findDropTarget: function () {
     // These are the minimum intersection values - we want to use the cell if
     // the site is >= 50% hovering its position.
     let minWidth = gDrag.cellWidth / 2;
     let minHeight = gDrag.cellHeight / 2;
 
     let cellPositions = this._getCellPositions();
     let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);
 
@@ -169,20 +209,19 @@ let gDropTargetShim = {
   },
 
   /**
    * Dispatches a custom DragEvent on the given target node.
    * @param aEvent The source event.
    * @param aType The event type.
    * @param aTarget The target node that receives the event.
    */
-  _dispatchEvent:
-    function DropTargetShim_dispatchEvent(aEvent, aType, aTarget) {
-
+  _dispatchEvent: function (aEvent, aType, aTarget) {
     let node = aTarget.node;
     let event = document.createEvent("DragEvents");
 
-    event.initDragEvent(aType, true, true, window, 0, 0, 0, 0, 0, false, false,
+    // The event should not bubble to prevent recursion.
+    event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
                         false, false, 0, node, aEvent.dataTransfer);
 
     node.dispatchEvent(event);
   }
 };
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -190,17 +190,14 @@ Site.prototype = {
         break;
       case "mouseover":
         this._node.removeEventListener("mouseover", this, false);
         this._speculativeConnect();
         break;
       case "dragstart":
         gDrag.start(this, aEvent);
         break;
-      case "drag":
-        gDrag.drag(this, aEvent);
-        break;
       case "dragend":
         gDrag.end(this, aEvent);
         break;
     }
   }
 };
--- a/browser/base/content/newtab/transformations.js
+++ b/browser/base/content/newtab/transformations.js
@@ -151,17 +151,17 @@ let gTransformation = {
     targetPosition.top += this._cellBorderWidths.top;
 
     // Nothing to do here if the positions already match.
     if (currentPosition.left == targetPosition.left &&
         currentPosition.top == targetPosition.top) {
       finish();
     } else {
       this.setSitePosition(aSite, targetPosition);
-      this._whenTransitionEnded(aSite.node, finish);
+      this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
     }
   },
 
   /**
    * Rearranges a given array of sites and moves them to their new positions or
    * fades in/out new/removed sites.
    * @param aSites An array of sites to rearrange.
    * @param aOptions Set of options (see below).
@@ -197,25 +197,29 @@ let gTransformation = {
     let wait = Promise.promised(function () callback && callback());
     wait.apply(null, batch);
   },
 
   /**
    * Listens for the 'transitionend' event on a given node and calls the given
    * callback.
    * @param aNode The node that is transitioned.
+   * @param aProperties The properties we'll wait to be transitioned.
    * @param aCallback The callback to call when finished.
    */
   _whenTransitionEnded:
-    function Transformation_whenTransitionEnded(aNode, aCallback) {
+    function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
 
-    aNode.addEventListener("transitionend", function onEnd() {
-      aNode.removeEventListener("transitionend", onEnd, false);
-      aCallback();
-    }, false);
+    let props = new Set(aProperties);
+    aNode.addEventListener("transitionend", function onEnd(e) {
+      if (props.has(e.propertyName)) {
+        aNode.removeEventListener("transitionend", onEnd);
+        aCallback();
+      }
+    });
   },
 
   /**
    * Gets a given node's opacity value.
    * @param aNode The node to get the opacity value from.
    * @return The node's opacity value.
    */
   _getNodeOpacity: function Transformation_getNodeOpacity(aNode) {
@@ -231,18 +235,19 @@ let gTransformation = {
    */
   _setNodeOpacity:
     function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
 
     if (this._getNodeOpacity(aNode) == aOpacity) {
       if (aCallback)
         aCallback();
     } else {
-      if (aCallback)
-        this._whenTransitionEnded(aNode, aCallback);
+      if (aCallback) {
+        this._whenTransitionEnded(aNode, ["opacity"], aCallback);
+      }
 
       aNode.style.opacity = aOpacity;
     }
   },
 
   /**
    * Moves a site to the cell with the given index.
    * @param aSite The site to move.
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -561,41 +561,32 @@ let SessionStoreInternal = {
     });
 
     XPCOMUtils.defineLazyGetter(this, "_max_windows_undo", function () {
       this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
       return this._prefBranch.getIntPref("sessionstore.max_windows_undo");
     });
   },
 
-  _initWindow: function ssi_initWindow(aWindow) {
-    if (aWindow) {
-      this.onLoad(aWindow);
-    } else if (this._loadState == STATE_STOPPED) {
-      // If init is being called with a null window, it's possible that we
-      // just want to tell sessionstore that a session is live (as is the case
-      // with starting Firefox with -private, for example; see bug 568816),
-      // so we should mark the load state as running to make sure that
-      // things like setBrowserState calls will succeed in restoring the session.
-      this._loadState = STATE_RUNNING;
-    }
-  },
-
   /**
    * Start tracking a window.
    *
    * This function also initializes the component if it is not
    * initialized yet.
    */
   init: function ssi_init(aWindow) {
+    if (!aWindow) {
+      throw new Error("init() must be called with a valid window.");
+    }
+
     let self = this;
     this.initService();
     this._promiseInitialization.promise.then(
       function onSuccess() {
-        self._initWindow(aWindow);
+        self.onLoad(aWindow);
       }
     );
   },
 
   /**
    * Called on application shutdown, after notifications:
    * quit-application-granted, quit-application
    */
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -418,16 +418,17 @@ ThreadState.prototype = {
  * Keeps the stack frame list up-to-date, using the thread client's
  * stack frame cache.
  */
 function StackFrames() {
   this._onPaused = this._onPaused.bind(this);
   this._onResumed = this._onResumed.bind(this);
   this._onFrames = this._onFrames.bind(this);
   this._onFramesCleared = this._onFramesCleared.bind(this);
+  this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
   this._afterFramesCleared = this._afterFramesCleared.bind(this);
   this.evaluate = this.evaluate.bind(this);
 }
 
 StackFrames.prototype = {
   get activeThread() DebuggerController.activeThread,
   autoScopeExpand: false,
   currentFrame: null,
@@ -442,31 +443,33 @@ StackFrames.prototype = {
    * Connect to the current thread client.
    */
   connect: function() {
     dumpn("StackFrames is connecting...");
     this.activeThread.addListener("paused", this._onPaused);
     this.activeThread.addListener("resumed", this._onResumed);
     this.activeThread.addListener("framesadded", this._onFrames);
     this.activeThread.addListener("framescleared", this._onFramesCleared);
+    window.addEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false);
     this._handleTabNavigation();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function() {
     if (!this.activeThread) {
       return;
     }
     dumpn("StackFrames is disconnecting...");
     this.activeThread.removeListener("paused", this._onPaused);
     this.activeThread.removeListener("resumed", this._onResumed);
     this.activeThread.removeListener("framesadded", this._onFrames);
     this.activeThread.removeListener("framescleared", this._onFramesCleared);
+    window.removeEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false);
   },
 
   /**
    * Handles any initialization on a tab navigation event issued by the client.
    */
   _handleTabNavigation: function() {
     dumpn("Handling tab navigation in the StackFrames");
     // Nothing to do here yet.
@@ -589,22 +592,32 @@ StackFrames.prototype = {
 
 
     // Make sure the debugger view panes are visible.
     DebuggerView.showInstrumentsPane();
 
     // Make sure all the previous stackframes are removed before re-adding them.
     DebuggerView.StackFrames.empty();
 
+    let previousBlackBoxed = null;
     for (let frame of this.activeThread.cachedFrames) {
-      let { depth, where: { url, line } } = frame;
+      let { depth, where: { url, line }, isBlackBoxed } = frame;
       let frameLocation = NetworkHelper.convertToUnicode(unescape(url));
       let frameTitle = StackFrameUtils.getFrameTitle(frame);
 
-      DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth);
+      if (isBlackBoxed) {
+        if (previousBlackBoxed == url) {
+          continue;
+        }
+        previousBlackBoxed = url;
+      } else {
+        previousBlackBoxed = null;
+      }
+
+      DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth, isBlackBoxed);
     }
     if (this.currentFrame == null) {
       DebuggerView.StackFrames.selectedDepth = 0;
     }
     if (this.activeThread.moreFrames) {
       DebuggerView.StackFrames.dirty = true;
     }
   },
@@ -622,16 +635,28 @@ StackFrames.prototype = {
     // After each frame step (in, over, out), framescleared is fired, which
     // forces the UI to be emptied and rebuilt on framesadded. Most of the times
     // this is not necessary, and will result in a brief redraw flicker.
     // To avoid it, invalidate the UI only after a short time if necessary.
     window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY);
   },
 
   /**
+   * Handler for the debugger's BlackBoxChange notification.
+   */
+  _onBlackBoxChange: function() {
+    if (this.activeThread.state == "paused") {
+      // We have to clear out the existing frames and refetch them to get their
+      // updated black boxed status.
+      this.activeThread._clearFrames();
+      this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+    }
+  },
+
+  /**
    * Called soon after the thread client's framescleared notification.
    */
   _afterFramesCleared: function() {
     // Ignore useless notifications.
     if (this.activeThread.cachedFrames.length) {
       return;
     }
     DebuggerView.StackFrames.empty();
@@ -996,16 +1021,37 @@ SourceScripts.prototype = {
     DebuggerController.Breakpoints.updateEditorBreakpoints();
     DebuggerController.Breakpoints.updatePaneBreakpoints();
 
     // Signal that scripts have been added.
     window.dispatchEvent(document, "Debugger:AfterSourcesAdded");
   },
 
   /**
+   * Set the black boxed status of the given source.
+   *
+   * @param Object aSource
+   *        The source form.
+   * @param bool aBlackBoxFlag
+   *        True to black box the source, false to un-black box it.
+   */
+  blackBox: function(aSource, aBlackBoxFlag) {
+    const sourceClient = this.activeThread.source(aSource);
+    sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](function({ error, message }) {
+      if (error) {
+        let msg = "Could not toggle black boxing for "
+          + aSource.url + ": " + message;
+        dumpn(msg);
+        return void Cu.reportError(msg);
+      }
+      window.dispatchEvent(document, "Debugger:BlackBoxChange", sourceClient);
+    });
+  },
+
+  /**
    * Gets a specified source's text.
    *
    * @param object aSource
    *        The source object coming from the active thread.
    * @param function aOnTimeout [optional]
    *        Function called when the source text takes a long time to fetch,
    *        but not necessarily failing. Long fetch times don't cause the
    *        rejection of the returned promise.
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -13,46 +13,51 @@ function SourcesView() {
 
   this._onEditorLoad = this._onEditorLoad.bind(this);
   this._onEditorUnload = this._onEditorUnload.bind(this);
   this._onEditorSelection = this._onEditorSelection.bind(this);
   this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
   this._onSourceSelect = this._onSourceSelect.bind(this);
   this._onSourceClick = this._onSourceClick.bind(this);
   this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
+  this._onSourceCheck = this._onSourceCheck.bind(this);
   this._onBreakpointClick = this._onBreakpointClick.bind(this);
   this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
   this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
   this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
   this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
   this._onConditionalTextboxInput = this._onConditionalTextboxInput.bind(this);
   this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
 }
 
 SourcesView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the SourcesView");
 
-    this.widget = new SideMenuWidget(document.getElementById("sources"));
+    this.widget = new SideMenuWidget(document.getElementById("sources"), {
+      showCheckboxes: true
+    });
     this.emptyText = L10N.getStr("noSourcesText");
     this.unavailableText = L10N.getStr("noMatchingSourcesText");
+    this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
 
     this._commandset = document.getElementById("debuggerCommands");
     this._popupset = document.getElementById("debuggerPopupset");
     this._cmPopup = document.getElementById("sourceEditorContextMenu");
     this._cbPanel = document.getElementById("conditional-breakpoint-panel");
     this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
 
     window.addEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
     window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
     this.widget.addEventListener("select", this._onSourceSelect, false);
     this.widget.addEventListener("click", this._onSourceClick, false);
+    this.widget.addEventListener("check", this._onSourceCheck, false);
     this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
     this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
     this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.addEventListener("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
 
     this.autoFocusOnSelection = false;
 
@@ -65,16 +70,17 @@ SourcesView.prototype = Heritage.extend(
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
     window.removeEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
     window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
     this.widget.removeEventListener("select", this._onSourceSelect, false);
     this.widget.removeEventListener("click", this._onSourceClick, false);
+    this.widget.removeEventListener("check", this._onSourceCheck, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
     this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.removeEventListener("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
   },
 
   /**
@@ -104,16 +110,18 @@ SourcesView.prototype = Heritage.extend(
     let url = aSource.url;
     let label = SourceUtils.getSourceLabel(url.split(" -> ").pop());
     let group = SourceUtils.getSourceGroup(url.split(" -> ").pop());
 
     // Append a source item to this container.
     this.push([label, url, group], {
       staged: aOptions.staged, /* stage the item to be appended later? */
       attachment: {
+        checkboxState: !aSource.isBlackBoxed,
+        checkboxTooltip: this._blackBoxCheckboxTooltip,
         source: aSource
       }
     });
   },
 
   /**
    * Adds a breakpoint to this sources container.
    *
@@ -635,16 +643,24 @@ SourcesView.prototype = Heritage.extend(
    * The click listener for the sources container.
    */
   _onSourceClick: function() {
     // Use this container as a filtering target.
     DebuggerView.Filtering.target = this;
   },
 
   /**
+   * The check listener for the sources container.
+   */
+  _onSourceCheck: function({ detail: { checked }, target }) {
+    let item = this.getItemForElement(target);
+    DebuggerController.SourceScripts.blackBox(item.attachment.source, !checked);
+  },
+
+  /**
    * The click listener for a breakpoint container.
    */
   _onBreakpointClick: function(e) {
     let sourceItem = this.getItemForElement(e.target);
     let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
     let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
 
     let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -419,18 +419,20 @@ StackFramesView.prototype = Heritage.ext
    * @param string aFrameTitle
    *        The frame title to be displayed in the list.
    * @param string aSourceLocation
    *        The source location to be displayed in the list.
    * @param string aLineNumber
    *        The line number to be displayed in the list.
    * @param number aDepth
    *        The frame depth specified by the debugger.
+   * @param boolean aIsBlackBoxed
+   *        Whether or not the frame is black boxed.
    */
-  addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
+  addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
     // Create the element node and menu entry for the stack frame item.
     let frameView = this._createFrameView.apply(this, arguments);
     let menuEntry = this._createMenuEntry.apply(this, arguments);
 
     // Append a stack frame item to this container.
     this.push([frameView], {
       index: 0, /* specifies on which position should the item be appended */
       attachment: {
@@ -466,59 +468,67 @@ StackFramesView.prototype = Heritage.ext
    * @param string aFrameTitle
    *        The frame title to be displayed in the list.
    * @param string aSourceLocation
    *        The source location to be displayed in the list.
    * @param string aLineNumber
    *        The line number to be displayed in the list.
    * @param number aDepth
    *        The frame depth specified by the debugger.
+   * @param boolean aIsBlackBoxed
+   *        Whether or not the frame is black boxed.
    * @return nsIDOMNode
    *         The stack frame view.
    */
-  _createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
-    let frameDetails =
-      SourceUtils.trimUrlLength(
-        SourceUtils.getSourceLabel(aSourceLocation),
-        STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
-        STACK_FRAMES_SOURCE_URL_TRIM_SECTION) + SEARCH_LINE_FLAG + aLineNumber;
+  _createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
+    let container = document.createElement("hbox");
+    container.id = "stackframe-" + aDepth;
+    container.className = "dbg-stackframe";
+
+    let frameDetails = SourceUtils.trimUrlLength(
+      SourceUtils.getSourceLabel(aSourceLocation),
+      STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
+      STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
 
-    let frameTitleNode = document.createElement("label");
-    frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
-    frameTitleNode.setAttribute("value", aFrameTitle);
+    if (aIsBlackBoxed) {
+      container.classList.add("dbg-stackframe-black-boxed");
+    } else {
+      let frameTitleNode = document.createElement("label");
+      frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
+      frameTitleNode.setAttribute("value", aFrameTitle);
+      container.appendChild(frameTitleNode);
+
+      frameDetails += SEARCH_LINE_FLAG + aLineNumber;
+    }
 
     let frameDetailsNode = document.createElement("label");
     frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
     frameDetailsNode.setAttribute("value", frameDetails);
-
-    let container = document.createElement("hbox");
-    container.id = "stackframe-" + aDepth;
-    container.className = "dbg-stackframe";
-
-    container.appendChild(frameTitleNode);
     container.appendChild(frameDetailsNode);
 
     return container;
   },
 
   /**
    * Customization function for populating an item's context menu.
    *
    * @param string aFrameTitle
    *        The frame title to be displayed in the list.
    * @param string aSourceLocation
    *        The source location to be displayed in the list.
    * @param string aLineNumber
    *        The line number to be displayed in the list.
    * @param number aDepth
    *        The frame depth specified by the debugger.
+   * @param boolean aIsBlackBoxed
+   *        Whether or not the frame is black boxed.
    * @return object
    *         An object containing the stack frame command and menu item.
    */
-  _createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) {
+  _createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) {
     let frameDescription =
       SourceUtils.trimUrlLength(
         SourceUtils.getSourceLabel(aSourceLocation),
         STACK_FRAMES_POPUP_SOURCE_URL_MAX_LENGTH,
         STACK_FRAMES_POPUP_SOURCE_URL_TRIM_SECTION) + SEARCH_LINE_FLAG + aLineNumber;
 
     let prefix = "sf-cMenu-"; // "stackframes context menu"
     let commandId = prefix + aDepth + "-" + "-command";
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -7,16 +7,20 @@ topsrcdir       = @top_srcdir@
 srcdir          = @srcdir@
 VPATH           = @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_aaa_run_first_leaktest.js \
+	browser_dbg_blackboxing-01.js \
+	browser_dbg_blackboxing-02.js \
+	browser_dbg_blackboxing-03.js \
+	browser_dbg_blackboxing-04.js \
 	browser_dbg_clean-exit.js \
 	browser_dbg_cmd.js \
 	browser_dbg_cmd_break.js \
 	browser_dbg_debuggerstatement.js \
 	browser_dbg_listtabs-01.js \
 	browser_dbg_listtabs-02.js \
 	browser_dbg_tabactor-01.js \
 	browser_dbg_tabactor-02.js \
@@ -106,16 +110,21 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_source_maps-01.js \
 	browser_dbg_source_maps-02.js \
 	browser_dbg_step-out.js \
 	browser_dbg_event-listeners.js \
 	head.js \
 	$(NULL)
 
 MOCHITEST_BROWSER_PAGES = \
+	browser_dbg_blackboxing.html \
+	blackboxing_blackboxme.js \
+	blackboxing_one.js \
+	blackboxing_two.js \
+	blackboxing_three.js \
 	browser_dbg_cmd_break.html \
 	browser_dbg_cmd.html \
 	testactors.js \
 	browser_dbg_tab1.html \
 	browser_dbg_tab2.html \
 	browser_dbg_debuggerstatement.html \
 	browser_dbg_stack.html \
 	browser_dbg_script-switching.html \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/blackboxing_blackboxme.js
@@ -0,0 +1,9 @@
+function blackboxme(fn) {
+  (function one() {
+    (function two() {
+      (function three() {
+        fn();
+      }());
+    }());
+  }());
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/blackboxing_one.js
@@ -0,0 +1,1 @@
+function one() { two(); }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/blackboxing_three.js
@@ -0,0 +1,1 @@
+function three() { doDebuggerStatement(); }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/blackboxing_two.js
@@ -0,0 +1,1 @@
+function two() { three(); }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that if we black box a source and then refresh, it is still black boxed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "binary_search.html";
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    resumed = true;
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+
+    testBlackBoxSource();
+  });
+}
+
+function testBlackBoxSource() {
+  once(gDebugger, "Debugger:SourceShown", function () {
+    const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
+    ok(checkbox, "Should get the checkbox for black boxing the source");
+    ok(checkbox.checked, "Should not be black boxed by default");
+
+    once(gDebugger, "Debugger:BlackBoxChange", function (event) {
+      const sourceClient = event.detail;
+      ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
+      ok(!checkbox.checked, "The checkbox should no longer be checked.");
+
+      testBlackBoxReload();
+    });
+
+    checkbox.click();
+  });
+}
+
+function testBlackBoxReload() {
+  once(gDebugger, "Debugger:SourceShown", function () {
+    const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
+    ok(checkbox, "Should get the checkbox for black boxing the source");
+    ok(!checkbox.checked, "Should still be black boxed");
+
+    closeDebuggerAndFinish();
+  });
+
+  gDebuggee.location.reload();
+}
+
+function once(target, event, callback) {
+  target.addEventListener(event, function _listener(...args) {
+    target.removeEventListener(event, _listener, false);
+    callback.apply(null, args);
+  }, false);
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that black boxed frames are compressed into a single frame on the stack
+ * view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    resumed = true;
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+
+    testBlackBoxSource();
+  });
+}
+
+function testBlackBoxSource() {
+  once(gDebugger, "Debugger:SourceShown", function () {
+    const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
+    ok(checkbox, "Should get the checkbox for blackBoxing the source");
+
+    once(gDebugger, "Debugger:BlackBoxChange", function (event) {
+      const sourceClient = event.detail;
+      ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
+
+      testBlackBoxStack();
+    });
+
+    checkbox.click();
+  });
+}
+
+function testBlackBoxStack() {
+  const { activeThread } = gDebugger.DebuggerController;
+  activeThread.addOneTimeListener("framesadded", function () {
+    const frames = gDebugger.DebuggerView.StackFrames.widget._list;
+
+    is(frames.querySelectorAll(".dbg-stackframe").length, 3,
+       "Should only get 3 frames");
+
+    is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
+       "And one of them should be the combined black boxed frames");
+
+    closeDebuggerAndFinish();
+  });
+
+  gDebuggee.runTest();
+}
+
+function getBlackBoxCheckbox(url) {
+  return gDebugger.document.querySelector(
+    ".side-menu-widget-item[tooltiptext=\""
+      + url + "\"] .side-menu-widget-item-checkbox");
+}
+
+function once(target, event, callback) {
+  target.addEventListener(event, function _listener(...args) {
+    target.removeEventListener(event, _listener, false);
+    callback.apply(null, args);
+  }, false);
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that black boxed frames are compressed into a single frame on the stack
+ * view when we are already paused.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
+const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    resumed = true;
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+
+    once(gDebugger, "Debugger:SourceShown", function () {
+      testBlackBoxStack();
+    });
+  });
+}
+
+function testBlackBoxStack() {
+  const { activeThread } = gDebugger.DebuggerController;
+  activeThread.addOneTimeListener("framesadded", function () {
+    const frames = gDebugger.DebuggerView.StackFrames.widget._list;
+
+    is(frames.querySelectorAll(".dbg-stackframe").length, 6,
+       "Should get 6 frames");
+
+    is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
+       "And none of them are black boxed");
+
+    testBlackBoxSource();
+  });
+
+  gDebuggee.runTest();
+}
+
+function testBlackBoxSource() {
+  const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
+  ok(checkbox, "Should get the checkbox for black boxing the source");
+
+  once(gDebugger, "Debugger:BlackBoxChange", function (event) {
+    const { activeThread } = gDebugger.DebuggerController;
+    activeThread.addOneTimeListener("framesadded", function () {
+      const sourceClient = event.detail;
+      ok(sourceClient.isBlackBoxed, "The source should be black boxed now");
+
+      const frames = gDebugger.DebuggerView.StackFrames.widget._list;
+      is(frames.querySelectorAll(".dbg-stackframe").length, 3,
+         "Should only get 3 frames");
+      is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
+         "And one of them is the combined black boxed frames");
+
+      closeDebuggerAndFinish();
+    });
+  });
+
+  checkbox.click();
+}
+
+function getBlackBoxCheckbox(url) {
+  return gDebugger.document.querySelector(
+    ".side-menu-widget-item[tooltiptext=\""
+      + url + "\"] .side-menu-widget-item-checkbox");
+}
+
+function once(target, event, callback) {
+  target.addEventListener(event, function _listener(...args) {
+    target.removeEventListener(event, _listener, false);
+    callback.apply(null, args);
+  }, false);
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get a stack frame for each black boxed source, not a single one
+ * for all of them.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html";
+
+var gPane = null;
+var gTab = null;
+var gDebuggee = null;
+var gDebugger = null;
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    resumed = true;
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+
+    once(gDebugger, "Debugger:SourceShown", function () {
+      blackBoxSources();
+    });
+  });
+}
+
+function blackBoxSources() {
+  let timesFired = 0;
+  gDebugger.addEventListener("Debugger:BlackBoxChange", function _onBlackboxChange() {
+    if (++timesFired !== 3) {
+      return;
+    }
+    gDebugger.removeEventListener("Debugger:BlackBoxChange", _onBlackboxChange, false);
+
+    const { activeThread } = gDebugger.DebuggerController;
+    activeThread.addOneTimeListener("framesadded", testStackFrames);
+
+    gDebuggee.one();
+  }, false);
+
+  getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_one.js").click();
+  getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_two.js").click();
+  getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_three.js").click();
+}
+
+function testStackFrames() {
+  const frames = gDebugger.DebuggerView.StackFrames.widget._list;
+  is(frames.querySelectorAll(".dbg-stackframe").length, 4,
+     "Should get 4 frames (one -> two -> three -> doDebuggerStatement)");
+  is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
+     "And one, two, and three should each have their own black boxed frame.");
+
+  closeDebuggerAndFinish();
+}
+
+function getBlackBoxCheckbox(url) {
+  return gDebugger.document.querySelector(
+    ".side-menu-widget-item[tooltiptext=\""
+      + url + "\"] .side-menu-widget-item-checkbox");
+}
+
+function once(target, event, callback) {
+  target.addEventListener(event, function _listener(...args) {
+    target.removeEventListener(event, _listener, false);
+    callback.apply(null, args);
+  }, false);
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebuggee = null;
+  gDebugger = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <title>Browser Debugger Blackbox Test</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript" src="blackboxing_blackboxme.js"></script>
+    <script type="text/javascript" src="blackboxing_one.js"></script>
+    <script type="text/javascript" src="blackboxing_two.js"></script>
+    <script type="text/javascript" src="blackboxing_three.js"></script>
+    <script>
+      function runTest() {
+        blackboxme(doDebuggerStatement);
+      }
+      function doDebuggerStatement() {
+        debugger;
+      }
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
--- a/browser/devtools/inspector/breadcrumbs.js
+++ b/browser/devtools/inspector/breadcrumbs.js
@@ -479,21 +479,23 @@ HTMLBreadcrumbs.prototype = {
       let fragment = this.chromeDoc.createDocumentFragment();
       let toAppend = aNode;
       let lastButtonInserted = null;
       let originalLength = this.nodeHierarchy.length;
       let stopNode = null;
       if (originalLength > 0) {
         stopNode = this.nodeHierarchy[originalLength - 1].node;
       }
-      while (toAppend && toAppend.tagName && toAppend != stopNode) {
-        let button = this.buildButton(toAppend);
-        fragment.insertBefore(button, lastButtonInserted);
-        lastButtonInserted = button;
-        this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
+      while (toAppend && toAppend != stopNode) {
+        if (toAppend.tagName) {
+          let button = this.buildButton(toAppend);
+          fragment.insertBefore(button, lastButtonInserted);
+          lastButtonInserted = button;
+          this.nodeHierarchy.splice(originalLength, 0, {node: toAppend, button: button});
+        }
         toAppend = toAppend.parentNode();
       }
       this.container.appendChild(fragment, this.container.firstChild);
   },
 
   /**
    * Get a child of a node that can be displayed in the breadcrumbs
    * and that is probably visible. See LOW_PRIORITY_ELEMENTS.
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -151,24 +151,27 @@ InspectorPanel.prototype = {
     this.setupSidebar();
 
     return deferred.promise;
   },
 
   /**
    * Return a promise that will resolve to the default node for selection.
    */
-  _getDefaultNodeForSelection : function() {
+  _getDefaultNodeForSelection: function() {
     if (this._defaultNode) {
       return this._defaultNode;
     }
     let walker = this.walker;
+
     // if available set body node as default selected node
     // else set documentElement
-    return walker.querySelector(this.walker.rootNode, "body").then(front => {
+    return walker.getRootNode().then(rootNode => {
+      return walker.querySelector(rootNode, "body");
+    }).then(front => {
       if (front) {
         return front;
       }
       return this.walker.documentElement(this.walker.rootNode);
     }).then(node => {
       if (walker !== this.walker) {
         promise.reject(null);
       }
@@ -280,43 +283,31 @@ InspectorPanel.prototype = {
     this.sidebar.show();
   },
 
   /**
    * Reset the inspector on navigate away.
    */
   onNavigatedAway: function InspectorPanel_onNavigatedAway(event, payload) {
     let newWindow = payload._navPayload || payload;
-    this.walker.release().then(null, console.error);
-    this.walker = null;
     this._defaultNode = null;
     this.selection.setNodeFront(null);
-    this.selection.setWalker(null);
     this._destroyMarkup();
     this.isDirty = false;
 
-    this.target.inspector.getWalker().then(walker => {
+    this._getDefaultNodeForSelection().then(defaultNode => {
       if (this._destroyPromise) {
-        walker.release().then(null, console.error);
         return;
       }
+      this.selection.setNodeFront(defaultNode, "navigateaway");
 
-      this.walker = walker;
-      this.selection.setWalker(walker);
-      this._getDefaultNodeForSelection().then(defaultNode => {
-        if (this._destroyPromise) {
-          return;
-        }
-        this.selection.setNodeFront(defaultNode, "navigateaway");
-
-        this._initMarkup();
-        this.once("markuploaded", () => {
-          this.markup.expandNode(this.selection.nodeFront);
-          this.setupSearchBox();
-        });
+      this._initMarkup();
+      this.once("markuploaded", () => {
+        this.markup.expandNode(this.selection.nodeFront);
+        this.setupSearchBox();
       });
     });
   },
 
   /**
    * When a new node is selected.
    */
   onNewSelection: function InspectorPanel_onNewSelection() {
--- a/browser/devtools/inspector/test/browser_inspector_iframeTest.js
+++ b/browser/devtools/inspector/test/browser_inspector_iframeTest.js
@@ -69,17 +69,38 @@ function performTestComparisons1()
 
 function performTestComparisons2()
 {
   let i = getActiveInspector();
 
   is(i.selection.node, div2, "selection matches div2 node");
   is(getHighlitNode(), div2, "highlighter matches selection");
 
-  finish();
+  selectRoot();
+}
+
+function selectRoot()
+{
+  // Select the root document element to clear the breadcrumbs.
+  let i = getActiveInspector();
+  i.selection.setNode(doc.documentElement);
+  i.once("inspector-updated", selectIframe);
+}
+
+function selectIframe()
+{
+  // Directly select an element in an iframe (without navigating to it
+  // with mousemoves).
+  let i = getActiveInspector();
+  i.selection.setNode(div2);
+  i.once("inspector-updated", () => {
+    let breadcrumbs = i.breadcrumbs;
+    is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items");
+    finish();
+  });
 }
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -9,39 +9,56 @@
  *
  * Copied and relicensed from the Public Domain.
  * See bug 653934 for details.
  * https://bugzilla.mozilla.org/show_bug.cgi?id=653934
  */
 
 "use strict";
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+let require = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+
+let { Cc, Ci, Cu } = require("chrome");
+let promise = require("sdk/core/promise");
+let Telemetry = require("devtools/shared/telemetry");
+let TargetFactory = require("devtools/framework/target").TargetFactory;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource:///modules/source-editor.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
 Cu.import("resource://gre/modules/jsdebugger.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
-let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js").Promise;
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
-                                  "resource:///modules/devtools/VariablesView.jsm");
+  "resource:///modules/devtools/VariablesView.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
+  "resource:///modules/devtools/VariablesViewController.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "GripClient",
+  "resource://gre/modules/devtools/dbg-client.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
+  "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "devtools",
-                                  "resource://gre/modules/devtools/Loader.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
+  "resource://gre/modules/devtools/dbg-server.jsm");
 
-let Telemetry = devtools.require("devtools/shared/telemetry");
+XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
+  "resource://gre/modules/devtools/dbg-client.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () =>
+  Services.prefs.getIntPref("devtools.debugger.remote-timeout")
+);
+ 
 const SCRATCHPAD_CONTEXT_CONTENT = 1;
 const SCRATCHPAD_CONTEXT_BROWSER = 2;
 const SCRATCHPAD_L10N = "chrome://browser/locale/devtools/scratchpad.properties";
 const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
 const BUTTON_POSITION_SAVE = 0;
 const BUTTON_POSITION_CANCEL = 1;
 const BUTTON_POSITION_DONT_SAVE = 2;
@@ -233,36 +250,25 @@ var Scratchpad = {
   },
 
   /**
    * Get the most recent chrome window of type navigator:browser.
    */
   get browserWindow() Services.wm.getMostRecentWindow("navigator:browser"),
 
   /**
-   * Reference to the last chrome window of type navigator:browser. We use this
-   * to check if the chrome window changed since the last code evaluation.
-   */
-  _previousWindow: null,
-
-  /**
    * Get the gBrowser object of the most recent browser window.
    */
   get gBrowser()
   {
     let recentWin = this.browserWindow;
     return recentWin ? recentWin.gBrowser : null;
   },
 
   /**
-   * Cached Cu.Sandbox object for the active tab content window object.
-   */
-  _contentSandbox: null,
-
-  /**
    * Unique name for the current Scratchpad instance. Used to distinguish
    * Scratchpad windows between each other. See bug 661762.
    */
   get uniqueName()
   {
     return "Scratchpad/" + this._instanceId;
   },
 
@@ -274,83 +280,16 @@ var Scratchpad = {
   {
     if (!this._sidebar) {
       this._sidebar = new ScratchpadSidebar(this);
     }
     return this._sidebar;
   },
 
   /**
-   * Get the Cu.Sandbox object for the active tab content window object. Note
-   * that the returned object is cached for later reuse. The cached object is
-   * kept only for the current location in the current tab of the current
-   * browser window and it is reset for each context switch,
-   * navigator:browser window switch, tab switch or navigation.
-   */
-  get contentSandbox()
-  {
-    if (!this.browserWindow) {
-      Cu.reportError(this.strings.
-                     GetStringFromName("browserWindow.unavailable"));
-      return;
-    }
-
-    if (!this._contentSandbox ||
-        this.browserWindow != this._previousBrowserWindow ||
-        this._previousBrowser != this.gBrowser.selectedBrowser ||
-        this._previousLocation != this.gBrowser.contentWindow.location.href) {
-      let contentWindow = this.gBrowser.selectedBrowser.contentWindow;
-      this._contentSandbox = new Cu.Sandbox(contentWindow,
-        { sandboxPrototype: contentWindow, wantXrays: false,
-          sandboxName: 'scratchpad-content'});
-      this._contentSandbox.__SCRATCHPAD__ = this;
-
-      this._previousBrowserWindow = this.browserWindow;
-      this._previousBrowser = this.gBrowser.selectedBrowser;
-      this._previousLocation = contentWindow.location.href;
-    }
-
-    return this._contentSandbox;
-  },
-
-  /**
-   * Cached Cu.Sandbox object for the most recently active navigator:browser
-   * chrome window object.
-   */
-  _chromeSandbox: null,
-
-  /**
-   * Get the Cu.Sandbox object for the most recently active navigator:browser
-   * chrome window object. Note that the returned object is cached for later
-   * reuse. The cached object is kept only for the current browser window and it
-   * is reset for each context switch or navigator:browser window switch.
-   */
-  get chromeSandbox()
-  {
-    if (!this.browserWindow) {
-      Cu.reportError(this.strings.
-                     GetStringFromName("browserWindow.unavailable"));
-      return;
-    }
-
-    if (!this._chromeSandbox ||
-        this.browserWindow != this._previousBrowserWindow) {
-      this._chromeSandbox = new Cu.Sandbox(this.browserWindow,
-        { sandboxPrototype: this.browserWindow, wantXrays: false,
-          sandboxName: 'scratchpad-chrome'});
-      this._chromeSandbox.__SCRATCHPAD__ = this;
-      addDebuggerToGlobal(this._chromeSandbox);
-
-      this._previousBrowserWindow = this.browserWindow;
-    }
-
-    return this._chromeSandbox;
-  },
-
-  /**
    * Drop the editor selection.
    */
   deselect: function SP_deselect()
   {
     this.editor.dropSelection();
   },
 
   /**
@@ -382,73 +321,87 @@ var Scratchpad = {
    * Evaluate a string in the currently desired context, that is either the
    * chrome window or the tab content window object.
    *
    * @param string aString
    *        The script you want to evaluate.
    * @return Promise
    *         The promise for the script evaluation result.
    */
-  evalForContext: function SP_evaluateForContext(aString)
+  evaluate: function SP_evaluate(aString)
   {
-    let deferred = promise.defer();
+    let connection;
+    if (this.executionContext == SCRATCHPAD_CONTEXT_CONTENT) {
+      connection = ScratchpadTab.consoleFor(this.gBrowser.selectedTab);
+    }
+    else {
+      connection = ScratchpadWindow.consoleFor(this.browserWindow);
+    }
+
+    let evalOptions = { url: this.uniqueName };
+
+    return connection.then(({ debuggerClient, webConsoleClient }) => {
+      let deferred = promise.defer();
 
-    // This setTimeout is temporary and will be replaced by DebuggerClient
-    // execution in a future patch (bug 825039). The purpose for using
-    // setTimeout is to ensure there is no accidental dependency on the
-    // promise being resolved synchronously, which can cause subtle bugs.
-    setTimeout(() => {
-      let chrome = this.executionContext != SCRATCHPAD_CONTEXT_CONTENT;
-      let sandbox = chrome ? this.chromeSandbox : this.contentSandbox;
-      let name = this.uniqueName;
+      webConsoleClient.evaluateJS(aString, aResponse => {
+        this.debuggerClient = debuggerClient;
+        this.webConsoleClient = webConsoleClient;
+        if (aResponse.error) {
+          deferred.reject(aResponse);
+        }
+        else if (aResponse.exception) {
+          deferred.resolve([aString, aResponse]);
+        }
+        else {
+          deferred.resolve([aString, undefined, aResponse.result]);
+        }
+      }, evalOptions);
 
-      try {
-        let result = Cu.evalInSandbox(aString, sandbox, "1.8", name, 1);
-        deferred.resolve([aString, undefined, result]);
-      }
-      catch (ex) {
-        deferred.resolve([aString, ex]);
-      }
-    }, 0);
-
-    return deferred.promise;
-  },
+      return deferred.promise;
+    });
+   },
 
   /**
    * Execute the selected text (if any) or the entire editor content in the
    * current context.
    *
    * @return Promise
    *         The promise for the script evaluation result.
    */
   execute: function SP_execute()
   {
     let selection = this.selectedText || this.getText();
-    return this.evalForContext(selection);
+    return this.evaluate(selection);
   },
 
   /**
    * Execute the selected text (if any) or the entire editor content in the
    * current context.
    *
    * @return Promise
    *         The promise for the script evaluation result.
    */
   run: function SP_run()
   {
-    let execPromise = this.execute();
-    execPromise.then(([, aError, ]) => {
+    let deferred = promise.defer();
+    let reject = aReason => deferred.reject(aReason);
+
+    this.execute().then(([aString, aError, aResult]) => {
+      let resolve = () => deferred.resolve([aString, aError, aResult]);
+
       if (aError) {
-        this.writeAsErrorComment(aError);
+        this.writeAsErrorComment(aError.exception).then(resolve, reject);
       }
       else {
         this.deselect();
+        resolve();
       }
-    });
-    return execPromise;
+    }, reject);
+
+    return deferred.promise;
   },
 
   /**
    * Execute the selected text (if any) or the entire editor content in the
    * current context. If the result is primitive then it is written as a
    * comment. Otherwise, the resulting object is inspected up in the sidebar.
    *
    * @return Promise
@@ -458,21 +411,20 @@ var Scratchpad = {
   {
     let deferred = promise.defer();
     let reject = aReason => deferred.reject(aReason);
 
     this.execute().then(([aString, aError, aResult]) => {
       let resolve = () => deferred.resolve([aString, aError, aResult]);
 
       if (aError) {
-        this.writeAsErrorComment(aError);
-        resolve();
+        this.writeAsErrorComment(aError.exception).then(resolve, reject);
       }
-      else if (!isObject(aResult)) {
-        this.writeAsComment(aResult);
+      else if (VariablesView.isPrimitive({ value: aResult })) {
+        this.writeAsComment(aResult.type || aResult);
         resolve();
       }
       else {
         this.deselect();
         this.sidebar.open(aString, aResult).then(resolve, reject);
       }
     }, reject);
 
@@ -521,26 +473,49 @@ var Scratchpad = {
    * the selected text, or at the end of the editor content if there is no
    * selected text.
    *
    * @return Promise
    *         The promise for the script evaluation result.
    */
   display: function SP_display()
   {
-    let execPromise = this.execute();
-    execPromise.then(([aString, aError, aResult]) => {
+    let deferred = promise.defer();
+    let reject = aReason => deferred.reject(aReason);
+
+    this.execute().then(([aString, aError, aResult]) => {
+      let resolve = () => deferred.resolve([aString, aError, aResult]);
+
       if (aError) {
-        this.writeAsErrorComment(aError);
+        this.writeAsErrorComment(aError.exception).then(resolve, reject);
+      }
+      else if (VariablesView.isPrimitive({ value: aResult })) {
+        this.writeAsComment(aResult.type || aResult);
+        resolve();
       }
       else {
-        this.writeAsComment(aResult);
+        let gripClient = new GripClient(this.debuggerClient, aResult);
+        gripClient.getDisplayString(aResponse => {
+          if (aResponse.error) {
+            reject(aResponse);
+          }
+          else {
+            let string = aResponse.displayString;
+            if (string && string.type == "null") {
+              string = "Exception: " +
+                       this.strings.GetStringFromName("stringConversionFailed");
+            }
+            this.writeAsComment(string);
+            resolve();
+          }
+        });
       }
-    });
-    return execPromise;
+    }, reject);
+
+    return deferred.promise;
   },
 
   /**
    * Write out a value at the next line from the current insertion point.
    * The comment block will always be preceded by a newline character.
    * @param object aValue
    *        The Object to write out as a string
    */
@@ -558,38 +533,129 @@ var Scratchpad = {
     // Select the new comment.
     this.selectRange(insertionPoint, insertionPoint + newComment.length);
   },
 
   /**
    * Write out an error at the current insertion point as a block comment
    * @param object aValue
    *        The Error object to write out the message and stack trace
+   * @return Promise
+   *         The promise that indicates when writing the comment completes.
    */
   writeAsErrorComment: function SP_writeAsErrorComment(aError)
   {
-    let stack = "";
-    if (aError.stack) {
-      stack = aError.stack;
+    let deferred = promise.defer();
+
+    if (VariablesView.isPrimitive({ value: aError })) {
+      deferred.resolve(aError);
     }
-    else if (aError.fileName) {
-      if (aError.lineNumber) {
-        stack = "@" + aError.fileName + ":" + aError.lineNumber;
+    else {
+      let reject = aReason => deferred.reject(aReason);
+      let gripClient = new GripClient(this.debuggerClient, aError);
+
+      // Because properties on Error objects are lazily added, this roundabout
+      // way of getting all the properties is required, rather than simply
+      // using getPrototypeAndProperties. See bug 724768.
+      let names = ["message", "stack", "fileName", "lineNumber"];
+      let promises = names.map(aName => {
+        let deferred = promise.defer();
+
+        gripClient.getProperty(aName, aResponse => {
+          if (aResponse.error) {
+            deferred.reject(aResponse);
+          }
+          else {
+            deferred.resolve({
+              name: aName,
+              descriptor: aResponse.descriptor
+            });
+          }
+        });
+
+        return deferred.promise;
+      });
+
+      {
+        // We also need to use getPrototypeAndProperties to retrieve any
+        // safeGetterValues in case this is a DOM error.
+        let deferred = promise.defer();
+        gripClient.getPrototypeAndProperties(aResponse => {
+          if (aResponse.error) {
+            deferred.reject(aResponse);
+          }
+          else {
+            deferred.resolve(aResponse);
+          }
+        });
+        promises.push(deferred.promise);
       }
-      else {
-        stack = "@" + aError.fileName;
-      }
-    }
-    else if (aError.lineNumber) {
-      stack = "@" + aError.lineNumber;
+
+      promise.all(promises).then(aProperties => {
+        let error = {};
+        let safeGetters;
+
+        // Combine all the property descriptor/getter values into one object.
+        for (let property of aProperties) {
+          if (property.descriptor) {
+            error[property.name] = property.descriptor.value;
+          }
+          else if (property.safeGetterValues) {
+            safeGetters = property.safeGetterValues;
+          }
+        }
+
+        if (safeGetters) {
+          for (let key of Object.keys(safeGetters)) {
+            if (!error.hasOwnProperty(key)) {
+              error[key] = safeGetters[key].getterValue;
+            }
+          }
+        }
+
+        // Assemble the best possible stack we can given the properties we have.
+        let stack;
+        if (typeof error.stack == "string") {
+          stack = error.stack;
+        }
+        else if (typeof error.fileName == "number") {
+          stack = "@" + error.fileName;
+          if (typeof error.lineNumber == "number") {
+            stack += ":" + error.lineNumber;
+          }
+        }
+        else if (typeof error.lineNumber == "number") {
+          stack = "@" + error.lineNumber;
+        }
+
+        stack = stack ? "\n" + stack.replace(/\n$/, "") : "";
+
+        if (typeof error.message == "string") {
+          deferred.resolve(error.message + stack);
+        }
+        else {
+          gripClient.getDisplayString(aResult => {
+            if (aResult.error) {
+              deferred.reject(aResult);
+            }
+            else if (aResult.displayString.type == "null") {
+              deferred.resolve(stack);
+            }
+            else {
+              deferred.resolve(aResult.displayString + stack);
+            }
+          }, reject);
+        }
+      }, reject);
     }
 
-    let newComment = "Exception: " + ( aError.message || aError) + ( stack == "" ? stack : "\n" + stack.replace(/\n$/, "") );
-
-    this.writeAsComment(newComment);
+    return deferred.promise.then(aMessage => {
+      console.log(aMessage);
+      this.writeAsComment("Exception: " + aMessage);
+    });
   },
 
   // Menu Operations
 
   /**
    * Open a new Scratchpad window.
    *
    * @return nsIWindow
@@ -1069,17 +1135,17 @@ var Scratchpad = {
     this.browserWindow.HUDConsoleUI.toggleBrowserConsole();
   },
 
   /**
    * Open the Web Console.
    */
   openWebConsole: function SP_openWebConsole()
   {
-    let target = devtools.TargetFactory.forTab(this.gBrowser.selectedTab);
+    let target = TargetFactory.forTab(this.gBrowser.selectedTab);
     gDevTools.showToolbox(target, "webconsole");
     this.browserWindow.focus();
   },
 
   /**
    * Set the current execution context to be the active tab content window.
    */
   setContentContext: function SP_setContentContext()
@@ -1089,17 +1155,16 @@ var Scratchpad = {
     }
 
     let content = document.getElementById("sp-menu-content");
     document.getElementById("sp-menu-browser").removeAttribute("checked");
     document.getElementById("sp-cmd-reloadAndRun").removeAttribute("disabled");
     content.setAttribute("checked", true);
     this.executionContext = SCRATCHPAD_CONTEXT_CONTENT;
     this.notificationBox.removeAllNotifications(false);
-    this.resetContext();
   },
 
   /**
    * Set the current execution context to be the most recent chrome window.
    */
   setBrowserContext: function SP_setBrowserContext()
   {
     if (this.executionContext == SCRATCHPAD_CONTEXT_BROWSER) {
@@ -1115,29 +1180,16 @@ var Scratchpad = {
 
     this.executionContext = SCRATCHPAD_CONTEXT_BROWSER;
     this.notificationBox.appendNotification(
       this.strings.GetStringFromName("browserContext.notification"),
       SCRATCHPAD_CONTEXT_BROWSER,
       null,
       this.notificationBox.PRIORITY_WARNING_HIGH,
       null);
-    this.resetContext();
-  },
-
-  /**
-   * Reset the cached Cu.Sandbox object for the current context.
-   */
-  resetContext: function SP_resetContext()
-  {
-    this._chromeSandbox = null;
-    this._contentSandbox = null;
-    this._previousWindow = null;
-    this._previousBrowser = null;
-    this._previousLocation = null;
   },
 
   /**
    * Gets the ID of the inner window of the given DOM window object.
    *
    * @param nsIDOMWindow aWindow
    * @return integer
    *         the inner window ID
@@ -1224,19 +1276,17 @@ var Scratchpad = {
                                  this._onDirtyChanged);
     this.editor.focus();
     this.editor.setCaretOffset(this.editor.getCharCount());
     if (aState) {
       this.editor.dirty = !aState.saved;
     }
 
     this.initialized = true;
-
     this._triggerObservers("Ready");
-
     this.populateRecentFilesMenu();
     PreferenceObserver.init();
   },
 
   /**
    * Insert text at the current caret location.
    *
    * @param string aText
@@ -1294,30 +1344,34 @@ var Scratchpad = {
    * @param nsIDOMEvent aEvent
    */
   onUnload: function SP_onUnload(aEvent)
   {
     if (aEvent.target != document) {
       return;
     }
 
-    this.resetContext();
-
     // This event is created only after user uses 'reload and run' feature.
     if (this._reloadAndRunEvent) {
       this.gBrowser.selectedBrowser.removeEventListener("load",
           this._reloadAndRunEvent, true);
     }
 
     this.editor.removeEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
                                     this._onDirtyChanged);
     PreferenceObserver.uninit();
 
     this.editor.destroy();
     this.editor = null;
+    if (this._sidebar) {
+      this._sidebar.destroy();
+      this._sidebar = null;
+    }
+    this.webConsoleClient = null;
+    this.debuggerClient = null;
     this.initialized = false;
   },
 
   /**
    * Prompt to save scratchpad if it has unsaved changes.
    *
    * @param function aCallback
    *        Optional function you want to call when file is saved. The callback
@@ -1475,23 +1529,162 @@ var Scratchpad = {
   {
     let url = this.strings.GetStringFromName("help.openDocumentationPage");
     let newTab = this.gBrowser.addTab(url);
     this.browserWindow.focus();
     this.gBrowser.selectedTab = newTab;
   },
 };
 
+
+/**
+ * Represents the DebuggerClient connection to a specific tab as used by the
+ * Scratchpad.
+ *
+ * @param object aTab
+ *              The tab to connect to.
+ */
+function ScratchpadTab(aTab)
+{
+  this._tab = aTab;
+}
+
+let scratchpadTargets = new WeakMap();
+
+/**
+ * Returns the object containing the DebuggerClient and WebConsoleClient for a
+ * given tab or window.
+ *
+ * @param object aSubject
+ *        The tab or window to obtain the connection for.
+ * @return Promise
+ *         The promise for the connection information.
+ */
+ScratchpadTab.consoleFor = function consoleFor(aSubject)
+{
+  if (!scratchpadTargets.has(aSubject)) {
+    scratchpadTargets.set(aSubject, new this(aSubject));
+  }
+  return scratchpadTargets.get(aSubject).connect();
+};
+
+
+ScratchpadTab.prototype = {
+  /**
+   * The promise for the connection.
+   */
+  _connector: null,
+
+  /**
+   * Initialize a debugger client and connect it to the debugger server.
+   *
+   * @return Promise
+   *         The promise for the result of connecting to this tab or window.
+   */
+  connect: function ST_connect()
+  {
+    if (this._connector) {
+      return this._connector;
+    }
+
+    let deferred = promise.defer();
+    this._connector = deferred.promise;
+
+    let connectTimer = setTimeout(() => {
+      deferred.reject({
+        error: "timeout",
+        message: Scratchpad.strings.GetStringFromName("connectionTimeout"),
+      });
+    }, REMOTE_TIMEOUT);
+
+    deferred.promise.then(() => clearTimeout(connectTimer));
+
+    this._attach().then(aTarget => {
+      let consoleActor = aTarget.form.consoleActor;
+      let client = aTarget.client;
+      client.attachConsole(consoleActor, [], (aResponse, aWebConsoleClient) => {
+        if (aResponse.error) {
+          reportError("attachConsole", aResponse);
+          deferred.reject(aResponse);
+        }
+        else {
+          deferred.resolve({
+            webConsoleClient: aWebConsoleClient,
+            debuggerClient: client
+          });
+        }
+      });
+    });
+
+    return deferred.promise;
+  },
+
+  /**
+   * Attach to this tab.
+   *
+   * @return Promise
+   *         The promise for the TabTarget for this tab.
+   */
+  _attach: function ST__attach()
+  {
+    let target = TargetFactory.forTab(this._tab);
+    return target.makeRemote().then(() => target);
+  },
+};
+
+
+/**
+ * Represents the DebuggerClient connection to a specific window as used by the
+ * Scratchpad.
+ */
+function ScratchpadWindow() {}
+
+ScratchpadWindow.consoleFor = ScratchpadTab.consoleFor;
+
+ScratchpadWindow.prototype = Heritage.extend(ScratchpadTab.prototype, {
+  /**
+   * Attach to this window.
+   *
+   * @return Promise
+   *         The promise for the target for this window.
+   */
+  _attach: function SW__attach()
+  {
+    let deferred = promise.defer();
+
+    if (!DebuggerServer.initialized) {
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors();
+    }
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    client.connect(() => {
+      client.listTabs(aResponse => {
+        if (aResponse.error) {
+          reportError("listTabs", aResponse);
+          deferred.reject(aResponse);
+        }
+        else {
+          deferred.resolve({ form: aResponse, client: client });
+        }
+      });
+    });
+
+    return deferred.promise;
+  }
+});
+
+
 /**
  * Encapsulates management of the sidebar containing the VariablesView for
  * object inspection.
  */
 function ScratchpadSidebar(aScratchpad)
 {
-  let ToolSidebar = devtools.require("devtools/framework/sidebar").ToolSidebar;
+  let ToolSidebar = require("devtools/framework/sidebar").ToolSidebar;
   let tabbox = document.querySelector("#scratchpad-sidebar");
   this._sidebar = new ToolSidebar(tabbox, this, "scratchpad");
   this._scratchpad = aScratchpad;
 }
 
 ScratchpadSidebar.prototype = {
   /*
    * The ToolSidebar for this sidebar.
@@ -1521,24 +1714,40 @@ ScratchpadSidebar.prototype = {
    */
   open: function SS_open(aEvalString, aObject)
   {
     this.show();
 
     let deferred = promise.defer();
 
     let onTabReady = () => {
-      if (!this.variablesView) {
+      if (this.variablesView) {
+        this.variablesView.controller.releaseActors();
+      }
+      else {
         let window = this._sidebar.getWindowForTab("variablesview");
         let container = window.document.querySelector("#variables");
+
         this.variablesView = new VariablesView(container, {
           searchEnabled: true,
           searchPlaceholder: this._scratchpad.strings
                              .GetStringFromName("propertiesFilterPlaceholder")
         });
+
+        VariablesViewController.attach(this.variablesView, {
+          getGripClient: aGrip => {
+            return new GripClient(this._scratchpad.debuggerClient, aGrip);
+          },
+          getLongStringClient: aActor => {
+            return this._scratchpad.webConsoleClient.longString(aActor);
+          },
+          releaseActor: aActor => {
+            this._scratchpad.debuggerClient.release(aActor);
+          }
+        });
       }
       this._update(aObject).then(() => deferred.resolve());
     };
 
     if (this._sidebar.getCurrentTabID() == "variablesview") {
       onTabReady();
     }
     else {
@@ -1567,43 +1776,65 @@ ScratchpadSidebar.prototype = {
   {
     if (this.visible) {
       this.visible = false;
       this._sidebar.hide();
     }
   },
 
   /**
+   * Destroy the sidebar.
+   *
+   * @return Promise
+   *         The promise that resolves when the sidebar is destroyed.
+   */
+  destroy: function SS_destroy()
+  {
+    if (this.variablesView) {
+      this.variablesView.controller.releaseActors();
+      this.variablesView = null;
+    }
+    return this._sidebar.destroy();
+  },
+
+  /**
    * Update the object currently inspected by the sidebar.
    *
    * @param object aObject
    *        The object to inspect in the sidebar.
    * @return Promise
    *         A promise that resolves when the update completes.
    */
   _update: function SS__update(aObject)
   {
-    let deferred = promise.defer();
-
-    this.variablesView.rawObject = aObject;
+    let view = this.variablesView;
+    view.empty();
 
-    // In the future this will work on remote values (bug 825039).
-    setTimeout(() => deferred.resolve(), 0);
-    return deferred.promise;
+    let scope = view.addScope();
+    scope.expanded = true;
+    scope.locked = true;
+
+    let container = scope.addItem();
+    return view.controller.expand(container, aObject);
   }
 };
 
 
 /**
- * Check whether a value is non-primitive.
+ * Report an error coming over the remote debugger protocol.
+ *
+ * @param string aAction
+ *        The name of the action or method that failed.
+ * @param object aResponse
+ *        The response packet that contains the error.
  */
-function isObject(aValue)
+function reportError(aAction, aResponse)
 {
-  let type = typeof aValue;
-  return type == "object" ? aValue != null : type == "function";
+  Cu.reportError(aAction + " failed: " + aResponse.error + " " +
+                 aResponse.message);
 }
 
 
 /**
  * The PreferenceObserver listens for preference changes while Scratchpad is
  * running.
  */
 var PreferenceObserver = {
--- a/browser/devtools/scratchpad/scratchpad.xul
+++ b/browser/devtools/scratchpad/scratchpad.xul
@@ -44,17 +44,16 @@
 
   <command id="sp-cmd-close" oncommand="Scratchpad.close();"/>
   <command id="sp-cmd-run" oncommand="Scratchpad.run();"/>
   <command id="sp-cmd-inspect" oncommand="Scratchpad.inspect();"/>
   <command id="sp-cmd-display" oncommand="Scratchpad.display();"/>
   <command id="sp-cmd-contentContext" oncommand="Scratchpad.setContentContext();"/>
   <command id="sp-cmd-browserContext" oncommand="Scratchpad.setBrowserContext();" disabled="true"/>
   <command id="sp-cmd-reloadAndRun" oncommand="Scratchpad.reloadAndRun();"/>
-  <command id="sp-cmd-resetContext" oncommand="Scratchpad.resetContext();"/>
   <command id="sp-cmd-errorConsole" oncommand="Scratchpad.openErrorConsole();" disabled="true"/>
   <command id="sp-cmd-webConsole" oncommand="Scratchpad.openWebConsole();"/>
   <command id="sp-cmd-documentationLink" oncommand="Scratchpad.openDocumentationPage();"/>
   <command id="sp-cmd-hideSidebar" oncommand="Scratchpad.sidebar.hide();"/>
 </commandset>
 
 <keyset id="sourceEditorKeys"/>
 
@@ -200,20 +199,16 @@
                 key="sp-key-display"
                 command="sp-cmd-display"/>
       <menuseparator/>
       <menuitem id="sp-text-reloadAndRun"
                 label="&reloadAndRun.label;"
                 key="sp-key-reloadAndRun"
                 accesskey="&reloadAndRun.accesskey;"
                 command="sp-cmd-reloadAndRun"/>
-      <menuitem id="sp-text-resetContext"
-                label="&resetContext2.label;"
-                accesskey="&resetContext2.accesskey;"
-                command="sp-cmd-resetContext"/>
     </menupopup>
   </menu>
 
   <menu id="sp-environment-menu"
         label="&environmentMenu.label;"
         accesskey="&environmentMenu.accesskey;"
         hidden="true">
     <menupopup id="sp-menu-environment">
@@ -300,21 +295,16 @@
               accesskey="&inspect.accesskey;"
               key="sp-key-inspect"
               command="sp-cmd-inspect"/>
     <menuitem id="sp-text-display"
               label="&display.label;"
               accesskey="&display.accesskey;"
               key="sp-key-display"
               command="sp-cmd-display"/>
-    <menuseparator/>
-    <menuitem id="sp-text-resetContext"
-              label="&resetContext2.label;"
-              accesskey="&resetContext2.accesskey;"
-              command="sp-cmd-resetContext"/>
   </menupopup>
 </popupset>
 
 <notificationbox id="scratchpad-notificationbox" flex="1">
   <hbox flex="1">
     <vbox id="scratchpad-editor" flex="1"/>
     <splitter class="devtools-side-splitter"/>
     <tabbox id="scratchpad-sidebar" class="devtools-sidebar-tabs"
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -31,14 +31,15 @@ MOCHITEST_BROWSER_FILES = \
 		browser_scratchpad_bug714942_goto_line_ui.js \
 		browser_scratchpad_bug_650760_help_key.js \
 		browser_scratchpad_bug_651942_recent_files.js \
 		browser_scratchpad_bug756681_display_non_error_exceptions.js \
 		browser_scratchpad_bug_751744_revert_to_saved.js \
 		browser_scratchpad_bug740948_reload_and_run.js \
 		browser_scratchpad_bug_661762_wrong_window_focus.js \
 		browser_scratchpad_bug_644413_modeline.js \
+		browser_scratchpad_bug807924_cannot_convert_to_string.js \
 		head.js \
 
 # Disable test due to bug 807234 becoming basically permanent
 #		browser_scratchpad_bug_653427_confirm_close.js \
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug807924_cannot_convert_to_string.js
@@ -0,0 +1,33 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    openScratchpad(runTests);
+  }, true);
+
+  content.location = "data:text/html;charset=utf8,test display of values" +
+                     " which can't be converted to string in Scratchpad";
+}
+
+
+function runTests()
+{
+  let sp = gScratchpadWindow.Scratchpad;
+  sp.setText("Object.create(null);");
+
+  sp.display().then(([, , aResult]) => {
+    is(sp.getText(),
+       "Object.create(null);\n" +
+       "/*\nException: Cannot convert value to string.\n*/",
+       "'Cannot convert value to string' comment is shown");
+
+    finish();
+  });
+}
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_661762_wrong_window_focus.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_661762_wrong_window_focus.js
@@ -84,11 +84,11 @@ function testFocus(sw, hud) {
   // Sending messages to web console is an asynchronous operation. That's
   // why we have to setup an observer here.
   Services.obs.addObserver(onMessage, "web-console-message-created", false);
 
   sp.setText("console.log('foo');");
   sp.run().then(function ([selection, error, result]) {
     is(selection, "console.log('foo');", "selection is correct");
     is(error, undefined, "error is correct");
-    is(result, undefined, "result is correct");
+    is(result.type, "undefined", "result is correct");
   });
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
@@ -39,24 +39,16 @@ function runTests()
          "content menuitem is checked");
 
       isnot(chromeMenu.getAttribute("checked"), "true",
          "chrome menuitem is not checked");
 
       ok(!notificationBox.currentNotification,
          "there is no notification in content context");
 
-      let dsp = sp.contentSandbox.__SCRATCHPAD__;
-
-      ok(sp.contentSandbox.__SCRATCHPAD__,
-          "there is a variable named __SCRATCHPAD__");
-
-      ok(sp.contentSandbox.__SCRATCHPAD__.editor,
-          "scratchpad is actually an instance of Scratchpad");
-
       sp.setText("window.foobarBug636725 = 'aloha';");
 
       ok(!content.wrappedJSObject.foobarBug636725,
          "no content.foobarBug636725");
     },
     then: function() {
       is(content.wrappedJSObject.foobarBug636725, "aloha",
          "content.foobarBug636725 has been set");
@@ -71,22 +63,16 @@ function runTests()
          "executionContext is chrome");
 
       is(chromeMenu.getAttribute("checked"), "true",
          "chrome menuitem is checked");
 
       isnot(contentMenu.getAttribute("checked"), "true",
          "content menuitem is not checked");
 
-      ok(sp.chromeSandbox.__SCRATCHPAD__,
-        "there is a variable named __SCRATCHPAD__");
-
-      ok(sp.chromeSandbox.__SCRATCHPAD__.editor,
-          "scratchpad is actually an instance of Scratchpad");
-
       ok(notificationBox.currentNotification,
          "there is a notification in browser context");
 
       sp.setText("2'", 31, 32);
 
       is(sp.getText(), "window.foobarBug636725 = 'aloha2';",
          "setText() worked");
     },
@@ -102,17 +88,17 @@ function runTests()
     method: "run",
     prepare: function() {
       sp.setText("gBrowser", 7);
 
       is(sp.getText(), "window.gBrowser",
          "setText() worked with no end for the replace range");
     },
     then: function([, , result]) {
-      is(typeof result.addTab, "function",
+      is(result.class, "XULElement",
          "chrome context has access to chrome objects");
     }
   },
   {
     method: "run",
     prepare: function() {
       // Check that the sandbox is cached.
       sp.setText("typeof foobarBug636725cache;");
@@ -130,27 +116,16 @@ function runTests()
     then: function([, , result]) {
       is(result, "string",
          "global variable exists across two different executions");
     }
   },
   {
     method: "run",
     prepare: function() {
-      sp.resetContext();
-      sp.setText("typeof foobarBug636725cache;");
-    },
-    then: function([, , result]) {
-      is(result, "undefined",
-         "global variable no longer exists after calling resetContext()");
-    }
-  },
-  {
-    method: "run",
-    prepare: function() {
       sp.setText("var foobarBug636725cache2 = 'foo';" +
                  "typeof foobarBug636725cache2;");
     },
     then: function([, , result]) {
       is(result, "string",
          "global variable exists across two different executions");
     }
   },
@@ -165,10 +140,15 @@ function runTests()
       sp.setText("typeof foobarBug636725cache2;");
     },
     then: function([, , result]) {
       is(result, "undefined",
          "global variable no longer exists after changing the context");
     }
   }];
 
-  runAsyncCallbackTests(sp, tests).then(finish);
+  runAsyncCallbackTests(sp, tests).then(() => {
+    sp.setBrowserContext();
+    sp.setText("delete foobarBug636725cache;" +
+               "delete foobarBug636725cache2;");
+    sp.run().then(finish);
+  });
 }
\ No newline at end of file
--- a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
@@ -24,17 +24,16 @@ function runTests()
   let methodsAndItems = {
     "sp-menu-newscratchpad": "openScratchpad",
     "sp-menu-open": "openFile",
     "sp-menu-save": "saveFile",
     "sp-menu-saveas": "saveFileAs",
     "sp-text-run": "run",
     "sp-text-inspect": "inspect",
     "sp-text-display": "display",
-    "sp-text-resetContext": "resetContext",
     "sp-menu-content": "setContentContext",
     "sp-menu-browser": "setBrowserContext",
   };
 
   let lastMethodCalled = null;
   sp.__noSuchMethod__ = function(aMethodName) {
     lastMethodCalled = aMethodName;
   };
--- a/browser/devtools/shared/widgets/SideMenuWidget.jsm
+++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm
@@ -20,31 +20,38 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 this.EXPORTED_SYMBOLS = ["SideMenuWidget"];
 
 /**
  * A simple side menu, with the ability of grouping menu items.
  * This widget should be used in tandem with the WidgetMethods in ViewHelpers.jsm
  *
  * @param nsIDOMNode aNode
  *        The element associated with the widget.
- * @param boolean aShowArrows
- *        Specifies if items in this container should display horizontal arrows.
+ * @param Object aOptions
+ *        - showArrows: Specifies if items in this container should display
+ *          horizontal arrows.
+ *        - showCheckboxes: Specifies if items in this container should display
+ *          checkboxes.
  */
-this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) {
+this.SideMenuWidget = function SideMenuWidget(aNode, aOptions={}) {
   this.document = aNode.ownerDocument;
   this.window = this.document.defaultView;
   this._parent = aNode;
-  this._showArrows = aShowArrows;
+
+  let { showArrows, showCheckboxes } = aOptions;
+  this._showArrows = showArrows || false;
+  this._showCheckboxes = showCheckboxes || false;
 
   // Create an internal scrollbox container.
   this._list = this.document.createElement("scrollbox");
   this._list.className = "side-menu-widget-container";
   this._list.setAttribute("flex", "1");
   this._list.setAttribute("orient", "vertical");
-  this._list.setAttribute("with-arrow", aShowArrows);
+  this._list.setAttribute("with-arrow", showArrows);
+  this._list.setAttribute("with-checkboxes", showCheckboxes);
   this._list.setAttribute("tabindex", "0");
   this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
   this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
   this._parent.appendChild(this._list);
   this._boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
 
   // Menu items can optionally be grouped.
   this._groupsByName = new Map(); // Can't use a WeakMap because keys are strings.
@@ -86,20 +93,22 @@ SideMenuWidget.prototype = {
    * @param number aIndex
    *        The position in the container intended for this item.
    * @param string | nsIDOMNode aContents
    *        The string or node displayed in the container.
    * @param string aTooltip [optional]
    *        A tooltip attribute for the displayed item.
    * @param string aGroup [optional]
    *        The group to place the displayed item into.
+   * @param Object aAttachment [optional]
+   *        Extra data for the user.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "") {
+  insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "", aAttachment={}) {
     aTooltip = NetworkHelper.convertToUnicode(unescape(aTooltip));
     aGroup = NetworkHelper.convertToUnicode(unescape(aGroup));
 
     // Invalidate any notices set on this widget.
     this.removeAttribute("notice");
 
     // Maintaining scroll position at the bottom when a new item is inserted
     // depends on several factors (the order of testing is important to avoid
@@ -110,17 +119,17 @@ SideMenuWidget.prototype = {
       // 2. There shouldn't currently be any selected item in the list.
       !this._selectedItem &&
       // 3. The new item should be appended at the end of the list.
       (aIndex < 0 || aIndex >= this._orderedMenuElementsArray.length) &&
       // 4. The list should already be scrolled at the bottom.
       (this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight);
 
     let group = this._getMenuGroupForName(aGroup);
-    let item = this._getMenuItemForGroup(group, aContents, aTooltip);
+    let item = this._getMenuItemForGroup(group, aContents, aTooltip, aAttachment);
     let element = item.insertSelfAt(aIndex);
 
     if (this.maintainSelectionVisible) {
       this.ensureSelectionIsVisible({ withGroup: true, delayed: true });
     }
     if (maintainScrollAtBottom) {
       this._list.scrollTop = this._list.scrollHeight;
     }
@@ -392,24 +401,27 @@ SideMenuWidget.prototype = {
    * @see SideMenuWidget.prototype._getMenuGroupForName
    *
    * @param SideMenuGroup aGroup
    *        The group to contain the menu item.
    * @param string | nsIDOMNode aContents
    *        The string or node displayed in the container.
    * @param string aTooltip [optional]
    *        A tooltip attribute for the displayed item.
+   * @param object aAttachment [optional]
+   *        The attachement object.
    */
-  _getMenuItemForGroup: function(aGroup, aContents, aTooltip) {
-    return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows);
+  _getMenuItemForGroup: function(aGroup, aContents, aTooltip, aAttachment) {
+    return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows, this._showCheckboxes, aAttachment);
   },
 
   window: null,
   document: null,
   _showArrows: false,
+  _showCheckboxes: false,
   _parent: null,
   _list: null,
   _boxObject: null,
   _selectedItem: null,
   _groupsByName: null,
   _orderedGroupElementsArray: null,
   _orderedMenuElementsArray: null,
   _ensureVisibleTimeout: null,
@@ -522,38 +534,63 @@ SideMenuGroup.prototype = {
  * @param SideMenuGroup aGroup
  *        The group to contain this menu item.
  * @param string aTooltip [optional]
  *        A tooltip attribute for the displayed item.
  * @param string | nsIDOMNode aContents
  *        The string or node displayed in the container.
  * @param boolean aArrowFlag
  *        True if a horizontal arrow should be shown.
+ * @param boolean aCheckboxFlag
+ *        True if a checkbox should be shown.
+ * @param object aAttachment [optional]
+ *        The attachment object.
  */
-function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) {
+function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag, aCheckboxFlag, aAttachment={}) {
   this.document = aGroup.document;
   this.window = aGroup.window;
   this.ownerView = aGroup;
 
-  // Show a horizontal arrow towards the content.
-  if (aArrowFlag) {
+  let makeCheckbox = () => {
+    let checkbox = this.document.createElement("checkbox");
+    checkbox.className = "side-menu-widget-item-checkbox";
+    checkbox.setAttribute("checked", aAttachment.checkboxState);
+    checkbox.setAttribute("tooltiptext", aAttachment.checkboxTooltip);
+    checkbox.addEventListener("command", function () {
+      ViewHelpers.dispatchEvent(checkbox, "check", {
+        checked: checkbox.checked,
+      });
+    }, false);
+    return checkbox;
+  };
+
+  if (aArrowFlag || aCheckboxFlag) {
     let container = this._container = this.document.createElement("hbox");
     container.className = "side-menu-widget-item";
     container.setAttribute("tooltiptext", aTooltip);
 
     let target = this._target = this.document.createElement("vbox");
     target.className = "side-menu-widget-item-contents";
 
-    let arrow = this._arrow = this.document.createElement("hbox");
-    arrow.className = "side-menu-widget-item-arrow";
+    // Show a checkbox before the content.
+    if (aCheckboxFlag) {
+      let checkbox = this._checkbox = makeCheckbox();
+      container.appendChild(checkbox);
+    }
 
     container.appendChild(target);
-    container.appendChild(arrow);
+
+    // Show a horizontal arrow towards the content.
+    if (aArrowFlag) {
+      let arrow = this._arrow = this.document.createElement("hbox");
+      arrow.className = "side-menu-widget-item-arrow";
+      container.appendChild(arrow);
+    }
   }
-  // Skip a few redundant nodes when no horizontal arrow is shown.
+  // Skip a few redundant nodes when no horizontal arrow or checkbox is shown.
   else {
     let target = this._target = this._container = this.document.createElement("hbox");
     target.className = "side-menu-widget-item side-menu-widget-item-contents";
   }
 
   this._target.setAttribute("flex", "1");
   this.contents = aContents;
 }
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -6,16 +6,17 @@
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const PANE_APPEARANCE_DELAY = 50;
 const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
+const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]);
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["Heritage", "ViewHelpers", "WidgetMethods"];
 
 /**
  * Inheritance helpers from the addon SDK's core/heritage.
@@ -604,17 +605,17 @@ this.WidgetMethods = {
    *          - description: an optional description of the item
    * @param object aOptions [optional]
    *        Additional options or flags supported by this operation:
    *          - staged: true to stage the item to be appended later
    *          - index: specifies on which position should the item be appended
    *          - relaxed: true if this container should allow dupes & degenerates
    *          - attachment: some attached primitive/object for the item
    *          - attributes: a batch of attributes set to the displayed element
-   *          - finalize: function invoked when the item is removed
+   *          - finalize: function invokde when the item is removed
    * @return Item
    *         The item associated with the displayed element if an unstaged push,
    *         undefined if the item was staged for a later commit.
    */
   push: function(aContents, aOptions = {}) {
     let item = new Item(this, aOptions.attachment, aContents);
 
     // Batch the item to be added later.
@@ -1112,26 +1113,31 @@ this.WidgetMethods = {
    *        Either "advanceFocus" or "rewindFocus".
    * @return boolean
    *         False if the focus went out of bounds and the first or last item
    *         in this container was focused instead.
    */
   _focusChange: function(aDirection) {
     let commandDispatcher = this._commandDispatcher;
     let prevFocusedElement = commandDispatcher.focusedElement;
+    let currFocusedElement;
 
-    commandDispatcher.suppressFocusScroll = true;
-    commandDispatcher[aDirection]();
+    do {
+      commandDispatcher.suppressFocusScroll = true;
+      commandDispatcher[aDirection]();
+      currFocusedElement = commandDispatcher.focusedElement;
 
-    // Make sure the newly focused item is a part of this container.
-    // If the focus goes out of bounds, revert the previously focused item.
-    if (!this.getItemForElement(commandDispatcher.focusedElement)) {
-      prevFocusedElement.focus();
-      return false;
-    }
+      // Make sure the newly focused item is a part of this container. If the
+      // focus goes out of bounds, revert the previously focused item.
+      if (!this.getItemForElement(currFocusedElement)) {
+        prevFocusedElement.focus();
+        return false;
+      }
+    } while (!WIDGET_FOCUSABLE_NODES.has(currFocusedElement.tagName));
+
     // Focus remained within bounds.
     return true;
   },
 
   /**
    * Gets the command dispatcher instance associated with this container's DOM.
    * If there are no items displayed in this container, null is returned.
    * @return nsIDOMXULCommandDispatcher | null
@@ -1203,17 +1209,20 @@ this.WidgetMethods = {
    *
    * @param nsIDOMNode aElement
    *        The element used to identify the item.
    * @return Item
    *         The matched item, or null if nothing is found.
    */
   getItemForElement: function(aElement) {
     while (aElement) {
-      let item = this._itemsByElement.get(aElement);
+      let item =
+        this._itemsByElement.get(aElement) ||
+        this._itemsByElement.get(aElement.nextElementSibling) ||
+        this._itemsByElement.get(aElement.previousElementSibling);
       if (item) {
         return item;
       }
       aElement = aElement.parentNode;
     }
     return null;
   },
 
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -60,16 +60,21 @@ noGlobalsText=No globals
 # LOCALIZATION NOTE (noMatchingGlobalsText): The text to display in the
 # menulist when there are no matching chrome globals after filtering.
 noMatchingGlobalsText=No matching globals
 
 # LOCALIZATION NOTE (noSourcesText): The text to display in the sources menu
 # when there are no scripts.
 noSourcesText=This page has no sources.
 
+# LOCALIZATION NOTE (blackBoxCheckboxTooltip) = The tooltip text to display when
+# the user hovers over the checkbox used to toggle black boxing its associated
+# source.
+blackBoxCheckboxTooltip=Toggle black boxing
+
 # LOCALIZATION NOTE (noMatchingSourcesText): The text to display in the
 # sources menu when there are no matching scripts after filtering.
 noMatchingSourcesText=No matching sources.
 
 # LOCALIZATION NOTE (noMatchingStringsText): The text to display in the
 # global search results when there are no matching strings after filtering.
 noMatchingStringsText=No matches found
 
--- a/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/scratchpad.properties
@@ -80,8 +80,16 @@ help.openDocumentationPage=https://devel
 
 # LOCALIZATION NOTE (fileExists.notification): This is the message displayed
 # over the top of the the editor when a file does not exist.
 fileNoLongerExists.notification=This file no longer exists.
 
 # LOCALIZATION NOTE (propertiesFilterPlaceholder): this is the text that
 # appears in the filter text box for the properties view container.
 propertiesFilterPlaceholder=Filter properties
+
+# LOCALIZATION NOTE (stringConversionFailed): Happens when a value cannot be
+# converted to a string for inspection.
+stringConversionFailed=Cannot convert value to string.
+
+# LOCALIZATION NOTE (connectionTimeout): message displayed when the Remote Scratchpad
+# fails to connect to the server due to a timeout.
+connectionTimeout=Connection timeout. Check the Error Console on both ends for potential error messages. Reopen the Scratchpad to try again.
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -12,16 +12,38 @@
 #sources-pane {
   min-width: 50px;
 }
 
 #sources-pane + .devtools-side-splitter {
   -moz-border-start-color: transparent;
 }
 
+.side-menu-widget-item-checkbox {
+ -moz-appearance: none;
+  padding: 0;
+  margin: 0 -4px 0 4px;
+}
+
+.side-menu-widget-item-checkbox > .checkbox-check {
+  -moz-appearance: none;
+  background: none;
+  background-image: url(itemToggle.png);
+  background-repeat: no-repeat;
+  background-clip: content-box;
+  background-position: -24px 0;
+  width: 24px;
+  height: 24px;
+  border: 0;
+}
+
+.side-menu-widget-item-checkbox[checked] > .checkbox-check {
+  background-position: 0 0;
+}
+
 /* ListWidget items */
 
 .list-widget-item {
   padding: 2px;
 }
 
 .list-widget-item:not(.selected):not(.empty):hover {
   background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -14,16 +14,38 @@
 #sources-pane {
   min-width: 50px;
 }
 
 #sources-pane + .devtools-side-splitter {
   -moz-border-start-color: transparent;
 }
 
+.side-menu-widget-item-checkbox {
+ -moz-appearance: none;
+  padding: 0;
+  margin: 0 -4px 0 4px;
+}
+
+.side-menu-widget-item-checkbox > .checkbox-check {
+  -moz-appearance: none;
+  background: none;
+  background-image: url(itemToggle.png);
+  background-repeat: no-repeat;
+  background-clip: content-box;
+  background-position: -24px 0;
+  width: 24px;
+  height: 24px;
+  border: 0;
+}
+
+.side-menu-widget-item-checkbox[checked] > .checkbox-check {
+  background-position: 0 0;
+}
+
 /* ListWidget items */
 
 .list-widget-item {
   padding: 2px;
 }
 
 .list-widget-item:not(.selected):not(.empty):hover {
   background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -12,16 +12,38 @@
 #sources-pane {
   min-width: 50px;
 }
 
 #sources-pane + .devtools-side-splitter {
   -moz-border-start-color: transparent;
 }
 
+.side-menu-widget-item-checkbox {
+ -moz-appearance: none;
+  padding: 0;
+  margin: 0 -4px 0 4px;
+}
+
+.side-menu-widget-item-checkbox > .checkbox-check {
+  -moz-appearance: none;
+  background: none;
+  background-image: url(itemToggle.png);
+  background-repeat: no-repeat;
+  background-clip: content-box;
+  background-position: -24px 0;
+  width: 24px;
+  height: 24px;
+  border: 0;
+}
+
+.side-menu-widget-item-checkbox[checked] > .checkbox-check {
+  background-position: 0 0;
+}
+
 /* ListWidget items */
 
 .list-widget-item {
   padding: 2px;
 }
 
 .list-widget-item:not(.selected):not(.empty):hover {
   background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testBug895774.js
@@ -0,0 +1,14 @@
+var g1 = newGlobal();
+var g2 = newGlobal();
+g1.eval("function f1() { debugger; evaluate('debugger', {newContext:true}) }");
+g2.eval("function f2() { f1(); assertEq(Number(this), 42) }");
+g2.f1 = g1.f1;
+
+var dbg = new Debugger(g1,g2);
+dbg.onDebuggerStatement = function(frame) {
+    var target = frame.older;
+    dbg.onDebuggerStatement = function(frame) {
+        assertEq(Number(target.this.unsafeDereference()), 42);
+    }
+}
+g2.f2.call(42);
--- a/js/src/jsdbgapi.cpp
+++ b/js/src/jsdbgapi.cpp
@@ -1055,17 +1055,17 @@ FormatFrame(JSContext *cx, const NonBuil
         JSAbstractFramePtr frame(Jsvalify(iter.abstractFramePtr()));
         callObj = frame.callObject(cx);
         if (callObj)
             callProps.fetch(callObj);
     }
 
     RootedValue thisVal(cx);
     AutoPropertyDescArray thisProps(cx);
-    if (iter.computeThis()) {
+    if (iter.computeThis(cx)) {
         thisVal = iter.thisv();
         if (showThisProps && !thisVal.isPrimitive())
             thisProps.fetch(&thisVal.toObject());
     }
 
     // print the frame number and function name
     if (funname) {
         JSAutoByteString funbytes;
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -3815,17 +3815,17 @@ DebuggerFrame_getConstructing(JSContext 
 
 static JSBool
 DebuggerFrame_getThis(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_FRAME(cx, argc, vp, "get this", args, thisobj, iter);
     RootedValue thisv(cx);
     {
         AutoCompartment ac(cx, iter.scopeChain());
-        if (!iter.computeThis())
+        if (!iter.computeThis(cx))
             return false;
         thisv = iter.thisv();
     }
     if (!Debugger::fromChildJSObject(thisobj)->wrapDebuggeeValue(cx, &thisv))
         return false;
     args.rval().set(thisv);
     return true;
 }
@@ -4215,17 +4215,17 @@ DebuggerGenericEval(JSContext *cx, const
         ac.construct(cx, iter->scopeChain());
     else
         ac.construct(cx, scope);
 
     RootedValue thisv(cx);
     Rooted<Env *> env(cx);
     if (iter) {
         /* ExecuteInEnv requires 'fp' to have a computed 'this" value. */
-        if (!iter->computeThis())
+        if (!iter->computeThis(cx))
             return false;
         thisv = iter->thisv();
         env = GetDebugScopeForFrame(cx, iter->abstractFramePtr());
         if (!env)
             return false;
     } else {
         thisv = ObjectValue(*scope);
         env = scope;
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -1092,22 +1092,22 @@ ScriptFrameIter::argsObj() const
 #endif
       case SCRIPTED:
         return interpFrame()->argsObj();
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected state");
 }
 
 bool
-ScriptFrameIter::computeThis() const
+ScriptFrameIter::computeThis(JSContext *cx) const
 {
     JS_ASSERT(!done());
     if (!isIon()) {
-        JS_ASSERT(data_.cx_);
-        return ComputeThis(data_.cx_, abstractFramePtr());
+        assertSameCompartment(cx, scopeChain());
+        return ComputeThis(cx, abstractFramePtr());
     }
     return true;
 }
 
 Value
 ScriptFrameIter::thisv() const
 {
     switch (data_.state_) {
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1515,17 +1515,17 @@ class ScriptFrameIter
 
     JSObject   *scopeChain() const;
     CallObject &callObj() const;
 
     bool        hasArgsObj() const;
     ArgumentsObject &argsObj() const;
 
     // Ensure that thisv is correct, see ComputeThis.
-    bool        computeThis() const;
+    bool        computeThis(JSContext *cx) const;
     Value       thisv() const;
 
     Value       returnValue() const;
     void        setReturnValue(const Value &v);
 
     JSFunction *maybeCallee() const {
         return isFunctionFrame() ? callee() : NULL;
     }
--- a/netwerk/base/src/BackgroundFileSaver.cpp
+++ b/netwerk/base/src/BackgroundFileSaver.cpp
@@ -489,17 +489,17 @@ BackgroundFileSaver::ProcessStateChange(
       mDigestContext =
         PK11_CreateDigestContext(static_cast<SECOidTag>(SEC_OID_SHA256));
       NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY);
     }
   }
 
   // When we are requested to append to an existing file, we should read the
   // existing data and ensure we include it as part of the final hash.
-  if (append && !isContinuation) {
+  if (mDigestContext && append && !isContinuation) {
     nsCOMPtr<nsIInputStream> inputStream;
     rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream),
                                     mActualTarget,
                                     PR_RDONLY | nsIFile::OS_READAHEAD);
     if (rv != NS_ERROR_FILE_NOT_FOUND) {
       NS_ENSURE_SUCCESS(rv, rv);
 
       char buffer[BUFFERED_IO_SIZE];
--- a/netwerk/test/unit/test_backgroundfilesaver.js
+++ b/netwerk/test/unit/test_backgroundfilesaver.js
@@ -454,16 +454,44 @@ add_task(function test_setTarget_multipl
 
   // Verify results and clean up.
   do_check_false(getTempFile(TEST_FILE_NAME_2).exists());
   do_check_false(getTempFile(TEST_FILE_NAME_3).exists());
   yield promiseVerifyContents(destFile, TEST_DATA_SHORT);
   destFile.remove(false);
 });
 
+add_task(function test_enableAppend()
+{
+  // This test checks append mode with hashing disabled.
+  let destFile = getTempFile(TEST_FILE_NAME_1);
+
+  // Test the case where the file does not already exists first, then the case
+  // where the file already exists.
+  for (let i = 0; i < 2; i++) {
+    let saver = new BackgroundFileSaverOutputStream();
+    saver.enableAppend();
+    let completionPromise = promiseSaverComplete(saver);
+
+    saver.setTarget(destFile, false);
+    yield promiseCopyToSaver(TEST_DATA_LONG, saver, true);
+
+    saver.finish(Cr.NS_OK);
+    yield completionPromise;
+
+    // Verify results.
+    let expectedContents = (i == 0 ? TEST_DATA_LONG
+                                   : TEST_DATA_LONG + TEST_DATA_LONG);
+    yield promiseVerifyContents(destFile, expectedContents);
+  }
+
+  // Clean up.
+  destFile.remove(false);
+});
+
 add_task(function test_enableAppend_hash()
 {
   // This test checks append mode, also verifying that the computed hash
   // includes the contents of the existing data.
   let destFile = getTempFile(TEST_FILE_NAME_1);
 
   // Test the case where the file does not already exists first, then the case
   // where the file already exists.
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3335,16 +3335,28 @@
     "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
   },
   "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPE_MS": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
   },
+  "DEVTOOLS_DEBUGGER_RDP_REMOTE_DISPLAYSTRING_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'displayString' request to go round trip."
+  },
+  "DEVTOOLS_DEBUGGER_RDP_LOCAL_DISPLAYSTRING_MS": {
+    "kind": "exponential",
+    "high": "10000",
+    "n_buckets": "1000",
+    "description": "The time (in milliseconds) that it took a 'displayString' request to go round trip."
+  },
   "DEVTOOLS_DEBUGGER_RDP_LOCAL_SUBSTRING_MS": {
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took a 'substring' request to go round trip."
   },
   "DEVTOOLS_DEBUGGER_RDP_REMOTE_SUBSTRING_MS": {
     "kind": "exponential",
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -1674,16 +1674,27 @@ GripClient.prototype = {
    *
    * @param aOnResponse function Called with the request's response.
    */
   getPrototype: DebuggerClient.requester({
     type: "prototype"
   }, {
     telemetry: "PROTOTYPE"
   }),
+
+  /**
+   * Request the display string of the object.
+   *
+   * @param aOnResponse function Called with the request's response.
+   */
+  getDisplayString: DebuggerClient.requester({
+    type: "displayString"
+  }, {
+    telemetry: "DISPLAYSTRING"
+  }),
 };
 
 /**
  * A LongStringClient provides a way to access "very long" strings from the
  * debugger server.
  *
  * @param aClient DebuggerClient
  *        The debugger client parent.
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -1576,16 +1576,24 @@ var WalkerActor = protocol.ActorClass({
         mutation.added = addedActors;
       }
       this.queueMutation(mutation);
     }
   },
 
   onFrameLoad: function(window) {
     let frame = window.frameElement;
+    if (!frame && !this.rootDoc) {
+      this.rootDoc = window.document;
+      this.rootNode = this.document();
+      this.queueMutation({
+        type: "newRoot",
+        target: this.rootNode.form()
+      });
+    }
     let frameActor = this._refMap.get(frame);
     if (!frameActor) {
       return;
     }
 
     this.queueMutation({
       type: "frameLoad",
       target: frameActor.actorID,
@@ -1636,16 +1644,21 @@ var WalkerActor = protocol.ActorClass({
     }
 
     let doc = window.document;
     let documentActor = this._refMap.get(doc);
     if (!documentActor) {
       return;
     }
 
+    if (this.rootDoc === doc) {
+      this.rootDoc = null;
+      this.rootNode = null;
+    }
+
     this.queueMutation({
       type: "documentUnload",
       target: documentActor.actorID
     });
 
     let walker = documentWalker(doc);
     let parentNode = walker.parentNode();
     if (parentNode) {
@@ -1668,29 +1681,41 @@ var WalkerActor = protocol.ActorClass({
 /**
  * Client side of the DOM walker.
  */
 var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
   // Set to true if cleanup should be requested after every mutation list.
   autoCleanup: true,
 
   initialize: function(client, form) {
+    this._rootNodeDeferred = promise.defer();
     protocol.Front.prototype.initialize.call(this, client, form);
     this._orphaned = new Set();
     this._retainedOrphans = new Set();
   },
 
   destroy: function() {
     protocol.Front.prototype.destroy.call(this);
   },
 
   // Update the object given a form representation off the wire.
   form: function(json) {
-    this.actorID = json.actorID;
+    this.actorID = json.actor;
     this.rootNode = types.getType("domnode").read(json.root, this);
+    this._rootNodeDeferred.resolve(this.rootNode);
+  },
+
+  /**
+   * Clients can use walker.rootNode to get the current root node of the
+   * walker, but during a reload the root node might be null.  This
+   * method returns a promise that will resolve to the root node when it is
+   * set.
+   */
+  getRootNode: function() {
+    return this._rootNodeDeferred.promise;
   },
 
   /**
    * When reading an actor form off the wire, we want to hook it up to its
    * parent front.  The protocol guarantees that the parent will be seen
    * by the client in either a previous or the current request.
    * So if we've already seen this parent return it, otherwise create
    * a bare-bones stand-in node.  The stand-in node will be updated
@@ -1788,18 +1813,29 @@ var WalkerFront = exports.WalkerFront = 
   /**
    * Get any unprocessed mutation records and process them.
    */
   getMutations: protocol.custom(function(options={}) {
     return this._getMutations(options).then(mutations => {
       let emitMutations = [];
       for (let change of mutations) {
         // The target is only an actorID, get the associated front.
-        let targetID = change.target;
-        let targetFront = this.get(targetID);
+        let targetID;
+        let targetFront;
+
+        if (change.type === "newRoot") {
+          this.rootNode = types.getType("domnode").read(change.target, this);
+          this._rootNodeDeferred.resolve(this.rootNode);
+          targetID = this.rootNode.actorID;
+          targetFront = this.rootNode;
+        } else {
+          targetID = change.target;
+          targetFront = this.get(targetID);
+        }
+
         if (!targetFront) {
           console.trace("Got a mutation for an unexpected actor: " + targetID + ", please file a bug on bugzilla.mozilla.org!");
           continue;
         }
 
         let emittedMutation = object.merge(change, { target: targetFront });
 
         if (change.type === "childList") {
@@ -1843,16 +1879,21 @@ var WalkerFront = exports.WalkerFront = 
           // document children, because we should have gotten a documentUnload
           // first.
           for (let child of targetFront.treeChildren()) {
             if (child.nodeType === Ci.nsIDOMNode.DOCUMENT_NODE) {
               console.trace("Got an unexpected frameLoad in the inspector, please file a bug on bugzilla.mozilla.org!");
             }
           }
         } else if (change.type === "documentUnload") {
+          if (targetFront === this.rootNode) {
+            this.rootNode = null;
+            this._rootNodeDeferred = promise.defer();
+          }
+
           // We try to give fronts instead of actorIDs, but these fronts need
           // to be destroyed now.
           emittedMutation.target = targetFront.actorID;
           emittedMutation.targetParent = targetFront.parentNode();
 
           // Release the document node and all of its children, even retained.
           this._releaseFront(targetFront, true);
         } else if (change.type === "unretained") {
@@ -1986,33 +2027,38 @@ var InspectorActor = protocol.ActorClass
       return tabActor.browser;
     } else if (tabActor.browser instanceof Ci.nsIDOMElement) {
       return tabActor.browser.contentWindow;
     }
     return null;
   },
 
   getWalker: method(function(options={}) {
+    if (this._walkerPromise) {
+      return this._walkerPromise;
+    }
+
     let deferred = promise.defer();
+    this._walkerPromise = deferred.promise;
 
     let window = this.window;
 
     var domReady = () => {
       let tabActor = this.tabActor;
       window.removeEventListener("DOMContentLoaded", domReady, true);
       deferred.resolve(WalkerActor(this.conn, window.document, tabActor._tabbrowser, options));
     };
 
     if (window.document.readyState === "loading") {
       window.addEventListener("DOMContentLoaded", domReady, true);
     } else {
       domReady();
     }
 
-    return deferred.promise;
+    return this._walkerPromise;
   }, {
     request: {},
     response: {
       walker: RetVal("domwalker")
     }
   })
 });
 
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -1962,16 +1962,52 @@ ObjectActor.prototype = {
                message: "no property name was specified" };
     }
 
     return { from: this.actorID,
              descriptor: this._propertyDescriptor(aRequest.name) };
   },
 
   /**
+   * Handle a protocol request to provide the display string for the object.
+   *
+   * @param aRequest object
+   *        The protocol request object.
+   */
+  onDisplayString: function OA_onDisplayString(aRequest) {
+    let toString;
+    try {
+      // Attempt to locate the object's "toString" method.
+      let obj = this.obj;
+      do {
+        let desc = obj.getOwnPropertyDescriptor("toString");
+        if (desc) {
+          toString = desc.value;
+          break;
+        }
+      } while (obj = obj.proto)
+    } catch (e) {
+      dumpn(e);
+    }
+
+    let result = null;
+    if (toString && toString.callable) {
+      // If a toString method was found then call it on the object.
+      let ret = toString.call(this.obj).return;
+      if (typeof ret == "string") {
+        // Only use the result if it was a returned string.
+        result = ret;
+      }
+    }
+
+    return { from: this.actorID,
+             displayString: this.threadActor.createValueGrip(result) };
+  },
+
+  /**
    * A helper method that creates a property descriptor for the provided object,
    * properly formatted for sending in a protocol response.
    *
    * @param string aName
    *        The property that the descriptor is generated for.
    */
   _propertyDescriptor: function OA_propertyDescriptor(aName) {
     let desc;
@@ -1984,16 +2020,20 @@ ObjectActor.prototype = {
       return {
         configurable: false,
         writable: false,
         enumerable: false,
         value: e.name
       };
     }
 
+    if (!desc) {
+      return undefined;
+    }
+
     let retval = {
       configurable: desc.configurable,
       enumerable: desc.enumerable
     };
 
     if ("value" in desc) {
       retval.writable = desc.writable;
       retval.value = this.threadActor.createValueGrip(desc.value);
@@ -2053,16 +2093,17 @@ ObjectActor.prototype = {
   },
 };
 
 ObjectActor.prototype.requestTypes = {
   "parameterNames": ObjectActor.prototype.onParameterNames,
   "prototypeAndProperties": ObjectActor.prototype.onPrototypeAndProperties,
   "prototype": ObjectActor.prototype.onPrototype,
   "property": ObjectActor.prototype.onProperty,
+  "displayString": ObjectActor.prototype.onDisplayString,
   "ownPropertyNames": ObjectActor.prototype.onOwnPropertyNames,
   "decompile": ObjectActor.prototype.onDecompile,
   "release": ObjectActor.prototype.onRelease,
 };
 
 
 /**
  * Creates a pause-scoped actor for the specified object.
@@ -2085,16 +2126,19 @@ update(PauseScopedObjectActor.prototype,
 
   onPrototypeAndProperties:
     PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
 
   onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
   onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
   onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
 
+  onDisplayString:
+    PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
+
   onParameterNames:
     PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
 
   /**
    * Handle a protocol request to provide the lexical scope of a function.
    *
    * @param aRequest object
    *        The protocol request object.
--- a/toolkit/devtools/server/actors/styleeditor.js
+++ b/toolkit/devtools/server/actors/styleeditor.js
@@ -402,17 +402,17 @@ StyleSheetActor.prototype = {
       title: this.styleSheet.title,
       styleSheetIndex: this.styleSheetIndex,
       text: this.text
     }
 
     // get parent actor if this sheet was @imported
     let parent = this.styleSheet.parentStyleSheet;
     if (parent) {
-      form.parentActor = this.parentActor._sheets.get(parent);
+      form.parentActor = this.parentActor._sheets.get(parent).form();
     }
 
     try {
       form.ruleCount = this.styleSheet.cssRules.length;
     }
     catch(e) {
       // stylesheet had an @import rule that wasn't loaded yet
     }
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -575,16 +575,17 @@ WebConsoleActor.prototype =
   onEvaluateJS: function WCA_onEvaluateJS(aRequest)
   {
     let input = aRequest.text;
     let timestamp = Date.now();
 
     let evalOptions = {
       bindObjectActor: aRequest.bindObjectActor,
       frameActor: aRequest.frameActor,
+      url: aRequest.url,
     };
     let evalInfo = this.evalWithDebugger(input, evalOptions);
     let evalResult = evalInfo.result;
     let helperResult = evalInfo.helperResult;
 
     let result, error, errorMessage;
     if (evalResult) {
       if ("return" in evalResult) {
@@ -791,16 +792,18 @@ WebConsoleActor.prototype =
    * @return object
    *         An object that holds the following properties:
    *         - dbg: the debugger where the string was evaluated.
    *         - frame: (optional) the frame where the string was evaluated.
    *         - window: the Debugger.Object for the global where the string was
    *         evaluated.
    *         - result: the result of the evaluation.
    *         - helperResult: any result coming from a JSTerm helper function.
+   *         - url: the url to evaluate the script as. Defaults to
+   *         "debugger eval code".
    */
   evalWithDebugger: function WCA_evalWithDebugger(aString, aOptions = {})
   {
     // The help function needs to be easy to guess, so we make the () optional.
     if (aString.trim() == "help" || aString.trim() == "?") {
       aString = "help()";
     }
 
@@ -889,22 +892,27 @@ WebConsoleActor.prototype =
     if (found$$) {
       $$ = bindings.$$;
       delete bindings.$$;
     }
 
     // Ready to evaluate the string.
     helpers.evalInput = aString;
 
+    let evalOptions;
+    if (typeof aOptions.url == "string") {
+      evalOptions = { url: aOptions.url };
+    }
+
     let result;
     if (frame) {
-      result = frame.evalWithBindings(aString, bindings);
+      result = frame.evalWithBindings(aString, bindings, evalOptions);
     }
     else {
-      result = dbgWindow.evalInGlobalWithBindings(aString, bindings);
+      result = dbgWindow.evalInGlobalWithBindings(aString, bindings, evalOptions);
     }
 
     let helperResult = helpers.helperResult;
     delete helpers.evalInput;
     delete helpers.helperResult;
 
     if ($) {
       bindings.$ = $;
--- a/toolkit/devtools/server/tests/mochitest/Makefile.in
+++ b/toolkit/devtools/server/tests/mochitest/Makefile.in
@@ -18,16 +18,17 @@ MOCHITEST_CHROME_FILES	= \
 	test_inspector-changevalue.html \
 	test_inspector-insert.html \
 	test_inspector-mutations-attr.html \
 	test_inspector-mutations-childlist.html \
 	test_inspector-mutations-frameload.html \
 	test_inspector-mutations-value.html \
 	test_inspector-release.html \
 	test_inspector-remove.html \
+	test_inspector-reload.html \
 	test_inspector-retain.html \
 	test_inspector-pseudoclass-lock.html \
 	test_inspector-traversal.html \
 	test_unsafeDereference.html \
 	nonchrome_unsafeDereference.html \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/toolkit/devtools/server/tests/mochitest/inspector-helpers.js
@@ -230,16 +230,20 @@ function isFrameLoad(change) {
 function isUnretained(change) {
   return change.type === "unretained";
 }
 
 function isChildList(change) {
   return change.type === "childList";
 }
 
+function isNewRoot(change) {
+  return change.type === "newRoot";
+}
+
 // Make sure an iframe's src attribute changed and then
 // strip that mutation out of the list.
 function assertSrcChange(mutations) {
   return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
 }
 
 // Make sure there's an unload in the mutation list and strip
 // that mutation out of the list
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_inspector-reload.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug </title>
+
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
+const Ci = Components.interfaces;
+const promise = devtools.require("sdk/core/promise");
+const inspector = devtools.require("devtools/server/actors/inspector");
+
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+  runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+addTest(function setup() {
+  let url = document.getElementById("inspectorContent").href;
+  attachURL(url, function(err, client, tab, doc) {
+    gInspectee = doc;
+    let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
+    let inspector = InspectorFront(client, tab);
+    promiseDone(inspector.getWalker().then(walker => {
+      ok(walker, "getWalker() should return an actor.");
+      gClient = client;
+      gWalker = walker;
+      return inspector.getWalker();
+    }).then(walker => {
+      dump(walker.actorID + "\n");
+      ok(walker === gWalker, "getWalker() twice should return the same walker.");
+    }).then(runNextTest));
+  });
+});
+
+addTest(function testReload() {
+  let nodeFront;
+  let oldRootID = gWalker.rootNode.actorID;
+  // Load a node to populate the tree a bit.
+  promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+    gInspectee.defaultView.location.reload();
+    return waitForMutation(gWalker, isNewRoot);
+  }).then(() => {
+    ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed.");
+  }).then(() => {
+    // Make sure we can still access the document
+    return gWalker.querySelector(gWalker.rootNode, "#a");
+  }).then(front => {
+    ok(front.actorID, "Got a new actor ID");
+  }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+  delete gWalker;
+  delete gInspectee;
+  delete gClient;
+  runNextTest();
+});
+
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/toolkit/devtools/webconsole/WebConsoleClient.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleClient.jsm
@@ -96,25 +96,29 @@ WebConsoleClient.prototype = {
    *        by knowing the ObjectActor it inspects and binding |_self| to the
    *        D.O of the OA. As such, variable view sends strings like these for
    *        eval:
    *          _self["prop"] = value;
    *
    *        - frameActor: a FrameActor ID. The FA holds a reference to
    *        a Debugger.Frame. This option allows you to evaluate the string in
    *        the frame of the given FA.
+   *
+   *        - url: the url to evaluate the script as. Defaults to
+   *        "debugger eval code".
    */
   evaluateJS: function WCC_evaluateJS(aString, aOnResponse, aOptions = {})
   {
     let packet = {
       to: this._actor,
       type: "evaluateJS",
       text: aString,
       bindObjectActor: aOptions.bindObjectActor,
       frameActor: aOptions.frameActor,
+      url: aOptions.url,
     };
     this._client.request(packet, aOnResponse);
   },
 
   /**
    * Autocomplete a JavaScript expression.
    *
    * @param string aString
--- a/widget/gtk2/nsWindow.cpp
+++ b/widget/gtk2/nsWindow.cpp
@@ -6214,13 +6214,39 @@ nsWindow::SynthesizeNativeMouseEvent(nsI
                                      uint32_t aNativeMessage,
                                      uint32_t aModifierFlags)
 {
   if (!mGdkWindow) {
     return NS_OK;
   }
 
   GdkDisplay* display = gdk_window_get_display(mGdkWindow);
-  GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
-  gdk_display_warp_pointer(display, screen, aPoint.x, aPoint.y);
+
+  // When a button-release event is requested, create it here and put it in the
+  // event queue. This will not emit a motion event - this needs to be done
+  // explicitly *before* requesting a button-release. You will also need to wait
+  // for the motion event to be dispatched before requesting a button-release
+  // event to maintain the desired event order.
+  if (aNativeMessage == GDK_BUTTON_RELEASE) {
+    GdkEvent event;
+    memset(&event, 0, sizeof(GdkEvent));
+    event.type = (GdkEventType)aNativeMessage;
+    event.button.button = 1;
+    event.button.window = mGdkWindow;
+    event.button.time = GDK_CURRENT_TIME;
+
+#if (MOZ_WIDGET_GTK == 3)
+    // Get device for event source
+    GdkDeviceManager *device_manager = gdk_display_get_device_manager(display);
+    event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+#endif
+
+    gdk_event_put(&event);
+  } else {
+    // We don't support specific events other than button-release. In case
+    // aNativeMessage != GDK_BUTTON_RELEASE we'll synthesize a motion event
+    // that will be emitted by gdk_display_warp_pointer().
+    GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+    gdk_display_warp_pointer(display, screen, aPoint.x, aPoint.y);
+  }
 
   return NS_OK;
 }