Bug 831794 - Variables View: allow users to override getter properties to plain value properties, r=past
authorVictor Porof <vporof@mozilla.com>
Thu, 31 Jan 2013 18:07:24 +0200
changeset 120472 af82229821f8
parent 120471 d3156a1adca7
child 120473 03c38d3c4182
push id24253
push userttaubert@mozilla.com
push dateFri, 01 Feb 2013 15:17:09 +0000
treeherdermozilla-central@a4f8cb70cc5d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspast
bugs831794
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
Bug 831794 - Variables View: allow users to override getter properties to plain value properties, r=past
browser/devtools/debugger/test/Makefile.in
browser/devtools/debugger/test/browser_dbg_frame-parameters.html
browser/devtools/debugger/test/browser_dbg_propertyview-07.js
browser/devtools/debugger/test/browser_dbg_propertyview-08.js
browser/devtools/debugger/test/browser_dbg_propertyview-09.js
browser/devtools/debugger/test/browser_dbg_propertyview-10.js
browser/devtools/debugger/test/browser_dbg_propertyview-big-data.js
browser/devtools/debugger/test/browser_dbg_propertyview-data-big.js
browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-01.js
browser/devtools/debugger/test/browser_dbg_propertyview-data-getset-02.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit-value.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit.js
browser/devtools/shared/VariablesView.jsm
browser/locales/en-US/chrome/browser/devtools/debugger.properties
browser/themes/gnomestripe/devtools/debugger.css
browser/themes/pinstripe/devtools/debugger.css
browser/themes/winstripe/devtools/debugger.css
--- 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_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-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'.");
--- 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;
+});
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/shared/VariablesView.jsm
+++ b/browser/devtools/shared/VariablesView.jsm
@@ -220,16 +220,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"),
 
@@ -880,16 +890,143 @@ 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]
@@ -906,16 +1043,17 @@ function Scope(aView, aName, aFlags = {}
 
   // 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);
 }
@@ -1631,16 +1769,17 @@ 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,
@@ -1676,20 +1815,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 }, {
@@ -1881,16 +2016,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" };
     }
 
@@ -1960,42 +2100,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.
@@ -2026,16 +2183,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);
@@ -2051,17 +2211,17 @@ create({ constructor: Variable, proto: S
     let name = this._nameString;
 
     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", "");
     }
@@ -2153,16 +2313,19 @@ create({ constructor: Variable, proto: S
     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;
   },
 
@@ -2189,16 +2352,19 @@ create({ constructor: Variable, proto: S
     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.
@@ -2209,21 +2375,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");
@@ -2243,21 +2416,27 @@ 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:
@@ -2286,33 +2465,46 @@ create({ constructor: Variable, proto: S
       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(e) {
     e.preventDefault();
     e.stopPropagation();
 
     if (this.ownerView.delete) {
-      this.ownerView.delete(this);
-      this.hide();
+      if (!this.ownerView.delete(this)) {
+        this.hide();
+      }
     }
   },
 
   _symbolicName: "",
   _absoluteName: "",
   _initialDescriptor: null,
+  _isPrimitive: false,
+  _isUndefined: false,
   _separatorLabel: null,
   _valueLabel: null,
+  _editNode: null,
   _deleteNode: null,
   _tooltip: null,
   _valueGrip: null,
   _valueString: "",
   _valueClassName: "",
   _prevExpandable: false,
   _prevExpanded: false
 });
--- 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=\ →
--- a/browser/themes/gnomestripe/devtools/debugger.css
+++ b/browser/themes/gnomestripe/devtools/debugger.css
@@ -289,18 +289,25 @@
   background-color: white;
   min-height: 10px;
 }
 
 .dbg-variable-delete:not(:hover) {
   opacity: 0.5;
 }
 
+.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
  */
 
@@ -311,17 +318,17 @@
 
 .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
--- a/browser/themes/pinstripe/devtools/debugger.css
+++ b/browser/themes/pinstripe/devtools/debugger.css
@@ -296,18 +296,25 @@
   min-height: 10px;
 }
 
 .dbg-variable-delete:not(:hover) {
   -moz-image-region: rect(0, 32px, 16px, 16px);
   opacity: 0.5;
 }
 
+.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
  */
 
@@ -318,17 +325,17 @@
 
 .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
--- a/browser/themes/winstripe/devtools/debugger.css
+++ b/browser/themes/winstripe/devtools/debugger.css
@@ -302,18 +302,25 @@
   min-height: 10px;
 }
 
 .dbg-variable-delete:not(:hover) {
   -moz-image-region: rect(0, 32px, 16px, 16px);
   opacity: 0.5;
 }
 
+.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
  */
 
@@ -324,17 +331,17 @@
 
 .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