Bug 1290086 - Use multiline inplace editor for markupview autocompletes. r=gl, a=ritu
authorJulian Descottes <jdescottes@mozilla.com>
Tue, 23 Aug 2016 16:59:07 +0200
changeset 347812 40076409f9b525ab4414e2919bfad49566fe55ae
parent 347811 8194b6ebcb72d699420315234762ebea6b718de4
child 347813 a1d6e5e2864b11d5224c4002b4f8b9b41d2992cc
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl, ritu
bugs1290086
milestone50.0a2
Bug 1290086 - Use multiline inplace editor for markupview autocompletes. r=gl, a=ritu MozReview-Commit-ID: DNki6wTxg5a
devtools/client/inspector/markup/markup.js
devtools/client/inspector/markup/markup.js.rej
devtools/client/inspector/markup/test/browser.ini
devtools/client/inspector/markup/test/browser_markup_tag_edit_12.js
devtools/client/inspector/markup/test/browser_markup_tag_edit_long-classname.js
devtools/client/inspector/markup/test/head.js
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -2901,21 +2901,17 @@ function TextEditor(container, node, tem
 
   this.markup.template(templateId, this);
 
   editableField({
     element: this.value,
     stopOnReturn: true,
     trigger: "dblclick",
     multiline: true,
-    maxWidth: () => {
-      let elementRect = this.value.getBoundingClientRect();
-      let containerRect = this.container.elt.getBoundingClientRect();
-      return containerRect.right - elementRect.left - 2;
-    },
+    maxWidth: () => getAutocompleteMaxWidth(this.value, this.container.elt),
     trimOutput: false,
     done: (val, commit) => {
       if (!commit) {
         return;
       }
       this.node.getNodeValue().then(longstr => {
         longstr.string().then(oldValue => {
           longstr.release().then(null, console.error);
@@ -2998,25 +2994,29 @@ function ElementEditor(container, node) 
 
   // Make the tag name editable (unless this is a remote node or
   // a document element)
   if (!node.isDocumentElement) {
     // Make the tag optionally tabbable but not by default.
     this.tag.setAttribute("tabindex", "-1");
     editableField({
       element: this.tag,
+      multiline: true,
+      maxWidth: () => getAutocompleteMaxWidth(this.tag, this.container.elt),
       trigger: "dblclick",
       stopOnReturn: true,
       done: this.onTagEdit.bind(this),
     });
   }
 
   // Make the new attribute space editable.
   this.newAttr.editMode = editableField({
     element: this.newAttr,
+    multiline: true,
+    maxWidth: () => getAutocompleteMaxWidth(this.newAttr, this.container.elt),
     trigger: "dblclick",
     stopOnReturn: true,
     contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
     popup: this.markup.popup,
     done: (val, commit) => {
       if (!commit) {
         return;
       }
@@ -3226,16 +3226,18 @@ ElementEditor.prototype = {
 
     // Make the attribute editable.
     attr.editMode = editableField({
       element: inner,
       trigger: "dblclick",
       stopOnReturn: true,
       selectAll: false,
       initial: initial,
+      multiline: true,
+      maxWidth: () => getAutocompleteMaxWidth(inner, this.container.elt),
       contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
       popup: this.markup.popup,
       start: (editor, event) => {
         // If the editing was started inside the name or value areas,
         // select accordingly.
         if (event && event.target === name) {
           editor.input.setSelectionRange(0, name.textContent.length);
         } else if (event && event.target.closest(".attr-value") === val) {
@@ -3597,16 +3599,27 @@ function flashElementOff(backgroundElt, 
 function map(value, oldMin, oldMax, newMin, newMax) {
   let ratio = oldMax - oldMin;
   if (ratio == 0) {
     return value;
   }
   return newMin + (newMax - newMin) * ((value - oldMin) / ratio);
 }
 
+/**
+ * Retrieve the available width between a provided element left edge and a container right
+ * edge. This used can be used as a max-width for inplace-editor (autocomplete) widgets
+ * replacing Editor elements of the the markup-view;
+ */
+function getAutocompleteMaxWidth(element, container) {
+  let elementRect = element.getBoundingClientRect();
+  let containerRect = container.getBoundingClientRect();
+  return containerRect.right - elementRect.left - 2;
+}
+
 loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle(
   "chrome://devtools/locale/inspector.properties"
 ));
 
 XPCOMUtils.defineLazyGetter(this, "clipboardHelper", function () {
   return Cc["@mozilla.org/widget/clipboardhelper;1"]
     .getService(Ci.nsIClipboardHelper);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/markup.js.rej
@@ -0,0 +1,33 @@
+--- markup.js
++++ markup.js
+@@ -2998,26 +2994,30 @@ function ElementEditor(container, node) 
+ 
+   // Make the tag name editable (unless this is a remote node or
+   // a document element)
+   if (!node.isDocumentElement) {
+     // Make the tag optionally tabbable but not by default.
+     this.tag.setAttribute("tabindex", "-1");
+     editableField({
+       element: this.tag,
++      multiline: true,
++      maxWidth: () => getAutocompleteMaxWidth(this.tag, this.container.elt),
+       trigger: "dblclick",
+       stopOnReturn: true,
+       done: this.onTagEdit.bind(this),
+       cssProperties: this._cssProperties
+     });
+   }
+ 
+   // Make the new attribute space editable.
+   this.newAttr.editMode = editableField({
+     element: this.newAttr,
++    multiline: true,
++    maxWidth: () => getAutocompleteMaxWidth(this.newAttr, this.container.elt),
+     trigger: "dblclick",
+     stopOnReturn: true,
+     contentType: InplaceEditor.CONTENT_TYPES.CSS_MIXED,
+     popup: this.markup.popup,
+     done: (val, commit) => {
+       if (!commit) {
+         return;
+       }
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -136,16 +136,17 @@ skip-if = e10s # Bug 1036409 - The last 
 [browser_markup_tag_edit_06.js]
 [browser_markup_tag_edit_07.js]
 [browser_markup_tag_edit_08.js]
 [browser_markup_tag_edit_09.js]
 [browser_markup_tag_edit_10.js]
 [browser_markup_tag_edit_11.js]
 [browser_markup_tag_edit_12.js]
 [browser_markup_tag_edit_13-other.js]
+[browser_markup_tag_edit_long-classname.js]
 [browser_markup_textcontent_display.js]
 [browser_markup_textcontent_edit_01.js]
 [browser_markup_textcontent_edit_02.js]
 [browser_markup_toggle_01.js]
 [browser_markup_toggle_02.js]
 [browser_markup_toggle_03.js]
 [browser_markup_update-on-navigtion.js]
 [browser_markup_void_elements_html.js]
--- a/devtools/client/inspector/markup/test/browser_markup_tag_edit_12.js
+++ b/devtools/client/inspector/markup/test/browser_markup_tag_edit_12.js
@@ -64,17 +64,17 @@ function* testAttributeDeletion(inspecto
 
   info("Deleting the last attribute");
   yield editAttributeAndTab(" ", inspector);
 
   // Check we're on the newattr element
   let focusedAttr = Services.focus.focusedElement;
   ok(focusedAttr.classList.contains("styleinspector-propertyeditor"),
      "in newattr");
-  is(focusedAttr.tagName, "input", "newattr is active");
+  is(focusedAttr.tagName, "textarea", "newattr is active");
 }
 
 function* editAttributeAndTab(newValue, inspector, goPrevious) {
   let onEditMutation = inspector.markup.once("refocusedonedit");
   inspector.markup.doc.activeElement.value = newValue;
   if (goPrevious) {
     EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
       inspector.panelWin);
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_tag_edit_long-classname.js
@@ -0,0 +1,41 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that editing long classnames shows the whole class attribute without scrollbars.
+
+const classname = "this-long-class-attribute-should-be-displayed " +
+                  "without-overflow-when-switching-to-edit-mode " +
+                  "AAAAAAAAAAAA-BBBBBBBBBBBBB-CCCCCCCCCCCCC-DDDDDDDDDDDDDD-EEEEEEEEEEEEE";
+const TEST_URL = `data:text/html;charset=utf8, <div class="${classname}"></div>`;
+
+add_task(function* () {
+  let {inspector} = yield openInspectorForURL(TEST_URL);
+
+  yield selectNode("div", inspector);
+  yield clickContainer("div", inspector);
+
+  let container = yield focusNode("div", inspector);
+  ok(container && container.editor, "The markup-container was found");
+
+  info("Listening for the markupmutation event");
+  let nodeMutated = inspector.once("markupmutation");
+  let attr = container.editor.attrElements.get("class").querySelector(".editable");
+
+  attr.focus();
+  EventUtils.sendKey("return", inspector.panelWin);
+  let input = inplaceEditor(attr).input;
+  ok(input, "Found editable field for class attribute");
+
+  is(input.scrollHeight, input.clientHeight, "input should not have vertical scrollbars");
+  is(input.scrollWidth, input.clientWidth, "input should not have horizontal scrollbars");
+  input.value = "class=\"other value\"";
+
+  info("Commit the new class value");
+  EventUtils.sendKey("return", inspector.panelWin);
+
+  info("Wait for the markup-mutation event");
+  yield nodeMutated;
+});
--- a/devtools/client/inspector/markup/test/head.js
+++ b/devtools/client/inspector/markup/test/head.js
@@ -384,23 +384,26 @@ function collapseSelectionAndShiftTab(in
 /**
  * Check that the current focused element is an attribute element in the markup
  * view.
  * @param {String} attrName The attribute name expected to be found
  * @param {Boolean} editMode Whether or not the attribute should be in edit mode
  */
 function checkFocusedAttribute(attrName, editMode) {
   let focusedAttr = Services.focus.focusedElement;
-  is(focusedAttr ? focusedAttr.parentNode.dataset.attr : undefined,
-     attrName, attrName + " attribute editor is currently focused.");
-  is(focusedAttr ? focusedAttr.tagName : undefined,
-     editMode ? "input" : "span",
-     editMode
-     ? attrName + " is in edit mode"
-     : attrName + " is not in edit mode");
+  ok(focusedAttr, "Has a focused element");
+
+  let dataAttr = focusedAttr.parentNode.dataset.attr;
+  is(dataAttr, attrName, attrName + " attribute editor is currently focused.");
+  if (editMode) {
+    // Using a multiline editor for attributes, the focused element should be a textarea.
+    is(focusedAttr.tagName, "textarea", attrName + "is in edit mode");
+  } else {
+    is(focusedAttr.tagName, "span", attrName + "is not in edit mode");
+  }
 }
 
 /**
  * Get attributes for node as how they are represented in editor.
  *
  * @param  {String} selector
  * @param  {InspectorPanel} inspector
  * @return {Promise}