Bug 1245365 - Fix markup view search with attribute selector. r=pbro
Add an ATTRIBUTE search state to better handle attribute's search.
Edit test to make sure we handle it right
MozReview-Commit-ID: ADcIJRbhjO
--- a/devtools/client/inspector/inspector-search.js
+++ b/devtools/client/inspector/inspector-search.js
@@ -175,16 +175,17 @@ SelectorAutocompleter.prototype = {
return this.inspector.walker;
},
// The possible states of the query.
States: {
CLASS: "class",
ID: "id",
TAG: "tag",
+ ATTRIBUTE: "attribute",
},
// The current state of the query.
_state: null,
// The query corresponding to last state computation.
_lastStateCheckAt: null,
@@ -223,42 +224,69 @@ SelectorAutocompleter.prototype = {
// Calculate the state.
subQuery = query.slice(0, i);
let [secondLastChar, lastChar] = subQuery.slice(-2);
switch (this._state) {
case null:
// This will happen only in the first iteration of the for loop.
lastChar = secondLastChar;
case this.States.TAG:
- this._state = lastChar == "."
- ? this.States.CLASS
- : lastChar == "#"
- ? this.States.ID
- : this.States.TAG;
+ if (lastChar == ".") {
+ this._state = this.States.CLASS;
+ } else if (lastChar == "#") {
+ this._state = this.States.ID;
+ } else if (lastChar == "[") {
+ this._state = this.States.ATTRIBUTE;
+ } else {
+ this._state = this.States.TAG;
+ }
break;
case this.States.CLASS:
if (subQuery.match(/[\.]+[^\.]*$/)[0].length > 2) {
// Checks whether the subQuery has atleast one [a-zA-Z] after the '.'.
- this._state = (lastChar == " " || lastChar == ">")
- ? this.States.TAG
- : lastChar == "#"
- ? this.States.ID
- : this.States.CLASS;
+ if (lastChar == " " || lastChar == ">") {
+ this._state = this.States.TAG;
+ } else if(lastChar == "#") {
+ this._state = this.States.ID;
+ } else if(lastChar == "[") {
+ this._state = this.States.ATTRIBUTE;
+ } else {
+ this._state = this.States.CLASS;
+ }
}
break;
case this.States.ID:
if (subQuery.match(/[#]+[^#]*$/)[0].length > 2) {
// Checks whether the subQuery has atleast one [a-zA-Z] after the '#'.
- this._state = (lastChar == " " || lastChar == ">")
- ? this.States.TAG
- : lastChar == "."
- ? this.States.CLASS
- : this.States.ID;
+ if (lastChar == " " || lastChar == ">") {
+ this._state = this.States.TAG;
+ } else if (lastChar == ".") {
+ this._state = this.States.CLASS;
+ } else if (lastChar == "[") {
+ this._state = this.States.ATTRIBUTE;
+ } else {
+ this._state = this.States.ID;
+ }
+ }
+ break;
+
+ case this.States.ATTRIBUTE:
+ if (subQuery.match(/[\[][^\]]+[\]]/) != null) {
+ // Checks whether the subQuery has at least one ']' after the '['.
+ if (lastChar == " " || lastChar == ">") {
+ this._state = this.States.TAG;
+ } else if (lastChar == ".") {
+ this._state = this.States.CLASS;
+ } else if (lastChar == "#") {
+ this._state = this.States.ID;
+ } else {
+ this._state = this.States.ATTRIBUTE;
+ }
}
break;
}
}
return this._state;
},
/**
@@ -407,19 +435,20 @@ SelectorAutocompleter.prototype = {
} else if (query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#\[]*$/)) {
// for cases like 'div #a' or 'div .a' or 'div > d' and likewise
let lastPart = query.match(/[\s>+][\.#a-zA-Z][^\s>+\.#\[]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
} else if (query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)) {
// for cases like 'div.class' or '#foo.bar' and likewise
let lastPart = query.match(/[a-zA-Z][#\.][^#\.\s+>]*$/)[0];
value = query.slice(0, -1 * lastPart.length + 1) + value;
- } else if (query.match(/[a-zA-Z]\[[^\]]*\]?$/)) {
- // for cases like 'div[foo=bar]' and likewise
- value = query;
+ } else if (query.match(/[a-zA-Z]*\[[^\]]*\][^\]]*/)) {
+ // for cases like '[foo].bar' and likewise
+ let attrPart = query.substring(0, query.lastIndexOf("]") + 1);
+ value = attrPart + value;
}
let item = {
preLabel: query,
label: value
};
// In case of tagNames, change the case to small
@@ -464,19 +493,20 @@ SelectorAutocompleter.prototype = {
* Suggests classes,ids and tags based on the user input as user types in the
* searchbox.
*/
showSuggestions: function() {
let query = this.searchBox.value;
let state = this.state;
let firstPart = "";
- if (query.endsWith("*")) {
- // Hide the popup if the query ends with * because we don't want to
- // suggest all nodes.
+ if (query.endsWith("*") || state === this.States.ATTRIBUTE) {
+ // Hide the popup if the query ends with * (because we don't want to
+ // suggest all nodes) or if it is an attribute selector (because
+ // it would give a lot of useless results).
this.hidePopup();
return;
}
if (state === this.States.TAG) {
// gets the tag that is being completed. For ex. 'div.foo > s' returns 's',
// 'di' returns 'di' and likewise.
firstPart = (query.match(/[\s>+]?([a-zA-Z]*)$/) || ["", query])[1];
--- a/devtools/client/inspector/test/browser_inspector_search-03.js
+++ b/devtools/client/inspector/test/browser_inspector_search-03.js
@@ -184,19 +184,35 @@ var TEST_DATA = [
},
{
key: "=", suggestions: []
},
{
key: "p", suggestions: []
},
{
- key: "]",
+ key: "]", suggestions: []
+ },
+ {
+ key: ".",
suggestions: [
- {label: "p[id*=p]"}
+ {label: "p[id*=p].c1"},
+ {label: "p[id*=p].c2"}
+ ]
+ },
+ {
+ key: "VK_BACK_SPACE",
+ suggestions: []
+ },
+ {
+ key: "#",
+ suggestions: [
+ {label: "p[id*=p]#p1"},
+ {label: "p[id*=p]#p2"},
+ {label: "p[id*=p]#p3"}
]
}
];
add_task(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
let searchBox = inspector.searchBox;
let popup = inspector.searchSuggestions.searchPopup;