Bug 892275 - F2 should make the selected node and its children editable as html; r=mratcliffe
authorBrian Grinstead <bgrinstead@mozilla.com>
Thu, 07 Nov 2013 10:23:25 -0600
changeset 154352 3e98116d9d408cc40f83caadefc531a34bd436d2
parent 154351 62c9bdb45a0a46d90ea7569a1b79931c1fbbcc07
child 154353 f16921967806d5dfd9e40dae2c5c04a14f550d8a
child 154398 f948e8233552216cf03d4369b75e029a05740f05
push id3457
push userbgrinstead@mozilla.com
push dateMon, 11 Nov 2013 16:59:22 +0000
treeherderfx-team@3e98116d9d40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmratcliffe
bugs892275
milestone28.0a1
Bug 892275 - F2 should make the selected node and its children editable as html; r=mratcliffe
browser/devtools/markupview/html-editor.js
browser/devtools/markupview/markup-view.js
browser/devtools/markupview/test/browser.ini
browser/devtools/markupview/test/browser_inspector_markup_edit_outerhtml.js
browser/devtools/markupview/test/browser_inspector_markup_edit_outerhtml2.js
--- a/browser/devtools/markupview/html-editor.js
+++ b/browser/devtools/markupview/html-editor.js
@@ -55,16 +55,17 @@ function HTMLEditor(htmlDocument)
     mode: Editor.modes.html,
     lineWrapping: true,
     styleActiveLine: false,
     extraKeys: {},
     theme: "mozilla markup-view"
   };
 
   config.extraKeys[ctrl("Enter")] = this.hide;
+  config.extraKeys["F2"] = this.hide;
   config.extraKeys["Esc"] = this.hide.bind(this, false);
 
   this.container.addEventListener("click", this.hide, false);
   this.editorInner.addEventListener("click", stopPropagation, false);
   this.editor = new Editor(config);
 
   this.editor.appendTo(this.editorInner).then(() => {
     this.hide(false);
@@ -135,16 +136,18 @@ HTMLEditor.prototype = {
     this._originalValue = text;
     this.editor.setText(text);
     this._attach(element);
     this.container.style.display = "flex";
     this._visible = true;
 
     this.editor.refresh();
     this.editor.focus();
+
+    this.emit("popupshown");
   },
 
   /**
    * Hide the editor, optionally committing the changes
    *
    * @param bool shouldCommit
    *             A change will be committed by default.  If this param
    *             strictly equals false, no change will occur.
@@ -156,19 +159,19 @@ HTMLEditor.prototype = {
     }
 
     this.container.style.display = "none";
     this._detach();
 
     let newValue = this.editor.getText();
     let valueHasChanged = this._originalValue !== newValue;
     let preventCommit = shouldCommit === false || !valueHasChanged;
-    this.emit("popup-hidden", !preventCommit, newValue);
     this._originalValue = undefined;
     this._visible = undefined;
+    this.emit("popuphidden", !preventCommit, newValue);
   },
 
   /**
    * Destroy this object and unbind all event handlers
    */
   destroy: function()
   {
     this.doc.defaultView.removeEventListener("resize",
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -293,16 +293,20 @@ MarkupView.prototype = {
           if (!next) {
             break;
           }
           selection = next.container;
         }
         this.navigate(selection);
         break;
       }
+      case Ci.nsIDOMKeyEvent.DOM_VK_F2: {
+        this.beginEditingOuterHTML(this._selectedContainer.node);
+        break;
+      }
       default:
         handled = false;
     }
     if (handled) {
       aEvent.stopPropagation();
       aEvent.preventDefault();
     }
   },
@@ -684,17 +688,21 @@ MarkupView.prototype = {
    */
   beginEditingOuterHTML: function(aNode) {
     this.getNodeOuterHTML(aNode).then((oldValue)=> {
       let container = this._containers.get(aNode);
       if (!container) {
         return;
       }
       this.htmlEditor.show(container.tagLine, oldValue);
-      this.htmlEditor.once("popup-hidden", (e, aCommit, aValue) => {
+      this.htmlEditor.once("popuphidden", (e, aCommit, aValue) => {
+        // Need to focus the <html> element instead of the frame / window
+        // in order to give keyboard focus back to doc (from editor).
+        this._frame.contentDocument.documentElement.focus();
+
         if (aCommit) {
           this.updateNodeOuterHTML(aNode, aValue, oldValue);
         }
       });
     });
   },
 
   /**
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -2,16 +2,17 @@
 support-files = head.js
 
 [browser_bug896181_css_mixed_completion_new_attribute.js]
 # Bug 916763 - too many intermittent failures
 skip-if = true
 [browser_inspector_markup_edit.html]
 [browser_inspector_markup_edit.js]
 [browser_inspector_markup_edit_outerhtml.js]
+[browser_inspector_markup_edit_outerhtml2.js]
 [browser_inspector_markup_mutation.html]
 [browser_inspector_markup_mutation.js]
 [browser_inspector_markup_mutation_flashing.html]
 [browser_inspector_markup_mutation_flashing.js]
 [browser_inspector_markup_navigation.html]
 [browser_inspector_markup_navigation.js]
 [browser_inspector_markup_subset.html]
 [browser_inspector_markup_subset.js]
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit_outerhtml.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit_outerhtml.js
@@ -159,17 +159,17 @@ function test() {
 
   function startTests() {
     inspector.markup._frame.focus();
     nextStep(0);
   }
 
   function nextStep(cursor) {
     if (cursor >= outerHTMLs.length) {
-      testBody();
+      finishUp();
       return;
     }
 
     let currentTestData = outerHTMLs[cursor];
     let selector = currentTestData.selector;
     let oldHTML = currentTestData.oldHTML;
     let newHTML = currentTestData.newHTML;
     let rawNode = doc.querySelector(selector);
@@ -218,78 +218,15 @@ function test() {
 
       is (inspector.selection.node, rawNode, "Selection is on the correct node");
       inspector.markup.updateNodeOuterHTML(inspector.selection.nodeFront, newHTML, oldHTML);
     });
 
     inspector.selection.setNode(rawNode);
   }
 
-  function testBody() {
-    let body = doc.querySelector("body");
-    let bodyHTML = '<body id="updated"><p></p></body>';
-    let bodyFront = inspector.markup.walker.frontForRawNode(body);
-    inspector.once("markupmutation", (e, aMutations) => {
-      is (doc.querySelector("body").outerHTML, bodyHTML, "<body> HTML has been updated");
-      is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
-      testHead();
-    });
-    inspector.markup.updateNodeOuterHTML(bodyFront, bodyHTML, body.outerHTML);
-  }
-
-  function testHead() {
-    let head = doc.querySelector("head");
-    let headHTML = '<head id="updated"><title>New Title</title><script>window.foo="bar";</script></head>';
-    let headFront = inspector.markup.walker.frontForRawNode(head);
-    inspector.once("markupmutation", (e, aMutations) => {
-      is (doc.title, "New Title", "New title has been added");
-      is (doc.defaultView.foo, undefined, "Script has not been executed");
-      is (doc.querySelector("head").outerHTML, headHTML, "<head> HTML has been updated");
-      is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
-      testDocumentElement();
-    });
-    inspector.markup.updateNodeOuterHTML(headFront, headHTML, head.outerHTML);
-  }
-
-  function testDocumentElement() {
-    let docElement = doc.documentElement;
-    let docElementHTML = '<html id="updated" foo="bar"><head><title>Updated from document element</title><script>window.foo="bar";</script></head><body><p>Hello</p></body></html>';
-    let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
-    inspector.once("markupmutation", (e, aMutations) => {
-      is (doc.title, "Updated from document element", "New title has been added");
-      is (doc.defaultView.foo, undefined, "Script has not been executed");
-      is (doc.documentElement.id, "updated", "<html> ID has been updated");
-      is (doc.documentElement.className, "", "<html> class has been updated");
-      is (doc.documentElement.getAttribute("foo"), "bar", "<html> attribute has been updated");
-      is (doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
-      is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
-      is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
-      is (doc.body.textContent, "Hello", "document.body.textContent has been updated");
-      testDocumentElement2();
-    });
-    inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
-  }
-
-  function testDocumentElement2() {
-    let docElement = doc.documentElement;
-    let docElementHTML = '<html class="updated" id="somethingelse"><head><title>Updated again from document element</title><script>window.foo="bar";</script></head><body><p>Hello again</p></body></html>';
-    let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
-    inspector.once("markupmutation", (e, aMutations) => {
-      is (doc.title, "Updated again from document element", "New title has been added");
-      is (doc.defaultView.foo, undefined, "Script has not been executed");
-      is (doc.documentElement.id, "somethingelse", "<html> ID has been updated");
-      is (doc.documentElement.className, "updated", "<html> class has been updated");
-      is (doc.documentElement.getAttribute("foo"), null, "<html> attribute has been removed");
-      is (doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
-      is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
-      is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
-      is (doc.body.textContent, "Hello again", "document.body.textContent has been updated");
-      finishUp();
-    });
-    inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
-  }
 
   function finishUp() {
     doc = inspector = null;
     gBrowser.removeCurrentTab();
     finish();
   }
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit_outerhtml2.js
@@ -0,0 +1,170 @@
+
+function test() {
+  let inspector;
+  let doc;
+  let selector = "#keyboard";
+  let oldHTML = '<div id="keyboard"></div>';
+  let newHTML = '<div id="keyboard">Edited</div>';
+
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(setupTest, content);
+  }, true);
+
+  content.location = "data:text/html," +
+    "<!DOCTYPE html>" +
+    "<head><meta charset='utf-8' /></head>" +
+    "<body>" +
+    "<div id=\"keyboard\"></div>" +
+    "</body>" +
+    "</html>";
+
+  function setupTest() {
+    var target = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      inspector = toolbox.getCurrentPanel();
+      inspector.once("inspector-updated", startTests);
+    });
+  }
+
+  function startTests() {
+    inspector.markup._frame.focus();
+    testEscapeCancels();
+  }
+
+  function testEscapeCancels() {
+    info("Checking to make sure that pressing escape cancels edits");
+    let rawNode = doc.querySelector(selector);
+
+    inspector.selection.once("new-node", () => {
+
+      inspector.markup.htmlEditor.on("popupshown", function onPopupShown() {
+        inspector.markup.htmlEditor.off("popupshown", onPopupShown);
+
+        ok (inspector.markup.htmlEditor._visible, "HTML Editor is visible");
+        is (rawNode.outerHTML, oldHTML, "The node is starting with old HTML.");
+
+        inspector.markup.htmlEditor.on("popuphidden", function onPopupHidden() {
+          inspector.markup.htmlEditor.off("popuphidden", onPopupHidden);
+          ok (!inspector.markup.htmlEditor._visible, "HTML Editor is not visible");
+
+          let rawNode = doc.querySelector(selector);
+          is (rawNode.outerHTML, oldHTML, "Escape cancels edits");
+          testF2Commits();
+        });
+
+        inspector.markup.htmlEditor.editor.setText(newHTML);
+
+        EventUtils.sendKey("ESCAPE", inspector.markup.htmlEditor.doc.defaultView);
+      });
+
+      EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
+    });
+
+    inspector.selection.setNode(rawNode);
+  }
+
+  function testF2Commits() {
+    info("Checking to make sure that pressing F2 commits edits");
+    let rawNode = doc.querySelector(selector);
+
+    inspector.markup.htmlEditor.on("popupshown", function onPopupShown() {
+      inspector.markup.htmlEditor.off("popupshown", onPopupShown);
+
+      ok (inspector.markup.htmlEditor._visible, "HTML Editor is visible");
+      is (rawNode.outerHTML, oldHTML, "The node is starting with old HTML.");
+
+      inspector.once("markupmutation", (e, aMutations) => {
+        ok (!inspector.markup.htmlEditor._visible, "HTML Editor is not visible");
+
+        let rawNode = doc.querySelector(selector);
+        is (rawNode.outerHTML, newHTML, "F2 commits edits - the node has new HTML.");
+        testBody();
+      });
+
+      inspector.markup.htmlEditor.editor.setText(newHTML);
+      EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
+    });
+
+    inspector.markup._frame.contentDocument.documentElement.focus();
+    EventUtils.sendKey("F2", inspector.markup._frame.contentWindow);
+  }
+
+  function testBody() {
+    info("Checking to make sure that editing the <body> element works like other nodes");
+    let body = doc.querySelector("body");
+    let bodyHTML = '<body id="updated"><p></p></body>';
+    let bodyFront = inspector.markup.walker.frontForRawNode(body);
+    inspector.once("markupmutation", (e, aMutations) => {
+      is (doc.querySelector("body").outerHTML, bodyHTML, "<body> HTML has been updated");
+      is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
+      testHead();
+    });
+    inspector.markup.updateNodeOuterHTML(bodyFront, bodyHTML, body.outerHTML);
+  }
+
+  function testHead() {
+    info("Checking to make sure that editing the <head> element works like other nodes");
+    let head = doc.querySelector("head");
+    let headHTML = '<head id="updated"><title>New Title</title><script>window.foo="bar";</script></head>';
+    let headFront = inspector.markup.walker.frontForRawNode(head);
+    inspector.once("markupmutation", (e, aMutations) => {
+      is (doc.title, "New Title", "New title has been added");
+      is (doc.defaultView.foo, undefined, "Script has not been executed");
+      is (doc.querySelector("head").outerHTML, headHTML, "<head> HTML has been updated");
+      is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
+      testDocumentElement();
+    });
+    inspector.markup.updateNodeOuterHTML(headFront, headHTML, head.outerHTML);
+  }
+
+  function testDocumentElement() {
+    info("Checking to make sure that editing the <html> element works like other nodes");
+    let docElement = doc.documentElement;
+    let docElementHTML = '<html id="updated" foo="bar"><head><title>Updated from document element</title><script>window.foo="bar";</script></head><body><p>Hello</p></body></html>';
+    let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
+    inspector.once("markupmutation", (e, aMutations) => {
+      is (doc.title, "Updated from document element", "New title has been added");
+      is (doc.defaultView.foo, undefined, "Script has not been executed");
+      is (doc.documentElement.id, "updated", "<html> ID has been updated");
+      is (doc.documentElement.className, "", "<html> class has been updated");
+      is (doc.documentElement.getAttribute("foo"), "bar", "<html> attribute has been updated");
+      is (doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
+      is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
+      is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
+      is (doc.body.textContent, "Hello", "document.body.textContent has been updated");
+      testDocumentElement2();
+    });
+    inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
+  }
+
+  function testDocumentElement2() {
+    info("Checking to make sure (again) that editing the <html> element works like other nodes");
+    let docElement = doc.documentElement;
+    let docElementHTML = '<html class="updated" id="somethingelse"><head><title>Updated again from document element</title><script>window.foo="bar";</script></head><body><p>Hello again</p></body></html>';
+    let docElementFront = inspector.markup.walker.frontForRawNode(docElement);
+    inspector.once("markupmutation", (e, aMutations) => {
+      is (doc.title, "Updated again from document element", "New title has been added");
+      is (doc.defaultView.foo, undefined, "Script has not been executed");
+      is (doc.documentElement.id, "somethingelse", "<html> ID has been updated");
+      is (doc.documentElement.className, "updated", "<html> class has been updated");
+      is (doc.documentElement.getAttribute("foo"), null, "<html> attribute has been removed");
+      is (doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
+      is (doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
+      is (doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
+      is (doc.body.textContent, "Hello again", "document.body.textContent has been updated");
+      finishUp();
+    });
+    inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
+  }
+
+  function finishUp() {
+    doc = inspector = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}