diff --git a/browser/devtools/inspector/inspector-panel.js b/browser/devtools/inspector/inspector-panel.js index f6df8f46e8f3..af93a2c4f1ce 100644 --- a/browser/devtools/inspector/inspector-panel.js +++ b/browser/devtools/inspector/inspector-panel.js @@ -262,25 +262,13 @@ InspectorPanel.prototype = { * Hooks the searchbar to show result and auto completion suggestions. */ setupSearchBox: function InspectorPanel_setupSearchBox() { - let searchDoc; - if (this.target.isLocalTab) { - searchDoc = this.browser.contentDocument; - } else if (this.target.window) { - searchDoc = this.target.window.document; - } else { - searchDoc = null; - } // Initiate the selectors search object. - let setNodeFunction = function(eventName, node) { - this.selection.setNodeFront(node, "selectorsearch"); - }.bind(this); if (this.searchSuggestions) { this.searchSuggestions.destroy(); this.searchSuggestions = null; } this.searchBox = this.panelDoc.getElementById("inspector-searchbox"); - this.searchSuggestions = new SelectorSearch(this, searchDoc, this.searchBox); - this.searchSuggestions.on("node-selected", setNodeFunction); + this.searchSuggestions = new SelectorSearch(this, this.searchBox); }, /** diff --git a/browser/devtools/inspector/selector-search.js b/browser/devtools/inspector/selector-search.js index fd436c5858d6..e2ebf8053e2d 100644 --- a/browser/devtools/inspector/selector-search.js +++ b/browser/devtools/inspector/selector-search.js @@ -4,7 +4,6 @@ "use strict"; -const EventEmitter = require("devtools/shared/event-emitter"); const promise = require("sdk/core/promise"); loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup); @@ -19,16 +18,12 @@ const MAX_SUGGESTIONS = 15; * @param InspectorPanel aInspector * The InspectorPanel whose `walker` attribute should be used for * document traversal. - * @param nsIDOMDocument aContentDocument - * The content document which inspector is attached to, or null if - * a remote document. * @param nsiInputElement aInputNode * The input element to which the panel will be attached and from where * search input will be taken. */ -function SelectorSearch(aInspector, aContentDocument, aInputNode) { +function SelectorSearch(aInspector, aInputNode) { this.inspector = aInspector; - this.doc = aContentDocument; this.searchBox = aInputNode; this.panelDoc = this.searchBox.ownerDocument; @@ -55,7 +50,7 @@ function SelectorSearch(aInspector, aContentDocument, aInputNode) { direction: "ltr", theme: "auto", onClick: this._onListBoxKeypress, - onKeypress: this._onListBoxKeypress, + onKeypress: this._onListBoxKeypress }; this.searchPopup = new AutocompletePopup(this.panelDoc, options); @@ -66,8 +61,6 @@ function SelectorSearch(aInspector, aContentDocument, aInputNode) { // For testing, we need to be able to wait for the most recent node request // to finish. Tests can watch this promise for that. this._lastQuery = promise.resolve(null); - - EventEmitter.decorate(this); } exports.SelectorSearch = SelectorSearch; @@ -165,23 +158,21 @@ SelectorSearch.prototype = { /** * Removes event listeners and cleans up references. */ - destroy: function SelectorSearch_destroy() { + destroy: function() { // event listeners. this.searchBox.removeEventListener("command", this._onHTMLSearch, true); this.searchBox.removeEventListener("keypress", this._onSearchKeypress, true); this.searchPopup.destroy(); this.searchPopup = null; this.searchBox = null; - this.doc = null; this.panelDoc = null; this._searchResults = null; this._searchSuggestions = null; - EventEmitter.decorate(this); }, _selectResult: function(index) { return this._searchResults.item(index).then(node => { - this.emit("node-selected", node); + this.inspector.selection.setNodeFront(node, "selectorsearch"); }); }, @@ -189,7 +180,7 @@ SelectorSearch.prototype = { * The command callback for the input box. This function is automatically * invoked as the user is typing if the input box type is search. */ - _onHTMLSearch: function SelectorSearch__onHTMLSearch() { + _onHTMLSearch: function() { let query = this.searchBox.value; if (query == this._lastSearched) { return; @@ -256,7 +247,7 @@ SelectorSearch.prototype = { } return this._selectResult(0).then(() => { this.searchBox.classList.remove("devtools-no-search-result"); - }).then( () => this.showSuggestions()); + }).then(() => this.showSuggestions()); } if (query.match(/[\s>+]$/)) { this._lastValidSearch = query + "*"; @@ -273,7 +264,7 @@ SelectorSearch.prototype = { /** * Handles keypresses inside the input box. */ - _onSearchKeypress: function SelectorSearch__onSearchKeypress(aEvent) { + _onSearchKeypress: function(aEvent) { let query = this.searchBox.value; switch(aEvent.keyCode) { case aEvent.DOM_VK_RETURN: @@ -348,7 +339,7 @@ SelectorSearch.prototype = { /** * Handles keypress and mouse click on the suggestions richlistbox. */ - _onListBoxKeypress: function SelectorSearch__onListBoxKeypress(aEvent) { + _onListBoxKeypress: function(aEvent) { switch(aEvent.keyCode || aEvent.button) { case aEvent.DOM_VK_RETURN: case aEvent.DOM_VK_TAB: @@ -404,11 +395,10 @@ SelectorSearch.prototype = { } }, - /** * Populates the suggestions list and show the suggestion popup. */ - _showPopup: function SelectorSearch__showPopup(aList, aFirstPart) { + _showPopup: function(aList, aFirstPart) { let total = 0; let query = this.searchBox.value; let toLowerCase = false; @@ -458,7 +448,7 @@ SelectorSearch.prototype = { * Suggests classes,ids and tags based on the user input as user types in the * searchbox. */ - showSuggestions: function SelectorSearch_showSuggestions() { + showSuggestions: function() { let query = this.searchBox.value; let firstPart = ""; if (this.state == this.States.TAG) { @@ -498,5 +488,5 @@ SelectorSearch.prototype = { } this._showPopup(result.suggestions, firstPart); }); - }, + } }; diff --git a/browser/devtools/markupview/markup-view.js b/browser/devtools/markupview/markup-view.js index bd89d407a1d7..71a06d700817 100644 --- a/browser/devtools/markupview/markup-view.js +++ b/browser/devtools/markupview/markup-view.js @@ -166,6 +166,9 @@ MarkupView.prototype = { _onMouseLeave: function() { this._hideBoxModel(); + if (this._hoveredNode) { + this._containers.get(this._hoveredNode).hovered = false; + } this._hoveredNode = null; }, diff --git a/browser/devtools/markupview/test/browser.ini b/browser/devtools/markupview/test/browser.ini index 03a31556432c..a790dd8ede19 100644 --- a/browser/devtools/markupview/test/browser.ini +++ b/browser/devtools/markupview/test/browser.ini @@ -7,6 +7,7 @@ support-files = browser_inspector_markup_subset.html browser_inspector_markup_765105_tooltip.png browser_inspector_markup_950732.html + browser_inspector_markup_962647_search.html head.js [browser_bug896181_css_mixed_completion_new_attribute.js] @@ -28,3 +29,4 @@ skip-if = true [browser_inspector_markup_964014_copy_image_data.js] [browser_inspector_markup_968316_highlit_node_on_hover_then_select.js] [browser_inspector_markup_968316_highlight_node_after_mouseleave_mousemove.js] +[browser_inspector_markup_962647_search.js] diff --git a/browser/devtools/markupview/test/browser_inspector_markup_962647_search.html b/browser/devtools/markupview/test/browser_inspector_markup_962647_search.html new file mode 100644 index 000000000000..08c047bcce2a --- /dev/null +++ b/browser/devtools/markupview/test/browser_inspector_markup_962647_search.html @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/browser/devtools/markupview/test/browser_inspector_markup_962647_search.js b/browser/devtools/markupview/test/browser_inspector_markup_962647_search.js new file mode 100644 index 000000000000..89c9a6094495 --- /dev/null +++ b/browser/devtools/markupview/test/browser_inspector_markup_962647_search.js @@ -0,0 +1,50 @@ +/* vim: set ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that searching for nodes using the selector-search input expands and +// selects the right nodes in the markup-view, even when those nodes are deeply +// nested (and therefore not attached yet when the markup-view is initialized). + +const TEST_URL = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_962647_search.html"; + +function test() { + waitForExplicitFinish(); + + let p = content.document.querySelector("p"); + Task.spawn(function() { + info("loading the test page"); + yield addTab(TEST_URL); + + info("opening the inspector"); + let {inspector, toolbox} = yield openInspector(); + + ok(!getContainerForRawNode(inspector.markup, getNode("em")), + "The tag isn't present yet in the markup-view"); + + // Searching for the innermost element first makes sure that the inspector + // back-end is able to attach the resulting node to the tree it knows at the + // moment. When the inspector is started, the is the default selected + // node, and only the parents up to the ROOT are known, and its direct children + info("searching for the innermost child: "); + let updated = inspector.once("inspector-updated"); + searchUsingSelectorSearch("em", inspector); + yield updated; + + ok(getContainerForRawNode(inspector.markup, getNode("em")), + "The tag is now imported in the markup-view"); + is(inspector.selection.node, getNode("em"), + "The tag is the currently selected node"); + + info("searching for other nodes too"); + for (let node of ["span", "li", "ul"]) { + let updated = inspector.once("inspector-updated"); + searchUsingSelectorSearch(node, inspector); + yield updated; + is(inspector.selection.node, getNode(node), + "The <" + node + "> tag is the currently selected node"); + } + + gBrowser.removeCurrentTab(); + }).then(null, ok.bind(null, false)).then(finish); +} diff --git a/browser/devtools/markupview/test/head.js b/browser/devtools/markupview/test/head.js index 2d39d7b6b642..caacff574e76 100644 --- a/browser/devtools/markupview/test/head.js +++ b/browser/devtools/markupview/test/head.js @@ -68,7 +68,6 @@ function openInspector() { function getContainerForRawNode(markupView, rawNode) { let front = markupView.walker.frontForRawNode(rawNode); let container = markupView.getContainer(front); - ok(container, "A markup-container object was found"); return container; } @@ -240,3 +239,26 @@ function redoChange(inspector) { inspector.markup.undo.redo(); return mutated; } + +/** + * Get the selector-search input box from the inspector panel + * @return {DOMNode} + */ +function getSelectorSearchBox(inspector) { + return inspector.panelWin.document.getElementById("inspector-searchbox"); +} + +/** + * Using the inspector panel's selector search box, search for a given selector. + * The selector input string will be entered in the input field and the + * keypress will be simulated. + * This function won't wait for any events and is not async. It's up to callers + * to subscribe to events and react accordingly. + */ +function searchUsingSelectorSearch(selector, inspector) { + info("Entering \"" + selector + "\" into the selector-search input field"); + let field = getSelectorSearchBox(inspector); + field.focus(); + field.value = selector; + EventUtils.sendKey("return", inspector.panelWin); +} diff --git a/toolkit/devtools/server/actors/inspector.js b/toolkit/devtools/server/actors/inspector.js index 8e07169def57..01628e6a44b5 100644 --- a/toolkit/devtools/server/actors/inspector.js +++ b/toolkit/devtools/server/actors/inspector.js @@ -681,12 +681,7 @@ var NodeListActor = exports.NodeListActor = protocol.ActorClass({ * Get a single node from the node list. */ item: method(function(index) { - let node = this.walker._ref(this.nodeList[index]); - let newParents = [node for (node of this.walker.ensurePathToRoot(node))]; - return { - node: node, - newParents: newParents - } + return this.walker.attachElement(this.nodeList[index]); }, { request: { item: Arg(0) }, response: RetVal("disconnectedNode")