Bug 757866 - Highlight and select DOM nodes in the web console output; r=msucan

This commit is contained in:
Patrick Brosset
2014-03-11 12:21:20 +02:00
parent c79aa9d2ea
commit e5ec09baf8
15 changed files with 742 additions and 27 deletions

View File

@@ -9,6 +9,8 @@ const {Cc, Ci, Cu} = require("chrome");
loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
loader.lazyImporter(this, "escapeHTML", "resource:///modules/devtools/VariablesView.jsm");
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
loader.lazyImporter(this, "Task","resource://gre/modules/Task.jsm");
const Heritage = require("sdk/core/heritage");
const XHTML_NS = "http://www.w3.org/1999/xhtml";
@@ -131,7 +133,7 @@ ConsoleOutput.prototype = {
* @type DOMDocument
*/
get document() {
return this.owner.document;
return this.owner ? this.owner.document : null;
},
/**
@@ -150,6 +152,14 @@ ConsoleOutput.prototype = {
return this.owner.webConsoleClient;
},
/**
* Getter for the current toolbox debuggee target.
* @type Target
*/
get toolboxTarget() {
return this.owner.owner.target;
},
/**
* Release an actor.
*
@@ -507,6 +517,14 @@ Messages.BaseMessage.prototype = {
{
this.output.openLink(event.target.href);
},
destroy: function()
{
// Destroy all widgets that have registered themselves in this.widgets
for (let widget of this.widgets) {
widget.destroy();
}
}
}; // Messages.BaseMessage.prototype
@@ -2017,6 +2035,7 @@ Widgets.ObjectRenderers.add({
case Ci.nsIDOMNode.TEXT_NODE:
case Ci.nsIDOMNode.COMMENT_NODE:
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE:
case Ci.nsIDOMNode.ELEMENT_NODE:
return true;
default:
return false;
@@ -2045,6 +2064,9 @@ Widgets.ObjectRenderers.add({
case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE:
this._renderDocumentFragmentNode();
break;
case Ci.nsIDOMNode.ELEMENT_NODE:
this._renderElementNode();
break;
default:
throw new Error("Unsupported nodeType: " + preview.nodeType);
}
@@ -2138,6 +2160,168 @@ Widgets.ObjectRenderers.add({
this._text(" ]");
},
_renderElementNode: function()
{
let doc = this.document;
let {attributes, nodeName} = this.objectActor.preview;
this.element = this.el("span." + "kind-" + this.objectActor.preview.kind + ".elementNode");
let openTag = this.el("span.cm-tag");
openTag.textContent = "<";
this.element.appendChild(openTag);
let tagName = this._anchor(nodeName, {
className: "cm-tag",
appendTo: openTag
});
if (this.options.concise) {
if (attributes.id) {
tagName.appendChild(this.el("span.cm-attribute", "#" + attributes.id));
}
if (attributes.class) {
tagName.appendChild(this.el("span.cm-attribute", "." + attributes.class.split(" ").join(".")));
}
} else {
for (let name of Object.keys(attributes)) {
let attr = this._renderAttributeNode(" " + name, attributes[name]);
this.element.appendChild(attr);
}
}
let closeTag = this.el("span.cm-tag");
closeTag.textContent = ">";
this.element.appendChild(closeTag);
// Register this widget in the owner message so that it gets destroyed when
// the message is destroyed.
this.message.widgets.add(this);
this.linkToInspector();
},
/**
* If the DOMNode being rendered can be highlit in the page, this function
* will attach mouseover/out event listeners to do so, and the inspector icon
* to open the node in the inspector.
* @return a promise (always the same) that resolves when the node has been
* linked to the inspector, or rejects if it wasn't (either if no toolbox
* could be found to access the inspector, or if the node isn't present in the
* inspector, i.e. if the node is in a DocumentFragment or not part of the
* tree, or not of type Ci.nsIDOMNode.ELEMENT_NODE).
*/
linkToInspector: function()
{
if (this._linkedToInspector) {
return this._linkedToInspector;
}
this._linkedToInspector = Task.spawn(function*() {
// Checking the node type
if (this.objectActor.preview.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
throw null;
}
// Checking the presence of a toolbox
let target = this.message.output.toolboxTarget;
this.toolbox = gDevTools.getToolbox(target);
if (!this.toolbox) {
throw null;
}
// Checking that the inspector supports the node
yield this.toolbox.initInspector();
this._nodeFront = yield this.toolbox.walker.getNodeActorFromObjectActor(this.objectActor.actor);
if (!this._nodeFront) {
throw null;
}
// At this stage, the message may have been cleared already
if (!this.document) {
throw null;
}
this.highlightDomNode = this.highlightDomNode.bind(this);
this.element.addEventListener("mouseover", this.highlightDomNode, false);
this.unhighlightDomNode = this.unhighlightDomNode.bind(this);
this.element.addEventListener("mouseout", this.unhighlightDomNode, false);
this._openInspectorNode = this._anchor("", {
className: "open-inspector",
onClick: this.openNodeInInspector.bind(this)
});
this._openInspectorNode.title = l10n.getStr("openNodeInInspector");
}.bind(this));
return this._linkedToInspector;
},
/**
* Highlight the DOMNode corresponding to the ObjectActor in the page.
* @return a promise that resolves when the node has been highlighted, or
* rejects if the node cannot be highlighted (detached from the DOM)
*/
highlightDomNode: function()
{
return Task.spawn(function*() {
yield this.linkToInspector();
let isAttached = yield this.toolbox.walker.isInDOMTree(this._nodeFront);
if (isAttached) {
yield this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront);
} else {
throw null;
}
}.bind(this));
},
/**
* Unhighlight a previously highlit node
* @see highlightDomNode
* @return a promise that resolves when the highlighter has been hidden
*/
unhighlightDomNode: function()
{
return this.linkToInspector().then(() => {
return this.toolbox.highlighterUtils.unhighlight();
});
},
/**
* Open the DOMNode corresponding to the ObjectActor in the inspector panel
* @return a promise that resolves when the inspector has been switched to
* and the node has been selected, or rejects if the node cannot be selected
* (detached from the DOM). Note that in any case, the inspector panel will
* be switched to.
*/
openNodeInInspector: function()
{
return Task.spawn(function*() {
yield this.linkToInspector();
yield this.toolbox.selectTool("inspector");
let isAttached = yield this.toolbox.walker.isInDOMTree(this._nodeFront);
if (isAttached) {
let onReady = this.toolbox.inspector.once("inspector-updated");
yield this.toolbox.selection.setNodeFront(this._nodeFront, "console");
yield onReady;
} else {
throw null;
}
}.bind(this));
},
destroy: function()
{
if (this.toolbox && this._nodeFront) {
this.element.removeEventListener("mouseover", this.highlightDomNode, false);
this.element.removeEventListener("mouseout", this.unhighlightDomNode, false);
this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector, true);
this.toolbox = null;
this._nodeFront = null;
}
},
}); // Widgets.ObjectRenderers.byKind.DOMNode
/**