Bug 1053898 - display shadow nodes as links to real nodes draft
authorJulian Descottes <jdescottes@mozilla.com>
Mon, 05 Feb 2018 17:51:15 +0100
changeset 753087 775eedd248d4c155efdb23c3fb126c6ec0828406
parent 753086 e51d05bbe4264f08c637b8b737fd3282a5754e35
child 753088 160b0fe0a2d5dfb1691b4115fc9b6ba61eef1e18
push id98478
push userjdescottes@mozilla.com
push dateFri, 09 Feb 2018 16:32:07 +0000
bugs1053898
milestone60.0a1
Bug 1053898 - display shadow nodes as links to real nodes MozReview-Commit-ID: I8jnZ5xzbCU
devtools/client/inspector/markup/markup.js
devtools/client/inspector/markup/views/element-container.js
devtools/client/inspector/markup/views/markup-container.js
devtools/client/inspector/markup/views/moz.build
devtools/client/inspector/markup/views/shadow-node-container.js
devtools/client/inspector/markup/views/shadow-node-editor.js
devtools/client/themes/markup.css
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -13,16 +13,17 @@ const EventEmitter = require("devtools/s
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const {PluralForm} = require("devtools/shared/plural-form");
 const AutocompletePopup = require("devtools/client/shared/autocomplete-popup");
 const KeyShortcuts = require("devtools/client/shared/key-shortcuts");
 const {scrollIntoViewIfNeeded} = require("devtools/client/shared/scroll");
 const {UndoStack} = require("devtools/client/shared/undo");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
 const {PrefObserver} = require("devtools/client/shared/prefs");
+const ShadowNodeContainer = require("devtools/client/inspector/markup/views/shadow-node-container");
 const MarkupElementContainer = require("devtools/client/inspector/markup/views/element-container");
 const MarkupReadOnlyContainer = require("devtools/client/inspector/markup/views/read-only-container");
 const MarkupTextContainer = require("devtools/client/inspector/markup/views/text-container");
 const RootContainer = require("devtools/client/inspector/markup/views/root-container");
 
 const INSPECTOR_L10N =
       new LocalizationHelper("devtools/client/locales/inspector.properties");
 
@@ -320,21 +321,20 @@ MarkupView.prototype = {
     while (parentNode !== this.doc.body) {
       if (parentNode.container) {
         container = parentNode.container;
         break;
       }
       parentNode = parentNode.parentNode;
     }
 
-    if (container instanceof MarkupElementContainer) {
+    if (typeof container._onContainerClick === "function") {
       // With the newly found container, delegate the tooltip content creation
       // and decision to show or not the tooltip
-      container._buildEventTooltipContent(event.target,
-        this.eventDetailsTooltip);
+      container._onContainerClick(event, this.eventDetailsTooltip);
     }
   },
 
   _onMouseUp: function (event) {
     if (this._draggedContainer) {
       this._draggedContainer.onMouseUp(event);
     }
 
@@ -957,21 +957,23 @@ MarkupView.prototype = {
       return null;
     }
 
     if (this._containers.has(node)) {
       return this.getContainer(node);
     }
 
     let container;
-    let {nodeType, isPseudoElement} = node;
+    let {nodeType, isPseudoElement, isShadowNode} = node;
     if (node === this.walker.rootNode) {
       container = new RootContainer(this, node);
       this._elt.appendChild(container.elt);
       this._rootNode = node;
+    } else if (isShadowNode) {
+      container = new ShadowNodeContainer(this, node, this.inspector);
     } else if (nodeType == nodeConstants.ELEMENT_NODE && !isPseudoElement) {
       container = new MarkupElementContainer(this, node, this.inspector);
     } else if (nodeType == nodeConstants.COMMENT_NODE ||
                nodeType == nodeConstants.TEXT_NODE) {
       container = new MarkupTextContainer(this, node, this.inspector);
     } else {
       container = new MarkupReadOnlyContainer(this, node, this.inspector);
     }
--- a/devtools/client/inspector/markup/views/element-container.js
+++ b/devtools/client/inspector/markup/views/element-container.js
@@ -12,17 +12,17 @@ const {Task} = require("devtools/shared/
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const {setImageTooltip, setBrokenImageTooltip} =
       require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const MarkupContainer = require("devtools/client/inspector/markup/views/markup-container");
 const ElementEditor = require("devtools/client/inspector/markup/views/element-editor");
 const {extend} = require("devtools/shared/extend");
 
-// Lazy load this module as _buildEventTooltipContent is only called on click
+// Lazy load this module as _onContainerClick is only called on click
 loader.lazyRequireGetter(this, "setEventTooltip",
   "devtools/client/shared/widgets/tooltip/EventTooltipHelper", true);
 
 /**
  * An implementation of MarkupContainer for Elements that can contain
  * child nodes.
  * Allows editing of tag name, attributes, expanding / collapsing.
  *
@@ -40,17 +40,18 @@ function MarkupElementContainer(markupVi
   } else {
     throw new Error("Invalid node for MarkupElementContainer");
   }
 
   this.tagLine.appendChild(this.editor.elt);
 }
 
 MarkupElementContainer.prototype = extend(MarkupContainer.prototype, {
-  _buildEventTooltipContent: Task.async(function* (target, tooltip) {
+  _onContainerClick: Task.async(function* (event, tooltip) {
+    let target = event.target;
     if (target.dataset.event) {
       yield tooltip.hide();
 
       let listenerInfo = yield this.node.getEventListenerInfo();
 
       let toolbox = this.markup.toolbox;
 
       setEventTooltip(tooltip, listenerInfo, toolbox);
--- a/devtools/client/inspector/markup/views/markup-container.js
+++ b/devtools/client/inspector/markup/views/markup-container.js
@@ -739,16 +739,21 @@ MarkupContainer.prototype = {
   _onToggle: function (event) {
     // Prevent the html tree from expanding when an event bubble or display node is
     // clicked.
     if (event.target.dataset.event || event.target.dataset.display) {
       event.stopPropagation();
       return;
     }
 
+    if (event.target.classList.contains("reveal-link")) {
+      event.stopPropagation();
+      return;
+    }
+
     this.markup.navigate(this);
     if (this.hasChildren) {
       this.markup.setNodeExpanded(this.node, !this.expanded, event.altKey);
     }
     event.stopPropagation();
   },
 
   /**
--- a/devtools/client/inspector/markup/views/moz.build
+++ b/devtools/client/inspector/markup/views/moz.build
@@ -7,11 +7,13 @@
 DevToolsModules(
     'element-container.js',
     'element-editor.js',
     'html-editor.js',
     'markup-container.js',
     'read-only-container.js',
     'read-only-editor.js',
     'root-container.js',
+    'shadow-node-container.js',
+    'shadow-node-editor.js',
     'text-container.js',
     'text-editor.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/views/shadow-node-container.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const ShadowNodeEditor = require("devtools/client/inspector/markup/views/shadow-node-editor");
+const MarkupContainer = require("devtools/client/inspector/markup/views/markup-container");
+const {extend} = require("devtools/shared/extend");
+
+/**
+ * An implementation of MarkupContainer for Pseudo Elements,
+ * Doctype nodes, or any other type generic node that doesn't
+ * fit for other editors.
+ * Does not allow any editing, just viewing / selecting.
+ *
+ * @param  {MarkupView} markupView
+ *         The markup view that owns this container.
+ * @param  {NodeFront} node
+ *         The node to display.
+ */
+function ShadowNodeContainer(markupView, node) {
+  MarkupContainer.prototype.initialize.call(this, markupView, node,
+    "readonlycontainer");
+
+  this.editor = new ShadowNodeEditor(this, node);
+  this.tagLine.appendChild(this.editor.elt);
+}
+
+ShadowNodeContainer.prototype = extend(MarkupContainer.prototype, {
+  _onMouseDown: function (event) {
+    if (event.target.classList.contains("reveal-link")) {
+      event.stopPropagation();
+      event.preventDefault();
+      return;
+    }
+    MarkupContainer.prototype._onMouseDown.call(this, event);
+  },
+
+  _onContainerClick: async function (event) {
+    if (event.target.classList.contains("reveal-link")) {
+      let actorID = this.node._form.nodeActor;
+      let walkerFront = this.markup.inspector.walker;
+      let nodeFront = await walkerFront.getNodeFromActor(actorID, []);
+      this.markup.inspector.selection.setNodeFront(nodeFront);
+    }
+  }
+});
+
+module.exports = ShadowNodeContainer;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/views/shadow-node-editor.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Creates an editor for non-editable nodes.
+ */
+function ReadOnlyEditor(container, node) {
+  this.container = container;
+  this.markup = this.container.markup;
+  this.buildMarkup();
+  this.tag.textContent = "→ <" + node.nodeName.toLowerCase() + ">";
+
+  // Make the "tag" part of this editor focusable.
+  this.tag.setAttribute("tabindex", "-1");
+}
+
+ReadOnlyEditor.prototype = {
+  buildMarkup: function () {
+    let doc = this.markup.doc;
+
+    this.elt = doc.createElement("span");
+    this.elt.classList.add("editor");
+
+    this.tag = doc.createElement("span");
+    this.tag.classList.add("tag");
+    this.elt.appendChild(this.tag);
+
+    this.revealLink = doc.createElement("span");
+    this.revealLink.classList.add("reveal-link");
+    this.revealLink.textContent = "reveal";
+    this.elt.appendChild(this.revealLink);
+  },
+
+  destroy: function () {
+    // We might be already destroyed.
+    if (!this.elt) {
+      return;
+    }
+
+    this.elt.remove();
+    this.elt = null;
+    this.tag = null;
+  },
+
+  /**
+   * Stub method for consistency with ElementEditor.
+   */
+  getInfoAtNode: function () {
+    return null;
+  }
+};
+
+module.exports = ReadOnlyEditor;
--- a/devtools/client/themes/markup.css
+++ b/devtools/client/themes/markup.css
@@ -316,16 +316,30 @@ ul.children + .tag-line::before {
 .more-nodes {
   padding-left: 16px;
 }
 
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
 }
 
+.reveal-link {
+  margin-inline-start: 10px;
+  cursor: pointer;
+  display: none;
+}
+
+.reveal-link:hover {
+  text-decoration: underline
+}
+
+.tag-line:hover .reveal-link {
+  display: inline;
+}
+
 /* Draw a circle next to nodes that have a pseudo class lock.
    Center vertically with the 1.4em line height on .tag-line */
 .child.pseudoclass-locked::before {
   content: "";
   background: var(--theme-highlight-lightorange);
   border-radius: 50%;
   width: .8em;
   height: .8em;