Bug 734365 - Rule view focus management needs an overhaul. r=paul
authorDave Camp <dcamp@mozilla.com>
Fri, 01 Jun 2012 15:13:48 -0700
changeset 95400 85c153eea9d4f26a1c459c1bc152e11cc5f1e4b9
parent 95399 1bc2c4e1493b3db5f2e260d01acfc64f0ee90d8a
child 95401 13d899f2545494d17090c8ed5156af649e5c8807
push id817
push userdcamp@campd.org
push dateFri, 01 Jun 2012 22:14:18 +0000
treeherderfx-team@13d899f25454 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaul
bugs734365
milestone15.0a1
Bug 734365 - Rule view focus management needs an overhaul. r=paul
browser/devtools/styleinspector/CssRuleView.jsm
browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
browser/devtools/styleinspector/test/browser_ruleview_editor.js
browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
browser/devtools/styleinspector/test/browser_ruleview_focus.js
browser/devtools/styleinspector/test/browser_ruleview_ui.js
browser/devtools/styleinspector/test/head.js
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -863,25 +863,16 @@ TextProperty.prototype = {
 function CssRuleView(aDoc, aStore)
 {
   this.doc = aDoc;
   this.store = aStore;
   this.element = this.doc.createElementNS(XUL_NS, "vbox");
   this.element.setAttribute("tabindex", "0");
   this.element.classList.add("ruleview");
   this.element.flex = 1;
-  this._selectionMode = false;
-
-  this._boundMouseDown = this._onMouseDown.bind(this);
-  this.element.addEventListener("mousedown",
-                                this._boundMouseDown);
-  this._boundMouseUp = this._onMouseUp.bind(this);
-  this.element.addEventListener("mouseup",
-                                this._boundMouseUp);
-  this._boundMouseMove = this._onMouseMove.bind(this);
 
   this._boundCopy = this._onCopy.bind(this);
   this.element.addEventListener("copy", this._boundCopy);
 
   this._createContextMenu();
   this._showEmpty();
 }
 
@@ -943,34 +934,16 @@ CssRuleView.prototype = {
     }
 
     this._elementStyle = new ElementStyle(aElement, this.store);
     this._elementStyle.onChanged = function() {
       this._changed();
     }.bind(this);
 
     this._createEditors();
-
-    // When creating a new property, we fake the normal property
-    // editor behavior (focusing a property's value after entering its
-    // name) by responding to the name's blur event, creating the
-    // value editor, and grabbing focus to the value editor.  But if
-    // focus has already moved to another document, we won't be able
-    // to move focus to the new editor.
-    // Create a focusable item at the end of the editors to catch these
-    // cases.
-    this._focusBackstop = createChild(this.element, "div", {
-      tabindex: 0,
-    });
-    this._backstopHandler = function() {
-      // If this item is actually focused long enough to get the focus
-      // event, allow focus to move on out of this document.
-      moveFocus(this.doc.defaultView, FOCUS_FORWARD);
-    }.bind(this);
-    this._focusBackstop.addEventListener("focus", this._backstopHandler, false);
   },
 
   /**
    * Update the rules for the currently highlighted element.
    */
   nodeChanged: function CssRuleView_nodeChanged()
   {
     // Repopulate the element style.
@@ -1008,22 +981,16 @@ CssRuleView.prototype = {
   /**
    * Clear the rule view.
    */
   clear: function CssRuleView_clear()
   {
     this._clearRules();
     this._viewedElement = null;
     this._elementStyle = null;
-
-    if (this._focusBackstop) {
-      this._focusBackstop.removeEventListener("focus", this._backstopHandler, false);
-      this._backstopHandler = null;
-      this._focusBackstop = null;
-    }
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
   _changed: function CssRuleView_changed()
   {
@@ -1147,32 +1114,16 @@ CssRuleView.prototype = {
       !node.classList.contains("ruleview-property") &&
       !node.classList.contains("ruleview-computed"));
 
     this._declarationItem.disabled = disablePropertyItems;
     this._propertyItem.disabled = disablePropertyItems;
     this._propertyValueItem.disabled = disablePropertyItems;
   },
 
-  _onMouseDown: function CssRuleView_onMouseDown()
-  {
-    this.element.addEventListener("mousemove", this._boundMouseMove);
-  },
-
-  _onMouseUp: function CssRuleView_onMouseUp()
-  {
-    this.element.removeEventListener("mousemove", this._boundMouseMove);
-    this._selectionMode = false;
-  },
-
-  _onMouseMove: function CssRuleView_onMouseMove()
-  {
-    this._selectionMode = true;
-  },
-
   /**
    * Copy selected text from the rule view.
    *
    * @param aEvent The event object
    */
   _onCopy: function CssRuleView_onCopy(aEvent)
   {
     let win = this.doc.defaultView;
@@ -1337,30 +1288,30 @@ CssRuleView.prototype = {
     }
   }
 };
 
 /**
  * Create a RuleEditor.
  *
  * @param CssRuleView aRuleView
- *        The CssRuleView containg the document holding this rule editor and the
- *        _selectionMode flag.
+ *        The CssRuleView containg the document holding this rule editor.
  * @param Rule aRule
  *        The Rule object we're editing.
  * @constructor
  */
 function RuleEditor(aRuleView, aRule)
 {
   this.ruleView = aRuleView;
   this.doc = this.ruleView.doc;
   this.rule = aRule;
   this.rule.editor = this;
 
   this._onNewProperty = this._onNewProperty.bind(this);
+  this._newPropertyDestroy = this._newPropertyDestroy.bind(this);
 
   this._create();
 }
 
 RuleEditor.prototype = {
   _create: function RuleEditor_create()
   {
     this.element = this.doc.createElementNS(HTML_NS, "div");
@@ -1411,29 +1362,20 @@ RuleEditor.prototype = {
     this.populate();
 
     this.closeBrace = createChild(code, "div", {
       class: "ruleview-ruleclose",
       tabindex: "0",
       textContent: "}"
     });
 
-    // We made the close brace focusable, tabbing to it
-    // or clicking on it should start the new property editor.
-    this.closeBrace.addEventListener("focus", function(aEvent) {
-      if (!this.ruleView._selectionMode) {
-        this.newProperty();
-      }
-    }.bind(this), true);
-    this.closeBrace.addEventListener("mousedown", function(aEvent) {
-      aEvent.preventDefault();
-    }.bind(this), true);
-    this.closeBrace.addEventListener("click", function(aEvent) {
-      this.closeBrace.focus();
-    }.bind(this), true);
+    // Create a property editor when the close brace is clicked.
+    editableItem(this.closeBrace, function(aElement) {
+      this.newProperty();
+    }.bind(this));
   },
 
   /**
    * Update the rule editor with the contents of the rule.
    */
   populate: function RuleEditor_populate()
   {
     this.selectorText.textContent = this.rule.selectorText;
@@ -1475,45 +1417,62 @@ RuleEditor.prototype = {
     // close brace for now.
     this.closeBrace.removeAttribute("tabindex");
 
     this.newPropItem = createChild(this.propertyList, "li", {
       class: "ruleview-property ruleview-newproperty",
     });
 
     this.newPropSpan = createChild(this.newPropItem, "span", {
-      class: "ruleview-propertyname"
+      class: "ruleview-propertyname",
+      tabindex: "0"
     });
 
     new InplaceEditor({
       element: this.newPropSpan,
       done: this._onNewProperty,
+      destroy: this._newPropertyDestroy,
       advanceChars: ":"
     });
   },
 
-  _onNewProperty: function RuleEditor_onNewProperty(aValue, aCommit)
+  /**
+   * Called when the new property input has been dismissed.
+   * Will create a new TextProperty if necessary.
+   *
+   * @param string aValue
+   *        The value in the editor.
+   * @param bool aCommit
+   *        True if the value should be committed.
+   */
+  _onNewProperty: function RuleEditor__onNewProperty(aValue, aCommit)
   {
-    // We're done, make the close brace focusable again.
-    this.closeBrace.setAttribute("tabindex", "0");
-
-    this.propertyList.removeChild(this.newPropItem);
-    delete this.newPropItem;
-    delete this.newPropSpan;
-
     if (!aValue || !aCommit) {
       return;
     }
 
     // Create an empty-valued property and start editing it.
     let prop = this.rule.createProperty(aValue, "", "");
     let editor = new TextPropertyEditor(this, prop);
     this.propertyList.appendChild(editor.element);
-    editor.valueSpan.focus();
+    editor.valueSpan.click();
   },
+
+  /**
+   * Called when the new property editor is destroyed.
+   */
+  _newPropertyDestroy: function RuleEditor__newPropertyDestroy()
+  {
+    // We're done, make the close brace focusable again.
+    this.closeBrace.setAttribute("tabindex", "0");
+
+    this.propertyList.removeChild(this.newPropItem);
+    delete this.newPropItem;
+    delete this.newPropSpan;
+  }
 };
 
 /**
  * Create a TextPropertyEditor.
  *
  * @param {RuleEditor} aRuleEditor
  *        The rule editor that owns this TextPropertyEditor.
  * @param {TextProperty} aProperty
@@ -1819,54 +1778,94 @@ TextPropertyEditor.prototype = {
  *    {function} start:
  *       Will be called when the inplace editor is initialized.
  *    {function} change:
  *       Will be called when the text input changes.  Will be called
  *       with the current value of the text input.
  *    {function} done:
  *       Called when input is committed or blurred.  Called with
  *       current value and a boolean telling the caller whether to
- *       commit the change.  This function is called after the editor
+ *       commit the change.  This function is called before the editor
  *       has been torn down.
+ *    {function} destroy:
+ *       Called when the editor is destroyed and has been torn down.
  *    {string} advanceChars:
  *       If any characters in advanceChars are typed, focus will advance
  *       to the next element.
  */
 function editableField(aOptions)
 {
-  aOptions.element.addEventListener("focus", function() {
+  editableItem(aOptions.element, function(aElement) {
     new InplaceEditor(aOptions);
-  }, false);
+  });
+}
 
-  // In order to allow selection on the element, prevent focus on
-  // mousedown.  Focus on click instead.
-  aOptions.element.addEventListener("mousedown", function(evt) {
-    evt.preventDefault();
-  }, false);
-  aOptions.element.addEventListener("click", function(evt) {
+/**
+ * Handle events for an element that should respond to
+ * clicks and sit in the editing tab order, and call
+ * a callback when it is activated.
+ *
+ * @param DOMElement aElement
+ *        The DOM element.
+ * @param function aCallback
+ *        Called when the editor is activated.
+ */
+
+function editableItem(aElement, aCallback)
+{
+  aElement.addEventListener("click", function() {
     let win = this.ownerDocument.defaultView;
     let selection = win.getSelection();
     if (selection.isCollapsed) {
-      aOptions.element.focus();
-    } else {
-      selection.removeAllRanges();
+      aCallback(aElement);
     }
   }, false);
+
+  // If focused by means other than a click, start editing by
+  // pressing enter or space.
+  aElement.addEventListener("keypress", function(evt) {
+    if (evt.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
+        evt.charCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
+      aCallback(aElement);
+    }
+  }, true);
+
+  // Ugly workaround - the element is focused on mousedown but
+  // the editor is activated on click/mouseup.  This leads
+  // to an ugly flash of the focus ring before showing the editor.
+  // So hide the focus ring while the mouse is down.
+  aElement.addEventListener("mousedown", function(evt) {
+    let cleanup = function() {
+      aElement.style.removeProperty("outline-style");
+      aElement.removeEventListener("mouseup", cleanup, false);
+      aElement.removeEventListener("mouseout", cleanup, false);
+    };
+    aElement.style.setProperty("outline-style", "none");
+    aElement.addEventListener("mouseup", cleanup, false);
+    aElement.addEventListener("mouseout", cleanup, false);
+  }, false);
+
+  // Mark the element editable field for tab
+  // navigation while editing.
+  aElement._editable = true;
 }
+
 var _editableField = editableField;
 
 function InplaceEditor(aOptions)
 {
   this.elt = aOptions.element;
+  let doc = this.elt.ownerDocument;
+  this.doc = doc;
   this.elt.inplaceEditor = this;
 
   this.change = aOptions.change;
   this.done = aOptions.done;
+  this.destroy = aOptions.destroy;
   this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
-  this.doc = this.elt.ownerDocument;
 
   this._onBlur = this._onBlur.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
   this._onInput = this._onInput.bind(this);
 
   this._createInput();
   this._autosize();
 
@@ -1906,23 +1905,34 @@ InplaceEditor.prototype = {
     copyTextStyles(this.elt, this.input);
   },
 
   /**
    * Get rid of the editor.
    */
   _clear: function InplaceEditor_clear()
   {
+    if (!this.input) {
+      // Already cleared.
+      return;
+    }
+
     this.input.removeEventListener("blur", this._onBlur, false);
     this.input.removeEventListener("keypress", this._onKeyPress, false);
     this.input.removeEventListener("oninput", this._onInput, false);
     this._stopAutosize();
 
+    this.elt.style.display = this.originalDisplay;
+    this.elt.focus();
+
+    if (this.destroy) {
+      this.destroy();
+    }
+
     this.elt.parentNode.removeChild(this.input);
-    this.elt.style.display = this.originalDisplay;
     this.input = null;
 
     delete this.elt.inplaceEditor;
     delete this.elt;
   },
 
   /**
    * Keeps the editor close to the size of its input string.  This is pretty
@@ -1975,44 +1985,81 @@ InplaceEditor.prototype = {
     // any letter that could be typed, otherwise we'll scroll before
     // we get a chance to resize.  Yuck.
     let width = this._measurement.offsetWidth + 10;
 
     this.input.style.width = width + "px";
   },
 
   /**
-   * Handle loss of focus by calling the client's done handler and
-   * clearing out.
+   * Call the client's done handler and clear out.
+   */
+  _apply: function InplaceEditor_apply(aEvent)
+  {
+    if (this._applied) {
+      return;
+    }
+
+    this._applied = true;
+
+    if (this.done) {
+      let val = this.input.value.trim();
+      return this.done(this.cancelled ? this.initial : val, !this.cancelled);
+    }
+    return null;
+  },
+
+  /**
+   * Handle loss of focus by calling done if it hasn't been called yet.
    */
   _onBlur: function InplaceEditor_onBlur(aEvent)
   {
-    let val = this.input.value.trim();
+    this._apply();
     this._clear();
-    if (this.done) {
-      this.done(this.cancelled ? this.initial : val, !this.cancelled);
-    }
   },
 
   _onKeyPress: function InplaceEditor_onKeyPress(aEvent)
   {
     let prevent = false;
     if (aEvent.charCode in this._advanceCharCodes
-       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
-      // Focus the next element, triggering a blur which
-      // will eventually shut us down (making return roughly equal
-      // tab).
+       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN
+       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) {
       prevent = true;
-      moveFocus(this.input.ownerDocument.defaultView, FOCUS_FORWARD);
+
+      let direction = FOCUS_FORWARD;
+      if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
+          aEvent.shiftKey) {
+        this.cancelled = true;
+        direction = FOCUS_BACKWARD;
+      }
+
+      let input = this.input;
+
+      this._apply();
+
+      let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+      if (fm.focusedElement === input) {
+        // If the focused element wasn't changed by the done callback,
+        // move the focus as requested.
+        let next = moveFocus(this.doc.defaultView, direction);
+
+        // If the next node to be focused has been tagged as an editable
+        // node, send it a click event to trigger
+        if (next && next.ownerDocument === this.doc && next._editable) {
+          next.click();
+        }
+      }
+
+      this._clear();
     } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
-      // Cancel and blur ourselves.  |_onBlur| will call the user's
-      // done handler for us.
+      // Cancel and blur ourselves.
       prevent = true;
       this.cancelled = true;
-      this.input.blur();
+      this._apply();
+      this._clear();
       aEvent.stopPropagation();
     } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
       // No need for leading spaces here.  This is particularly
       // noticable when adding a property: it's very natural to type
       // <name>: (which advances to the next property) then spacebar.
       prevent = !this.input.value;
     }
 
@@ -2189,17 +2236,17 @@ function copyTextStyles(aFrom, aTo)
 }
 
 /**
  * Trigger a focus change similar to pressing tab/shift-tab.
  */
 function moveFocus(aWin, aDirection)
 {
   let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-  fm.moveFocus(aWin, null, aDirection, 0);
+  return fm.moveFocus(aWin, null, aDirection, 0);
 }
 
 XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function() {
   return Cc["@mozilla.org/widget/clipboardhelper;1"].
     getService(Ci.nsIClipboardHelper);
 });
 
 XPCOMUtils.defineLazyGetter(this, "_strings", function() {
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
@@ -46,40 +46,16 @@ function openInspector()
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
   ok(InspectorUI.store.isEmpty(), "Inspector.store is empty");
 
   Services.obs.addObserver(inspectorUIOpen,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.openInspectorUI();
 }
 
-function waitForEditorFocus(aParent, aCallback)
-{
-  aParent.addEventListener("focus", function onFocus(evt) {
-    if (inplaceEditor(evt.target)) {
-      aParent.removeEventListener("focus", onFocus, true);
-      let editor = inplaceEditor(evt.target);
-      executeSoon(function() {
-        aCallback(editor);
-      });
-    }
-  }, true);
-}
-
-function waitForEditorBlur(aEditor, aCallback)
-{
-  let input = aEditor.input;
-  input.addEventListener("blur", function onBlur() {
-    input.removeEventListener("blur", onBlur, false);
-    executeSoon(function() {
-      aCallback();
-    });
-  }, false);
-}
-
 function inspectorUIOpen()
 {
   Services.obs.removeObserver(inspectorUIOpen,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
   ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
--- a/browser/devtools/styleinspector/test/browser_ruleview_editor.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_editor.js
@@ -45,33 +45,33 @@ function testReturnCommit()
     initial: "explicit initial",
     start: function() {
       is(inplaceEditor(span).input.value, "explicit initial", "Explicit initial value should be used.");
       inplaceEditor(span).input.value = "Test Value";
       EventUtils.sendKey("return");
     },
     done: expectDone("Test Value", true, testBlurCommit)
   });
-  span.focus();
+  span.click();
 }
 
 function testBlurCommit()
 {
   clearBody();
   let span = createSpan();
   _editableField({
     element: span,
     start: function() {
       is(inplaceEditor(span).input.value, "Edit Me!", "textContent of the span used.");
       inplaceEditor(span).input.value = "Test Value";
       inplaceEditor(span).input.blur();
     },
     done: expectDone("Test Value", true, testAdvanceCharCommit)
   });
-  span.focus();
+  span.click();
 }
 
 function testAdvanceCharCommit()
 {
   clearBody();
   let span = createSpan();
   _editableField({
     element: span,
@@ -79,33 +79,33 @@ function testAdvanceCharCommit()
     start: function() {
       let input = inplaceEditor(span).input;
       for each (let ch in "Test:") {
         EventUtils.sendChar(ch);
       }
     },
     done: expectDone("Test", true, testEscapeCancel)
   });
-  span.focus();
+  span.click();
 }
 
 function testEscapeCancel()
 {
   clearBody();
   let span = createSpan();
   _editableField({
     element: span,
     initial: "initial text",
     start: function() {
       inplaceEditor(span).input.value = "Test Value";
       EventUtils.sendKey("escape");
     },
     done: expectDone("initial text", false, finishTest)
   });
-  span.focus();
+  span.click();
 }
 
 
 function finishTest()
 {
   doc = null;
   gBrowser.removeCurrentTab();
   finish();
--- a/browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_editor_changedvalues.js
@@ -8,40 +8,16 @@ let CssRuleView = tempScope.CssRuleView;
 let _ElementStyle = tempScope._ElementStyle;
 let _editableField = tempScope._editableField;
 let inplaceEditor = tempScope._getInplaceEditorForSpan;
 
 let doc;
 let ruleDialog;
 let ruleView;
 
-function waitForEditorFocus(aParent, aCallback)
-{
-  aParent.addEventListener("focus", function onFocus(evt) {
-    if (inplaceEditor(evt.target)) {
-      aParent.removeEventListener("focus", onFocus, true);
-      let editor = inplaceEditor(evt.target);
-      executeSoon(function() {
-        aCallback(editor);
-      });
-    }
-  }, true);
-}
-
-function waitForEditorBlur(aEditor, aCallback)
-{
-  let input = aEditor.input;
-  input.addEventListener("blur", function onBlur() {
-    input.removeEventListener("blur", onBlur, false);
-    executeSoon(function() {
-      aCallback();
-    });
-  }, false);
-}
-
 var gRuleViewChanged = false;
 function ruleViewChanged()
 {
   gRuleViewChanged = true;
 }
 
 function expectChange()
 {
@@ -110,17 +86,16 @@ function testCreateNew()
     input.value = "background-color";
 
     waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
       expectChange();
       is(elementRuleEditor.rule.textProps.length,  1, "Should have created a new text property.");
       is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
       let textProp = elementRuleEditor.rule.textProps[0];
       is(aEditor, inplaceEditor(textProp.editor.valueSpan), "Should be editing the value span now.");
-
       aEditor.input.value = "#XYZ";
       waitForEditorBlur(aEditor, function() {
         expectChange();
         is(textProp.value, "#XYZ", "Text prop should have been changed.");
         is(textProp.editor._validate(), false, "#XYZ should not be a valid entry");
         testEditProperty();
       });
       aEditor.input.blur();
--- a/browser/devtools/styleinspector/test/browser_ruleview_focus.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_focus.js
@@ -11,29 +11,16 @@ let inplaceEditor = tempScope._getInplac
 let doc;
 let stylePanel;
 
 function waitForRuleView(aCallback)
 {
   InspectorUI.currentInspector.once("sidebaractivated-ruleview", aCallback);
 }
 
-function waitForEditorFocus(aParent, aCallback)
-{
-  aParent.addEventListener("focus", function onFocus(evt) {
-    if (inplaceEditor(evt.target)) {
-      aParent.removeEventListener("focus", onFocus, true);
-      let editor = inplaceEditor(evt.target);
-      executeSoon(function() {
-        aCallback(editor);
-      });
-    }
-  }, true);
-}
-
 function openRuleView()
 {
   Services.obs.addObserver(function onOpened() {
     Services.obs.removeObserver(onOpened,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
     // Highlight a node.
     let node = content.document.getElementsByTagName("h1")[0];
@@ -68,17 +55,17 @@ function testFocus()
         aEditor.input.blur();
         finishUp();
       });
       EventUtils.sendKey("return");
     });
     EventUtils.sendKey("return");
   });
 
-  brace.focus();
+  brace.click();
 }
 
 function finishUp()
 {
   InspectorUI.sidebar.hide();
   InspectorUI.closeInspectorUI();
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -8,40 +8,16 @@ let CssRuleView = tempScope.CssRuleView;
 let _ElementStyle = tempScope._ElementStyle;
 let _editableField = tempScope._editableField;
 let inplaceEditor = tempScope._getInplaceEditorForSpan;
 
 let doc;
 let ruleDialog;
 let ruleView;
 
-function waitForEditorFocus(aParent, aCallback)
-{
-  aParent.addEventListener("focus", function onFocus(evt) {
-    if (inplaceEditor(evt.target)) {
-      aParent.removeEventListener("focus", onFocus, true);
-      let editor = inplaceEditor(evt.target);
-      executeSoon(function() {
-        aCallback(editor);
-      });
-    }
-  }, true);
-}
-
-function waitForEditorBlur(aEditor, aCallback)
-{
-  let input = aEditor.input;
-  input.addEventListener("blur", function onBlur() {
-    input.removeEventListener("blur", onBlur, false);
-    executeSoon(function() {
-      aCallback();
-    });
-  }, false);
-}
-
 var gRuleViewChanged = false;
 function ruleViewChanged()
 {
   gRuleViewChanged = true;
 }
 
 function expectChange()
 {
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -300,12 +300,36 @@ ComputedViewPanel.prototype = {
   },
 };
 
 function ruleView()
 {
   return InspectorUI.sidebar._toolContext("ruleview").view;
 }
 
+function waitForEditorFocus(aParent, aCallback)
+{
+  aParent.addEventListener("focus", function onFocus(evt) {
+    if (inplaceEditor(evt.target) && evt.target.tagName == "input") {
+      aParent.removeEventListener("focus", onFocus, true);
+      let editor = inplaceEditor(evt.target);
+      executeSoon(function() {
+        aCallback(editor);
+      });
+    }
+  }, true);
+}
+
+function waitForEditorBlur(aEditor, aCallback)
+{
+  let input = aEditor.input;
+  input.addEventListener("blur", function onBlur() {
+    input.removeEventListener("blur", onBlur, false);
+    executeSoon(function() {
+      aCallback();
+    });
+  }, false);
+}
+
 registerCleanupFunction(tearDown);
 
 waitForExplicitFinish();