Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 17 Nov 2012 13:36:17 -0500
changeset 113630 be255a7ccfdfa30df35a4a59c13f388eb512c727
parent 113629 3747d5a7e01edb452b85b71d7ecbc1313bf44b98 (current diff)
parent 113610 4639da479a93dd49d928f16b8e879541fd1cd3fa (diff)
child 113631 ee74c1c99707cba7ef641e827d3b55f8be44d887
push id23880
push userryanvm@gmail.com
push dateSun, 18 Nov 2012 13:36:46 +0000
treeherdermozilla-central@174440fca7da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone19.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -47,16 +47,17 @@ let DebuggerController = {
     if (this._isInitialized) {
       return;
     }
     this._isInitialized = true;
     window.removeEventListener("load", this._startupDebugger, true);
 
     DebuggerView.initialize(function() {
       DebuggerView._isInitialized = true;
+
       window.dispatchEvent("Debugger:Loaded");
       this._connect();
     }.bind(this));
   },
 
   /**
    * Destroys the view and disconnects the debugger client from the server.
    */
@@ -366,17 +367,20 @@ function StackFrames() {
   this._onFrames = this._onFrames.bind(this);
   this._onFramesCleared = this._onFramesCleared.bind(this);
   this._afterFramesCleared = this._afterFramesCleared.bind(this);
   this.evaluate = this.evaluate.bind(this);
 }
 
 StackFrames.prototype = {
   get activeThread() DebuggerController.activeThread,
+  autoScopeExpand: false,
   currentFrame: null,
+  currentBreakpointLocation: null,
+  currentEvaluation: null,
   currentException: null,
 
   /**
    * Connect to the current thread client.
    */
   connect: function SF_connect() {
     dumpn("StackFrames is connecting...");
     this.activeThread.addListener("paused", this._onPaused);
@@ -412,19 +416,29 @@ StackFrames.prototype = {
    * Handler for the thread client's paused notification.
    *
    * @param string aEvent
    *        The name of the notification ("paused" in this case).
    * @param object aPacket
    *        The response packet.
    */
   _onPaused: function SF__onPaused(aEvent, aPacket) {
-    // In case the pause was caused by an exception, store the exception value.
-    if (aPacket.why.type == "exception") {
-      this.currentException = aPacket.why.exception;
+    switch (aPacket.why.type) {
+      // If paused by a breakpoint, store the breakpoint location.
+      case "breakpoint":
+        this.currentBreakpointLocation = aPacket.frame.where;
+        break;
+      // If paused by a client evaluation, store the evaluated value.
+      case "clientEvaluated":
+        this.currentEvaluation = aPacket.why.frameFinished.return;
+        break;
+      // If paused by an exception, store the exception value.
+      case "exception":
+        this.currentException = aPacket.why.exception;
+        break;
     }
 
     this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
     DebuggerView.editor.focus();
   },
 
   /**
    * Handler for the thread client's resumed notification.
@@ -438,32 +452,63 @@ StackFrames.prototype = {
    */
   _onFrames: function SF__onFrames() {
     // Ignore useless notifications.
     if (!this.activeThread.cachedFrames.length) {
       return;
     }
     DebuggerView.StackFrames.empty();
 
+    // Conditional breakpoints are { breakpoint, expression } tuples. The
+    // boolean evaluation of the expression decides if the active thread
+    // automatically resumes execution or not.
+    if (this.currentBreakpointLocation) {
+      let { url, line } = this.currentBreakpointLocation;
+      let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
+      let conditionalExpression = breakpointClient.conditionalExpression;
+      if (conditionalExpression) {
+        // Evaluating the current breakpoint's conditional expression will
+        // cause the stack frames to be cleared and active thread to pause,
+        // sending a 'clientEvaluated' packed and adding the frames again.
+        this.evaluate("(" + conditionalExpression + ")", 0);
+        this._isConditionalBreakpointEvaluation = true;
+        return;
+      }
+    }
+
+    // Got our evaluation of the current breakpoint's conditional expression.
+    if (this._isConditionalBreakpointEvaluation) {
+      this._isConditionalBreakpointEvaluation = false;
+
+      // If the breakpoint's conditional expression evaluation is falsy,
+      // automatically resume execution.
+      if (VariablesView.isFalsy({ value: this.currentEvaluation })) {
+        this.activeThread.resume();
+        return;
+      }
+    }
+
     for (let frame of this.activeThread.cachedFrames) {
       this._addFrame(frame);
     }
     if (!this.currentFrame) {
       this.selectFrame(0);
     }
     if (this.activeThread.moreFrames) {
       DebuggerView.StackFrames.dirty = true;
     }
   },
 
   /**
    * Handler for the thread client's framescleared notification.
    */
   _onFramesCleared: function SF__onFramesCleared() {
     this.currentFrame = null;
+    this.currentBreakpointLocation = null;
+    this.currentEvaluation = null;
     this.currentException = null;
     // 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);
   },
 
@@ -488,234 +533,319 @@ StackFrames.prototype = {
    * @param number aDepth
    *        The depth of the frame in the stack.
    */
   selectFrame: function SF_selectFrame(aDepth) {
     let frame = this.activeThread.cachedFrames[this.currentFrame = aDepth];
     if (!frame) {
       return;
     }
-    let env = frame.environment;
+    let environment = frame.environment;
     let { url, line } = frame.where;
 
     // Check if the frame does not represent the evaluation of debuggee code.
-    if (!env) {
+    if (!environment) {
       return;
     }
 
     // Move the editor's caret to the proper url and line.
     DebuggerView.updateEditor(url, line);
     // Highlight the stack frame at the specified depth.
     DebuggerView.StackFrames.highlightFrame(aDepth);
     // Highlight the breakpoint at the specified url and line if it exists.
     DebuggerView.Breakpoints.highlightBreakpoint(url, line);
     // Start recording any added variables or properties in any scope.
     DebuggerView.Variables.createHierarchy();
     // Clear existing scopes and create each one dynamically.
     DebuggerView.Variables.empty();
 
-    let self = this;
-    let name = "";
-
     do {
-      // Name the outermost scope Global.
-      if (!env.parent) {
-        name = L10N.getStr("globalScopeLabel");
-      }
-      // Otherwise construct the scope name.
-      else {
-        name = env.type.charAt(0).toUpperCase() + env.type.slice(1);
-      }
-
-      let label = L10N.getFormatStr("scopeLabel", [name]);
-      switch (env.type) {
-        case "with":
-        case "object":
-          label += " [" + env.object.class + "]";
-          break;
-        case "function":
-          label += " [" + env.functionName + "]";
-          break;
-      }
-
       // Create a scope to contain all the inspected variables.
+      let label = this._getScopeLabel(environment);
       let scope = DebuggerView.Variables.addScope(label);
 
       // Special additions to the innermost scope.
-      if (env == frame.environment) {
-        // Add any thrown exception.
-        if (aDepth == 0 && this.currentException) {
-          let excVar = scope.addVar("<exception>", { value: this.currentException });
-          this._addExpander(excVar, this.currentException);
-        }
-        // Add "this".
-        if (frame.this) {
-          let thisVar = scope.addVar("this", { value: frame.this });
-          this._addExpander(thisVar, frame.this);
-        }
-        // Expand the innermost scope by default.
-        scope.expand(true);
+      if (environment == frame.environment) {
+        this._insertScopeFrameReferences(scope, frame);
+        this._fetchScopeVariables(scope, environment);
+        // Always expand the innermost scope by default.
+        scope.expand();
       }
-
-      switch (env.type) {
-        case "with":
-        case "object":
-          // Add nodes for all variables in the environment object scope.
-          this.activeThread.pauseGrip(env.object).getPrototypeAndProperties(function(aResponse) {
-            self._addScopeVariables(aResponse.ownProperties, scope);
-
-            // Signal that variables have been fetched.
-            window.dispatchEvent("Debugger:FetchedVariables");
-            DebuggerView.Variables.commitHierarchy();
-          });
-          break;
-        case "block":
-        case "function":
-          // Add nodes for every argument.
-          for (let variable of env.bindings.arguments) {
-            let name = Object.getOwnPropertyNames(variable)[0];
-            let paramVar = scope.addVar(name, variable[name]);
-            let paramVal = variable[name].value;
-            this._addExpander(paramVar, paramVal);
-          }
-          // Add nodes for every other variable in scope.
-          this._addScopeVariables(env.bindings.variables, scope);
-          break;
-        default:
-          Cu.reportError("Unknown Debugger.Environment type: " + env.type);
-          break;
+      // Lazily add nodes for every other environment scope.
+      else {
+        this._addScopeExpander(scope, environment);
+        this.autoScopeExpand && scope.expand();
       }
-    } while (env = env.parent);
+    } while (environment = environment.parent);
 
     // Signal that variables have been fetched.
     window.dispatchEvent("Debugger:FetchedVariables");
     DebuggerView.Variables.commitHierarchy();
   },
 
   /**
+   * Adds an 'onexpand' callback for a scope, lazily handling
+   * the addition of new variables.
+   *
+   * @param Scope aScope
+   *        The scope where the variables will be placed into.
+   * @param object aEnv
+   *        The scope's environment.
+   */
+  _addScopeExpander: function SF__addScopeExpander(aScope, aEnv) {
+    let callback = this._fetchScopeVariables.bind(this, aScope, aEnv);
+
+    // It's a good idea to be prepared in case of an expansion.
+    aScope.onmouseover = callback;
+    // Make sure that variables are always available on expansion.
+    aScope.onexpand = callback;
+  },
+
+  /**
+   * Adds an 'onexpand' callback for a variable, lazily handling
+   * the addition of new properties.
+   *
+   * @param Variable aVar
+   *        The variable where the properties will be placed into.
+   * @param any aGrip
+   *        The grip of the variable.
+   */
+  _addVarExpander: function SF__addVarExpander(aVar, aGrip) {
+    // No need for expansion for primitive values.
+    if (VariablesView.isPrimitive({ value: aGrip })) {
+      return;
+    }
+    let callback = this._fetchVarProperties.bind(this, aVar, aGrip);
+
+    // Some variables are likely to contain a very large number of properties.
+    // It's a good idea to be prepared in case of an expansion.
+    if (aVar.name == "window" || aVar.name == "this") {
+      aVar.onmouseover = callback;
+    }
+    // Make sure that properties are always available on expansion.
+    aVar.onexpand = callback;
+  },
+
+  /**
+   * Adds variables to a scope in the view. Triggered when a scope is
+   * expanded or is hovered. It does not expand the scope.
+   *
+   * @param Scope aScope
+   *        The scope where the variables will be placed into.
+   * @param object aEnv
+   *        The scope's environment.
+   */
+  _fetchScopeVariables: function SF__fetchScopeVariables(aScope, aEnv) {
+    // Retrieve the variables only once.
+    if (aScope.fetched) {
+      return;
+    }
+    aScope.fetched = true;
+
+    switch (aEnv.type) {
+      case "with":
+      case "object":
+        // Add nodes for every variable in scope.
+        this.activeThread.pauseGrip(aEnv.object).getPrototypeAndProperties(function(aResponse) {
+          this._insertScopeVariables(aResponse.ownProperties, aScope);
+
+          // Signal that variables have been fetched.
+          window.dispatchEvent("Debugger:FetchedVariables");
+          DebuggerView.Variables.commitHierarchy();
+        }.bind(this));
+        break;
+      case "block":
+      case "function":
+        // Add nodes for every argument and every other variable in scope.
+        this._insertScopeArguments(aEnv.bindings.arguments, aScope);
+        this._insertScopeVariables(aEnv.bindings.variables, aScope);
+        break;
+      default:
+        Cu.reportError("Unknown Debugger.Environment type: " + aEnv.type);
+        break;
+    }
+  },
+
+  /**
+   * Add nodes for special frame references in the innermost scope.
+   *
+   * @param Scope aScope
+   *        The scope where the references will be placed into.
+   * @param object aFrame
+   *        The frame to get some references from.
+   */
+  _insertScopeFrameReferences: function SF__insertScopeFrameReferences(aScope, aFrame) {
+    // Add any thrown exception.
+    if (this.currentException) {
+      let excRef = aScope.addVar("<exception>", { value: this.currentException });
+      this._addVarExpander(excRef, this.currentException);
+    }
+    // Add "this".
+    if (aFrame.this) {
+      let thisRef = aScope.addVar("this", { value: aFrame.this });
+      this._addVarExpander(thisRef, aFrame.this);
+    }
+  },
+
+  /**
+   * Add nodes for every argument in scope.
+   *
+   * @param object aArguments
+   *        The map of names to arguments, as specified in the protocol.
+   * @param Scope aScope
+   *        The scope where the nodes will be placed into.
+   */
+  _insertScopeArguments: function SF__insertScopeArguments(aArguments, aScope) {
+    if (!aArguments) {
+      return;
+    }
+    for (let argument of aArguments) {
+      let name = Object.getOwnPropertyNames(argument)[0];
+      let argRef = aScope.addVar(name, argument[name]);
+      let argVal = argument[name].value;
+      this._addVarExpander(argRef, argVal);
+    }
+  },
+
+  /**
    * Add nodes for every variable in scope.
    *
    * @param object aVariables
-   *        The map of names to variables, as specified in the Remote
-   *        Debugging Protocol.
+   *        The map of names to variables, as specified in the protocol.
    * @param Scope aScope
    *        The scope where the nodes will be placed into.
    */
-  _addScopeVariables: function SF_addScopeVariables(aVariables, aScope) {
+  _insertScopeVariables: function SF__insertScopeVariables(aVariables, aScope) {
     if (!aVariables) {
       return;
     }
     let variableNames = Object.keys(aVariables);
 
     // Sort all of the variables before adding them if preferred.
     if (Prefs.variablesSortingEnabled) {
       variableNames.sort();
     }
     // Add the sorted variables to the specified scope.
     for (let name of variableNames) {
-      let paramVar = aScope.addVar(name, aVariables[name]);
-      let paramVal = aVariables[name].value;
-      this._addExpander(paramVar, paramVal);
+      let varRef = aScope.addVar(name, aVariables[name]);
+      let varVal = aVariables[name].value;
+      this._addVarExpander(varRef, varVal);
     }
   },
 
   /**
-   * Adds an 'onexpand' callback for a variable, lazily handling
-   * the addition of new properties.
+   * Adds properties to a variable in the view. Triggered when a variable is
+   * expanded or certain variables are hovered. It does not expand the variable.
    *
    * @param Variable aVar
    *        The variable where the properties will be placed into.
    * @param any aGrip
    *        The grip of the variable.
    */
-  _addExpander: function SF__addExpander(aVar, aGrip) {
-    // No need for expansion for primitive values.
-    if (VariablesView.isPrimitive({ value: aGrip })) {
-      return;
-    }
-    aVar.onexpand = this._addVarProperties.bind(this, aVar, aGrip);
-  },
-
-  /**
-   * Adds properties to a variable in the view. Triggered when a variable is
-   * expanded.
-   *
-   * @param Variable aVar
-   *        The variable where the properties will be placed into.
-   * @param any aGrip
-   *        The grip of the variable.
-   */
-  _addVarProperties: function SF__addVarProperties(aVar, aGrip) {
+  _fetchVarProperties: function SF__fetchVarProperties(aVar, aGrip) {
     // Retrieve the properties only once.
     if (aVar.fetched) {
       return;
     }
+    aVar.fetched = true;
 
     this.activeThread.pauseGrip(aGrip).getPrototypeAndProperties(function(aResponse) {
       let { ownProperties, prototype } = aResponse;
 
       // Add all the variable properties.
       if (ownProperties) {
         aVar.addProperties(ownProperties);
         // Expansion handlers must be set after the properties are added.
         for (let name in ownProperties) {
-          this._addExpander(aVar.get(name), ownProperties[name].value);
+          this._addVarExpander(aVar.get(name), ownProperties[name].value);
         }
       }
 
       // Add the variable's __proto__.
       if (prototype.type != "null") {
         aVar.addProperty("__proto__", { value: prototype });
         // Expansion handlers must be set after the properties are added.
-        this._addExpander(aVar.get("__proto__"), prototype);
+        this._addVarExpander(aVar.get("__proto__"), prototype);
       }
 
-      aVar.fetched = true;
+      aVar._retrieved = true;
 
       // Signal that properties have been fetched.
       window.dispatchEvent("Debugger:FetchedProperties");
       DebuggerView.Variables.commitHierarchy();
     }.bind(this));
   },
 
   /**
+   * Constructs a scope label based on its environment.
+   *
+   * @param object aEnv
+   *        The scope's environment.
+   * @return string
+   *         The scope's label.
+   */
+  _getScopeLabel: function SV__getScopeLabel(aEnv) {
+    let name = "";
+
+    // Name the outermost scope Global.
+    if (!aEnv.parent) {
+      name = L10N.getStr("globalScopeLabel");
+    }
+    // Otherwise construct the scope name.
+    else {
+      name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
+    }
+
+    let label = L10N.getFormatStr("scopeLabel", [name]);
+    switch (aEnv.type) {
+      case "with":
+      case "object":
+        label += " [" + aEnv.object.class + "]";
+        break;
+      case "function":
+        label += " [" + aEnv.functionName + "]";
+        break;
+    }
+    return label;
+  },
+
+  /**
    * Adds the specified stack frame to the list.
    *
    * @param object aFrame
    *        The new frame to add.
    */
   _addFrame: function SF__addFrame(aFrame) {
     let depth = aFrame.depth;
     let { url, line } = aFrame.where;
 
     let startText = StackFrameUtils.getFrameTitle(aFrame);
     let endText = SourceUtils.getSourceLabel(url) + ":" + line;
-
-    DebuggerView.StackFrames.addFrame(startText, endText, depth, {
-      attachment: aFrame
-    });
+    DebuggerView.StackFrames.addFrame(startText, endText, depth);
   },
 
   /**
    * Loads more stack frames from the debugger server cache.
    */
   addMoreFrames: function SF_addMoreFrames() {
     this.activeThread.fillFrames(
       this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
   },
 
   /**
    * Evaluate an expression in the context of the selected frame. This is used
    * for modifying the value of variables or properties in scope.
    *
    * @param string aExpression
    *        The expression to evaluate.
+   * @param number aFrame [optional]
+   *        The frame depth used for evaluation.
    */
-  evaluate: function SF_evaluate(aExpression) {
-    let frame = this.activeThread.cachedFrames[this.currentFrame];
+  evaluate: function SF_evaluate(aExpression, aFrame = this.currentFrame) {
+    let frame = this.activeThread.cachedFrames[aFrame];
     this.activeThread.eval(frame.actor, aExpression);
   }
 };
 
 /**
  * Keeps the source script list up-to-date, using the thread client's
  * source script cache.
  *
@@ -1012,51 +1142,60 @@ Breakpoints.prototype = {
   /**
    * Update the breakpoints in the editor view. This function takes the list of
    * breakpoints in the debugger and adds them back into the editor view.
    * This is invoked when the selected script is changed.
    */
   updateEditorBreakpoints: function BP_updateEditorBreakpoints() {
     for each (let breakpointClient in this.store) {
       if (DebuggerView.Sources.selectedValue == breakpointClient.location.url) {
-        this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
+        this._showBreakpoint(breakpointClient, {
+          noPaneUpdate: true,
+          noPaneHighlight: true
+        });
       }
     }
   },
 
   /**
    * Update the breakpoints in the pane view. This function takes the list of
    * breakpoints in the debugger and adds them back into the breakpoints pane.
    * This is invoked when scripts are added.
    */
   updatePaneBreakpoints: function BP_updatePaneBreakpoints() {
     for each (let breakpointClient in this.store) {
       if (DebuggerView.Sources.containsValue(breakpointClient.location.url)) {
-        this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
+        this._showBreakpoint(breakpointClient, {
+          noEditorUpdate: true,
+          noPaneHighlight: true
+        });
       }
     }
   },
 
   /**
    * Add a breakpoint.
    *
    * @param object aLocation
    *        The location where you want the breakpoint. This object must have
    *        two properties:
-   *          - url - the url of the source.
-   *          - line - the line number (starting from 1).
+   *          - url: the url of the source.
+   *          - line: the line number (starting from 1).
    * @param function aCallback [optional]
    *        Optional function to invoke once the breakpoint is added. The
    *        callback is invoked with two arguments:
    *          - aBreakpointClient: the BreakpointActor client object
    *          - aResponseError: if there was any error
    * @param object aFlags [optional]
    *        An object containing some of the following boolean properties:
+   *          - conditionalExpression: tells this breakpoint's conditional expression
+   *          - openPopup: tells if the expression popup should be shown
    *          - noEditorUpdate: tells if you want to skip editor updates
    *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
+   *          - noPaneHighlight: tells if you don't want to highlight the breakpoint
    */
   addBreakpoint:
   function BP_addBreakpoint(aLocation, aCallback, aFlags = {}) {
     let breakpointClient = this.getBreakpoint(aLocation.url, aLocation.line);
 
     // If the breakpoint was already added, callback immediately.
     if (breakpointClient) {
       aCallback && aCallback(breakpointClient);
@@ -1087,16 +1226,19 @@ Breakpoints.prototype = {
         // Update the breakpoint client with the actual location.
         aBreakpointClient.location.url = aResponse.actualLocation.url;
         aBreakpointClient.location.line = aResponse.actualLocation.line;
       }
 
       // Remember the breakpoint client in the store.
       this.store[aBreakpointClient.actor] = aBreakpointClient;
 
+      // Attach any specified conditional expression to the breakpoint client.
+      aBreakpointClient.conditionalExpression = aFlags.conditionalExpression;
+
       // Preserve some information about the breakpoint's source url and line
       // to display in the breakpoints pane.
       aBreakpointClient.lineText = DebuggerView.getEditorLine(line - 1);
       aBreakpointClient.lineInfo = SourceUtils.getSourceLabel(url) + ":" + line;
 
       // Show the breakpoint in the editor and breakpoints pane.
       this._showBreakpoint(aBreakpointClient, aFlags);
 
@@ -1110,19 +1252,17 @@ Breakpoints.prototype = {
    *
    * @param object aBreakpointClient
    *        The BreakpointActor client object to remove.
    * @param function aCallback [optional]
    *        Optional function to invoke once the breakpoint is removed. The
    *        callback is invoked with one argument
    *          - aBreakpointClient: the breakpoint location (url and line)
    * @param object aFlags [optional]
-   *        An object containing some of the following boolean properties:
-   *          - noEditorUpdate: tells if you want to skip editor updates
-   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   removeBreakpoint:
   function BP_removeBreakpoint(aBreakpointClient, aCallback, aFlags = {}) {
     let breakpointActor = (aBreakpointClient || {}).actor;
 
     // If the breakpoint was already removed, callback immediately.
     if (!this.store[breakpointActor]) {
       aCallback && aCallback(aBreakpointClient.location);
@@ -1142,59 +1282,66 @@ Breakpoints.prototype = {
   },
 
   /**
    * Update the editor and breakpoints pane to show a specified breakpoint.
    *
    * @param object aBreakpointClient
    *        The BreakpointActor client object to show.
    * @param object aFlags [optional]
-   *        An object containing some of the following boolean properties:
-   *          - noEditorUpdate: tells if you want to skip editor updates
-   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   _showBreakpoint: function BP__showBreakpoint(aBreakpointClient, aFlags = {}) {
     let currentSourceUrl = DebuggerView.Sources.selectedValue;
     let { url, line } = aBreakpointClient.location;
 
+    // Update the editor if required.
     if (!aFlags.noEditorUpdate) {
       if (url == currentSourceUrl) {
         this._skipEditorBreakpointCallbacks = true;
         this.editor.addBreakpoint(line - 1);
         this._skipEditorBreakpointCallbacks = false;
       }
     }
+    // Update the breakpoints pane if required.
     if (!aFlags.noPaneUpdate) {
-      let { lineText, lineInfo } = aBreakpointClient;
-      let actor = aBreakpointClient.actor;
-      DebuggerView.Breakpoints.addBreakpoint(lineInfo, lineText, url, line, actor);
+      let { lineText, lineInfo, actor } = aBreakpointClient;
+      let conditionalFlag = aBreakpointClient.conditionalExpression !== undefined;
+      let openPopupFlag = aFlags.openPopup;
+
+      DebuggerView.Breakpoints.addBreakpoint(
+        url, line, actor, lineInfo, lineText, conditionalFlag, openPopupFlag);
+    }
+    // Highlight the breakpoint in the pane if required.
+    if (!aFlags.noPaneHighlight) {
+      DebuggerView.Breakpoints.highlightBreakpoint(url, line);
     }
   },
 
   /**
    * Update the editor and breakpoints pane to hide a specified breakpoint.
    *
    * @param object aBreakpointClient
    *        The BreakpointActor client object to hide.
    * @param object aFlags [optional]
-   *        An object containing some of the following boolean properties:
-   *          - noEditorUpdate: tells if you want to skip editor updates
-   *          - noPaneUpdate: tells if you want to skip breakpoint pane updates
+   *        @see DebuggerController.Breakpoints.addBreakpoint
    */
   _hideBreakpoint: function BP__hideBreakpoint(aBreakpointClient, aFlags = {}) {
     let currentSourceUrl = DebuggerView.Sources.selectedValue;
     let { url, line } = aBreakpointClient.location;
 
+    // Update the editor if required.
     if (!aFlags.noEditorUpdate) {
       if (url == currentSourceUrl) {
         this._skipEditorBreakpointCallbacks = true;
         this.editor.removeBreakpoint(line - 1);
         this._skipEditorBreakpointCallbacks = false;
       }
     }
+    // Update the breakpoints pane if required.
     if (!aFlags.noPaneUpdate) {
       DebuggerView.Breakpoints.removeBreakpoint(url, line);
     }
   },
 
   /**
    * Get the breakpoint object at the given location.
    *
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -45,31 +45,30 @@ create({ constructor: StackFramesView, p
    * Adds a frame in this stackframes container.
    *
    * @param string aFrameName
    *        Name to be displayed in the list.
    * @param string aFrameDetails
    *        Details to be displayed in the list.
    * @param number aDepth
    *        The frame depth specified by the debugger.
-   * @param object aOptions [optional]
-   *        Additional options or flags supported by this operation:
-   *          - attachment: any kind of primitive/object to attach
    */
   addFrame:
-  function DVSF_addFrame(aFrameName, aFrameDetails, aDepth, aOptions = {}) {
+  function DVSF_addFrame(aFrameName, aFrameDetails, aDepth) {
     // Stackframes are UI elements which benefit from visible panes.
     DebuggerView.showPanesSoon();
 
     // Append a stackframe item to this container.
     let stackframeItem = this.push(aFrameName, aFrameDetails, {
       forced: true,
       unsorted: true,
       relaxed: true,
-      attachment: aOptions.attachment
+      attachment: {
+        depth: aDepth
+      }
     });
 
     // Check if stackframe was already appended.
     if (!stackframeItem) {
       return;
     }
 
     let element = stackframeItem.target;
@@ -149,86 +148,122 @@ let StackFrameUtils = {
 /**
  * Functions handling the breakpoints UI.
  */
 function BreakpointsView() {
   dumpn("BreakpointsView was instantiated");
   MenuContainer.call(this);
   this._createItemView = this._createItemView.bind(this);
   this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
-  this._onClick = this._onClick.bind(this);
+  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._onBreakpointClick = this._onBreakpointClick.bind(this);
   this._onCheckboxClick = this._onCheckboxClick.bind(this);
+  this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
+  this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
+  this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
+  this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
 }
 
 create({ constructor: BreakpointsView, proto: MenuContainer.prototype }, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function DVB_initialize() {
     dumpn("Initializing the BreakpointsView");
     this._container = new StackList(document.getElementById("breakpoints"));
+    this._commandset = document.getElementById("debuggerCommands");
     this._popupset = document.getElementById("debuggerPopupset");
+    this._cbPanel = document.getElementById("conditional-breakpoint-panel");
+    this._cbTextbox = document.getElementById("conditional-breakpoint-textbox");
 
     this._container.emptyText = L10N.getStr("emptyBreakpointsText");
     this._container.itemFactory = this._createItemView;
     this._container.uniquenessQualifier = 2;
-    this._container.addEventListener("click", this._onClick, false);
+
+    window.addEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
+    window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
+    this._container.addEventListener("click", this._onBreakpointClick, 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("keypress", this._onConditionalTextboxKeyPress, false);
 
     this._cache = new Map();
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function DVB_destroy() {
     dumpn("Destroying the BreakpointsView");
-    this._container.removeEventListener("click", this._onClick, false);
+    window.removeEventListener("Debugger:EditorLoaded", this._onEditorLoad, false);
+    window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false);
+    this._container.removeEventListener("click", this._onBreakpointClick, false);
+    this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
+    this._cbPanel.removeEventListener("popupshown", this._onConditionalPopupShown, false);
+    this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false)
+    this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
   },
 
   /**
    * Adds a breakpoint in this breakpoints container.
    *
+   * @param string aSourceLocation
+   *        The breakpoint source location specified by the debugger controller.
+   * @param number aLineNumber
+   *        The breakpoint line number specified by the debugger controller.
+   * @param string aActor
+   *        A breakpoint identifier specified by the debugger controller.
    * @param string aLineInfo
    *        Line information (parent source etc.) to be displayed in the list.
    * @param string aLineText
    *        Line text to be displayed in the list.
-   * @param string aSourceLocation
-   *        The breakpoint source location specified by the debugger controller.
-   * @param number aLineNumber
-   *        The breakpoint line number specified by the debugger controller.
-   * @parm string aId
-   *       A breakpoint identifier specified by the debugger controller.
+   * @param boolean aConditionalFlag [optional]
+   *        A flag specifying if this is a conditional breakpoint.
+   * @param boolean aOpenPopupFlag [optional]
+   *        A flag specifying if the expression popup should be shown.
    */
-  addBreakpoint:
-  function DVB_addBreakpoint(aLineInfo, aLineText, aSourceLocation, aLineNumber, aId) {
+  addBreakpoint: function DVB_addBreakpoint(aSourceLocation, aLineNumber,
+                                            aActor, aLineInfo, aLineText,
+                                            aConditionalFlag, aOpenPopupFlag) {
     // Append a breakpoint item to this container.
     let breakpointItem = this.push(aLineInfo.trim(), aLineText.trim(), {
       forced: true,
       attachment: {
         enabled: true,
         sourceLocation: aSourceLocation,
-        lineNumber: aLineNumber
+        lineNumber: aLineNumber,
+        isConditional: aConditionalFlag
       }
     });
 
     // Check if breakpoint was already appended.
     if (!breakpointItem) {
-      this.enableBreakpoint(aSourceLocation, aLineNumber, { id: aId });
+      this.enableBreakpoint(aSourceLocation, aLineNumber, { id: aActor });
       return;
     }
 
     let element = breakpointItem.target;
-    element.id = "breakpoint-" + aId;
+    element.id = "breakpoint-" + aActor;
     element.className = "dbg-breakpoint list-item";
     element.infoNode.className = "dbg-breakpoint-info plain";
     element.textNode.className = "dbg-breakpoint-text plain";
     element.setAttribute("contextmenu", this._createContextMenu(element));
 
     breakpointItem.finalize = this._onBreakpointRemoved;
     this._cache.set(this._key(aSourceLocation, aLineNumber), breakpointItem);
+
+    // If this is a conditional breakpoint, display the panes and a panel
+    // to input the corresponding conditional expression.
+    if (aConditionalFlag && aOpenPopupFlag) {
+      this.highlightBreakpoint(aSourceLocation, aLineNumber, { openPopup: true });
+    }
   },
 
   /**
    * Removes a breakpoint from this breakpoints container.
    *
    * @param string aSourceLocation
    *        The breakpoint source location.
    * @param number aLineNumber
@@ -262,16 +297,17 @@ create({ constructor: BreakpointsView, p
   enableBreakpoint:
   function DVB_enableBreakpoint(aSourceLocation, aLineNumber, aOptions = {}) {
     let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
     if (breakpointItem) {
       // Set a new id to the corresponding breakpoint element if required.
       if (aOptions.id) {
         breakpointItem.target.id = "breakpoint-" + aOptions.id;
       }
+
       // Update the checkbox state if necessary.
       if (!aOptions.silent) {
         breakpointItem.target.checkbox.setAttribute("checked", "true");
       }
 
       let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
       let breakpointLocation = { url: url, line: line };
       DebuggerController.Breakpoints.addBreakpoint(breakpointLocation, aOptions.callback, {
@@ -325,23 +361,57 @@ create({ constructor: BreakpointsView, p
 
   /**
    * Highlights a breakpoint in this breakpoints container.
    *
    * @param string aSourceLocation
    *        The breakpoint source location.
    * @param number aLineNumber
    *        The breakpoint line number.
+   * @param object aFlags [optional]
+   *        An object containing some of the following boolean properties:
+   *          - updateEditor: true if editor updates should be allowed
+   *          - openPopup: true if the expression popup should be shown
    */
-  highlightBreakpoint: function DVB_highlightBreakpoint(aSourceLocation, aLineNumber) {
+  highlightBreakpoint:
+  function DVB_highlightBreakpoint(aSourceLocation, aLineNumber, aFlags = {}) {
     let breakpointItem = this.getBreakpoint(aSourceLocation, aLineNumber);
     if (breakpointItem) {
+      // Update the editor source location and line number if necessary.
+      if (aFlags.updateEditor) {
+        DebuggerView.updateEditor(aSourceLocation, aLineNumber, { noDebug: true });
+      }
+
+      // If the breakpoint requires a new conditional expression, display
+      // the panes and the panel to input the corresponding expression.
+      if (aFlags.openPopup && breakpointItem.attachment.isConditional) {
+        let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+        let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line);
+
+        // The conditional expression popup can only be shown with visible panes.
+        DebuggerView.showPanesSoon(function() {
+          // Verify if the breakpoint wasn't removed before the panes were shown.
+          if (this.getBreakpoint(aSourceLocation, aLineNumber)) {
+            this._cbTextbox.value = breakpointClient.conditionalExpression || "";
+            this._cbPanel.openPopup(breakpointItem.target,
+              BREAKPOINT_CONDITIONAL_POPUP_POSITION,
+              BREAKPOINT_CONDITIONAL_POPUP_OFFSET);
+          }
+        }.bind(this));
+      } else {
+        this._cbPanel.hidePopup();
+      }
+
+      // Breakpoint is now highlighted.
       this._container.selectedItem = breakpointItem.target;
-    } else {
+    }
+    // Can't find a breakpoint at the requested source location and line number.
+    else {
       this._container.selectedIndex = -1;
+      this._cbPanel.hidePopup();
     }
   },
 
   /**
    * Unhighlights the current breakpoint in this breakpoints container.
    */
   unhighlightBreakpoint: function DVB_highlightBreakpoint() {
     this.highlightBreakpoint(null);
@@ -359,16 +429,29 @@ create({ constructor: BreakpointsView, p
    * @return object
    *         The corresponding item.
    */
   getBreakpoint: function DVB_getBreakpoint(aSourceLocation, aLineNumber) {
     return this._cache.get(this._key(aSourceLocation, aLineNumber));
   },
 
   /**
+   * Gets the currently selected breakpoint client.
+   * @return object
+   */
+  get selectedClient() {
+    let selectedItem = this.selectedItem;
+    if (selectedItem) {
+      let { sourceLocation: url, lineNumber: line } = selectedItem.attachment;
+      return DebuggerController.Breakpoints.getBreakpoint(url, line);
+    }
+    return null;
+  },
+
+  /**
    * Customization function for creating an item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
    * @param string aInfo
    *        The breakpoint's line info.
    * @param string aText
    *        The breakpoint's line text.
@@ -387,16 +470,17 @@ create({ constructor: BreakpointsView, p
     let lineText = document.createElement("label");
     lineText.setAttribute("value", aText);
     lineText.setAttribute("crop", "end");
     lineText.setAttribute("tooltiptext",
       aText.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH));
 
     let state = document.createElement("vbox");
     state.className = "state";
+    state.setAttribute("pack", "center");
     state.appendChild(checkbox);
 
     let content = document.createElement("vbox");
     content.className = "content";
     content.setAttribute("flex", "1");
     content.appendChild(lineInfo);
     content.appendChild(lineText);
 
@@ -430,22 +514,24 @@ create({ constructor: BreakpointsView, p
     createMenuSeparator();
     createMenuItem.call(this, "enableAll");
     createMenuItem.call(this, "disableAll");
     createMenuSeparator();
     createMenuItem.call(this, "enableOthers");
     createMenuItem.call(this, "disableOthers");
     createMenuItem.call(this, "deleteOthers");
     createMenuSeparator();
+    createMenuItem.call(this, "setConditional");
+    createMenuSeparator();
     createMenuItem.call(this, "enableSelf", true);
     createMenuItem.call(this, "disableSelf");
     createMenuItem.call(this, "deleteSelf");
 
     this._popupset.appendChild(menupopup);
-    document.documentElement.appendChild(commandset);
+    this._commandset.appendChild(commandset);
 
     aElementNode.commandset = commandset;
     aElementNode.menupopup = menupopup;
     return menupopupId;
 
     /**
      * Creates a menu item specified by a name with the appropriate attributes
      * (label and handler).
@@ -506,48 +592,194 @@ create({ constructor: BreakpointsView, p
   /**
    * Function called each time a breakpoint item is removed.
    */
   _onBreakpointRemoved: function DVB__onBreakpointRemoved(aItem) {
     this._destroyContextMenu(aItem.target);
   },
 
   /**
+   * The load listener for the source editor.
+   */
+  _onEditorLoad: function DVB__onEditorLoad({ detail: editor }) {
+    editor.addEventListener("Selection", this._onEditorSelection, false);
+    editor.addEventListener("ContextMenu", this._onEditorContextMenu, false);
+  },
+
+  /**
+   * The unload listener for the source editor.
+   */
+  _onEditorUnload: function DVB__onEditorUnload({ detail: editor }) {
+    editor.removeEventListener("Selection", this._onEditorSelection, false);
+    editor.removeEventListener("ContextMenu", this._onEditorContextMenu, false);
+  },
+
+  /**
+   * The selection listener for the source editor.
+   */
+  _onEditorSelection: function DVB__onEditorSelection(e) {
+    let { start, end } = e.newValue;
+
+    let sourceLocation = DebuggerView.Sources.selectedValue;
+    let lineStart = DebuggerView.editor.getLineAtOffset(start) + 1;
+    let lineEnd = DebuggerView.editor.getLineAtOffset(end) + 1;
+
+    if (this.getBreakpoint(sourceLocation, lineStart) && lineStart == lineEnd) {
+      this.highlightBreakpoint(sourceLocation, lineStart);
+    } else {
+      this.unhighlightBreakpoint();
+    }
+  },
+
+  /**
+   * The context menu listener for the source editor.
+   */
+  _onEditorContextMenu: function DVB__onEditorContextMenu({ x, y }) {
+    let offset = DebuggerView.editor.getOffsetAtLocation(x, y);
+    let line = DebuggerView.editor.getLineAtOffset(offset);
+    this._editorContextMenuLineNumber = line;
+  },
+
+  /**
+   * Called when the add breakpoint key sequence was pressed.
+   */
+  _onCmdAddBreakpoint: function BP__onCmdAddBreakpoint() {
+    // If this command was executed via the context menu, add the breakpoint
+    // on the currently hovered line in the source editor.
+    if (this._editorContextMenuLineNumber >= 0) {
+      DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
+    }
+    // Avoid placing breakpoints incorrectly when using key shortcuts.
+    this._editorContextMenuLineNumber = -1;
+
+    let url = DebuggerView.Sources.selectedValue;
+    let line = DebuggerView.editor.getCaretPosition().line + 1;
+    let breakpointItem = this.getBreakpoint(url, line);
+
+    // If a breakpoint already existed, remove it now.
+    if (breakpointItem) {
+      let breakpointClient = DebuggerController.Breakpoints.getBreakpoint(url, line)
+      DebuggerController.Breakpoints.removeBreakpoint(breakpointClient);
+      DebuggerView.Breakpoints.unhighlightBreakpoint();
+    }
+    // No breakpoint existed at the required location, add one now.
+    else {
+      let breakpointLocation = { url: url, line: line };
+      DebuggerController.Breakpoints.addBreakpoint(breakpointLocation);
+    }
+  },
+
+  /**
+   * Called when the add conditional breakpoint key sequence was pressed.
+   */
+  _onCmdAddConditionalBreakpoint: function BP__onCmdAddConditionalBreakpoint() {
+    // If this command was executed via the context menu, add the breakpoint
+    // on the currently hovered line in the source editor.
+    if (this._editorContextMenuLineNumber >= 0) {
+      DebuggerView.editor.setCaretPosition(this._editorContextMenuLineNumber);
+    }
+    // Avoid placing breakpoints incorrectly when using key shortcuts.
+    this._editorContextMenuLineNumber = -1;
+
+    let url =  DebuggerView.Sources.selectedValue;
+    let line = DebuggerView.editor.getCaretPosition().line + 1;
+    let breakpointItem = this.getBreakpoint(url, line);
+
+    // If a breakpoint already existed or wasn't a conditional, morph it now.
+    if (breakpointItem) {
+      breakpointItem.attachment.isConditional = true;
+      this.selectedClient.conditionalExpression = "";
+      this.highlightBreakpoint(url, line, { openPopup: true });
+    }
+    // No breakpoint existed at the required location, add one now.
+    else {
+      DebuggerController.Breakpoints.addBreakpoint({ url: url, line: line }, null, {
+        conditionalExpression: "",
+        openPopup: true
+      });
+    }
+  },
+
+  /**
+   * The popup showing listener for the breakpoints conditional expression panel.
+   */
+  _onConditionalPopupShowing: function DVB__onConditionalPopupShowing() {
+    this._popupShown = true;
+  },
+
+  /**
+   * The popup shown listener for the breakpoints conditional expression panel.
+   */
+  _onConditionalPopupShown: function DVB__onConditionalPopupShown() {
+    this._cbTextbox.focus();
+    this._cbTextbox.select();
+  },
+
+  /**
+   * The popup hiding listener for the breakpoints conditional expression panel.
+   */
+  _onConditionalPopupHiding: function DVB__onConditionalPopupHiding() {
+    this._popupShown = false;
+    this.selectedClient.conditionalExpression = this._cbTextbox.value;
+  },
+
+  /**
+   * The keypress listener for the breakpoints conditional expression textbox.
+   */
+  _onConditionalTextboxKeyPress: function DVB__onConditionalTextboxKeyPress(e) {
+    if (e.keyCode == e.DOM_VK_RETURN || e.keyCode == e.DOM_VK_ENTER) {
+      this._cbPanel.hidePopup();
+    }
+  },
+
+  /**
    * The click listener for the breakpoints container.
    */
-  _onClick: function DVB__onClick(e) {
+  _onBreakpointClick: function DVB__onBreakpointClick(e) {
     let breakpointItem = this.getItemForElement(e.target);
     if (!breakpointItem) {
       // The container is empty or we didn't click on an actual item.
       return;
     }
     let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
-
-    DebuggerView.updateEditor(url, line, { noDebug: true });
-    this.highlightBreakpoint(url, line);
+    this.highlightBreakpoint(url, line, { updateEditor: true, openPopup: e.button == 0 });
   },
 
   /**
    * The click listener for a breakpoint checkbox.
    */
   _onCheckboxClick: function DVB__onCheckboxClick(e) {
     let breakpointItem = this.getItemForElement(e.target);
     if (!breakpointItem) {
       // The container is empty or we didn't click on an actual item.
       return;
     }
     let { sourceLocation: url, lineNumber: line, enabled } = breakpointItem.attachment;
+    this[enabled ? "disableBreakpoint" : "enableBreakpoint"](url, line, { silent: true });
 
     // Don't update the editor location.
     e.preventDefault();
     e.stopPropagation();
+  },
 
-    this[enabled
-      ? "disableBreakpoint"
-      : "enableBreakpoint"](url, line, { silent: true });
+  /**
+   * Listener handling the "setConditional" menuitem command.
+   *
+   * @param object aTarget
+   *        The corresponding breakpoint element node.
+   */
+  _onSetConditional: function DVB__onSetConditional(aTarget) {
+    if (!aTarget) {
+      return;
+    }
+    let breakpointItem = this.getItemForElement(aTarget);
+    let { sourceLocation: url, lineNumber: line } = breakpointItem.attachment;
+
+    breakpointItem.attachment.isConditional = true;
+    this.highlightBreakpoint(url, line, { openPopup: true });
   },
 
   /**
    * Listener handling the "enableSelf" menuitem command.
    *
    * @param object aTarget
    *        The corresponding breakpoint element node.
    */
@@ -679,17 +911,22 @@ create({ constructor: BreakpointsView, p
   /**
    * Gets an identifier for a breakpoint item for the current cache.
    */
   _key: function DVB__key(aSourceLocation, aLineNumber) {
     return aSourceLocation + aLineNumber;
   },
 
   _popupset: null,
-  _cache: null
+  _commandset: null,
+  _cbPanel: null,
+  _cbTextbox: null,
+  _popupShown: false,
+  _cache: null,
+  _editorContextMenuLineNumber: -1
 });
 
 /**
  * Functions handling the global search UI.
  */
 function GlobalSearchView() {
   dumpn("GlobalSearchView was instantiated");
   MenuContainer.call(this);
@@ -1039,17 +1276,17 @@ create({ constructor: GlobalSearchView, 
     if (e instanceof Event) {
       e.preventDefault();
       e.stopPropagation();
     }
     let target = e.target;
     let sourceResultsItem = SourceResults.getItemForElement(target);
     let lineResultsItem = LineResults.getItemForElement(target);
 
-    sourceResultsItem.instance.expand(true);
+    sourceResultsItem.instance.expand();
     this._currentlyFocusedMatch = LineResults.indexOfElement(target);
     this._scrollMatchIntoViewIfNeeded(target);
     this._bounceMatch(target);
 
     let location = sourceResultsItem.location;
     let lineNumber = lineResultsItem.lineNumber;
     DebuggerView.updateEditor(location, lineNumber + 1, { noDebug: true });
 
@@ -1079,17 +1316,17 @@ create({ constructor: GlobalSearchView, 
     if (sourceResultsItem.instance.toggled ||
         sourceResultsItem.instance.expanded) {
       return;
     }
     let { top, height } = aTarget.getBoundingClientRect();
     let { clientHeight } = this._container._parent;
 
     if (top - height <= clientHeight || this._forceExpandResults) {
-      sourceResultsItem.instance.expand(true);
+      sourceResultsItem.instance.expand();
     }
   },
 
   /**
    * Scrolls a match into view.
    *
    * @param nsIDOMNode aMatch
    *        The match to scroll into view.
@@ -1298,17 +1535,17 @@ SourceResults.prototype = {
       lineResults.createView(resultsContainer, lineNumber, aCallbacks)
     }
 
     aElementNode.arrow = arrow;
     aElementNode.resultsHeader = resultsHeader;
     aElementNode.resultsContainer = resultsContainer;
 
     if (aExpandFlag && aMatchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
-      this.expand(true);
+      this.expand();
     }
 
     let resultsBox = document.createElement("vbox");
     resultsBox.setAttribute("flex", "1");
     resultsBox.appendChild(resultsHeader);
     resultsBox.appendChild(resultsContainer);
 
     aElementNode.id = "source-results-" + aLocation;
@@ -1523,17 +1760,17 @@ LineResults.indexOfElement = function DV
  * Gets the number of cached items associated with a specified element.
  *
  * @return number
  *         The number of key/value pairs in the corresponding map.
  */
 SourceResults.size =
 LineResults.size = function DVGS_size() {
   let count = 0;
-  for (let [_, item] of this._itemsByElement) {
+  for (let [, item] of this._itemsByElement) {
     if (!item.nonenumerable) {
       count++;
     }
   }
   return count;
 };
 
 /**
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -879,16 +879,17 @@ FilterView.prototype = {
       } else {
         DebuggerView.GlobalSearch[["focusNextMatch", "focusPrevMatch"][action]]();
       }
       return;
     }
 
     // Perform a variable search based on the specified operator.
     if (isVariable) {
+      DebuggerView.Variables.performSearch(token);
       DebuggerView.Variables.expandFirstSearchResults();
       return;
     }
 
     let editor = DebuggerView.editor;
     let offset = editor[["findNext", "findPrevious"][action]](true);
     if (offset > -1) {
       editor.setSelection(offset, offset + token.length)
@@ -908,17 +909,16 @@ FilterView.prototype = {
    * Called when a filtering key sequence was pressed.
    *
    * @param string aOperator
    *        The operator to use for filtering.
    */
   _doSearch: function DVF__doSearch(aOperator = "") {
     this._searchbox.focus();
     this._searchbox.value = aOperator;
-    DebuggerView.GlobalSearch.clearView();
   },
 
   /**
    * Called when the source location filter key sequence was pressed.
    */
   _doFileSearch: function DVF__doFileSearch() {
     this._doSearch();
     this._searchboxPanel.openPopup(this._searchbox);
@@ -947,16 +947,17 @@ FilterView.prototype = {
     this._doSearch(SEARCH_GLOBAL_FLAG);
     this._searchboxPanel.hidePopup();
   },
 
   /**
    * Called when the variable search filter key sequence was pressed.
    */
   _doVariableSearch: function DVF__doVariableSearch() {
+    DebuggerView.Variables.performSearch("");
     this._doSearch(SEARCH_VARIABLE_FLAG);
     this._searchboxPanel.hidePopup();
   },
 
   _searchbox: null,
   _searchboxPanel: null,
   _globalOperatorButton: null,
   _globalOperatorLabel: null,
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const SOURCE_URL_MAX_LENGTH = 64; // chars
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
 const PANES_APPEARANCE_DELAY = 50; // ms
 const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
+const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "after_start";
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET = 50; // px
 const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
 const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
 const GLOBAL_SEARCH_ACTION_DELAY = 150; // ms
 const SEARCH_GLOBAL_FLAG = "!";
 const SEARCH_LINE_FLAG = ":";
 const SEARCH_TOKEN_FLAG = "#";
 const SEARCH_VARIABLE_FLAG = "*";
 
@@ -172,27 +174,29 @@ let DebuggerView = {
   /**
    * The load event handler for the source editor, also executing any necessary
    * post-load operations.
    */
   _onEditorLoad: function DV__onEditorLoad() {
     dumpn("Finished loading the DebuggerView editor");
 
     DebuggerController.Breakpoints.initialize();
+    window.dispatchEvent("Debugger:EditorLoaded", this.editor);
     this.editor.focus();
   },
 
   /**
    * Destroys the SourceEditor instance and also executes any necessary
    * post-unload operations.
    */
   _destroyEditor: function DV__destroyEditor() {
     dumpn("Destroying the DebuggerView editor");
 
     DebuggerController.Breakpoints.destroy();
+    window.dispatchEvent("Debugger:EditorUnloaded", this.editor);
     this.editor = null;
   },
 
   /**
    * Sets the proper editor mode (JS or HTML) according to the specified
    * content type, or by determining the type from the url.
    *
    * @param string aUrl
@@ -232,17 +236,17 @@ let DebuggerView = {
 
   /**
    * Load the editor with the specified source text.
    *
    * @param object aSource
    *        The source object coming from the active thread.
    * @param object aOptions [optional]
    *        Additional options for showing the source. Supported options:
-   *        - targetLine: place the caret position at the given line number
+   *        - caretLine: place the caret position at the given line number
    *        - debugLine: place the debug location at the given line number
    *        - callback: function called when the source is shown
    */
   setEditorSource: function DV_setEditorSource(aSource, aOptions = {}) {
     if (!this.editor) {
       return;
     }
     dumpn("Setting the DebuggerView editor source: " + aSource.url +
@@ -259,31 +263,35 @@ let DebuggerView = {
       DebuggerController.SourceScripts.getText(aSource, function(aUrl, aText) {
         aSource.loaded = true;
         aSource.text = aText;
         this.setEditorSource(aSource, aOptions);
       }.bind(this));
     }
     // If the source is already loaded, display it immediately.
     else {
-      if (aSource.text.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
-        this.setEditorMode(aSource.url, aSource.contentType, aSource.text);
-      } else {
-        this.editor.setMode(SourceEditor.MODES.TEXT);
+      if (this._editorSource != aSource) {
+        // Avoid setting the editor mode for very large files.
+        if (aSource.text.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
+          this.setEditorMode(aSource.url, aSource.contentType, aSource.text);
+        } else {
+          this.editor.setMode(SourceEditor.MODES.TEXT);
+        }
+        this.editor.setText(aSource.text);
+        this.editor.resetUndo();
       }
-      this.editor.setText(aSource.text);
-      this.editor.resetUndo();
+      this._editorSource = aSource;
       this.updateEditor();
 
       DebuggerView.Sources.selectedValue = aSource.url;
       DebuggerController.Breakpoints.updateEditorBreakpoints();
 
       // Handle any additional options for showing the source.
-      if (aOptions.targetLine) {
-        editor.setCaretPosition(aOptions.targetLine - 1);
+      if (aOptions.caretLine) {
+        editor.setCaretPosition(aOptions.caretLine - 1);
       }
       if (aOptions.debugLine) {
         editor.setDebugLocation(aOptions.debugLine - 1);
       }
       if (aOptions.callback) {
         aOptions.callback(aSource);
       }
       // Notify that we've shown a source file.
@@ -377,20 +385,22 @@ let DebuggerView = {
 
   /**
    * Sets all the panes hidden or visible.
    *
    * @param object aFlags [optional]
    *        An object containing some of the following boolean properties:
    *        - visible: true if the pane should be shown, false for hidden
    *        - animated: true to display an animation on toggle
+   *        - callback: a function to invoke when the panes toggle finishes
    */
   togglePanes: function DV__togglePanes(aFlags = {}) {
     // Avoid useless toggles.
     if (aFlags.visible == !this.panesHidden) {
+      aFlags.callback && aFlags.callback();
       return;
     }
 
     if (aFlags.visible) {
       this._stackframesAndBreakpoints.style.marginLeft = "0";
       this._variables.style.marginRight = "0";
       this._togglePanesButton.removeAttribute("panesHidden");
       this._togglePanesButton.setAttribute("tooltiptext", L10N.getStr("collapsePanes"));
@@ -409,33 +419,39 @@ let DebuggerView = {
 
       // Displaying the panes may have the effect of triggering scrollbars to
       // appear in the source editor, which would render the currently
       // highlighted line to appear behind them in some cases.
       let self = this;
 
       window.addEventListener("transitionend", function onEvent() {
         window.removeEventListener("transitionend", onEvent, false);
+        aFlags.callback && aFlags.callback();
         self.updateEditor();
       }, false);
     } else {
       this._stackframesAndBreakpoints.removeAttribute("animated");
       this._variables.removeAttribute("animated");
+      aFlags.callback && aFlags.callback();
     }
   },
 
   /**
    * Sets all the panes visible after a short period of time.
+   *
+   * @param function aCallback
+   *        A function to invoke when the panes toggle finishes.
    */
-  showPanesSoon: function DV__showPanesSoon() {
+  showPanesSoon: function DV__showPanesSoon(aCallback) {
     // Try to keep animations as smooth as possible, so wait a few cycles.
     window.setTimeout(function() {
       DebuggerView.togglePanes({
         visible: true,
-        animated: true
+        animated: true,
+        callback: aCallback
       });
     }, PANES_APPEARANCE_DELAY);
   },
 
   /**
    * Handles any initialization on a tab navigation event issued by the client.
    */
   _handleTabNavigation: function DV__handleTabNavigation() {
@@ -443,34 +459,37 @@ let DebuggerView = {
 
     this.ChromeGlobals.empty();
     this.Sources.empty();
     this.Filtering.clearSearch();
     this.GlobalSearch.clearView();
     this.GlobalSearch.clearCache();
     this.StackFrames.empty();
     this.Breakpoints.empty();
+    this.Breakpoints.unhighlightBreakpoint();
     this.Variables.empty();
     SourceUtils.clearLabelsCache();
 
     if (this.editor) {
       this.editor.setText("");
+      this._editorSource = null;
     }
   },
 
   Toolbar: null,
   Options: null,
   ChromeGlobals: null,
   Sources: null,
   Filtering: null,
   StackFrames: null,
   Breakpoints: null,
   GlobalSearch: null,
   Variables: null,
   _editor: null,
+  _editorSource: null,
   _togglePanesButton: null,
   _stackframesAndBreakpoints: null,
   _variables: null,
   _isInitialized: false,
   _isDestroyed: false
 };
 
 /**
@@ -675,17 +694,17 @@ MenuContainer.prototype = {
    */
   empty: function DVMC_empty() {
     this._preferredValue = this.selectedValue;
     this._container.selectedIndex = -1;
     this._container.setAttribute("label", this._emptyLabel);
     this._container.removeAttribute("tooltiptext");
     this._container.removeAllItems();
 
-    for (let [_, item] of this._itemsByElement) {
+    for (let [, item] of this._itemsByElement) {
       this._untangleItem(item);
     }
 
     this._itemsByLabel = new Map();
     this._itemsByValue = new Map();
     this._itemsByElement = new Map();
     this._stagedItems = [];
   },
@@ -1052,17 +1071,17 @@ MenuContainer.prototype = {
     aItem._target = null;
     return aItem;
   },
 
   /**
    * A generator-iterator over all the items in this container.
    */
   __iterator__: function DVMC_iterator() {
-    for (let [_, item] of this._itemsByElement) {
+    for (let [, item] of this._itemsByElement) {
       yield item;
     }
   },
 
   _container: null,
   _stagedItems: null,
   _itemsByLabel: null,
   _itemsByValue: null,
--- a/browser/devtools/debugger/debugger.css
+++ b/browser/devtools/debugger/debugger.css
@@ -8,24 +8,20 @@
  * Sources searching
  */
 
 #globalsearch {
   overflow-x: hidden;
   overflow-y: auto;
 }
 
-.dbg-results-container {
+.dbg-results-container:not([open]) {
   display: none;
 }
 
-.dbg-results-container[open] {
-  display: -moz-box;
-}
-
 /**
  * Stack frames
  */
 
 #stackframes {
   overflow: auto;
 }
 
@@ -43,31 +39,28 @@
   overflow: hidden;
 }
 
 /**
  * Variables view
  */
 
 #variables {
-  overflow: auto;
+  overflow-x: hidden;
+  overflow-y: auto;
 }
 
 /**
  * Scope, variable and property elements
  */
 
-#variables .details {
+#variables .details:not([open]) {
   display: none;
 }
 
-#variables .details[open] {
-  display: -moz-box;
-}
-
 .scope[non-header] > .title,
 .variable[non-header] > .title,
 .property[non-header] > .title {
   display: none;
 }
 
 /**
  * Variables and properties searching
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -38,29 +38,42 @@
     <command id="lineSearchCommand"
              oncommand="DebuggerView.Filtering._doLineSearch()"/>
     <command id="tokenSearchCommand"
              oncommand="DebuggerView.Filtering._doTokenSearch()"/>
     <command id="globalSearchCommand"
              oncommand="DebuggerView.Filtering._doGlobalSearch()"/>
     <command id="variableSearchCommand"
              oncommand="DebuggerView.Filtering._doVariableSearch()"/>
+    <command id="addBreakpointCommand"
+             oncommand="DebuggerView.Breakpoints._onCmdAddBreakpoint()"/>
+    <command id="addConditionalBreakpointCommand"
+             oncommand="DebuggerView.Breakpoints._onCmdAddConditionalBreakpoint()"/>
     <command id="togglePauseOnExceptions"
              oncommand="DebuggerView.Options._togglePauseOnExceptions()"/>
     <command id="toggleShowPanesOnStartup"
              oncommand="DebuggerView.Options._toggleShowPanesOnStartup()"/>
     <command id="toggleShowNonEnum"
              oncommand="DebuggerView.Options._toggleShowVariablesNonEnum()"/>
     <command id="toggleShowVariablesSearchbox"
              oncommand="DebuggerView.Options._toggleShowVariablesSearchbox()"/>
   </commandset>
 
   <popupset id="debuggerPopupset">
     <menupopup id="sourceEditorContextMenu"
                onpopupshowing="goUpdateSourceEditorMenuItems()">
+      <menuitem id="se-dbg-cMenu-addBreakpoint"
+                label="&debuggerUI.seMenuBreak;"
+                key="addBreakpointKey"
+                command="addBreakpointCommand"/>
+      <menuitem id="se-dbg-cMenu-addConditionalBreakpoint"
+                label="&debuggerUI.seMenuCondBreak;"
+                key="addConditionalBreakpointKey"
+                command="addConditionalBreakpointCommand"/>
+      <menuseparator/>
       <menuitem id="se-cMenu-copy"/>
       <menuseparator/>
       <menuitem id="se-cMenu-selectAll"/>
       <menuseparator/>
       <menuitem id="se-cMenu-find"/>
       <menuitem id="se-cMenu-findAgain"/>
       <menuseparator/>
       <menuitem id="se-cMenu-gotoLine"/>
@@ -123,16 +136,24 @@
     <key id="globalSearchKey"
          key="F"
          modifiers="control shift"
          command="globalSearchCommand"/>
     <key id="variableSearchKey"
          key="V"
          modifiers="control shift"
          command="variableSearchCommand"/>
+    <key id="addBreakpointKey"
+         key="B"
+         modifiers="accel"
+         command="addBreakpointCommand"/>
+    <key id="addConditionalBreakpointKey"
+         key="B"
+         modifiers="accel shift"
+         command="addConditionalBreakpointCommand"/>
   </keyset>
 
   <vbox id="body" flex="1">
     <toolbar id="dbg-toolbar" class="devtools-toolbar">
 #ifdef XP_MACOSX
       <toolbarbutton id="close"
                      class="devtools-closebutton"
                      tooltiptext="&debuggerUI.closeButton.tooltip;"/>
@@ -196,16 +217,26 @@
         <hbox align="center">
           <button id="variable-operator-button" class="operator"
                   command="variableSearchCommand"/>
           <label id="variable-operator-label" class="plain operator"/>
         </hbox>
       </vbox>
     </panel>
 
+    <panel id="conditional-breakpoint-panel"
+           type="arrow"
+           noautofocus="true"
+           position="after_start">
+      <vbox>
+        <label class="description" value="&debuggerUI.condBreakPanelTitle;"/>
+        <textbox id="conditional-breakpoint-textbox"/>
+      </vbox>
+    </panel>
+
     <vbox id="dbg-content" flex="1">
       <vbox id="globalsearch" hidden="true"/>
       <splitter id="globalsearch-splitter"
                 class="devtools-horizontal-splitter" hidden="true"/>
       <hbox flex="1">
         <vbox id="stackframes+breakpoints">
           <vbox id="stackframes" flex="1"/>
           <splitter class="devtools-horizontal-splitter"/>
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -34,16 +34,19 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_propertyview-10.js \
 	browser_dbg_propertyview-edit.js \
 	browser_dbg_propertyview-data.js \
 	browser_dbg_propertyview-filter-01.js \
 	browser_dbg_propertyview-filter-02.js \
 	browser_dbg_propertyview-filter-03.js \
 	browser_dbg_propertyview-filter-04.js \
 	browser_dbg_propertyview-filter-05.js \
+	browser_dbg_propertyview-filter-06.js \
+	browser_dbg_propertyview-filter-07.js \
+	browser_dbg_propertyview-filter-08.js \
 	browser_dbg_propertyview-reexpand.js \
 	browser_dbg_reload-same-script.js \
 	browser_dbg_pane-collapse.js \
 	browser_dbg_panesize.js \
 	browser_dbg_panesize-inner.js \
 	browser_dbg_stack-01.js \
 	browser_dbg_stack-02.js \
 	browser_dbg_stack-03.js \
@@ -64,16 +67,18 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_scripts-searching-08.js \
 	browser_dbg_scripts-searching-popup.js \
 	browser_dbg_pause-resume.js \
 	browser_dbg_update-editor-mode.js \
 	$(warning browser_dbg_select-line.js temporarily disabled due to oranges, see bug 726609) \
 	browser_dbg_clean-exit.js \
 	browser_dbg_bug723069_editor-breakpoints.js \
 	browser_dbg_bug723071_editor-breakpoints-pane.js \
+	browser_dbg_bug740825_conditional-breakpoints-01.js \
+	browser_dbg_bug740825_conditional-breakpoints-02.js \
 	browser_dbg_bug731394_editor-contextmenu.js \
 	browser_dbg_bug786070_hide_nonenums.js \
 	browser_dbg_displayName.js \
 	browser_dbg_iframes.js \
 	browser_dbg_pause-exceptions.js \
 	browser_dbg_multiple-windows.js \
 	browser_dbg_menustatus.js \
 	browser_dbg_bfcache.js \
@@ -97,13 +102,14 @@ MOCHITEST_BROWSER_PAGES = \
 	browser_dbg_frame-parameters.html \
 	browser_dbg_update-editor-mode.html \
 	test-editor-mode \
 	browser_dbg_displayName.html \
 	browser_dbg_iframes.html \
 	browser_dbg_with-frame.html \
 	browser_dbg_pause-exceptions.html \
 	browser_dbg_breakpoint-new-script.html \
+	browser_dbg_conditional-breakpoints.html \
 	$(NULL)
 
 MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug723071_editor-breakpoints-pane.js
@@ -168,17 +168,17 @@ function test()
           function(cl, err) {
           onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
 
           line = 7;
           gPane.addBreakpoint({url: gScripts.selectedValue, line: line},
             function(cl, err) {
             onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
 
-            line = 8;
+            line = 9;
             gPane.addBreakpoint({url: gScripts.selectedValue, line: line},
               function(cl, err) {
               onBreakpointAdd.call({ increment: increment, line: line }, cl, err);
 
               executeSoon(function() {
                 callback();
               });
             });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-01.js
@@ -0,0 +1,384 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 740825: test the debugger conditional breakpoints.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_conditional-breakpoints.html";
+
+let gPane = null;
+let gTab = null;
+let gDebuggee = null;
+let gDebugger = null;
+let gScripts = null;
+let gEditor = null;
+let gBreakpoints = null;
+let gBreakpointsPane = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  let scriptShown = false;
+  let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.contentWindow;
+    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    gBreakpointsPane = gDebugger.DebuggerView.Breakpoints;
+
+    gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
+    resumed = true;
+
+    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+      framesAdded = true;
+      executeSoon(startTest);
+    });
+
+    executeSoon(function() {
+      gDebuggee.ermahgerd(); // ermahgerd!!
+    });
+  });
+
+  function onScriptShown(aEvent)
+  {
+    scriptShown = aEvent.detail.url.indexOf("conditional-breakpoints") != -1;
+    executeSoon(startTest);
+  }
+
+  window.addEventListener("Debugger:SourceShown", onScriptShown);
+
+  function startTest()
+  {
+    if (scriptShown && framesAdded && resumed && !testStarted) {
+      window.removeEventListener("Debugger:SourceShown", onScriptShown);
+      testStarted = true;
+      Services.tm.currentThread.dispatch({ run: addBreakpoints }, 0);
+    }
+  }
+
+  function performTest()
+  {
+    gScripts = gDebugger.DebuggerView.Sources;
+
+    is(gDebugger.DebuggerController.activeThread.state, "paused",
+      "Should only be getting stack frames while paused.");
+
+    is(gScripts._container.itemCount, 1, "Found the expected number of scripts.");
+
+    gEditor = gDebugger.editor;
+
+    isnot(gEditor.getText().indexOf("ermahgerd"), -1,
+          "The correct script was loaded initially.");
+    is(gScripts.selectedValue, gScripts.values[0],
+          "The correct script is selected");
+
+    gBreakpoints = gPane.breakpoints;
+    is(Object.keys(gBreakpoints).length, 13, "thirteen breakpoints");
+    ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
+
+    is(gEditor.getBreakpoints().length, 13, "thirteen breakpoints in the editor");
+
+    executeSoon(test1);
+  }
+
+  function test1(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 14, test2);
+  }
+
+  function test2(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 15, test3);
+  }
+
+  function test3(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 16, test4);
+  }
+
+  function test4(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 17, test5);
+  }
+
+  function test5(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 18, test6);
+  }
+
+  function test6(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 19, test7);
+  }
+
+  function test7(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 21, test8);
+  }
+
+  function test8(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 22, test9);
+  }
+
+  function test9(callback)
+  {
+    resumeAndTestBreakpoint(gScripts.selectedValue, 23, test10);
+  }
+
+  function test10(callback)
+  {
+    gDebugger.addEventListener("Debugger:AfterFramesCleared", function listener() {
+      gDebugger.removeEventListener("Debugger:AfterFramesCleared", listener, true);
+
+      is(gBreakpointsPane.selectedItem, null,
+        "There should be no selected breakpoint in the breakpoints pane.")
+      is(gBreakpointsPane._popupShown, false,
+        "The breakpoint conditional expression popup should not be shown.");
+
+      is(gDebugger.DebuggerView.StackFrames.visibleItems, 0,
+        "There should be no visible stackframes.");
+      is(gDebugger.DebuggerView.Breakpoints.visibleItems, 13,
+        "There should be thirteen visible breakpoints.");
+
+      testReload();
+    }, true);
+
+    gDebugger.DebuggerController.activeThread.resume();
+  }
+
+  function resumeAndTestBreakpoint(url, line, callback)
+  {
+    resume(line, function() {
+      waitForCaretPos(line - 1, function() {
+        testBreakpoint(gBreakpointsPane.selectedItem, gBreakpointsPane.selectedClient, url, line, true);
+        callback();
+      });
+    });
+  }
+
+  function testBreakpoint(aBreakpointItem, aBreakpointClient, url, line, editor)
+  {
+    is(aBreakpointItem.attachment.sourceLocation, gScripts.selectedValue,
+      "The breakpoint on line " + line + " wasn't added on the correct source.");
+    is(aBreakpointItem.attachment.lineNumber, line,
+      "The breakpoint on line " + line + " wasn't found.");
+    is(aBreakpointItem.attachment.enabled, true,
+      "The breakpoint on line " + line + " should be enabled.");
+    is(aBreakpointItem.attachment.isConditional, true,
+      "The breakpoint on line " + line + " should be conditional.");
+    is(gBreakpointsPane._popupShown, false,
+      "The breakpoint conditional expression popup should not be shown.");
+
+    is(aBreakpointClient.location.url, url,
+       "The breakpoint's client url is correct");
+    is(aBreakpointClient.location.line, line,
+       "The breakpoint's client line is correct");
+    isnot(aBreakpointClient.conditionalExpression, undefined,
+      "The breakpoint on line " + line + " should have a conditional expression.");
+
+    if (editor) {
+      is(gEditor.getCaretPosition().line + 1, line,
+        "The editor caret position is not situated on the proper line.");
+      is(gEditor.getCaretPosition().col, 0,
+        "The editor caret position is not situated on the proper column.");
+    }
+  }
+
+  function addBreakpoints(callback)
+  {
+    let currentUrl = gDebugger.DebuggerView.Sources.selectedValue;
+
+    gPane.addBreakpoint({ url: currentUrl, line: 12 }, function() {
+      gPane.addBreakpoint({ url: currentUrl, line: 13 }, function() {
+        gPane.addBreakpoint({ url: currentUrl, line: 14 }, function() {
+          gPane.addBreakpoint({ url: currentUrl, line: 15 }, function() {
+            gPane.addBreakpoint({ url: currentUrl, line: 16 }, function() {
+              gPane.addBreakpoint({ url: currentUrl, line: 17 }, function() {
+                gPane.addBreakpoint({ url: currentUrl, line: 18 }, function() {
+                  gPane.addBreakpoint({ url: currentUrl, line: 19 }, function() {
+                    gPane.addBreakpoint({ url: currentUrl, line: 20 }, function() {
+                      gPane.addBreakpoint({ url: currentUrl, line: 21 }, function() {
+                        gPane.addBreakpoint({ url: currentUrl, line: 22 }, function() {
+                          gPane.addBreakpoint({ url: currentUrl, line: 23 }, function() {
+                            gPane.addBreakpoint({ url: currentUrl, line: 24 }, function() {
+                              performTest();
+                            }, {
+                              conditionalExpression: "b"
+                            });
+                          }, {
+                            conditionalExpression: "a !== null"
+                          });
+                        }, {
+                          conditionalExpression: "a !== undefined"
+                        });
+                      }, {
+                        conditionalExpression: "a"
+                      });
+                    }, {
+                      conditionalExpression: "(function() { return false; })()"
+                    });
+                  }, {
+                    conditionalExpression: "function() {}"
+                  });
+                }, {
+                  conditionalExpression: "{}"
+                });
+              }, {
+                conditionalExpression: "/regexp/"
+              });
+            }, {
+              conditionalExpression: "'nasu'"
+            });
+          }, {
+            conditionalExpression: "true"
+          });
+        }, {
+          conditionalExpression: "42"
+        });
+      }, {
+        conditionalExpression: "null"
+      });
+    }, {
+      conditionalExpression: "undefined"
+    });
+  }
+
+  function testReload()
+  {
+    function _get(url, line) {
+      return [
+        gDebugger.DebuggerView.Breakpoints.getBreakpoint(url, line),
+        gDebugger.DebuggerController.Breakpoints.getBreakpoint(url, line),
+        url, line, false
+      ];
+    }
+
+    waitForBreakpoints(13, function() {
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 14));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 15));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 16));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 17));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 18));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 19));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 21));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 22));
+      testBreakpoint.apply(this, _get(gScripts.selectedValue, 23));
+
+      is(gBreakpointsPane.selectedItem, null,
+        "There should be no selected item in the breakpoints pane.");
+      is(gBreakpointsPane.selectedClient, null,
+        "There should be no selected client in the breakpoints pane.");
+
+      closeDebuggerAndFinish();
+    });
+
+    finalCheck();
+    gDebuggee.location.reload();
+  }
+
+  function finalCheck() {
+    isnot(gEditor.getText().indexOf("ermahgerd"), -1,
+          "The correct script is still loaded.");
+    is(gScripts.selectedValue, gScripts.values[0],
+          "The correct script is still selected");
+  }
+
+  function resume(expected, callback) {
+    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+      Services.tm.currentThread.dispatch({ run: function() {
+        waitForBreakpoint(expected, callback);
+      }}, 0);
+    });
+
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      gDebugger.document.getElementById("resume"),
+      gDebugger);
+  }
+
+  let bogusClient = {
+    location: {
+      url: null,
+      line: null
+    }
+  };
+
+  function waitForBreakpoint(expected, callback) {
+    // Poll every few milliseconds until expected breakpoint is hit.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the breakpoint.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if ((gBreakpointsPane.selectedClient !== expected) &&
+          (gBreakpointsPane.selectedClient || bogusClient).location.line !== expected) {
+        return;
+      }
+      // We arrived at the expected line, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  function waitForBreakpoints(total, callback)
+  {
+    // Poll every few milliseconds until the breakpoints are retrieved.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the breakpoints.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if (gBreakpointsPane.visibleItems != total) {
+        return;
+      }
+      // We got all the breakpoints, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  function waitForCaretPos(number, callback)
+  {
+    // Poll every few milliseconds until the source editor line is active.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the line.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if (gEditor.getCaretPosition().line != number) {
+        return;
+      }
+      // We got the source editor at the expected line, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  registerCleanupFunction(function() {
+    removeTab(gTab);
+    gPane = null;
+    gTab = null;
+    gDebuggee = null;
+    gDebugger = null;
+    gScripts = null;
+    gEditor = null;
+    gBreakpoints = null;
+    gBreakpointsPane = null;
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_bug740825_conditional-breakpoints-02.js
@@ -0,0 +1,511 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 740825: test the debugger conditional breakpoints.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_conditional-breakpoints.html";
+
+let gPane = null;
+let gTab = null;
+let gDebuggee = null;
+let gDebugger = null;
+let gScripts = null;
+let gEditor = null;
+let gBreakpoints = null;
+let gBreakpointsPane = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  let tempScope = {};
+  Cu.import("resource:///modules/source-editor.jsm", tempScope);
+  let SourceEditor = tempScope.SourceEditor;
+
+  let scriptShown = false;
+  let framesAdded = false;
+  let resumed = false;
+  let testStarted = false;
+
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gDebuggee = aDebuggee;
+    gPane = aPane;
+    gDebugger = gPane.contentWindow;
+    gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+    gBreakpointsPane = gDebugger.DebuggerView.Breakpoints;
+
+    gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
+    resumed = true;
+
+    gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
+      framesAdded = true;
+      executeSoon(startTest);
+    });
+
+    executeSoon(function() {
+      gDebuggee.ermahgerd(); // ermahgerd!!
+    });
+  });
+
+  function onScriptShown(aEvent)
+  {
+    scriptShown = aEvent.detail.url.indexOf("conditional-breakpoints") != -1;
+    executeSoon(startTest);
+  }
+
+  window.addEventListener("Debugger:SourceShown", onScriptShown);
+
+  function startTest()
+  {
+    if (scriptShown && framesAdded && resumed && !testStarted) {
+      window.removeEventListener("Debugger:SourceShown", onScriptShown);
+      testStarted = true;
+      Services.tm.currentThread.dispatch({ run: performTest }, 0);
+    }
+  }
+
+  function performTest()
+  {
+    gScripts = gDebugger.DebuggerView.Sources;
+
+    is(gDebugger.DebuggerController.activeThread.state, "paused",
+      "Should only be getting stack frames while paused.");
+
+    is(gScripts._container.itemCount, 1, "Found the expected number of scripts.");
+
+    gEditor = gDebugger.editor;
+
+    isnot(gEditor.getText().indexOf("ermahgerd"), -1,
+          "The correct script was loaded initially.");
+    is(gScripts.selectedValue, gScripts.values[0],
+          "The correct script is selected");
+
+    gBreakpoints = gPane.breakpoints;
+    is(Object.keys(gBreakpoints), 0, "no breakpoints");
+    ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
+
+    is(gEditor.getBreakpoints().length, 0, "no breakpoints in the editor");
+
+    executeSoon(addBreakpoint1);
+  }
+
+  function addBreakpoint1()
+  {
+    gPane.addBreakpoint({ url: gScripts.selectedValue, line: 12 });
+
+    waitForBreakpoint(12, function() {
+      waitForCaretPos(10, function() {
+        waitForPopup(false, function() {
+          testBreakpoint(gBreakpointsPane.selectedItem,
+                         gBreakpointsPane.selectedClient,
+                         gScripts.selectedValue, 12, false, false, false);
+
+          executeSoon(addBreakpoint2);
+        });
+      });
+    });
+  }
+
+  function addBreakpoint2()
+  {
+    gBreakpointsPane._editorContextMenuLineNumber = 12;
+    gBreakpointsPane._onCmdAddBreakpoint();
+
+    waitForBreakpoint(13, function() {
+      waitForCaretPos(12, function() {
+        waitForPopup(false, function() {
+          testBreakpoint(gBreakpointsPane.selectedItem,
+                         gBreakpointsPane.selectedClient,
+                         gScripts.selectedValue, 13, false, false, true);
+
+          executeSoon(modBreakpoint2);
+        });
+      });
+    });
+  }
+
+  function modBreakpoint2()
+  {
+    gBreakpointsPane._editorContextMenuLineNumber = 12;
+    gBreakpointsPane._onCmdAddConditionalBreakpoint();
+
+    waitForBreakpoint(13, function() {
+      waitForCaretPos(12, function() {
+        waitForPopup(true, function() {
+          testBreakpoint(gBreakpointsPane.selectedItem,
+                         gBreakpointsPane.selectedClient,
+                         gScripts.selectedValue, 13, true, true, true);
+
+          executeSoon(addBreakpoint3);
+        });
+      });
+    });
+  }
+
+  function addBreakpoint3()
+  {
+    gBreakpointsPane._editorContextMenuLineNumber = 13;
+    gBreakpointsPane._onCmdAddConditionalBreakpoint();
+
+    waitForBreakpoint(14, function() {
+      waitForCaretPos(13, function() {
+        waitForPopup(true, function() {
+          testBreakpoint(gBreakpointsPane.selectedItem,
+                         gBreakpointsPane.selectedClient,
+                         gScripts.selectedValue, 14, true, true, true);
+
+          executeSoon(modBreakpoint3);
+        });
+      });
+    });
+  }
+
+  function modBreakpoint3()
+  {
+    write("bamboocha");
+    EventUtils.sendKey("RETURN");
+
+    waitForBreakpoint(14, function() {
+      waitForCaretPos(13, function() {
+        waitForPopup(false, function() {
+          is(gBreakpointsPane.selectedClient.conditionalExpression, "bamboocha",
+            "The bamboocha expression wasn't fonud on the conditional breakpoint");
+
+          executeSoon(testHighlights1);
+        });
+      });
+    });
+  }
+
+  function testHighlights1()
+  {
+    isnot(gBreakpointsPane.selectedItem, null,
+      "There should be a selected breakpoint in the breakpoints pane.");
+    is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
+      "The selected breakpoint should have the correct location.");
+    is(gBreakpointsPane.selectedItem.attachment.lineNumber, 14,
+      "The selected breakpoint should have the correct line number.");
+    is(gBreakpointsPane._popupShown, false,
+      "The breakpoint conditional expression popup should not be shown.");
+    is(gEditor.getCaretPosition().line, 13,
+      "The source editor caret position should be at line 13");
+    is(gEditor.getCaretPosition().col, 0,
+      "The source editor caret position should be at column 0");
+
+    gEditor.setCaretPosition(12);
+
+    waitForCaretPos(12, function() {
+      waitForPopup(false, function() {
+        isnot(gBreakpointsPane.selectedItem, null,
+          "There should be a selected breakpoint in the breakpoints pane.");
+        is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
+          "The selected breakpoint should have the correct location.");
+        is(gBreakpointsPane.selectedItem.attachment.lineNumber, 13,
+          "The selected breakpoint should have the correct line number.");
+        is(gBreakpointsPane._popupShown, false,
+          "The breakpoint conditional expression popup should not be shown.");
+        is(gEditor.getCaretPosition().line, 12,
+          "The source editor caret position should be at line 12");
+        is(gEditor.getCaretPosition().col, 0,
+          "The source editor caret position should be at column 0");
+
+        gEditor.setCaretPosition(11);
+
+        waitForCaretPos(11, function() {
+          waitForPopup(false, function() {
+            isnot(gBreakpointsPane.selectedItem, null,
+              "There should be a selected breakpoint in the breakpoints pane.");
+            is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
+              "The selected breakpoint should have the correct location.");
+            is(gBreakpointsPane.selectedItem.attachment.lineNumber, 12,
+              "The selected breakpoint should have the correct line number.");
+            is(gBreakpointsPane._popupShown, false,
+              "The breakpoint conditional expression popup should not be shown.");
+            is(gEditor.getCaretPosition().line, 11,
+              "The source editor caret position should be at line 11");
+            is(gEditor.getCaretPosition().col, 0,
+              "The source editor caret position should be at column 0");
+
+            gEditor.setCaretPosition(10);
+
+            waitForCaretPos(10, function() {
+              waitForPopup(false, function() {
+                is(gBreakpointsPane.selectedItem, null,
+                  "There should not be a selected breakpoint in the breakpoints pane.");
+                is(gBreakpointsPane._popupShown, false,
+                  "The breakpoint conditional expression popup should not be shown.");
+                is(gEditor.getCaretPosition().line, 10,
+                  "The source editor caret position should be at line 10");
+                is(gEditor.getCaretPosition().col, 0,
+                  "The source editor caret position should be at column 0");
+
+                gEditor.setCaretPosition(14);
+
+                waitForCaretPos(14, function() {
+                  waitForPopup(false, function() {
+                    is(gBreakpointsPane.selectedItem, null,
+                      "There should not be a selected breakpoint in the breakpoints pane.");
+                    is(gBreakpointsPane._popupShown, false,
+                      "The breakpoint conditional expression popup should not be shown.");
+                    is(gEditor.getCaretPosition().line, 14,
+                      "The source editor caret position should be at line 14");
+                    is(gEditor.getCaretPosition().col, 0,
+                      "The source editor caret position should be at column 0");
+
+                    executeSoon(testHighlights2);
+                  });
+                });
+              });
+            });
+          });
+        });
+      });
+    });
+  }
+
+  function testHighlights2()
+  {
+    EventUtils.sendMouseEvent({ type: "click" },
+      gBreakpointsPane._container.getItemAtIndex(2),
+      gDebugger);
+
+    waitForCaretPos(13, function() {
+      waitForPopup(true, function() {
+        isnot(gBreakpointsPane.selectedItem, null,
+          "There should be a selected breakpoint in the breakpoints pane.");
+        is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
+          "The selected breakpoint should have the correct location.");
+        is(gBreakpointsPane.selectedItem.attachment.lineNumber, 14,
+          "The selected breakpoint should have the correct line number.");
+        is(gBreakpointsPane._popupShown, true,
+          "The breakpoint conditional expression popup should be shown.");
+        is(gEditor.getCaretPosition().line, 13,
+          "The source editor caret position should be at line 13");
+        is(gEditor.getCaretPosition().col, 0,
+          "The source editor caret position should be at column 0");
+
+        EventUtils.sendMouseEvent({ type: "click" },
+          gBreakpointsPane._container.getItemAtIndex(1),
+          gDebugger);
+
+        waitForCaretPos(12, function() {
+          waitForPopup(true, function() {
+            isnot(gBreakpointsPane.selectedItem, null,
+              "There should be a selected breakpoint in the breakpoints pane.");
+            is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
+              "The selected breakpoint should have the correct location.");
+            is(gBreakpointsPane.selectedItem.attachment.lineNumber, 13,
+              "The selected breakpoint should have the correct line number.");
+            is(gBreakpointsPane._popupShown, true,
+              "The breakpoint conditional expression popup should be shown.");
+            is(gEditor.getCaretPosition().line, 12,
+              "The source editor caret position should be at line 12");
+            is(gEditor.getCaretPosition().col, 0,
+              "The source editor caret position should be at column 0");
+
+            EventUtils.sendMouseEvent({ type: "click" },
+              gBreakpointsPane._container.getItemAtIndex(0),
+              gDebugger);
+
+            waitForCaretPos(11, function() {
+              waitForPopup(false, function() {
+                isnot(gBreakpointsPane.selectedItem, null,
+                  "There should be a selected breakpoint in the breakpoints pane.");
+                is(gBreakpointsPane.selectedItem.attachment.sourceLocation, gScripts.selectedValue,
+                  "The selected breakpoint should have the correct location.");
+                is(gBreakpointsPane.selectedItem.attachment.lineNumber, 12,
+                  "The selected breakpoint should have the correct line number.");
+                is(gBreakpointsPane._popupShown, false,
+                  "The breakpoint conditional expression popup should be shown.");
+                is(gEditor.getCaretPosition().line, 11,
+                  "The source editor caret position should be at line 11");
+                is(gEditor.getCaretPosition().col, 0,
+                  "The source editor caret position should be at column 0");
+
+                executeSoon(delBreakpoint2);
+              });
+            });
+          });
+        });
+      });
+    });
+  }
+
+  function delBreakpoint2()
+  {
+    gBreakpointsPane._editorContextMenuLineNumber = 12;
+    gBreakpointsPane._onCmdAddBreakpoint();
+
+    waitForBreakpoint(null, function() {
+      waitForPopup(false, function() {
+        is(gBreakpointsPane.selectedItem, null,
+          "There should be no selected breakpoint in the breakpoints pane.")
+        is(gBreakpointsPane._popupShown, false,
+          "The breakpoint conditional expression popup should not be shown.");
+
+        executeSoon(delBreakpoint3);
+      });
+    });
+  }
+
+  function delBreakpoint3()
+  {
+    gBreakpointsPane._editorContextMenuLineNumber = 13;
+    gBreakpointsPane._onCmdAddBreakpoint();
+
+    waitForBreakpoint(null, function() {
+      waitForPopup(false, function() {
+        is(gBreakpointsPane.selectedItem, null,
+          "There should be no selected breakpoint in the breakpoints pane.")
+        is(gBreakpointsPane._popupShown, false,
+          "The breakpoint conditional expression popup should not be shown.");
+
+        executeSoon(testBreakpoints);
+      });
+    });
+  }
+
+  function testBreakpoints()
+  {
+    is(Object.keys(gBreakpoints).length, 1, "one breakpoint");
+    ok(!gPane.getBreakpoint("foo", 3), "getBreakpoint('foo', 3) returns falsey");
+
+    is(gEditor.getBreakpoints().length, 1, "one breakpoint in the editor");
+
+    closeDebuggerAndFinish();
+  }
+
+  function testBreakpoint(aBreakpointItem, aBreakpointClient, url, line, conditional, popup, editor)
+  {
+    is(aBreakpointItem.attachment.sourceLocation, gScripts.selectedValue,
+      "The breakpoint on line " + line + " wasn't added on the correct source.");
+    is(aBreakpointItem.attachment.lineNumber, line,
+      "The breakpoint on line " + line + " wasn't found.");
+    is(aBreakpointItem.attachment.enabled, true,
+      "The breakpoint on line " + line + " should be enabled.");
+    is(aBreakpointItem.attachment.isConditional, conditional,
+      "The breakpoint on line " + line + " should " + (conditional ? "" : "not ") + "be conditional.");
+    is(gBreakpointsPane._popupShown, conditional,
+      "The breakpoint conditional expression popup should" + (conditional ? "" : "not ") + "be shown.");
+
+    is(aBreakpointClient.location.url, url,
+       "The breakpoint's client url is correct");
+    is(aBreakpointClient.location.line, line,
+       "The breakpoint's client line is correct");
+
+    if (conditional) {
+      isnot(aBreakpointClient.conditionalExpression, undefined,
+        "The breakpoint on line " + line + " should have a conditional expression.");
+    } else {
+      is(aBreakpointClient.conditionalExpression, undefined,
+        "The breakpoint on line " + line + " should not have a conditional expression.");
+    }
+
+    if (editor) {
+      is(gEditor.getCaretPosition().line + 1, line,
+        "The editor caret position is not situated on the proper line.");
+      is(gEditor.getCaretPosition().col, 0,
+        "The editor caret position is not situated on the proper column.");
+    }
+  }
+
+  let bogusClient = {
+    location: {
+      url: null,
+      line: null
+    }
+  };
+
+  function waitForBreakpoint(expected, callback) {
+    // Poll every few milliseconds until expected breakpoint is hit.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the breakpoint.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if ((gBreakpointsPane.selectedClient !== expected) &&
+          (gBreakpointsPane.selectedClient || bogusClient).location.line !== expected) {
+        return;
+      }
+      // We arrived at the expected line, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  function waitForCaretPos(number, callback)
+  {
+    // Poll every few milliseconds until the source editor line is active.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the line.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if (gEditor.getCaretPosition().line != number) {
+        return;
+      }
+      // We got the source editor at the expected line, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  function waitForPopup(state, callback)
+  {
+    // Poll every few milliseconds until the expression popup is shown.
+    let count = 0;
+    let intervalID = window.setInterval(function() {
+      info("count: " + count + " ");
+      if (++count > 50) {
+        ok(false, "Timed out while polling for the popup.");
+        window.clearInterval(intervalID);
+        return closeDebuggerAndFinish();
+      }
+      if (gBreakpointsPane._popupShown != state) {
+        return;
+      }
+      // We got the expression popup at the expected state, it's safe to callback.
+      window.clearInterval(intervalID);
+      callback();
+    }, 100);
+  }
+
+  function clear() {
+    gBreakpointsPane._cbTextbox.focus();
+    gBreakpointsPane._cbTextbox.value = "";
+  }
+
+  function write(text) {
+    clear();
+    append(text);
+  }
+
+  function append(text) {
+    gBreakpointsPane._cbTextbox.focus();
+
+    for (let i = 0; i < text.length; i++) {
+      EventUtils.sendChar(text[i]);
+    }
+  }
+
+  registerCleanupFunction(function() {
+    removeTab(gTab);
+    gPane = null;
+    gTab = null;
+    gDebuggee = null;
+    gDebugger = null;
+    gScripts = null;
+    gEditor = null;
+    gBreakpoints = null;
+    gBreakpointsPane = null;
+  });
+}
--- a/browser/devtools/debugger/test/browser_dbg_bug786070_hide_nonenums.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug786070_hide_nonenums.js
@@ -31,78 +31,92 @@ function testNonEnumProperties() {
         },
 
         bar: {
           value: "foo",
           enumerable: false
         }
       });
 
+      // Expand the variable.
       testScope.expand();
       testVar.expand();
 
-      let details = testVar._enum;
-      let nonenum = testVar._nonenum;
+      executeSoon(function() {
+        let details = testVar._enum;
+        let nonenum = testVar._nonenum;
 
-      is(details.childNodes.length, 1,
-        "There should be just one property in the .details container.");
+        is(details.childNodes.length, 1,
+          "There should be just one property in the .details container.");
 
-      ok(details.hasAttribute("open"),
-        ".details container should be visible.");
+        ok(details.hasAttribute("open"),
+          ".details container should be visible.");
 
-      is(nonenum.childNodes.length, 1,
-        "There should be just one property in the .nonenum container.");
+        ok(nonenum.hasAttribute("open"),
+          ".nonenum container should be visible.");
+
+        is(nonenum.childNodes.length, 1,
+          "There should be just one property in the .nonenum container.");
 
-      ok(nonenum.hasAttribute("open"),
-        ".nonenum container should be visible.");
+        // Uncheck 'show hidden properties'.
+        gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
+        gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
 
-      // Uncheck 'show hidden properties'.
-      gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
-      gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
+        executeSoon(function() {
+          ok(details.hasAttribute("open"),
+            ".details container should stay visible.");
 
-      ok(details.hasAttribute("open"),
-        ".details container should stay visible.");
+          ok(!nonenum.hasAttribute("open"),
+            ".nonenum container should become hidden.");
 
-      ok(!nonenum.hasAttribute("open"),
-        ".nonenum container should become hidden.");
+          // Check 'show hidden properties'.
+          gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
+          gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
 
-      // Check 'show hidden properties'.
-      gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
-      gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
+          executeSoon(function() {
+            ok(details.hasAttribute("open"),
+              ".details container should stay visible.");
 
-      ok(details.hasAttribute("open"),
-        ".details container should stay visible.");
+            ok(nonenum.hasAttribute("open"),
+              ".nonenum container should become visible.");
 
-      ok(nonenum.hasAttribute("open"),
-        ".nonenum container should become visible.");
+            // Collapse the variable.
+            testVar.collapse();
 
-      testVar.collapse();
+            executeSoon(function() {
+              ok(!details.hasAttribute("open"),
+                ".details container should be hidden.");
 
-      ok(!details.hasAttribute("open"),
-        ".details container should be hidden.");
+              ok(!nonenum.hasAttribute("open"),
+                ".nonenum container should be hidden.");
 
-      ok(!nonenum.hasAttribute("open"),
-        ".nonenum container should be hidden.");
+              // Uncheck 'show hidden properties'.
+              gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
+              gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
 
-      // Uncheck 'show hidden properties'.
-      gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "false");
-      gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
+              executeSoon(function() {
+                ok(!details.hasAttribute("open"),
+                  ".details container should stay hidden.");
 
-      ok(!details.hasAttribute("open"),
-        ".details container should stay hidden.");
+                ok(!nonenum.hasAttribute("open"),
+                  ".nonenum container should stay hidden.");
 
-      ok(!nonenum.hasAttribute("open"),
-        ".nonenum container should stay hidden.");
+                // Check 'show hidden properties'.
+                gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
+                gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
 
-      // Check 'show hidden properties'.
-      gDebugger.DebuggerView.Options._showVariablesNonEnumItem.setAttribute("checked", "true");
-      gDebugger.DebuggerView.Options._toggleShowVariablesNonEnum();
-
-      gDebugger.DebuggerController.activeThread.resume(function() {
-        closeDebuggerAndFinish();
+                executeSoon(function() {
+                  gDebugger.DebuggerController.activeThread.resume(function() {
+                    closeDebuggerAndFinish();
+                  });
+                });
+              });
+            });
+          });
+        });
       });
     }}, 0);
   });
 
   gDebuggee.simpleCall();
 }
 
 registerCleanupFunction(function() {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_conditional-breakpoints.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset='utf-8'/>
+    <title>Browser Debugger Conditional Breakpoints Test</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+         http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript">
+      function ermahgerd() {
+        var a = {};
+        debugger;
+        console.log("undefined");
+        console.log("null");
+        console.log("42");
+        console.log("true");
+        console.log("'nasu'");
+        console.log("/regexp/");
+        console.log("{}");
+        console.log("function() {}");
+        console.log("(function { return false; })()");
+        console.log("a");
+        console.log("a !== undefined");
+        console.log("a !== null");
+        console.log("b");
+      }
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
--- a/browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
+++ b/browser/devtools/debugger/test/browser_dbg_pause-exceptions.js
@@ -8,23 +8,27 @@
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_pause-exceptions.html";
 
 var gPane = null;
 var gTab = null;
 var gDebugger = null;
 var gCount = 0;
 
+requestLongerTimeout(2);
+
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    gDebugger.DebuggerView.Variables.nonEnumVisible = false;
     testWithFrame();
   });
 }
 
 function testWithFrame()
 {
   gPane.contentWindow.gClient.addOneTimeListener("paused", function() {
     gDebugger.addEventListener("Debugger:FetchedVariables", function testA() {
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -87,19 +87,19 @@ function testFrameParameters()
       let count = 0;
       let intervalID = window.setInterval(function(){
         info("count: " + count + " ");
         if (++count > 50) {
           ok(false, "Timed out while polling for the properties.");
           window.clearInterval(intervalID);
           return resumeAndFinish();
         }
-        if (!thisNode.fetched ||
-            !argumentsNode.fetched ||
-            !cNode.fetched) {
+        if (!thisNode._retrieved ||
+            !argumentsNode._retrieved ||
+            !cNode._retrieved) {
           return;
         }
         window.clearInterval(intervalID);
         is(thisNode.target.querySelector(".property > .title > .name")
                         .getAttribute("value"), "InstallTrigger",
           "Should have the right property name for InstallTrigger.");
         ok(thisNode.target.querySelector(".property > .title > .value")
                         .getAttribute("value").search(/object/) == -1,
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
@@ -7,23 +7,27 @@
  */
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
 
 var gPane = null;
 var gTab = null;
 var gDebugger = null;
 
+requestLongerTimeout(2);
+
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    gDebugger.DebuggerView.Variables.nonEnumVisible = false;
     testFrameParameters();
   });
 }
 
 function testFrameParameters()
 {
   let count = 0;
   gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
@@ -7,23 +7,27 @@
  */
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
 
 var gPane = null;
 var gTab = null;
 var gDebugger = null;
 
+requestLongerTimeout(2);
+
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    gDebugger.DebuggerView.Variables.nonEnumVisible = false;
     testWithFrame();
   });
 }
 
 function testWithFrame()
 {
   let count = 0;
   gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
@@ -1,27 +1,32 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /*
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
+
 var gPane = null;
 var gTab = null;
 var gDebuggee = null;
 var gDebugger = null;
 
-const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
+requestLongerTimeout(2);
 
 function test() {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    gDebugger.DebuggerView.Variables.nonEnumVisible = false;
     testFrameEval();
   });
 }
 
 function testFrameEval() {
   gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
     gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
     Services.tm.currentThread.dispatch({ run: function() {
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-01.js
@@ -19,16 +19,17 @@ requestLongerTimeout(2);
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
     gDebuggee = aDebuggee;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
     testSearchbox();
     prepareVariables(testVariablesFiltering);
   });
 }
 
 function testSearchbox()
 {
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
@@ -116,16 +117,18 @@ function testVariablesFiltering()
     is(windowItem.expanded, true,
       "The local scope 'this.window' should be expanded");
     is(documentItem.expanded, true,
       "The local scope 'this.window.document' should be expanded");
     is(locationItem.expanded, true,
       "The local scope 'this.window.document.location' should be expanded");
 
     ignoreExtraMatchedProperties();
+    locationItem.toggle();
+    locationItem.toggle();
 
     is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
       "There should be 1 variable displayed in the inner scope");
     is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the math scope");
     is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the test scope");
     is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
@@ -202,16 +205,18 @@ function testVariablesFiltering()
     is(windowItem.expanded, true,
       "The local scope 'this.window' should be expanded");
     is(documentItem.expanded, true,
       "The local scope 'this.window.document' should be expanded");
     is(locationItem.expanded, true,
       "The local scope 'this.window.document.location' should be expanded");
 
     ignoreExtraMatchedProperties();
+    locationItem.toggle();
+    locationItem.toggle();
 
     is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
       "There should be 1 variable displayed in the inner scope");
     is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the math scope");
     is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the test scope");
     is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
@@ -314,16 +319,32 @@ function prepareVariables(aCallback)
         testScope.querySelector(".name").getAttribute("value"));
       let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
         loadScope.querySelector(".name").getAttribute("value"));
       let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
         globalScope.querySelector(".name").getAttribute("value"));
 
       is(innerScopeItem.expanded, true,
         "The innerScope expanded getter should return true");
+      is(mathScopeItem.expanded, true,
+        "The mathScope expanded getter should return true");
+      is(testScopeItem.expanded, true,
+        "The testScope expanded getter should return true");
+      is(loadScopeItem.expanded, true,
+        "The loadScope expanded getter should return true");
+      is(globalScopeItem.expanded, true,
+        "The globalScope expanded getter should return true");
+
+      mathScopeItem.collapse();
+      testScopeItem.collapse();
+      loadScopeItem.collapse();
+      globalScopeItem.collapse();
+
+      is(innerScopeItem.expanded, true,
+        "The innerScope expanded getter should return true");
       is(mathScopeItem.expanded, false,
         "The mathScope expanded getter should return false");
       is(testScopeItem.expanded, false,
         "The testScope expanded getter should return false");
       is(loadScopeItem.expanded, false,
         "The loadScope expanded getter should return false");
       is(globalScopeItem.expanded, false,
         "The globalScope expanded getter should return false");
@@ -438,17 +459,17 @@ function prepareVariables(aCallback)
 
   EventUtils.sendMouseEvent({ type: "click" },
     gDebuggee.document.querySelector("button"),
     gDebuggee.window);
 }
 
 function ignoreExtraMatchedProperties()
 {
-  for (let [_, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
+  for (let [, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
     let name = item.name.toLowerCase();
     let value = item._valueString || "";
 
     if ((name.contains("tracemallocdumpallocations")) ||
         (name.contains("geolocation")) ||
         (name.contains("webgl"))) {
       item.target.setAttribute("non-match", "");
     }
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-02.js
@@ -19,16 +19,17 @@ requestLongerTimeout(2);
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
     gDebuggee = aDebuggee;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
     testSearchbox();
     prepareVariables(testVariablesFiltering);
   });
 }
 
 function testSearchbox()
 {
   ok(!gDebugger.DebuggerView.Variables._searchboxNode,
@@ -64,16 +65,21 @@ function testVariablesFiltering()
       "The local scope 'this' should be expanded");
     is(windowItem.expanded, true,
       "The local scope 'this.window' should be expanded");
     is(documentItem.expanded, true,
       "The local scope 'this.window.document' should be expanded");
     is(locationItem.expanded, true,
       "The local scope 'this.window.document.location' should be expanded");
 
+    locationItem.toggle();
+    locationItem.toggle();
+    documentItem.toggle();
+    documentItem.toggle();
+
     is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
       "There should be 1 variable displayed in the inner scope");
     is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the math scope");
     is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the test scope");
     is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the load scope");
@@ -145,16 +151,29 @@ function testVariablesFiltering()
       "The local scope 'this.window' should not be expanded");
     is(documentItem.expanded, false,
       "The local scope 'this.window.document' should not be expanded");
     is(locationItem.expanded, false,
       "The local scope 'this.window.document.location' should not be expanded");
 
     write("htmldocument");
 
+    is(thisItem.expanded, true,
+      "The local scope 'this' should be expanded");
+    is(windowItem.expanded, true,
+      "The local scope 'this.window' should be expanded");
+    is(documentItem.expanded, true,
+      "The local scope 'this.window.document' should be expanded");
+    is(locationItem.expanded, false,
+      "The local scope 'this.window.document.location' should not be expanded");
+
+    documentItem.toggle();
+    documentItem.toggle();
+    locationItem.toggle();
+
     is(innerScope.querySelectorAll(".variable:not([non-match])").length, 1,
       "There should be 1 variable displayed in the inner scope");
     is(mathScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the math scope");
     is(testScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the test scope");
     is(loadScope.querySelectorAll(".variable:not([non-match])").length, 0,
       "There should be 0 variables displayed in the load scope");
@@ -262,16 +281,32 @@ function prepareVariables(aCallback)
         testScope.querySelector(".name").getAttribute("value"));
       let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
         loadScope.querySelector(".name").getAttribute("value"));
       let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
         globalScope.querySelector(".name").getAttribute("value"));
 
       is(innerScopeItem.expanded, true,
         "The innerScope expanded getter should return true");
+      is(mathScopeItem.expanded, true,
+        "The mathScope expanded getter should return true");
+      is(testScopeItem.expanded, true,
+        "The testScope expanded getter should return true");
+      is(loadScopeItem.expanded, true,
+        "The loadScope expanded getter should return true");
+      is(globalScopeItem.expanded, true,
+        "The globalScope expanded getter should return true");
+
+      mathScopeItem.collapse();
+      testScopeItem.collapse();
+      loadScopeItem.collapse();
+      globalScopeItem.collapse();
+
+      is(innerScopeItem.expanded, true,
+        "The innerScope expanded getter should return true");
       is(mathScopeItem.expanded, false,
         "The mathScope expanded getter should return false");
       is(testScopeItem.expanded, false,
         "The testScope expanded getter should return false");
       is(loadScopeItem.expanded, false,
         "The loadScope expanded getter should return false");
       is(globalScopeItem.expanded, false,
         "The globalScope expanded getter should return false");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-05.js
@@ -1,32 +1,35 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Make sure that the property view correctly filters nodes by value.
+ * Make sure that the property view correctly filters nodes.
  */
 
 const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
 
 var gPane = null;
 var gTab = null;
 var gDebugger = null;
 var gDebuggee = null;
 var gSearchBox = null;
 
+requestLongerTimeout(2);
+
 function test()
 {
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.contentWindow;
     gDebuggee = aDebuggee;
 
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
     prepareVariables(testVariablesFiltering);
   });
 }
 
 function testVariablesFiltering()
 {
   function test1()
   {
@@ -253,17 +256,17 @@ function prepareVariables(aCallback)
 
   EventUtils.sendMouseEvent({ type: "click" },
     gDebuggee.document.querySelector("button"),
     gDebuggee.window);
 }
 
 function ignoreExtraMatchedProperties()
 {
-  for (let [_, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
+  for (let [, item] of gDebugger.DebuggerView.Variables._currHierarchy) {
     let name = item.name.toLowerCase();
     let value = item._valueString || "";
 
     if ((value.contains("DOM")) ||
         (value.contains("XPC") && !name.contains("__proto__"))) {
       item.target.setAttribute("non-match", "");
     }
   }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-06.js
@@ -0,0 +1,248 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the property view correctly filters nodes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
+
+var gPane = null;
+var gTab = null;
+var gDebugger = null;
+var gDebuggee = null;
+var gSearchBox = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    gDebugger = gPane.contentWindow;
+    gDebuggee = aDebuggee;
+
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = false;
+    prepareVariables(testVariablesFiltering);
+  });
+}
+
+function testVariablesFiltering()
+{
+  let f = {
+    test1: function()
+    {
+      assertExpansion(1, [true, false, false, false, false]);
+      clear();
+    },
+    test2: function()
+    {
+      assertExpansion(2, [true, false, false, false, false]);
+      EventUtils.sendKey("RETURN");
+    },
+    test3: function()
+    {
+      assertExpansion(3, [true, false, false, false, false]);
+      gDebugger.editor.focus();
+    },
+    test4: function()
+    {
+      assertExpansion(4, [true, false, false, false, false]);
+      write("*");
+    },
+    test5: function() {
+      assertExpansion(5, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test6: function() {
+      assertExpansion(6, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test7: function() {
+      assertExpansion(7, [true, true, true, true, true]);
+      backspace(1);
+    },
+    test8: function() {
+      assertExpansion(8, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test9: function() {
+      assertExpansion(9, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test10: function() {
+      assertExpansion(10, [true, true, true, true, true]);
+      innerScopeItem.collapse();
+      mathScopeItem.collapse();
+      testScopeItem.collapse();
+      loadScopeItem.collapse();
+      globalScopeItem.collapse();
+    },
+    test11: function() {
+      assertExpansion(11, [false, false, false, false, false]);
+      clear();
+    },
+    test12: function() {
+      assertExpansion(12, [false, false, false, false, false]);
+      EventUtils.sendKey("RETURN");
+    },
+    test13: function() {
+      assertExpansion(13, [false, false, false, false, false]);
+      gDebugger.editor.focus();
+    },
+    test14: function() {
+      assertExpansion(14, [false, false, false, false, false]);
+      write("*");
+    },
+    test15: function() {
+      assertExpansion(15, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test16: function() {
+      assertExpansion(16, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test17: function() {
+      assertExpansion(17, [true, true, true, true, true]);
+      backspace(1);
+    },
+    test18: function() {
+      assertExpansion(18, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test19: function() {
+      assertExpansion(19, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test20: function() {
+      assertExpansion(20, [true, true, true, true, true]);
+    }
+  };
+
+  function assertExpansion(n, array) {
+    is(innerScopeItem.expanded, array[0],
+      "The innerScope should " + (array[0] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(mathScopeItem.expanded, array[1],
+      "The mathScope should " + (array[1] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(testScopeItem.expanded, array[2],
+      "The testScope should " + (array[2] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(loadScopeItem.expanded, array[3],
+      "The loadScope should " + (array[3] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(globalScopeItem.expanded, array[4],
+      "The globalScope should " + (array[4] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+  }
+
+  var scopes = gDebugger.DebuggerView.Variables._list,
+      innerScope = scopes.querySelectorAll(".scope")[0],
+      mathScope = scopes.querySelectorAll(".scope")[1],
+      testScope = scopes.querySelectorAll(".scope")[2],
+      loadScope = scopes.querySelectorAll(".scope")[3],
+      globalScope = scopes.querySelectorAll(".scope")[4];
+
+  let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    innerScope.querySelector(".name").getAttribute("value"));
+  let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    mathScope.querySelector(".name").getAttribute("value"));
+  let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    testScope.querySelector(".name").getAttribute("value"));
+  let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    loadScope.querySelector(".name").getAttribute("value"));
+  let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    globalScope.querySelector(".name").getAttribute("value"));
+
+  gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+  executeSoon(function() {
+    for (let i = 1; i <= Object.keys(f).length; i++) {
+      f["test" + i]();
+    }
+    closeDebuggerAndFinish();
+  });
+}
+
+function prepareVariables(aCallback)
+{
+  let count = 0;
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    // We expect 2 Debugger:FetchedVariables events, one from the inner object
+    // scope and the regular one.
+    if (++count < 2) {
+      info("Number of received Debugger:FetchedVariables events: " + count);
+      return;
+    }
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+          scopes = gDebugger.DebuggerView.Variables._list,
+          innerScope = scopes.querySelectorAll(".scope")[0],
+          mathScope = scopes.querySelectorAll(".scope")[1],
+          testScope = scopes.querySelectorAll(".scope")[2],
+          loadScope = scopes.querySelectorAll(".scope")[3],
+          globalScope = scopes.querySelectorAll(".scope")[4];
+
+      let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        innerScope.querySelector(".name").getAttribute("value"));
+      let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        mathScope.querySelector(".name").getAttribute("value"));
+      let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        testScope.querySelector(".name").getAttribute("value"));
+      let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        loadScope.querySelector(".name").getAttribute("value"));
+      let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        globalScope.querySelector(".name").getAttribute("value"));
+
+      executeSoon(function() {
+        aCallback();
+      });
+    }}, 0);
+  }, false);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    gDebuggee.document.querySelector("button"),
+    gDebuggee.window);
+}
+
+function clear() {
+  gSearchBox.focus();
+  gSearchBox.value = "";
+}
+
+function write(text) {
+  clear();
+  append(text);
+}
+
+function backspace(times) {
+  for (let i = 0; i < times; i++) {
+    EventUtils.sendKey("BACK_SPACE")
+  }
+}
+
+function append(text) {
+  gSearchBox.focus();
+
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i]);
+  }
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+  gDebuggee = null;
+  gSearchBox = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-07.js
@@ -0,0 +1,253 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the property view correctly filters nodes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
+
+var gPane = null;
+var gTab = null;
+var gDebugger = null;
+var gDebuggee = null;
+var gSearchBox = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    gDebugger = gPane.contentWindow;
+    gDebuggee = aDebuggee;
+
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    prepareVariables(testVariablesFiltering);
+  });
+}
+
+function testVariablesFiltering()
+{
+  let f = {
+    test1: function()
+    {
+      assertExpansion(1, [true, false, false, false, false]);
+      clear();
+    },
+    test2: function()
+    {
+      assertExpansion(2, [true, false, false, false, false]);
+      EventUtils.sendKey("RETURN");
+    },
+    test3: function()
+    {
+      assertExpansion(3, [true, false, false, false, false]);
+      gDebugger.editor.focus();
+    },
+    test4: function()
+    {
+      assertExpansion(4, [true, false, false, false, false]);
+      write("*");
+    },
+    test5: function() {
+      assertExpansion(5, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test6: function() {
+      assertExpansion(6, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test7: function() {
+      assertExpansion(7, [true, true, true, true, true]);
+      backspace(1);
+    },
+    test8: function() {
+      assertExpansion(8, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test9: function() {
+      assertExpansion(9, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test10: function() {
+      assertExpansion(10, [true, true, true, true, true]);
+      innerScopeItem.collapse();
+      mathScopeItem.collapse();
+      testScopeItem.collapse();
+      loadScopeItem.collapse();
+      globalScopeItem.collapse();
+    },
+    test11: function() {
+      assertExpansion(11, [false, false, false, false, false]);
+      clear();
+    },
+    test12: function() {
+      assertExpansion(12, [false, false, false, false, false]);
+      EventUtils.sendKey("RETURN");
+    },
+    test13: function() {
+      assertExpansion(13, [false, false, false, false, false]);
+      gDebugger.editor.focus();
+    },
+    test14: function() {
+      assertExpansion(14, [false, false, false, false, false]);
+      write("*");
+    },
+    test15: function() {
+      assertExpansion(15, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test16: function() {
+      assertExpansion(16, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test17: function() {
+      assertExpansion(17, [true, true, true, true, true]);
+      backspace(1);
+    },
+    test18: function() {
+      assertExpansion(18, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+    },
+    test19: function() {
+      assertExpansion(19, [true, true, true, true, true]);
+      gDebugger.editor.focus();
+    },
+    test20: function() {
+      assertExpansion(20, [true, true, true, true, true]);
+    }
+  };
+
+  function assertExpansion(n, array) {
+    is(innerScopeItem.expanded, array[0],
+      "The innerScope should " + (array[0] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(mathScopeItem.expanded, array[1],
+      "The mathScope should " + (array[1] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(testScopeItem.expanded, array[2],
+      "The testScope should " + (array[2] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(loadScopeItem.expanded, array[3],
+      "The loadScope should " + (array[3] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(globalScopeItem.expanded, array[4],
+      "The globalScope should " + (array[4] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+  }
+
+  var scopes = gDebugger.DebuggerView.Variables._list,
+      innerScope = scopes.querySelectorAll(".scope")[0],
+      mathScope = scopes.querySelectorAll(".scope")[1],
+      testScope = scopes.querySelectorAll(".scope")[2],
+      loadScope = scopes.querySelectorAll(".scope")[3],
+      globalScope = scopes.querySelectorAll(".scope")[4];
+
+  let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    innerScope.querySelector(".name").getAttribute("value"));
+  let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    mathScope.querySelector(".name").getAttribute("value"));
+  let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    testScope.querySelector(".name").getAttribute("value"));
+  let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    loadScope.querySelector(".name").getAttribute("value"));
+  let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    globalScope.querySelector(".name").getAttribute("value"));
+
+  gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+  executeSoon(function() {
+    for (let i = 1; i <= Object.keys(f).length; i++) {
+      f["test" + i]();
+    }
+    closeDebuggerAndFinish();
+  });
+}
+
+function prepareVariables(aCallback)
+{
+  let count = 0;
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    // We expect 2 Debugger:FetchedVariables events, one from the inner object
+    // scope and the regular one.
+    if (++count < 2) {
+      info("Number of received Debugger:FetchedVariables events: " + count);
+      return;
+    }
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+          scopes = gDebugger.DebuggerView.Variables._list,
+          innerScope = scopes.querySelectorAll(".scope")[0],
+          mathScope = scopes.querySelectorAll(".scope")[1],
+          testScope = scopes.querySelectorAll(".scope")[2],
+          loadScope = scopes.querySelectorAll(".scope")[3],
+          globalScope = scopes.querySelectorAll(".scope")[4];
+
+      let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        innerScope.querySelector(".name").getAttribute("value"));
+      let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        mathScope.querySelector(".name").getAttribute("value"));
+      let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        testScope.querySelector(".name").getAttribute("value"));
+      let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        loadScope.querySelector(".name").getAttribute("value"));
+      let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        globalScope.querySelector(".name").getAttribute("value"));
+
+      EventUtils.sendMouseEvent({ type: "mousedown" }, mathScope.querySelector(".arrow"), gDebuggee);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, testScope.querySelector(".arrow"), gDebuggee);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, loadScope.querySelector(".arrow"), gDebuggee);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, globalScope.querySelector(".arrow"), gDebuggee);
+
+      executeSoon(function() {
+        aCallback();
+      });
+    }}, 0);
+  }, false);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    gDebuggee.document.querySelector("button"),
+    gDebuggee.window);
+}
+
+function clear() {
+  gSearchBox.focus();
+  gSearchBox.value = "";
+}
+
+function write(text) {
+  clear();
+  append(text);
+}
+
+function backspace(times) {
+  for (let i = 0; i < times; i++) {
+    EventUtils.sendKey("BACK_SPACE")
+  }
+}
+
+function append(text) {
+  gSearchBox.focus();
+
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i]);
+  }
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+  gDebuggee = null;
+  gSearchBox = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-filter-08.js
@@ -0,0 +1,323 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the property view correctly filters nodes.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_with-frame.html";
+
+var gPane = null;
+var gTab = null;
+var gDebugger = null;
+var gDebuggee = null;
+var gSearchBox = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    gDebugger = gPane.contentWindow;
+    gDebuggee = aDebuggee;
+
+    gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
+    prepareVariables(testVariablesFiltering);
+  });
+}
+
+function testVariablesFiltering()
+{
+  let f = {
+    test1: function(aCallback)
+    {
+      assertExpansion(1, [true, false, false, false, false]);
+      write("*arguments");
+      aCallback();
+    },
+    test2: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, false,
+        "The arguments pseudoarray in the testScope should not be expanded");
+      is(loadScopeItem.get("arguments").expanded, false,
+        "The arguments pseudoarray in the testScope should not be expanded");
+
+      assertExpansion(1, [true, true, true, true, true]);
+      EventUtils.sendKey("RETURN");
+      aCallback();
+    },
+    test3: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+      is(loadScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+
+      waitForFetchedProperties(2, function() {
+        is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the testScope should have 4 visible properties");
+        is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the loadScope should have 4 visible properties");
+
+        assertExpansion(2, [true, true, true, true, true]);
+        backspace(1);
+        aCallback();
+      });
+    },
+    test4: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+      is(loadScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+
+      waitForFetchedProperties(0, function() {
+        is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the testScope should have 4 visible properties");
+        is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the loadScope should have 4 visible properties");
+
+        assertExpansion(3, [true, true, true, true, true]);
+        backspace(8);
+        aCallback();
+      });
+    },
+    test5: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+      is(loadScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+
+      waitForFetchedProperties(0, function() {
+        is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the testScope should have 4 visible properties");
+        is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the loadScope should have 4 visible properties");
+
+        assertExpansion(4, [true, true, true, true, true]);
+        backspace(1);
+        aCallback();
+      });
+    },
+    test6: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+      is(loadScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+
+      waitForFetchedProperties(0, function() {
+        is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the testScope should have 4 visible properties");
+        is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the loadScope should have 4 visible properties");
+
+        assertExpansion(5, [true, true, true, true, true]);
+        write("*");
+        aCallback();
+      });
+    },
+    test7: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+      is(loadScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+
+      waitForFetchedProperties(0, function() {
+        is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the testScope should have 4 visible properties");
+        is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 4,
+          "The arguments in the loadScope should have 4 visible properties");
+
+        assertExpansion(5, [true, true, true, true, true]);
+        append("arguments");
+        aCallback();
+      });
+    },
+    test8: function(aCallback)
+    {
+      is(testScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+      is(loadScopeItem.get("arguments").expanded, true,
+        "The arguments pseudoarray in the testScope should now be expanded");
+
+      waitForFetchedProperties(0, function() {
+        is(testScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 0,
+          "The arguments in the testScope should have 0 visible properties");
+        is(loadScopeItem.get("arguments").target.querySelectorAll(".property:not([non-match])").length, 0,
+          "The arguments in the loadScope should have 0 visible properties");
+
+        assertExpansion(5, [true, true, true, true, true]);
+        aCallback();
+      });
+    },
+  };
+
+  function assertExpansion(n, array) {
+    is(innerScopeItem.expanded, array[0],
+      "The innerScope should " + (array[0] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(mathScopeItem.expanded, array[1],
+      "The mathScope should " + (array[1] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(testScopeItem.expanded, array[2],
+      "The testScope should " + (array[2] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(loadScopeItem.expanded, array[3],
+      "The loadScope should " + (array[3] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+
+    is(globalScopeItem.expanded, array[4],
+      "The globalScope should " + (array[4] ? "" : "not ") +
+       "be expanded at this point (" + n + ")");
+  }
+
+  function waitForFetchedProperties(n, aCallback) {
+    if (n == 0) {
+      aCallback();
+      return;
+    }
+
+    let count = 0;
+    gDebugger.addEventListener("Debugger:FetchedProperties", function test() {
+      // We expect n Debugger:FetchedProperties events.
+      if (++count < n) {
+        info("Number of received Debugger:FetchedVariables events: " + count);
+        return;
+      }
+      gDebugger.removeEventListener("Debugger:FetchedProperties", test, false);
+      Services.tm.currentThread.dispatch({ run: function() {
+        executeSoon(aCallback);
+      }}, 0);
+    }, false);
+  }
+
+  var scopes = gDebugger.DebuggerView.Variables._list,
+      innerScope = scopes.querySelectorAll(".scope")[0],
+      mathScope = scopes.querySelectorAll(".scope")[1],
+      testScope = scopes.querySelectorAll(".scope")[2],
+      loadScope = scopes.querySelectorAll(".scope")[3],
+      globalScope = scopes.querySelectorAll(".scope")[4];
+
+  let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    innerScope.querySelector(".name").getAttribute("value"));
+  let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    mathScope.querySelector(".name").getAttribute("value"));
+  let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    testScope.querySelector(".name").getAttribute("value"));
+  let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    loadScope.querySelector(".name").getAttribute("value"));
+  let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+    globalScope.querySelector(".name").getAttribute("value"));
+
+  gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+  executeSoon(function() {
+    f.test1(function() {
+      f.test2(function() {
+        f.test3(function() {
+          f.test4(function() {
+            f.test5(function() {
+              f.test6(function() {
+                f.test7(function() {
+                  f.test8(function() {
+                    closeDebuggerAndFinish();
+                  });
+                });
+              });
+            });
+          });
+        });
+      });
+    });
+  });
+}
+
+function prepareVariables(aCallback)
+{
+  let count = 0;
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    // We expect 2 Debugger:FetchedVariables events, one from the inner object
+    // scope and the regular one.
+    if (++count < 2) {
+      info("Number of received Debugger:FetchedVariables events: " + count);
+      return;
+    }
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      var frames = gDebugger.DebuggerView.StackFrames._container._list,
+          scopes = gDebugger.DebuggerView.Variables._list,
+          innerScope = scopes.querySelectorAll(".scope")[0],
+          mathScope = scopes.querySelectorAll(".scope")[1],
+          testScope = scopes.querySelectorAll(".scope")[2],
+          loadScope = scopes.querySelectorAll(".scope")[3],
+          globalScope = scopes.querySelectorAll(".scope")[4];
+
+      let innerScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        innerScope.querySelector(".name").getAttribute("value"));
+      let mathScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        mathScope.querySelector(".name").getAttribute("value"));
+      let testScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        testScope.querySelector(".name").getAttribute("value"));
+      let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        loadScope.querySelector(".name").getAttribute("value"));
+      let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
+        globalScope.querySelector(".name").getAttribute("value"));
+
+      EventUtils.sendMouseEvent({ type: "mousedown" }, mathScope.querySelector(".arrow"), gDebuggee);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, testScope.querySelector(".arrow"), gDebuggee);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, loadScope.querySelector(".arrow"), gDebuggee);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, globalScope.querySelector(".arrow"), gDebuggee);
+
+      executeSoon(function() {
+        aCallback();
+      });
+    }}, 0);
+  }, false);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    gDebuggee.document.querySelector("button"),
+    gDebuggee.window);
+}
+
+function clear() {
+  gSearchBox.focus();
+  gSearchBox.value = "";
+}
+
+function write(text) {
+  clear();
+  append(text);
+}
+
+function backspace(times) {
+  for (let i = 0; i < times; i++) {
+    EventUtils.sendKey("BACK_SPACE")
+  }
+}
+
+function append(text) {
+  gSearchBox.focus();
+
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i]);
+  }
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+  gDebuggee = null;
+  gSearchBox = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-reexpand.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-reexpand.js
@@ -30,17 +30,19 @@ function test()
 function addBreakpoint()
 {
   gDebugger.DebuggerController.Breakpoints.addBreakpoint({
     url: gDebugger.DebuggerView.Sources.selectedValue,
     line: 16
   }, function() {
     // Wait for the resume...
     gDebugger.gClient.addOneTimeListener("resumed", function() {
+      gDebugger.DebuggerController.StackFrames.autoScopeExpand = true;
       gDebugger.DebuggerView.Variables.nonEnumVisible = false;
+      gDebugger.DebuggerView.Variables.commitHierarchyIgnoredItems = Object.create(null);
       testVariablesExpand();
     });
   });
 }
 
 function testVariablesExpand()
 {
   let count = 0;
@@ -70,16 +72,54 @@ function testVariablesExpand()
         testScope.querySelector(".name").getAttribute("value"));
       let loadScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
         loadScope.querySelector(".name").getAttribute("value"));
       let globalScopeItem = gDebugger.DebuggerView.Variables._currHierarchy.get(
         globalScope.querySelector(".name").getAttribute("value"));
 
       is(innerScope.querySelector(".arrow").hasAttribute("open"), true,
         "The innerScope arrow should initially be expanded");
+      is(mathScope.querySelector(".arrow").hasAttribute("open"), true,
+        "The mathScope arrow should initially be expanded");
+      is(testScope.querySelector(".arrow").hasAttribute("open"), true,
+        "The testScope arrow should initially be expanded");
+      is(loadScope.querySelector(".arrow").hasAttribute("open"), true,
+        "The loadScope arrow should initially be expanded");
+      is(globalScope.querySelector(".arrow").hasAttribute("open"), true,
+        "The globalScope arrow should initially be expanded");
+
+      is(innerScope.querySelector(".details").hasAttribute("open"), true,
+        "The innerScope enumerables should initially be expanded");
+      is(mathScope.querySelector(".details").hasAttribute("open"), true,
+        "The mathScope enumerables should initially be expanded");
+      is(testScope.querySelector(".details").hasAttribute("open"), true,
+        "The testScope enumerables should initially be expanded");
+      is(loadScope.querySelector(".details").hasAttribute("open"), true,
+        "The loadScope enumerables should initially be expanded");
+      is(globalScope.querySelector(".details").hasAttribute("open"), true,
+        "The globalScope enumerables should initially be expanded");
+
+      is(innerScopeItem.expanded, true,
+        "The innerScope expanded getter should return true");
+      is(mathScopeItem.expanded, true,
+        "The mathScope expanded getter should return true");
+      is(testScopeItem.expanded, true,
+        "The testScope expanded getter should return true");
+      is(loadScopeItem.expanded, true,
+        "The loadScope expanded getter should return true");
+      is(globalScopeItem.expanded, true,
+        "The globalScope expanded getter should return true");
+
+      mathScopeItem.collapse();
+      testScopeItem.collapse();
+      loadScopeItem.collapse();
+      globalScopeItem.collapse();
+
+      is(innerScope.querySelector(".arrow").hasAttribute("open"), true,
+        "The innerScope arrow should initially be expanded");
       is(mathScope.querySelector(".arrow").hasAttribute("open"), false,
         "The mathScope arrow should initially not be expanded");
       is(testScope.querySelector(".arrow").hasAttribute("open"), false,
         "The testScope arrow should initially not be expanded");
       is(loadScope.querySelector(".arrow").hasAttribute("open"), false,
         "The loadScope arrow should initially not be expanded");
       is(globalScope.querySelector(".arrow").hasAttribute("open"), false,
         "The globalScope arrow should initially not be expanded");
--- a/browser/devtools/shared/VariablesView.jsm
+++ b/browser/devtools/shared/VariablesView.jsm
@@ -134,29 +134,29 @@ VariablesView.prototype = {
 
   /**
    * Specifies if enumerable properties and variables should be displayed.
    * @param boolean aFlag
    */
   set enumVisible(aFlag) {
     this._enumVisible = aFlag;
 
-    for (let [_, scope] in this) {
+    for (let [, scope] in this) {
       scope._nonEnumVisible = aFlag;
     }
   },
 
   /**
    * Specifies if non-enumerable properties and variables should be displayed.
    * @param boolean aFlag
    */
   set nonEnumVisible(aFlag) {
     this._nonEnumVisible = aFlag;
 
-    for (let [_, scope] in this) {
+    for (let [, scope] in this) {
       scope._nonEnumVisible = aFlag;
     }
   },
 
   /**
    * Enables variable and property searching in this view.
    */
   enableSearch: function VV_enableSearch() {
@@ -211,33 +211,37 @@ VariablesView.prototype = {
   /**
    * Performs a case insensitive search for variables or properties matching
    * the query, and hides non-matched items.
    *
    * @param string aQuery
    *        The variable or property to search for.
    */
   performSearch: function VV_performSerch(aQuery) {
-    if (!aQuery) {
-      for (let [_, item] of this._currHierarchy) {
-        item._match = true;
-      }
-    } else {
-      for (let [_, scope] in this) {
-        scope._performSearch(aQuery.toLowerCase());
+    for (let [, scope] in this) {
+      switch (aQuery) {
+        case "":
+          scope.expand();
+          // fall through
+        case null:
+          scope._performSearch("");
+          break;
+        default:
+          scope._performSearch(aQuery.toLowerCase());
+          break;
       }
     }
   },
 
   /**
    * Expands the first search results in this container.
    */
   expandFirstSearchResults: function VV_expandFirstSearchResults() {
-    for (let [_, scope] in this) {
-      for (let [_, variable] in scope) {
+    for (let [, scope] in this) {
+      for (let [, variable] in scope) {
         if (variable._isMatch) {
           variable.expand();
           break;
         }
       }
     }
   },
 
@@ -361,16 +365,18 @@ VariablesView.prototype = {
  *        Additional options or flags for this scope.
  */
 function Scope(aView, aName, aFlags = {}) {
   this.show = this.show.bind(this);
   this.hide = this.hide.bind(this);
   this.expand = this.expand.bind(this);
   this.collapse = this.collapse.bind(this);
   this.toggle = this.toggle.bind(this);
+  this._openEnum = this._openEnum.bind(this);
+  this._openNonEnum = this._openNonEnum.bind(this);
 
   this.ownerView = aView;
   this.eval = aView.eval;
 
   this._store = new Map();
   this._init(aName.trim(), aFlags);
 }
 
@@ -438,34 +444,26 @@ Scope.prototype = {
 
     if (this.onhide) {
       this.onhide(this);
     }
   },
 
   /**
    * Expands the scope, showing all the added details.
-   *
-   * @param boolean aSkipAnimationFlag
-   *        Pass true to not show an opening animation.
    */
-  expand: function S_expand(aSkipAnimationFlag) {
+  expand: function S_expand() {
     if (this._isExpanded || this._locked) {
       return;
     }
     if (this._variablesView._enumVisible) {
-      this._arrow.setAttribute("open", "");
-      this._enum.setAttribute("open", "");
+      this._openEnum();
     }
     if (this._variablesView._nonEnumVisible) {
-      this._nonenum.setAttribute("open", "");
-    }
-    if (!aSkipAnimationFlag) {
-      this._enum.setAttribute("animated", "");
-      this._nonenum.setAttribute("animated", "");
+      Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
     }
     this._isExpanded = true;
 
     if (this.onexpand) {
       this.onexpand(this);
     }
   },
 
@@ -474,67 +472,82 @@ Scope.prototype = {
    */
   collapse: function S_collapse() {
     if (!this._isExpanded || this._locked) {
       return;
     }
     this._arrow.removeAttribute("open");
     this._enum.removeAttribute("open");
     this._nonenum.removeAttribute("open");
-    this._enum.removeAttribute("animated");
-    this._nonenum.removeAttribute("animated");
     this._isExpanded = false;
 
     if (this.oncollapse) {
       this.oncollapse(this);
     }
   },
 
   /**
    * Toggles between the scope's collapsed and expanded state.
    */
   toggle: function S_toggle() {
     this._wasToggled = true;
     this.expanded ^= 1;
 
+    // Make sure the scope and its contents are visibile.
+    for (let [, variable] in this) {
+      variable.header = true;
+      variable._match = true;
+    }
     if (this.ontoggle) {
       this.ontoggle(this);
     }
   },
 
   /**
    * Shows the scope's title header.
    */
   showHeader: function S_showHeader() {
+    if (this._isHeaderVisible) {
+      return;
+    }
     this._target.removeAttribute("non-header");
     this._isHeaderVisible = true;
   },
 
   /**
    * Hides the scope's title header.
    * This action will automatically expand the scope.
    */
   hideHeader: function S_hideHeader() {
+    if (!this._isHeaderVisible) {
+      return;
+    }
     this.expand();
     this._target.setAttribute("non-header", "");
     this._isHeaderVisible = false;
   },
 
   /**
    * Shows the scope's expand/collapse arrow.
    */
   showArrow: function S_showArrow() {
+    if (this._isArrowVisible) {
+      return;
+    }
     this._arrow.removeAttribute("invisible");
     this._isArrowVisible = true;
   },
 
   /**
    * Hides the scope's expand/collapse arrow.
    */
   hideArrow: function S_hideArrow() {
+    if (!this._isArrowVisible) {
+      return;
+    }
     this._arrow.setAttribute("invisible", "");
     this._isArrowVisible = false;
   },
 
   /**
    * Gets the visibility state.
    * @return boolean
    */
@@ -659,21 +672,44 @@ Scope.prototype = {
   /**
    * Adds the necessary event listeners for this scope.
    */
   _addEventListeners: function S__addEventListeners() {
     this._title.addEventListener("mousedown", this.toggle, false);
   },
 
   /**
+   * Adds an event listener for the mouse over event on the title element.
+   * @param function aCallback
+   */
+  set onmouseover(aCallback) {
+    this._title.addEventListener("mouseover", aCallback, false);
+  },
+
+  /**
+   * Opens the enumerable items container.
+   */
+  _openEnum: function S__openEnum() {
+    this._arrow.setAttribute("open", "");
+    this._enum.setAttribute("open", "");
+  },
+
+  /**
+   * Opens the non-enumerable items container.
+   */
+  _openNonEnum: function S__openNonEnum() {
+    this._nonenum.setAttribute("open", "");
+  },
+
+  /**
    * Specifies if enumerable properties and variables should be displayed.
    * @param boolean aFlag
    */
   set _enumVisible(aFlag) {
-    for (let [_, variable] in this) {
+    for (let [, variable] in this) {
       variable._enumVisible = aFlag;
 
       if (!this.expanded) {
         continue;
       }
       if (aFlag) {
         this._enum.setAttribute("open", "");
       } else {
@@ -682,17 +718,17 @@ Scope.prototype = {
     }
   },
 
   /**
    * Specifies if non-enumerable properties and variables should be displayed.
    * @param boolean aFlag
    */
   set _nonEnumVisible(aFlag) {
-    for (let [_, variable] in this) {
+    for (let [, variable] in this) {
       variable._nonEnumVisible = aFlag;
 
       if (!this.expanded) {
         continue;
       }
       if (aFlag) {
         this._nonenum.setAttribute("open", "");
       } else {
@@ -704,17 +740,17 @@ Scope.prototype = {
   /**
    * Performs a case insensitive search for variables or properties matching
    * the query, and hides non-matched items.
    *
    * @param string aLowerCaseQuery
    *        The lowercased name of the variable or property to search for.
    */
   _performSearch: function S__performSearch(aLowerCaseQuery) {
-    for (let [_, variable] in this) {
+    for (let [, variable] in this) {
       let currentObject = variable;
       let lowerCaseName = variable._nameString.toLowerCase();
       let lowerCaseValue = variable._valueString.toLowerCase();
 
       // Non-matched variables or properties require a corresponding attribute.
       if (!lowerCaseName.contains(aLowerCaseQuery) &&
           !lowerCaseValue.contains(aLowerCaseQuery)) {
         variable._match = false;
@@ -722,37 +758,42 @@ Scope.prototype = {
       // Variable or property is matched.
       else {
         variable._match = true;
 
         // If the variable was ever expanded, there's a possibility it may
         // contain some matched properties, so make sure they're visible
         // ("expand downwards").
 
-        if (variable._wasToggled) {
-          variable.expand(true);
+        if (variable._wasToggled && aLowerCaseQuery) {
+          variable.expand();
+        }
+        if (variable._isExpanded && !aLowerCaseQuery) {
+          variable._wasToggled = true;
         }
 
         // If the variable is contained in another scope (variable or property),
         // the parent may not be a match, thus hidden. It should be visible
         // ("expand upwards").
 
         while ((variable = variable.ownerView) &&  /* Parent object exists. */
                (variable instanceof Scope ||
                 variable instanceof Variable ||
                 variable instanceof Property)) {
 
           // Show and expand the parent, as it is certainly accessible.
           variable._match = true;
-          variable.expand(true);
+          aLowerCaseQuery && variable.expand();
         }
       }
 
       // Proceed with the search recursively inside this variable or property.
-      if (variable._wasToggled || variable.expanded || variable.getter || variable.setter) {
+      if (currentObject._wasToggled ||
+          currentObject.getter ||
+          currentObject.setter) {
         currentObject._performSearch(aLowerCaseQuery);
       }
     }
   },
 
   /**
    * Sets if this object instance is a match or non-match.
    * @param boolean aStatus
@@ -831,16 +872,17 @@ Scope.prototype = {
  * @param Scope aScope
  *        The scope to contain this varialbe.
  * @param string aName
  *        The variable's name.
  * @param object aDescriptor
  *        The variable's descriptor.
  */
 function Variable(aScope, aName, aDescriptor) {
+  this._displayTooltip = this._displayTooltip.bind(this);
   this._activateInput = this._activateInput.bind(this);
   this._deactivateInput = this._deactivateInput.bind(this);
   this._saveInput = this._saveInput.bind(this);
   this._onInputKeyPress = this._onInputKeyPress.bind(this);
 
   Scope.call(this, aScope, aName, aDescriptor);
   this._setGrip(aDescriptor.value);
   this._symbolicName = aName;
@@ -913,16 +955,17 @@ create({ constructor: Variable, proto: S
    * @param object aObject
    *        The raw object you want to display.
    */
   populate: function V_populate(aObject) {
     // Retrieve the properties only once.
     if (this.fetched) {
       return;
     }
+    this.fetched = true;
 
     // Sort all of the properties before adding them.
     let sortedPropertyNames = Object.getOwnPropertyNames(aObject).sort();
     let prototype = Object.getPrototypeOf(aObject);
 
     // Add all the variable properties.
     for (let name of sortedPropertyNames) {
       let descriptor = Object.getOwnPropertyDescriptor(aObject, name);
@@ -931,18 +974,16 @@ create({ constructor: Variable, proto: S
       } else {
         this._addRawValueProperty(name, descriptor, aObject[name]);
       }
     }
     // Add the variable's __proto__.
     if (prototype) {
       this._addRawValueProperty("__proto__", {}, prototype);
     }
-
-    this.fetched = true;
   },
 
   /**
    * Adds a property for this variable based on a raw value descriptor.
    *
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
@@ -1051,17 +1092,17 @@ create({ constructor: Variable, proto: S
    *        The variable's name.
    * @param object aDescriptor
    *        The variable's descriptor.
    */
   _init: function V__init(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._createScope(aName, "variable");
     this._displayVariable(aDescriptor);
-    this._displayTooltip();
+    this._prepareTooltip();
     this._setAttributes(aName, aDescriptor);
     this._addEventListeners();
 
     if (aDescriptor.enumerable || aName == "this" || aName == "<exception>") {
       this.ownerView._enum.appendChild(this._target);
     } else {
       this.ownerView._nonenum.appendChild(this._target);
     }
@@ -1087,26 +1128,34 @@ create({ constructor: Variable, proto: S
     this._title.appendChild(valueLabel);
 
     if (VariablesView.isPrimitive(aDescriptor)) {
       this.hideArrow();
     }
     if (aDescriptor.get || aDescriptor.set) {
       this.addProperty("get", { value: aDescriptor.get });
       this.addProperty("set", { value: aDescriptor.set });
-      this.expand(true);
+      this.expand();
       separatorLabel.hidden = true;
       valueLabel.hidden = true;
     }
   },
 
   /**
+   * Prepares a tooltip for this variable.
+   */
+  _prepareTooltip: function V__prepareTooltip() {
+    this._target.addEventListener("mouseover", this._displayTooltip, false);
+  },
+
+  /**
    * Creates a tooltip for this variable.
    */
   _displayTooltip: function V__displayTooltip() {
+    this._target.removeEventListener("mouseover", this._displayTooltip, false);
     let document = this.document;
 
     let tooltip = document.createElement("tooltip");
     tooltip.id = "tooltip-" + this.id;
 
     let configurableLabel = document.createElement("label");
     configurableLabel.setAttribute("value", "configurable");
 
@@ -1168,27 +1217,29 @@ create({ constructor: Variable, proto: S
 
   /**
    * Makes this variable's value editable.
    */
   _activateInput: function V__activateInput(e) {
     if (!this.eval) {
       return;
     }
+    let window = this.window;
+    let document = this.document;
 
     let title = this._title;
     let valueLabel = this._valueLabel;
     let initialString = this._valueLabel.getAttribute("value");
 
     // Create a texbox input element which will be shown in the current
     // element's value location.
     let input = this.document.createElement("textbox");
     input.setAttribute("value", initialString);
-    input.className = "element-input";
-    input.width = valueLabel.clientWidth + 1;
+    input.className = "plain element-input";
+    input.width = this._target.clientWidth;
 
     title.removeChild(valueLabel);
     title.appendChild(input);
     input.select();
 
     // When the value is a string (displayed as "value"), then we probably want
     // to change it to another string in the textbox, so to avoid typing the ""
     // again, tackle with the selection bounds just a bit.
@@ -1233,19 +1284,21 @@ create({ constructor: Variable, proto: S
     let input = e.target;
     let valueLabel = this._valueLabel;
     let initialString = this._valueLabel.getAttribute("value");
     let currentString = input.value;
 
     this._deactivateInput(e);
 
     if (initialString != currentString) {
+      this._arrow.setAttribute("invisible", "");
       this._separatorLabel.hidden = true;
       this._valueLabel.hidden = true;
-      this.collapse();
+      this._enum.hidden = true;
+      this._nonenum.hidden = true;
       this.eval("(" + this._symbolicName + "=" + currentString + ")");
     }
   },
 
   /**
    * The key press listener for this variable's editable mode textbox.
    */
   _onInputKeyPress: function V__onInputKeyPress(e) {
@@ -1299,17 +1352,17 @@ create({ constructor: Property, proto: V
    *        The property's name.
    * @param object aDescriptor
    *        The property's descriptor.
    */
   _init: function P__init(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._createScope(aName, "property");
     this._displayVariable(aDescriptor);
-    this._displayTooltip();
+    this._prepareTooltip();
     this._setAttributes(aName, aDescriptor);
     this._addEventListeners();
 
     if (aDescriptor.enumerable) {
       this.ownerView._enum.appendChild(this._target);
     } else {
       this.ownerView._nonenum.appendChild(this._target);
     }
@@ -1345,39 +1398,44 @@ VariablesView.prototype.commitHierarchy 
   let prevHierarchy = this._prevHierarchy;
   let currHierarchy = this._currHierarchy;
 
   for (let [absoluteName, currVariable] of currHierarchy) {
     // Ignore variables which were already commmitted.
     if (currVariable._committed) {
       continue;
     }
+    // Avoid performing expensive operations.
+    if (this.commitHierarchyIgnoredItems[currVariable._nameString]) {
+      continue;
+    }
 
     // Try to get the previous instance of the inspected variable to
     // determine the difference in state.
     let prevVariable = prevHierarchy.get(absoluteName);
+    let expanded = false;
     let changed = false;
 
     // If the inspected variable existed in a previous hierarchy, check if
-    // the displayed value (a representation of the grip) has changed.
+    // the displayed value (a representation of the grip) has changed and if
+    // it was previously expanded.
     if (prevVariable) {
-      let prevString = prevVariable._valueString;
-      let currString = currVariable._valueString;
-      changed = prevString != currString;
-
-      // Re-expand the variable if not previously collapsed.
-      if (prevVariable.expanded) {
-        currVariable.expand(true);
-      }
+      expanded = prevVariable._isExpanded;
+      changed = prevVariable._valueString != currVariable._valueString;
     }
 
     // Make sure this variable is not handled in ulteror commits for the
     // same hierarchy.
     currVariable._committed = true;
 
+    // Re-expand the variable if not previously collapsed.
+    if (expanded) {
+      currVariable._wasToggled = prevVariable._wasToggled;
+      currVariable.expand();
+    }
     // This variable was either not changed or removed, no need to continue.
     if (!changed) {
       continue;
     }
 
     // Apply an attribute determining the flash type and duration.
     // Dispatch this action after all the nodes have been drawn, so that
     // the transition efects can take place.
@@ -1387,16 +1445,23 @@ VariablesView.prototype.commitHierarchy 
       aTarget.addEventListener("transitionend", function onEvent() {
         aTarget.removeEventListener("transitionend", onEvent, false);
         aTarget.removeAttribute("changed");
       }, false);
     }.bind(this, currVariable.target), LAZY_EMPTY_DELAY + 1);
   }
 };
 
+// Some variables are likely to contain a very large number of properties.
+// It would be a bad idea to re-expand them or perform expensive operations.
+VariablesView.prototype.commitHierarchyIgnoredItems = Object.create(null, {
+  "window": { value: true },
+  "this": { value: true }
+});
+
 /**
  * Returns true if the descriptor represents an undefined, null or
  * primitive value.
  *
  * @param object aDescriptor
  *        The variable's descriptor.
  */
 VariablesView.isPrimitive = function VV_isPrimitive(aDescriptor) {
@@ -1416,17 +1481,44 @@ VariablesView.isPrimitive = function VV_
   // must be contained in a 'value' property.
   let grip = aDescriptor.value;
   if (!grip || typeof grip != "object") {
     return true;
   }
 
   // For convenience, undefined and null are both considered types.
   let type = grip.type;
-  if (["undefined", "null"].indexOf(type + "") != -1) {
+  if (type == "undefined" || type == "null") {
+    return true;
+  }
+
+  return false;
+};
+
+/**
+ * Returns true if the descriptor represents a falsy value.
+ *
+ * @param object aDescriptor
+ *        The variable's descriptor.
+ */
+VariablesView.isFalsy = function VV_isFalsy(aDescriptor) {
+  if (!aDescriptor || typeof aDescriptor != "object") {
+    return true;
+  }
+
+  // As described in the remote debugger protocol, the value grip
+  // must be contained in a 'value' property.
+  let grip = aDescriptor.value;
+  if (typeof grip != "object") {
+    return !grip;
+  }
+
+  // For convenience, undefined and null are both considered types.
+  let type = grip.type;
+  if (type == "undefined" || type == "null") {
     return true;
   }
 
   return false;
 };
 
 /**
  * Returns a standard grip for a value.
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -2006,16 +2006,76 @@ SourceEditor.prototype = {
       breakpoints.push({line: this._model.getLineAtOffset(annotation.start),
                         condition: annotation.breakpointCondition});
     }, this);
 
     return breakpoints;
   },
 
   /**
+   * Convert the given rectangle from one coordinate reference to another.
+   *
+   * Known coordinate references:
+   * - "document" - gives the coordinates relative to the entire document.
+   * - "view" - gives the coordinates relative to the editor viewport.
+   *
+   * @param object aRect
+   *         The rectangle to convert. Object properties: x, y, width and height.
+   * @param string aFrom
+   *         The source coordinate reference.
+   * @param string aTo
+   *         The destination coordinate reference.
+   * @return object aRect
+   *         Returns the rectangle with changed coordinates.
+   */
+  convertCoordinates: function SE_convertCoordinates(aRect, aFrom, aTo)
+  {
+    return this._view.convert(aRect, aFrom, aTo);
+  },
+
+  /**
+   * Get the character offset nearest to the given pixel location.
+   *
+   * @param number aX
+   * @param number aY
+   * @return number
+   *         Returns the character offset at the given location.
+   */
+  getOffsetAtLocation: function SE_getOffsetAtLocation(aX, aY)
+  {
+    return this._view.getOffsetAtLocation(aX, aY);
+  },
+
+  /**
+   * Get the pixel location, relative to the document, at the given character
+   * offset.
+   *
+   * @param number aOffset
+   * @return object
+   *         The pixel location relative to the document being edited. Two
+   *         properties are included: x and y.
+   */
+  getLocationAtOffset: function SE_getLocationAtOffset(aOffset)
+  {
+    return this._view.getLocationAtOffset(aOffset);
+  },
+
+  /**
+   * Get the line location for a given character offset.
+   *
+   * @param number aOffset
+   * @return number
+   *         The line location relative to the give character offset.
+   */
+  getLineAtOffset: function SE_getLineAtOffset(aOffset)
+  {
+    return this._model.getLineAtOffset(aOffset);
+  },
+
+  /**
    * Destroy/uninitialize the editor.
    */
   destroy: function SE_destroy()
   {
     if (this._config.highlightCurrentLine || Services.appinfo.OS == "Linux") {
       this.removeEventListener(SourceEditor.EVENTS.SELECTION,
                                this._onOrionSelection);
     }
--- a/browser/devtools/sourceeditor/test/Makefile.in
+++ b/browser/devtools/sourceeditor/test/Makefile.in
@@ -26,11 +26,12 @@ MOCHITEST_BROWSER_FILES = \
 		browser_bug712982_line_ruler_click.js \
 		browser_bug725618_moveLines_shortcut.js \
 		browser_bug700893_dirty_state.js \
 		browser_bug729480_line_vertical_align.js \
 		browser_bug725430_comment_uncomment.js \
 		browser_bug731721_debugger_stepping.js \
 		browser_bug729960_block_bracket_jump.js \
 		browser_bug744021_next_prev_bracket_jump.js \
+		browser_bug725392_mouse_coords_char_offset.js \
 		head.js \
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/sourceeditor/test/browser_bug687160_line_api.js
+++ b/browser/devtools/sourceeditor/test/browser_bug687160_line_api.js
@@ -66,16 +66,25 @@ function editorLoaded()
   editor.setCaretPosition(2);
   is(editor.getCaretOffset(), 12, "setCaretLine() works, confirmed");
 
   if (component != "textarea") {
     pos = editor.getCaretPosition();
     ok(pos.line == 2 && pos.col == 0, "setCaretPosition(line) works, again");
   }
 
+  let offsetLine = editor.getLineAtOffset(0);
+  is(offsetLine, 0, "getLineAtOffset() is correct for offset 0");
+
+  let offsetLine = editor.getLineAtOffset(6);
+  is(offsetLine, 1, "getLineAtOffset() is correct for offset 6");
+
+  let offsetLine = editor.getLineAtOffset(12);
+  is(offsetLine, 2, "getLineAtOffset() is correct for offset 12");
+
   editor.destroy();
 
   testWin.close();
   testWin = editor = null;
 
   waitForFocus(finish, window);
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/sourceeditor/test/browser_bug725392_mouse_coords_char_offset.js
@@ -0,0 +1,160 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test()
+{
+  let testWin;
+  let editor;
+  let mousePos = { x: 36, y: 4 };
+  let expectedOffset = 5;
+  let maxDiff = 10;
+
+  waitForExplicitFinish();
+
+  function editorLoaded(aEditor, aWindow)
+  {
+    editor = aEditor;
+    testWin = aWindow;
+
+    let text = fillEditor(editor, 3);
+    editor.setText(text);
+    editor.setCaretOffset(0);
+
+    doMouseMove(testPage1);
+  }
+
+  function doMouseMove(aCallback)
+  {
+    function mouseEventHandler(aEvent)
+    {
+      editor.removeEventListener(editor.EVENTS.MOUSE_OUT, mouseEventHandler);
+      editor.removeEventListener(editor.EVENTS.MOUSE_OVER, mouseEventHandler);
+      editor.removeEventListener(editor.EVENTS.MOUSE_MOVE, mouseEventHandler);
+
+      executeSoon(aCallback.bind(null, aEvent));
+    }
+
+    editor.addEventListener(editor.EVENTS.MOUSE_MOVE, mouseEventHandler);
+    editor.addEventListener(editor.EVENTS.MOUSE_OUT, mouseEventHandler);
+    editor.addEventListener(editor.EVENTS.MOUSE_OVER, mouseEventHandler);
+
+    let target = editor.editorElement;
+    let targetWin = testWin;
+
+    EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
+                               {type: "mousemove"}, targetWin);
+    EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
+                               {type: "mouseout"}, targetWin);
+    EventUtils.synthesizeMouse(target, mousePos.x, mousePos.y,
+                               {type: "mouseover"}, targetWin);
+  }
+
+  function checkValue(aValue, aExpectedValue)
+  {
+    let result = Math.abs(aValue - aExpectedValue) <= maxDiff;
+    if (!result) {
+      info("checkValue() given " + aValue + " expected " + aExpectedValue);
+    }
+    return result;
+  }
+
+  function testPage1(aEvent)
+  {
+    let {event: { clientX: clientX, clientY: clientY }, x: x, y: y} = aEvent;
+
+    info("testPage1 " + aEvent.type +
+         " clientX " + clientX + " clientY " + clientY +
+         " x " + x + " y " + y);
+
+    // x and y are in document coordinates.
+    // clientX and clientY are in view coordinates.
+    // since we are scrolled at the top, both are expected to be approximately
+    // the same.
+    ok(checkValue(x, mousePos.x), "x is in range");
+    ok(checkValue(y, mousePos.y), "y is in range");
+
+    ok(checkValue(clientX, mousePos.x), "clientX is in range");
+    ok(checkValue(clientY, mousePos.y), "clientY is in range");
+
+    // we give document-relative coordinates here.
+    let offset = editor.getOffsetAtLocation(x, y);
+    ok(checkValue(offset, expectedOffset), "character offset is correct");
+
+    let rect = {x: x, y: y};
+    let viewCoords = editor.convertCoordinates(rect, "document", "view");
+    ok(checkValue(viewCoords.x, clientX), "viewCoords.x is in range");
+    ok(checkValue(viewCoords.y, clientY), "viewCoords.y is in range");
+
+    rect = {x: clientX, y: clientY};
+    let docCoords = editor.convertCoordinates(rect, "view", "document");
+    ok(checkValue(docCoords.x, x), "docCoords.x is in range");
+    ok(checkValue(docCoords.y, y), "docCoords.y is in range");
+
+    // we are given document-relative coordinates.
+    let offsetPos = editor.getLocationAtOffset(expectedOffset);
+    ok(checkValue(offsetPos.x, x), "offsetPos.x is in range");
+    ok(checkValue(offsetPos.y, y), "offsetPos.y is in range");
+
+    // Scroll the view and test again.
+    let topIndex = Math.round(editor.getLineCount() / 2);
+    editor.setTopIndex(topIndex);
+    expectedOffset += editor.getLineStart(topIndex);
+
+    executeSoon(doMouseMove.bind(null, testPage2));
+  }
+
+  function testPage2(aEvent)
+  {
+    let {event: { clientX: clientX, clientY: clientY }, x: x, y: y} = aEvent;
+
+    info("testPage2 " + aEvent.type +
+         " clientX " + clientX + " clientY " + clientY +
+         " x " + x + " y " + y);
+
+    // after page scroll document coordinates need to be different from view
+    // coordinates.
+    ok(checkValue(x, mousePos.x), "x is not different from clientX");
+    ok(!checkValue(y, mousePos.y), "y is different from clientY");
+
+    ok(checkValue(clientX, mousePos.x), "clientX is in range");
+    ok(checkValue(clientY, mousePos.y), "clientY is in range");
+
+    // we give document-relative coordinates here.
+    let offset = editor.getOffsetAtLocation(x, y);
+    ok(checkValue(offset, expectedOffset), "character offset is correct");
+
+    let rect = {x: x, y: y};
+    let viewCoords = editor.convertCoordinates(rect, "document", "view");
+    ok(checkValue(viewCoords.x, clientX), "viewCoords.x is in range");
+    ok(checkValue(viewCoords.y, clientY), "viewCoords.y is in range");
+
+    rect = {x: clientX, y: clientY};
+    let docCoords = editor.convertCoordinates(rect, "view", "document");
+    ok(checkValue(docCoords.x, x), "docCoords.x is in range");
+    ok(checkValue(docCoords.y, y), "docCoords.y is in range");
+
+    // we are given document-relative coordinates.
+    let offsetPos = editor.getLocationAtOffset(expectedOffset);
+    ok(checkValue(offsetPos.x, x), "offsetPos.x is in range");
+    ok(checkValue(offsetPos.y, y), "offsetPos.y is in range");
+
+    executeSoon(testEnd);
+  }
+
+  function testEnd()
+  {
+    if (editor) {
+      editor.destroy();
+    }
+    if (testWin) {
+      testWin.close();
+    }
+
+    waitForFocus(finish, window);
+  }
+
+  openSourceEditorWindow(editorLoaded);
+}
--- a/browser/devtools/sourceeditor/test/head.js
+++ b/browser/devtools/sourceeditor/test/head.js
@@ -108,8 +108,75 @@ function waitForSelection(aExpectedStrin
 }
 
 waitForSelection._polls = 0;
 waitForSelection.__monotonicCounter = 0;
 waitForSelection.__defineGetter__("_monotonicCounter", function () {
   return waitForSelection.__monotonicCounter++;
 });
 
+/**
+ * Open a new window with a source editor inside.
+ *
+ * @param function aCallback
+ *        The function you want invoked once the editor is loaded. The function
+ *        is given two arguments: editor instance and the window object.
+ * @param object [aOptions]
+ *        The options object to pass to the SourceEditor.init() method.
+ */
+function openSourceEditorWindow(aCallback, aOptions) {
+  const windowUrl = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
+    "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
+    " title='Test for Source Editor' width='600' height='500'><box flex='1'/></window>";
+  const windowFeatures = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
+
+  let editor = null;
+  let testWin = Services.ww.openWindow(null, windowUrl, "_blank",
+                                       windowFeatures, null);
+  testWin.addEventListener("load", function onWindowLoad() {
+    testWin.removeEventListener("load", onWindowLoad, false);
+    waitForFocus(initEditor, testWin);
+  }, false);
+
+  function initEditor()
+  {
+    let tempScope = {};
+    Cu.import("resource:///modules/source-editor.jsm", tempScope);
+
+    let box = testWin.document.querySelector("box");
+    editor = new tempScope.SourceEditor();
+    editor.init(box, aOptions || {}, editorLoaded);
+  }
+
+  function editorLoaded()
+  {
+    editor.focus();
+    waitForFocus(aCallback.bind(null, editor, testWin), testWin);
+  }
+}
+
+/**
+ * Get text needed to fill the editor view.
+ *
+ * @param object aEditor
+ *        The SourceEditor instance you work with.
+ * @param number aPages
+ *        The number of pages you want filled with lines.
+ * @return string
+ *         The string you can insert into the editor so you fill the desired
+ *         number of pages.
+ */
+function fillEditor(aEditor, aPages) {
+  let view = aEditor._view;
+  let model = aEditor._model;
+
+  let lineHeight = view.getLineHeight();
+  let editorHeight = view.getClientArea().height;
+  let linesPerPage = Math.floor(editorHeight / lineHeight);
+  let totalLines = aPages * linesPerPage;
+
+  let text = "";
+  for (let i = 0; i < totalLines; i++) {
+    text += "l" + i + " lorem ipsum dolor sit amet. lipsum foobaris bazbaz,\n";
+  }
+
+  return text;
+}
--- a/browser/devtools/webconsole/NetworkPanel.jsm
+++ b/browser/devtools/webconsole/NetworkPanel.jsm
@@ -27,25 +27,34 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
 this.EXPORTED_SYMBOLS = ["NetworkPanel"];
 
 /**
  * Creates a new NetworkPanel.
  *
+ * @constructor
  * @param nsIDOMNode aParent
  *        Parent node to append the created panel to.
  * @param object aHttpActivity
  *        HttpActivity to display in the panel.
+ * @param object aWebConsoleFrame
+ *        The parent WebConsoleFrame object that owns this network panel
+ *        instance.
  */
-this.NetworkPanel = function NetworkPanel(aParent, aHttpActivity)
+this.NetworkPanel =
+function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame)
 {
   let doc = aParent.ownerDocument;
   this.httpActivity = aHttpActivity;
+  this.webconsole = aWebConsoleFrame;
+  this._longStringClick = this._longStringClick.bind(this);
+  this._responseBodyFetch = this._responseBodyFetch.bind(this);
+  this._requestBodyFetch = this._requestBodyFetch.bind(this);
 
   // Create the underlaying panel
   this.panel = createElement(doc, "panel", {
     label: l10n.getStr("NetworkPanel.label"),
     titlebar: "normal",
     noautofocus: "true",
     noautohide: "true",
     close: "true"
@@ -62,16 +71,17 @@ this.NetworkPanel = function NetworkPane
 
   // Destroy the panel when it's closed.
   this.panel.addEventListener("popuphidden", function onPopupHide() {
     self.panel.removeEventListener("popuphidden", onPopupHide, false);
     self.panel.parentNode.removeChild(self.panel);
     self.panel = null;
     self.iframe = null;
     self.httpActivity = null;
+    self.webconsole = null;
 
     if (self.linkNode) {
       self.linkNode._panelOpen = false;
       self.linkNode = null;
     }
   }, false);
 
   // Set the document object and update the content once the panel is loaded.
@@ -212,38 +222,18 @@ NetworkPanel.prototype =
 
   /**
    *
    * @returns boolean
    *          True if the response body contains text, false otherwise.
    */
   get _isResponseBodyTextData()
   {
-    let contentType = this.contentType;
-
-    if (!contentType)
-      return false;
-
-    if (contentType.indexOf("text/") == 0) {
-      return true;
-    }
-
-    switch (NetworkHelper.mimeCategoryMap[contentType]) {
-      case "txt":
-      case "js":
-      case "json":
-      case "css":
-      case "html":
-      case "svg":
-      case "xml":
-        return true;
-
-      default:
-        return false;
-    }
+    return this.contentType ?
+           NetworkHelper.isTextMimeType(this.contentType) : false;
   },
 
   /**
    * Tells if the server response is cached.
    *
    * @returns boolean
    *          Returns true if the server responded that the request is already
    *          in the browser's cache, false otherwise.
@@ -257,30 +247,37 @@ NetworkPanel.prototype =
    * Tells if the request body includes form data.
    *
    * @returns boolean
    *          Returns true if the posted body contains form data.
    */
   get _isRequestBodyFormData()
   {
     let requestBody = this.httpActivity.request.postData.text;
+    if (typeof requestBody == "object" && requestBody.type == "longString") {
+      requestBody = requestBody.initial;
+    }
     return this._fromDataRegExp.test(requestBody);
   },
 
   /**
    * Appends the node with id=aId by the text aValue.
    *
+   * @private
    * @param string aId
    * @param string aValue
-   * @returns void
+   * @return nsIDOMElement
+   *         The DOM element with id=aId.
    */
-  _appendTextNode: function NP_appendTextNode(aId, aValue)
+  _appendTextNode: function NP__appendTextNode(aId, aValue)
   {
     let textNode = this.document.createTextNode(aValue);
-    this.document.getElementById(aId).appendChild(textNode);
+    let elem = this.document.getElementById(aId);
+    elem.appendChild(textNode);
+    return elem;
   },
 
   /**
    * Generates some HTML to display the key-value pair of the aList data. The
    * generated HTML is added to node with id=aParentId.
    *
    * @param string aParentId
    *        Id of the parent node to append the list to.
@@ -297,19 +294,25 @@ NetworkPanel.prototype =
     let doc = this.document;
 
     aList.sort(function(a, b) {
       return a.name.toLowerCase() < b.name.toLowerCase();
     });
 
     aList.forEach(function(aItem) {
       let name = aItem.name;
+      if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) {
+        return;
+      }
+
       let value = aItem.value;
-      if (aIgnoreCookie && name == "Cookie") {
-        return;
+      let longString = null;
+      if (typeof value == "object" && value.type == "longString") {
+        value = value.initial;
+        longString = true;
       }
 
       /**
        * The following code creates the HTML:
        * <tr>
        * <th scope="row" class="property-name">${line}:</th>
        * <td class="property-value">${aList[line]}</td>
        * </tr>
@@ -322,31 +325,76 @@ NetworkPanel.prototype =
       th.setAttribute("class", "property-name");
       th.appendChild(textNode);
       row.appendChild(th);
 
       textNode = doc.createTextNode(value);
       let td = doc.createElement("td");
       td.setAttribute("class", "property-value");
       td.appendChild(textNode);
+
+      if (longString) {
+        let a = doc.createElement("a");
+        a.href = "#";
+        a.className = "longStringEllipsis";
+        a.addEventListener("mousedown", this._longStringClick.bind(this, aItem));
+        a.textContent = l10n.getStr("longStringEllipsis");
+        td.appendChild(a);
+      }
+
       row.appendChild(td);
 
       parent.appendChild(row);
-    });
+    }.bind(this));
+  },
+
+  /**
+   * The click event handler for the ellipsis which allows the user to retrieve
+   * the full header value.
+   *
+   * @private
+   * @param object aHeader
+   *        The header object with the |name| and |value| properties.
+   * @param nsIDOMEvent aEvent
+   *        The DOM click event object.
+   */
+  _longStringClick: function NP__longStringClick(aHeader, aEvent)
+  {
+    aEvent.preventDefault();
+
+    let longString = this.webconsole.webConsoleClient.longString(aHeader.value);
+
+    longString.substring(longString.initial.length, longString.length,
+      function NP__onLongStringSubstring(aResponse)
+      {
+        if (aResponse.error) {
+          Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
+          return;
+        }
+
+        aHeader.value = aHeader.value.initial + aResponse.substring;
+
+        let textNode = aEvent.target.previousSibling;
+        textNode.textContent += aResponse.substring;
+        textNode.parentNode.removeChild(aEvent.target);
+      });
   },
 
   /**
    * Displays the node with id=aId.
    *
+   * @private
    * @param string aId
-   * @returns void
+   * @return nsIDOMElement
+   *         The element with id=aId.
    */
-  _displayNode: function NP_displayNode(aId)
+  _displayNode: function NP__displayNode(aId)
   {
-    this.document.getElementById(aId).style.display = "block";
+    let elem = this.document.getElementById(aId);
+    elem.style.display = "block";
   },
 
   /**
    * Sets the request URL, request method, the timing information when the
    * request started and the request header content on the NetworkPanel.
    * If the request header contains cookie data, a list of sent cookies is
    * generated and a special sent cookie section is displayed + the cookie list
    * added to it.
@@ -448,41 +496,55 @@ NetworkPanel.prototype =
         deltaDuration += ms;
       }
     });
 
     this._appendTextNode("responseHeadersInfo",
       this._format("durationMS", [deltaDuration]));
 
     this._displayNode("responseContainer");
-    this._appendList("responseHeadersContent", response.headers);
+    this._appendList("responseHeadersContent", response.headers, true);
+
+    if (response.cookies.length > 0) {
+      this._displayNode("responseCookie");
+      this._appendList("responseCookieContent", response.cookies);
+    }
   },
 
   /**
    * Displays the respones image section, sets the source of the image displayed
    * in the image response section to the request URL and the duration between
    * the receiving of the response header and the end of the request. Once the
    * image is loaded, the size of the requested image is set.
    *
    * @returns void
    */
   _displayResponseImage: function NP__displayResponseImage()
   {
     let self = this;
     let timing = this.httpActivity.timings;
     let request = this.httpActivity.request;
+    let response = this.httpActivity.response;
     let cached = "";
 
     if (this._isResponseCached) {
       cached = "Cached";
     }
 
     let imageNode = this.document.getElementById("responseImage" +
                                                  cached + "Node");
-    imageNode.setAttribute("src", request.url);
+
+    let text = response.content.text;
+    if (typeof text == "object" && text.type == "longString") {
+      this._showResponseBodyFetchLink();
+    }
+    else {
+      imageNode.setAttribute("src",
+        "data:" + this.contentType + ";base64," + text);
+    }
 
     // This function is called to set the imageInfo.
     function setImageInfo() {
       self._appendTextNode("responseImage" + cached + "Info",
         self._format("imageSizeDeltaDurationMS",
           [ imageNode.width, imageNode.height, timing.receive ]
         )
       );
@@ -515,18 +577,78 @@ NetworkPanel.prototype =
     let timing = this.httpActivity.timings;
     let response = this.httpActivity.response;
     let cached =  this._isResponseCached ? "Cached" : "";
 
     this._appendTextNode("responseBody" + cached + "Info",
       this._format("durationMS", [timing.receive]));
 
     this._displayNode("responseBody" + cached);
-    this._appendTextNode("responseBody" + cached + "Content",
-                         response.content.text);
+
+    let text = response.content.text;
+    if (typeof text == "object") {
+      text = text.initial;
+      this._showResponseBodyFetchLink();
+    }
+
+    this._appendTextNode("responseBody" + cached + "Content", text);
+  },
+
+  /**
+   * Show the "fetch response body" link.
+   * @private
+   */
+  _showResponseBodyFetchLink: function NP__showResponseBodyFetchLink()
+  {
+    let content = this.httpActivity.response.content;
+
+    let elem = this._appendTextNode("responseBodyFetchLink",
+      this._format("fetchRemainingResponseContentLink",
+                   [content.text.length - content.text.initial.length]));
+
+    elem.style.display = "block";
+    elem.addEventListener("mousedown", this._responseBodyFetch);
+  },
+
+  /**
+   * Click event handler for the link that allows users to fetch the remaining
+   * response body.
+   *
+   * @private
+   * @param nsIDOMEvent aEvent
+   */
+  _responseBodyFetch: function NP__responseBodyFetch(aEvent)
+  {
+    aEvent.target.style.display = "none";
+    aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
+
+    let content = this.httpActivity.response.content;
+    let longString = this.webconsole.webConsoleClient.longString(content.text);
+    longString.substring(longString.initial.length, longString.length,
+      function NP__onLongStringSubstring(aResponse)
+      {
+        if (aResponse.error) {
+          Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
+          return;
+        }
+
+        content.text = content.text.initial + aResponse.substring;
+        let cached =  this._isResponseCached ? "Cached" : "";
+
+        if (this._responseIsImage) {
+          let imageNode = this.document.getElementById("responseImage" +
+                                                       cached + "Node");
+          imageNode.src =
+            "data:" + this.contentType + ";base64," + content.text;
+        }
+        else {
+          this._appendTextNode("responseBody" + cached + "Content",
+                               aResponse.substring);
+        }
+      }.bind(this));
   },
 
   /**
    * Displays the `Unknown Content-Type hint` and sets the duration between the
    * receiving of the response header on the NetworkPanel.
    *
    * @returns void
    */
@@ -579,23 +701,17 @@ NetworkPanel.prototype =
       case this._INIT:
         this._displayRequestHeader();
         this._state = this._DISPLAYED_REQUEST_HEADER;
         // FALL THROUGH
 
       case this._DISPLAYED_REQUEST_HEADER:
         // Process the request body if there is one.
         if (!this.httpActivity.discardRequestBody && request.postData.text) {
-          // Check if we send some form data. If so, display the form data special.
-          if (this._isRequestBodyFormData) {
-            this._displayRequestForm();
-          }
-          else {
-            this._displayRequestBody();
-          }
+          this._updateRequestBody();
           this._state = this._DISPLAYED_REQUEST_BODY;
         }
         // FALL THROUGH
 
       case this._DISPLAYED_REQUEST_BODY:
         if (!response.headers.length || !Object.keys(timing).length) {
           break;
         }
@@ -627,18 +743,72 @@ NetworkPanel.prototype =
           this._displayResponseBody();
         }
         break;
     }
 
     if (this._onUpdate) {
       this._onUpdate();
     }
-  }
-}
+  },
+
+  /**
+   * Update the panel to hold the current information we have about the request
+   * body.
+   * @private
+   */
+  _updateRequestBody: function NP__updateRequestBody()
+  {
+    let postData = this.httpActivity.request.postData;
+    if (typeof postData.text == "object" && postData.text.type == "longString") {
+      let elem = this._appendTextNode("requestBodyFetchLink",
+        this._format("fetchRemainingRequestContentLink",
+                     [postData.text.length - postData.text.initial.length]));
+
+      elem.style.display = "block";
+      elem.addEventListener("mousedown", this._requestBodyFetch);
+      return;
+    }
+
+    // Check if we send some form data. If so, display the form data special.
+    if (this._isRequestBodyFormData) {
+      this._displayRequestForm();
+    }
+    else {
+      this._displayRequestBody();
+    }
+  },
+
+  /**
+   * Click event handler for the link that allows users to fetch the remaining
+   * request body.
+   *
+   * @private
+   * @param nsIDOMEvent aEvent
+   */
+  _requestBodyFetch: function NP__requestBodyFetch(aEvent)
+  {
+    aEvent.target.style.display = "none";
+    aEvent.target.removeEventListener("mousedown", this._responseBodyFetch);
+
+    let postData = this.httpActivity.request.postData;
+    let longString = this.webconsole.webConsoleClient.longString(postData.text);
+    longString.substring(longString.initial.length, longString.length,
+      function NP__onLongStringSubstring(aResponse)
+      {
+        if (aResponse.error) {
+          Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
+          return;
+        }
+
+        postData.text = postData.text.initial + aResponse.substring;
+        this._updateRequestBody();
+      }.bind(this));
+  },
+};
 
 /**
  * Creates a DOMNode and sets all the attributes of aAttributes on the created
  * element.
  *
  * @param nsIDOMDocument aDocument
  *        Document to create the new DOMNode.
  * @param string aTag
--- a/browser/devtools/webconsole/NetworkPanel.xhtml
+++ b/browser/devtools/webconsole/NetworkPanel.xhtml
@@ -15,29 +15,29 @@
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet" href="chrome://browser/skin/devtools/webconsole_networkpanel.css" type="text/css"/>
 </head>
 <body role="application">
 <table id="header">
   <tr>
     <th class="property-name"
-        scope="row">&networkPanel.requestURL;:</th>
+        scope="row">&networkPanel.requestURLColon;</th>
     <td class="property-value"
         id="headUrl"></td>
   </tr>
   <tr>
     <th class="property-name"
-        scope="row">&networkPanel.requestMethod;:</th>
+        scope="row">&networkPanel.requestMethodColon;</th>
     <td class="property-value"
         id="headMethod"></td>
   </tr>
   <tr>
     <th class="property-name"
-        scope="row">&networkPanel.statusCode;:</th>
+        scope="row">&networkPanel.statusCodeColon;</th>
     <td class="property-value"
         id="headStatus"></td>
   </tr>
 </table>
 
 <div class="group">
   <h1>
     &networkPanel.requestHeaders;
@@ -53,25 +53,31 @@
   <div id="requestBody" style="display:none">
     <h1>&networkPanel.requestBody;</h1>
     <table class="property-table" id="requestBodyContent"></table>
   </div>
   <div id="requestFormData" style="display:none">
     <h1>&networkPanel.requestFormData;</h1>
     <table class="property-table" id="requestFormDataContent"></table>
   </div>
+  <p id="requestBodyFetchLink" style="display:none"></p>
 </div>
 
 <div class="group" id="responseContainer" style="display:none">
   <h1>
     &networkPanel.responseHeaders;
     <span id="responseHeadersInfo" class="info">&Delta;</span>
   </h1>
   <table class="property-table" id="responseHeadersContent"></table>
 
+  <div id="responseCookie" style="display:none">
+    <h1>&networkPanel.responseCookie;</h1>
+    <table class="property-table" id="responseCookieContent"></table>
+  </div>
+
   <div id="responseBody" style="display:none">
     <h1>
       &networkPanel.responseBody;
       <span class="info" id="responseBodyInfo">&Delta;</span>
     </h1>
     <table class="property-table" id="responseBodyContent"></table>
   </div>
   <div id="responseBodyCached" style="display:none">
@@ -107,11 +113,12 @@
     <h1>
       &networkPanel.responseImageCached;
       <span id="responseImageCachedInfo" class="info"></span>
     </h1>
     <div id="responseImageNodeDiv">
       <img id="responseImageCachedNode" />
     </div>
   </div>
+  <p id="responseBodyFetchLink" style="display:none"></p>
 </div>
 </body>
 </html>
--- a/browser/devtools/webconsole/PropertyPanel.jsm
+++ b/browser/devtools/webconsole/PropertyPanel.jsm
@@ -142,16 +142,20 @@ PropertyTreeView.prototype = {
       aItem._open = false;
       aItem._children = null;
 
       if (this._releaseObject) {
         ["value", "get", "set"].forEach(function(aProp) {
           let val = aItem[aProp];
           if (val && val.actor) {
             this._objectActors.push(val.actor);
+            if (typeof val.displayString == "object" &&
+                val.displayString.type == "longString") {
+              this._objectActors.push(val.displayString.actor);
+            }
           }
         }, this);
       }
     }, this);
   },
 
   /**
    * Inspect a local object.
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -113,16 +113,18 @@ MOCHITEST_BROWSER_FILES = \
 	browser_cached_messages.js \
 	browser_bug664688_sandbox_update_after_navigation.js \
 	browser_webconsole_menustatus.js \
 	browser_result_format_as_string.js \
 	browser_webconsole_bug_737873_mixedcontent.js \
 	browser_output_breaks_after_console_dir_uninspectable.js \
 	browser_console_log_inspectable_object.js \
 	browser_bug_638949_copy_link_location.js \
+	browser_output_longstring_expand.js \
+	browser_netpanel_longstring_expand.js \
 	head.js \
 	$(NULL)
 
 ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
 MOCHITEST_BROWSER_FILES += \
         browser_webconsole_bug_618311_private_browsing.js \
         $(NULL)
 endif
@@ -131,16 +133,17 @@ MOCHITEST_BROWSER_FILES += \
 	test-console.html \
 	test-network.html \
 	test-network-request.html \
 	test-mutation.html \
 	testscript.js \
 	test-filter.html \
 	test-observe-http-ajax.html \
 	test-data.json \
+	test-data.json^headers^ \
 	test-property-provider.html \
 	test-error.html \
 	test-duplicate-error.html \
 	test-image.png \
 	test-encoding-ISO-8859-1.html \
 	test-bug-593003-iframe-wrong-hud.html \
 	test-bug-593003-iframe-wrong-hud-iframe.html \
 	test-console-replaced-api.html \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_netpanel_longstring_expand.js
@@ -0,0 +1,309 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tests that the network panel works with LongStringActors.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
+
+const TEST_IMG_BASE64 =
+  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJREFU" +
+  "OI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuFDjq5dxD8" +
+  "FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNUyhsKAChRBQcP" +
+  "FVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQEod+Z5sP1FizDxGgy" +
+  "BqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTsDizDRMPuIOC+zEeTMZo9" +
+  "BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joREN2uqnlBmCwW1hIJagtev4f3z" +
+  "A16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOnidadmwtwsnMu18q83/kHSou+bFND" +
+  "Dr4AAAAASUVORK5CYII=";
+
+let testDriver;
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, testNetworkPanel);
+  }, true);
+}
+
+function testNetworkPanel() {
+  testDriver = testGen();
+  testDriver.next();
+}
+
+function checkIsVisible(aPanel, aList) {
+  for (let id in aList) {
+    let node = aPanel.document.getElementById(id);
+    let isVisible = aList[id];
+    is(node.style.display, (isVisible ? "block" : "none"), id + " isVisible=" + isVisible);
+  }
+}
+
+function checkNodeContent(aPanel, aId, aContent) {
+  let node = aPanel.document.getElementById(aId);
+  if (node == null) {
+    ok(false, "Tried to access node " + aId + " that doesn't exist!");
+  }
+  else if (node.textContent.indexOf(aContent) != -1) {
+    ok(true, "checking content of " + aId);
+  }
+  else {
+    ok(false, "Got false value for " + aId + ": " + node.textContent + " doesn't have " + aContent);
+  }
+}
+
+function checkNodeKeyValue(aPanel, aId, aKey, aValue) {
+  let node = aPanel.document.getElementById(aId);
+
+  let headers = node.querySelectorAll("th");
+  for (let i = 0; i < headers.length; i++) {
+    if (headers[i].textContent == (aKey + ":")) {
+      is(headers[i].nextElementSibling.textContent, aValue,
+         "checking content of " + aId + " for key " + aKey);
+      return;
+    }
+  }
+
+  ok(false, "content check failed for " + aId + ", key " + aKey);
+}
+
+function testGen() {
+  let hud = HUDService.getHudByWindow(content);
+  let filterBox = hud.ui.filterBox;
+
+  let headerValue = (new Array(456)).join("fooz bar");
+  let headerValueGrip = {
+    type: "longString",
+    initial: headerValue.substr(0, 123),
+    length: headerValue.length,
+    actor: "faktor",
+    _fullString: headerValue,
+  };
+
+  let imageContentGrip = {
+    type: "longString",
+    initial: TEST_IMG_BASE64.substr(0, 143),
+    length: TEST_IMG_BASE64.length,
+    actor: "faktor2",
+    _fullString: TEST_IMG_BASE64,
+  };
+
+  let postDataValue = (new Array(123)).join("post me");
+  let postDataGrip = {
+    type: "longString",
+    initial: postDataValue.substr(0, 172),
+    length: postDataValue.length,
+    actor: "faktor3",
+    _fullString: postDataValue,
+  };
+
+  let httpActivity = {
+    updates: ["responseContent", "eventTimings"],
+    discardRequestBody: false,
+    discardResponseBody: false,
+    startedDateTime: (new Date()).toISOString(),
+    request: {
+      url: TEST_IMG,
+      method: "GET",
+      cookies: [],
+      headers: [
+        { name: "foo", value: "bar" },
+        { name: "loongstring", value: headerValueGrip },
+      ],
+      postData: { text: postDataGrip },
+    },
+    response: {
+      httpVersion: "HTTP/3.14",
+      status: 2012,
+      statusText: "ddahl likes tacos :)",
+      headers: [
+        { name: "Content-Type", value: "image/png" },
+      ],
+      content: { mimeType: "image/png", text: imageContentGrip },
+      cookies: [],
+    },
+    timings: { wait: 15, receive: 23 },
+  };
+
+  let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+
+  is(filterBox._netPanel, networkPanel,
+     "Network panel stored on the anchor object");
+
+  networkPanel._onUpdate = function() {
+    networkPanel._onUpdate = null;
+    executeSoon(function() {
+      testDriver.next();
+    });
+  };
+
+  yield;
+
+  info("test 1: check if a header value is expandable");
+
+  checkIsVisible(networkPanel, {
+    requestCookie: false,
+    requestFormData: false,
+    requestBody: false,
+    requestBodyFetchLink: true,
+    responseContainer: true,
+    responseBody: false,
+    responseNoBody: false,
+    responseImage: true,
+    responseImageCached: false,
+    responseBodyFetchLink: true,
+  });
+
+  checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar");
+  checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring",
+                    headerValueGrip.initial + "[\u2026]");
+
+  let webConsoleClient = networkPanel.webconsole.webConsoleClient;
+  let longStringFn = webConsoleClient.longString;
+
+  let expectedGrip = headerValueGrip;
+
+  function longStringClientProvider(aLongString)
+  {
+    is(aLongString, expectedGrip,
+       "longString grip is correct");
+
+    return {
+      initial: expectedGrip.initial,
+      length: expectedGrip.length,
+      substring: function(aStart, aEnd, aCallback) {
+        is(aStart, expectedGrip.initial.length,
+           "substring start is correct");
+        is(aEnd, expectedGrip.length,
+           "substring end is correct");
+
+        executeSoon(function() {
+          aCallback({
+            substring: expectedGrip._fullString.substring(aStart, aEnd),
+          });
+
+          executeSoon(function() {
+            testDriver.next();
+          });
+        });
+      },
+    };
+  }
+
+  webConsoleClient.longString = longStringClientProvider;
+
+  let clickable = networkPanel.document
+                  .querySelector("#requestHeadersContent .longStringEllipsis");
+  ok(clickable, "long string ellipsis is shown");
+
+  EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+                             networkPanel.document.defaultView);
+
+  yield;
+
+  clickable = networkPanel.document
+              .querySelector("#requestHeadersContent .longStringEllipsis");
+  ok(!clickable, "long string ellipsis is not shown");
+
+  checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring",
+                    expectedGrip._fullString);
+
+  info("test 2: check that response body image fetching works");
+  expectedGrip = imageContentGrip;
+
+  let imgNode = networkPanel.document.getElementById("responseImageNode");
+  ok(!imgNode.getAttribute("src"), "no image is displayed");
+
+  clickable = networkPanel.document.querySelector("#responseBodyFetchLink");
+  EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+                             networkPanel.document.defaultView);
+
+  yield;
+
+  imgNode = networkPanel.document.getElementById("responseImageNode");
+  is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
+     "displayed image is correct");
+  is(clickable.style.display, "none", "#responseBodyFetchLink is not visible");
+
+  info("test 3: expand the request body");
+
+  expectedGrip = postDataGrip;
+
+  clickable = networkPanel.document.querySelector("#requestBodyFetchLink");
+  EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+                             networkPanel.document.defaultView);
+  yield;
+
+  is(clickable.style.display, "none", "#requestBodyFetchLink is not visible");
+
+  checkIsVisible(networkPanel, {
+    requestBody: true,
+    requestBodyFetchLink: false,
+  });
+
+  checkNodeContent(networkPanel, "requestBodyContent", expectedGrip._fullString);
+
+  webConsoleClient.longString = longStringFn;
+
+  networkPanel.panel.hidePopup();
+
+  info("test 4: reponse body long text");
+
+  httpActivity.response.content.mimeType = "text/plain";
+  httpActivity.response.headers[0].value = "text/plain";
+
+  expectedGrip = imageContentGrip;
+
+  // Reset response.content.text to avoid caching of the full string.
+  httpActivity.response.content.text = expectedGrip;
+
+  networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+  is(filterBox._netPanel, networkPanel,
+     "Network panel stored on httpActivity object");
+
+  networkPanel._onUpdate = function() {
+    networkPanel._onUpdate = null;
+    executeSoon(function() {
+      testDriver.next();
+    });
+  };
+
+  yield;
+
+  checkIsVisible(networkPanel, {
+    requestCookie: false,
+    requestFormData: false,
+    requestBody: true,
+    requestBodyFetchLink: false,
+    responseContainer: true,
+    responseBody: true,
+    responseNoBody: false,
+    responseImage: false,
+    responseImageCached: false,
+    responseBodyFetchLink: true,
+  });
+
+  checkNodeContent(networkPanel, "responseBodyContent", expectedGrip.initial);
+
+  webConsoleClient.longString = longStringClientProvider;
+
+  clickable = networkPanel.document.querySelector("#responseBodyFetchLink");
+  EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+                             networkPanel.document.defaultView);
+
+  yield;
+
+  webConsoleClient.longString = longStringFn;
+  is(clickable.style.display, "none", "#responseBodyFetchLink is not visible");
+  checkNodeContent(networkPanel, "responseBodyContent", expectedGrip._fullString);
+
+  networkPanel.panel.hidePopup();
+
+  // All done!
+  testDriver = null;
+  executeSoon(finishTest);
+
+  yield;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_output_longstring_expand.js
@@ -0,0 +1,153 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that long strings can be expanded in the console output.
+
+function test()
+{
+  waitForExplicitFinish();
+
+  let tempScope = {};
+  Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+  let DebuggerServer = tempScope.DebuggerServer;
+
+  let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a") +
+                   "foobar";
+  let initialString =
+    longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+  addTab("data:text/html;charset=utf8,test for bug 787981 - check that long strings can be expanded in the output.");
+
+  let hud = null;
+
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    openConsole(null, performTest);
+  }, true);
+
+  function performTest(aHud)
+  {
+    hud = aHud;
+
+    hud.jsterm.clearOutput(true);
+    hud.jsterm.execute("console.log('bazbaz', '" + longString +"', 'boom')");
+
+    waitForSuccess(waitForConsoleLog);
+  }
+
+  let waitForConsoleLog = {
+    name: "console.log output shown",
+    validatorFn: function()
+    {
+      return hud.outputNode.querySelector(".webconsole-msg-console");
+    },
+    successFn: function()
+    {
+      let msg = hud.outputNode.querySelector(".webconsole-msg-console");
+      is(msg.textContent.indexOf("foobar"), -1,
+         "foobar is not shown");
+      isnot(msg.textContent.indexOf("bazbaz"), -1,
+            "bazbaz is shown");
+      isnot(msg.textContent.indexOf("boom"), -1,
+            "boom is shown");
+      isnot(msg.textContent.indexOf(initialString), -1,
+            "initial string is shown");
+
+      let clickable = msg.querySelector(".longStringEllipsis");
+      ok(clickable, "long string ellipsis is shown");
+
+      scrollToVisible(clickable);
+
+      executeSoon(function() {
+        EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+        waitForSuccess(waitForFullString);
+      });
+    },
+    failureFn: finishTest,
+  };
+
+  let waitForFullString = {
+    name: "full string shown",
+    validatorFn: function()
+    {
+      let msg = hud.outputNode.querySelector(".webconsole-msg-log");
+      return msg.textContent.indexOf(longString) > -1;
+    },
+    successFn: function()
+    {
+      let msg = hud.outputNode.querySelector(".webconsole-msg-log");
+      isnot(msg.textContent.indexOf("bazbaz"), -1,
+            "bazbaz is shown");
+      isnot(msg.textContent.indexOf("boom"), -1,
+            "boom is shown");
+
+      let clickable = msg.querySelector(".longStringEllipsis");
+      ok(!clickable, "long string ellipsis is not shown");
+
+      executeSoon(function() {
+        hud.jsterm.clearOutput(true);
+        hud.jsterm.execute("'" + longString +"'");
+        waitForSuccess(waitForExecute);
+      });
+    },
+    failureFn: finishTest,
+  };
+
+  let waitForExecute = {
+    name: "execute() output shown",
+    validatorFn: function()
+    {
+      return hud.outputNode.querySelector(".webconsole-msg-output");
+    },
+    successFn: function()
+    {
+      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
+      isnot(msg.textContent.indexOf(initialString), -1,
+           "initial string is shown");
+      is(msg.textContent.indexOf(longString), -1,
+         "full string is not shown");
+
+      let clickable = msg.querySelector(".longStringEllipsis");
+      ok(clickable, "long string ellipsis is shown");
+
+      scrollToVisible(clickable);
+
+      executeSoon(function() {
+        EventUtils.synthesizeMouse(clickable, 3, 4, {}, hud.iframeWindow);
+        waitForSuccess(waitForFullStringAfterExecute);
+      });
+    },
+    failureFn: finishTest,
+  };
+
+  let waitForFullStringAfterExecute = {
+    name: "full string shown again",
+    validatorFn: function()
+    {
+      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
+      return msg.textContent.indexOf(longString) > -1;
+    },
+    successFn: function()
+    {
+      let msg = hud.outputNode.querySelector(".webconsole-msg-output");
+      let clickable = msg.querySelector(".longStringEllipsis");
+      ok(!clickable, "long string ellipsis is not shown");
+
+      executeSoon(finishTest);
+    },
+    failureFn: finishTest,
+  };
+
+  function scrollToVisible(aNode)
+  {
+    let richListBoxNode = aNode.parentNode;
+    while (richListBoxNode.tagName != "richlistbox") {
+      richListBoxNode = richListBoxNode.parentNode;
+    }
+
+    let boxObject = richListBoxNode.scrollBoxObject;
+    let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
+    nsIScrollBoxObject.ensureElementIsVisible(aNode);
+  }
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_598357_jsterm_output.js
@@ -10,16 +10,23 @@
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
 
 let testEnded = false;
 let pos = -1;
 
 let dateNow = Date.now();
 
+let tempScope = {};
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+
+let longString = (new Array(tempScope.DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
+let initialString = longString.substring(0,
+  tempScope.DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
 let inputValues = [
   // [showsPropertyPanel?, input value, expected output format,
   //    print() output, console output, optional console API test]
 
   // 0
   [false, "'hello \\nfrom \\rthe \\\"string world!'",
     '"hello \\nfrom \\rthe \\"string world!"',
     "hello \nfrom \rthe \"string world!"],
@@ -82,18 +89,26 @@ let inputValues = [
   [true, "[1,2,3,'a','b','c','4','5']", '[1, 2, 3, "a", "b", "c", "4", "5"]',
     '1,2,3,a,b,c,4,5',
     '[1, 2, 3, "a", "b", "c", "4", "5"]'],
 
   // 17
   [true, "({a:'b', c:'d', e:1, f:'2'})", '({a:"b", c:"d", e:1, f:"2"})',
     "[object Object",
     '({a:"b", c:"d", e:1, f:"2"})'],
+
+  // 18
+  [false, "'" + longString + "'",
+    '"' + initialString + "\"[\u2026]", initialString],
 ];
 
+longString = null;
+initialString = null;
+tempScope = null;
+
 let eventHandlers = [];
 let popupShown = [];
 let HUD;
 let testDriver;
 
 function tabLoad(aEvent) {
   browser.removeEventListener(aEvent.type, tabLoad, true);
 
--- a/browser/devtools/webconsole/test/browser_webconsole_network_panel.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_network_panel.js
@@ -4,16 +4,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Tests that the network panel works.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
 const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
 const TEST_ENCODING_ISO_8859_1 = "http://example.com/browser/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html";
 
+const TEST_IMG_BASE64 =
+  "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJREFU" +
+  "OI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuFDjq5dxD8" +
+  "FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNUyhsKAChRBQcP" +
+  "FVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQEod+Z5sP1FizDxGgy" +
+  "BqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTsDizDRMPuIOC+zEeTMZo9" +
+  "BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joREN2uqnlBmCwW1hIJagtev4f3z" +
+  "A16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOnidadmwtwsnMu18q83/kHSou+bFND" +
+  "Dr4AAAAASUVORK5CYII=";
+
 let testDriver;
 
 function test() {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testNetworkPanel);
   }, true);
@@ -75,16 +85,17 @@ function testGen() {
       cookies: [],
       headers: [
         { name: "foo", value: "bar" },
       ],
     },
     response: {
       headers: [],
       content: {},
+      cookies: [],
     },
     timings: {},
   };
 
   let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
 
   is(filterBox._netPanel, networkPanel,
      "Network panel stored on the anchor object");
@@ -218,33 +229,73 @@ function testGen() {
 
   yield;
 
   checkIsVisible(networkPanel, {
     requestBody: true,
     requestFormData: false,
     requestCookie: true,
     responseContainer: true,
+    responseCookie: false,
     responseBody: true,
     responseNoBody: false,
     responseImage: false,
     responseImageCached: false
   });
 
   checkNodeKeyValue(networkPanel, "requestCookieContent", "foo", "bar");
   checkNodeKeyValue(networkPanel, "requestCookieContent", "hello", "world");
   checkNodeContent(networkPanel, "responseBodyContent", "get out here");
   checkNodeContent(networkPanel, "responseBodyInfo", "2ms");
 
   networkPanel.panel.hidePopup();
 
+  // Third run: Test for response cookies.
+  info("test 6b: response cookies");
+  httpActivity.response.cookies.push(
+    { name: "foobar", value: "boom" },
+    { name: "foobaz", value: "omg" }
+  );
+
+  networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+  is(filterBox._netPanel, networkPanel,
+     "Network panel stored on httpActivity object");
+
+  networkPanel._onUpdate = function() {
+    networkPanel._onUpdate = null;
+    executeSoon(function() {
+      testDriver.next();
+    });
+  };
+
+  yield;
+
+  checkIsVisible(networkPanel, {
+    requestBody: true,
+    requestFormData: false,
+    requestCookie: true,
+    responseContainer: true,
+    responseCookie: true,
+    responseBody: true,
+    responseNoBody: false,
+    responseImage: false,
+    responseImageCached: false,
+    responseBodyFetchLink: false,
+  });
+
+  checkNodeKeyValue(networkPanel, "responseCookieContent", "foobar", "boom");
+  checkNodeKeyValue(networkPanel, "responseCookieContent", "foobaz", "omg");
+
+  networkPanel.panel.hidePopup();
+
   // Check image request.
   info("test 7: image request");
   httpActivity.response.headers[1].value = "image/png";
   httpActivity.response.content.mimeType = "image/png";
+  httpActivity.response.content.text = TEST_IMG_BASE64;
   httpActivity.request.url = TEST_IMG;
 
   networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
   networkPanel._onUpdate = function() {
     networkPanel._onUpdate = null;
     executeSoon(function() {
       testDriver.next();
     });
@@ -255,42 +306,37 @@ function testGen() {
   checkIsVisible(networkPanel, {
     requestBody: true,
     requestFormData: false,
     requestCookie: true,
     responseContainer: true,
     responseBody: false,
     responseNoBody: false,
     responseImage: true,
-    responseImageCached: false
+    responseImageCached: false,
+    responseBodyFetchLink: false,
   });
 
   let imgNode = networkPanel.document.getElementById("responseImageNode");
-  is(imgNode.getAttribute("src"), TEST_IMG, "Displayed image is correct");
+  is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
+      "Displayed image is correct");
 
   function checkImageResponseInfo() {
     checkNodeContent(networkPanel, "responseImageInfo", "2ms");
     checkNodeContent(networkPanel, "responseImageInfo", "16x16px");
   }
 
   // Check if the image is loaded already.
-  if (imgNode.width == 0) {
-    imgNode.addEventListener("load", function onLoad() {
-      imgNode.removeEventListener("load", onLoad, false);
-      checkImageResponseInfo();
-      networkPanel.panel.hidePopup();
-      testDriver.next();
-    }, false);
-    // Wait until the image is loaded.
-    yield;
-  }
-  else {
+  imgNode.addEventListener("load", function onLoad() {
+    imgNode.removeEventListener("load", onLoad, false);
     checkImageResponseInfo();
     networkPanel.panel.hidePopup();
-  }
+    testDriver.next();
+  }, false);
+  yield;
 
   // Check cached image request.
   info("test 8: cached image request");
   httpActivity.response.httpVersion = "HTTP/1.1";
   httpActivity.response.status = 304;
   httpActivity.response.statusText = "Not Modified";
 
   networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
@@ -310,17 +356,18 @@ function testGen() {
     responseContainer: true,
     responseBody: false,
     responseNoBody: false,
     responseImage: false,
     responseImageCached: true
   });
 
   let imgNode = networkPanel.document.getElementById("responseImageCachedNode");
-  is(imgNode.getAttribute("src"), TEST_IMG, "Displayed image is correct");
+  is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
+     "Displayed image is correct");
 
   networkPanel.panel.hidePopup();
 
   // Test sent form data.
   info("test 9: sent form data");
   httpActivity.request.postData.text = [
     "Content-Type:      application/x-www-form-urlencoded",
     "Content-Length: 59",
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-data.json^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/json
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1068,16 +1068,24 @@ WebConsoleFrame.prototype = {
       case "dir":
       case "groupEnd": {
         body = { arguments: args };
         let clipboardArray = [];
         args.forEach(function(aValue) {
           clipboardArray.push(WebConsoleUtils.objectActorGripToString(aValue));
           if (aValue && typeof aValue == "object" && aValue.actor) {
             objectActors.push(aValue.actor);
+            let displayStringIsLong = typeof aValue.displayString == "object" &&
+                                      aValue.displayString.type == "longString";
+            if (aValue.type == "longString" || displayStringIsLong) {
+              clipboardArray.push(l10n.getStr("longStringEllipsis"));
+            }
+            if (displayStringIsLong) {
+              objectActors.push(aValue.displayString.actor);
+            }
           }
         }, this);
         clipboardText = clipboardArray.join(" ");
         sourceURL = aMessage.filename;
         sourceLine = aMessage.lineNumber;
 
         if (level == "dir") {
           body.objectProperties = aMessage.objectProperties;
@@ -1706,17 +1714,17 @@ WebConsoleFrame.prototype = {
 
         aNode._panelOpen = false;
         aNode._netPanel = null;
       });
 
       aNode._panelOpen = true;
     }.bind(this);
 
-    let netPanel = new NetworkPanel(this.popupset, aHttpActivity);
+    let netPanel = new NetworkPanel(this.popupset, aHttpActivity, this);
     netPanel.linkNode = aNode;
 
     if (!actor) {
       openPanel();
     }
 
     return netPanel;
   },
@@ -2355,16 +2363,38 @@ WebConsoleFrame.prototype = {
       if (aContainer.firstChild) {
         aContainer.appendChild(this.document.createTextNode(" "));
       }
 
       let text = WebConsoleUtils.objectActorGripToString(aItem);
 
       if (aItem && typeof aItem != "object" || !aItem.inspectable) {
         aContainer.appendChild(this.document.createTextNode(text));
+
+        let longString = null;
+        if (aItem.type == "longString") {
+          longString = aItem;
+        }
+        else if (!aItem.inspectable &&
+                 typeof aItem.displayString == "object" &&
+                 aItem.displayString.type == "longString") {
+          longString = aItem.displayString;
+        }
+
+        if (longString) {
+          let ellipsis = this.document.createElement("description");
+          ellipsis.classList.add("hud-clickable");
+          ellipsis.classList.add("longStringEllipsis");
+          ellipsis.textContent = l10n.getStr("longStringEllipsis");
+
+          this._addMessageLinkCallback(ellipsis,
+            this._longStringClick.bind(this, aMessage, longString, null));
+
+          aContainer.appendChild(ellipsis);
+        }
         return;
       }
 
       // For inspectable objects.
       let elem = this.document.createElement("description");
       elem.classList.add("hud-clickable");
       elem.setAttribute("aria-haspopup", "true");
       elem.appendChild(this.document.createTextNode(text));
@@ -2372,16 +2402,63 @@ WebConsoleFrame.prototype = {
       this._addMessageLinkCallback(elem,
         this._consoleLogClick.bind(this, aMessage, elem, aItem));
 
       aContainer.appendChild(elem);
     }, this);
   },
 
   /**
+   * Click event handler for the ellipsis shown immediately after a long string.
+   * This method retrieves the full string and updates the console output to
+   * show it.
+   *
+   * @private
+   * @param nsIDOMElement aMessage
+   *        The message element.
+   * @param object aActor
+   *        The LongStringActor instance we work with.
+   * @param [function] aFormatter
+   *        Optional function you can use to format the string received from the
+   *        server, before being displayed in the console.
+   * @param nsIDOMElement aEllipsis
+   *        The DOM element the user can click on to expand the string.
+   * @param nsIDOMEvent aEvent
+   *        The DOM click event triggered by the user.
+   */
+  _longStringClick:
+  function WCF__longStringClick(aMessage, aActor, aFormatter, aEllipsis, aEvent)
+  {
+    aEvent.preventDefault();
+
+    if (!aFormatter) {
+      aFormatter = function(s) s;
+    }
+
+    let longString = this.webConsoleClient.longString(aActor);
+    longString.substring(longString.initial.length, longString.length,
+      function WCF__onSubstring(aResponse) {
+        if (aResponse.error) {
+          Cu.reportError("WCF__longStringClick substring failure: " +
+                         aResponse.error);
+          return;
+        }
+
+        let node = aEllipsis.previousSibling;
+        node.textContent = aFormatter(longString.initial + aResponse.substring);
+        aEllipsis.parentNode.removeChild(aEllipsis);
+
+        if (aMessage.category == CATEGORY_WEBDEV ||
+            aMessage.category == CATEGORY_OUTPUT) {
+          aMessage.clipboardText = aMessage.textContent;
+        }
+      });
+  },
+
+  /**
    * Creates the XUL label that displays the textual location of an incoming
    * message.
    *
    * @param string aSourceURL
    *        The URL of the source file responsible for the error.
    * @param number aSourceLine [optional]
    *        The line number on which the error occurred. If zero or omitted,
    *        there is no line number associated with this message.
@@ -2827,16 +2904,51 @@ JSTerm.prototype = {
     }
     else {
       node = this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
                               aAfterNode, aResponse.timestamp);
     }
 
     if (result && typeof result == "object" && result.actor) {
       node._objectActors = [result.actor];
+      if (typeof result.displayString == "object" &&
+          result.displayString.type == "longString") {
+        node._objectActors.push(result.displayString.actor);
+      }
+
+      // Add an ellipsis to expand the short string if the object is not
+      // inspectable.
+      let longString = null;
+      let formatter = null;
+      if (result.type == "longString") {
+        longString = result;
+        if (!helperHasRawOutput) {
+          formatter = WebConsoleUtils.formatResultString.bind(WebConsoleUtils);
+        }
+      }
+      else if (!inspectable && !errorMessage &&
+               typeof result.displayString == "object" &&
+               result.displayString.type == "longString") {
+        longString = result.displayString;
+      }
+
+      if (longString) {
+        let body = node.querySelector(".webconsole-msg-body");
+        let ellipsis = this.hud.document.createElement("description");
+        ellipsis.classList.add("hud-clickable");
+        ellipsis.classList.add("longStringEllipsis");
+        ellipsis.textContent = l10n.getStr("longStringEllipsis");
+
+        this.hud._addMessageLinkCallback(ellipsis,
+          this.hud._longStringClick.bind(this.hud, node, longString, formatter));
+
+        body.appendChild(ellipsis);
+
+        node.clipboardText += " " + ellipsis.textContent;
+      }
     }
   },
 
   /**
    * Execute a string. Execution happens asynchronously in the content process.
    *
    * @param string [aExecuteString]
    *        The string you want to execute. If this is not provided, the current
@@ -2844,17 +2956,18 @@ JSTerm.prototype = {
    * @param function [aCallback]
    *        Optional function to invoke when the result is displayed.
    */
   execute: function JST_execute(aExecuteString, aCallback)
   {
     // attempt to execute the content of the inputNode
     aExecuteString = aExecuteString || this.inputNode.value;
     if (!aExecuteString) {
-      this.writeOutput("no value to execute", CATEGORY_OUTPUT, SEVERITY_LOG);
+      this.writeOutput(l10n.getStr("executeEmptyInput"), CATEGORY_OUTPUT,
+                       SEVERITY_LOG);
       return;
     }
 
     let node = this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG);
     let onResult = this._executeResultCallback.bind(this, node, aCallback);
 
     this.webConsoleClient.evaluateJS(aExecuteString, onResult);
 
@@ -3479,58 +3592,16 @@ JSTerm.prototype = {
   updateCompleteNode: function JSTF_updateCompleteNode(aSuffix)
   {
     // completion prefix = input, with non-control chars replaced by spaces
     let prefix = aSuffix ? this.inputNode.value.replace(/[\S]/g, " ") : "";
     this.completeNode.value = prefix + aSuffix;
   },
 
   /**
-   * Clear the object cache from the Web Console content instance.
-   *
-   * @param string aCacheId
-   *        The cache ID you want to clear. Multiple objects are cached into one
-   *        group which is given an ID.
-   */
-  clearObjectCache: function JST_clearObjectCache(aCacheId)
-  {
-    if (this.hud) {
-      this.hud.owner.sendMessageToContent("JSTerm:ClearObjectCache",
-                                          { cacheId: aCacheId });
-    }
-  },
-
-  /**
-   * The remote object provider allows you to retrieve a given object from
-   * a specific cache and have your callback invoked when the desired object is
-   * received from the Web Console content instance.
-   *
-   * @param string aCacheId
-   *        Retrieve the desired object from this cache ID.
-   * @param string aObjectId
-   *        The ID of the object you want.
-   * @param string aResultCacheId
-   *        The ID of the cache where you want any object references to be
-   *        stored into.
-   * @param function aCallback
-   *        The function you want invoked when the desired object is retrieved.
-   */
-  remoteObjectProvider:
-  function JST_remoteObjectProvider(aCacheId, aObjectId, aResultCacheId,
-                                    aCallback) {
-    let message = {
-      cacheId: aCacheId,
-      objectId: aObjectId,
-      resultCacheId: aResultCacheId,
-    };
-
-    this.hud.owner.sendMessageToContent("JSTerm:GetEvalObject", message, aCallback);
-  },
-
-  /**
    * The JSTerm InspectObject remote message handler. This allows the remote
    * process to open the Property Panel for a given object.
    *
    * @param object aRequest
    *        The request message from the content process. This message includes
    *        the user input string that was evaluated to inspect an object and
    *        the result object which is to be inspected.
    */
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -58,8 +58,21 @@
   -  checkbox that toggles visibility of hidden (non-enumerable) variables and
   -  properties in stack views. -->
 <!ENTITY debuggerUI.showNonEnums        "Show hidden properties">
 <!ENTITY debuggerUI.showNonEnums.key    "P">
 
 <!-- LOCALIZATION NOTE (debuggerUI.searchPanelTitle): This is the text that
   -  appears in the filter panel popup as a description. -->
 <!ENTITY debuggerUI.searchPanelTitle    "Operators">
+
+<!-- LOCALIZATION NOTE (debuggerUI.condBreakPanelTitle): This is the text that
+  -  appears in the conditional breakpoint panel popup as a description. -->
+<!ENTITY debuggerUI.condBreakPanelTitle "This breakpoint will stop execution only if the following expression is true">
+
+<!-- LOCALIZATION NOTE (debuggerUI.seMenuBreak): This is the text that
+  -  appears in the source editor context menu for adding a breakpoint. -->
+<!ENTITY debuggerUI.seMenuBreak         "Add breakpoint">
+
+<!-- LOCALIZATION NOTE (debuggerUI.seMenuCondBreak): This is the text that
+  -  appears in the source editor context menu for adding a conditional
+  -  breakpoint. -->
+<!ENTITY debuggerUI.seMenuCondBreak     "Add conditional breakpoint">
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -129,16 +129,17 @@ searchPanelToken=Find in this file (%S)
 searchPanelLine=Jump to line (%S)
 
 # LOCALIZATION NOTE (searchPanelVariable): This is the text that appears in the
 # filter panel popup for the variables search operation.
 searchPanelVariable=Filter variables (%S)
 
 # LOCALIZATION NOTE (breakpointMenuItem): The text for all the elements that
 # are displayed in the breakpoints menu item popup.
+breakpointMenuItem.setConditional=Configure conditional breakpoint
 breakpointMenuItem.enableSelf=Enable breakpoint
 breakpointMenuItem.disableSelf=Disable breakpoint
 breakpointMenuItem.deleteSelf=Remove breakpoint
 breakpointMenuItem.enableOthers=Enable others
 breakpointMenuItem.disableOthers=Disable others
 breakpointMenuItem.deleteOthers=Remove others
 breakpointMenuItem.enableAll=Enable all breakpoints
 breakpointMenuItem.disableAll=Disable all breakpoints
--- a/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/webConsole.dtd
@@ -5,26 +5,27 @@
 <!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
   - keep it in English, or another language commonly spoken among web developers.
   - You want to make that choice consistent across the developer tools.
   - A good criteria is the language in which you'd find the best
   - documentation on web development on the web. -->
 
 <!ENTITY window.title "Web Console">
 
-<!ENTITY networkPanel.requestURL                  "Request URL">
-<!ENTITY networkPanel.requestMethod               "Request Method">
-<!ENTITY networkPanel.statusCode                  "Status Code">
+<!ENTITY networkPanel.requestURLColon             "Request URL:">
+<!ENTITY networkPanel.requestMethodColon          "Request Method:">
+<!ENTITY networkPanel.statusCodeColon             "Status Code:">
 
 <!ENTITY networkPanel.requestHeaders              "Request Headers">
 <!ENTITY networkPanel.requestCookie               "Sent Cookie">
 <!ENTITY networkPanel.requestBody                 "Request Body">
 <!ENTITY networkPanel.requestFormData             "Sent Form Data">
 
 <!ENTITY networkPanel.responseHeaders             "Response Headers">
+<!ENTITY networkPanel.responseCookie              "Received Cookie">
 <!ENTITY networkPanel.responseBody                "Response Body">
 <!ENTITY networkPanel.responseBodyCached          "Cached Data">
 <!ENTITY networkPanel.responseBodyUnknownType     "Unknown Content Type">
 <!ENTITY networkPanel.responseNoBody              "No Response Body">
 <!ENTITY networkPanel.responseImage               "Received Image">
 <!ENTITY networkPanel.responseImageCached         "Cached Image">
 
 <!-- LOCALIZATION NOTE (saveBodies.label): You can see this string in the Web
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -160,8 +160,27 @@ remoteWebConsoleSelectTabTitle=Tab list 
 
 # LOCALIZATION NOTE (remoteWebConsoleSelectTabMessage): The message displayed on the
 # Web Console prompt asking the user to pick a tab to attach to.
 remoteWebConsoleSelectTabMessage=Select one of the tabs you want to attach to, or select the global console.
 
 # LOCALIZATION NOTE (listTabs.globalConsoleActor): The string displayed for the
 # global console in the tabs selection.
 listTabs.globalConsoleActor=*Global Console*
+
+# LOCALIZATION NOTE (longStringEllipsis): The string displayed after a long
+# string. This string is clickable such that the rest of the string is retrieved
+# from the server.
+longStringEllipsis=[…]
+
+# LOCALIZATION NOTE (executeEmptyInput): This is displayed when the user tries
+# to execute code, but the input is empty.
+executeEmptyInput=No value to execute.
+
+# LOCALIZATION NOTE (NetworkPanel.fetchRemainingResponseContentLink): This is
+# displayed in the network panel when the response body is only partially
+# available.
+NetworkPanel.fetchRemainingResponseContentLink=Fetch the remaining %1$S bytes
+
+# LOCALIZATION NOTE (NetworkPanel.fetchRemainingRequestContentLink): This is
+# displayed in the network panel when the request body is only partially
+# available.
+NetworkPanel.fetchRemainingRequestContentLink=Fetch the request body (%1$S bytes)
--- a/browser/themes/gnomestripe/devtools/debugger.css
+++ b/browser/themes/gnomestripe/devtools/debugger.css
@@ -131,51 +131,62 @@
 }
 
 /**
  * Stack frames view
  */
 
 #stackframes {
   background-color: white;
-  min-height: 30px;
+  min-height: 10px;
 }
 
 .dbg-stackframe {
   -moz-padding-start: 4px;
   -moz-padding-end: 4px;
 }
 
 .dbg-stackframe-name {
-  -moz-padding-end: 4px;
   font-weight: 600;
 }
 
 /**
  * Breakpoints view
  */
 
 #breakpoints {
   background-color: white;
-  min-height: 30px;
+  min-height: 10px;
 }
 
 #breakpoints > vbox:not(:empty) {
-  min-height: 30px;
+  min-height: 10px;
   max-height: 200px;
 }
 
+.dbg-breakpoint:not(:last-child) {
+  border-bottom: 1px solid #eee;
+}
+
 .dbg-breakpoint-info {
   font-weight: 600;
 }
 
 .dbg-breakpoint-text {
   font: 8pt monospace;
 }
 
+#conditional-breakpoint-panel .description {
+  margin: -6px 0 8px 0;
+}
+
+#conditional-breakpoint-panel textbox {
+  margin: 0 0 -2px 0;
+}
+
 /**
  * Variables view
  */
 
 #variables {
   background-color: white;
   min-width: 50px;
 }
@@ -204,32 +215,24 @@
 
 /**
  * Variable element
  */
 
 .variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
-  transition: background 1s ease-in-out;
+  border-bottom: 1px solid #eee;
   background: #fff;
-}
-
-.variable:not(:last-child) {
-  border-bottom: 1px dotted #ddd;
-  border-radius: 8px;
-}
-
-.variable:last-child {
-  margin-bottom: 2px;
+  transition: background 1s ease-in-out;
 }
 
 .variable[changed] {
+  background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
-  background: rgba(255, 255, 0, 0.65);
 }
 
 .variable > .title > .name {
   color: #048;
   font-weight: 600;
 }
 
 .variable > .title > .value {
@@ -240,24 +243,23 @@
   -moz-margin-start: 10px;
 }
 
 /**
  * Property element
  */
 
 .property {
+  background: #fff;
   transition: background 1s ease-in-out;
-  background: #fff;
-  border-radius: 8px;
 }
 
 .property[changed] {
+  background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
-  background: rgba(255, 255, 0, 0.65);
 }
 
 .property > .title > .name {
   color: #881090;
 }
 
 .property > .title > .value {
   -moz-padding-start: 6px;
@@ -322,16 +324,24 @@
 .variable[non-configurable] > tooltip > label[value="configurable"],
 .property[non-configurable] > tooltip > label[value="configurable"],
 .variable[non-writable] > tooltip > label[value="writable"],
 .property[non-writable] > tooltip > label[value="writable"] {
   text-decoration: line-through;
 }
 
 /**
+ * Variables and properties editing
+ */
+
+#variables .element-input {
+  -moz-margin-start: 5px !important;
+}
+
+/**
  * Variables and properties searching
  */
 
 #variables .devtools-searchinput {
   min-height: 24px;
 }
 
 .variable[non-match],
@@ -382,40 +392,16 @@
   -moz-appearance: treetwistyopen;
 }
 
 .arrow[invisible] {
   visibility: hidden;
 }
 
 /**
- * Animations
- */
-
-#variables .details[open][animated],
-#globalsearch .dbg-results-container[open][animated] {
-  animation-duration: 0.25s;
-  animation-name: showblock;
-}
-
-@keyframes showblock {
-  from {
-    opacity: 0;
-    transform-origin: top;
-    transform: scaleY(0);
-  }
-
-  to {
-    opacity: 1;
-    transform-origin: top;
-    transform: scaleY(1);
-  }
-}
-
-/**
  * Toolbar Controls
  */
 
 #toggle-panes {
   background: none;
   box-shadow: none;
   border: none;
   list-style-image: url("chrome://browser/skin/devtools/debugger-expand.png");
--- a/browser/themes/gnomestripe/devtools/webconsole_networkpanel.css
+++ b/browser/themes/gnomestripe/devtools/webconsole_networkpanel.css
@@ -79,8 +79,21 @@ div.group,
 img#responseImageNode {
   box-shadow: rgba(0,0,0,0.2) 0px 3px 3.5px;
   max-width: 100%;
 }
 
 #responseImageNodeDiv {
   padding: 5px;
 }
+
+#responseBodyFetchLink, #requestBodyFetchLink {
+  padding: 5px;
+  margin: 0;
+  cursor: pointer;
+  font-weight: bold;
+  font-size: 1.1em;
+  text-decoration: underline;
+}
+
+.longStringEllipsis {
+  margin-left: 0.6em;
+}
--- a/browser/themes/pinstripe/devtools/debugger.css
+++ b/browser/themes/pinstripe/devtools/debugger.css
@@ -133,51 +133,62 @@
 }
 
 /**
  * Stack frames view
  */
 
 #stackframes {
   background-color: white;
-  min-height: 30px;
+  min-height: 10px;
 }
 
 .dbg-stackframe {
   -moz-padding-start: 4px;
   -moz-padding-end: 4px;
 }
 
 .dbg-stackframe-name {
-  -moz-padding-end: 4px;
   font-weight: 600;
 }
 
 /**
  * Breakpoints view
  */
 
 #breakpoints {
   background-color: white;
-  min-height: 30px;
+  min-height: 10px;
 }
 
 #breakpoints > vbox:not(:empty) {
-  min-height: 30px;
+  min-height: 10px;
   max-height: 200px;
 }
 
+.dbg-breakpoint:not(:last-child) {
+  border-bottom: 1px solid #eee;
+}
+
 .dbg-breakpoint-info {
   font-weight: 600;
 }
 
 .dbg-breakpoint-text {
   font: 8pt monospace;
 }
 
+#conditional-breakpoint-panel .description {
+  margin: -6px 0 8px 0;
+}
+
+#conditional-breakpoint-panel textbox {
+  margin: 0 0 -2px 0;
+}
+
 /**
  * Variables view
  */
 
 #variables {
   background-color: white;
   min-width: 50px;
 }
@@ -206,32 +217,24 @@
 
 /**
  * Variable element
  */
 
 .variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
-  transition: background 1s ease-in-out;
+  border-bottom: 1px solid #eee;
   background: #fff;
-}
-
-.variable:not(:last-child) {
-  border-bottom: 1px dotted #ddd;
-  border-radius: 8px;
-}
-
-.variable:last-child {
-  margin-bottom: 2px;
+  transition: background 1s ease-in-out;
 }
 
 .variable[changed] {
+  background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
-  background: rgba(255, 255, 0, 0.65);
 }
 
 .variable > .title > .name {
   color: #048;
   font-weight: 600;
 }
 
 .variable > .title > .value {
@@ -242,24 +245,23 @@
   -moz-margin-start: 10px;
 }
 
 /**
  * Property element
  */
 
 .property {
+  background: #fff;
   transition: background 1s ease-in-out;
-  background: #fff;
-  border-radius: 8px;
 }
 
 .property[changed] {
+  background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
-  background: rgba(255, 255, 0, 0.65);
 }
 
 .property > .title > .name {
   color: #881090;
 }
 
 .property > .title > .value {
   -moz-padding-start: 6px;
@@ -324,16 +326,24 @@
 .variable[non-configurable] > tooltip > label[value="configurable"],
 .property[non-configurable] > tooltip > label[value="configurable"],
 .variable[non-writable] > tooltip > label[value="writable"],
 .property[non-writable] > tooltip > label[value="writable"] {
   text-decoration: line-through;
 }
 
 /**
+ * Variables and properties editing
+ */
+
+#variables .element-input {
+  -moz-margin-start: 5px !important;
+}
+
+/**
  * Variables and properties searching
  */
 
 #variables .devtools-searchinput {
   min-height: 24px;
 }
 
 .variable[non-match],
@@ -382,40 +392,16 @@
   -moz-appearance: treetwistyopen;
 }
 
 .arrow[invisible] {
   visibility: hidden;
 }
 
 /**
- * Animations
- */
-
-#variables .details[open][animated],
-#globalsearch .dbg-results-container[open][animated] {
-  animation-duration: 0.25s;
-  animation-name: showblock;
-}
-
-@keyframes showblock {
-  from {
-    opacity: 0;
-    transform-origin: top;
-    transform: scaleY(0);
-  }
-
-  to {
-    opacity: 1;
-    transform-origin: top;
-    transform: scaleY(1);
-  }
-}
-
-/**
  * Toolbar Controls
  */
 
 #toggle-panes {
   background: none;
   box-shadow: none;
   border: none;
   list-style-image: url("chrome://browser/skin/devtools/debugger-expand.png");
--- a/browser/themes/pinstripe/devtools/webconsole_networkpanel.css
+++ b/browser/themes/pinstripe/devtools/webconsole_networkpanel.css
@@ -80,8 +80,21 @@ div.group,
 img#responseImageNode {
   box-shadow: rgba(0,0,0,0.2) 0px 3px 3.5px;
   max-width: 100%;
 }
 
 #responseImageNodeDiv {
   padding: 5px;
 }
+
+#responseBodyFetchLink, #requestBodyFetchLink {
+  padding: 5px;
+  margin: 0;
+  cursor: pointer;
+  font-weight: bold;
+  font-size: 1.1em;
+  text-decoration: underline;
+}
+
+.longStringEllipsis {
+  margin-left: 0.6em;
+}
--- a/browser/themes/winstripe/devtools/debugger.css
+++ b/browser/themes/winstripe/devtools/debugger.css
@@ -139,51 +139,62 @@
 }
 
 /**
  * Stack frames view
  */
 
 #stackframes {
   background-color: white;
-  min-height: 30px;
+  min-height: 10px;
 }
 
 .dbg-stackframe {
   -moz-padding-start: 4px;
   -moz-padding-end: 4px;
 }
 
 .dbg-stackframe-name {
-  -moz-padding-end: 4px;
   font-weight: 600;
 }
 
 /**
  * Breakpoints view
  */
 
 #breakpoints {
   background-color: white;
-  min-height: 30px;
+  min-height: 10px;
 }
 
 #breakpoints > vbox:not(:empty) {
-  min-height: 30px;
+  min-height: 10px;
   max-height: 200px;
 }
 
+.dbg-breakpoint:not(:last-child) {
+  border-bottom: 1px solid #eee;
+}
+
 .dbg-breakpoint-info {
   font-weight: 600;
 }
 
 .dbg-breakpoint-text {
   font: 8pt monospace;
 }
 
+#conditional-breakpoint-panel .description {
+  margin: -6px 0 8px 0;
+}
+
+#conditional-breakpoint-panel textbox {
+  margin: 0 0 -2px 0;
+}
+
 /**
  * Variables view
  */
 
 #variables {
   background-color: white;
   min-width: 50px;
 }
@@ -212,32 +223,24 @@
 
 /**
  * Variable element
  */
 
 .variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
-  transition: background 1s ease-in-out;
+  border-bottom: 1px solid #eee;
   background: #fff;
-}
-
-.variable:not(:last-child) {
-  border-bottom: 1px dotted #ddd;
-  border-radius: 8px;
-}
-
-.variable:last-child {
-  margin-bottom: 2px;
+  transition: background 1s ease-in-out;
 }
 
 .variable[changed] {
+  background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
-  background: rgba(255, 255, 0, 0.65);
 }
 
 .variable > .title > .name {
   color: #048;
   font-weight: 600;
 }
 
 .variable > .title > .value {
@@ -248,24 +251,23 @@
   -moz-margin-start: 10px;
 }
 
 /**
  * Property element
  */
 
 .property {
+  background: #fff;
   transition: background 1s ease-in-out;
-  background: #fff;
-  border-radius: 8px;
 }
 
 .property[changed] {
+  background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
-  background: rgba(255, 255, 0, 0.65);
 }
 
 .property > .title > .name {
   color: #881090;
 }
 
 .property > .title > .value {
   -moz-padding-start: 6px;
@@ -330,16 +332,24 @@
 .variable[non-configurable] > tooltip > label[value="configurable"],
 .property[non-configurable] > tooltip > label[value="configurable"],
 .variable[non-writable] > tooltip > label[value="writable"],
 .property[non-writable] > tooltip > label[value="writable"] {
   text-decoration: line-through;
 }
 
 /**
+ * Variables and properties editing
+ */
+
+#variables .element-input {
+  -moz-margin-start: 5px !important;
+}
+
+/**
  * Variables and properties searching
  */
 
 #variables .devtools-searchinput {
   min-height: 24px;
 }
 
 .variable[non-match],
@@ -393,40 +403,16 @@
   background-image: url("chrome://global/skin/tree/twisty-open.png");
 }
 
 .arrow[invisible] {
   visibility: hidden;
 }
 
 /**
- * Animations
- */
-
-#variables .details[open][animated],
-#globalsearch .dbg-results-container[open][animated] {
-  animation-duration: 0.25s;
-  animation-name: showblock;
-}
-
-@keyframes showblock {
-  from {
-    opacity: 0;
-    transform-origin: top;
-    transform: scaleY(0);
-  }
-
-  to {
-    opacity: 1;
-    transform-origin: top;
-    transform: scaleY(1);
-  }
-}
-
-/**
  * Toolbar Controls
  */
 
 #toggle-panes {
   background: none;
   box-shadow: none;
   border: none;
   list-style-image: url("chrome://browser/skin/devtools/debugger-expand.png");
--- a/browser/themes/winstripe/devtools/webconsole_networkpanel.css
+++ b/browser/themes/winstripe/devtools/webconsole_networkpanel.css
@@ -80,8 +80,21 @@ div.group,
 img#responseImageNode {
   box-shadow: rgba(0,0,0,0.2) 0px 3px 3.5px;
   max-width: 100%;
 }
 
 #responseImageNodeDiv {
   padding: 5px;
 }
+
+#responseBodyFetchLink, #requestBodyFetchLink {
+  padding: 5px;
+  margin: 0;
+  cursor: pointer;
+  font-weight: bold;
+  font-size: 1.1em;
+  text-decoration: underline;
+}
+
+.longStringEllipsis {
+  margin-left: 0.6em;
+}
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -7,17 +7,18 @@
 "use strict";
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 this.EXPORTED_SYMBOLS = ["DebuggerTransport",
                          "DebuggerClient",
-                         "debuggerSocketConnect"];
+                         "debuggerSocketConnect",
+                         "LongStringClient"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
                                    "@mozilla.org/network/socket-transport-service;1",
                                    "nsISocketTransportService");
@@ -1179,16 +1180,17 @@ GripClient.prototype = {
 function LongStringClient(aClient, aGrip) {
   this._grip = aGrip;
   this._client = aClient;
 }
 
 LongStringClient.prototype = {
   get actor() { return this._grip.actor; },
   get length() { return this._grip.length; },
+  get initial() { return this._grip.initial; },
 
   valid: true,
 
   /**
    * Get the substring of this LongString from aStart to aEnd.
    *
    * @param aStart Number
    *        The starting index.
--- a/toolkit/devtools/debugger/server/dbg-script-actors.js
+++ b/toolkit/devtools/debugger/server/dbg-script-actors.js
@@ -27,17 +27,37 @@
 function ThreadActor(aHooks, aGlobal)
 {
   this._state = "detached";
   this._frameActors = [];
   this._environmentActors = [];
   this._hooks = {};
   this._hooks = aHooks;
   this.global = aGlobal;
+
+  /**
+   * A script cache that maps script URLs to arrays of different Debugger.Script
+   * instances that have the same URL. For example, when an inline <script> tag
+   * in a web page contains a function declaration, the JS engine creates two
+   * Debugger.Script objects, one for the function and one for the script tag
+   * as a whole. The two objects will usually have different startLine and/or
+   * lineCount properties. For the edge case where two scripts are contained in
+   * the same line we need column support.
+   *
+   * The sparse array that is mapped to each URL serves as an additional mapping
+   * from startLine numbers to Debugger.Script objects, facilitating retrieval
+   * of the scripts that contain a particular line number. For example, if a
+   * cache holds two scripts with the URL http://foo.com/ starting at lines 4
+   * and 10, then the corresponding cache will be:
+   * this._scripts: {
+   *   'http://foo.com/': [,,,,[Debugger.Script],,,,,,[Debugger.Script]]
+   * }
+   */
   this._scripts = {};
+
   this.findGlobals = this.globalManager.findGlobals.bind(this);
   this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
 }
 
 /**
  * The breakpoint store must be shared across instances of ThreadActor so that
  * page reloads don't blow away all of our breakpoints.
  */
@@ -484,23 +504,25 @@ ThreadActor.prototype = {
    * take that into account.
    *
    * @param object aLocation
    *        The location of the breakpoint as specified in the protocol.
    */
   _setBreakpoint: function TA__setBreakpoint(aLocation) {
     // Fetch the list of scripts in that url.
     let scripts = this._scripts[aLocation.url];
-    // Fetch the specified script in that list.
+    // Fetch the outermost script in that list.
     let script = null;
-    for (let i = aLocation.line; i >= 0; i--) {
+    for (let i = 0; i <= aLocation.line; i++) {
       // Stop when the first script that contains this location is found.
       if (scripts[i]) {
         // If that first script does not contain the line specified, it's no
-        // good.
+        // good. Note that |i === scripts[i].startLine| in this case, so the
+        // following check makes sure we are not considering a script that does
+        // not include |aLocation.line|.
         if (i + scripts[i].lineCount < aLocation.line) {
           continue;
         }
         script = scripts[i];
         break;
       }
     }
 
@@ -520,81 +542,96 @@ ThreadActor.prototype = {
         scriptBreakpoints[location.line].actor = bpActor;
       }
     }
 
     if (!script) {
       return { error: "noScript", actor: bpActor.actorID };
     }
 
-    script = this._getInnermostContainer(script, aLocation.line);
-    bpActor.addScript(script, this);
+    let inner, codeFound = false;
+    // We need to set the breakpoint in every script that has bytecode in the
+    // specified line.
+    for (let s of this._getContainers(script, aLocation.line)) {
+      // The first result of the iteration is the innermost script.
+      if (!inner) {
+        inner = s;
+      }
 
-    let offsets = script.getLineOffsets(aLocation.line);
-    let codeFound = false;
-    for (let i = 0; i < offsets.length; i++) {
-      script.setBreakpoint(offsets[i], bpActor);
-      codeFound = true;
+      let offsets = s.getLineOffsets(aLocation.line);
+      if (offsets.length) {
+        bpActor.addScript(s, this);
+        for (let i = 0; i < offsets.length; i++) {
+          s.setBreakpoint(offsets[i], bpActor);
+          codeFound = true;
+        }
+      }
     }
 
     let actualLocation;
-    if (offsets.length == 0) {
-      // No code at that line in any script, skipping forward.
-      let lines = script.getAllOffsets();
+    if (!codeFound) {
+      // No code at that line in any script, skipping forward in the innermost
+      // script.
+      let lines = inner.getAllOffsets();
       let oldLine = aLocation.line;
       for (let line = oldLine; line < lines.length; ++line) {
         if (lines[line]) {
           for (let i = 0; i < lines[line].length; i++) {
-            script.setBreakpoint(lines[line][i], bpActor);
+            inner.setBreakpoint(lines[line][i], bpActor);
             codeFound = true;
           }
           actualLocation = {
             url: aLocation.url,
             line: line,
             column: aLocation.column
           };
           bpActor.location = actualLocation;
           // Update the cache as well.
           scriptBreakpoints[line] = scriptBreakpoints[oldLine];
           scriptBreakpoints[line].line = line;
           delete scriptBreakpoints[oldLine];
           break;
         }
       }
     }
+
     if (!codeFound) {
       return  { error: "noCodeAtLineColumn", actor: bpActor.actorID };
     }
 
     return { actor: bpActor.actorID, actualLocation: actualLocation };
   },
 
   /**
-   * Get the innermost script that contains this line, by looking through child
-   * scripts of the supplied script.
+   * A recursive generator function for iterating over the scripts that contain
+   * the specified line, by looking through child scripts of the supplied
+   * script. As an example, an inline <script> tag has the top-level functions
+   * declared in it as its children.
    *
    * @param aScript Debugger.Script
    *        The source script.
    * @param aLine number
    *        The line number.
    */
-  _getInnermostContainer: function TA__getInnermostContainer(aScript, aLine) {
+  _getContainers: function TA__getContainers(aScript, aLine) {
     let children = aScript.getChildScripts();
     if (children.length > 0) {
       for (let i = 0; i < children.length; i++) {
         let child = children[i];
-        // Stop when the first script that contains this location is found.
+        // Iterate over the children that contain this location.
         if (child.startLine <= aLine &&
             child.startLine + child.lineCount > aLine) {
-          return this._getInnermostContainer(child, aLine);
+          for (let j of this._getContainers(child, aLine)) {
+            yield j;
+          }
         }
       }
     }
-    // Location not found in children, this is the innermost containing script.
-    return aScript;
+    // Include this script in the iteration, too.
+    yield aScript;
   },
 
   /**
    * Handle a protocol request to return the list of loaded scripts.
    */
   onScripts: function TA_onScripts(aRequest) {
     // Get the script list from the debugger.
     for (let s of this.dbg.findScripts()) {
@@ -1713,22 +1750,36 @@ LongStringActor.prototype = {
    * @param aRequest object
    *        The protocol request object.
    */
   onSubstring: function LSA_onSubString(aRequest) {
     return {
       "from": this.actorID,
       "substring": this.string.substring(aRequest.start, aRequest.end)
     };
-  }
+  },
 
+  /**
+   * Handle a request to release this LongStringActor instance.
+   */
+  onRelease: function LSA_onRelease() {
+    // TODO: also check if registeredPool === threadActor.threadLifetimePool
+    // when the web console moves aray from manually releasing pause-scoped
+    // actors.
+    if (this.registeredPool.longStringActors) {
+      delete this.registeredPool.longStringActors[this.actorID];
+    }
+    this.registeredPool.removeActor(this);
+    return {};
+  },
 };
 
 LongStringActor.prototype.requestTypes = {
-  "substring": LongStringActor.prototype.onSubstring
+  "substring": LongStringActor.prototype.onSubstring,
+  "release": LongStringActor.prototype.onRelease
 };
 
 
 /**
  * Creates an actor for the specified stack frame.
  *
  * @param aFrame Debugger.Frame
  *        The debuggee frame.
@@ -2137,16 +2188,52 @@ function getFunctionName(aFunction) {
     } else {
       // Otherwise use SpiderMonkey's inferred name.
       name = aFunction.displayName;
     }
   }
   return name;
 }
 
+/**
+ * Override the toString method in order to get more meaningful script output
+ * for debugging the debugger.
+ */
+Debugger.Script.prototype.toString = function() {
+  let output = "";
+  if (this.url) {
+    output += this.url;
+  }
+  if (typeof this.startLine != "undefined") {
+    output += ":" + this.startLine;
+    if (this.lineCount && this.lineCount > 1) {
+      output += "-" + (this.startLine + this.lineCount - 1);
+    }
+  }
+  if (this.strictMode) {
+    output += ":strict";
+  }
+  return output;
+};
+
+/**
+ * Helper property for quickly getting to the line number a stack frame is
+ * currently paused at.
+ */
+Object.defineProperty(Debugger.Frame.prototype, "line", {
+  configurable: true,
+  get: function() {
+    if (this.script) {
+      return this.script.getOffsetLine(this.offset);
+    } else {
+      return null;
+    }
+  }
+});
+
 
 /**
  * Creates an actor for handling chrome debugging. ChromeDebuggerActor is a
  * thin wrapper over ThreadActor, slightly changing some of its behavior.
  *
  * @param aHooks object
  *        An object with preNest and postNest methods for calling when entering
  *        and exiting a nested event loop and also addToParentPool and
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -674,16 +674,39 @@ DebuggerServerConnection.prototype = {
     dumpn("Cleaning up connection.");
 
     this._actorPool.cleanup();
     this._actorPool = null;
     this._extraPools.map(function(p) { p.cleanup(); });
     this._extraPools = null;
 
     DebuggerServer._connectionClosed(this);
+  },
+
+  /*
+   * Debugging helper for inspecting the state of the actor pools.
+   */
+  _dumpPools: function DSC_dumpPools() {
+    dumpn("/-------------------- dumping pools:");
+    if (this._actorPool) {
+      dumpn("--------------------- actorPool actors: " +
+            uneval(Object.keys(this._actorPool._actors)));
+    }
+    for each (let pool in this._extraPools)
+      dumpn("--------------------- extraPool actors: " +
+            uneval(Object.keys(pool._actors)));
+  },
+
+  /*
+   * Debugging helper for inspecting the state of an actor pool.
+   */
+  _dumpPool: function DSC_dumpPools(aPool) {
+    dumpn("/-------------------- dumping pool:");
+    dumpn("--------------------- actorPool actors: " +
+          uneval(Object.keys(aPool._actors)));
   }
 };
 
 /**
  * Localization convenience methods.
  */
 let L10N = {
 
--- a/toolkit/devtools/debugger/tests/unit/test_breakpoint-09.js
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-09.js
@@ -25,26 +25,23 @@ function run_test()
   do_test_pending();
 }
 
 function test_remove_breakpoint()
 {
   let done = false;
   gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
     let path = getFilePath('test_breakpoint-09.js');
-    let location = { url: path, line: gDebuggee.line0 + 1};
+    let location = { url: path, line: gDebuggee.line0 + 2};
     gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
-      // Check that the breakpoint has properly skipped forward one line.
-      do_check_eq(aResponse.actualLocation.url, location.url);
-      do_check_eq(aResponse.actualLocation.line, location.line + 1);
       gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
         // Check the return value.
         do_check_eq(aPacket.type, "paused");
         do_check_eq(aPacket.frame.where.url, path);
-        do_check_eq(aPacket.frame.where.line, location.line + 1);
+        do_check_eq(aPacket.frame.where.line, location.line);
         do_check_eq(aPacket.why.type, "breakpoint");
         do_check_eq(aPacket.why.actors[0], bpClient.actor);
         // Check that the breakpoint worked.
         do_check_eq(gDebuggee.a, undefined);
 
         // Remove the breakpoint.
         bpClient.remove(function (aResponse) {
           done = true;
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpoint-11.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that setting a breakpoint in a line with bytecodes in multiple
+ * scripts, sets the breakpoint in all of them (bug 793214).
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(function () {
+    attachTestGlobalClientAndResume(gClient,
+                                    "test-stack",
+                                    function (aResponse, aThreadClient) {
+      gThreadClient = aThreadClient;
+      test_child_breakpoint();
+    });
+  });
+  do_test_pending();
+}
+
+function test_child_breakpoint()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+    let path = getFilePath('test_breakpoint-11.js');
+    let location = { url: path, line: gDebuggee.line0 + 2};
+    gThreadClient.setBreakpoint(location, function (aResponse, bpClient) {
+      // actualLocation is not returned when breakpoints don't skip forward.
+      do_check_eq(aResponse.actualLocation, undefined);
+      gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+        // Check the return value.
+        do_check_eq(aPacket.type, "paused");
+        do_check_eq(aPacket.why.type, "breakpoint");
+        do_check_eq(aPacket.why.actors[0], bpClient.actor);
+        // Check that the breakpoint worked.
+        do_check_eq(gDebuggee.a, undefined);
+
+        gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+          // Check the return value.
+          do_check_eq(aPacket.type, "paused");
+          do_check_eq(aPacket.why.type, "breakpoint");
+          do_check_eq(aPacket.why.actors[0], bpClient.actor);
+          // Check that the breakpoint worked.
+          do_check_eq(gDebuggee.a.b, 1);
+          do_check_eq(gDebuggee.res, undefined);
+
+          // Remove the breakpoint.
+          bpClient.remove(function (aResponse) {
+            gThreadClient.resume(function () {
+              finishClient(gClient);
+            });
+          });
+        });
+
+        // Continue until the breakpoint is hit again.
+        gThreadClient.resume();
+
+      });
+      // Continue until the breakpoint is hit.
+      gThreadClient.resume();
+
+    });
+
+  });
+
+
+  gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+                 "debugger;\n" +                      // line0 + 1
+                 "var a = { b: 1, f: function() { return 2; } };\n" + // line0+2
+                 "var res = a.f();\n");               // line0 + 3
+}
--- a/toolkit/devtools/debugger/tests/unit/test_breakpointstore.js
+++ b/toolkit/devtools/debugger/tests/unit/test_breakpointstore.js
@@ -2,16 +2,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Note: Removing this test will regress bug 754251. See comment above
 // ThreadActor._breakpointStore.
 
 function run_test()
 {
+  Cu.import("resource://gre/modules/jsdebugger.jsm");
+  addDebuggerToGlobal(this);
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
     .getService(Components.interfaces.mozIJSSubScriptLoader);
   loader.loadSubScript("chrome://global/content/devtools/dbg-script-actors.js");
 
   let instance1 = new ThreadActor();
   let instance2 = new ThreadActor();
   do_check_eq(instance1._breakpointStore, ThreadActor._breakpointStore);
   do_check_eq(instance2._breakpointStore, ThreadActor._breakpointStore);
--- a/toolkit/devtools/debugger/tests/unit/test_longstringactor.js
+++ b/toolkit/devtools/debugger/tests/unit/test_longstringactor.js
@@ -1,14 +1,16 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; js-indent-level: 2; -*- */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test()
 {
+  Cu.import("resource://gre/modules/jsdebugger.jsm");
+  addDebuggerToGlobal(this);
   let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
     .getService(Components.interfaces.mozIJSSubScriptLoader);
   loader.loadSubScript("chrome://global/content/devtools/dbg-script-actors.js");
 
   test_LSA_disconnect();
   test_LSA_grip();
   test_LSA_onSubstring();
 }
--- a/toolkit/devtools/debugger/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/debugger/tests/unit/xpcshell.ini
@@ -39,16 +39,17 @@ tail =
 [test_breakpoint-03.js]
 [test_breakpoint-04.js]
 [test_breakpoint-05.js]
 [test_breakpoint-06.js]
 [test_breakpoint-07.js]
 [test_breakpoint-08.js]
 [test_breakpoint-09.js]
 [test_breakpoint-10.js]
+[test_breakpoint-11.js]
 [test_listscripts-01.js]
 [test_objectgrips-01.js]
 [test_objectgrips-02.js]
 [test_objectgrips-03.js]
 [test_objectgrips-04.js]
 [test_interrupt.js]
 [test_stepping-01.js]
 [test_stepping-02.js]
--- a/toolkit/devtools/webconsole/NetworkHelper.jsm
+++ b/toolkit/devtools/webconsole/NetworkHelper.jsm
@@ -414,10 +414,41 @@ this.NetworkHelper =
     "audio/x-midi": "media",
     "music/crescendo": "media",
     "audio/wav": "media",
     "audio/x-wav": "media",
     "text/json": "json",
     "application/x-json": "json",
     "application/json-rpc": "json",
     "application/x-web-app-manifest+json": "json",
-  }
+  },
+
+  /**
+   * Check if the given MIME type is a text-only MIME type.
+   *
+   * @param string aMimeType
+   * @return boolean
+   */
+  isTextMimeType: function NH_isTextMimeType(aMimeType)
+  {
+    if (aMimeType.indexOf("text/") == 0) {
+      return true;
+    }
+
+    if (/^application\/[a-z-]+\+xml$/.test(aMimeType)) {
+      return true;
+    }
+
+    switch (NetworkHelper.mimeCategoryMap[aMimeType]) {
+      case "txt":
+      case "js":
+      case "json":
+      case "css":
+      case "html":
+      case "svg":
+      case "xml":
+        return true;
+
+      default:
+        return false;
+    }
+  },
 }
--- a/toolkit/devtools/webconsole/WebConsoleClient.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleClient.jsm
@@ -5,34 +5,42 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LongStringClient",
+                                  "resource://gre/modules/devtools/dbg-client.jsm");
+
 this.EXPORTED_SYMBOLS = ["WebConsoleClient"];
 
 /**
  * A WebConsoleClient is used as a front end for the WebConsoleActor that is
  * created on the server, hiding implementation details.
  *
  * @param object aDebuggerClient
  *        The DebuggerClient instance we live for.
  * @param string aActor
  *        The WebConsoleActor ID.
  */
 this.WebConsoleClient = function WebConsoleClient(aDebuggerClient, aActor)
 {
   this._actor = aActor;
   this._client = aDebuggerClient;
+  this._longStrings = {};
 }
 
 WebConsoleClient.prototype = {
+  _longStrings: null,
+
   /**
    * Retrieve the cached messages from the server.
    *
    * @see this.CACHED_MESSAGES
    * @param array aTypes
    *        The array of message types you want from the server. See
    *        this.CACHED_MESSAGES for known types.
    * @param function aOnResponse
@@ -290,20 +298,40 @@ WebConsoleClient.prototype = {
       to: this._actor,
       type: "stopListeners",
       listeners: aListeners,
     };
     this._client.request(packet, aOnResponse);
   },
 
   /**
+   * Return an instance of LongStringClient for the given long string grip.
+   *
+   * @param object aGrip
+   *        The long string grip returned by the protocol.
+   * @return object
+   *         The LongStringClient for the given long string grip.
+   */
+  longString: function WCC_longString(aGrip)
+  {
+    if (aGrip.actor in this._longStrings) {
+      return this._longStrings[aGrip.actor];
+    }
+
+    let client = new LongStringClient(this._client, aGrip);
+    this._longStrings[aGrip.actor] = client;
+    return client;
+  },
+
+  /**
    * Close the WebConsoleClient. This stops all the listeners on the server and
    * detaches from the console actor.
    *
    * @param function aOnResponse
    *        Function to invoke when the server response is received.
    */
   close: function WCC_close(aOnResponse)
   {
     this.stopListeners(null, aOnResponse);
+    this._longStrings = null;
     this._client = null;
   },
 };
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -584,19 +584,20 @@ this.WebConsoleUtils = {
    * @return mixed
    *         The value grip.
    */
   createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
   {
     let type = typeof(aValue);
     switch (type) {
       case "boolean":
-      case "string":
       case "number":
         return aValue;
+      case "string":
+          return aObjectWrapper(aValue);
       case "object":
       case "function":
         if (aValue) {
           return aObjectWrapper(aValue);
         }
       default:
         if (aValue === null) {
           return { type: "null" };
@@ -681,23 +682,38 @@ this.WebConsoleUtils = {
    *        not.
    * @return string
    *         The object grip converted to a string.
    */
   objectActorGripToString: function WCU_objectActorGripToString(aGrip, aFormatString)
   {
     // Primitives like strings and numbers are not sent as objects.
     // But null and undefined are sent as objects with the type property
-    // telling which type of value we have.
+    // telling which type of value we have. We also have long strings which are
+    // sent using the LongStringActor.
+
     let type = typeof(aGrip);
+    if (type == "string" ||
+        (aGrip && type == "object" && aGrip.type == "longString")) {
+      let str = type == "string" ? aGrip : aGrip.initial;
+      if (aFormatString) {
+        return this.formatResultString(str);
+      }
+      return str;
+    }
+
     if (aGrip && type == "object") {
+      if (aGrip.displayString && typeof aGrip.displayString == "object" &&
+          aGrip.displayString.type == "longString") {
+        return aGrip.displayString.initial;
+      }
       return aGrip.displayString || aGrip.className || aGrip.type || type;
     }
-    return type == "string" && aFormatString ?
-           this.formatResultString(aGrip) : aGrip + "";
+
+    return aGrip + "";
   },
 
   /**
    * Helper function to deduce the name of the provided function.
    *
    * @param funtion aFunction
    *        The function whose name will be returned.
    * @return string
@@ -808,24 +824,33 @@ this.WebConsoleUtils = {
     if (typeof val == "string") {
       return this.formatResultString(val);
     }
 
     if (typeof val != "object" || !val) {
       return val;
     }
 
+    if (val.type == "longString") {
+      return this.formatResultString(val.initial) + "\u2026";
+    }
+
     if (val.type == "function" && val.functionName) {
       return "function " + val.functionName + "(" +
              val.functionArguments.join(", ") + ")";
     }
     if (val.type == "object" && val.className) {
       return val.className;
     }
 
+    if (val.displayString && typeof val.displayString == "object" &&
+        val.displayString.type == "longString") {
+      return val.displayString.initial;
+    }
+
     return val.displayString || val.type;
   },
 };
 
 //////////////////////////////////////////////////////////////////////////
 // Localization
 //////////////////////////////////////////////////////////////////////////
 
@@ -1910,23 +1935,28 @@ NetworkResponseListener.prototype = {
    */
   _onComplete: function NRL__onComplete(aData)
   {
     let response = {
       mimeType: "",
       text: aData || "",
     };
 
-    // TODO: Bug 787981 - use LongStringActor for strings that are too long.
+    response.size = response.text.length;
 
     try {
       response.mimeType = this.request.contentType;
     }
     catch (ex) { }
 
+    if (!response.mimeType || !NetworkHelper.isTextMimeType(response.mimeType)) {
+      response.encoding = "base64";
+      response.text = btoa(response.text);
+    }
+
     if (response.mimeType && this.request.contentCharset) {
       response.mimeType += "; charset=" + this.request.contentCharset;
     }
 
     this.receivedData = "";
 
     this.httpActivity.owner.
       addResponseContent(response, this.httpActivity.discardResponseBody);
--- a/toolkit/devtools/webconsole/dbg-webconsole-actors.js
+++ b/toolkit/devtools/webconsole/dbg-webconsole-actors.js
@@ -62,51 +62,38 @@ function WebConsoleActor(aConnection, aP
            aParentActor.browser instanceof Ci.nsIDOMElement) {
     this._window = aParentActor.browser.contentWindow;
   }
   else {
     this._window = Services.wm.getMostRecentWindow("navigator:browser");
     this._isGlobalActor = true;
   }
 
-  this._objectActorsPool = new ActorPool(this.conn);
-  this.conn.addActorPool(this._objectActorsPool);
-
-  this._networkEventActorsPool = new ActorPool(this.conn);
-  this.conn.addActorPool(this._networkEventActorsPool);
+  this._actorPool = new ActorPool(this.conn);
+  this.conn.addActorPool(this._actorPool);
 
   this._prefs = {};
 }
 
 WebConsoleActor.prototype =
 {
   /**
    * Tells if this Web Console actor is a global actor or not.
    * @private
    * @type boolean
    */
   _isGlobalActor: false,
 
   /**
-   * Actor pool for all of the object actors for objects we send to the client.
+   * Actor pool for all of the actors we send to the client.
    * @private
    * @type object
    * @see ActorPool
-   * @see WebConsoleObjectActor
-   * @see this.objectGrip()
    */
-  _objectActorsPool: null,
-
-  /**
-   * Actor pool for all of the network event actors.
-   * @private
-   * @type object
-   * @see NetworkEventActor
-   */
-  _networkEventActorsPool: null,
+  _actorPool: null,
 
   /**
    * Web Console-related preferences.
    * @private
    * @type object
    */
   _prefs: null,
 
@@ -207,20 +194,18 @@ WebConsoleActor.prototype =
     if (this.networkMonitor) {
       this.networkMonitor.destroy();
       this.networkMonitor = null;
     }
     if (this.consoleProgressListener) {
       this.consoleProgressListener.destroy();
       this.consoleProgressListener = null;
     }
-    this.conn.removeActorPool(this._objectActorsPool);
-    this.conn.removeActorPool(this._networkEventActorsPool);
-    this._objectActorsPool = null;
-    this._networkEventActorsPool = null;
+    this.conn.removeActorPool(this.actorPool);
+    this._actorPool = null;
     this._sandboxLocation = this.sandbox = null;
     this.conn = this._window = null;
   },
 
   /**
    * Create a grip for the given value. If the value is an object,
    * a WebConsoleObjectActor will be created.
    *
@@ -238,55 +223,68 @@ WebConsoleActor.prototype =
    *
    * @param object aObject
    *        The object you want.
    * @param object
    *        The object grip.
    */
   createObjectActor: function WCA_createObjectActor(aObject)
   {
+    if (typeof aObject == "string") {
+      return this.createStringGrip(aObject);
+    }
+
     // We need to unwrap the object, otherwise we cannot access the properties
     // and methods added by the content scripts.
     let obj = WebConsoleUtils.unwrap(aObject);
     let actor = new WebConsoleObjectActor(obj, this);
-    this._objectActorsPool.addActor(actor);
+    this._actorPool.addActor(actor);
     return actor.grip();
   },
 
   /**
+   * Create a grip for the given string. If the given string is a long string,
+   * then a LongStringActor grip will be used.
+   *
+   * @param string aString
+   *        The string you want to create the grip for.
+   * @return string|object
+   *         The same string, as is, or a LongStringActor object that wraps the
+   *         given string.
+   */
+  createStringGrip: function WCA_createStringGrip(aString)
+  {
+    if (aString.length >= DebuggerServer.LONG_STRING_LENGTH) {
+      let actor = new LongStringActor(aString, this);
+      this._actorPool.addActor(actor);
+      return actor.grip();
+    }
+    return aString;
+  },
+
+  /**
    * Get an object actor by its ID.
    *
    * @param string aActorID
    * @return object
    */
-  getObjectActorByID: function WCA_getObjectActorByID(aActorID)
+  getActorByID: function WCA_getActorByID(aActorID)
   {
-    return this._objectActorsPool.get(aActorID);
+    return this._actorPool.get(aActorID);
   },
 
   /**
-   * Release an object grip for the given object actor.
+   * Release an actor.
    *
    * @param object aActor
-   *        The WebConsoleObjectActor instance you want to release.
+   *        The actor instance you want to release.
    */
-  releaseObject: function WCA_releaseObject(aActor)
+  releaseActor: function WCA_releaseActor(aActor)
   {
-    this._objectActorsPool.removeActor(aActor.actorID);
-  },
-
-  /**
-   * Release a network event actor.
-   *
-   * @param object aActor
-   *        The NetworkEventActor instance you want to release.
-   */
-  releaseNetworkEvent: function WCA_releaseNetworkEvent(aActor)
-  {
-    this._networkEventActorsPool.removeActor(aActor.actorID);
+    this._actorPool.removeActor(aActor.actorID);
   },
 
   //////////////////
   // Request handlers for known packet types.
   //////////////////
 
   /**
    * Handler for the "startListeners" request.
@@ -702,17 +700,17 @@ WebConsoleActor.prototype =
    *        The initial network request event information.
    * @return object
    *         A new NetworkEventActor is returned. This is used for tracking the
    *         network request and response.
    */
   onNetworkEvent: function WCA_onNetworkEvent(aEvent)
   {
     let actor = new NetworkEventActor(aEvent, this);
-    this._networkEventActorsPool.addActor(actor);
+    this._actorPool.addActor(actor);
 
     let packet = {
       from: this.actorID,
       type: "networkEvent",
       eventActor: actor.grip(),
     };
 
     this.conn.send(packet);
@@ -803,17 +801,17 @@ WebConsoleActor.prototype =
           function(aObj) {
             return this.createValueGrip(aObj);
           }, this);
 
         if (result.level == "dir") {
           result.objectProperties = [];
           let first = result.arguments[0];
           if (typeof first == "object" && first && first.inspectable) {
-            let actor = this.getObjectActorByID(first.actor);
+            let actor = this.getActorByID(first.actor);
             result.objectProperties = actor.onInspectProperties().properties;
           }
         }
         break;
     }
 
     return result;
   },
@@ -864,38 +862,38 @@ WebConsoleObjectActor.prototype =
 
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   grip: function WCOA_grip()
   {
     let grip = WebConsoleUtils.getObjectGrip(this.obj);
     grip.actor = this.actorID;
+    grip.displayString = this.parent.createStringGrip(grip.displayString);
     return grip;
   },
 
   /**
    * Releases this actor from the pool.
    */
   release: function WCOA_release()
   {
-    this.parent.releaseObject(this);
+    this.parent.releaseActor(this);
     this.parent = this.obj = null;
   },
 
   /**
    * Handle a protocol request to inspect the properties of the object.
    *
    * @return object
    *         Message to send to the client. This holds the 'properties' property
    *         - an array with a descriptor for each property in the object.
    */
   onInspectProperties: function WCOA_onInspectProperties()
   {
-    // TODO: Bug 787981 - use LongStringActor for strings that are too long.
     let createObjectActor = this.parent.createObjectActor.bind(this.parent);
     let props = WebConsoleUtils.inspectObject(this.obj, createObjectActor);
     return {
       from: this.actorID,
       properties: props,
     };
   },
 
@@ -944,26 +942,28 @@ function NetworkEventActor(aNetworkEvent
 
   this._response = {
     headers: [],
     cookies: [],
     content: {},
   };
 
   this._timings = {};
+  this._longStringActors = new Set();
 
   this._discardRequestBody = aNetworkEvent.discardRequestBody;
   this._discardResponseBody = aNetworkEvent.discardResponseBody;
 }
 
 NetworkEventActor.prototype =
 {
   _request: null,
   _response: null,
   _timings: null,
+  _longStringActors: null,
 
   actorPrefix: "netEvent",
 
   /**
    * Returns a grip for this actor for returning in a protocol message.
    */
   grip: function NEA_grip()
   {
@@ -975,17 +975,24 @@ NetworkEventActor.prototype =
     };
   },
 
   /**
    * Releases this actor from the pool.
    */
   release: function NEA_release()
   {
-    this.parent.releaseNetworkEvent(this);
+    for (let grip of this._longStringActors) {
+      let actor = this.parent.getActorByID(grip.actor);
+      if (actor) {
+        this.parent.releaseActor(actor);
+      }
+    }
+    this._longStringActors = new Set();
+    this.parent.releaseActor(this);
   },
 
   /**
    * Handle a protocol request to release a grip.
    */
   onRelease: function NEA_onRelease()
   {
     this.release();
@@ -1103,16 +1110,17 @@ NetworkEventActor.prototype =
    * Add network request headers.
    *
    * @param array aHeaders
    *        The request headers array.
    */
   addRequestHeaders: function NEA_addRequestHeaders(aHeaders)
   {
     this._request.headers = aHeaders;
+    this._prepareHeaders(aHeaders);
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "requestHeaders",
       headers: aHeaders.length,
       headersSize: this._request.headersSize,
     };
@@ -1124,16 +1132,17 @@ NetworkEventActor.prototype =
    * Add network request cookies.
    *
    * @param array aCookies
    *        The request cookies array.
    */
   addRequestCookies: function NEA_addRequestCookies(aCookies)
   {
     this._request.cookies = aCookies;
+    this._prepareHeaders(aCookies);
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "requestCookies",
       cookies: aCookies.length,
     };
 
@@ -1144,16 +1153,20 @@ NetworkEventActor.prototype =
    * Add network request POST data.
    *
    * @param object aPostData
    *        The request POST data.
    */
   addRequestPostData: function NEA_addRequestPostData(aPostData)
   {
     this._request.postData = aPostData;
+    aPostData.text = this.parent.createStringGrip(aPostData.text);
+    if (typeof aPostData.text == "object") {
+      this._longStringActors.add(aPostData.text);
+    }
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "requestPostData",
       dataSize: aPostData.text.length,
       discardRequestBody: this._discardRequestBody,
     };
@@ -1189,16 +1202,17 @@ NetworkEventActor.prototype =
    * Add network response headers.
    *
    * @param array aHeaders
    *        The response headers array.
    */
   addResponseHeaders: function NEA_addResponseHeaders(aHeaders)
   {
     this._response.headers = aHeaders;
+    this._prepareHeaders(aHeaders);
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "responseHeaders",
       headers: aHeaders.length,
       headersSize: this._response.headersSize,
     };
@@ -1210,16 +1224,17 @@ NetworkEventActor.prototype =
    * Add network response cookies.
    *
    * @param array aCookies
    *        The response cookies array.
    */
   addResponseCookies: function NEA_addResponseCookies(aCookies)
   {
     this._response.cookies = aCookies;
+    this._prepareHeaders(aCookies);
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "responseCookies",
       cookies: aCookies.length,
     };
 
@@ -1233,16 +1248,20 @@ NetworkEventActor.prototype =
    *        The response content.
    * @param boolean aDiscardedResponseBody
    *        Tells if the response content was recorded or not.
    */
   addResponseContent:
   function NEA_addResponseContent(aContent, aDiscardedResponseBody)
   {
     this._response.content = aContent;
+    aContent.text = this.parent.createStringGrip(aContent.text);
+    if (typeof aContent.text == "object") {
+      this._longStringActors.add(aContent.text);
+    }
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "responseContent",
       mimeType: aContent.mimeType,
       contentSize: aContent.text.length,
       discardResponseBody: aDiscardedResponseBody,
@@ -1268,16 +1287,33 @@ NetworkEventActor.prototype =
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "eventTimings",
       totalTime: aTotal,
     };
 
     this.conn.send(packet);
   },
+
+  /**
+   * Prepare the headers array to be sent to the client by using the
+   * LongStringActor for the header values, when needed.
+   *
+   * @private
+   * @param array aHeaders
+   */
+  _prepareHeaders: function NEA__prepareHeaders(aHeaders)
+  {
+    for (let header of aHeaders) {
+      header.value = this.parent.createStringGrip(header.value);
+      if (typeof header.value == "object") {
+        this._longStringActors.add(header.value);
+      }
+    }
+  },
 };
 
 NetworkEventActor.prototype.requestTypes =
 {
   "release": NetworkEventActor.prototype.onRelease,
   "getRequestHeaders": NetworkEventActor.prototype.onGetRequestHeaders,
   "getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies,
   "getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
--- a/toolkit/devtools/webconsole/test/Makefile.in
+++ b/toolkit/devtools/webconsole/test/Makefile.in
@@ -14,15 +14,17 @@ MOCHITEST_CHROME_FILES = \
     test_basics.html \
     test_cached_messages.html \
     test_page_errors.html \
     test_consoleapi.html \
     test_jsterm.html \
     test_object_actor.html \
     test_network_get.html \
     test_network_post.html \
+    test_network_longstring.html \
     test_file_uri.html \
     network_requests_iframe.html \
     data.json \
+    data.json^headers^ \
     common.js \
     $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/devtools/webconsole/test/common.js
+++ b/toolkit/devtools/webconsole/test/common.js
@@ -95,45 +95,78 @@ function checkConsoleAPICall(aCall, aExp
 }
 
 function checkObject(aObject, aExpected)
 {
   for (let name of Object.keys(aExpected))
   {
     let expected = aExpected[name];
     let value = aObject[name];
-    if (value === undefined) {
-      ok(false, "'" + name + "' is undefined");
-    }
-    else if (typeof expected == "string" ||
-        typeof expected == "number" ||
-        typeof expected == "boolean") {
-      is(value, expected, "property '" + name + "'");
-    }
-    else if (expected instanceof RegExp) {
-      ok(expected.test(value), name + ": " + expected);
-    }
-    else if (Array.isArray(expected)) {
-      info("checking array for property '" + name + "'");
-      checkObject(value, expected);
-    }
-    else if (typeof expected == "object") {
-      info("checking object for property '" + name + "'");
-      checkObject(value, expected);
-    }
+    checkValue(name, value, expected);
+  }
+}
+
+function checkValue(aName, aValue, aExpected)
+{
+  if (aValue === undefined) {
+    ok(false, "'" + aName + "' is undefined");
+  }
+  else if (typeof aExpected == "string" || typeof aExpected == "number" ||
+           typeof aExpected == "boolean") {
+    is(aValue, aExpected, "property '" + aName + "'");
+  }
+  else if (aExpected instanceof RegExp) {
+    ok(aExpected.test(aValue), aName + ": " + aExpected + " matched " + aValue);
+  }
+  else if (Array.isArray(aExpected)) {
+    info("checking array for property '" + aName + "'");
+    checkObject(aValue, aExpected);
+  }
+  else if (typeof aExpected == "object") {
+    info("checking object for property '" + aName + "'");
+    checkObject(aValue, aExpected);
   }
 }
 
 function checkHeadersOrCookies(aArray, aExpected)
 {
+  let foundHeaders = {};
+
   for (let elem of aArray) {
     if (!(elem.name in aExpected)) {
       continue;
     }
-    let expected = aExpected[elem.name];
-    if (expected instanceof RegExp) {
-      ok(expected.test(elem.value), elem.name + ": " + expected);
-    }
-    else {
-      is(elem.value, expected, elem.name);
+    foundHeaders[elem.name] = true;
+    info("checking value of header " + elem.name);
+    checkValue(elem.name, elem.value, aExpected[elem.name]);
+  }
+
+  for (let header in aExpected) {
+    if (!(header in foundHeaders)) {
+      ok(false, header + " was not found");
     }
   }
 }
+
+var gTestState = {};
+
+function runTests(aTests, aEndCallback)
+{
+  function driver()
+  {
+    let lastResult, sendToNext;
+    for (let i = 0; i < aTests.length; i++) {
+      gTestState.index = i;
+      let fn = aTests[i];
+      info("will run test #" + i + ": " + fn.name);
+      lastResult = fn(sendToNext, lastResult);
+      sendToNext = yield lastResult;
+    }
+    yield aEndCallback(sendToNext, lastResult);
+  }
+  gTestState.driver = driver();
+  return gTestState.driver.next();
+}
+
+function nextTest(aMessage)
+{
+  return gTestState.driver.send(aMessage);
+}
--- a/toolkit/devtools/webconsole/test/data.json
+++ b/toolkit/devtools/webconsole/test/data.json
@@ -1,1 +1,3 @@
-{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }
\ No newline at end of file
+{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ],
+  veryLong: "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar"
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/data.json^headers^
@@ -0,0 +1,3 @@
+Content-Type: application/json
+x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum.
+x-very-short: hello world
--- a/toolkit/devtools/webconsole/test/network_requests_iframe.html
+++ b/toolkit/devtools/webconsole/test/network_requests_iframe.html
@@ -19,17 +19,18 @@
         xmlhttp.send(aRequestBody);
       }
 
       function testXhrGet(aCallback) {
         makeXhr('get', 'data.json', null, aCallback);
       }
 
       function testXhrPost(aCallback) {
-        makeXhr('post', 'data.json', "Hello world!", aCallback);
+        var body = "Hello world! " + (new Array(50)).join("foobaz barr");
+        makeXhr('post', 'data.json', body, aCallback);
       }
 
       document.cookie = "foobar=fooval";
       document.cookie = "omgfoo=bug768096";
     // --></script>
   </head>
   <body>
     <h1>Web Console HTTP Logging Testpage</h1>
--- a/toolkit/devtools/webconsole/test/test_consoleapi.html
+++ b/toolkit/devtools/webconsole/test/test_consoleapi.html
@@ -13,22 +13,25 @@
 
 <script class="testbody" type="text/javascript;version=1.8">
 SimpleTest.waitForExplicitFinish();
 
 let expectedConsoleCalls = [];
 
 function doConsoleCalls(aState)
 {
+  let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
+
   console.log("foobarBaz-log", undefined);
   console.info("foobarBaz-info", null);
   console.warn("foobarBaz-warn", document.body);
   console.debug(null);
   console.trace();
   console.dir(document, window);
+  console.log("foo", longString);
 
   expectedConsoleCalls = [
     {
       level: "log",
       filename: /test_consoleapi/,
       functionName: "doConsoleCalls",
       timeStamp: /^\d+$/,
       arguments: ["foobarBaz-log", { type: "undefined" }],
@@ -93,16 +96,32 @@ function doConsoleCalls(aState)
           value: 2,
         },
         {
           name: "CDATA_SECTION_NODE",
           value: 4,
         }, // ...
       ],
     },
+    {
+      level: "log",
+      filename: /test_consoleapi/,
+      functionName: "doConsoleCalls",
+      timeStamp: /^\d+$/,
+      arguments: [
+        "foo",
+        {
+          type: "longString",
+          initial: longString.substring(0,
+            DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+          length: longString.length,
+          actor: /[a-z]/,
+        },
+      ],
+    },
   ];
 }
 
 function startTest()
 {
   removeEventListener("load", startTest);
 
   attachConsole(["ConsoleAPI"], onAttach);
--- a/toolkit/devtools/webconsole/test/test_jsterm.html
+++ b/toolkit/devtools/webconsole/test/test_jsterm.html
@@ -9,137 +9,211 @@
      - http://creativecommons.org/publicdomain/zero/1.0/ -->
 </head>
 <body>
 <p>Test for JavaScript terminal functionality</p>
 
 <script class="testbody" type="text/javascript;version=1.8">
 SimpleTest.waitForExplicitFinish();
 
+let gState;
+
 function startTest()
 {
   removeEventListener("load", startTest);
 
   attachConsole(["PageError"], onAttach, true);
 }
 
 function onAttach(aState, aResponse)
 {
   top.foobarObject = Object.create(null);
   top.foobarObject.foo = 1;
   top.foobarObject.foobar = 2;
   top.foobarObject.foobaz = 3;
   top.foobarObject.omg = 4;
   top.foobarObject.omgfoo = 5;
+  top.foobarObject.strfoo = "foobarz";
+  top.foobarObject.omgstr = "foobarz" +
+    (new Array(DebuggerServer.LONG_STRING_LENGTH * 2)).join("abb");
 
-  info("test autocomplete for 'window.foo'");
-  onAutocomplete1 = onAutocomplete1.bind(null, aState);
-  aState.client.autocomplete("window.foo", 0, onAutocomplete1);
+  gState = aState;
+
+  let tests = [doAutocomplete1, doAutocomplete2, doSimpleEval, doWindowEval,
+    doEvalWithException, doEvalWithHelper, doEvalString, doEvalLongString];
+  runTests(tests, testEnd);
 }
 
-function onAutocomplete1(aState, aResponse)
+function doAutocomplete1()
+{
+  info("test autocomplete for 'window.foo'");
+  gState.client.autocomplete("window.foo", 0, onAutocomplete1);
+}
+
+function onAutocomplete1(aResponse)
 {
   let matches = aResponse.matches;
 
   is(aResponse.matchProp, "foo", "matchProp");
   is(matches.length, 1, "matches.length");
   is(matches[0], "foobarObject", "matches[0]");
 
-  info("test autocomplete for 'window.foobarObject.'");
-
-  onAutocomplete2 = onAutocomplete2.bind(null, aState);
-  aState.client.autocomplete("window.foobarObject.", 0, onAutocomplete2);
+  nextTest();
 }
 
-function onAutocomplete2(aState, aResponse)
+function doAutocomplete2()
+{
+  info("test autocomplete for 'window.foobarObject.'");
+  gState.client.autocomplete("window.foobarObject.", 0, onAutocomplete2);
+}
+
+function onAutocomplete2(aResponse)
 {
   let matches = aResponse.matches;
 
   ok(!aResponse.matchProp, "matchProp");
-  is(matches.length, 5, "matches.length");
-  checkObject(matches, ["foo", "foobar", "foobaz", "omg", "omgfoo"]);
+  is(matches.length, 7, "matches.length");
+  checkObject(matches,
+    ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
 
-  info("test eval '2+2'");
-
-  onEval1 = onEval1.bind(null, aState);
-  aState.client.evaluateJS("2+2", onEval1);
+  nextTest();
 }
 
-function onEval1(aState, aResponse)
+function doSimpleEval()
+{
+  info("test eval '2+2'");
+  gState.client.evaluateJS("2+2", onSimpleEval);
+}
+
+function onSimpleEval(aResponse)
 {
   checkObject(aResponse, {
-    from: aState.actor,
+    from: gState.actor,
     input: "2+2",
     result: 4,
   });
 
   ok(!aResponse.error, "no js error");
   ok(!aResponse.helperResult, "no helper result");
 
-  info("test eval 'window'");
-  onEval2 = onEval2.bind(null, aState);
-  aState.client.evaluateJS("window", onEval2);
+  nextTest();
 }
 
-function onEval2(aState, aResponse)
+function doWindowEval()
+{
+  info("test eval 'window'");
+  gState.client.evaluateJS("window", onWindowEval);
+}
+
+function onWindowEval(aResponse)
 {
   checkObject(aResponse, {
-    from: aState.actor,
+    from: gState.actor,
     input: "window",
     result: {
       type: "object",
       className: "Window",
       actor: /[a-z]/,
     },
   });
 
   ok(!aResponse.error, "no js error");
   ok(!aResponse.helperResult, "no helper result");
 
-  info("test eval with exception");
-
-  onEvalWithException = onEvalWithException.bind(null, aState);
-  aState.client.evaluateJS("window.doTheImpossible()",
-                           onEvalWithException);
+  nextTest();
 }
 
-function onEvalWithException(aState, aResponse)
+function doEvalWithException()
+{
+  info("test eval with exception");
+  gState.client.evaluateJS("window.doTheImpossible()", onEvalWithException);
+}
+
+function onEvalWithException(aResponse)
 {
   checkObject(aResponse, {
-    from: aState.actor,
+    from: gState.actor,
     input: "window.doTheImpossible()",
     result: {
       type: "undefined",
     },
     errorMessage: /doTheImpossible/,
   });
 
   ok(aResponse.error, "js error object");
   ok(!aResponse.helperResult, "no helper result");
 
-  info("test eval with helper");
-
-  onEvalWithHelper = onEvalWithHelper.bind(null, aState);
-  aState.client.evaluateJS("clear()", onEvalWithHelper);
+  nextTest();
 }
 
-function onEvalWithHelper(aState, aResponse)
+function doEvalWithHelper()
+{
+  info("test eval with helper");
+  gState.client.evaluateJS("clear()", onEvalWithHelper);
+}
+
+function onEvalWithHelper(aResponse)
 {
   checkObject(aResponse, {
-    from: aState.actor,
+    from: gState.actor,
     input: "clear()",
     result: {
       type: "undefined",
     },
     helperResult: { type: "clearOutput" },
   });
 
   ok(!aResponse.error, "no js error");
 
-  closeDebugger(aState, function() {
+  nextTest();
+}
+
+function doEvalString()
+{
+  gState.client.evaluateJS("window.foobarObject.strfoo", onEvalString);
+}
+
+function onEvalString(aResponse)
+{
+  checkObject(aResponse, {
+    from: gState.actor,
+    input: "window.foobarObject.strfoo",
+    result: "foobarz",
+  });
+
+  nextTest();
+}
+
+function doEvalLongString()
+{
+  gState.client.evaluateJS("window.foobarObject.omgstr", onEvalLongString);
+}
+
+function onEvalLongString(aResponse)
+{
+  let str = top.foobarObject.omgstr;
+  let initial = str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+  checkObject(aResponse, {
+    from: gState.actor,
+    input: "window.foobarObject.omgstr",
+    result: {
+      type: "longString",
+      initial: initial,
+      length: str.length,
+    },
+  });
+
+  nextTest();
+}
+
+function testEnd()
+{
+  closeDebugger(gState, function() {
+    gState = null;
     SimpleTest.finish();
   });
 }
 
 addEventListener("load", startTest);
 </script>
 </body>
 </html>
--- a/toolkit/devtools/webconsole/test/test_network_get.html
+++ b/toolkit/devtools/webconsole/test/test_network_get.html
@@ -93,17 +93,17 @@ function onNetworkEventUpdate(aState, aT
       break;
     case "responseCookies":
       expectedPacket = {
         cookies: 0,
       };
       break;
     case "responseContent":
       expectedPacket = {
-        mimeType: /^application\/(json|octet-stream)$/,
+        mimeType: "application/json",
         contentSize: 0,
         discardResponseBody: true,
       };
       break;
     case "eventTimings":
       expectedPacket = {
         totalTime: /^\d+$/,
       };
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/webconsole/test/test_network_longstring.html
@@ -0,0 +1,299 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+  <meta charset="utf8">
+  <title>Test that the network actor uses the LongStringActor</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript;version=1.8" src="common.js"></script>
+  <!-- Any copyright is dedicated to the Public Domain.
+     - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test that the network actor uses the LongStringActor</p>
+
+<iframe src="http://example.com/chrome/toolkit/devtools/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+  removeEventListener("load", startTest);
+
+  attachConsole(["NetworkActivity"], onAttach, true);
+}
+
+function onAttach(aState, aResponse)
+{
+  info("enable network request and response body logging");
+
+  window.ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
+  window.ORIGINAL_LONG_STRING_INITIAL_LENGTH =
+    DebuggerServer.LONG_STRING_INITIAL_LENGTH;
+
+  DebuggerServer.LONG_STRING_LENGTH = 400;
+  DebuggerServer.LONG_STRING_INITIAL_LENGTH = 400;
+
+  onSetPreferences = onSetPreferences.bind(null, aState);
+  aState.client.setPreferences({
+    "NetworkMonitor.saveRequestAndResponseBodies": true,
+  }, onSetPreferences);
+}
+
+function onSetPreferences(aState, aResponse)
+{
+  is(aResponse.updated.length, 1, "updated prefs length");
+  is(aResponse.updated[0], "NetworkMonitor.saveRequestAndResponseBodies",
+     "updated prefs length");
+
+  info("test network POST request");
+
+  onNetworkEvent = onNetworkEvent.bind(null, aState);
+  aState.dbgClient.addListener("networkEvent", onNetworkEvent);
+  onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+  aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+  let iframe = document.querySelector("iframe").contentWindow;
+  iframe.wrappedJSObject.testXhrPost();
+}
+
+function onNetworkEvent(aState, aType, aPacket)
+{
+  is(aPacket.from, aState.actor, "network event actor");
+
+  info("checking the network event packet");
+
+  let netActor = aPacket.eventActor;
+
+  checkObject(netActor, {
+    actor: /[a-z]/,
+    startedDateTime: /^\d+\-\d+\-\d+T.+$/,
+    url: /data\.json/,
+    method: "POST",
+  });
+
+  aState.netActor = netActor.actor;
+
+  aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+}
+
+let updates = [];
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+  info("received networkEventUpdate " + aPacket.updateType);
+  is(aPacket.from, aState.netActor, "networkEventUpdate actor");
+
+  updates.push(aPacket.updateType);
+
+  let expectedPacket = null;
+
+  switch (aPacket.updateType) {
+    case "requestHeaders":
+    case "responseHeaders":
+      ok(aPacket.headers > 0, "headers > 0");
+      ok(aPacket.headersSize > 0, "headersSize > 0");
+      break;
+    case "requestCookies":
+      expectedPacket = {
+        cookies: 2,
+      };
+      break;
+    case "requestPostData":
+      ok(aPacket.dataSize > 0, "dataSize > 0");
+      ok(!aPacket.discardRequestBody, "discardRequestBody");
+      break;
+    case "responseStart":
+      expectedPacket = {
+        response: {
+          httpVersion: /^HTTP\/\d\.\d$/,
+          status: 200,
+          statusText: "OK",
+          headersSize: /^\d+$/,
+          discardResponseBody: false,
+        },
+      };
+      break;
+    case "responseCookies":
+      expectedPacket = {
+        cookies: 0,
+      };
+      break;
+    case "responseContent":
+      expectedPacket = {
+        mimeType: "application/json",
+        contentSize: /^\d+$/,
+        discardResponseBody: false,
+      };
+      break;
+    case "eventTimings":
+      expectedPacket = {
+        totalTime: /^\d+$/,
+      };
+      break;
+    default:
+      ok(false, "unknown network event update type: " +
+         aPacket.updateType);
+      return;
+  }
+
+  if (expectedPacket) {
+    info("checking the packet content");
+    checkObject(aPacket, expectedPacket);
+  }
+
+  if (updates.indexOf("responseContent") > -1 &&
+      updates.indexOf("eventTimings") > -1) {
+    aState.dbgClient.removeListener("networkEventUpdate",
+                                    onNetworkEvent);
+
+    onRequestHeaders = onRequestHeaders.bind(null, aState);
+    aState.client.getRequestHeaders(aState.netActor,
+                                    onRequestHeaders);
+  }
+}
+
+function onRequestHeaders(aState, aResponse)
+{
+  info("checking request headers");
+
+  ok(aResponse.headers.length > 0, "request headers > 0");
+  ok(aResponse.headersSize > 0, "request headersSize > 0");
+
+  checkHeadersOrCookies(aResponse.headers, {
+    Referer: /network_requests_iframe\.html/,
+    Cookie: /bug768096/,
+  });
+
+  onRequestCookies = onRequestCookies.bind(null, aState);
+  aState.client.getRequestCookies(aState.netActor,
+                                  onRequestCookies);
+}
+
+function onRequestCookies(aState, aResponse)
+{
+  info("checking request cookies");
+
+  is(aResponse.cookies.length, 2, "request cookies length");
+
+  checkHeadersOrCookies(aResponse.cookies, {
+    foobar: "fooval",
+    omgfoo: "bug768096",
+  });
+
+  onRequestPostData = onRequestPostData.bind(null, aState);
+  aState.client.getRequestPostData(aState.netActor,
+                                   onRequestPostData);
+}
+
+function onRequestPostData(aState, aResponse)
+{
+  info("checking request POST data");
+
+  checkObject(aResponse, {
+    postData: {
+      text: {
+        type: "longString",
+        initial: /^Hello world! foobaz barr.+foobaz barrfo$/,
+        length: 552,
+        actor: /[a-z]/,
+      },
+    },
+    postDataDiscarded: false,
+  });
+
+  is(aResponse.postData.text.initial.length,
+     DebuggerServer.LONG_STRING_INITIAL_LENGTH, "postData text initial length");
+
+  onResponseHeaders = onResponseHeaders.bind(null, aState);
+  aState.client.getResponseHeaders(aState.netActor,
+                                   onResponseHeaders);
+}
+
+function onResponseHeaders(aState, aResponse)
+{
+  info("checking response headers");
+
+  ok(aResponse.headers.length > 0, "response headers > 0");
+  ok(aResponse.headersSize > 0, "response headersSize > 0");
+
+  checkHeadersOrCookies(aResponse.headers, {
+    "Content-Type": /^application\/(json|octet-stream)$/,
+    "Content-Length": /^\d+$/,
+    "x-very-short": "hello world",
+    "x-very-long": {
+      "type": "longString",
+      "length": 521,
+      "initial": /^Lorem ipsum.+\. Donec vitae d$/,
+      "actor": /[a-z]/,
+    },
+  });
+
+  onResponseCookies = onResponseCookies.bind(null, aState);
+  aState.client.getResponseCookies(aState.netActor,
+                                  onResponseCookies);
+}
+
+function onResponseCookies(aState, aResponse)
+{
+  info("checking response cookies");
+
+  is(aResponse.cookies.length, 0, "response cookies length");
+
+  onResponseContent = onResponseContent.bind(null, aState);
+  aState.client.getResponseContent(aState.netActor,
+                                   onResponseContent);
+}
+
+function onResponseContent(aState, aResponse)
+{
+  info("checking response content");
+
+  checkObject(aResponse, {
+    content: {
+      text: {
+        type: "longString",
+        initial: /^\{ id: "test JSON data"(.|\r|\n)+ barfoo ba$/g,
+        length: 1070,
+        actor: /[a-z]/,
+      },
+    },
+    contentDiscarded: false,
+  });
+
+  is(aResponse.content.text.initial.length,
+     DebuggerServer.LONG_STRING_INITIAL_LENGTH, "content initial length");
+
+  onEventTimings = onEventTimings.bind(null, aState);
+  aState.client.getEventTimings(aState.netActor,
+                                onEventTimings);
+}
+
+function onEventTimings(aState, aResponse)
+{
+  info("checking event timings");
+
+  checkObject(aResponse, {
+    timings: {
+      blocked: /^-1|\d+$/,
+      dns: /^-1|\d+$/,
+      connect: /^-1|\d+$/,
+      send: /^-1|\d+$/,
+      wait: /^-1|\d+$/,
+      receive: /^-1|\d+$/,
+    },
+    totalTime: /^\d+$/,
+  });
+
+  closeDebugger(aState, function() {
+    DebuggerServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH;
+    DebuggerServer.LONG_STRING_INITIAL_LENGTH = ORIGINAL_LONG_STRING_INITIAL_LENGTH;
+
+    SimpleTest.finish();
+  });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
--- a/toolkit/devtools/webconsole/test/test_network_post.html
+++ b/toolkit/devtools/webconsole/test/test_network_post.html
@@ -109,17 +109,17 @@ function onNetworkEventUpdate(aState, aT
       break;
     case "responseCookies":
       expectedPacket = {
         cookies: 0,
       };
       break;
     case "responseContent":
       expectedPacket = {
-        mimeType: /^application\/(json|octet-stream)$/,
+        mimeType: "application/json",
         contentSize: /^\d+$/,
         discardResponseBody: false,
       };
       break;
     case "eventTimings":
       expectedPacket = {
         totalTime: /^\d+$/,
       };
@@ -180,21 +180,23 @@ function onRequestCookies(aState, aRespo
 }
 
 function onRequestPostData(aState, aResponse)
 {
   info("checking request POST data");
 
   checkObject(aResponse, {
     postData: {
-      text: "Hello world!",
+      text: /^Hello world! foobaz barr.+foobaz barr$/,
     },
     postDataDiscarded: false,
   });
 
+  is(aResponse.postData.text.length, 552, "postData text length");
+
   onResponseHeaders = onResponseHeaders.bind(null, aState);
   aState.client.getResponseHeaders(aState.netActor,
                                    onResponseHeaders);
 }
 
 function onResponseHeaders(aState, aResponse)
 {
   info("checking response headers");
--- a/toolkit/devtools/webconsole/test/test_object_actor.html
+++ b/toolkit/devtools/webconsole/test/test_object_actor.html
@@ -23,17 +23,20 @@ function startTest()
   attachConsole(["ConsoleAPI"], onAttach, true);
 }
 
 function onAttach(aState, aResponse)
 {
   onConsoleCall = onConsoleCall.bind(null, aState);
   aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
 
+  let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 3)).join("\u0629");
+
   window.foobarObject = Object.create(null);
+  foobarObject.tamarbuta = longString;
   foobarObject.foo = 1;
   foobarObject.foobar = "hello";
   foobarObject.foobaz = document;
   foobarObject.omg = null;
   foobarObject.testfoo = false;
   foobarObject.notInspectable = {};
   foobarObject.omgfn = function _omgfn() {
     return "myResult";
@@ -41,16 +44,22 @@ function onAttach(aState, aResponse)
   foobarObject.abArray = ["a", "b"];
 
   Object.defineProperty(foobarObject, "getterAndSetter", {
     enumerable: true,
     get: function fooGet() { return "foo"; },
     set: function fooSet() { 1+2 },
   });
 
+  foobarObject.longStringObj = {
+    toSource: function() longString,
+    toString: function() longString,
+    boom: "explode",
+  };
+
   console.log("hello", foobarObject);
 
   expectedProps = [
     {
       name: "abArray",
       value: {
         type: "object",
         className: "Array",
@@ -92,16 +101,31 @@ function onAttach(aState, aResponse)
         type: "function",
         className: "function",
         displayString: /function fooSet/,
         actor: /[a-z]/,
         inspectable: false,
       },
     },
     {
+      name: "longStringObj",
+      value: {
+        type: "object",
+        className: "Object",
+        actor: /[a-z]/,
+        inspectable: true,
+        displayString: {
+          type: "longString",
+          initial: longString.substring(0,
+            DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+          length: longString.length,
+        },
+      },
+    },
+    {
       name: "notInspectable",
       value: {
         type: "object",
         className: "Object",
         actor: /[a-z]/,
         inspectable: false,
       },
     },
@@ -115,16 +139,25 @@ function onAttach(aState, aResponse)
         type: "function",
         className: "function",
         displayString: /function _omgfn/,
         actor: /[a-z]/,
         inspectable: false,
       },
     },
     {
+      name: "tamarbuta",
+      value: {
+        type: "longString",
+        initial: longString.substring(0,
+          DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+        length: longString.length,
+      },
+    },
+    {
       name: "testfoo",
       value: false,
     },
   ];
 }
 
 function onConsoleCall(aState, aType, aPacket)
 {