merge m-c to fx-team
authorTim Taubert <ttaubert@mozilla.com>
Fri, 01 Feb 2013 10:17:01 -0500
changeset 130352 a4f8cb70cc5d1949fdcf2b1347c4bd43494a06bd
parent 130341 50cf5bbcb1803ec23cb6cb0847b5cb8101eb864f (current diff)
parent 130351 44772e261a55070796a716202738cd52bd5700b5 (diff)
child 130353 95bec0e5a600654c34f4b9f280e4293c71055885
child 130468 d6a13004613f11ef0be15aff9e9e12527cff7568
child 130599 964839e7a2d1b531337be98f747e24dfcda3a59f
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.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 fx-team
browser/devtools/debugger/test/browser_dbg_propertyview-big-data.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -75,16 +75,17 @@
   pointer-events: auto;
 }
 
 html|*.highlighter-nodeinfobar-id,
 html|*.highlighter-nodeinfobar-classes,
 html|*.highlighter-nodeinfobar-pseudo-classes,
 html|*.highlighter-nodeinfobar-tagname {
   -moz-user-select: text;
+  -moz-user-focus: normal;
   cursor: text;
 }
 
 .highlighter-nodeinfobar-arrow {
   display: none;
 }
 
 .highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -30,16 +30,19 @@ const VARIABLES_VIEW_NON_SORTABLE = [
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 Cu.import("resource:///modules/source-editor.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource:///modules/devtools/VariablesView.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this,
+  "Reflect", "resource://gre/modules/reflect.jsm");
+
 /**
  * Object defining the debugger controller components.
  */
 let DebuggerController = {
   /**
    * Initializes the debugger controller.
    */
   initialize: function DC_initialize() {
@@ -421,16 +424,19 @@ ThreadState.prototype = {
  * stack frame cache.
  */
 function StackFrames() {
   this._onPaused = this._onPaused.bind(this);
   this._onResumed = this._onResumed.bind(this);
   this._onFrames = this._onFrames.bind(this);
   this._onFramesCleared = this._onFramesCleared.bind(this);
   this._afterFramesCleared = this._afterFramesCleared.bind(this);
+  this._fetchScopeVariables = this._fetchScopeVariables.bind(this);
+  this._fetchVarProperties = this._fetchVarProperties.bind(this);
+  this._addVarExpander = this._addVarExpander.bind(this);
   this.evaluate = this.evaluate.bind(this);
 }
 
 StackFrames.prototype = {
   get activeThread() DebuggerController.activeThread,
   autoScopeExpand: false,
   currentFrame: null,
   syncedWatchExpressions: null,
@@ -679,17 +685,17 @@ StackFrames.prototype = {
     do {
       // Create a scope to contain all the inspected variables.
       let label = this._getScopeLabel(environment);
       let scope = DebuggerView.Variables.addScope(label);
 
       // Handle additions to the innermost scope.
       if (environment == frame.environment) {
         this._insertScopeFrameReferences(scope, frame);
-        this._fetchScopeVariables(scope, environment);
+        this._addScopeExpander(scope, environment);
         // Always expand the innermost scope by default.
         scope.expand();
       }
       // Lazily add nodes for every other environment scope.
       else {
         this._addScopeExpander(scope, environment);
         this.autoScopeExpand && scope.expand();
       }
@@ -705,47 +711,47 @@ StackFrames.prototype = {
    * 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);
+    aScope._sourceEnvironment = aEnv;
 
     // It's a good idea to be prepared in case of an expansion.
-    aScope.addEventListener("mouseover", callback, false);
+    aScope.addEventListener("mouseover", this._fetchScopeVariables, false);
     // Make sure that variables are always available on expansion.
-    aScope.onexpand = callback;
+    aScope.onexpand = this._fetchScopeVariables;
   },
 
   /**
    * 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);
+    aVar._sourceGrip = 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.addEventListener("mouseover", callback, false);
+      aVar.addEventListener("mouseover", this._fetchVarProperties, false);
     }
     // Make sure that properties are always available on expansion.
-    aVar.onexpand = callback;
+    aVar.onexpand = this._fetchVarProperties;
   },
 
   /**
    * Adds the watch expressions evaluation results to a scope in the view.
    *
    * @param Scope aScope
    *        The scope where the watch expressions will be placed into.
    * @param object aExp
@@ -783,50 +789,49 @@ StackFrames.prototype = {
   },
 
   /**
    * 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) {
+  _fetchScopeVariables: function SF__fetchScopeVariables(aScope) {
     // Fetch the variables only once.
     if (aScope._fetched) {
       return;
     }
     aScope._fetched = true;
+    let env = aScope._sourceEnvironment;
 
-    switch (aEnv.type) {
+    switch (env.type) {
       case "with":
       case "object":
         // Add nodes for every variable in scope.
-        this.activeThread.pauseGrip(aEnv.object).getPrototypeAndProperties(function(aResponse) {
+        this.activeThread.pauseGrip(env.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);
+        this._insertScopeArguments(env.bindings.arguments, aScope);
+        this._insertScopeVariables(env.bindings.variables, aScope);
 
         // No need to signal that variables have been fetched, since
         // the scope arguments and variables are already attached to the
         // environment bindings, so pausing the active thread is unnecessary.
         break;
       default:
-        Cu.reportError("Unknown Debugger.Environment type: " + aEnv.type);
+        Cu.reportError("Unknown Debugger.Environment type: " + env.type);
         break;
     }
   },
 
   /**
    * Add nodes for special frame references in the innermost scope.
    *
    * @param Scope aScope
@@ -894,37 +899,37 @@ StackFrames.prototype = {
   },
 
   /**
    * 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.
    */
-  _fetchVarProperties: function SF__fetchVarProperties(aVar, aGrip) {
+  _fetchVarProperties: function SF__fetchVarProperties(aVar) {
     // Fetch the properties only once.
     if (aVar._fetched) {
       return;
     }
     aVar._fetched = true;
+    let grip = aVar._sourceGrip;
 
-    this.activeThread.pauseGrip(aGrip).getPrototypeAndProperties(function(aResponse) {
+    this.activeThread.pauseGrip(grip).getPrototypeAndProperties(function(aResponse) {
       let { ownProperties, prototype } = aResponse;
-      let sortable = VARIABLES_VIEW_NON_SORTABLE.indexOf(aGrip.class) == -1;
+      let sortable = VARIABLES_VIEW_NON_SORTABLE.indexOf(grip.class) == -1;
 
       // Add all the variable properties.
       if (ownProperties) {
-        aVar.addProperties(ownProperties, { sorted: sortable });
-        // Expansion handlers must be set after the properties are added.
-        for (let name in ownProperties) {
-          this._addVarExpander(aVar.get(name), ownProperties[name].value);
-        }
+        aVar.addProperties(ownProperties, {
+          // Not all variables need to force sorted properties.
+          sorted: sortable,
+          // Expansion handlers must be set after the properties are added.
+          callback: this._addVarExpander
+        });
       }
 
       // Add the variable's __proto__.
       if (prototype && prototype.type != "null") {
         aVar.addProperty("__proto__", { value: prototype });
         // Expansion handlers must be set after the properties are added.
         this._addVarExpander(aVar.get("__proto__"), prototype);
       }
@@ -997,27 +1002,47 @@ StackFrames.prototype = {
   },
 
   /**
    * Updates a list of watch expressions to evaluate on each pause.
    */
   syncWatchExpressions: function SF_syncWatchExpressions() {
     let list = DebuggerView.WatchExpressions.getExpressions();
 
-    if (list.length) {
+    // Sanity check all watch expressions before syncing them. To avoid
+    // having the whole watch expressions array throw because of a single
+    // faulty expression, simply convert it to a string describing the error.
+    // There's no other information necessary to be offered in such cases.
+    let sanitizedExpressions = list.map(function(str) {
+      // Reflect.parse throws when encounters a syntax error.
+      try {
+        Reflect.parse(str);
+        return str; // Watch expression can be executed safely.
+      } catch (e) {
+        return "\"" + e.name + ": " + e.message + "\""; // Syntax error.
+      }
+    });
+
+    if (sanitizedExpressions.length) {
       this.syncedWatchExpressions =
-        this.currentWatchExpressions = "[" + list.map(function(str)
-          // Avoid yielding an empty pseudo-array when evaluating `arguments`,
-          // since they're overridden by the expression's closure scope.
-          "(function(arguments) {" +
-            // Make sure all the quotes are escaped in the expression's syntax.
-            "try { return eval(\"" + str.replace(/"/g, "\\$&") + "\"); }" +
-            "catch(e) { return e.name + ': ' + e.message; }" +
-          "})(arguments)"
-        ).join(",") + "]";
+        this.currentWatchExpressions =
+          "[" +
+            sanitizedExpressions.map(function(str)
+              "eval(\"" +
+                "try {" +
+                  // Make sure all quotes are escaped in the expression's syntax,
+                  // and add a newline after the statement to avoid comments
+                  // breaking the code integrity inside the eval block.
+                  str.replace(/"/g, "\\$&") + "\" + " + "'\\n'" + " + \"" +
+                "} catch (e) {" +
+                  "e.name + ': ' + e.message;" + // FIXME: bug 812765, 812764
+                "}" +
+              "\")"
+            ).join(",") +
+          "]";
     } else {
       this.syncedWatchExpressions =
         this.currentWatchExpressions = null;
     }
     this.currentFrame = null;
     this._onFrames();
   },
 
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -947,18 +947,16 @@ function WatchExpressionsView() {
   MenuContainer.call(this);
   this.switchExpression = this.switchExpression.bind(this);
   this.deleteExpression = this.deleteExpression.bind(this);
   this._createItemView = this._createItemView.bind(this);
   this._onClick = this._onClick.bind(this);
   this._onClose = this._onClose.bind(this);
   this._onBlur = this._onBlur.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
-  this._onMouseOver = this._onMouseOver.bind(this);
-  this._onMouseOut = this._onMouseOut.bind(this);
 }
 
 create({ constructor: WatchExpressionsView, proto: MenuContainer.prototype }, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function DVWE_initialize() {
     dumpn("Initializing the WatchExpressionsView");
@@ -992,17 +990,17 @@ create({ constructor: WatchExpressionsVi
     DebuggerView.showPanesSoon();
 
     // Append a watch expression item to this container.
     let expressionItem = this.push("", aExpression, {
       forced: { atIndex: 0 },
       unsorted: true,
       relaxed: true,
       attachment: {
-        expression: "",
+        currentExpression: "",
         initialExpression: aExpression,
         id: this._generateId()
       }
     });
 
     // Check if watch expression was already appended.
     if (!expressionItem) {
       return;
@@ -1041,41 +1039,41 @@ create({ constructor: WatchExpressionsVi
    *
    * @param Variable aVar
    *        The variable representing the watch expression evaluation.
    * @param string aExpression
    *        The new watch expression text.
    */
   switchExpression: function DVWE_switchExpression(aVar, aExpression) {
     let expressionItem =
-      [i for (i of this._cache) if (i.attachment.expression == aVar.name)][0];
+      [i for (i of this._cache) if (i.attachment.currentExpression == aVar.name)][0];
 
     // Remove the watch expression if it's going to be a duplicate.
     if (!aExpression || this.getExpressions().indexOf(aExpression) != -1) {
       this.deleteExpression(aVar);
       return;
     }
 
     // Save the watch expression code string.
-    expressionItem.attachment.expression = aExpression;
+    expressionItem.attachment.currentExpression = aExpression;
     expressionItem.target.inputNode.value = aExpression;
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
   },
 
   /**
    * Removes the watch expression corresponding to the specified variable item.
    *
    * @param Variable aVar
    *        The variable representing the watch expression evaluation.
    */
   deleteExpression: function DVWE_deleteExpression(aVar) {
     let expressionItem =
-      [i for (i of this._cache) if (i.attachment.expression == aVar.name)][0];
+      [i for (i of this._cache) if (i.attachment.currentExpression == aVar.name)][0];
 
     // Remove the watch expression at its respective index.
     this.removeExpressionAt(this._cache.indexOf(expressionItem));
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
   },
 
@@ -1083,27 +1081,27 @@ create({ constructor: WatchExpressionsVi
    * Gets the watch expression code string for an item in this container.
    *
    * @param number aIndex
    *        The index used to identify the watch expression.
    * @return string
    *         The watch expression code string.
    */
   getExpression: function DVWE_getExpression(aIndex) {
-    return this._cache[aIndex].attachment.expression;
+    return this._cache[aIndex].attachment.currentExpression;
   },
 
   /**
    * Gets the watch expressions code strings for all items in this container.
    *
    * @return array
    *         The watch expressions code strings.
    */
   getExpressions: function DVWE_getExpressions() {
-    return [item.attachment.expression for (item of this._cache)];
+    return [item.attachment.currentExpression for (item of this._cache)];
   },
 
   /**
    * Customization function for creating an item's UI.
    *
    * @param nsIDOMNode aElementNode
    *        The element associated with the displayed item.
    * @param string aExpression
@@ -1115,18 +1113,16 @@ create({ constructor: WatchExpressionsVi
     let closeNode = document.createElement("toolbarbutton");
 
     inputNode.setAttribute("value", aExpression);
     inputNode.setAttribute("flex", "1");
 
     closeNode.addEventListener("click", this._onClose, false);
     inputNode.addEventListener("blur", this._onBlur, false);
     inputNode.addEventListener("keypress", this._onKeyPress, false);
-    aElementNode.addEventListener("mouseover", this._onMouseOver, false);
-    aElementNode.addEventListener("mouseout", this._onMouseOut, false);
 
     aElementNode.appendChild(arrowNode);
     aElementNode.appendChild(inputNode);
     aElementNode.appendChild(closeNode);
     aElementNode.arrowNode = arrowNode;
     aElementNode.inputNode = inputNode;
     aElementNode.closeNode = closeNode;
   },
@@ -1182,33 +1178,30 @@ create({ constructor: WatchExpressionsVi
     e.stopPropagation();
   },
 
   /**
    * The blur listener for a watch expression's textbox.
    */
   _onBlur: function DVWE__onBlur({ target: textbox }) {
     let expressionItem = this.getItemForElement(textbox);
-    let oldExpression = expressionItem.attachment.expression;
+    let oldExpression = expressionItem.attachment.currentExpression;
     let newExpression = textbox.value.trim();
 
     // Remove the watch expression if it's empty.
     if (!newExpression) {
       this.removeExpressionAt(this._cache.indexOf(expressionItem));
     }
     // Remove the watch expression if it's a duplicate.
     else if (!oldExpression && this.getExpressions().indexOf(newExpression) != -1) {
       this.removeExpressionAt(this._cache.indexOf(expressionItem));
     }
     // Expression is eligible.
     else {
-      // Save the watch expression code string.
-      expressionItem.attachment.expression = newExpression;
-      // Make sure the close button is hidden when the textbox is unfocused.
-      expressionItem.target.closeNode.hidden = true;
+      expressionItem.attachment.currentExpression = newExpression;
     }
 
     // Synchronize with the controller's watch expressions store.
     DebuggerController.StackFrames.syncWatchExpressions();
   },
 
   /**
    * The keypress listener for a watch expression's textbox.
@@ -1219,30 +1212,16 @@ create({ constructor: WatchExpressionsVi
       case e.DOM_VK_ENTER:
       case e.DOM_VK_ESCAPE:
         DebuggerView.editor.focus();
         return;
     }
   },
 
   /**
-   * The mouse over listener for a watch expression.
-   */
-  _onMouseOver: function DVWE__onMouseOver({ target: element }) {
-    this.getItemForElement(element).target.closeNode.hidden = false;
-  },
-
-  /**
-   * The mouse out listener for a watch expression.
-   */
-  _onMouseOut: function DVWE__onMouseOut({ target: element }) {
-    this.getItemForElement(element).target.closeNode.hidden = true;
-  },
-
-  /**
    * Gets an identifier for a new watch expression item in the current cache.
    * @return string
    */
   _generateId: (function() {
     let count = 0;
     return function DVWE__generateId() {
       return (++count) + "";
     };
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -1013,16 +1013,24 @@ FilterView.prototype = {
    * 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();
   },
 
+  /**
+   * Called when the variables focus key sequence was pressed.
+   */
+  _doVariablesFocus: function DVG__doVariablesFocus() {
+    DebuggerView.showPanesSoon();
+    DebuggerView.Variables.focusFirstVisibleNode();
+  },
+
   _searchbox: null,
   _searchboxPanel: null,
   _globalOperatorButton: null,
   _globalOperatorLabel: null,
   _tokenOperatorButton: null,
   _tokenOperatorLabel: null,
   _lineOperatorButton: null,
   _lineOperatorLabel: null,
--- a/browser/devtools/debugger/debugger.css
+++ b/browser/devtools/debugger/debugger.css
@@ -35,43 +35,51 @@
 }
 
 .dbg-breakpoint > .state,
 .dbg-breakpoint > .content {
   overflow: hidden;
 }
 
 /**
+ * Watch expressions view
+ */
+
+#expressions {
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+/**
  * Variables view
  */
 
 #variables {
   overflow-x: hidden;
   overflow-y: auto;
 }
 
 /**
  * Scope, variable and property elements
  */
 
-#variables .details:not([open]) {
+.details:not([open]) {
   display: none;
 }
 
+.scope,
+.variable,
+.property {
+  -moz-user-focus: normal;
+}
+
 .scope[non-header] > .title,
 .variable[non-header] > .title,
-.property[non-header] > .title {
-  display: none;
-}
-
-/**
- * Variables and properties searching
- */
-
 .variable[non-match] > .title,
+.property[non-header] > .title,
 .property[non-match] > .title {
   display: none;
 }
 
 /**
  * Toolbar
  */
 
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -38,16 +38,18 @@
     <command id="globalSearchCommand"
              oncommand="DebuggerView.Filtering._doGlobalSearch()"/>
     <command id="tokenSearchCommand"
              oncommand="DebuggerView.Filtering._doTokenSearch()"/>
     <command id="lineSearchCommand"
              oncommand="DebuggerView.Filtering._doLineSearch()"/>
     <command id="variableSearchCommand"
              oncommand="DebuggerView.Filtering._doVariableSearch()"/>
+    <command id="variablesFocusCommand"
+             oncommand="DebuggerView.Filtering._doVariablesFocus()"/>
     <command id="addBreakpointCommand"
              oncommand="DebuggerView.Breakpoints._onCmdAddBreakpoint()"/>
     <command id="addConditionalBreakpointCommand"
              oncommand="DebuggerView.Breakpoints._onCmdAddConditionalBreakpoint()"/>
     <command id="addWatchExpressionCommand"
              oncommand="DebuggerView.WatchExpressions._onCmdAddExpression()"/>
     <command id="removeAllWatchExpressionsCommand"
              oncommand="DebuggerView.WatchExpressions._onCmdRemoveAllExpressions()"/>
@@ -103,16 +105,21 @@
                 key="lineSearchKey"
                 command="lineSearchCommand"/>
       <menuseparator/>
       <menuitem id="se-dbg-cMenu-findVariable"
                 label="&debuggerUI.searchVariable;"
                 accesskey="&debuggerUI.searchVariable.key;"
                 key="variableSearchKey"
                 command="variableSearchCommand"/>
+      <menuitem id="se-dbg-cMenu-focusVariables"
+                label="&debuggerUI.focusVariables;"
+                accesskey="&debuggerUI.focusVariables.key;"
+                key="variablesFocusKey"
+                command="variablesFocusCommand"/>
     </menupopup>
 
     <menupopup id="debuggerWatchExpressionsContextMenu">
       <menuitem id="add-watch-expression"
                 label="&debuggerUI.addWatch;"
                 accesskey="&debuggerUI.addWatch.key;"
                 key="addWatchExpressionKey"
                 command="addWatchExpressionCommand"/>
@@ -179,16 +186,20 @@
     <key id="lineSearchKey"
          key="&debuggerUI.searchLine.key;"
          modifiers="accel"
          command="lineSearchCommand"/>
     <key id="variableSearchKey"
          key="&debuggerUI.searchVariable.key;"
          modifiers="accel alt"
          command="variableSearchCommand"/>
+    <key id="variablesFocusKey"
+         key="&debuggerUI.focusVariables.key;"
+         modifiers="accel shift"
+         command="variablesFocusCommand"/>
     <key id="addBreakpointKey"
          key="&debuggerUI.seMenuBreak.key;"
          modifiers="accel"
          command="addBreakpointCommand"/>
     <key id="addConditionalBreakpointKey"
          key="&debuggerUI.seMenuCondBreak.key;"
          modifiers="accel shift"
          command="addConditionalBreakpointCommand"/>
--- a/browser/devtools/debugger/test/Makefile.in
+++ b/browser/devtools/debugger/test/Makefile.in
@@ -27,19 +27,21 @@ MOCHITEST_BROWSER_TESTS = \
 	browser_dbg_propertyview-03.js \
 	browser_dbg_propertyview-04.js \
 	browser_dbg_propertyview-05.js \
 	browser_dbg_propertyview-06.js \
 	browser_dbg_propertyview-07.js \
 	browser_dbg_propertyview-08.js \
 	browser_dbg_propertyview-09.js \
 	browser_dbg_propertyview-10.js \
-	browser_dbg_propertyview-edit.js \
+	browser_dbg_propertyview-edit-value.js \
 	browser_dbg_propertyview-edit-watch.js \
-	browser_dbg_propertyview-big-data.js \
+	browser_dbg_propertyview-data-big.js \
+	browser_dbg_propertyview-data-getset-01.js \
+	browser_dbg_propertyview-data-getset-02.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 \
--- a/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-01.js
@@ -18,20 +18,17 @@ function test()
   debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPane = aPane;
     gDebugger = gPane.panelWin;
     gWatch = gDebugger.DebuggerView.WatchExpressions;
 
     gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
-
-    executeSoon(function() {
-      performTest();
-    });
+    performTest();
   });
 
   function performTest()
   {
     is(gWatch.getExpressions().length, 0,
       "There should initially be no watch expressions");
 
     addAndCheckExpressions(1, 0, "a");
@@ -124,19 +121,19 @@ function test()
     let id = gWatch.getItemAtIndex(index).attachment.id;
     let element = gDebugger.document.getElementById("expression-" + id);
 
     is(gWatch.getItemAtIndex(index).attachment.initialExpression, "",
       "The initial expression at index " + index + " should be correct (1)");
     is(gWatch.getItemForElement(element).attachment.initialExpression, "",
       "The initial expression at index " + index + " should be correct (2)");
 
-    is(gWatch.getItemAtIndex(index).attachment.expression, string,
+    is(gWatch.getItemAtIndex(index).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (1)");
-    is(gWatch.getItemForElement(element).attachment.expression, string,
+    is(gWatch.getItemForElement(element).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (2)");
 
     is(gWatch.getExpression(index), string,
       "The expression at index " + index + " should be correct (3)");
     is(gWatch.getExpressions()[index], string,
       "The expression at index " + index + " should be correct (4)");
   }
 
@@ -190,19 +187,19 @@ function test()
     if (!noBlur) {
       gDebugger.editor.focus();
 
       is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
         "The initial expression at index " + index + " should be correct (1)");
       is(gWatch.getItemForElement(element).attachment.initialExpression, string,
         "The initial expression at index " + index + " should be correct (2)");
 
-      is(gWatch.getItemAtIndex(index).attachment.expression, string,
+      is(gWatch.getItemAtIndex(index).attachment.currentExpression, string,
         "The expression at index " + index + " should be correct (1)");
-      is(gWatch.getItemForElement(element).attachment.expression, string,
+      is(gWatch.getItemForElement(element).attachment.currentExpression, string,
         "The expression at index " + index + " should be correct (2)");
 
       is(gWatch.getExpression(index), string,
         "The expression at index " + index + " should be correct (3)");
       is(gWatch.getExpressions()[index], string,
         "The expression at index " + index + " should be correct (4)");
     }
   }
@@ -225,19 +222,19 @@ function test()
     let id = gWatch.getItemAtIndex(index).attachment.id;
     let element = gDebugger.document.getElementById("expression-" + id);
 
     is(gWatch.getItemAtIndex(index).attachment.initialExpression, string,
       "The initial expression at index " + index + " should be correct (1)");
     is(gWatch.getItemForElement(element).attachment.initialExpression, string,
       "The initial expression at index " + index + " should be correct (2)");
 
-    is(gWatch.getItemAtIndex(index).attachment.expression, string,
+    is(gWatch.getItemAtIndex(index).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (1)");
-    is(gWatch.getItemForElement(element).attachment.expression, string,
+    is(gWatch.getItemForElement(element).attachment.currentExpression, string,
       "The expression at index " + index + " should be correct (2)");
 
     is(gWatch.getExpression(index), string,
       "The expression at index " + index + " should be correct (3)");
     is(gWatch.getExpressions()[index], string,
       "The expression at index " + index + " should be correct (4)");
   }
 
--- a/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
@@ -32,32 +32,45 @@ function test()
   function addExpressions()
   {
     gWatch.addExpression("'a'");
     gWatch.addExpression("\"a\"");
     gWatch.addExpression("'a\"\"'");
     gWatch.addExpression("\"a''\"");
     gWatch.addExpression("?");
     gWatch.addExpression("a");
+    gWatch.addExpression("this");
+    gWatch.addExpression("this.canada");
     gWatch.addExpression("[1, 2, 3]");
     gWatch.addExpression("x = [1, 2, 3]");
     gWatch.addExpression("y = [1, 2, 3]; y.test = 4");
     gWatch.addExpression("z = [1, 2, 3]; z.test = 4; z");
     gWatch.addExpression("t = [1, 2, 3]; t.test = 4; !t");
     gWatch.addExpression("arguments[0]");
     gWatch.addExpression("encodeURI(\"\\\")");
     gWatch.addExpression("decodeURI(\"\\\")");
+    gWatch.addExpression("decodeURIComponent(\"%\")");
+    gWatch.addExpression("//");
+    gWatch.addExpression("// 42");
+    gWatch.addExpression("{}.foo");
+    gWatch.addExpression("{}.foo()");
+    gWatch.addExpression("({}).foo()");
+    gWatch.addExpression("new Array(-1)");
+    gWatch.addExpression("4.2.toExponential(-4.2)");
+    gWatch.addExpression("throw new Error(\"bazinga\")");
+    gWatch.addExpression("({ get error() { throw new Error(\"bazinga\") } }).error");
+    gWatch.addExpression("throw { get name() { throw \"bazinga\" } }");
   }
 
   function performTest()
   {
     is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
       "There should be 0 hidden nodes in the watch expressions container");
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 14,
-      "There should be 14 visible nodes in the watch expressions container");
+    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
+      "There should be 27 visible nodes in the watch expressions container");
 
     test1(function() {
       test2(function() {
         test3(function() {
           test4(function() {
             test5(function() {
               test6(function() {
                 test7(function() {
@@ -74,107 +87,139 @@ function test()
       });
     });
   }
 
   function finishTest()
   {
     is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
       "There should be 0 hidden nodes in the watch expressions container");
-    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 13,
-      "There should be 13 visible nodes in the watch expressions container");
+    is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 27,
+      "There should be 27 visible nodes in the watch expressions container");
 
     closeDebuggerAndFinish();
   }
 
   function test1(callback) {
     waitForWatchExpressions(function() {
       info("Performing test1");
-      checkWatchExpressions("ReferenceError: a is not defined", undefined);
+      checkWatchExpressions("ReferenceError: a is not defined",
+                            { type: "object", class: "Object" },
+                            { type: "object", class: "String" },
+                            undefined,
+                            26);
       callback();
     });
     executeSoon(function() {
-      gDebuggee.ermahgerd(); // ermahgerd!!
+      gDebuggee.test(); // ermahgerd!!
     });
   }
 
   function test2(callback) {
     waitForWatchExpressions(function() {
       info("Performing test2");
-      checkWatchExpressions(undefined, "sensational");
+      checkWatchExpressions(undefined,
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            26);
       callback();
     });
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.getElementById("resume"),
       gDebugger);
   }
 
   function test3(callback) {
     waitForWatchExpressions(function() {
       info("Performing test3");
-      checkWatchExpressions({ type: "object", class: "Object" }, "sensational");
+      checkWatchExpressions({ type: "object", class: "Object" },
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            26);
       callback();
     });
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.getElementById("resume"),
       gDebugger);
   }
 
   function test4(callback) {
     waitForWatchExpressions(function() {
       info("Performing test4");
-      checkWatchExpressions(5, "sensational", 13);
+      checkWatchExpressions(5,
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("a = 5");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test5(callback) {
     waitForWatchExpressions(function() {
       info("Performing test5");
-      checkWatchExpressions(5, "sensational", 13);
+      checkWatchExpressions(5,
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("encodeURI(\"\\\")");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test6(callback) {
     waitForWatchExpressions(function() {
       info("Performing test6");
-      checkWatchExpressions(5, "sensational", 13);
+      checkWatchExpressions(5,
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            27);
       callback();
     })
     executeSoon(function() {
       gWatch.addExpression("decodeURI(\"\\\")");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test7(callback) {
     waitForWatchExpressions(function() {
       info("Performing test7");
-      checkWatchExpressions(5, "sensational", 13);
+      checkWatchExpressions(5,
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("?");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test8(callback) {
     waitForWatchExpressions(function() {
       info("Performing test8");
-      checkWatchExpressions(5, "sensational", 13);
+      checkWatchExpressions(5,
+                            { type: "object", class: "Proxy" },
+                            undefined,
+                            "sensational",
+                            27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("a");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
@@ -197,17 +242,22 @@ function test()
 
   function waitForWatchExpressions(callback) {
     gDebugger.addEventListener("Debugger:FetchedWatchExpressions", function onFetch() {
       gDebugger.removeEventListener("Debugger:FetchedWatchExpressions", onFetch, false);
       executeSoon(callback);
     }, false);
   }
 
-  function checkWatchExpressions(expected_a, expected_arguments, total = 12) {
+  function checkWatchExpressions(expected_a,
+                                 expected_this,
+                                 expected_prop,
+                                 expected_arguments,
+                                 total)
+  {
     is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, total,
       "There should be " + total + " hidden nodes in the watch expressions container");
     is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
       "There should be 0 visible nodes in the watch expressions container");
 
     let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
     let scope = gVars._currHierarchy.get(label);
 
@@ -215,58 +265,115 @@ function test()
     is(scope._store.size, total, "There should be " + total + " evaluations availalble");
 
     let w1 = scope.get("'a'");
     let w2 = scope.get("\"a\"");
     let w3 = scope.get("'a\"\"'");
     let w4 = scope.get("\"a''\"");
     let w5 = scope.get("?");
     let w6 = scope.get("a");
-    let w7 = scope.get("x = [1, 2, 3]");
-    let w8 = scope.get("y = [1, 2, 3]; y.test = 4");
-    let w9 = scope.get("z = [1, 2, 3]; z.test = 4; z");
-    let w10 = scope.get("t = [1, 2, 3]; t.test = 4; !t");
-    let w11 = scope.get("arguments[0]");
-    let w12 = scope.get("encodeURI(\"\\\")");
-    let w13 = scope.get("decodeURI(\"\\\")");
+    let w7 = scope.get("this");
+    let w8 = scope.get("this.canada");
+    let w9 = scope.get("[1, 2, 3]");
+    let w10 = scope.get("x = [1, 2, 3]");
+    let w11 = scope.get("y = [1, 2, 3]; y.test = 4");
+    let w12 = scope.get("z = [1, 2, 3]; z.test = 4; z");
+    let w13 = scope.get("t = [1, 2, 3]; t.test = 4; !t");
+    let w14 = scope.get("arguments[0]");
+    let w15 = scope.get("encodeURI(\"\\\")");
+    let w16 = scope.get("decodeURI(\"\\\")");
+    let w17 = scope.get("decodeURIComponent(\"%\")");
+    let w18 = scope.get("//");
+    let w19 = scope.get("// 42");
+    let w20 = scope.get("{}.foo");
+    let w21 = scope.get("{}.foo()");
+    let w22 = scope.get("({}).foo()");
+    let w23 = scope.get("new Array(-1)");
+    let w24 = scope.get("4.2.toExponential(-4.2)");
+    let w25 = scope.get("throw new Error(\"bazinga\")");
+    let w26 = scope.get("({ get error() { throw new Error(\"bazinga\") } }).error");
+    let w27 = scope.get("throw { get name() { throw \"bazinga\" } }");
 
     ok(w1, "The first watch expression should be present in the scope");
     ok(w2, "The second watch expression should be present in the scope");
     ok(w3, "The third watch expression should be present in the scope");
     ok(w4, "The fourth watch expression should be present in the scope");
     ok(w5, "The fifth watch expression should be present in the scope");
     ok(w6, "The sixth watch expression should be present in the scope");
     ok(w7, "The seventh watch expression should be present in the scope");
     ok(w8, "The eight watch expression should be present in the scope");
     ok(w9, "The ninth watch expression should be present in the scope");
     ok(w10, "The tenth watch expression should be present in the scope");
     ok(w11, "The eleventh watch expression should be present in the scope");
-    ok(!w12, "The twelveth watch expression should not be present in the scope");
-    ok(!w13, "The thirteenth watch expression should not be present in the scope");
+    ok(w12, "The twelfth watch expression should be present in the scope");
+    ok(w13, "The 13th watch expression should be present in the scope");
+    ok(w14, "The 14th watch expression should be present in the scope");
+    ok(w15, "The 15th watch expression should be present in the scope");
+    ok(w16, "The 16th watch expression should be present in the scope");
+    ok(w17, "The 17th watch expression should be present in the scope");
+    ok(w18, "The 18th watch expression should be present in the scope");
+    ok(w19, "The 19th watch expression should be present in the scope");
+    ok(w20, "The 20th watch expression should be present in the scope");
+    ok(w21, "The 21st watch expression should be present in the scope");
+    ok(w22, "The 22nd watch expression should be present in the scope");
+    ok(w23, "The 23nd watch expression should be present in the scope");
+    ok(w24, "The 24th watch expression should be present in the scope");
+    ok(w25, "The 25th watch expression should be present in the scope");
+    ok(w26, "The 26th watch expression should be present in the scope");
+    ok(!w27, "The 27th watch expression should not be present in the scope");
 
     is(w1.value, "a", "The first value is correct");
     is(w2.value, "a", "The second value is correct");
     is(w3.value, "a\"\"", "The third value is correct");
     is(w4.value, "a''", "The fourth value is correct");
     is(w5.value, "SyntaxError: syntax error", "The fifth value is correct");
 
     if (typeof expected_a == "object") {
       is(w6.value.type, expected_a.type, "The sixth value type is correct");
       is(w6.value.class, expected_a.class, "The sixth value class is correct");
     } else {
       is(w6.value, expected_a, "The sixth value is correct");
     }
 
-    is(w7.value.type, "object", "The seventh value type is correct");
-    is(w7.value.class, "Array", "The seventh value class is correct");
-    is(w8.value, "4", "The eight value is correct");
+    if (typeof expected_this == "object") {
+      is(w7.value.type, expected_this.type, "The seventh value type is correct");
+      is(w7.value.class, expected_this.class, "The seventh value class is correct");
+    } else {
+      is(w7.value, expected_this, "The seventh value is correct");
+    }
+
+    if (typeof expected_prop == "object") {
+      is(w8.value.type, expected_prop.type, "The eighth value type is correct");
+      is(w8.value.class, expected_prop.class, "The eighth value class is correct");
+    } else {
+      is(w8.value, expected_prop, "The eighth value is correct");
+    }
+
     is(w9.value.type, "object", "The ninth value type is correct");
     is(w9.value.class, "Array", "The ninth value class is correct");
-    is(w10.value, false, "The tenth value is correct");
-    is(w11.value, expected_arguments, "The eleventh value is correct");
+    is(w10.value.type, "object", "The tenth value type is correct");
+    is(w10.value.class, "Array", "The tenth value class is correct");
+    is(w11.value, "4", "The eleventh value is correct");
+    is(w12.value.type, "object", "The eleventh value type is correct");
+    is(w12.value.class, "Array", "The twelfth value class is correct");
+    is(w13.value, false, "The 13th value is correct");
+    is(w14.value, expected_arguments, "The 14th value is correct");
+
+    is(w15.value, "SyntaxError: unterminated string literal", "The 15th value is correct");
+    is(w16.value, "SyntaxError: unterminated string literal", "The 16th value is correct");
+    is(w17.value, "URIError: malformed URI sequence", "The 17th value is correct");
+    is(w18.value, undefined, "The 18th value is correct");
+    is(w19.value, undefined, "The 19th value is correct");
+    is(w20.value, "SyntaxError: syntax error", "The 20th value is correct");
+    is(w21.value, "SyntaxError: syntax error", "The 21th value is correct");
+    is(w22.value, "TypeError: (intermediate value).foo is not a function", "The 22th value is correct");
+    is(w23.value, "RangeError: invalid array length", "The 23th value is correct");
+    is(w24.value, "RangeError: precision -4 out of range", "The 24st value is correct");
+    is(w25.value, "Error: bazinga", "The 25nd value is correct");
+    is(w26.value, "Error: bazinga", "The 26rd value is correct");
   }
 
   registerCleanupFunction(function() {
     removeTab(gTab);
     gPane = null;
     gTab = null;
     gDebuggee = null;
     gDebugger = null;
--- a/browser/devtools/debugger/test/browser_dbg_frame-parameters.html
+++ b/browser/devtools/debugger/test/browser_dbg_frame-parameters.html
@@ -6,16 +6,21 @@
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
     <script type="text/javascript">
       window.addEventListener("load", function() {
         function test(aArg, bArg, cArg, dArg, eArg, fArg) {
           var a = 1;
           var b = { a: a };
           var c = { a: 1, b: "beta", c: true, d: b };
+          var myVar = {
+            _prop: 42,
+            get prop() { return this._prop; },
+            set prop(val) { this._prop = val; }
+          };
 
           debugger;
         }
         function load() {
           var a = { a: 1, b: "beta", c: true };
           var e = eval("test(a, 'beta', 3, false, null)");
         }
         var button = document.querySelector("button");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-03.js
@@ -134,18 +134,18 @@ function testSimpleCall() {
       ok(!testVar.expanded,
         "Clicking again the testVar arrow should collapse it.");
 
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         testVar.target.querySelector(".title"),
         gDebugger);
 
-      ok(!testVar.expanded,
-        "Clicking the testVar title div shouldn't expand it.");
+      ok(testVar.expanded,
+        "Clicking the testVar title div should expand it again.");
 
 
       testScope.show();
       testScope.expand();
       testVar.show();
       testVar.expand();
 
       ok(!testVar.get("child").expanded,
@@ -180,18 +180,18 @@ function testSimpleCall() {
       ok(!testVar.get("child").expanded,
         "Clicking again the testVar child property arrow should collapse it.");
 
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         testVar.get("child").target.querySelector(".title"),
         gDebugger);
 
-      ok(!testVar.get("child").expanded,
-        "Clicking the testVar child property title div shouldn't expand it.");
+      ok(testVar.get("child").expanded,
+        "Clicking the testVar child property title div should expand it again.");
 
 
       gDebugger.DebuggerView.Variables.empty();
       is(gDebugger.DebuggerView.Variables._list.childNodes.length, 0,
         "The scopes should have been removed from the parent container tree.");
 
       gDebugger.DebuggerController.activeThread.resume(function() {
         closeDebuggerAndFinish();
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -35,17 +35,17 @@ function testFrameParameters()
           localNodes = localScope.querySelector(".details").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
-      is(localNodes.length, 11,
+      is(localNodes.length, 12,
         "The localScope should contain all the created variable elements.");
 
       is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Proxy]",
         "Should have the right property value for 'this'.");
 
       is(localNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
         "Should have the right property value for 'aArg'.");
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -35,17 +35,17 @@ function testFrameParameters()
           localNonEnums = localScope.querySelector(".nonenum").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
-      is(localNodes.length + localNonEnums.length, 11,
+      is(localNodes.length + localNonEnums.length, 12,
         "The localScope and localNonEnums should contain all the created variable elements.");
 
       is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Proxy]",
         "Should have the right property value for 'this'.");
       is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Arguments]",
         "Should have the right property value for 'arguments'.");
       is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Object]",
         "Should have the right property value for 'c'.");
@@ -69,19 +69,19 @@ function testFrameParameters()
       is(gVars.getScopeForNode(gVars._list.querySelectorAll(".scope")[0]).expanded, true,
         "The local scope should be expanded by default.");
       is(gVars.getScopeForNode(gVars._list.querySelectorAll(".scope")[1]).expanded, false,
         "The block scope should be collapsed by default.");
       is(gVars.getScopeForNode(gVars._list.querySelectorAll(".scope")[2]).expanded, false,
         "The global scope should be collapsed by default.");
 
 
-      let thisNode = gVars.getVariableOrPropertyForNode(localNodes[0]);
-      let argumentsNode = gVars.getVariableOrPropertyForNode(localNodes[8]);
-      let cNode = gVars.getVariableOrPropertyForNode(localNodes[10]);
+      let thisNode = gVars.getItemForNode(localNodes[0]);
+      let argumentsNode = gVars.getItemForNode(localNodes[8]);
+      let cNode = gVars.getItemForNode(localNodes[10]);
 
       is(thisNode.expanded, false,
         "The thisNode should not be expanded at this point.");
       is(argumentsNode.expanded, false,
         "The argumentsNode should not be expanded at this point.");
       is(cNode.expanded, false,
         "The cNode should not be expanded at this point.");
 
@@ -175,30 +175,30 @@ function testFrameParameters()
         is(cNode.target.querySelectorAll(".property > .title > .name")[2]
            .getAttribute("value"), "c",
           "Should have the right property name for 'c.c'.");
         is(cNode.target.querySelectorAll(".property > .title > .value")[2]
            .getAttribute("value"), "true",
           "Should have the right value for 'c.c'.");
 
 
-        is(gVars.getVariableOrPropertyForNode(
+        is(gVars.getItemForNode(
            cNode.target.querySelectorAll(".property")[0]).target,
            cNode.target.querySelectorAll(".property")[0],
-          "getVariableOrPropertyForNode([0]) didn't return the expected property.");
+          "getItemForNode([0]) didn't return the expected property.");
 
-        is(gVars.getVariableOrPropertyForNode(
+        is(gVars.getItemForNode(
            cNode.target.querySelectorAll(".property")[1]).target,
            cNode.target.querySelectorAll(".property")[1],
-          "getVariableOrPropertyForNode([1]) didn't return the expected property.");
+          "getItemForNode([1]) didn't return the expected property.");
 
-        is(gVars.getVariableOrPropertyForNode(
+        is(gVars.getItemForNode(
            cNode.target.querySelectorAll(".property")[2]).target,
            cNode.target.querySelectorAll(".property")[2],
-          "getVariableOrPropertyForNode([2]) didn't return the expected property.");
+          "getItemForNode([2]) didn't return the expected property.");
 
 
         is(cNode.find(
            cNode.target.querySelectorAll(".property")[0]).target,
            cNode.target.querySelectorAll(".property")[0],
           "find([0]) didn't return the expected property.");
 
         is(cNode.find(
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
@@ -48,17 +48,17 @@ function testFrameParameters()
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(globalNodes[0].querySelector(".name").getAttribute("value"), "InstallTrigger",
         "Should have the right property name for |InstallTrigger|.");
 
-      is(globalNodes[0].querySelector(".value").getAttribute("value"), "undefined",
+      is(globalNodes[0].querySelector(".value").getAttribute("value"), "",
         "Should have the right property value for |InstallTrigger|.");
 
       is(globalNodes[1].querySelector(".name").getAttribute("value"), "SpecialPowers",
         "Should have the right property name for |SpecialPowers|.");
 
       is(globalNodes[1].querySelector(".value").getAttribute("value"), "[object Proxy]",
         "Should have the right property value for |SpecialPowers|.");
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
@@ -59,17 +59,17 @@ function testWithFrame()
         "Should have the right property name for |one|.");
 
       is(innerNodes[1].querySelector(".value").getAttribute("value"), "1",
         "Should have the right property value for |one|.");
 
       is(globalNodes[0].querySelector(".name").getAttribute("value"), "InstallTrigger",
         "Should have the right property name for |InstallTrigger|.");
 
-      is(globalNodes[0].querySelector(".value").getAttribute("value"), "undefined",
+      is(globalNodes[0].querySelector(".value").getAttribute("value"), "",
         "Should have the right property value for |InstallTrigger|.");
 
       let len = globalNodes.length - 1;
       is(globalNodes[len].querySelector(".name").getAttribute("value"), "window",
         "Should have the right property name for |window|.");
 
       is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Proxy]",
         "Should have the right property value for |window|.");
rename from browser/devtools/debugger/test/browser_dbg_propertyview-big-data.js
rename to browser/devtools/debugger/test/browser_dbg_propertyview-data-big.js
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-01.js
@@ -0,0 +1,343 @@
+/* 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 knows how to edit getters and setters.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
+
+var gPane = null;
+var gTab = null;
+var gDebugger = null;
+var gVars = null;
+var gWatch = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+    gVars = gDebugger.DebuggerView.Variables;
+    gWatch = gDebugger.DebuggerView.WatchExpressions;
+
+    gVars.switch = function() {};
+    gVars.delete = function() {};
+
+    prepareVariablesView();
+  });
+}
+
+function prepareVariablesView() {
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      testVariablesView();
+
+    }}, 0);
+  }, false);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    content.document.querySelector("button"),
+    content.window);
+}
+
+function testVariablesView()
+{
+  executeSoon(function() {
+    addWatchExpressions(function() {
+      testEdit("set", "this._prop = value + ' BEER CAN'", function() {
+        testEdit("set", "{ this._prop = value + ' BEACON' }", function() {
+          testEdit("set", "{ this._prop = value + ' BEACON;'; }", function() {
+            testEdit("set", "{ return this._prop = value + ' BEACON;;'; }", function() {
+              testEdit("set", "function(value) { this._prop = value + ' BACON' }", function() {
+                testEdit("get", "'brelx BEER CAN'", function() {
+                  testEdit("get", "{ 'brelx BEACON' }", function() {
+                    testEdit("get", "{ 'brelx BEACON;'; }", function() {
+                      testEdit("get", "{ return 'brelx BEACON;;'; }", function() {
+                        testEdit("get", "function() { return 'brelx BACON'; }", function() {
+                          testEdit("get", "bogus", function() {
+                            testEdit("set", "sugob", function() {
+                              testEdit("get", "", function() {
+                                testEdit("set", "", function() {
+                                  waitForWatchExpressions(function() {
+                                    testEdit("self", "2507", function() {
+                                      closeDebuggerAndFinish();
+                                    }, {
+                                      "myVar.prop": 2507,
+                                      "myVar.prop + 42": "250742"
+                                    });
+                                  })
+                                  gWatch.deleteExpression({ name: "myVar.prop = 'xlerb'" });
+                                }, {
+                                  "myVar.prop": "xlerb",
+                                  "myVar.prop + 42": NaN,
+                                  "myVar.prop = 'xlerb'": "xlerb"
+                                });
+                              }, {
+                                "myVar.prop": undefined,
+                                "myVar.prop + 42": NaN,
+                                "myVar.prop = 'xlerb'": "ReferenceError: sugob is not defined"
+                              });
+                            }, {
+                              "myVar.prop": "ReferenceError: bogus is not defined",
+                              "myVar.prop + 42": "ReferenceError: bogus is not defined",
+                              "myVar.prop = 'xlerb'": "ReferenceError: sugob is not defined"
+                            });
+                          }, {
+                            "myVar.prop": "ReferenceError: bogus is not defined",
+                            "myVar.prop + 42": "ReferenceError: bogus is not defined",
+                            "myVar.prop = 'xlerb'": "xlerb"
+                          });
+                        }, {
+                          "myVar.prop": "brelx BACON",
+                          "myVar.prop + 42": "brelx BACON42",
+                          "myVar.prop = 'xlerb'": "xlerb"
+                        });
+                      }, {
+                        "myVar.prop": "brelx BEACON;;",
+                        "myVar.prop + 42": "brelx BEACON;;42",
+                        "myVar.prop = 'xlerb'": "xlerb"
+                      });
+                    }, {
+                      "myVar.prop": undefined,
+                      "myVar.prop + 42": NaN,
+                      "myVar.prop = 'xlerb'": "xlerb"
+                    });
+                  }, {
+                    "myVar.prop": undefined,
+                    "myVar.prop + 42": NaN,
+                    "myVar.prop = 'xlerb'": "xlerb"
+                  });
+                }, {
+                  "myVar.prop": "brelx BEER CAN",
+                  "myVar.prop + 42": "brelx BEER CAN42",
+                  "myVar.prop = 'xlerb'": "xlerb"
+                });
+              }, {
+                "myVar.prop": "xlerb BACON",
+                "myVar.prop + 42": "xlerb BACON42",
+                "myVar.prop = 'xlerb'": "xlerb"
+              });
+            }, {
+              "myVar.prop": "xlerb BEACON;;",
+              "myVar.prop + 42": "xlerb BEACON;;42",
+              "myVar.prop = 'xlerb'": "xlerb"
+            });
+          }, {
+            "myVar.prop": "xlerb BEACON;",
+            "myVar.prop + 42": "xlerb BEACON;42",
+            "myVar.prop = 'xlerb'": "xlerb"
+          });
+        }, {
+          "myVar.prop": "xlerb BEACON",
+          "myVar.prop + 42": "xlerb BEACON42",
+          "myVar.prop = 'xlerb'": "xlerb"
+        });
+      }, {
+        "myVar.prop": "xlerb BEER CAN",
+        "myVar.prop + 42": "xlerb BEER CAN42",
+        "myVar.prop = 'xlerb'": "xlerb"
+      });
+    });
+  });
+}
+
+function addWatchExpressions(callback)
+{
+  waitForWatchExpressions(function() {
+    let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+    let scope = gVars._currHierarchy.get(label);
+
+    ok(scope, "There should be a wach expressions scope in the variables view");
+    is(scope._store.size, 1, "There should be 1 evaluation availalble");
+
+    let w1 = scope.get("myVar.prop");
+    let w2 = scope.get("myVar.prop + 42");
+    let w3 = scope.get("myVar.prop = 'xlerb'");
+
+    ok(w1, "The first watch expression should be present in the scope");
+    ok(!w2, "The second watch expression should not be present in the scope");
+    ok(!w3, "The third watch expression should not be present in the scope");
+
+    is(w1.value, 42, "The first value is correct.");
+
+
+    waitForWatchExpressions(function() {
+      let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+      let scope = gVars._currHierarchy.get(label);
+
+      ok(scope, "There should be a wach expressions scope in the variables view");
+      is(scope._store.size, 2, "There should be 2 evaluations availalble");
+
+      let w1 = scope.get("myVar.prop");
+      let w2 = scope.get("myVar.prop + 42");
+      let w3 = scope.get("myVar.prop = 'xlerb'");
+
+      ok(w1, "The first watch expression should be present in the scope");
+      ok(w2, "The second watch expression should be present in the scope");
+      ok(!w3, "The third watch expression should not be present in the scope");
+
+      is(w1.value, "42", "The first expression value is correct.");
+      is(w2.value, "84", "The second expression value is correct.");
+
+
+      waitForWatchExpressions(function() {
+        let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+        let scope = gVars._currHierarchy.get(label);
+
+        ok(scope, "There should be a wach expressions scope in the variables view");
+        is(scope._store.size, 3, "There should be 3 evaluations availalble");
+
+        let w1 = scope.get("myVar.prop");
+        let w2 = scope.get("myVar.prop + 42");
+        let w3 = scope.get("myVar.prop = 'xlerb'");
+
+        ok(w1, "The first watch expression should be present in the scope");
+        ok(w2, "The second watch expression should be present in the scope");
+        ok(w3, "The third watch expression should be present in the scope");
+
+        is(w1.value, "xlerb", "The first expression value is correct.");
+        is(w2.value, "xlerb42", "The second expression value is correct.");
+        is(w3.value, "xlerb", "The third expression value is correct.");
+
+        callback();
+      });
+
+      gWatch.addExpression("myVar.prop = 'xlerb'");
+      gDebugger.editor.focus();
+    });
+
+    gWatch.addExpression("myVar.prop + 42");
+    gDebugger.editor.focus();
+  });
+
+  gWatch.addExpression("myVar.prop");
+  gDebugger.editor.focus();
+}
+
+function testEdit(what, string, callback, expected)
+{
+  let localScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".scope")[1],
+      localNodes = localScope.querySelector(".details").childNodes,
+      myVar = gVars.getItemForNode(localNodes[11]);
+
+  waitForProperties(function() {
+    let prop = myVar.get("prop");
+    let getterOrSetter = (what != "self" ? prop.get(what) : prop);
+
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      getterOrSetter._target.querySelector(".title > .value"),
+      gDebugger);
+
+    waitForElement(".element-value-input", true, function() {
+      waitForWatchExpressions(function() {
+        let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+        let scope = gVars._currHierarchy.get(label);
+
+        let w1 = scope.get(Object.keys(expected)[0]);
+        let w2 = scope.get(Object.keys(expected)[1]);
+        let w3 = scope.get(Object.keys(expected)[2]);
+
+        if (w1) {
+          if (isNaN(expected[w1.name]) && typeof expected[w1.name] == "number") {
+            ok(isNaN(w1.value),
+              "The first expression value is correct after the edit (NaN).");
+          } else {
+            is(w1.value, expected[w1.name],
+              "The first expression value is correct after the edit.");
+          }
+          info(w1.value + " is equal to " + expected[w1.name]);
+        }
+        if (w2) {
+          if (isNaN(expected[w2.name]) && typeof expected[w2.name] == "number") {
+            ok(isNaN(w2.value),
+              "The second expression value is correct after the edit (NaN).");
+          } else {
+            is(w2.value, expected[w2.name],
+              "The second expression value is correct after the edit.");
+          }
+          info(w2.value + " is equal to " + expected[w2.name]);
+        }
+        if (w3) {
+          if (isNaN(expected[w3.name]) && typeof expected[w3.name] == "number") {
+            ok(isNaN(w3.value),
+              "The third expression value is correct after the edit (NaN).");
+          } else {
+            is(w3.value, expected[w3.name],
+              "The third expression value is correct after the edit.");
+          }
+          info(w3.value + " is equal to " + expected[w3.name]);
+        }
+
+        callback();
+      });
+
+      info("Changing the " + what + "ter with '" + string + "'.");
+
+      write(string);
+      EventUtils.sendKey("RETURN", gDebugger);
+    });
+  });
+
+  myVar.expand();
+  gVars.clearHierarchy();
+}
+
+function waitForWatchExpressions(callback) {
+  gDebugger.addEventListener("Debugger:FetchedWatchExpressions", function onFetch() {
+    gDebugger.removeEventListener("Debugger:FetchedWatchExpressions", onFetch, false);
+    executeSoon(callback);
+  }, false);
+}
+
+function waitForProperties(callback) {
+  gDebugger.addEventListener("Debugger:FetchedProperties", function onFetch() {
+    gDebugger.removeEventListener("Debugger:FetchedProperties", onFetch, false);
+    executeSoon(callback);
+  }, false);
+}
+
+function waitForElement(selector, exists, callback)
+{
+  // Poll every few milliseconds until the element are retrieved.
+  let count = 0;
+  let intervalID = window.setInterval(function() {
+    info("count: " + count + " ");
+    if (++count > 50) {
+      ok(false, "Timed out while polling for the element.");
+      window.clearInterval(intervalID);
+      return closeDebuggerAndFinish();
+    }
+    if (!!gVars._list.querySelector(selector) != exists) {
+      return;
+    }
+    // We got the element, it's safe to callback.
+    window.clearInterval(intervalID);
+    callback();
+  }, 100);
+}
+
+function write(text) {
+  if (!text) {
+    EventUtils.sendKey("BACK_SPACE", gDebugger);
+    return;
+  }
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i], gDebugger);
+  }
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+  gVars = null;
+  gWatch = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-02.js
@@ -0,0 +1,185 @@
+/* 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 is able to override getter properties
+ * to plain value properties.
+ */
+
+const TAB_URL = EXAMPLE_URL + "browser_dbg_frame-parameters.html";
+
+var gPane = null;
+var gTab = null;
+var gDebugger = null;
+var gVars = null;
+var gWatch = null;
+
+requestLongerTimeout(2);
+
+function test()
+{
+  debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
+    gTab = aTab;
+    gPane = aPane;
+    gDebugger = gPane.panelWin;
+    gVars = gDebugger.DebuggerView.Variables;
+    gWatch = gDebugger.DebuggerView.WatchExpressions;
+
+    gVars.switch = function() {};
+    gVars.delete = function() {};
+
+    prepareVariablesView();
+  });
+}
+
+function prepareVariablesView() {
+  gDebugger.addEventListener("Debugger:FetchedVariables", function test() {
+    gDebugger.removeEventListener("Debugger:FetchedVariables", test, false);
+    Services.tm.currentThread.dispatch({ run: function() {
+
+      testVariablesView();
+
+    }}, 0);
+  }, false);
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    content.document.querySelector("button"),
+    content.window);
+}
+
+function testVariablesView()
+{
+  executeSoon(function() {
+    addWatchExpressions(function() {
+      testEdit("\"xlerb\"", "xlerb", function() {
+        closeDebuggerAndFinish();
+      });
+    });
+  });
+}
+
+function addWatchExpressions(callback)
+{
+  waitForWatchExpressions(function() {
+    let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+    let scope = gVars._currHierarchy.get(label);
+
+    ok(scope, "There should be a wach expressions scope in the variables view");
+    is(scope._store.size, 1, "There should be 1 evaluation availalble");
+
+    let expr = scope.get("myVar.prop");
+    ok(expr, "The watch expression should be present in the scope");
+    is(expr.value, 42, "The value is correct.");
+
+    callback();
+  });
+
+  gWatch.addExpression("myVar.prop");
+  gDebugger.editor.focus();
+}
+
+function testEdit(string, expected, callback)
+{
+  let localScope = gDebugger.DebuggerView.Variables._list.querySelectorAll(".scope")[1],
+      localNodes = localScope.querySelector(".details").childNodes,
+      myVar = gVars.getItemForNode(localNodes[11]);
+
+  waitForProperties(function() {
+    let prop = myVar.get("prop");
+
+    is(prop.ownerView.name, "myVar",
+      "The right owner property name wasn't found.");
+    is(prop.name, "prop",
+      "The right property name wasn't found.");
+
+    is(prop.ownerView.value.type, "object",
+      "The right owner property value type wasn't found.");
+    is(prop.ownerView.value.class, "Object",
+      "The right owner property value class wasn't found.");
+
+    is(prop.name, "prop",
+      "The right property name wasn't found.");
+    is(prop.value, undefined,
+      "The right property value wasn't found.");
+    ok(prop.getter,
+      "The right property getter wasn't found.");
+    ok(prop.setter,
+      "The right property setter wasn't found.");
+
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      prop._target.querySelector(".dbg-variable-edit"),
+      gDebugger);
+
+    waitForElement(".element-value-input", true, function() {
+      waitForWatchExpressions(function() {
+        let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
+        let scope = gVars._currHierarchy.get(label);
+
+        let expr = scope.get("myVar.prop");
+        is(expr.value, expected, "The value is correct.");
+
+        callback();
+      });
+
+      write(string);
+      EventUtils.sendKey("RETURN", gDebugger);
+    });
+  });
+
+  myVar.expand();
+  gVars.clearHierarchy();
+}
+
+function waitForWatchExpressions(callback) {
+  gDebugger.addEventListener("Debugger:FetchedWatchExpressions", function onFetch() {
+    gDebugger.removeEventListener("Debugger:FetchedWatchExpressions", onFetch, false);
+    executeSoon(callback);
+  }, false);
+}
+
+function waitForProperties(callback) {
+  gDebugger.addEventListener("Debugger:FetchedProperties", function onFetch() {
+    gDebugger.removeEventListener("Debugger:FetchedProperties", onFetch, false);
+    executeSoon(callback);
+  }, false);
+}
+
+function waitForElement(selector, exists, callback)
+{
+  // Poll every few milliseconds until the element are retrieved.
+  let count = 0;
+  let intervalID = window.setInterval(function() {
+    info("count: " + count + " ");
+    if (++count > 50) {
+      ok(false, "Timed out while polling for the element.");
+      window.clearInterval(intervalID);
+      return closeDebuggerAndFinish();
+    }
+    if (!!gVars._list.querySelector(selector) != exists) {
+      return;
+    }
+    // We got the element, it's safe to callback.
+    window.clearInterval(intervalID);
+    callback();
+  }, 100);
+}
+
+function write(text) {
+  if (!text) {
+    EventUtils.sendKey("BACK_SPACE", gDebugger);
+    return;
+  }
+  for (let i = 0; i < text.length; i++) {
+    EventUtils.sendChar(text[i], gDebugger);
+  }
+}
+
+registerCleanupFunction(function() {
+  removeTab(gTab);
+  gPane = null;
+  gTab = null;
+  gDebugger = null;
+  gVars = null;
+  gWatch = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-data.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-data.js
@@ -16,16 +16,17 @@ var gVariable = null;
 function test()
 {
   debug_tab_pane(TAB1_URL, function(aTab, aDebuggee, aPane) {
     gTab = aTab;
     gPane = aPane;
     gDebugger = gPane.panelWin;
     gVariablesView = gDebugger.DebuggerView.Variables;
 
+    gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
     testVariablesView();
   });
 }
 
 function testVariablesView()
 {
   let arr = [
     42,
@@ -56,41 +57,45 @@ function testVariablesView()
     someProp3: undefined,
     someProp4: null,
     someProp5: arr,
     someProp6: obj,
     get someProp7() { return arr; },
     set someProp7(value) { arr[0] = value }
   };
 
+  gVariablesView.eval = function() {};
+  gVariablesView.switch = function() {};
+  gVariablesView.delete = function() {};
   gVariablesView.rawObject = test;
 
   testHierarchy();
   testHeader();
   testFirstLevelContents();
   testSecondLevelContents();
   testThirdLevelContents();
   testIntegrity(arr, obj);
 
-  gVariablesView.eval = function() {};
-  gVariablesView.switch = function() {};
-  gVariablesView.delete = function() {};
-
   let fooScope = gVariablesView.addScope("foo");
   let anonymousVar = fooScope.addVar();
 
   let anonymousScope = gVariablesView.addScope();
   let barVar = anonymousScope.addVar("bar");
   let bazProperty = barVar.addProperty("baz");
 
   testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
   testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty);
-  testClearHierarchy();
 
-  closeDebuggerAndFinish();
+  executeSoon(function() {
+    testKeyboardAccessibility(function() {
+      testClearHierarchy();
+
+      closeDebuggerAndFinish();
+    });
+  });
 }
 
 function testHierarchy() {
   is(gVariablesView._currHierarchy.size, 13,
     "There should be 1 scope, 1 var, 1 proto, 8 props, 1 getter and 1 setter.");
 
   gScope = gVariablesView._currHierarchy.get("");
   gVariable = gVariablesView._currHierarchy.get("[\"\"]");
@@ -577,21 +582,278 @@ function testPropertyInheritance(fooScop
   is(bazProperty.delete, gVariablesView.delete,
     "The delete property should persist from the view to all properties.");
   isnot(bazProperty.eval, bazProperty.switch,
     "The eval and switch functions got mixed up in the property.");
   isnot(bazProperty.switch, bazProperty.delete,
     "The eval and switch functions got mixed up in the property.");
 }
 
+function testKeyboardAccessibility(callback) {
+  gDebugger.DebuggerView.Filtering._doVariablesFocus();
+  gDebugger.DebuggerView.Variables.pageSize = 5;
+
+  is(gVariablesView.getFocusedItem().name, "someProp0",
+    "The someProp0 item should be focused.");
+
+  gVariablesView.focusNextItem();
+  is(gVariablesView.getFocusedItem().name, "someProp1",
+    "The someProp1 item should be focused.");
+
+  gVariablesView.focusPrevItem();
+  is(gVariablesView.getFocusedItem().name, "someProp0",
+    "The someProp0 item should be focused again.");
+
+
+  ok(!gVariablesView._list.querySelector(".element-value-input"),
+    "There shouldn't be a value input element created.");
+
+  EventUtils.synthesizeKey("VK_ENTER", {}, gDebugger);
+  waitForElement(".element-value-input", true, function() {
+
+    ok(gVariablesView._list.querySelector(".element-value-input"),
+      "There should be a value input element created.");
+
+    EventUtils.sendKey("ESCAPE", gDebugger);
+    waitForElement(".element-value-input", false, function() {
+
+      ok(!gVariablesView._list.querySelector(".element-value-input"),
+        "There shouldn't be a value input element anymore.");
+
+      ok(!gVariablesView._list.querySelector(".element-name-input"),
+        "There shouldn't be a name input element created.");
+
+      EventUtils.synthesizeKey("VK_ENTER", { shiftKey: true }, gDebugger);
+      waitForElement(".element-name-input", true, function() {
+
+        ok(gVariablesView._list.querySelector(".element-name-input"),
+          "There should be a name input element created.");
+
+        EventUtils.sendKey("ESCAPE", gDebugger);
+        waitForElement(".element-name-input", false, function() {
+
+          ok(!gVariablesView._list.querySelector(".element-name-input"),
+            "There shouldn't be a name input element anymore.");
+
+
+          EventUtils.sendKey("DOWN", gDebugger);
+          executeSoon(function() {
+            is(gVariablesView._parent.scrollTop, 0,
+              "The variables view shouldn't scroll when pressing the DOWN key.");
+
+            EventUtils.sendKey("UP", gDebugger);
+            executeSoon(function() {
+              is(gVariablesView._parent.scrollTop, 0,
+                "The variables view shouldn't scroll when pressing the UP key.");
+
+
+              EventUtils.sendKey("PAGE_DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp5",
+                "The someProp5 item should be focused now.");
+
+              EventUtils.sendKey("DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "0",
+                "The 0 item should be focused now.");
+
+              EventUtils.sendKey("END", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo item should be focused now.");
+
+              EventUtils.sendKey("DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo item should still be focused now.");
+
+              EventUtils.sendKey("RIGHT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "bar",
+                "The bar item should still be focused now.");
+
+              EventUtils.sendKey("PAGE_DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo item should still be focused now.");
+
+
+              EventUtils.sendKey("PAGE_UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The __proto__ item should be focused now.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "set",
+                "The set item should be focused now.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "get",
+                "The get item should be focused now.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "p8",
+                "The p8 item should be focused now.");
+
+              EventUtils.sendKey("HOME", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp0",
+                "The someProp0 item should be focused now.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp0",
+                "The someProp0 item should still be focused now.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp0",
+                "The someProp0 item should still be focused now.");
+
+              EventUtils.sendKey("PAGE_UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp0",
+                "The someProp0 item should still be focused now.");
+
+
+              for (let i = 0; i < 16; i++) {
+                // Advance to the first collapsed __proto__ property.
+                EventUtils.sendKey("RIGHT", gDebugger);
+              }
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The __proto__ item should be focused now.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The __proto__ item shouldn't be expanded yet.");
+
+              EventUtils.sendKey("RIGHT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The __proto__ item should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, true,
+                "The __proto__ item should be expanded now.");
+
+              for (let i = 0; i < 2; i++) {
+                // Advance to the fifth top-level someProp5 property.
+                EventUtils.sendKey("LEFT", gDebugger);
+              }
+              is(gVariablesView.getFocusedItem().name, "5",
+                "The fifth array item should be focused.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The fifth array item should not be expanded now.");
+
+              for (let i = 0; i < 6; i++) {
+                // Advance to the fifth top-level someProp5 property.
+                EventUtils.sendKey("UP", gDebugger);
+              }
+              is(gVariablesView.getFocusedItem().name, "someProp5",
+                "The someProp5 item should be focused now.");
+              is(gVariablesView.getFocusedItem().expanded, true,
+                "The someProp5 item should already be expanded.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp5",
+                "The someProp5 item should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The someProp5 item should not be expanded now.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp4",
+                "The someProp4 item should be focused.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp3",
+                "The someProp3 item should be focused.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp2",
+                "The someProp2 item should be focused.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp1",
+                "The someProp1 item should be focused.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp0",
+                "The someProp0 item should be focused.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "someProp0",
+                "The someProp0 item should still be focused.");
+
+              for (let i = 0; i < 32; i++) {
+                // Advance to the last property in this scope.
+                EventUtils.sendKey("DOWN", gDebugger);
+              }
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The top-level __proto__ item should be focused.");
+
+              EventUtils.sendKey("DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo scope should be focused now.");
+              is(gVariablesView.getFocusedItem().expanded, true,
+                "The foo scope should already be expanded yet.");
+
+              EventUtils.sendKey("LEFT", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo scope should be focused now.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The foo scope shouldn't be expanded now.");
+
+              EventUtils.sendKey("DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo scope should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, true,
+                "The foo scope should be expanded now.");
+
+              EventUtils.sendKey("DOWN", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "bar",
+                "The bar variable should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The bar variable shouldn't be expanded.");
+              is(gVariablesView.getFocusedItem().visible, true,
+                "The bar variable shouldn't be hidden.");
+
+              EventUtils.sendKey("BACK_SPACE", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "bar",
+                "The bar variable should still be focused.");
+              is(gVariablesView.getFocusedItem().expanded, false,
+                "The bar variable should still not be expanded.");
+              is(gVariablesView.getFocusedItem().visible, false,
+                "The bar variable should be hidden.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "foo",
+                "The foo scope should be focused.");
+
+              EventUtils.sendKey("UP", gDebugger);
+              is(gVariablesView.getFocusedItem().name, "__proto__",
+                "The top-level __proto__ item should be focused.");
+
+              executeSoon(callback);
+            });
+          });
+        });
+      });
+    });
+  });
+}
+
+function waitForElement(selector, exists, callback)
+{
+  // Poll every few milliseconds until the element are retrieved.
+  let count = 0;
+  let intervalID = window.setInterval(function() {
+    info("count: " + count + " ");
+    if (++count > 50) {
+      ok(false, "Timed out while polling for the element.");
+      window.clearInterval(intervalID);
+      return closeDebuggerAndFinish();
+    }
+    if (!!gVariablesView._list.querySelector(selector) != exists) {
+      return;
+    }
+    // We got the element, it's safe to callback.
+    window.clearInterval(intervalID);
+    callback();
+  }, 100);
+}
+
 function testClearHierarchy() {
   gVariablesView.clearHierarchy();
-  is (gVariablesView._prevHierarchy.size, 0,
+  ok(!gVariablesView._prevHierarchy.size,
     "The previous hierarchy should have been cleared.");
-  is (gVariablesView._currHierarchy.size, 0,
+  ok(!gVariablesView._currHierarchy.size,
     "The current hierarchy should have been cleared.");
 }
 
 registerCleanupFunction(function() {
   removeTab(gTab);
   gPane = null;
   gTab = null;
   gDebugger = null;
rename from browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
rename to browser/devtools/debugger/test/browser_dbg_propertyview-edit-value.js
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-edit-value.js
@@ -64,17 +64,17 @@ function testFrameEval() {
 }
 
 function testModification(aVar, aCallback, aNewValue, aNewResult) {
   function makeChangesAndExitInputMode() {
     EventUtils.sendString(aNewValue, gDebugger);
     EventUtils.sendKey("RETURN", gDebugger);
   }
 
-  EventUtils.sendMouseEvent({ type: "click" },
+  EventUtils.sendMouseEvent({ type: "mousedown" },
     aVar.querySelector(".value"),
     gDebugger);
 
   executeSoon(function() {
     ok(aVar.querySelector(".element-value-input"),
       "There should be an input element created.");
 
     let count = 0;
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
@@ -63,59 +63,59 @@ function testFrameEval() {
         "There should be 0 visible nodes in the watch expressions container");
 
       let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel");
       let scope = gVars._currHierarchy.get(label);
 
       ok(scope, "There should be a wach expressions scope in the variables view");
       is(scope._store.size, 5, "There should be 5 evaluations availalble");
 
-      is(scope.get("this")._isShown, true,
+      is(scope.get("this")._isContentVisible, true,
         "Should have the right visibility state for 'this'.");
       is(scope.get("this").target.querySelectorAll(".dbg-variable-delete").length, 1,
         "Should have the one close button visible for 'this'.");
       is(scope.get("this").name, "this",
         "Should have the right name for 'this'.");
       is(scope.get("this").value.type, "object",
         "Should have the right value type for 'this'.");
       is(scope.get("this").value.class, "Proxy",
         "Should have the right value type for 'this'.");
 
-      is(scope.get("ermahgerd")._isShown, true,
+      is(scope.get("ermahgerd")._isContentVisible, true,
         "Should have the right visibility state for 'ermahgerd'.");
       is(scope.get("ermahgerd").target.querySelectorAll(".dbg-variable-delete").length, 1,
         "Should have the one close button visible for 'ermahgerd'.");
       is(scope.get("ermahgerd").name, "ermahgerd",
         "Should have the right name for 'ermahgerd'.");
       is(scope.get("ermahgerd").value.type, "object",
         "Should have the right value type for 'ermahgerd'.");
       is(scope.get("ermahgerd").value.class, "Function",
         "Should have the right value type for 'ermahgerd'.");
 
-      is(scope.get("aArg")._isShown, true,
+      is(scope.get("aArg")._isContentVisible, true,
         "Should have the right visibility state for 'aArg'.");
       is(scope.get("aArg").target.querySelectorAll(".dbg-variable-delete").length, 1,
         "Should have the one close button visible for 'aArg'.");
       is(scope.get("aArg").name, "aArg",
         "Should have the right name for 'aArg'.");
       is(scope.get("aArg").value, undefined,
         "Should have the right value for 'aArg'.");
 
-      is(scope.get("document.title")._isShown, true,
+      is(scope.get("document.title")._isContentVisible, true,
         "Should have the right visibility state for 'document.title'.");
       is(scope.get("document.title").target.querySelectorAll(".dbg-variable-delete").length, 1,
         "Should have the one close button visible for 'document.title'.");
       is(scope.get("document.title").name, "document.title",
         "Should have the right name for 'document.title'.");
       is(scope.get("document.title").value, "42",
         "Should have the right value for 'document.title'.");
       is(typeof scope.get("document.title").value, "string",
         "Should have the right value type for 'document.title'.");
 
-      is(scope.get("document.title = 42")._isShown, true,
+      is(scope.get("document.title = 42")._isContentVisible, true,
         "Should have the right visibility state for 'document.title = 42'.");
       is(scope.get("document.title = 42").target.querySelectorAll(".dbg-variable-delete").length, 1,
         "Should have the one close button visible for 'document.title = 42'.");
       is(scope.get("document.title = 42").name, "document.title = 42",
         "Should have the right name for 'document.title = 42'.");
       is(scope.get("document.title = 42").value, 42,
         "Should have the right value for 'document.title = 42'.");
       is(typeof scope.get("document.title = 42").value, "number",
@@ -335,148 +335,148 @@ function test1(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 5, "There should be 5 evaluations availalble");
 
   is(gWatch._cache[0].target.inputNode.value, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.expression, "document.title = 43",
+  is(gWatch._cache[0].attachment.currentExpression, "document.title = 43",
     "The first textbox input value is not the correct one");
   is(gWatch._cache[1].target.inputNode.value, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.expression, "document.title",
+  is(gWatch._cache[1].attachment.currentExpression, "document.title",
     "The second textbox input value is not the correct one");
   is(gWatch._cache[2].target.inputNode.value, "aArg",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.expression, "aArg",
+  is(gWatch._cache[2].attachment.currentExpression, "aArg",
     "The third textbox input value is not the correct one");
   is(gWatch._cache[3].target.inputNode.value, "ermahgerd",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.expression, "ermahgerd",
+  is(gWatch._cache[3].attachment.currentExpression, "ermahgerd",
     "The fourth textbox input value is not the correct one");
   is(gWatch._cache[4].target.inputNode.value, "this",
     "The fifth textbox input value is not the correct one");
-  is(gWatch._cache[4].attachment.expression, "this",
+  is(gWatch._cache[4].attachment.currentExpression, "this",
     "The fifth textbox input value is not the correct one");
 }
 
 function test2(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 5,
     "There should be 5 hidden nodes in the watch expressions container");
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 5, "There should be 5 evaluations availalble");
 
   is(gWatch._cache[0].target.inputNode.value, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.expression, "document.title = 43",
+  is(gWatch._cache[0].attachment.currentExpression, "document.title = 43",
     "The first textbox input value is not the correct one");
   is(gWatch._cache[1].target.inputNode.value, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.expression, "document.title",
+  is(gWatch._cache[1].attachment.currentExpression, "document.title",
     "The second textbox input value is not the correct one");
   is(gWatch._cache[2].target.inputNode.value, "aArg = 44",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.expression, "aArg = 44",
+  is(gWatch._cache[2].attachment.currentExpression, "aArg = 44",
     "The third textbox input value is not the correct one");
   is(gWatch._cache[3].target.inputNode.value, "ermahgerd",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.expression, "ermahgerd",
+  is(gWatch._cache[3].attachment.currentExpression, "ermahgerd",
     "The fourth textbox input value is not the correct one");
   is(gWatch._cache[4].target.inputNode.value, "this",
     "The fifth textbox input value is not the correct one");
-  is(gWatch._cache[4].attachment.expression, "this",
+  is(gWatch._cache[4].attachment.currentExpression, "this",
     "The fifth textbox input value is not the correct one");
 }
 
 function test3(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 4,
     "There should be 4 hidden nodes in the watch expressions container");
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 4, "There should be 4 evaluations availalble");
 
   is(gWatch._cache[0].target.inputNode.value, "document.title = 43",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.expression, "document.title = 43",
+  is(gWatch._cache[0].attachment.currentExpression, "document.title = 43",
     "The first textbox input value is not the correct one");
   is(gWatch._cache[1].target.inputNode.value, "document.title",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.expression, "document.title",
+  is(gWatch._cache[1].attachment.currentExpression, "document.title",
     "The second textbox input value is not the correct one");
   is(gWatch._cache[2].target.inputNode.value, "ermahgerd",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.expression, "ermahgerd",
+  is(gWatch._cache[2].attachment.currentExpression, "ermahgerd",
     "The third textbox input value is not the correct one");
   is(gWatch._cache[3].target.inputNode.value, "this",
     "The fourth textbox input value is not the correct one");
-  is(gWatch._cache[3].attachment.expression, "this",
+  is(gWatch._cache[3].attachment.currentExpression, "this",
     "The fourth textbox input value is not the correct one");
 }
 
 function test4(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 3,
     "There should be 3 hidden nodes in the watch expressions container");
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 3, "There should be 3 evaluations availalble");
 
   is(gWatch._cache[0].target.inputNode.value, "document.title",
     "The first textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.expression, "document.title",
+  is(gWatch._cache[0].attachment.currentExpression, "document.title",
     "The first textbox input value is not the correct one");
   is(gWatch._cache[1].target.inputNode.value, "ermahgerd",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.expression, "ermahgerd",
+  is(gWatch._cache[1].attachment.currentExpression, "ermahgerd",
     "The second textbox input value is not the correct one");
   is(gWatch._cache[2].target.inputNode.value, "this",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[2].attachment.expression, "this",
+  is(gWatch._cache[2].attachment.currentExpression, "this",
     "The third textbox input value is not the correct one");
 }
 
 function test5(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 2,
     "There should be 2 hidden nodes in the watch expressions container");
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 2, "There should be 2 evaluations availalble");
 
   is(gWatch._cache[0].target.inputNode.value, "ermahgerd",
     "The second textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.expression, "ermahgerd",
+  is(gWatch._cache[0].attachment.currentExpression, "ermahgerd",
     "The second textbox input value is not the correct one");
   is(gWatch._cache[1].target.inputNode.value, "this",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[1].attachment.expression, "this",
+  is(gWatch._cache[1].attachment.currentExpression, "this",
     "The third textbox input value is not the correct one");
 }
 
 function test6(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 1,
     "There should be 1 hidden nodes in the watch expressions container");
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
 
   ok(scope, "There should be a wach expressions scope in the variables view");
   is(scope._store.size, 1, "There should be 1 evaluation availalble");
 
   is(gWatch._cache[0].target.inputNode.value, "ermahgerd",
     "The third textbox input value is not the correct one");
-  is(gWatch._cache[0].attachment.expression, "ermahgerd",
+  is(gWatch._cache[0].attachment.currentExpression, "ermahgerd",
     "The third textbox input value is not the correct one");
 }
 
 function test7(scope) {
   is(gWatch._container._parent.querySelectorAll(".dbg-expression[hidden=true]").length, 0,
     "There should be 0 hidden nodes in the watch expressions container");
   is(gWatch._container._parent.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0,
     "There should be 0 visible nodes in the watch expressions container");
--- a/browser/devtools/debugger/test/browser_dbg_watch-expressions.html
+++ b/browser/devtools/debugger/test/browser_dbg_watch-expressions.html
@@ -1,23 +1,27 @@
 <!DOCTYPE HTML>
 <html>
   <head>
     <meta charset='utf-8'/>
     <title>Browser Debugger Watch Expressions Test</title>
     <!-- Any copyright is dedicated to the Public Domain.
          http://creativecommons.org/publicdomain/zero/1.0/ -->
     <script type="text/javascript">
+      function test() {
+        ermahgerd.call({ canada: new String("eh") });
+      }
       function ermahgerd(aArg) {
         var t = document.title;
         debugger;
         (function() {
           var a = undefined;
           debugger;
           var a = {};
           debugger;
         }("sensational"));
       }
     </script>
+
   </head>
   <body>
   </body>
 </html>
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -28,16 +28,17 @@ browser.jar:
     content/browser/debugger-toolbar.js           (debugger/debugger-toolbar.js)
     content/browser/debugger-panes.js             (debugger/debugger-panes.js)
     content/browser/profiler.xul                  (profiler/profiler.xul)
     content/browser/profiler.css                  (profiler/profiler.css)
     content/browser/devtools/cleopatra.html       (profiler/cleopatra/cleopatra.html)
     content/browser/devtools/profiler/cleopatra/css/ui.css              (profiler/cleopatra/css/ui.css)
     content/browser/devtools/profiler/cleopatra/css/tree.css            (profiler/cleopatra/css/tree.css)
     content/browser/devtools/profiler/cleopatra/css/devtools.css        (profiler/cleopatra/css/devtools.css)
+    content/browser/devtools/profiler/cleopatra/js/strings.js           (profiler/cleopatra/js/strings.js)
     content/browser/devtools/profiler/cleopatra/js/parser.js            (profiler/cleopatra/js/parser.js)
     content/browser/devtools/profiler/cleopatra/js/parserWorker.js      (profiler/cleopatra/js/parserWorker.js)
     content/browser/devtools/profiler/cleopatra/js/tree.js              (profiler/cleopatra/js/tree.js)
     content/browser/devtools/profiler/cleopatra/js/ui.js                (profiler/cleopatra/js/ui.js)
     content/browser/devtools/profiler/cleopatra/js/ProgressReporter.js  (profiler/cleopatra/js/ProgressReporter.js)
     content/browser/devtools/profiler/cleopatra/js/devtools.js          (profiler/cleopatra/js/devtools.js)
     content/browser/devtools/profiler/cleopatra/images/circlearrow.svg  (profiler/cleopatra/images/circlearrow.svg)
     content/browser/devtools/profiler/cleopatra/images/noise.png        (profiler/cleopatra/images/noise.png)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/profiler/ProfilerHelpers.jsm
@@ -0,0 +1,43 @@
+/* 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/. */
+
+"use strict";
+
+const Cu = Components.utils;
+const ProfilerProps = "chrome://browser/locale/devtools/profiler.properties";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = ["L10N"];
+
+/**
+ * Localization helper methods.
+ */
+let L10N = {
+  /**
+   * Returns a simple localized string.
+   *
+   * @param string name
+   * @return string
+   */
+  getStr: function L10N_getStr(name) {
+    return this.stringBundle.GetStringFromName(name);
+  },
+
+  /**
+   * Returns formatted localized string.
+   *
+   * @param string name
+   * @param array params
+   * @return string
+   */
+  getFormatStr: function L10N_getFormatStr(name, params) {
+    return this.stringBundle.formatStringFromName(name, params, params.length);
+  }
+};
+
+XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function () {
+  return Services.strings.createBundle(ProfilerProps);
+});
\ No newline at end of file
--- a/browser/devtools/profiler/ProfilerPanel.jsm
+++ b/browser/devtools/profiler/ProfilerPanel.jsm
@@ -2,16 +2,17 @@
  * 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 Cu = Components.utils;
 
 Cu.import("resource:///modules/devtools/ProfilerController.jsm");
+Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
 Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
 
 XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
   Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
@@ -244,22 +245,21 @@ ProfilerPanel.prototype = {
     let uid  = ++this._uid;
     let list = this.document.getElementById("profiles-list");
     let item = this.document.createElement("li");
     let wrap = this.document.createElement("h1");
 
     item.setAttribute("id", "profile-" + uid);
     item.setAttribute("data-uid", uid);
     item.addEventListener("click", function (ev) {
-      let uid = parseInt(ev.target.getAttribute("data-uid"), 10);
       this.switchToProfile(this.profiles.get(uid));
     }.bind(this), false);
 
     wrap.className = "profile-name";
-    wrap.textContent = "Profile " + uid;
+    wrap.textContent = L10N.getFormatStr("profiler.profileName", [uid]);
 
     item.appendChild(wrap);
     list.appendChild(item);
 
     let profile = new ProfileUI(uid, this);
     this.profiles.set(uid, profile);
 
     this.emit("profileCreated", uid);
--- a/browser/devtools/profiler/cleopatra/cleopatra.html
+++ b/browser/devtools/profiler/cleopatra/cleopatra.html
@@ -7,16 +7,17 @@
   <head>
     <title>Firefox Profiler (SPS)</title>
     <meta charset="utf-8">
 
     <link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/ui.css">
     <link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/tree.css">
     <link rel="stylesheet" type="text/css" href="profiler/cleopatra/css/devtools.css">
 
+    <script src="profiler/cleopatra/js/strings.js"></script>
     <script src="profiler/cleopatra/js/parser.js"></script>
     <script src="profiler/cleopatra/js/tree.js"></script>
     <script src="profiler/cleopatra/js/ui.js"></script>
     <script src="profiler/cleopatra/js/ProgressReporter.js"></script>
     <script src="profiler/cleopatra/js/devtools.js"></script>
   </head>
 
   <body onload="notifyParent('loaded');">
--- a/browser/devtools/profiler/cleopatra/js/devtools.js
+++ b/browser/devtools/profiler/cleopatra/js/devtools.js
@@ -80,34 +80,39 @@ function initUI() {
 
   gMainArea = document.createElement("div");
   gMainArea.id = "mainarea";
 
   container.appendChild(gMainArea);
   document.body.appendChild(container);
 
   var startButton = document.createElement("button");
-  startButton.innerHTML = "Start";
+  startButton.innerHTML = gStrings.getStr("profiler.start");
   startButton.addEventListener("click", function (event) {
     event.target.setAttribute("disabled", true);
     notifyParent("start");
   }, false);
 
   var stopButton = document.createElement("button");
-  stopButton.innerHTML = "Stop";
+  stopButton.innerHTML = gStrings.getStr("profiler.stop");
   stopButton.addEventListener("click", function (event) {
     event.target.setAttribute("disabled", true);
     notifyParent("stop");
   }, false);
 
   var controlPane = document.createElement("div");
+  var startProfiling = gStrings.getFormatStr("profiler.startProfiling",
+    ["<span class='btn'></span>"]);
+  var stopProfiling = gStrings.getFormatStr("profiler.stopProfiling",
+    ["<span class='btn'></span>"]);
+
   controlPane.className = "controlPane";
   controlPane.innerHTML =
-    "<p id='startWrapper'>Click <span class='btn'></span> to start profiling.</p>" +
-    "<p id='stopWrapper'>Click <span class='btn'></span> to stop profiling.</p>";
+    "<p id='startWrapper'>" + startProfiling + "</p>" +
+    "<p id='stopWrapper'>" + stopProfiling + "</p>";
 
   controlPane.querySelector("#startWrapper > span.btn").appendChild(startButton);
   controlPane.querySelector("#stopWrapper > span.btn").appendChild(stopButton);
 
   gMainArea.appendChild(controlPane);
 }
 
 /**
@@ -148,19 +153,19 @@ function enterFinishedProfileUI() {
 
   var tree = document.createElement("div");
   tree.className = "treeContainer";
   tree.style.width = "100%";
   tree.style.height = "100%";
 
   gTreeManager = new ProfileTreeManager();
   gTreeManager.treeView.setColumns([
-    { name: "sampleCount", title: "Running time" },
-    { name: "selfSampleCount", title: "Self" },
-    { name: "resource", title: "" },
+    { name: "sampleCount", title: gStrings["Running Time"] },
+    { name: "selfSampleCount", title: gStrings["Self"] },
+    { name: "resource", title: "" }
   ]);
 
   currRow = pane.insertRow(rowIndex++);
   currRow.style.height = "100%";
 
   var cell = currRow.insertCell(0);
   cell.appendChild(tree);
   tree.appendChild(gTreeManager.getContainer());
new file mode 100644
--- /dev/null
+++ b/browser/devtools/profiler/cleopatra/js/strings.js
@@ -0,0 +1,23 @@
+const Cu = Components.utils;
+Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
+
+/**
+ * Shortcuts for the L10N helper functions. Used in Cleopatra.
+ */
+var gStrings = {
+  // This strings are here so that Cleopatra code could use a simple object
+  // lookup. This makes it easier to merge upstream changes.
+  "Complete Profile": L10N.getStr("profiler.completeProfile"),
+  "Sample Range": L10N.getStr("profiler.sampleRange"),
+  "Running Time": L10N.getStr("profiler.runningTime"),
+  "Self": L10N.getStr("profiler.self"),
+  "Symbol Name": L10N.getStr("profiler.symbolName"),
+
+  getStr: function (name) {
+    return L10N.getStr(name);
+  },
+
+  getFormatStr: function (name, params) {
+    return L10N.getFormatStr(name, params);
+  }
+};
\ No newline at end of file
--- a/browser/devtools/profiler/cleopatra/js/ui.js
+++ b/browser/devtools/profiler/cleopatra/js/ui.js
@@ -148,20 +148,20 @@ FileList.prototype = {
 
 function treeObjSort(a, b) {
   return b.counter - a.counter;
 }
 
 function ProfileTreeManager() {
   this.treeView = new TreeView();
   this.treeView.setColumns([
-    { name: "sampleCount", title: "Running time" },
-    { name: "selfSampleCount", title: "Self" },
+    { name: "sampleCount", title: gStrings["Running Time"] },
+    { name: "selfSampleCount", title: gStrings["Self"] },
     { name: "resource", title: "" },
-    { name: "symbolName", title: "Symbol Name"}
+    { name: "symbolName", title: gStrings["Symbol Name"] }
   ]);
   var self = this;
   this.treeView.addEventListener("select", function (frameData) {
     self.highlightFrame(frameData);
     if (window.comparator_setSelection) {
       window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), frameData);
     }
   });
@@ -747,17 +747,17 @@ RangeSelector.prototype = {
     if (this._transientRestrictionEnteringAffordance) {
       this._transientRestrictionEnteringAffordance.discard();
     }
   },
   _finishSelection: function RangeSelector__finishSelection(start, end) {
     var newFilterChain = gSampleFilters.concat({ type: "RangeSampleFilter", start: start, end: end });
     var self = this;
     self._transientRestrictionEnteringAffordance = gBreadcrumbTrail.add({
-      title: "Sample Range [" + start + ", " + (end + 1) + "]",
+      title: gStrings["Sample Range"] + " [" + start + ", " + (end + 1) + "]",
       enterCallback: function () {
         gSampleFilters = newFilterChain;
         self.collapseHistogramSelection();
         filtersChanged();
       }
     });
   },
   finishHistogramSelection: function RangeSelector_finishHistgramSelection(isSomethingSelected) {
@@ -1769,17 +1769,17 @@ function enterFinishedProfileUI() {
   //currRow = finishedProfilePane.insertRow(4);
   treeContainerDiv.appendChild(gPluginView.getContainer());
 
   gMainArea.appendChild(finishedProfilePaneBackgroundCover);
   gMainArea.appendChild(finishedProfilePane);
 
   var currentBreadcrumb = gSampleFilters;
   gBreadcrumbTrail.add({
-    title: "Complete Profile",
+    title: gStrings["Complete Profile"],
     enterCallback: function () {
       gSampleFilters = [];
       filtersChanged();
     }
   });
   if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
     gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
     viewOptionsChanged();
--- a/browser/devtools/profiler/profiler.xul
+++ b/browser/devtools/profiler/profiler.xul
@@ -5,16 +5,21 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css"?>
 <?xml-stylesheet href="chrome://browser/content/splitview.css"?>
 <?xml-stylesheet href="chrome://browser/content/profiler.css"?>
 
+<!DOCTYPE window [
+<!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
+  %profilerDTD;
+]>
+
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <box flex="1" id="profiler-chrome" class="splitview-root">
     <box class="splitview-controller" width="180px">
       <box class="splitview-main"></box>
 
       <box class="splitview-nav-container">
         <ol class="splitview-nav" id="profiles-list">
           <!-- Example:
@@ -24,17 +29,17 @@
           -->
         </ol>
 
         <spacer flex="1"/>
 
         <toolbar class="devtools-toolbar" mode="full">
           <toolbarbutton id="profiler-create"
                          class="devtools-toolbarbutton"
-                         label="New"
+                         label="&profilerNew.label;"
                          disabled="true"/>
         </toolbar>
       </box> <!-- splitview-nav-container -->
     </box> <!-- splitview-controller -->
 
     <box flex="1">
       <vbox flex="1" id="profiler-report">
         <!-- Example:
--- a/browser/devtools/shared/VariablesView.jsm
+++ b/browser/devtools/shared/VariablesView.jsm
@@ -1,24 +1,31 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 "use strict";
 
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const LAZY_EMPTY_DELAY = 150; // ms
 const LAZY_EXPAND_DELAY = 50; // ms
 const LAZY_APPEND_DELAY = 100; // ms
 const LAZY_APPEND_BATCH = 100; // nodes
+const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
+const PAGE_SIZE_MAX_JUMPS = 30;
 const SEARCH_ACTION_MAX_DELAY = 1000; // ms
+const ELEMENT_INPUT_DEFAULT_WIDTH = 100; // px
+const ELEMENT_INPUT_EXTRA_SPACE = 4; // px
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this,
   "WebConsoleUtils", "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["VariablesView", "create"];
 
 /**
  * Debugger localization strings.
@@ -35,27 +42,31 @@ const STR = Services.strings.createBundl
  * provide a "switch" function. To handle deleting variables or properties,
  * provide a "delete" function.
  *
  * @param nsIDOMNode aParentNode
  *        The parent node to hold this view.
  */
 this.VariablesView = function VariablesView(aParentNode) {
   this._store = new Map();
+  this._itemsByElement = new WeakMap();
   this._prevHierarchy = new Map();
   this._currHierarchy = new Map();
 
   this._parent = aParentNode;
   this._appendEmptyNotice();
 
   this._onSearchboxInput = this._onSearchboxInput.bind(this);
   this._onSearchboxKeyPress = this._onSearchboxKeyPress.bind(this);
+  this._onViewKeyPress = this._onViewKeyPress.bind(this);
 
   // Create an internal list container.
-  this._list = this.document.createElement("vbox");
+  this._list = this.document.createElement("scrollbox");
+  this._list.setAttribute("orient", "vertical");
+  this._list.addEventListener("keypress", this._onViewKeyPress, false);
   this._parent.appendChild(this._list);
 };
 
 VariablesView.prototype = {
   /**
    * Helper setter for populating this container with a raw object.
    *
    * @param object aData
@@ -77,16 +88,17 @@ VariablesView.prototype = {
    */
   addScope: function VV_addScope(aName = "") {
     this._removeEmptyNotice();
     this._toggleSearchVisibility(true);
 
     let scope = new Scope(this, aName);
     this._store.set(scope.id, scope);
     this._currHierarchy.set(aName, scope);
+    this._itemsByElement.set(scope._target, scope);
     scope.header = !!aName;
     return scope;
   },
 
   /**
    * Removes all items from this container.
    *
    * @param number aTimeout [optional]
@@ -107,16 +119,18 @@ VariablesView.prototype = {
     let list = this._list;
     let firstChild;
 
     while (firstChild = list.firstChild) {
       list.removeChild(firstChild);
     }
 
     this._store = new Map();
+    this._itemsByElement = new WeakMap();
+
     this._appendEmptyNotice();
     this._toggleSearchVisibility(false);
   },
 
   /**
    * Emptying this container and rebuilding it immediately afterwards would
    * result in a brief redraw flicker, because the previously expanded nodes
    * may get asynchronously re-expanded, after fetching the prototype and
@@ -128,22 +142,28 @@ VariablesView.prototype = {
    * data fetching delay. In the meantime, any operations can be executed
    * normally.
    *
    * @see VariablesView.empty
    * @see VariablesView.commitHierarchy
    */
   _emptySoon: function VV__emptySoon(aTimeout) {
     let prevList = this._list;
-    let currList = this._list = this.document.createElement("vbox");
+    let currList = this._list = this.document.createElement("scrollbox");
+
     this._store = new Map();
+    this._itemsByElement = new WeakMap();
 
     this._emptyTimeout = this.window.setTimeout(function() {
       this._emptyTimeout = null;
 
+      prevList.removeEventListener("keypress", this._onViewKeyPress, false);
+      currList.addEventListener("keypress", this._onViewKeyPress, false);
+      currList.setAttribute("orient", "vertical");
+
       this._parent.removeChild(prevList);
       this._parent.appendChild(currList);
 
       if (!this._store.size) {
         this._appendEmptyNotice();
         this._toggleSearchVisibility(false);
       }
     }.bind(this), aTimeout);
@@ -202,16 +222,26 @@ VariablesView.prototype = {
    * function is provided, in order to change the variable or property's name.
    *
    * This flag is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
    */
   editableNameTooltip: STR.GetStringFromName("variablesEditableNameTooltip"),
 
   /**
+   * The tooltip text shown on a variable or property's edit button if an
+   * |eval| function is provided and a getter/setter descriptor is present,
+   * in order to change the variable or property to a plain value.
+   *
+   * This flag is applied recursively onto each scope in this view and
+   * affects only the child nodes when they're created.
+   */
+  editButtonTooltip: STR.GetStringFromName("variablesEditButtonTooltip"),
+
+  /**
    * The tooltip text shown on a variable or property's delete button if a
    * |delete| function is provided, in order to delete the variable or property.
    *
    * This flag is applied recursively onto each scope in this view and
    * affects only the child nodes when they're created.
    */
   deleteButtonTooltip: STR.GetStringFromName("variablesCloseButtonTooltip"),
 
@@ -463,62 +493,338 @@ VariablesView.prototype = {
     }
   },
 
   /**
    * Expands the first search results in this container.
    */
   expandFirstSearchResults: function VV_expandFirstSearchResults() {
     for (let [, scope] of this._store) {
-      for (let [, variable] of scope._store) {
-        if (variable._isMatch) {
-          variable.expand();
-          break;
-        }
+      let match = scope._firstMatch;
+      if (match) {
+        match.expand();
+      }
+    }
+  },
+
+  /**
+   * Focuses the first visible variable or property in this container.
+   */
+  focusFirstVisibleNode: function VV_focusFirstVisibleNode() {
+    let property, variable, scope;
+
+    for (let [, item] of this._currHierarchy) {
+      if (!item.focusable) {
+        continue;
+      }
+      if (item instanceof Property) {
+        property = item;
+        break;
+      } else if (item instanceof Variable) {
+        variable = item;
+        break;
+      } else if (item instanceof Scope) {
+        scope = item;
+        break;
       }
     }
+    if (scope) {
+      this._focusItem(scope);
+    } else if (variable) {
+      this._focusItem(variable);
+    } else if (property) {
+      this._focusItem(property);
+    }
+    this._parent.scrollTop = 0;
+    this._parent.scrollLeft = 0;
+  },
+
+  /**
+   * Focuses the last visible variable or property in this container.
+   */
+  focusLastVisibleNode: function VV_focusLastVisibleNode() {
+    let property, variable, scope;
+
+    for (let [, item] of this._currHierarchy) {
+      if (!item.focusable) {
+        continue;
+      }
+      if (item instanceof Property) {
+        property = item;
+      } else if (item instanceof Variable) {
+        variable = item;
+      } else if (item instanceof Scope) {
+        scope = item;
+      }
+    }
+    if (property && (!variable || property.isDescendantOf(variable))) {
+      this._focusItem(property);
+    } else if (variable && (!scope || variable.isDescendantOf(scope))) {
+      this._focusItem(variable);
+    } else if (scope) {
+      this._focusItem(scope);
+      this._parent.scrollTop = this._parent.scrollHeight;
+      this._parent.scrollLeft = 0;
+    }
   },
 
   /**
    * Searches for the scope in this container displayed by the specified node.
    *
    * @param nsIDOMNode aNode
    *        The node to search for.
    * @return Scope
    *         The matched scope, or null if nothing is found.
    */
   getScopeForNode: function VV_getScopeForNode(aNode) {
-    for (let [, scope] of this._store) {
-      if (scope._target == aNode) {
-        return scope;
-      }
+    let item = this._itemsByElement.get(aNode);
+    if (item && !(item instanceof Variable) && !(item instanceof Property)) {
+      return item;
     }
     return null;
   },
 
   /**
-   * Recursively searches all the scopes for the variable or property
+   * Recursively searches this container for the scope, variable or property
    * displayed by the specified node.
    *
    * @param nsIDOMNode aNode
    *        The node to search for.
-   * @return Variable | Property
-   *         The matched variable or property, or null if nothing is found.
+   * @return Scope | Variable | Property
+   *         The matched scope, variable or property, or null if nothing is found.
+   */
+  getItemForNode: function VV_getItemForNode(aNode) {
+    return this._itemsByElement.get(aNode);
+  },
+
+  /**
+   * Gets the currently focused scope, variable or property in this view.
+   *
+   * @return Scope | Variable | Property
+   *         The focused scope, variable or property, or null if nothing is found.
+   */
+  getFocusedItem: function VV_getFocusedItem() {
+    let focused = this.document.commandDispatcher.focusedElement;
+    return this.getItemForNode(focused);
+  },
+
+  /**
+   * Focuses the next scope, variable or property in this view.
+   * @see VariablesView.prototype._focusChange
+   */
+  focusNextItem: function VV_focusNextItem(aMaintainViewFocusedFlag)
+    this._focusChange("advanceFocus", aMaintainViewFocusedFlag),
+
+  /**
+   * Focuses the previous scope, variable or property in this view.
+   * @see VariablesView.prototype._focusChange
+   */
+  focusPrevItem: function VV_focusPrevItem(aMaintainViewFocusedFlag)
+    this._focusChange("rewindFocus", aMaintainViewFocusedFlag),
+
+  /**
+   * Focuses the next or previous scope, variable or property in this view.
+   *
+   * @param string aDirection
+   *        Either "advanceFocus" or "rewindFocus".
+   * @param boolean aMaintainViewFocusedFlag
+   *        True too keep this view focused if the element is out of bounds.
+   * @return boolean
+   *         True if the focus went out of bounds and the first or last element
+   *         in this view was focused instead.
+   */
+  _focusChange: function VV__changeFocus(aDirection, aMaintainViewFocusedFlag) {
+    let commandDispatcher = this.document.commandDispatcher;
+    let item;
+
+    do {
+      commandDispatcher[aDirection]();
+
+      // If maintaining this view focused is not mandatory, a simple
+      // "advanceFocus" or "rewindFocus" command dispatch is sufficient.
+      if (!aMaintainViewFocusedFlag) {
+        return false;
+      }
+
+      // Make sure the newly focused target is a part of this view.
+      item = this.getFocusedItem();
+      if (!item) {
+        if (aDirection == "advanceFocus") {
+          this.focusLastVisibleNode();
+        } else {
+          this.focusFirstVisibleNode();
+        }
+        // Focus went out of bounds so the first or last element in this view
+        // was focused instead.
+        return true;
+      }
+    } while (!item.focusable);
+
+    // Focus remained within bounds.
+    return false;
+  },
+
+  /**
+   * Focuses a scope, variable or property and makes sure it's visible.
+   *
+   * @param aItem Scope | Variable | Property
+   *        The item to focus.
+   * @param boolean aCollapseFlag
+   *        True if the focused item should also be collapsed.
+   * @return boolean
+   *         True if the item was successfully focused.
+   */
+  _focusItem: function VV__focusItem(aItem, aCollapseFlag) {
+    if (!aItem.focusable) {
+      return false;
+    }
+    if (aCollapseFlag) {
+      aItem.collapse();
+    }
+    aItem._target.focus();
+
+    let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
+    boxObject.ensureElementIsVisible(aItem._title);
+    boxObject.scrollBy(-this._list.clientWidth, 0);
+    return true;
+  },
+
+  /**
+   * Listener handling a key press event on the view.
    */
-  getVariableOrPropertyForNode: function VV_getVariableOrPropertyForNode(aNode) {
-    for (let [, scope] of this._store) {
-      let match = scope.find(aNode);
-      if (match) {
-        return match;
-      }
+  _onViewKeyPress: function VV__onViewKeyPress(e) {
+    let item = this.getFocusedItem();
+
+    switch (e.keyCode) {
+      case e.DOM_VK_UP:
+      case e.DOM_VK_DOWN:
+      case e.DOM_VK_LEFT:
+      case e.DOM_VK_RIGHT:
+      case e.DOM_VK_PAGE_UP:
+      case e.DOM_VK_PAGE_DOWN:
+      case e.DOM_VK_HOME:
+      case e.DOM_VK_END:
+        // Prevent scrolling when pressing navigation keys.
+        e.preventDefault();
+        e.stopPropagation();
     }
-    return null;
+
+    switch (e.keyCode) {
+      case e.DOM_VK_UP:
+        // Always rewind focus.
+        this.focusPrevItem(true);
+        return;
+
+      case e.DOM_VK_DOWN:
+        // Only expand scopes before advancing focus.
+        if (!(item instanceof Variable) &&
+            !(item instanceof Property) &&
+            !item._isExpanded && item._isArrowVisible) {
+          item.expand();
+        } else {
+          this.focusNextItem(true);
+        }
+        return;
+
+      case e.DOM_VK_LEFT:
+        // If this is a collapsed or un-expandable item that has an expandable
+        // variable or property parent, collapse and focus the owner view.
+        if (!item._isExpanded || !item._isArrowVisible) {
+          let ownerView = item.ownerView;
+          if ((ownerView instanceof Variable ||
+               ownerView instanceof Property) &&
+               ownerView._isExpanded && ownerView._isArrowVisible) {
+            if (this._focusItem(ownerView, true)) {
+              return;
+            }
+          }
+        }
+        // Collapse scopes, variables and properties before rewinding focus.
+        if (item._isExpanded && item._isArrowVisible) {
+          item.collapse();
+        } else {
+          this.focusPrevItem(true);
+        }
+        return;
+
+      case e.DOM_VK_RIGHT:
+        // Expand scopes, variables and properties before advancing focus.
+        if (!item._isExpanded && item._isArrowVisible) {
+          item.expand();
+        } else {
+          this.focusNextItem(true);
+        }
+        return;
+
+      case e.DOM_VK_PAGE_UP:
+        // Rewind a certain number of elements based on the container height.
+        var jumps = this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
+          PAGE_SIZE_SCROLL_HEIGHT_RATIO),
+          PAGE_SIZE_MAX_JUMPS);
+
+        while (jumps--) {
+          if (this.focusPrevItem(true)) {
+            return;
+          }
+        }
+        return;
+
+      case e.DOM_VK_PAGE_DOWN:
+        // Advance a certain number of elements based on the container height.
+        var jumps = this.pageSize || Math.min(Math.floor(this._list.scrollHeight /
+          PAGE_SIZE_SCROLL_HEIGHT_RATIO),
+          PAGE_SIZE_MAX_JUMPS);
+
+        while (jumps--) {
+          if (this.focusNextItem(true)) {
+            return;
+          }
+        }
+        return;
+
+      case e.DOM_VK_HOME:
+        this.focusFirstVisibleNode();
+        return;
+
+      case e.DOM_VK_END:
+        this.focusLastVisibleNode();
+        return;
+
+      case e.DOM_VK_RETURN:
+      case e.DOM_VK_ENTER:
+        // Start editing the value or name of the variable or property.
+        if (item instanceof Variable ||
+            item instanceof Property) {
+          if (e.metaKey || e.altKey || e.shiftKey) {
+            item._activateNameInput();
+          } else {
+            item._activateValueInput();
+          }
+        }
+        return;
+
+      case e.DOM_VK_DELETE:
+      case e.DOM_VK_BACK_SPACE:
+        // Delete the variable or property if allowed.
+        if (item instanceof Variable ||
+            item instanceof Property) {
+          item._onDelete(e);
+        }
+        return;
+    }
   },
 
   /**
+   * The number of elements in this container to jump when Page Up or Page Down
+   * keys are pressed. If falsy, then the page size will be based on the
+   * container height.
+   */
+  pageSize: 0,
+
+  /**
    * Sets the text displayed in this container when there are no available items.
    * @param string aValue
    */
   set emptyText(aValue) {
     if (this._emptyTextNode) {
       this._emptyTextNode.setAttribute("value", aValue);
     }
     this._emptyTextValue = aValue;
@@ -586,43 +892,170 @@ VariablesView.prototype = {
   _searchboxNode: null,
   _searchboxContainer: null,
   _searchboxPlaceholder: "",
   _emptyTextNode: null,
   _emptyTextValue: ""
 };
 
 /**
+ * Generates the string evaluated when performing simple value changes.
+ *
+ * @param Variable | Property aItem
+ *        The current variable or property.
+ * @param string aCurrentString
+ *        The trimmed user inputted string.
+ * @return string
+ *         The string to be evaluated.
+ */
+VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString) {
+  return aItem._symbolicName + "=" + aCurrentString;
+};
+
+/**
+ * Generates the string evaluated when overriding getters and setters with
+ * plain values.
+ *
+ * @param Property aItem
+ *        The current getter or setter property.
+ * @param string aCurrentString
+ *        The trimmed user inputted string.
+ * @return string
+ *         The string to be evaluated.
+ */
+VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString) {
+  let property = "\"" + aItem._nameString + "\"";
+  let parent = aItem.ownerView._symbolicName || "this";
+
+  return "Object.defineProperty(" + parent + "," + property + "," +
+    "{ value: " + aCurrentString +
+    ", enumerable: " + parent + ".propertyIsEnumerable(" + property + ")" +
+    ", configurable: true" +
+    ", writable: true" +
+    "})";
+};
+
+/**
+ * Generates the string evaluated when performing getters and setters changes.
+ *
+ * @param Property aItem
+ *        The current getter or setter property.
+ * @param string aCurrentString
+ *        The trimmed user inputted string.
+ * @return string
+ *         The string to be evaluated.
+ */
+VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString) {
+  let type = aItem._nameString;
+  let propertyObject = aItem.ownerView;
+  let parentObject = propertyObject.ownerView;
+  let property = "\"" + propertyObject._nameString + "\"";
+  let parent = parentObject._symbolicName || "this";
+
+  switch (aCurrentString) {
+    case "":
+    case "null":
+    case "undefined":
+      let mirrorType = type == "get" ? "set" : "get";
+      let mirrorLookup = type == "get" ? "__lookupSetter__" : "__lookupGetter__";
+
+      // If the parent object will end up without any getter or setter,
+      // morph it into a plain value.
+      if ((type == "set" && propertyObject.getter.type == "undefined") ||
+          (type == "get" && propertyObject.setter.type == "undefined")) {
+        return VariablesView.overrideValueEvalMacro(propertyObject, "undefined");
+      }
+
+      // Construct and return the getter/setter removal evaluation string.
+      // e.g: Object.defineProperty(foo, "bar", {
+      //   get: foo.__lookupGetter__("bar"),
+      //   set: undefined,
+      //   enumerable: true,
+      //   configurable: true
+      // })
+      return "Object.defineProperty(" + parent + "," + property + "," +
+        "{" + mirrorType + ":" + parent + "." + mirrorLookup + "(" + property + ")" +
+        "," + type + ":" + undefined +
+        ", enumerable: " + parent + ".propertyIsEnumerable(" + property + ")" +
+        ", configurable: true" +
+        "})";
+
+    default:
+      // Wrap statements inside a function declaration if not already wrapped.
+      if (aCurrentString.indexOf("function") != 0) {
+        let header = "function(" + (type == "set" ? "value" : "") + ")";
+        let body = "";
+        // If there's a return statement explicitly written, always use the
+        // standard function definition syntax
+        if (aCurrentString.indexOf("return ") != -1) {
+          body = "{" + aCurrentString + "}";
+        }
+        // If block syntax is used, use the whole string as the function body.
+        else if (aCurrentString.indexOf("{") == 0) {
+          body = aCurrentString;
+        }
+        // Prefer an expression closure.
+        else {
+          body = "(" + aCurrentString + ")";
+        }
+        aCurrentString = header + body;
+      }
+
+      // Determine if a new getter or setter should be defined.
+      let defineType = type == "get" ? "__defineGetter__" : "__defineSetter__";
+
+      // Make sure all quotes are escaped in the expression's syntax,
+      let defineFunc = "eval(\"(" + aCurrentString.replace(/"/g, "\\$&") + ")\")";
+
+      // Construct and return the getter/setter evaluation string.
+      // e.g: foo.__defineGetter__("bar", eval("(function() { return 42; })"))
+      return parent + "." + defineType + "(" + property + "," + defineFunc + ")";
+  }
+};
+
+/**
+ * Function invoked when a getter or setter is deleted.
+ *
+ * @param Property aItem
+ *        The current getter or setter property.
+ */
+VariablesView.getterOrSetterDeleteCallback = function(aItem) {
+  aItem._disable();
+  aItem.ownerView.eval(VariablesView.getterOrSetterEvalMacro(aItem, ""));
+  return true; // Don't hide the element.
+};
+
+/**
  * A Scope is an object holding Variable instances.
  * Iterable via "for (let [name, variable] in instance) { }".
  *
  * @param VariablesView aView
  *        The view to contain this scope.
  * @param string aName
  *        The scope's name.
  * @param object aFlags [optional]
  *        Additional options or flags for this scope.
  */
 function Scope(aView, aName, aFlags = {}) {
   this.ownerView = aView;
 
-  this.expand = this.expand.bind(this);
-  this.toggle = this.toggle.bind(this);
+  this._onClick = this._onClick.bind(this);
   this._openEnum = this._openEnum.bind(this);
   this._openNonEnum = this._openNonEnum.bind(this);
   this._batchAppend = this._batchAppend.bind(this);
   this._batchItems = [];
 
   // Inherit properties and flags from the parent view. You can override
   // each of these directly onto any scope, variable or property instance.
   this.eval = aView.eval;
   this.switch = aView.switch;
   this.delete = aView.delete;
   this.editableValueTooltip = aView.editableValueTooltip;
   this.editableNameTooltip = aView.editableNameTooltip;
+  this.editButtonTooltip = aView.editButtonTooltip;
   this.deleteButtonTooltip = aView.deleteButtonTooltip;
   this.descriptorTooltip = aView.descriptorTooltip;
   this.contextMenuId = aView.contextMenuId;
   this.separatorStr = aView.separatorStr;
 
   this._store = new Map();
   this._init(aName.trim(), aFlags);
 }
@@ -651,16 +1084,17 @@ Scope.prototype = {
   addVar: function S_addVar(aName = "", aDescriptor = {}) {
     if (this._store.has(aName)) {
       return null;
     }
 
     let variable = new Variable(this, aName, aDescriptor);
     this._store.set(aName, variable);
     this._variablesView._currHierarchy.set(variable._absoluteName, variable);
+    this._variablesView._itemsByElement.set(variable._target, variable);
     variable.header = !!aName;
     return variable;
   },
 
   /**
    * Gets the variable in this container having the specified name.
    *
    * @param string aName
@@ -692,33 +1126,66 @@ Scope.prototype = {
       if (match) {
         return match;
       }
     }
     return null;
   },
 
   /**
+   * Determines if this scope is a direct child of a parent variables view,
+   * scope, variable or property.
+   *
+   * @param VariablesView | Scope | Variable | Property
+   *        The parent to check.
+   * @return boolean
+   *         True if the specified item is a direct child, false otherwise.
+   */
+  isChildOf: function S_isChildOf(aParent) {
+    return this.ownerView == aParent;
+  },
+
+  /**
+   * Determines if this scope is a descendant of a parent variables view,
+   * scope, variable or property.
+   *
+   * @param VariablesView | Scope | Variable | Property
+   *        The parent to check.
+   * @return boolean
+   *         True if the specified item is a descendant, false otherwise.
+   */
+  isDescendantOf: function S_isDescendantOf(aParent) {
+    if (this.isChildOf(aParent)) {
+      return true;
+    }
+    if (this.ownerView instanceof Scope ||
+        this.ownerView instanceof Variable ||
+        this.ownerView instanceof Property) {
+      return this.ownerView.isDescendantOf(aParent);
+    }
+  },
+
+  /**
    * Shows the scope.
    */
   show: function S_show() {
     this._target.hidden = false;
-    this._isShown = true;
+    this._isContentVisible = true;
 
     if (this.onshow) {
       this.onshow(this);
     }
   },
 
   /**
    * Hides the scope.
    */
   hide: function S_hide() {
     this._target.hidden = true;
-    this._isShown = false;
+    this._isContentVisible = false;
 
     if (this.onhide) {
       this.onhide(this);
     }
   },
 
   /**
    * Expands the scope, showing all the added details.
@@ -733,17 +1200,17 @@ Scope.prototype = {
     // to suggest that this scope is expanding.
     if (!this._isExpanding &&
          this._variablesView.lazyAppend && this._store.size > LAZY_APPEND_BATCH) {
       this._isExpanding = true;
 
       // Start spinning a throbber in this scope's title and allow a few
       // milliseconds for it to be painted.
       this._startThrobber();
-      this.window.setTimeout(this.expand, LAZY_EXPAND_DELAY);
+      this.window.setTimeout(this.expand.bind(this), LAZY_EXPAND_DELAY);
       return;
     }
 
     if (this._variablesView._enumVisible) {
       this._openEnum();
     }
     if (this._variablesView._nonEnumVisible) {
       Services.tm.currentThread.dispatch({ run: this._openNonEnum }, 0);
@@ -782,17 +1249,17 @@ Scope.prototype = {
       return;
     }
     this._wasToggled = true;
     this.expanded ^= 1;
 
     // Make sure the scope and its contents are visibile.
     for (let [, variable] of this._store) {
       variable.header = true;
-      variable._match = true;
+      variable._matched = true;
     }
     if (this.ontoggle) {
       this.ontoggle(this);
     }
   },
 
   /**
    * Shows the scope's title header.
@@ -839,17 +1306,17 @@ Scope.prototype = {
     this._arrow.setAttribute("invisible", "");
     this._isArrowVisible = false;
   },
 
   /**
    * Gets the visibility state.
    * @return boolean
    */
-  get visible() this._isShown,
+  get visible() this._isContentVisible,
 
   /**
    * Gets the expanded state.
    * @return boolean
    */
   get expanded() this._isExpanded,
 
   /**
@@ -860,16 +1327,22 @@ Scope.prototype = {
 
   /**
    * Gets the twisty visibility state.
    * @return boolean
    */
   get twisty() this._isArrowVisible,
 
   /**
+   * Gets the expand lock state.
+   * @return boolean
+   */
+  get locked() this._locked,
+
+  /**
    * Sets the visibility state.
    * @param boolean aFlag
    */
   set visible(aFlag) aFlag ? this.show() : this.hide(),
 
   /**
    * Sets the expanded state.
    * @param boolean aFlag
@@ -884,28 +1357,47 @@ Scope.prototype = {
 
   /**
    * Sets the twisty visibility state.
    * @param boolean aFlag
    */
   set twisty(aFlag) aFlag ? this.showArrow() : this.hideArrow(),
 
   /**
-   * Gets the expand lock state.
-   * @return boolean
-   */
-  get locked() this._locked,
-
-  /**
    * Sets the expand lock state.
    * @param boolean aFlag
    */
   set locked(aFlag) this._locked = aFlag,
 
   /**
+   * Specifies if this target node may be focused.
+   * @return boolean
+   */
+  get focusable() {
+    // Check if this target node is actually visibile.
+    if (!this._nameString ||
+        !this._isContentVisible ||
+        !this._isHeaderVisible ||
+        !this._isMatch) {
+      return false;
+    }
+    // Check if all parent objects are expanded.
+    let item = this;
+    while ((item = item.ownerView) &&  /* Parent object exists. */
+           (item instanceof Scope ||
+            item instanceof Variable ||
+            item instanceof Property)) {
+      if (!item._isExpanded) {
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
    * Adds an event listener for a certain event on this scope's title.
    * @param string aName
    * @param function aCallback
    * @param boolean aCapture
    */
   addEventListener: function S_addEventListener(aName, aCallback, aCapture) {
     this._title.addEventListener(aName, aCallback, aCapture);
   },
@@ -993,17 +1485,28 @@ Scope.prototype = {
     element.appendChild(enumerable);
     element.appendChild(nonenum);
   },
 
   /**
    * Adds the necessary event listeners for this scope.
    */
   _addEventListeners: function S__addEventListeners() {
-    this._title.addEventListener("mousedown", this.toggle, false);
+    this._title.addEventListener("mousedown", this._onClick, false);
+  },
+
+  /**
+   * The click listener for this scope's title.
+   */
+  _onClick: function S__onClick(e) {
+    if (e.target == this._inputNode) {
+      return;
+    }
+    this.toggle();
+    this._variablesView._focusItem(this);
   },
 
   /**
    * Lazily appends a node to this scope's enumerable or non-enumerable
    * container. Once a certain number of nodes have been batched, they
    * will be appended.
    *
    * @param boolean aImmediateFlag
@@ -1152,21 +1655,21 @@ Scope.prototype = {
     for (let [, variable] of this._store) {
       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;
+        variable._matched = false;
       }
       // Variable or property is matched.
       else {
-        variable._match = true;
+        variable._matched = 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 && aLowerCaseQuery) {
           variable.expand();
         }
@@ -1179,48 +1682,67 @@ Scope.prototype = {
         // ("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._matched = true;
           aLowerCaseQuery && variable.expand();
         }
       }
 
       // Proceed with the search recursively inside this variable or property.
       if (currentObject._wasToggled ||
           currentObject.getter ||
           currentObject.setter) {
         currentObject._performSearch(aLowerCaseQuery);
       }
     }
   },
 
   /**
-   * Sets if this object instance is a match or non-match.
+   * Sets if this object instance is a matched or non-matched item.
    * @param boolean aStatus
    */
-  set _match(aStatus) {
+  set _matched(aStatus) {
     if (this._isMatch == aStatus) {
       return;
     }
     if (aStatus) {
       this._isMatch = true;
       this.target.removeAttribute("non-match");
     } else {
       this._isMatch = false;
       this.target.setAttribute("non-match", "");
     }
   },
 
   /**
+   * Gets the first search results match in this scope.
+   * @return Variable | Property
+   */
+  get _firstMatch() {
+    for (let [, variable] of this._store) {
+      let match;
+      if (variable._isMatch) {
+        match = variable;
+      } else {
+        match = variable._firstMatch;
+      }
+      if (match) {
+        return match;
+      }
+    }
+    return null;
+  },
+
+  /**
    * Gets top level variables view instance.
    * @return VariablesView
    */
   get _variablesView() this._topView || (this._topView = (function(self) {
     let parentView = self.ownerView;
     let topView;
 
     while (topView = parentView.ownerView) {
@@ -1252,32 +1774,33 @@ Scope.prototype = {
   _window: null,
 
   ownerView: null,
   eval: null,
   switch: null,
   delete: null,
   editableValueTooltip: "",
   editableNameTooltip: "",
+  editButtonTooltip: "",
   deleteButtonTooltip: "",
   descriptorTooltip: true,
   contextMenuId: "",
   separatorStr: "",
 
   _store: null,
   _fetched: false,
   _retrieved: false,
   _committed: false,
   _batchItems: null,
   _batchTimeout: null,
   _locked: false,
-  _isShown: true,
   _isExpanding: false,
   _isExpanded: false,
   _wasToggled: false,
+  _isContentVisible: true,
   _isHeaderVisible: true,
   _isArrowVisible: true,
   _isMatch: true,
   _idString: "",
   _nameString: "",
   _target: null,
   _arrow: null,
   _name: null,
@@ -1297,20 +1820,16 @@ Scope.prototype = {
  *        The variable's name.
  * @param object aDescriptor
  *        The variable's descriptor.
  */
 function Variable(aScope, aName, aDescriptor) {
   this._displayTooltip = this._displayTooltip.bind(this);
   this._activateNameInput = this._activateNameInput.bind(this);
   this._activateValueInput = this._activateValueInput.bind(this);
-  this._deactivateNameInput = this._deactivateNameInput.bind(this);
-  this._deactivateValueInput = this._deactivateValueInput.bind(this);
-  this._onNameInputKeyPress = this._onNameInputKeyPress.bind(this);
-  this._onValueInputKeyPress = this._onValueInputKeyPress.bind(this);
 
   Scope.call(this, aScope, aName, this._initialDescriptor = aDescriptor);
   this.setGrip(aDescriptor.value);
   this._symbolicName = aName;
   this._absoluteName = aScope.name + "[\"" + aName + "\"]";
 }
 
 create({ constructor: Variable, proto: Scope.prototype }, {
@@ -1337,16 +1856,17 @@ create({ constructor: Variable, proto: S
   addProperty: function V_addProperty(aName = "", aDescriptor = {}) {
     if (this._store.has(aName)) {
       return null;
     }
 
     let property = new Property(this, aName, aDescriptor);
     this._store.set(aName, property);
     this._variablesView._currHierarchy.set(property._absoluteName, property);
+    this._variablesView._itemsByElement.set(property._target, property);
     property.header = !!aName;
     return property;
   },
 
   /**
    * Adds properties for this variable.
    *
    * @param object aProperties
@@ -1360,27 +1880,33 @@ create({ constructor: Variable, proto: S
    *                 someProp3: { value: { type: "undefined" } },
    *                 someProp4: { value: { type: "null" } },
    *                 someProp5: { value: { type: "object", class: "Object" } },
    *                 someProp6: { get: { type: "object", class: "Function" },
    *                              set: { type: "undefined" } }
    * @param object aOptions [optional]
    *        Additional options for adding the properties. Supported options:
    *        - sorted: true to sort all the properties before adding them
+   *        - callback: function invoked after each property is added
    */
   addProperties: function V_addProperties(aProperties, aOptions = {}) {
     let propertyNames = Object.keys(aProperties);
 
     // Sort all of the properties before adding them, if preferred.
     if (aOptions.sorted) {
       propertyNames.sort();
     }
     // Add the properties to the current scope.
     for (let name of propertyNames) {
-      this.addProperty(name, aProperties[name]);
+      let descriptor = aProperties[name];
+      let property = this.addProperty(name, descriptor);
+
+      if (aOptions.callback) {
+        aOptions.callback(property, descriptor.value);
+      }
     }
   },
 
   /**
    * Populates this variable to contain all the properties of an object.
    *
    * @param object aObject
    *        The raw object you want to display.
@@ -1413,36 +1939,51 @@ create({ constructor: Variable, proto: S
     }
     // Add the variable's __proto__.
     if (prototype) {
       this._addRawValueProperty("__proto__", {}, prototype);
     }
   },
 
   /**
+   * Populates a specific variable or property instance to contain all the
+   * properties of an object
+   *
+   * @param Variable | Property aVar
+   *        The target variable to populate.
+   * @param object aObject [optional]
+   *        The raw object you want to display. If unspecified, the object is
+   *        assumed to be defined in a _sourceValue property on the target.
+   */
+  _populateTarget: function V__populateTarget(aVar, aObject = aVar._sourceValue) {
+    aVar.populate(aObject);
+  },
+
+  /**
    * Adds a property for this variable based on a raw value descriptor.
    *
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
    *        Specifies the exact property descriptor as returned by a call to
    *        Object.getOwnPropertyDescriptor.
    * @param object aValue
    *        The raw property value you want to display.
    */
   _addRawValueProperty: function V__addRawValueProperty(aName, aDescriptor, aValue) {
     let descriptor = Object.create(aDescriptor);
     descriptor.value = VariablesView.getGrip(aValue);
 
     let propertyItem = this.addProperty(aName, descriptor);
+    propertyItem._sourceValue = aValue;
 
     // Add an 'onexpand' callback for the property, lazily handling
     // the addition of new child properties.
     if (!VariablesView.isPrimitive(descriptor)) {
-      propertyItem.onexpand = this.populate.bind(propertyItem, aValue);
+      propertyItem.onexpand = this._populateTarget;
     }
   },
 
   /**
    * Adds a property for this variable based on a getter/setter descriptor.
    *
    * @param string aName
    *        The property's name.
@@ -1450,18 +1991,17 @@ create({ constructor: Variable, proto: S
    *        Specifies the exact property descriptor as returned by a call to
    *        Object.getOwnPropertyDescriptor.
    */
   _addRawNonValueProperty: function V__addRawNonValueProperty(aName, aDescriptor) {
     let descriptor = Object.create(aDescriptor);
     descriptor.get = VariablesView.getGrip(aDescriptor.get);
     descriptor.set = VariablesView.getGrip(aDescriptor.set);
 
-    let propertyItem = this.addProperty(aName, descriptor);
-    return propertyItem;
+    this.addProperty(aName, descriptor);
   },
 
   /**
    * Gets this variable's path to the topmost scope.
    * For example, a symbolic name may look like "arguments['0']['foo']['bar']".
    * @return string
    */
   get symbolicName() this._symbolicName,
@@ -1501,16 +2041,21 @@ create({ constructor: Variable, proto: S
    *             - { type: "null" }
    *             - { type: "object", class: "Object" }
    */
   setGrip: function V_setGrip(aGrip) {
     // Don't allow displaying grip information if there's no name available.
     if (!this._nameString) {
       return;
     }
+    // Getters and setters should display grip information in sub-properties.
+    if (!this._isUndefined && (this.getter || this.setter)) {
+      this._valueLabel.setAttribute("value", "");
+      return;
+    }
 
     if (aGrip === undefined) {
       aGrip = { type: "undefined" };
     }
     if (aGrip === null) {
       aGrip = { type: "null" };
     }
 
@@ -1531,17 +2076,17 @@ create({ constructor: Variable, proto: S
    *
    * @param string aName
    *        The variable's name.
    * @param object aDescriptor
    *        The variable's descriptor.
    */
   _init: function V__init(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
-    this._displayScope(aName, "variable");
+    this._displayScope(aName, "variable variable-or-property");
 
     // Don't allow displaying variable information there's no name available.
     if (this._nameString) {
       this._displayVariable();
       this._customizeVariable();
       this._prepareTooltip();
       this._setAttributes();
       this._addEventListeners();
@@ -1580,42 +2125,59 @@ create({ constructor: Variable, proto: S
 
     let valueLabel = this._valueLabel = document.createElement("label");
     valueLabel.className = "plain value";
     valueLabel.setAttribute("crop", "center");
 
     this._title.appendChild(separatorLabel);
     this._title.appendChild(valueLabel);
 
-    let isPrimitive = VariablesView.isPrimitive(descriptor);
-    let isUndefined = VariablesView.isUndefined(descriptor);
+    let isPrimitive = this._isPrimitive = VariablesView.isPrimitive(descriptor);
+    let isUndefined = this._isUndefined = VariablesView.isUndefined(descriptor);
 
     if (isPrimitive || isUndefined) {
       this.hideArrow();
     }
     if (!isUndefined && (descriptor.get || descriptor.set)) {
-      // FIXME: editing getters and setters is not allowed yet. Bug 831794.
-      this.eval = null;
-      this.addProperty("get", { value: descriptor.get });
-      this.addProperty("set", { value: descriptor.set });
-      this.expand();
       separatorLabel.hidden = true;
       valueLabel.hidden = true;
+
+      this.delete = VariablesView.getterOrSetterDeleteCallback;
+      this.evaluationMacro = VariablesView.overrideValueEvalMacro;
+
+      let getter = this.addProperty("get", { value: descriptor.get });
+      let setter = this.addProperty("set", { value: descriptor.set });
+      getter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
+      setter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
+
+      getter.hideArrow();
+      setter.hideArrow();
+      this.expand();
     }
   },
 
   /**
    * Adds specific nodes for this variable based on custom flags.
    */
   _customizeVariable: function V__customizeVariable() {
+    if (this.ownerView.eval) {
+      if (!this._isUndefined && (this.getter || this.setter)) {
+        let editNode = this._editNode = this.document.createElement("toolbarbutton");
+        editNode.className = "plain dbg-variable-edit";
+        editNode.addEventListener("mousedown", this._onEdit.bind(this), false);
+        this._title.appendChild(editNode);
+      }
+    }
     if (this.ownerView.delete) {
-      let deleteNode = this._deleteNode = this.document.createElement("toolbarbutton");
-      deleteNode.className = "plain dbg-variable-delete devtools-closebutton";
-      deleteNode.addEventListener("click", this._onDelete.bind(this), false);
-      this._title.appendChild(deleteNode);
+      if (!this._isUndefined || !(this.ownerView.getter && this.ownerView.setter)) {
+        let deleteNode = this._deleteNode = this.document.createElement("toolbarbutton");
+        deleteNode.className = "plain dbg-variable-delete devtools-closebutton";
+        deleteNode.addEventListener("click", this._onDelete.bind(this), false);
+        this._title.appendChild(deleteNode);
+      }
     }
     if (this.ownerView.contextMenuId) {
       this._title.setAttribute("context", this.ownerView.contextMenuId);
     }
   },
 
   /**
    * Prepares a tooltip for this variable.
@@ -1646,16 +2208,19 @@ create({ constructor: Variable, proto: S
       tooltip.setAttribute("orient", "horizontal");
       tooltip.appendChild(configurableLabel);
       tooltip.appendChild(enumerableLabel);
       tooltip.appendChild(writableLabel);
 
       this._target.appendChild(tooltip);
       this._target.setAttribute("tooltip", tooltip.id);
     }
+    if (this.ownerView.eval && !this._isUndefined && (this.getter || this.setter)) {
+      this._editNode.setAttribute("tooltiptext", this.ownerView.editButtonTooltip);
+    }
     if (this.ownerView.eval) {
       this._valueLabel.setAttribute("tooltiptext", this.ownerView.editableValueTooltip);
     }
     if (this.ownerView.switch) {
       this._name.setAttribute("tooltiptext", this.ownerView.editableNameTooltip);
     }
     if (this.ownerView.delete) {
       this._deleteNode.setAttribute("tooltiptext", this.ownerView.deleteButtonTooltip);
@@ -1665,23 +2230,26 @@ create({ constructor: Variable, proto: S
   /**
    * Sets a variable's configurable, enumerable and writable attributes,
    * and specifies if it's a 'this', '<exception>' or '__proto__' reference.
    */
   _setAttributes: function V__setAttributes() {
     let descriptor = this._initialDescriptor;
     let name = this._nameString;
 
+    if (this.ownerView.eval) {
+      this._target.setAttribute("editable", "");
+    }
     if (!descriptor.configurable) {
       this._target.setAttribute("non-configurable", "");
     }
     if (!descriptor.enumerable) {
       this._target.setAttribute("non-enumerable", "");
     }
-    if (!descriptor.writable) {
+    if (!descriptor.writable && !this.ownerView.get && !this.ownerView.set) {
       this._target.setAttribute("non-writable", "");
     }
     if (name == "this") {
       this._target.setAttribute("self", "");
     }
     else if (name == "<exception>") {
       this._target.setAttribute("exception", "");
     }
@@ -1689,20 +2257,19 @@ create({ constructor: Variable, proto: S
       this._target.setAttribute("proto", "");
     }
   },
 
   /**
    * Adds the necessary event listeners for this variable.
    */
   _addEventListeners: function V__addEventListeners() {
-    this._arrow.addEventListener("mousedown", this.toggle, false);
-    this._name.addEventListener("mousedown", this.toggle, false);
     this._name.addEventListener("dblclick", this._activateNameInput, false);
-    this._valueLabel.addEventListener("click", this._activateValueInput, false);
+    this._valueLabel.addEventListener("mousedown", this._activateValueInput, false);
+    this._title.addEventListener("mousedown", this._onClick, false);
   },
 
   /**
    * Creates a textbox node in place of a label.
    *
    * @param nsIDOMNode aLabel
    *        The label to be replaced with a textbox.
    * @param string aClassName
@@ -1713,18 +2280,24 @@ create({ constructor: Variable, proto: S
   _activateInput: function V__activateInput(aLabel, aClassName, aCallbacks) {
     let initialString = aLabel.getAttribute("value");
 
     // Create a texbox input element which will be shown in the current
     // element's specified label location.
     let input = this.document.createElement("textbox");
     input.setAttribute("value", initialString);
     input.className = "plain " + aClassName;
-    input.width = this._target.clientWidth;
 
+    // Can't use clientWidth because labels may have extra unnecessary padding.
+    let style = this.window.getComputedStyle(aLabel);
+    input.width = (parseInt(style.getPropertyValue("width")) ||
+      ELEMENT_INPUT_DEFAULT_WIDTH) + // If no content was previously available.
+      ELEMENT_INPUT_EXTRA_SPACE; // Extra space added for editing.
+
+    // Replace the specified label with a textbox input element.
     aLabel.parentNode.replaceChild(input, aLabel);
     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.
     if (aLabel.getAttribute("value").match(/^"[^"]*"$/)) {
       input.selectionEnd--;
@@ -1735,16 +2308,17 @@ create({ constructor: Variable, proto: S
     input.addEventListener("blur", aCallbacks.onBlur, false);
 
     this._prevExpandable = this.twisty;
     this._prevExpanded = this.expanded;
     this.collapse();
     this.hideArrow();
     this._locked = true;
 
+    this._inputNode = input;
     this._stopThrobber();
   },
 
   /**
    * Removes the textbox node in place of a label.
    *
    * @param nsIDOMNode aLabel
    *        The label which was replaced with a textbox.
@@ -1755,30 +2329,39 @@ create({ constructor: Variable, proto: S
     aInput.parentNode.replaceChild(aLabel, aInput);
     aInput.removeEventListener("keypress", aCallbacks.onKeypress, false);
     aInput.removeEventListener("blur", aCallbacks.onBlur, false);
 
     this._locked = false;
     this.twisty = this._prevExpandable;
     this.expanded = this._prevExpanded;
 
+    this._inputNode = null;
     this._stopThrobber();
   },
 
   /**
    * Makes this variable's name editable.
    */
   _activateNameInput: function V__activateNameInput(e) {
     if (e && e.button != 0) {
       // Only allow left-click to trigger this event.
       return;
     }
     if (!this.ownerView.switch) {
       return;
     }
+    if (e) {
+      e.preventDefault();
+      e.stopPropagation();
+    }
+
+    this._onNameInputKeyPress = this._onNameInputKeyPress.bind(this);
+    this._deactivateNameInput = this._deactivateNameInput.bind(this);
+
     this._activateInput(this._name, "element-name-input", {
       onKeypress: this._onNameInputKeyPress,
       onBlur: this._deactivateNameInput
     });
     this._separatorLabel.hidden = true;
     this._valueLabel.hidden = true;
   },
 
@@ -1800,16 +2383,24 @@ create({ constructor: Variable, proto: S
   _activateValueInput: function V__activateValueInput(e) {
     if (e && e.button != 0) {
       // Only allow left-click to trigger this event.
       return;
     }
     if (!this.ownerView.eval) {
       return;
     }
+    if (e) {
+      e.preventDefault();
+      e.stopPropagation();
+    }
+
+    this._onValueInputKeyPress = this._onValueInputKeyPress.bind(this);
+    this._deactivateValueInput = this._deactivateValueInput.bind(this);
+
     this._activateInput(this._valueLabel, "element-value-input", {
       onKeypress: this._onValueInputKeyPress,
       onBlur: this._deactivateValueInput
     });
   },
 
   /**
    * Deactivates this variable's editable value mode.
@@ -1820,21 +2411,28 @@ create({ constructor: Variable, proto: S
       onBlur: this._deactivateValueInput
     });
   },
 
   /**
    * Disables this variable prior to a new name switch or value evaluation.
    */
   _disable: function V__disable() {
-    this.twisty = false;
+    this.hideArrow();
     this._separatorLabel.hidden = true;
     this._valueLabel.hidden = true;
     this._enum.hidden = true;
     this._nonenum.hidden = true;
+
+    if (this._editNode) {
+      this._editNode.hidden = true;
+    }
+    if (this._deleteNode) {
+      this._deleteNode.hidden = true;
+    }
   },
 
   /**
    * Deactivates this variable's editable mode and callbacks the new name.
    */
   _saveNameInput: function V__saveNameInput(e) {
     let input = e.target;
     let initialString = this._name.getAttribute("value");
@@ -1854,66 +2452,96 @@ create({ constructor: Variable, proto: S
   _saveValueInput: function V__saveValueInput(e) {
     let input = e.target;
     let initialString = this._valueLabel.getAttribute("value");
     let currentString = input.value.trim();
     this._deactivateValueInput(e);
 
     if (initialString != currentString) {
       this._disable();
-      this.ownerView.eval(this._symbolicName + "=" + currentString);
+      this.ownerView.eval(this.evaluationMacro(this, currentString.trim()));
     }
   },
 
   /**
+   * The current macro used to generate the string evaluated when performing
+   * a variable or property value change.
+   */
+  evaluationMacro: VariablesView.simpleValueEvalMacro,
+
+  /**
    * The key press listener for this variable's editable name textbox.
    */
   _onNameInputKeyPress: function V__onNameInputKeyPress(e) {
+    e.stopPropagation();
+
     switch(e.keyCode) {
       case e.DOM_VK_RETURN:
       case e.DOM_VK_ENTER:
         this._saveNameInput(e);
+        this._variablesView._focusItem(this);
         return;
       case e.DOM_VK_ESCAPE:
         this._deactivateNameInput(e);
+        this._variablesView._focusItem(this);
         return;
     }
   },
 
   /**
    * The key press listener for this variable's editable value textbox.
    */
   _onValueInputKeyPress: function V__onValueInputKeyPress(e) {
+    e.stopPropagation();
+
     switch(e.keyCode) {
       case e.DOM_VK_RETURN:
       case e.DOM_VK_ENTER:
         this._saveValueInput(e);
+        this._variablesView._focusItem(this);
         return;
       case e.DOM_VK_ESCAPE:
         this._deactivateValueInput(e);
+        this._variablesView._focusItem(this);
         return;
     }
   },
 
   /**
+   * The click listener for the edit button.
+   */
+  _onEdit: function V__onEdit(e) {
+    e.preventDefault();
+    e.stopPropagation();
+    this._activateValueInput();
+  },
+
+  /**
    * The click listener for the delete button.
    */
-  _onDelete: function V__onDelete() {
-    this.hide();
+  _onDelete: function V__onDelete(e) {
+    e.preventDefault();
+    e.stopPropagation();
 
     if (this.ownerView.delete) {
-      this.ownerView.delete(this);
+      if (!this.ownerView.delete(this)) {
+        this.hide();
+      }
     }
   },
 
   _symbolicName: "",
   _absoluteName: "",
   _initialDescriptor: null,
+  _isPrimitive: false,
+  _isUndefined: false,
   _separatorLabel: null,
   _valueLabel: null,
+  _inputNode: null,
+  _editNode: null,
   _deleteNode: null,
   _tooltip: null,
   _valueGrip: null,
   _valueString: "",
   _valueClassName: "",
   _prevExpandable: false,
   _prevExpanded: false
 });
@@ -1941,17 +2569,17 @@ create({ constructor: Property, proto: V
    *
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
    *        The property's descriptor.
    */
   _init: function P__init(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
-    this._displayScope(aName, "property");
+    this._displayScope(aName, "property variable-or-property");
 
     // Don't allow displaying property information there's no name available.
     if (this._nameString) {
       this._displayVariable();
       this._customizeVariable();
       this._prepareTooltip();
       this._setAttributes();
       this._addEventListeners();
--- a/browser/devtools/styleeditor/test/Makefile.in
+++ b/browser/devtools/styleeditor/test/Makefile.in
@@ -19,17 +19,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_styleeditor_import.js \
                  browser_styleeditor_init.js \
                  $(filter disabled-temporarily--bug-817294, browser_styleeditor_loading.js) \
                  browser_styleeditor_new.js \
                  browser_styleeditor_passedinsheet.js \
                  browser_styleeditor_pretty.js \
                  browser_styleeditor_private_perwindowpb.js \
                  browser_styleeditor_readonly.js \
-                 $(filter disabled-for-intermittent-failures--bug-707891, browser_styleeditor_reopen.js) \
+                 browser_styleeditor_reopen.js \
                  browser_styleeditor_sv_keynav.js \
                  browser_styleeditor_sv_resize.js \
                  head.js \
                  helpers.js \
                  four.html \
                  head.js \
                  helpers.js \
                  media.html \
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -89,16 +89,21 @@
 <!ENTITY debuggerUI.searchLine          "Jump to line…">
 <!ENTITY debuggerUI.searchLine.key      "J">
 
 <!-- LOCALIZATION NOTE (debuggerUI.searchVariable): This is the text that appears
   -  in the source editor's context menu for the variables search operation. -->
 <!ENTITY debuggerUI.searchVariable      "Filter variables">
 <!ENTITY debuggerUI.searchVariable.key  "V">
 
+<!-- LOCALIZATION NOTE (debuggerUI.focusVariables): This is the text that appears
+  -  in the source editor's context menu for the variables focus operation. -->
+<!ENTITY debuggerUI.focusVariables      "Focus variables tree">
+<!ENTITY debuggerUI.focusVariables.key  "V">
+
 <!-- 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">
 <!ENTITY debuggerUI.seMenuBreak.key     "B">
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -191,22 +191,26 @@ ToolboxDebugger.label=Debugger
 # displayed inside the developer tools window..
 ToolboxDebugger.tooltip=JavaScript Debugger
 
 # LOCALIZATION NOTE (variablesEditableNameTooltip): The text that is displayed
 # in the variables list on an item with an editable name.
 variablesEditableNameTooltip=Double click to edit
 
 # LOCALIZATION NOTE (variablesEditableValueTooltip): The text that is displayed
-# in the variables list on an item with an editable name.
+# in the variables list on an item with an editable value.
 variablesEditableValueTooltip=Click to change value
 
 # LOCALIZATION NOTE (variablesCloseButtonTooltip): The text that is displayed
-# in the variables list on an item with which can be removed.
+# in the variables list on an item which can be removed.
 variablesCloseButtonTooltip=Click to remove
 
+# LOCALIZATION NOTE (variablesEditButtonTooltip): The text that is displayed
+# in the variables list on a getter or setter which can be edited.
+variablesEditButtonTooltip=Click to set value
+
 # LOCALIZATION NOTE (variablesSeparatorLabel): The text that is displayed
 # in the variables list as a separator between the name and value.
 variablesSeparatorLabel=:
 
 # LOCALIZATION NOTE (watchExpressionsSeparatorLabel): The text that is displayed
 # in the watch expressions list as a separator between the code and evaluation.
 watchExpressionsSeparatorLabel=\ →
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/profiler.dtd
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the Profiler strings -->
+
+<!-- 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. -->
+
+<!-- LOCALIZATION NOTE (profilerNew.label): This is the label for the
+  -  button that creates a new profile. -->
+<!ENTITY profilerNew.label "New">
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/profiler.properties
@@ -14,8 +14,53 @@
 # This string is displayed in the title of the tab when the profiler is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 profiler.label=Profiler
 
 # LOCALIZATION NOTE (profiler.tooltip):
 # This string is displayed in the tooltip of the tab when the profiler is
 # displayed inside the developer tools window.
 profiler.tooltip=Profiler
+
+# LOCALIZATION NOTE (profiler.profileName):
+# This string is the default name for new profiles. Its parameter is a number.
+# For example: "Profile 1", "Profile 2", etc.
+profiler.profileName=Profile %S
+
+# LOCALIZATION NOTE (profiler.completeProfile):
+# This string is displayed as a tab in the profiler UI. Clicking on it
+# displays everything that the profiler has generated so far.
+profiler.completeProfile=Complete Profile
+
+# LOCALIZATION NOTE (profiler.sampleRange):
+# This string is displayed as a tab in the profiler UI. Clicking on it
+# displays a sample range of data selected by user themselves.
+profiler.sampleRange=Sample Range
+
+# LOCALIZATION NOTE (profiler.runningTime):
+# This string is displayed as a table header in the profiler UI.
+profiler.runningTime=Running Time
+
+# LOCALIZATION NOTE (profiler.self):
+# This string is displayed as a table header in the profiler UI.
+profiler.self=Self
+
+# LOCALIZATION NOTE (profiler.symbolName)
+# This string is displayed as a table header in the profiler UI.
+profiler.symbolName=Symbol Name
+
+# LOCALIZATION NOTE (profiler.startProfiling)
+# This string is displayed around the button that starts the profiler.
+# String argument will be replaced with a Start button.
+profiler.startProfiling=Click here %S to start profiling
+
+# LOCALIZATION NOTE (profiler.stopProfiling)
+# This string is displayed around the button that stops the profiler.
+# String argument will be replaced with a Stop button.
+profiler.stopProfiling = Click here %S to stop profiling
+
+# LOCALIZATION NOTE (profiler.start)
+# This string is displayed on the button that starts the profiler.
+profiler.start=Start
+
+# LOCALIZATION NOTE (profiler.stop)
+# This string is displayed on the button that stops the profiler.
+profiler.stop=Stop
\ No newline at end of file
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -33,16 +33,17 @@
     locale/browser/devtools/scratchpad.dtd            (%chrome/browser/devtools/scratchpad.dtd)
     locale/browser/devtools/styleeditor.properties    (%chrome/browser/devtools/styleeditor.properties)
     locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.properties (%chrome/browser/devtools/styleinspector.properties)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
     locale/browser/devtools/sourceeditor.properties   (%chrome/browser/devtools/sourceeditor.properties)
     locale/browser/devtools/sourceeditor.dtd          (%chrome/browser/devtools/sourceeditor.dtd)
+    locale/browser/devtools/profiler.dtd              (%chrome/browser/devtools/profiler.dtd)
     locale/browser/devtools/profiler.properties       (%chrome/browser/devtools/profiler.properties)
     locale/browser/devtools/layoutview.dtd            (%chrome/browser/devtools/layoutview.dtd)
     locale/browser/devtools/responsiveUI.properties   (%chrome/browser/devtools/responsiveUI.properties)
     locale/browser/devtools/toolbox.dtd            (%chrome/browser/devtools/toolbox.dtd)
     locale/browser/devtools/toolbox.properties     (%chrome/browser/devtools/toolbox.properties)
     locale/browser/devtools/inspector.dtd          (%chrome/browser/devtools/inspector.dtd)
     locale/browser/devtools/connection-screen.dtd  (%chrome/browser/devtools/connection-screen.dtd)
     locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
--- a/browser/themes/gnomestripe/devtools/debugger.css
+++ b/browser/themes/gnomestripe/devtools/debugger.css
@@ -170,43 +170,40 @@
   color: #888;
 }
 
 /**
  * Stack frames and breakpoints pane
  */
 
 #stackframes\+breakpoints {
-  background-color: white;
   min-width: 50px;
 }
 
 #stackframes\+breakpoints[animated] {
   transition: margin 0.25s ease-in-out;
 }
 
 /**
  * Variables and watch expressions pane
  */
 
 #variables\+expressions {
-  background-color: white;
   min-width: 50px;
 }
 
 #variables\+expressions[animated] {
   transition: margin 0.25s ease-in-out;
 }
 
 /**
  * Stack frames view
  */
 
 #stackframes {
-  background-color: white;
   min-height: 10px;
 }
 
 .dbg-stackframe {
   -moz-padding-start: 4px;
   -moz-padding-end: 4px;
 }
 
@@ -218,17 +215,16 @@
   -moz-padding-start: 4px;
 }
 
 /**
  * Breakpoints view
  */
 
 #breakpoints {
-  background-color: white;
   min-height: 10px;
 }
 
 #breakpoints > vbox:not(:empty) {
   min-height: 10px;
   max-height: 200px;
 }
 
@@ -252,18 +248,18 @@
   margin: 0 0 -2px 0;
 }
 
 /**
  * Watch expressions view
  */
 
 #expressions {
-  background-color: white;
   min-height: 10px;
+  max-height: 125px;
 }
 
 .dbg-expression {
   height: 20px;
   -moz-padding-start: 8px;
 }
 
 .dbg-expression-arrow {
@@ -271,117 +267,168 @@
   height: auto;
   background: url("chrome://browser/skin/devtools/commandline.png") 0px 4px no-repeat;
 }
 
 .dbg-expression-input {
   font: 9pt monospace;
 }
 
-.dbg-expression-delete:not(:hover) {
+.dbg-expression-delete {
+  opacity: 0;
+}
+
+.dbg-expression-delete:hover {
+  opacity: 1;
+}
+
+.dbg-expression:hover > .dbg-expression-delete:not(:hover) {
   opacity: 0.5;
+  transition: opacity 0.2s ease-in-out;
 }
 
 /**
  * Variables view
  */
 
 #variables {
-  background-color: white;
   min-height: 10px;
 }
 
-.dbg-variable-delete:not(:hover) {
+.dbg-variable-delete {
+  opacity: 0;
+}
+
+.dbg-variable-delete:hover {
+  opacity: 1;
+}
+
+.variable-or-property:hover > .title > .dbg-variable-delete:not(:hover),
+.variable-or-property:focus > .title > .dbg-variable-delete:not(:hover) {
   opacity: 0.5;
+  transition: opacity 0.2s ease-in-out;
+}
+
+.dbg-variable-edit {
+  background: url("chrome://browser/skin/tabview/edit-light.png") center no-repeat;
+  width: 20px;
+  height: 16px;
+  cursor: pointer;
 }
 
 .dbg-variable-throbber {
-  background: url("chrome://global/skin/icons/loading_16.png");
+  background: url("chrome://global/skin/icons/loading_16.png") center no-repeat;
   width: 16px;
   height: 16px;
 }
 
 /**
  * Scope element
  */
 
+.scope:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+}
+
 .scope > .title {
   text-shadow: 0 1px #222;
   color: #fff;
 }
 
 .scope > .details {
   -moz-margin-start: 2px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
 }
 
 .scope > .details.nonenum:not(:empty) {
   border-top: 1px solid #ddd;
 }
 
 /**
  * Variable element
  */
 
 .variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
   border-bottom: 1px solid #eee;
-  background: #fff;
   transition: background 1s ease-in-out;
 }
 
 .variable[changed] {
   background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
 }
 
+.variable:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+  border-radius: 4px;
+}
+
 .variable > .title > .name {
+  font-weight: 600;
+}
+
+.variable:not(:focus) > .title > .name {
   color: #048;
-  font-weight: 600;
 }
 
 .variable > .title > .value {
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
+.variable[editable] > .title > .value {
+  cursor: text;
+}
+
 .variable:not([non-header]) > .details {
   -moz-margin-start: 10px;
 }
 
 /**
  * Property element
  */
 
 .property {
-  background: #fff;
   transition: background 1s ease-in-out;
 }
 
 .property[changed] {
   background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
 }
 
-.property > .title > .name {
+.property:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+  border-radius: 4px;
+}
+
+.property:not(:focus) > .title > .name {
   color: #881090;
 }
 
 .property > .title > .value {
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
+.property[editable] > .title > .value {
+  cursor: text;
+}
+
 .property:not([non-header]) > .details {
   -moz-margin-start: 10px;
 }
 
 /**
- * Non enumerable, configurable and writable variables and properties.
+ * Non enumerable, configurable and writable variables and properties
  */
 
 .variable[proto] > .title > .name,
 .property[proto] > .title > .name,
 .variable[non-enumerable]:not([self]):not([exception]) > .title > .name,
 .property[non-enumerable]:not([self]):not([exception]) > .title > .name {
   opacity: 0.5;
 }
@@ -409,18 +456,18 @@
 @media (min-resolution: 2dppx) {
   .variable[non-writable] > .title:after,
   .property[non-writable] > .title:after {
     background-image: url("chrome://browser/skin/identity-icons-https@2x.png");
     background-size: 32px;
   }
 }
 
-.variable[exception] > .title > .name,
-.property[exception] > .title > .name {
+.variable[exception]:not(:focus) > .title > .name,
+.property[exception]:not(:focus) > .title > .name {
   color: #a00;
   text-shadow: 0 0 8px #fcc;
 }
 
 .variable > tooltip > label,
 .property > tooltip > label {
   margin: 0 2px 0 2px;
 }
@@ -433,28 +480,33 @@
 .property[non-writable] > tooltip > label[value="writable"] {
   text-decoration: line-through;
 }
 
 /**
  * Variables and properties editing
  */
 
-#variables .element-value-input {
-  overflow: hidden;
-  max-width: 30em;
-  -moz-margin-start: 5px !important;
+.element-value-input {
+  -moz-margin-start: 4px !important;
 }
 
-#variables .element-name-input {
-  -moz-margin-start: -1px !important;
+.element-name-input {
+  -moz-margin-start: -2px !important;
   color: #048;
   font-weight: 600;
 }
 
+.element-value-input,
+.element-name-input {
+  max-width: 30em;
+  border: 1px solid #999 !important;
+  box-shadow: 1px 2px 4px #aaa;
+}
+
 /**
  * Variables and properties searching
  */
 
 .variables-searchinput.devtools-searchinput {
   min-height: 24px;
 }
 
@@ -463,38 +515,38 @@
   border: none;
   margin: 0;
 }
 
 /**
  * Token value colors
  */
 
-.token-undefined {
+.variable-or-property:not(:focus) > .title > .token-undefined {
   color: #bbb;
 }
 
-.token-null {
+.variable-or-property:not(:focus) > .title > .token-null {
   color: #999;
 }
 
-.token-boolean {
+.variable-or-property:not(:focus) > .title > .token-boolean {
   color: #777;
 }
 
-.token-number {
+.variable-or-property:not(:focus) > .title > .token-number {
   color: #c40a16;
 }
 
-.token-string {
+.variable-or-property:not(:focus) > .title > .token-string {
   max-width: 30em;
   color: #1c00cf;
 }
 
-.token-other {
+.variable-or-property:not(:focus) > .title > .token-other {
   color: #333;
 }
 
 /**
  * Expand/collapse arrow
  */
 
 .arrow {
--- a/browser/themes/pinstripe/devtools/debugger.css
+++ b/browser/themes/pinstripe/devtools/debugger.css
@@ -172,43 +172,40 @@
   color: #888;
 }
 
 /**
  * Stack frames and breakpoints pane
  */
 
 #stackframes\+breakpoints {
-  background-color: white;
   min-width: 50px;
 }
 
 #stackframes\+breakpoints[animated] {
   transition: margin 0.25s ease-in-out;
 }
 
 /**
  * Variables and watch expressions pane
  */
 
 #variables\+expressions {
-  background-color: white;
   min-width: 50px;
 }
 
 #variables\+expressions[animated] {
   transition: margin 0.25s ease-in-out;
 }
 
 /**
  * Stack frames view
  */
 
 #stackframes {
-  background-color: white;
   min-height: 10px;
 }
 
 .dbg-stackframe {
   -moz-padding-start: 4px;
   -moz-padding-end: 4px;
 }
 
@@ -220,17 +217,16 @@
   -moz-padding-start: 4px;
 }
 
 /**
  * Breakpoints view
  */
 
 #breakpoints {
-  background-color: white;
   min-height: 10px;
 }
 
 #breakpoints > vbox:not(:empty) {
   min-height: 10px;
   max-height: 200px;
 }
 
@@ -254,18 +250,18 @@
   margin: 0 0 -2px 0;
 }
 
 /**
  * Watch expressions view
  */
 
 #expressions {
-  background-color: white;
   min-height: 10px;
+  max-height: 125px;
 }
 
 .dbg-expression {
   height: 20px;
   -moz-padding-start: 8px;
 }
 
 .dbg-expression-arrow {
@@ -274,121 +270,175 @@
   background: url("chrome://browser/skin/devtools/commandline.png") 0px 4px no-repeat;
 }
 
 .dbg-expression-input {
   font: 9pt monospace;
 }
 
 .dbg-expression-delete {
+  opacity: 0;
+}
+
+.dbg-expression-delete:not(:hover) {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-.dbg-expression-delete:not(:hover) {
+.dbg-expression-delete:hover {
+  opacity: 1;
+}
+
+.dbg-expression:hover > .dbg-expression-delete:not(:hover) {
   opacity: 0.5;
+  transition: opacity 0.2s ease-in-out;
 }
 
 /**
  * Variables view
  */
 
 #variables {
-  background-color: white;
   min-height: 10px;
 }
 
+.dbg-variable-delete {
+  opacity: 0;
+}
+
 .dbg-variable-delete:not(:hover) {
   -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.dbg-variable-delete:hover {
+  opacity: 1;
+}
+
+.variable-or-property:hover > .title > .dbg-variable-delete:not(:hover),
+.variable-or-property:focus > .title > .dbg-variable-delete:not(:hover) {
   opacity: 0.5;
+  transition: opacity 0.2s ease-in-out;
+}
+
+.dbg-variable-edit {
+  background: url("chrome://browser/skin/tabview/edit-light.png") center no-repeat;
+  width: 20px;
+  height: 16px;
+  cursor: pointer;
 }
 
 .dbg-variable-throbber {
-  background: url("chrome://global/skin/icons/loading_16.png");
+  background: url("chrome://global/skin/icons/loading_16.png") center no-repeat;
   width: 16px;
   height: 16px;
 }
 
 /**
  * Scope element
  */
 
+.scope:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+}
+
 .scope > .title {
   text-shadow: 0 1px #222;
   color: #fff;
 }
 
 .scope > .details {
   -moz-margin-start: 2px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
 }
 
 .scope > .details.nonenum:not(:empty) {
   border-top: 1px solid #ddd;
 }
 
 /**
  * Variable element
  */
 
 .variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
   border-bottom: 1px solid #eee;
-  background: #fff;
   transition: background 1s ease-in-out;
 }
 
 .variable[changed] {
   background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
 }
 
+.variable:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+  border-radius: 4px;
+}
+
 .variable > .title > .name {
+  font-weight: 600;
+}
+
+.variable:not(:focus) > .title > .name {
   color: #048;
-  font-weight: 600;
 }
 
 .variable > .title > .value {
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
+.variable[editable] > .title > .value {
+  cursor: text;
+}
+
 .variable:not([non-header]) > .details {
   -moz-margin-start: 10px;
 }
 
 /**
  * Property element
  */
 
 .property {
-  background: #fff;
   transition: background 1s ease-in-out;
 }
 
 .property[changed] {
   background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
 }
 
-.property > .title > .name {
+.property:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+  border-radius: 4px;
+}
+
+.property:not(:focus) > .title > .name {
   color: #881090;
 }
 
 .property > .title > .value {
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
+.property[editable] > .title > .value {
+  cursor: text;
+}
+
 .property:not([non-header]) > .details {
   -moz-margin-start: 10px;
 }
 
 /**
- * Non enumerable, configurable and writable variables and properties.
+ * Non enumerable, configurable and writable variables and properties
  */
 
 .variable[proto] > .title > .name,
 .property[proto] > .title > .name,
 .variable[non-enumerable]:not([self]):not([exception]) > .title > .name,
 .property[non-enumerable]:not([self]):not([exception]) > .title > .name {
   opacity: 0.5;
 }
@@ -416,18 +466,18 @@
 @media (min-resolution: 2dppx) {
   .variable[non-writable] > .title:after,
   .property[non-writable] > .title:after {
     background-image: url("chrome://browser/skin/identity-icons-https@2x.png");
     background-size: 32px;
   }
 }
 
-.variable[exception] > .title > .name,
-.property[exception] > .title > .name {
+.variable[exception]:not(:focus) > .title > .name,
+.property[exception]:not(:focus) > .title > .name {
   color: #a00;
   text-shadow: 0 0 8px #fcc;
 }
 
 .variable > tooltip > label,
 .property > tooltip > label {
   margin: 0 2px 0 2px;
 }
@@ -440,28 +490,33 @@
 .property[non-writable] > tooltip > label[value="writable"] {
   text-decoration: line-through;
 }
 
 /**
  * Variables and properties editing
  */
 
-#variables .element-value-input {
-  overflow: hidden;
-  max-width: 30em;
-  -moz-margin-start: 5px !important;
+.element-value-input {
+  -moz-margin-start: 4px !important;
 }
 
-#variables .element-name-input {
-  -moz-margin-start: -1px !important;
+.element-name-input {
+  -moz-margin-start: -2px !important;
   color: #048;
   font-weight: 600;
 }
 
+.element-value-input,
+.element-name-input {
+  max-width: 30em;
+  border: 1px solid #999 !important;
+  box-shadow: 1px 2px 4px #aaa;
+}
+
 /**
  * Variables and properties searching
  */
 
 .variables-searchinput.devtools-searchinput {
   min-height: 24px;
 }
 
@@ -470,38 +525,38 @@
   border: none;
   margin: 0;
 }
 
 /**
  * Token value colors
  */
 
-.token-undefined {
+.variable-or-property:not(:focus) > .title > .token-undefined {
   color: #bbb;
 }
 
-.token-null {
+.variable-or-property:not(:focus) > .title > .token-null {
   color: #999;
 }
 
-.token-boolean {
+.variable-or-property:not(:focus) > .title > .token-boolean {
   color: #777;
 }
 
-.token-number {
+.variable-or-property:not(:focus) > .title > .token-number {
   color: #c40a16;
 }
 
-.token-string {
+.variable-or-property:not(:focus) > .title > .token-string {
   max-width: 30em;
   color: #1c00cf;
 }
 
-.token-other {
+.variable-or-property:not(:focus) > .title > .token-other {
   color: #333;
 }
 
 /**
  * Expand/collapse arrow
  */
 
 .arrow {
--- a/browser/themes/winstripe/devtools/debugger.css
+++ b/browser/themes/winstripe/devtools/debugger.css
@@ -178,43 +178,40 @@
   color: #888;
 }
 
 /**
  * Stack frames and breakpoints pane
  */
 
 #stackframes\+breakpoints {
-  background-color: white;
   min-width: 50px;
 }
 
 #stackframes\+breakpoints[animated] {
   transition: margin 0.25s ease-in-out;
 }
 
 /**
  * Variables and watch expressions pane
  */
 
 #variables\+expressions {
-  background-color: white;
   min-width: 50px;
 }
 
 #variables\+expressions[animated] {
   transition: margin 0.25s ease-in-out;
 }
 
 /**
  * Stack frames view
  */
 
 #stackframes {
-  background-color: white;
   min-height: 10px;
 }
 
 .dbg-stackframe {
   -moz-padding-start: 4px;
   -moz-padding-end: 4px;
 }
 
@@ -226,17 +223,16 @@
   -moz-padding-start: 4px;
 }
 
 /**
  * Breakpoints view
  */
 
 #breakpoints {
-  background-color: white;
   min-height: 10px;
 }
 
 #breakpoints > vbox:not(:empty) {
   min-height: 10px;
   max-height: 200px;
 }
 
@@ -260,18 +256,18 @@
   margin: 0 0 -2px 0;
 }
 
 /**
  * Watch expressions view
  */
 
 #expressions {
-  background-color: white;
   min-height: 10px;
+  max-height: 125px;
 }
 
 .dbg-expression {
   height: 20px;
   -moz-padding-start: 8px;
 }
 
 .dbg-expression-arrow {
@@ -280,121 +276,176 @@
   background: url("chrome://browser/skin/devtools/commandline.png") 0px 4px no-repeat;
 }
 
 .dbg-expression-input {
   font: 9pt monospace;
 }
 
 .dbg-expression-delete {
+  opacity: 0;
+}
+
+.dbg-expression-delete:not(:hover) {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
-.dbg-expression-delete:not(:hover) {
+.dbg-expression-delete:hover {
+  opacity: 1;
+}
+
+.dbg-expression:hover > .dbg-expression-delete:not(:hover) {
   opacity: 0.5;
+  transition: opacity 0.2s ease-in-out;
 }
 
 /**
  * Variables view
  */
 
 #variables {
-  background-color: white;
   min-height: 10px;
 }
 
+.dbg-variable-delete {
+  opacity: 0;
+}
+
 .dbg-variable-delete:not(:hover) {
   -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.dbg-variable-delete:hover {
+  opacity: 1;
+}
+
+.variable-or-property:hover > .title > .dbg-variable-delete:not(:hover),
+.variable-or-property:focus > .title > .dbg-variable-delete:not(:hover) {
   opacity: 0.5;
+  transition: opacity 0.2s ease-in-out;
+}
+
+.dbg-variable-edit {
+  background: url("chrome://browser/skin/tabview/edit-light.png") center no-repeat;
+  width: 20px;
+  height: 16px;
+  cursor: pointer;
 }
 
 .dbg-variable-throbber {
-  background: url("chrome://global/skin/icons/loading_16.png");
+  background: url("chrome://global/skin/icons/loading_16.png") center no-repeat;
   width: 16px;
   height: 16px;
 }
 
 /**
  * Scope element
  */
 
+.scope:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+}
+
 .scope > .title {
   text-shadow: 0 1px #222;
   color: #fff;
 }
 
 .scope > .details {
   -moz-margin-start: 2px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
 }
 
 .scope > .details.nonenum:not(:empty) {
   border-top: 1px solid #ddd;
 }
 
 /**
  * Variable element
  */
 
 .variable {
   -moz-margin-start: 1px;
   -moz-margin-end: 1px;
   border-bottom: 1px solid #eee;
-  background: #fff;
   transition: background 1s ease-in-out;
 }
 
 .variable[changed] {
   background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
 }
 
+.variable:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+  border-radius: 4px;
+}
+
 .variable > .title > .name {
+  font-weight: 600;
+}
+
+.variable:not(:focus) > .title > .name {
   color: #048;
-  font-weight: 600;
 }
 
 .variable > .title > .value {
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
 }
 
+.variable[editable] > .title > .value {
+  cursor: text;
+}
+
 .variable:not([non-header]) > .details {
   -moz-margin-start: 10px;
 }
 
 /**
  * Property element
  */
 
 .property {
-  background: #fff;
   transition: background 1s ease-in-out;
 }
 
 .property[changed] {
   background: rgba(255, 255, 0, 0.65);
   transition-duration: 0.4s;
 }
 
-.property > .title > .name {
+.property:focus > .title {
+  background: Highlight;
+  color: HighlightText;
+  border-radius: 4px;
+}
+
+.property:not(:focus) > .title > .name {
   color: #881090;
 }
 
 .property > .title > .value {
   -moz-padding-start: 6px;
   -moz-padding-end: 4px;
+  cursor: text;
+}
+
+.property[editable] > .title > .value {
+  cursor: text;
 }
 
 .property:not([non-header]) > .details {
   -moz-margin-start: 10px;
 }
 
 /**
- * Non enumerable, configurable and writable variables and properties.
+ * Non enumerable, configurable and writable variables and properties
  */
 
 .variable[proto] > .title > .name,
 .property[proto] > .title > .name,
 .variable[non-enumerable]:not([self]):not([exception]) > .title > .name,
 .property[non-enumerable]:not([self]):not([exception]) > .title > .name {
   opacity: 0.5;
 }
@@ -422,18 +473,18 @@
 @media (min-resolution: 2dppx) {
   .variable[non-writable] > .title:after,
   .property[non-writable] > .title:after {
     background-image: url("chrome://browser/skin/identity-icons-https@2x.png");
     background-size: 32px;
   }
 }
 
-.variable[exception] > .title > .name,
-.property[exception] > .title > .name {
+.variable[exception]:not(:focus) > .title > .name,
+.property[exception]:not(:focus) > .title > .name {
   color: #a00;
   text-shadow: 0 0 8px #fcc;
 }
 
 .variable > tooltip > label,
 .property > tooltip > label {
   margin: 0 2px 0 2px;
 }
@@ -446,28 +497,33 @@
 .property[non-writable] > tooltip > label[value="writable"] {
   text-decoration: line-through;
 }
 
 /**
  * Variables and properties editing
  */
 
-#variables .element-value-input {
-  overflow: hidden;
-  max-width: 30em;
-  -moz-margin-start: 5px !important;
+.element-value-input {
+  -moz-margin-start: 4px !important;
 }
 
-#variables .element-name-input {
-  -moz-margin-start: -1px !important;
+.element-name-input {
+  -moz-margin-start: -2px !important;
   color: #048;
   font-weight: 600;
 }
 
+.element-value-input,
+.element-name-input {
+  max-width: 30em;
+  border: 1px solid #999 !important;
+  box-shadow: 1px 2px 4px #aaa;
+}
+
 /**
  * Variables and properties searching
  */
 
 .variables-searchinput.devtools-searchinput {
   min-height: 24px;
 }
 
@@ -476,38 +532,38 @@
   border: none;
   margin: 0;
 }
 
 /**
  * Token value colors
  */
 
-.token-undefined {
+.variable-or-property:not(:focus) > .title > .token-undefined {
   color: #bbb;
 }
 
-.token-null {
+.variable-or-property:not(:focus) > .title > .token-null {
   color: #999;
 }
 
-.token-boolean {
+.variable-or-property:not(:focus) > .title > .token-boolean {
   color: #777;
 }
 
-.token-number {
+.variable-or-property:not(:focus) > .title > .token-number {
   color: #c40a16;
 }
 
-.token-string {
+.variable-or-property:not(:focus) > .title > .token-string {
   max-width: 30em;
   color: #1c00cf;
 }
 
-.token-other {
+.variable-or-property:not(:focus) > .title > .token-other {
   color: #333;
 }
 
 /**
  * Expand/collapse arrow
  */
 
 .arrow {