Bug 792387 - Rule view should highlight which sections of a css selector matches an element. r=jwalker
authorDave Camp <dcamp@mozilla.com>
Thu, 20 Sep 2012 12:54:39 -0700
changeset 107595 dc326f5870d2403a6239a4fb530ea121ddc207ef
parent 107594 196ca3ef5b570e136c7fa2d4641e0d2482032e56
child 107596 f731fa718465e7cfc252c0acf1aeec35913d7123
push id23497
push userdcamp@campd.org
push dateThu, 20 Sep 2012 22:52:58 +0000
treeherdermozilla-central@f731fa718465 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalker
bugs792387
milestone18.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 792387 - Rule view should highlight which sections of a css selector matches an element. r=jwalker
browser/devtools/styleinspector/CssLogic.jsm
browser/devtools/styleinspector/CssRuleView.jsm
browser/devtools/styleinspector/test/browser_ruleview_ui.js
browser/themes/gnomestripe/devtools/csshtmltree.css
browser/themes/pinstripe/devtools/csshtmltree.css
browser/themes/winstripe/devtools/csshtmltree.css
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -827,16 +827,79 @@ CssLogic.getShortNamePath = function Css
     });
     aElement = aElement.parentNode;
   } while (aElement && aElement != doc.body && aElement != doc.head && aElement != doc);
 
   return reply;
 };
 
 /**
+ * Get a string list of selectors for a given CSSStyleRule.selectorText
+ *
+ * @param {string} aSelectorText The CSSStyleRule.selectorText to parse.
+ * @return {array} An array of string selectors.
+ */
+CssLogic.getSelectors = function CssLogic_getSelectors(aSelectorText)
+{
+  let selectors = [];
+
+  let selector = aSelectorText.trim();
+  if (!selector) {
+    return selectors;
+  }
+
+  let nesting = 0;
+  let currentSelector = [];
+
+  // Parse a selector group into selectors. Normally we could just .split(',')
+  // however Gecko allows -moz-any(a, b, c) as a selector so we ignore commas
+  // inside brackets.
+  for (let i = 0, selLen = selector.length; i < selLen; i++) {
+    let c = selector.charAt(i);
+    switch (c) {
+      case ",":
+        if (nesting == 0 && currentSelector.length > 0) {
+          let selectorStr = currentSelector.join("").trim();
+          if (selectorStr) {
+            selectors.push(selectorStr);
+          }
+          currentSelector = [];
+        } else {
+          currentSelector.push(c);
+        }
+        break;
+
+      case "(":
+        nesting++;
+        currentSelector.push(c);
+        break;
+
+      case ")":
+        nesting--;
+        currentSelector.push(c);
+        break;
+
+      default:
+        currentSelector.push(c);
+        break;
+    }
+  }
+
+  // Add the last selector.
+  if (nesting == 0 && currentSelector.length > 0) {
+    let selectorStr = currentSelector.join("").trim();
+    if (selectorStr) {
+      selectors.push(selectorStr);
+    }
+  }
+
+  return selectors;
+}
+
+/**
  * Memonized lookup of a l10n string from a string bundle.
  * @param {string} aName The key to lookup.
  * @returns A localized version of the given key.
  */
 CssLogic.l10n = function(aName) CssLogic._strings.GetStringFromName(aName);
 
 XPCOMUtils.defineLazyGetter(CssLogic, "_strings", function() Services.strings
         .createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
@@ -1263,66 +1326,18 @@ CssRule.prototype = {
 
     // Parse the CSSStyleRule.selectorText string.
     this._selectors = [];
 
     if (!this._domRule.selectorText) {
       return this._selectors;
     }
 
-    let selector = this._domRule.selectorText.trim();
-    if (!selector) {
-      return this._selectors;
-    }
-
-    let nesting = 0;
-    let currentSelector = [];
-
-    // Parse a selector group into selectors. Normally we could just .split(',')
-    // however Gecko allows -moz-any(a, b, c) as a selector so we ignore commas
-    // inside brackets.
-    for (let i = 0, selLen = selector.length; i < selLen; i++) {
-      let c = selector.charAt(i);
-      switch (c) {
-        case ",":
-          if (nesting == 0 && currentSelector.length > 0) {
-            let selectorStr = currentSelector.join("").trim();
-            if (selectorStr) {
-              this._selectors.push(new CssSelector(this, selectorStr));
-            }
-            currentSelector = [];
-          } else {
-            currentSelector.push(c);
-          }
-          break;
-
-        case "(":
-          nesting++;
-          currentSelector.push(c);
-          break;
-
-        case ")":
-          nesting--;
-          currentSelector.push(c);
-          break;
-
-        default:
-          currentSelector.push(c);
-          break;
-      }
-    }
-
-    // Add the last selector.
-    if (nesting == 0 && currentSelector.length > 0) {
-      let selectorStr = currentSelector.join("").trim();
-      if (selectorStr) {
-        this._selectors.push(new CssSelector(this, selectorStr));
-      }
-    }
-
+    let selectors = CssLogic.getSelectors(this._domRule.selectorText);
+    this._selectors = [new CssSelector(this, text) for (text of selectors)];
     return this._selectors;
   },
 
   toString: function CssRule_toString()
   {
     return "[CssRule " + this._domRule.selectorText + "]";
   },
 };
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -1399,17 +1399,44 @@ RuleEditor.prototype = {
     }.bind(this));
   },
 
   /**
    * Update the rule editor with the contents of the rule.
    */
   populate: function RuleEditor_populate()
   {
-    this.selectorText.textContent = this.rule.selectorText;
+    // Clear out existing viewers.
+    while (this.selectorText.hasChildNodes()) {
+      this.selectorText.removeChild(this.selectorText.lastChild);
+    }
+
+    // If selector text comes from a css rule, highlight selectors that
+    // actually match.  For custom selector text (such as for the 'element'
+    // style, just show the text directly.
+    if (this.rule.domRule && this.rule.domRule.selectorText) {
+      let selectors = CssLogic.getSelectors(this.rule.selectorText);
+      let element = this.rule.inherited || this.ruleView._viewedElement;
+      for (let i = 0; i < selectors.length; i++) {
+        let selector = selectors[i];
+        if (i != 0) {
+          createChild(this.selectorText, "span", {
+            class: "ruleview-selector-separator",
+            textContent: ", "
+          });
+        }
+        let cls = element.mozMatchesSelector(selector) ? "ruleview-selector-matched" : "ruleview-selector-unmatched";
+        createChild(this.selectorText, "span", {
+          class: cls,
+          textContent: selector
+        });
+      }
+    } else {
+      this.selectorText.textContent = this.rule.selectorText;
+    }
 
     for (let prop of this.rule.textProps) {
       if (!prop.editor) {
         new TextPropertyEditor(this, prop);
         this.propertyList.appendChild(prop.editor.element);
       }
     }
   },
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -26,17 +26,17 @@ function expectChange()
 }
 
 function startTest()
 {
   let style = '' +
     '#testid {' +
     '  background-color: blue;' +
     '} ' +
-    '.testclass {' +
+    '.testclass, .unmatched {' +
     '  background-color: green;' +
     '}';
 
   let styleNode = addStyle(doc, style);
   doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>';
   let testElement = doc.getElementById("testid");
 
   ruleDialog = openDialog("chrome://browser/content/devtools/cssruleview.xul",
@@ -50,16 +50,20 @@ function startTest()
     ruleView.element.addEventListener("CssRuleViewChanged", ruleViewChanged, false);
     is(ruleView.element.querySelectorAll("#noResults").length, 1, "Has a no-results element.");
     ruleView.highlight(testElement);
     is(ruleView.element.querySelectorAll("#noResults").length, 0, "After a highlight, no longer has a no-results element.");
     ruleView.highlight(null);
     is(ruleView.element.querySelectorAll("#noResults").length, 1, "After highlighting null, has a no-results element again.");
     ruleView.highlight(testElement);
 
+    let classEditor = ruleView.element.children[2]._ruleEditor;
+    is(classEditor.selectorText.querySelector(".ruleview-selector-matched").textContent, ".testclass", ".textclass should be matched.");
+    is(classEditor.selectorText.querySelector(".ruleview-selector-unmatched").textContent, ".unmatched", ".unmatched should not be matched.");
+
     waitForFocus(testCancelNew, ruleDialog);
   }, true);
 }
 
 function testCancelNew()
 {
   // Start at the beginning: start to add a rule to the element's style
   // declaration, but leave it empty.
--- a/browser/themes/gnomestripe/devtools/csshtmltree.css
+++ b/browser/themes/gnomestripe/devtools/csshtmltree.css
@@ -276,8 +276,12 @@
 .ruleview-propertycontainer > .ruleview-propertyvalue {
   border-bottom: 1px dashed transparent;
 }
 
 .ruleview-namecontainer:hover > .ruleview-propertyname,
 .ruleview-propertycontainer:hover > .ruleview-propertyvalue {
   border-bottom-color: hsl(0,0%,50%);
 }
+
+.ruleview-selector-separator, .ruleview-selector-unmatched {
+  color: #888;
+}
--- a/browser/themes/pinstripe/devtools/csshtmltree.css
+++ b/browser/themes/pinstripe/devtools/csshtmltree.css
@@ -278,8 +278,13 @@
 .ruleview-propertycontainer > .ruleview-propertyvalue {
   border-bottom: 1px dashed transparent;
 }
 
 .ruleview-namecontainer:hover > .ruleview-propertyname,
 .ruleview-propertycontainer:hover > .ruleview-propertyvalue {
   border-bottom-color: hsl(0,0%,50%);
 }
+
+.ruleview-selector-separator, .ruleview-selector-unmatched {
+  color: #888;
+}
+
--- a/browser/themes/winstripe/devtools/csshtmltree.css
+++ b/browser/themes/winstripe/devtools/csshtmltree.css
@@ -278,8 +278,12 @@
 .ruleview-propertycontainer > .ruleview-propertyvalue {
   border-bottom: 1px dashed transparent;
 }
 
 .ruleview-namecontainer:hover > .ruleview-propertyname,
 .ruleview-propertycontainer:hover > .ruleview-propertyvalue {
   border-bottom-color: hsl(0,0%,50%);
 }
+
+.ruleview-selector-separator, .ruleview-selector-unmatched {
+  color: #888;
+}