diff --git a/browser/devtools/framework/toolbox.js b/browser/devtools/framework/toolbox.js index 76cfddbf4186..93ac1dfca1f5 100644 --- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -465,7 +465,7 @@ Toolbox.prototype = { fireCustomKey: function(toolId) { let toolDefinition = gDevTools.getToolDefinition(toolId); - if (toolDefinition.onkey && + if (toolDefinition.onkey && ((this.currentToolId === toolId) || (toolId == "webconsole" && this.splitConsole))) { toolDefinition.onkey(this.getCurrentPanel(), this); @@ -1041,27 +1041,17 @@ Toolbox.prototype = { * Returns a promise that resolves when the fronts are initialized */ initInspector: function() { - let deferred = promise.defer(); - - if (!this._inspector) { - this._inspector = InspectorFront(this._target.client, this._target.form); - this._inspector.getWalker().then(walker => { - this._walker = walker; + if (!this._initInspector) { + this._initInspector = Task.spawn(function*() { + this._inspector = InspectorFront(this._target.client, this._target.form); + this._walker = yield this._inspector.getWalker(); this._selection = new Selection(this._walker); if (this.highlighterUtils.isRemoteHighlightable) { - this._inspector.getHighlighter().then(highlighter => { - this._highlighter = highlighter; - deferred.resolve(); - }); - } else { - deferred.resolve(); + this._highlighter = yield this._inspector.getHighlighter(); } - }); - } else { - deferred.resolve(); + }.bind(this)); } - - return deferred.promise; + return this._initInspector; }, /** diff --git a/browser/devtools/webconsole/console-output.js b/browser/devtools/webconsole/console-output.js index 525f9d8a2588..14a3465f96a7 100644 --- a/browser/devtools/webconsole/console-output.js +++ b/browser/devtools/webconsole/console-output.js @@ -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 /** diff --git a/browser/devtools/webconsole/test/browser.ini b/browser/devtools/webconsole/test/browser.ini index d68628e76663..9de382ece30f 100644 --- a/browser/devtools/webconsole/test/browser.ini +++ b/browser/devtools/webconsole/test/browser.ini @@ -69,6 +69,7 @@ support-files = test-console-output-02.html test-console-output-03.html test-console-output-04.html + test-console-output-dom-elements.html test-console-output-events.html test-consoleiframes.html test-data.json @@ -266,6 +267,10 @@ run-if = os == "mac" [browser_webconsole_output_02.js] [browser_webconsole_output_03.js] [browser_webconsole_output_04.js] +[browser_webconsole_output_dom_elements_01.js] +[browser_webconsole_output_dom_elements_02.js] +[browser_webconsole_output_dom_elements_03.js] +[browser_webconsole_output_dom_elements_04.js] [browser_webconsole_output_events.js] [browser_console_variables_view_highlighter.js] [browser_webconsole_start_netmon_first.js] diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_04.js b/browser/devtools/webconsole/test/browser_webconsole_output_04.js index c76fc8923111..579529a2b253 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_output_04.js +++ b/browser/devtools/webconsole/test/browser_webconsole_output_04.js @@ -29,7 +29,7 @@ let inputTests = [ // 2 { input: "testDocumentFragment()", - output: 'DocumentFragment [
', + printOutput: "[object HTMLParagraphElement]", + inspectable: true, + noClick: true, + inspectorIcon: true + }, + + { + input: "testNodeList()", + output: 'NodeList [ ,
, ,,