Bug 962647 - Nodes searched in the inspector selector-search box now appear correctly in the markup-view; r=harth

This commit is contained in:
Patrick Brosset 2014-02-25 16:33:57 +01:00
parent df93062b9a
commit ac63b04c1c
8 changed files with 102 additions and 41 deletions

View File

@ -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);
},
/**

View File

@ -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);
});
},
}
};

View File

@ -166,6 +166,9 @@ MarkupView.prototype = {
_onMouseLeave: function() {
this._hideBoxModel();
if (this._hoveredNode) {
this._containers.get(this._hoveredNode).hovered = false;
}
this._hoveredNode = null;
},

View File

@ -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]

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head></head>
<body>
<ul>
<li>
<span>this is an <em>important</em> node</span>
</li>
</ul>
</body>
</html>

View File

@ -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 <em> 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 <body> 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: <em>");
let updated = inspector.once("inspector-updated");
searchUsingSelectorSearch("em", inspector);
yield updated;
ok(getContainerForRawNode(inspector.markup, getNode("em")),
"The <em> tag is now imported in the markup-view");
is(inspector.selection.node, getNode("em"),
"The <em> 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);
}

View File

@ -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 <ENTER>
* 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);
}

View File

@ -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")