Bug 820926 - Hide void-element's closing tag if the page is an HTML page. r=pbro draft
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Tue, 19 Apr 2016 07:37:52 +0200
changeset 356931 552268b602eceaddd98f96f3cda8682957dba9f5
parent 356930 b0d046653bdc35de6edbd851ea104985ba95c336
child 519526 422f559a6b5435acc81d5a5813798b0d14e1715f
push id16647
push userchevobbe.nicolas@gmail.com
push dateWed, 27 Apr 2016 16:31:50 +0000
reviewerspbro
bugs820926
milestone49.0a1
Bug 820926 - Hide void-element's closing tag if the page is an HTML page. r=pbro Though, if the element has pseudo-elements, and thus can be expanded, show the closing tag when the void element is expanded. MozReview-Commit-ID: 7UMMlVjYqLX
devtools/client/inspector/markup/markup.js
devtools/client/inspector/markup/test/browser.ini
devtools/client/inspector/markup/test/browser_markup_void_elements_html.js
devtools/client/inspector/markup/test/browser_markup_void_elements_xhtml.js
devtools/client/inspector/markup/test/doc_markup_void_elements.html
devtools/client/inspector/markup/test/doc_markup_void_elements.xhtml
devtools/client/themes/markup.css
devtools/server/actors/inspector.js
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -18,16 +18,21 @@ const NEW_SELECTION_HIGHLIGHTER_TIMER = 
 const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50;
 const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5;
 const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 15;
 const DRAG_DROP_MIN_INITIAL_DISTANCE = 10;
 const AUTOCOMPLETE_POPUP_PANEL_ID = "markupview_autoCompletePopup";
 const ATTR_COLLAPSE_ENABLED_PREF = "devtools.markup.collapseAttributes";
 const ATTR_COLLAPSE_LENGTH_PREF = "devtools.markup.collapseAttributeLength";
 
+// Contains only void (without end tag) HTML elements
+const HTML_VOID_ELEMENTS = [ "area", "base", "br", "col", "command", "embed",
+  "hr", "img", "input", "keygen", "link", "meta", "param", "source",
+  "track", "wbr" ];
+
 const {UndoStack} = require("devtools/client/shared/undo");
 const {editableField, InplaceEditor} =
       require("devtools/client/shared/inplace-editor");
 const {HTMLEditor} = require("devtools/client/inspector/markup/html-editor");
 const promise = require("promise");
 const Services = require("Services");
 const {Tooltip} = require("devtools/client/shared/widgets/Tooltip");
 const EventEmitter = require("devtools/shared/event-emitter");
@@ -2674,16 +2679,21 @@ function ElementEditor(container, node) 
       });
     }
   });
 
   let tagName = this.getTagName(this.node);
   this.tag.textContent = tagName;
   this.closeTag.textContent = tagName;
 
+  let isVoidElement = HTML_VOID_ELEMENTS.includes(tagName);
+  if (node.isInHTMLDocument && isVoidElement) {
+    this.elt.classList.add("void-element");
+  }
+
   this.update();
   this.initialized = true;
 }
 
 ElementEditor.prototype = {
   set selected(value) {
     if (this.textEditor) {
       this.textEditor.selected = value;
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -22,16 +22,18 @@ support-files =
   doc_markup_navigation.html
   doc_markup_not_displayed.html
   doc_markup_pagesize_01.html
   doc_markup_pagesize_02.html
   doc_markup_search.html
   doc_markup_svg_attributes.html
   doc_markup_toggle.html
   doc_markup_tooltip.png
+  doc_markup_void_elements.html
+  doc_markup_void_elements.xhtml
   doc_markup_xul.xul
   head.js
   helper_attributes_test_runner.js
   helper_events_test_runner.js
   helper_outerhtml_test_runner.js
   helper_style_attr_test_runner.js
   lib_jquery_1.0.js
   lib_jquery_1.1.js
@@ -123,8 +125,10 @@ skip-if = e10s # Bug 1036409 - The last 
 [browser_markup_tag_edit_12.js]
 [browser_markup_tag_edit_13-other.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]
+[browser_markup_void_elements_xhtml.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_void_elements_html.js
@@ -0,0 +1,44 @@
+/* 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 void element display in the markupview.
+const TEST_URL = URL_ROOT + "doc_markup_void_elements.html";
+
+add_task(function* () {
+  let {inspector} = yield openInspectorForURL(TEST_URL);
+  let {win} = inspector.markup;
+
+  info("check non-void element closing tag is displayed");
+  let {editor} = yield getContainerForSelector("h1", inspector);
+  ok(!editor.elt.classList.contains("void-element"),
+    "h1 element does not have void-element class");
+  ok(!editor.elt.querySelector(".close").style.display !== "none",
+    "h1 element tag is not hidden");
+
+  info("check void element closing tag is hidden in HTML document");
+  let container = yield getContainerForSelector("img", inspector);
+  ok(container.editor.elt.classList.contains("void-element"),
+    "img element has the expected class");
+  let closeElement = container.editor.elt.querySelector(".close");
+  let computedStyle = win.getComputedStyle(closeElement, null);
+  ok(computedStyle.display === "none", "img closing tag is hidden");
+
+  info("check void element with pseudo element");
+  let hrNodeFront = yield getNodeFront("hr.before", inspector);
+  container = getContainerForNodeFront(hrNodeFront, inspector);
+  ok(container.editor.elt.classList.contains("void-element"),
+    "hr element has the expected class");
+  closeElement = container.editor.elt.querySelector(".close");
+  computedStyle = win.getComputedStyle(closeElement, null);
+  ok(computedStyle.display === "none", "hr closing tag is hidden");
+
+  info("check expanded void element closing tag is not hidden");
+  yield inspector.markup.expandNode(hrNodeFront);
+  yield waitForMultipleChildrenUpdates(inspector);
+  ok(container.expanded, "hr container is expanded");
+  computedStyle = win.getComputedStyle(closeElement, null);
+  ok(computedStyle.display === "none", "hr closing tag is not hidden anymore");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_void_elements_xhtml.js
@@ -0,0 +1,28 @@
+/* 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 void element display in the markupview.
+const TEST_URL = URL_ROOT + "doc_markup_void_elements.xhtml";
+
+add_task(function* () {
+  let {inspector} = yield openInspectorForURL(TEST_URL);
+  let {win} = inspector.markup;
+
+  info("check non-void element closing tag is displayed");
+  let {editor} = yield getContainerForSelector("h1", inspector);
+  ok(!editor.elt.classList.contains("void-element"),
+    "h1 element does not have void-element class");
+  ok(!editor.elt.querySelector(".close").style.display !== "none",
+    "h1 element tag is not hidden");
+
+  info("check void element closing tag is not hidden in XHTML document");
+  let container = yield getContainerForSelector("br", inspector);
+  ok(!container.editor.elt.classList.contains("void-element"),
+    "br element does not have void-element class");
+  let closeElement = container.editor.elt.querySelector(".close");
+  let computedStyle = win.getComputedStyle(closeElement, null);
+  ok(computedStyle.display !== "none", "br closing tag is not hidden");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/doc_markup_void_elements.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="html">
+  <head class="head">
+    <meta charset=utf-8 />
+    <style>
+    .before:before {
+      content: "before";
+    }
+    </style>
+  </head>
+  <body class="body">
+    <h1>Test void elements in HTML document</h1>
+    <img>
+    <hr>
+    <hr class="before">
+    <br>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/doc_markup_void_elements.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html
+     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+  <head class="head">
+    <meta charset="utf-8" />
+    <style>
+    .before:before {
+      content: "before";
+    }
+    </style>
+  </head>
+  <body class="body">
+    <h1>Test void elements in XHTML document</h1>
+    <hr class="before" />
+    <img />
+    <hr />
+    <br />
+  </body>
+</html>
--- a/devtools/client/themes/markup.css
+++ b/devtools/client/themes/markup.css
@@ -188,16 +188,22 @@ ul.children + .tag-line::before {
 .child.collapsed > .tag-line ~ .tag-line {
   display: none;
 }
 
 .child.collapsed .close {
   display: inline;
 }
 
+/* Hide HTML void elements (img, hr, br, …) closing tag when the element is not
+ * expanded (it can be if it has pseudo-elements attached) */
+.child.collapsed > .tag-line .void-element .close {
+  display: none;
+}
+
 .closing-bracket {
   pointer-events: none;
 }
 
 .newattr {
   display: inline-block;
   width: 1em;
   height: 1ex;
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -276,17 +276,18 @@ var NodeActor = exports.NodeActor = prot
       isAfterPseudoElement: this.isAfterPseudoElement,
       isAnonymous: isAnonymous(this.rawNode),
       isNativeAnonymous: isNativeAnonymous(this.rawNode),
       isXBLAnonymous: isXBLAnonymous(this.rawNode),
       isShadowAnonymous: isShadowAnonymous(this.rawNode),
       pseudoClassLocks: this.writePseudoClassLocks(),
 
       isDisplayed: this.isDisplayed,
-
+      isInHTMLDocument: this.rawNode.ownerDocument &&
+        this.rawNode.ownerDocument.contentType === "text/html",
       hasEventListeners: this._hasEventListeners,
     };
 
     if (this.isDocumentElement()) {
       form.isDocumentElement = true;
     }
 
     if (this.rawNode.nodeValue) {
@@ -911,17 +912,19 @@ var NodeFront = protocol.FrontClass(Node
     return this._form.isAfterPseudoElement;
   },
   get isPseudoElement() {
     return this.isBeforePseudoElement || this.isAfterPseudoElement;
   },
   get isAnonymous() {
     return this._form.isAnonymous;
   },
-
+  get isInHTMLDocument() {
+    return this._form.isInHTMLDocument;
+  },
   get tagName() {
     return this.nodeType === Ci.nsIDOMNode.ELEMENT_NODE ? this.nodeName : null;
   },
   get shortValue() {
     return this._form.shortValue;
   },
   get incompleteValue() {
     return !!this._form.incompleteValue;