Bug 886035 - Port markup view attribute editing to the inspector actor. r=paul
authorDave Camp <dcamp@mozilla.com>
Mon, 17 Jun 2013 06:52:55 -0700
changeset 151600 89fd81b99fcfa2881b850343da680b9d5a975d65
parent 151599 0c1e36a643b8a4e194f6bcc71b84173bd6ba20b2
child 151601 89e4e5c1581957b38d50ba323562a22b33f31287
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaul
bugs886035
milestone25.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 886035 - Port markup view attribute editing to the inspector actor. r=paul
browser/devtools/markupview/markup-view.js
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -1101,47 +1101,53 @@ function ElementEditor(aContainer, aNode
     this.template("elementContentSummary", this);
   }
 
   // Create the closing tag
   this.template("elementClose", this);
 
   this.rawNode = aNode.rawNode();
 
-  // Make the tag name editable (unless this is a document element)
-  if (this.rawNode) {
-    if (!aNode.isDocumentElement) {
-      this.tag.setAttribute("tabindex", "0");
-      editableField({
-        element: this.tag,
-        trigger: "dblclick",
-        stopOnReturn: true,
-        done: this.onTagEdit.bind(this),
-      });
-    }
-
-    // Make the new attribute space editable.
+  // Make the tag name editable (unless this is a remote node or
+  // a document element)
+  if (this.rawNode && !aNode.isDocumentElement) {
+    this.tag.setAttribute("tabindex", "0");
     editableField({
-      element: this.newAttr,
+      element: this.tag,
       trigger: "dblclick",
       stopOnReturn: true,
-      done: function EE_onNew(aVal, aCommit) {
-        if (!aCommit) {
-          return;
-        }
-
-        try {
-          this._applyAttributes(aVal);
-        } catch (x) {
-          return;
-        }
-      }.bind(this)
+      done: this.onTagEdit.bind(this),
     });
   }
 
+  // Make the new attribute space editable.
+  editableField({
+    element: this.newAttr,
+    trigger: "dblclick",
+    stopOnReturn: true,
+    done: (aVal, aCommit) => {
+      if (!aCommit) {
+        return;
+      }
+
+      try {
+        let doMods = this._startModifyingAttributes();
+        let undoMods = this._startModifyingAttributes();
+        this._applyAttributes(aVal, null, doMods, undoMods);
+        this.undo.do(() => {
+          doMods.apply();
+        }, function() {
+          undoMods.apply();
+        });
+      } catch(x) {
+        console.log(x);
+      }
+    }
+  });
+
   let tagName = this.node.nodeName.toLowerCase();
   this.tag.textContent = tagName;
   this.closeTag.textContent = tagName;
 
   this.update();
 }
 
 ElementEditor.prototype = {
@@ -1170,16 +1176,20 @@ ElementEditor.prototype = {
     for (let i = 0; i < attrs.length; i++) {
       let attr = this._createAttribute(attrs[i]);
       if (!attr.inplaceEditor) {
         attr.style.removeProperty("display");
       }
     }
   },
 
+  _startModifyingAttributes: function() {
+    return this.node.startModifyingAttributes();
+  },
+
   _createAttribute: function EE_createAttribute(aAttr, aBefore)
   {
     if (this.attrs.indexOf(aAttr.name) !== -1) {
       var attr = this.attrs[aAttr.name];
       var name = attr.querySelector(".attrname");
       var val = attr.querySelector(".attrvalue");
     } else {
       // Create the template editor, which will save some variables here.
@@ -1194,59 +1204,61 @@ ElementEditor.prototype = {
       if (aAttr.name == "id") {
         before = this.attrList.firstChild;
       } else if (aAttr.name == "class") {
         let idNode = this.attrs["id"];
         before = idNode ? idNode.nextSibling : this.attrList.firstChild;
       }
       this.attrList.insertBefore(attr, before);
 
-      if (this.rawNode) {
+      // Make the attribute editable.
+      editableField({
+        element: inner,
+        trigger: "dblclick",
+        stopOnReturn: true,
+        selectAll: false,
+        start: (aEditor, aEvent) => {
+          // If the editing was started inside the name or value areas,
+          // select accordingly.
+          if (aEvent && aEvent.target === name) {
+            aEditor.input.setSelectionRange(0, name.textContent.length);
+          } else if (aEvent && aEvent.target === val) {
+            let length = val.textContent.length;
+            let editorLength = aEditor.input.value.length;
+            let start = editorLength - (length + 1);
+            aEditor.input.setSelectionRange(start, start + length);
+          } else {
+            aEditor.input.select();
+          }
+        },
+        done: (aVal, aCommit) => {
+          if (!aCommit) {
+            return;
+          }
 
-        // Make the attribute editable.
-        editableField({
-          element: inner,
-          trigger: "dblclick",
-          stopOnReturn: true,
-          selectAll: false,
-          start: function EE_editAttribute_start(aEditor, aEvent) {
-            // If the editing was started inside the name or value areas,
-            // select accordingly.
-            if (aEvent && aEvent.target === name) {
-              aEditor.input.setSelectionRange(0, name.textContent.length);
-            } else if (aEvent && aEvent.target === val) {
-              let length = val.textContent.length;
-              let editorLength = aEditor.input.value.length;
-              let start = editorLength - (length + 1);
-              aEditor.input.setSelectionRange(start, start + length);
-            } else {
-              aEditor.input.select();
-            }
-          },
-          done: function EE_editAttribute_done(aVal, aCommit) {
-            if (!aCommit) {
-              return;
-            }
+          let doMods = this._startModifyingAttributes();
+          let undoMods = this._startModifyingAttributes();
 
-            this.undo.startBatch();
-
-            // Remove the attribute stored in this editor and re-add any attributes
-            // parsed out of the input element. Restore original attribute if
-            // parsing fails.
-            this._removeAttribute(this.rawNode, aAttr.name);
-            try {
-              this._applyAttributes(aVal, attr);
-              this.undo.endBatch();
-            } catch (e) {
-              this.undo.endBatch();
-              this.undo.undo();
-            }
-          }.bind(this)
-        });
-      }
+          // Remove the attribute stored in this editor and re-add any attributes
+          // parsed out of the input element. Restore original attribute if
+          // parsing fails.
+          try {
+            this._saveAttribute(aAttr.name, undoMods);
+            doMods.removeAttribute(aAttr.name);
+            this._applyAttributes(aVal, attr, doMods, undoMods);
+            this.undo.do(() => {
+              doMods.apply();
+            }, () => {
+              undoMods.apply();
+            })
+          } catch(ex) {
+            console.error(ex);
+          }
+        }
+      });
 
       this.attrs[aAttr.name] = attr;
     }
 
     name.textContent = aAttr.name;
     val.textContent = aAttr.value;
 
     return attr;
@@ -1256,94 +1268,45 @@ ElementEditor.prototype = {
    * Parse a user-entered attribute string and apply the resulting
    * attributes to the node.  This operation is undoable.
    *
    * @param string aValue the user-entered value.
    * @param Element aAttrNode the attribute editor that created this
    *        set of attributes, used to place new attributes where the
    *        user put them.
    */
-  _applyAttributes: function EE__applyAttributes(aValue, aAttrNode)
+  _applyAttributes: function EE__applyAttributes(aValue, aAttrNode, aDoMods, aUndoMods)
   {
     let attrs = escapeAttributeValues(aValue);
 
-    this.undo.startBatch();
-
     for (let attr of attrs) {
-      let attribute = {
-        name: attr.name,
-        value: attr.value
-      };
       // Create an attribute editor next to the current attribute if needed.
-      this._createAttribute(attribute, aAttrNode ? aAttrNode.nextSibling : null);
-      this._setAttribute(this.rawNode, attr.name, attr.value);
-    }
-
-    this.undo.endBatch();
-  },
+      this._createAttribute(attr, aAttrNode ? aAttrNode.nextSibling : null);
 
-  /**
-   * Helper function for _setAttribute and _removeAttribute,
-   * returns a function that puts an attribute back the way it was.
-   */
-  _restoreAttribute: function EE_restoreAttribute(aNode, aName)
-  {
-    if (aNode.hasAttribute(aName)) {
-      let oldValue = aNode.getAttribute(aName);
-      return function() {
-        aNode.setAttribute(aName, oldValue);
-        this.markup.nodeChanged(aNode);
-      }.bind(this);
-    } else {
-      return function() {
-        aNode.removeAttribute(aName);
-        this.markup.nodeChanged(aNode);
-      }.bind(this);
+      this._saveAttribute(attr.name, aUndoMods);
+      aDoMods.setAttribute(attr.name, attr.value);
     }
   },
 
   /**
-   * Sets an attribute.  This operation is undoable.
+   * Saves the current state of the given attribute into an attribute
+   * modification list.
    */
-  _setAttribute: function EE_setAttribute(aNode, aName, aValue)
-  {
-    this.undo.do(function() {
-      aNode.setAttribute(aName, aValue);
-      this.markup.nodeChanged(aNode);
-    }.bind(this), this._restoreAttribute(aNode, aName));
-  },
-
-  /**
-   * Removes an attribute.  This operation is undoable.
-   */
-  _removeAttribute: function EE_removeAttribute(aNode, aName)
+  _saveAttribute: function(aName, aUndoMods)
   {
-    this.undo.do(function() {
-      aNode.removeAttribute(aName);
-      this.markup.nodeChanged(aNode);
-    }.bind(this), this._restoreAttribute(aNode, aName));
+    let node = this.node;
+    if (node.hasAttribute(aName)) {
+      let oldValue = node.getAttribute(aName);
+      aUndoMods.setAttribute(aName, oldValue);
+    } else {
+      aUndoMods.removeAttribute(aName);
+    }
   },
 
   /**
-   * Handler for the new attribute editor.
-   */
-  _onNewAttribute: function EE_onNewAttribute(aValue, aCommit)
-  {
-    if (!aValue || !aCommit) {
-      return;
-    }
-
-    this._setAttribute(this.rawNode, aValue, "");
-    let attr = this._createAttribute({ name: aValue, value: ""});
-    attr.style.removeAttribute("display");
-    attr.querySelector("attrvalue").click();
-  },
-
-
-  /**
    * Called when the tag name editor has is done editing.
    */
   onTagEdit: function EE_onTagEdit(aVal, aCommit) {
     if (!aCommit || aVal == this.rawNode.tagName) {
       return;
     }
 
     // Create a new element with the same attributes as the