Bug 1245365 - Fix markup view search with attribute selector. r=pbro draft
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Thu, 21 Apr 2016 21:54:03 +0200
changeset 358370 c752acee2abac1feb4e4d3b0d663b1abb504c6cb
parent 358369 8faf7ba00b3148a01db1701e80c1c3e611c4066d
child 519835 6d0ea68ea2b4111e4b2d42adeb4a94380ae462d3
push id16988
push userchevobbe.nicolas@gmail.com
push dateMon, 02 May 2016 16:11:17 +0000
reviewerspbro
bugs1245365
milestone49.0a1
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
devtools/client/inspector/inspector-search.js
devtools/client/inspector/test/browser_inspector_search-03.js
--- 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;