Bug 855523 - Markup-view lines are easier to expand/collapse. r=miker

New markup structure for the markup-view inspector.
This allows for tags to have a full edge-to-edge line.
So highlight and click areas are easier to use.
This commit is contained in:
Patrick Brosset 2013-09-12 10:48:13 -04:00
parent 795cf6ac1f
commit 4374414931
8 changed files with 199 additions and 155 deletions

View File

@ -43,6 +43,8 @@ function inspectNode(aInspector)
function performScrollingTest()
{
executeSoon(function() {
// FIXME: this will fail on retina displays. EventUtils will only scroll
// 25px down instead of 50.
EventUtils.synthesizeWheel(div, 10, 10,
{ deltaY: 50.0, deltaMode: WheelEvent.DOM_DELTA_PIXEL },
iframe.contentWindow);

View File

@ -2,16 +2,64 @@
* 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/. */
ul {
list-style: none;
#root-wrapper {
overflow: hidden;
min-width: 250px;
}
ul.children:not([expanded]) {
.children {
list-style: none;
padding: 0;
margin: 0;
}
.child {
margin-left: -1000em;
padding-left: 1001em;
}
.tag-line {
min-height: 1.4em;
line-height: 1.4em;
position: relative;
}
/* Children are indented thanks to their parent's left padding, that means they
* are not stretching from edge to edge, which is what we want.
* So we insert a pseudo-element and make sure it covers the whole "line" */
.tag-line .highlighter {
content: "";
position: absolute;
left: -1000em;
right: 0;
height: 100%;
z-index: -1;
}
.expander {
display: inline-block;
margin-left: -14px;
vertical-align: middle;
}
.child.collapsed .child {
display: none;
}
.codebox {
display: inline-block;
.child > .tag-line:first-child .close {
display: none;
}
.child.collapsed > .tag-line:first-child .close {
display: inline;
}
.child.collapsed > .tag-line ~ .tag-line {
display: none;
}
.child.collapsed .close {
display: inline;
}
.newattr {
@ -19,24 +67,13 @@ ul.children:not([expanded]) {
width: 1em;
height: 1ex;
margin-right: -1em;
padding: 1px 0;
}
.newattr:focus {
margin-right: 0;
}
.closing-bracket {
pointer-events: none;
}
.summary {
cursor: pointer;
}
.summary[expanded] {
display: none;
}
/* Preview */
#previewbar {

View File

@ -8,19 +8,16 @@ const {Cc, Cu, Ci} = require("chrome");
// Page size for pageup/pagedown
const PAGE_SIZE = 10;
const PREVIEW_AREA = 700;
const DEFAULT_MAX_CHILDREN = 100;
let {UndoStack} = require("devtools/shared/undo");
let EventEmitter = require("devtools/shared/event-emitter");
let {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
let promise = require("sdk/core/promise");
const {UndoStack} = require("devtools/shared/undo");
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
const promise = require("sdk/core/promise");
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
Cu.import("resource://gre/modules/devtools/Templater.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
loader.lazyGetter(this, "DOMParser", function() {
return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
@ -192,7 +189,8 @@ MarkupView.prototype = {
}
break;
case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
if (!this._selectedContainer.expanded) {
if (!this._selectedContainer.expanded &&
this._selectedContainer.hasChildren) {
this._expandContainer(this._selectedContainer);
} else {
let next = this._selectionWalker().nextNode();
@ -365,7 +363,7 @@ MarkupView.prototype = {
if (mutation.type === "documentUnload") {
// Treat this as a childList change of the child (maybe the protocol
// should do this).
type = "childList"
type = "childList";
target = mutation.targetParent;
if (!target) {
continue;
@ -390,7 +388,6 @@ MarkupView.prototype = {
});
},
/**
* Make sure the given node's parents are expanded and the
* node is scrolled on to screen.
@ -419,7 +416,7 @@ MarkupView.prototype = {
{
return this._updateChildren(aContainer, true).then(() => {
aContainer.expanded = true;
})
});
},
/**
@ -807,8 +804,7 @@ MarkupView.prototype = {
this._updatePreview();
this._previewBar.classList.remove("hide");
}.bind(this), 1000);
},
}
};
@ -817,14 +813,13 @@ MarkupView.prototype = {
* tree. Manages creation of the editor for the node and
* a <ul> for placing child elements, and expansion/collapsing
* of the element.
*
*
* @param MarkupView aMarkupView
* The markup view that owns this container.
* @param DOMNode aNode
* The node to display.
*/
function MarkupContainer(aMarkupView, aNode)
{
function MarkupContainer(aMarkupView, aNode) {
this.markup = aMarkupView;
this.doc = this.markup.doc;
this.undo = this.markup.undo;
@ -839,48 +834,43 @@ function MarkupContainer(aMarkupView, aNode)
} else if (aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
this.editor = new DoctypeEditor(this, aNode);
} else {
this.editor = new GenericEditor(this.markup, aNode);
this.editor = new GenericEditor(this, aNode);
}
// The template will fill the following properties
this.elt = null;
this.expander = null;
this.codeBox = null;
this.highlighter = null;
this.tagLine = null;
this.children = null;
this.markup.template("container", this);
this.elt.container = this;
this.children.container = this;
this.expander.addEventListener("click", function() {
this.markup.navigate(this);
// Expanding/collapsing the node on dblclick of the whole tag-line element
this._onToggle = this._onToggle.bind(this);
this.elt.addEventListener("dblclick", this._onToggle, false);
this.expander.addEventListener("click", this._onToggle, false);
this.markup.setNodeExpanded(this.node, !this.expanded);
}.bind(this));
// Dealing with the highlighting of the row via javascript rather than :hover
// This is to allow highlighting the closing tag-line as well as reusing the
// theme css classes (which wouldn't have been possible with a :hover pseudo)
this._onMouseOver = this._onMouseOver.bind(this);
this.elt.addEventListener("mouseover", this._onMouseOver, false);
this.codeBox.insertBefore(this.editor.elt, this.children);
this._onMouseOut = this._onMouseOut.bind(this);
this.elt.addEventListener("mouseout", this._onMouseOut, false);
this.editor.elt.addEventListener("mousedown", function(evt) {
this.markup.navigate(this);
}.bind(this), false);
// Appending the editor element and attaching event listeners
this.tagLine.appendChild(this.editor.elt);
if (this.editor.summaryElt) {
this.editor.summaryElt.addEventListener("click", function(evt) {
this.markup.navigate(this);
this.markup.expandNode(this.node);
}.bind(this), false);
this.codeBox.appendChild(this.editor.summaryElt);
}
if (this.editor.closeElt) {
this.editor.closeElt.addEventListener("mousedown", function(evt) {
this.markup.navigate(this);
}.bind(this), false);
this.codeBox.appendChild(this.editor.closeElt);
}
this.elt.addEventListener("mousedown", this._onMouseDown.bind(this), false);
}
MarkupContainer.prototype = {
toString: function() { return "[MarkupContainer for " + this.node + "]" },
toString: function() {
return "[MarkupContainer for " + this.node + "]";
},
/**
* True if the current node has children. The MarkupView
@ -909,21 +899,88 @@ MarkupContainer.prototype = {
* True if the node has been visually expanded in the tree.
*/
get expanded() {
return this.children.hasAttribute("expanded");
return !this.elt.classList.contains("collapsed");
},
set expanded(aValue) {
if (aValue) {
if (aValue && this.elt.classList.contains("collapsed")) {
// Expanding a node means cloning its "inline" closing tag into a new
// tag-line that the user can interact with and showing the children.
if (this.editor instanceof ElementEditor) {
let closingTag = this.elt.querySelector(".close");
if (closingTag) {
if (!this.closeTagLine) {
let line = this.markup.doc.createElement("div");
line.classList.add("tag-line");
let highlighter = this.markup.doc.createElement("div");
highlighter.classList.add("highlighter");
line.appendChild(highlighter);
line.appendChild(closingTag.cloneNode(true));
line.addEventListener("mouseover", this._onMouseOver, false);
line.addEventListener("mouseout", this._onMouseOut, false);
this.closeTagLine = line;
}
this.elt.appendChild(this.closeTagLine);
}
}
this.elt.classList.remove("collapsed");
this.expander.setAttribute("open", "");
this.children.setAttribute("expanded", "");
if (this.editor.summaryElt) {
this.editor.summaryElt.setAttribute("expanded", "");
this.highlighted = false;
} else if (!aValue) {
if (this.editor instanceof ElementEditor && this.closeTagLine) {
this.elt.removeChild(this.closeTagLine);
}
this.elt.classList.add("collapsed");
this.expander.removeAttribute("open");
}
},
_onToggle: function(event) {
this.markup.navigate(this);
if(this.hasChildren) {
this.markup.setNodeExpanded(this.node, !this.expanded);
}
event.stopPropagation();
},
_onMouseOver: function(event) {
this.highlighted = true;
event.stopPropagation();
},
_onMouseOut: function(event) {
this.highlighted = false;
event.stopPropagation();
},
_onMouseDown: function(event) {
this.highlighted = false;
this.markup.navigate(this);
event.stopPropagation();
},
_highlighted: false,
/**
* Highlight the currently hovered tag + its closing tag if necessary
* (that is if the tag is expanded)
*/
set highlighted(aValue) {
this._highlighted = aValue;
if (aValue) {
if (!this.selected) {
this.highlighter.classList.add("theme-bg-darker");
}
if (this.closeTagLine) {
this.closeTagLine.querySelector(".highlighter").classList.add("theme-bg-darker");
}
} else {
this.expander.removeAttribute("open");
this.children.removeAttribute("expanded");
if (this.editor.summaryElt) {
this.editor.summaryElt.removeAttribute("expanded");
this.highlighter.classList.remove("theme-bg-darker");
if (this.closeTagLine) {
this.closeTagLine.querySelector(".highlighter").classList.remove("theme-bg-darker");
}
}
},
@ -931,8 +988,7 @@ MarkupContainer.prototype = {
/**
* True if the container is visible in the markup tree.
*/
get visible()
{
get visible() {
return this.elt.getBoundingClientRect().height > 0;
},
@ -949,15 +1005,11 @@ MarkupContainer.prototype = {
this._selected = aValue;
this.editor.selected = aValue;
if (this._selected) {
this.editor.elt.classList.add("theme-selected");
if (this.editor.closeElt) {
this.editor.closeElt.classList.add("theme-selected");
}
this.tagLine.setAttribute("selected", "");
this.highlighter.classList.add("theme-selected");
} else {
this.editor.elt.classList.remove("theme-selected");
if (this.editor.closeElt) {
this.editor.closeElt.classList.remove("theme-selected");
}
this.tagLine.removeAttribute("selected");
this.highlighter.classList.remove("theme-selected");
}
},
@ -965,8 +1017,7 @@ MarkupContainer.prototype = {
* Update the container's editor to the current state of the
* viewed node.
*/
update: function MC_update()
{
update: function() {
if (this.editor.update) {
this.editor.update();
}
@ -975,20 +1026,19 @@ MarkupContainer.prototype = {
/**
* Try to put keyboard focus on the current editor.
*/
focus: function MC_focus()
{
focus: function() {
let focusable = this.editor.elt.querySelector("[tabindex]");
if (focusable) {
focusable.focus();
}
},
}
}
};
/**
* Dummy container node used for the root document element.
*/
function RootContainer(aMarkupView, aNode)
{
function RootContainer(aMarkupView, aNode) {
this.doc = aMarkupView.doc;
this.elt = this.doc.createElement("ul");
this.elt.container = this;
@ -997,6 +1047,12 @@ function RootContainer(aMarkupView, aNode)
this.toString = function() { return "[root container]"}
}
RootContainer.prototype = {
hasChildren: true,
expanded: true,
update: function() {}
};
/**
* Creates an editor for simple nodes.
*/
@ -1117,27 +1173,19 @@ function ElementEditor(aContainer, aNode)
this.markup = this.container.markup;
this.node = aNode;
this.attrs = { };
this.attrs = {};
// The templates will fill the following properties
this.elt = null;
this.tag = null;
this.closeTag = null;
this.attrList = null;
this.newAttr = null;
this.summaryElt = null;
this.closeElt = null;
// Create the main editor
this.template("element", this);
if (this.node.hasChildren) {
// Create the summary placeholder
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 remote node or
@ -1338,7 +1386,6 @@ ElementEditor.prototype = {
for (let attr of attrs) {
// Create an attribute editor next to the current attribute if needed.
this._createAttribute(attr, aAttrNode ? aAttrNode.nextSibling : null);
this._saveAttribute(attr.name, aUndoMods);
aDoMods.setAttribute(attr.name, attr.value);
}
@ -1414,14 +1461,6 @@ ElementEditor.prototype = {
});
}).then(null, console.error);
}
}
RootContainer.prototype = {
hasChildren: true,
expanded: true,
update: function RC_update() {}
};
function nodeDocument(node) {
@ -1439,7 +1478,6 @@ function nodeDocument(node) {
* An array of attribute names and their values.
*/
function parseAttributeValues(attr, doc) {
attr = attr.trim();
// Handle bad user inputs by appending a " or ' if it fails to parse without them.
@ -1466,19 +1504,6 @@ function parseAttributeValues(attr, doc) {
return attributes.reverse();
}
/**
* A tree walker filter for avoiding empty whitespace text nodes.
*/
function whitespaceTextFilter(aNode)
{
if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
!/[^\s]/.exec(aNode.nodeValue)) {
return Ci.nsIDOMNodeFilter.FILTER_SKIP;
} else {
return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
}
}
loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle(
"chrome://browser/locale/devtools/inspector.properties"
));

View File

@ -15,33 +15,36 @@
</head>
<body class="theme-body devtools-monospace" role="application">
<div id="root"></div>
<div id="root-wrapper">
<div id="root"></div>
</div>
<div id="templates" style="display:none">
<ul>
<li id="template-container" save="${elt}" class="container"><span save="${codeBox}" class="codebox"><span save="${expander}" class="theme-twisty expander"></span><ul save="${children}" class="children"></ul></span></li>
<ul class="children">
<li id="template-container" save="${elt}" class="child collapsed">
<div save="${tagLine}" class="tag-line"><span save="${highlighter}" class="highlighter"></span><span save="${expander}" class="theme-twisty expander"></span></div>
<ul save="${children}" class="children"></ul>
</li>
<li id="template-more-nodes" class="more-nodes devtools-class-comment" save="${elt}"><span>${showing}</span> <button href="#" onclick="${allButtonClick}">${showAll}</button></li>
</ul>
<span id="template-element" save="${elt}" class="editor"><span>&lt;</span><span save="${tag}" class="tagname theme-fg-color3"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span><span class="closing-bracket">&gt;</span></span>
<span id="template-element" save="${elt}" class="editor"><span class="open">&lt;<span save="${tag}" class="tag theme-fg-color3" tabindex="0"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>&gt;</span><span class="close">&lt;/<span save="${closeTag}" class="tag theme-fg-color3"></span>&gt;</span></span>
<span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attrname theme-fg-color2"></span>=&quot;<span save="${val}" class="attrvalue theme-fg-color6"></span>&quot;</span></span>
<span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attr-name theme-fg-color2"></span>=&quot;<span save="${val}" class="attr-value theme-fg-color6"></span>&quot;</span></span>
<span id="template-text" save="${elt}" class="editor text">
<pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
</span>
<span id="template-comment" save="${elt}" class="editor comment theme-comment">
<span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span>
</span>
<span id="template-comment" save="${elt}" class="editor comment theme-comment"><span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span></span>
<span id="template-elementContentSummary" save="${summaryElt}" class="summary"></span>
<!-- span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname theme-fg-color3"></span>&gt;</span -->
<span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname theme-fg-color3"></span>&gt;</span>
</div>
<div id="previewbar" class="disabled">
</div>
<div id="previewbar" class="disabled">
<div id="preview"/>
<div id="viewbox"/>
</div>
</div>
</body>
</html>

View File

@ -34,7 +34,6 @@ function test() {
["left", "node7"],
["right", "node7"],
["right", "*text*"],
["right", "*text*"],
["down", "node8"],
["right", "node8"],
["left", "node8"],

View File

@ -48,7 +48,7 @@
}
.theme-bg-darker {
background-color: rgba(0,0,0,0.1);
background-color: rgba(0,0,0,0.5);
}
.theme-link { /* blue */

View File

@ -48,7 +48,7 @@
}
.theme-bg-darker {
background: #F9F9F9;
background: #EFEFEF;
}
.theme-link { /* blue */

View File

@ -7,28 +7,6 @@
margin: 0;
}
.newattr {
cursor: pointer;
padding: 1px 0;
}
li.container {
padding: 2px 0 0 2px;
}
.codebox {
padding-left: 14px;
}
.codebox > * {
vertical-align: middle;
}
.expander {
display: inline-block;
margin-left: -14px;
}
.more-nodes {
padding-left: 16px;
}