Bug 1037519 - Allow matching pseudo-elements in inIDOMUtils.selectorMatchesElement. r=bz
authorBrian Grinstead <bgrinstead@mozilla.com>
Fri, 18 Jul 2014 14:30:00 -0400
changeset 217127 1cda8d9d66ba29d142d3437aac4b28a4397c44e8
parent 217126 c809edd9b1c37352484596c86aa950b60ce1fce1
child 217128 4ca15c5bab96212bc22a4e4e3c189e2b5fe63094
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1037519
milestone33.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 1037519 - Allow matching pseudo-elements in inIDOMUtils.selectorMatchesElement. r=bz
layout/inspector/inDOMUtils.cpp
layout/inspector/inIDOMUtils.idl
layout/inspector/tests/mochitest.ini
layout/inspector/tests/test_selectormatcheselement.html
layout/style/StyleRule.cpp
layout/style/StyleRule.h
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -358,39 +358,54 @@ inDOMUtils::GetSpecificity(nsIDOMCSSStyl
   *aSpecificity = sel->mWeight;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 inDOMUtils::SelectorMatchesElement(nsIDOMElement* aElement,
                                    nsIDOMCSSStyleRule* aRule,
                                    uint32_t aSelectorIndex,
+                                   const nsAString& aPseudo,
                                    bool* aMatches)
 {
   nsCOMPtr<Element> element = do_QueryInterface(aElement);
   NS_ENSURE_ARG_POINTER(element);
 
   ErrorResult rv;
   nsCSSSelectorList* tail = GetSelectorAtIndex(aRule, aSelectorIndex, rv);
   if (rv.Failed()) {
     return rv.ErrorCode();
   }
 
   // We want just the one list item, not the whole list tail
   nsAutoPtr<nsCSSSelectorList> sel(tail->Clone(false));
 
-  // SelectorListMatches does not handle selectors that begin with a
-  // pseudo-element, which you can get from selectors like
-  // |input::-moz-placeholder:hover|.  This function doesn't take
-  // a pseudo-element nsIAtom*, so we know we can't match.
-  if (sel->mSelectors->IsPseudoElement()) {
+  // Do not attempt to match if a pseudo element is requested and this is not
+  // a pseudo element selector, or vice versa.
+  if (aPseudo.IsEmpty() == sel->mSelectors->IsPseudoElement()) {
     *aMatches = false;
     return NS_OK;
   }
 
+  if (!aPseudo.IsEmpty()) {
+    // We need to make sure that the requested pseudo element type
+    // matches the selector pseudo element type before proceeding.
+    nsCOMPtr<nsIAtom> pseudoElt = do_GetAtom(aPseudo);
+    if (sel->mSelectors->PseudoType() !=
+        nsCSSPseudoElements::GetPseudoType(pseudoElt)) {
+      *aMatches = false;
+      return NS_OK;
+    }
+
+    // We have a matching pseudo element, now remove it so we can compare
+    // directly against |element| when proceeding into SelectorListMatches.
+    // It's OK to do this - we just cloned sel and nothing else is using it.
+    sel->RemoveRightmostSelector();
+  }
+
   element->OwnerDoc()->FlushPendingLinkUpdates();
   // XXXbz what exactly should we do with visited state here?
   TreeMatchContext matchingContext(false,
                                    nsRuleWalker::eRelevantLinkUnvisited,
                                    element->OwnerDoc(),
                                    TreeMatchContext::eNeverMatchVisited);
   *aMatches = nsCSSRuleProcessor::SelectorListMatches(element, matchingContext,
                                                       sel);
--- a/layout/inspector/inIDOMUtils.idl
+++ b/layout/inspector/inIDOMUtils.idl
@@ -12,17 +12,17 @@ interface nsIDOMDocument;
 interface nsIDOMCSSRule;
 interface nsIDOMCSSStyleRule;
 interface nsIDOMNode;
 interface nsIDOMNodeList;
 interface nsIDOMFontFaceList;
 interface nsIDOMRange;
 interface nsIDOMCSSStyleSheet;
 
-[scriptable, uuid(bd6b3dee-b8dd-40c7-a40a-ad8455b49917)]
+[scriptable, uuid(1f5b7f08-fa80-49e9-b881-888f081240da)]
 interface inIDOMUtils : nsISupports
 {
   // CSS utilities
   void getAllStyleSheets (in nsIDOMDocument aDoc,
                           [optional] out unsigned long aLength,
                           [array, size_is (aLength), retval] out nsISupports aSheets);
   nsISupportsArray getCSSStyleRules(in nsIDOMElement aElement, [optional] in DOMString aPseudo);
   unsigned long getRuleLine(in nsIDOMCSSRule aRule);
@@ -39,17 +39,18 @@ interface inIDOMUtils : nsISupports
   AString getSelectorText(in nsIDOMCSSStyleRule aRule,
                           in unsigned long aSelectorIndex);
   unsigned long long getSpecificity(in nsIDOMCSSStyleRule aRule,
                                     in unsigned long aSelectorIndex);
   // Note: This does not handle scoped selectors correctly, because it has no
   // idea what the right scope is.
   bool selectorMatchesElement(in nsIDOMElement aElement,
                               in nsIDOMCSSStyleRule aRule,
-                              in unsigned long aSelectorIndex);
+                              in unsigned long aSelectorIndex,
+                              [optional] in DOMString aPseudo);
 
   // Utilities for working with CSS properties
   //
   // Returns true if the string names a property that is inherited by default.
   bool isInheritedProperty(in AString aPropertyName);
 
   // Get a list of all our supported property names.  Optionally
   // shorthands can be excluded or property aliases included.
--- a/layout/inspector/tests/mochitest.ini
+++ b/layout/inspector/tests/mochitest.ini
@@ -1,19 +1,20 @@
 [DEFAULT]
 support-files = bug856317.css
 
 [test_bug462787.html]
 [test_bug462789.html]
 [test_bug522601.xhtml]
+[test_bug536379.html]
 [test_bug536379-2.html]
-[test_bug536379.html]
 [test_bug557726.html]
 [test_bug609549.xhtml]
 [test_bug806192.html]
 [test_bug856317.html]
 [test_bug877690.html]
 [test_bug1006595.html]
 [test_color_to_rgba.html]
-[test_is_valid_css_color.html]
 [test_css_property_is_valid.html]
 [test_get_all_style_sheets.html]
+[test_is_valid_css_color.html]
 [test_isinheritableproperty.html]
+[test_selectormatcheselement.html]
new file mode 100644
--- /dev/null
+++ b/layout/inspector/tests/test_selectormatcheselement.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1037519
+-->
+<head>
+  <title>Test for nsIDOMUtils::selectorMatchesElement</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style type="text/css">
+    #foo,
+    #bar,
+    #foo::before {
+      color: red;
+    }
+    #foo::before,
+    #bar::before {
+      content: 'foo-before';
+      color: green;
+    }
+    #foo::after,
+    #bar::after {
+      content: 'foo-after';
+      color: blue;
+    }
+    #foo::first-line,
+    #bar::first-line {
+      text-decoration: underline;
+    }
+    #foo::first-letter,
+    #bar::first-letter {
+      font-variant: small-caps;
+    }
+  </style>
+</head>
+<body>
+<div id="foo">foo content</div>
+<pre id="test">
+<script type="application/javascript">
+
+function do_test() {
+  var utils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]
+    .getService(SpecialPowers.Ci.inIDOMUtils);
+
+  var element = document.querySelector("#foo");
+
+  var elementRules = utils.getCSSStyleRules(element);
+  var elementRule = elementRules.GetElementAt(elementRules.Count() - 1);
+
+  is (utils.selectorMatchesElement(element, elementRule, 0), true,
+    "Matches #foo");
+  is (utils.selectorMatchesElement(element, elementRule, 1), false,
+    "Doesn't match #bar");
+  is (utils.selectorMatchesElement(element, elementRule, 0, ":bogus"), false,
+    "Doesn't match #foo with a bogus pseudo");
+  is (utils.selectorMatchesElement(element, elementRule, 2, ":bogus"), false,
+    "Doesn't match #foo::before with bogus pseudo");
+  is (utils.selectorMatchesElement(element, elementRule, 0, ":after"), false,
+    "Does match #foo::before with the :after pseudo");
+
+  checkPseudo(":before");
+  checkPseudo(":after");
+  checkPseudo(":first-letter");
+  checkPseudo(":first-line");
+
+  SimpleTest.finish();
+
+  function checkPseudo(pseudo) {
+    var rules = utils.getCSSStyleRules(element, pseudo);
+    var rule = rules.GetElementAt(rules.Count() - 1);
+
+    is (utils.selectorMatchesElement(element, rule, 0), false,
+      "Doesn't match without " + pseudo);
+    is (utils.selectorMatchesElement(element, rule, 1), false,
+      "Doesn't match without " + pseudo);
+
+    is (utils.selectorMatchesElement(element, rule, 0, pseudo), true,
+      "Matches on #foo" + pseudo);
+    is (utils.selectorMatchesElement(element, rule, 1, pseudo), false,
+      "Doesn't match on #bar" + pseudo);
+  }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(do_test);
+
+</script>
+</pre>
+</body>
+</html>
--- a/layout/style/StyleRule.cpp
+++ b/layout/style/StyleRule.cpp
@@ -890,16 +890,31 @@ nsCSSSelectorList::AddSelector(char16_t 
   }
 
   newSel->mNext = mSelectors;
   mSelectors = newSel;
   return newSel;
 }
 
 void
+nsCSSSelectorList::RemoveRightmostSelector()
+{
+  nsCSSSelector* current = mSelectors;
+  mSelectors = mSelectors->mNext;
+  MOZ_ASSERT(mSelectors,
+             "Rightmost selector has been removed, but now "
+             "mSelectors is null");
+  mSelectors->SetOperator(char16_t(0));
+
+  // Make sure that deleting current won't delete the whole list.
+  current->mNext = nullptr;
+  delete current;
+}
+
+void
 nsCSSSelectorList::ToString(nsAString& aResult, CSSStyleSheet* aSheet)
 {
   aResult.Truncate();
   nsCSSSelectorList *p = this;
   for (;;) {
     p->mSelectors->ToString(aResult, aSheet, true);
     p = p->mNext;
     if (!p)
--- a/layout/style/StyleRule.h
+++ b/layout/style/StyleRule.h
@@ -234,16 +234,23 @@ struct nsCSSSelectorList {
    * must be char16_t(0).
    * Returns the new selector.
    * The list owns the new selector.
    * The caller is responsible for updating |mWeight|.
    */
   nsCSSSelector* AddSelector(char16_t aOperator);
 
   /**
+   * Point |mSelectors| to its |mNext|, and delete the first node in the old
+   * |mSelectors|.
+   * Should only be used on a list with more than one selector in it.
+   */
+  void RemoveRightmostSelector();
+
+  /**
    * Should be used only on the first in the list
    */
   void ToString(nsAString& aResult, mozilla::CSSStyleSheet* aSheet);
 
   /**
    * Do a deep clone.  Should be used only on the first in the list.
    */
   nsCSSSelectorList* Clone() const { return Clone(true); }