Add support for regexp() function in @-moz-document rule. (Bug 398962) r=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Thu, 28 Apr 2011 10:21:37 -0700
changeset 68715 52a05ef70c662092f14efefc396f2cc8f8e96bb0
parent 68714 ebe749bb585b7b45ec670ba3eef67c8fee48135f
child 68716 2f41abc5a89dab8422372e415c28a8d880d91413
push idunknown
push userunknown
push dateunknown
reviewersbzbarsky
bugs398962
milestone6.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
Add support for regexp() function in @-moz-document rule. (Bug 398962) r=bzbarsky
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
dom/locales/en-US/chrome/layout/css.properties
layout/style/nsCSSParser.cpp
layout/style/nsCSSRules.cpp
layout/style/nsCSSRules.h
layout/style/test/chrome/Makefile.in
layout/style/test/chrome/moz_document_helper.html
layout/style/test/chrome/test_moz_document_rules.html
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1773,16 +1773,35 @@ public:
       TYPE_UNKNOWN
   };
 
   static already_AddRefed<nsIDocumentLoaderFactory>
   FindInternalContentViewer(const char* aType,
                             ContentViewerType* aLoaderType = nsnull);
 
   /**
+   * This helper method returns true if the aPattern pattern matches aValue.
+   * aPattern should not contain leading and trailing slashes (/).
+   * The pattern has to match the entire value not just a subset.
+   * aDocument must be a valid pointer (not null).
+   *
+   * This is following the HTML5 specification:
+   * http://dev.w3.org/html5/spec/forms.html#attr-input-pattern
+   *
+   * WARNING: This method mutates aPattern and aValue!
+   *
+   * @param aValue    the string to check.
+   * @param aPattern  the string defining the pattern.
+   * @param aDocument the owner document of the element.
+   * @result          whether the given string is matches the pattern.
+   */
+  static PRBool IsPatternMatching(nsAString& aValue, nsAString& aPattern,
+                                  nsIDocument* aDocument);
+
+  /**
    * Calling this adds support for
    * ontouch* event handler DOM attributes.
    */
   static void InitializeTouchEventTable();
 private:
   static PRBool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -6547,8 +6547,42 @@ nsContentUtils::FindInternalContentViewe
       }
     }
   }
 #endif
 #endif // MOZ_MEDIA
 
   return NULL;
 }
+
+// static
+PRBool
+nsContentUtils::IsPatternMatching(nsAString& aValue, nsAString& aPattern,
+                                  nsIDocument* aDocument)
+{
+  NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
+  NS_ENSURE_TRUE(aDocument->GetScriptGlobalObject(), PR_TRUE);
+
+  JSContext* ctx = (JSContext*) aDocument->GetScriptGlobalObject()->
+                                  GetContext()->GetNativeContext();
+  NS_ENSURE_TRUE(ctx, PR_TRUE);
+
+  JSAutoRequest ar(ctx);
+
+  // The pattern has to match the entire value.
+  aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
+  aPattern.Append(NS_LITERAL_STRING(")$"));
+
+  JSObject* re = JS_NewUCRegExpObjectNoStatics(ctx, reinterpret_cast<jschar*>
+                                                 (aPattern.BeginWriting()),
+                                                aPattern.Length(), 0);
+  NS_ENSURE_TRUE(re, PR_TRUE);
+
+  jsval rval = JSVAL_NULL;
+  size_t idx = 0;
+  JSBool res;
+
+  res = JS_ExecuteRegExpNoStatics(ctx, re, reinterpret_cast<jschar*>
+                                    (aValue.BeginWriting()),
+                                  aValue.Length(), &idx, JS_TRUE, &rval);
+
+  return res == JS_FALSE || rval != JSVAL_NULL;
+}
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -3808,17 +3808,17 @@ nsHTMLInputElement::HasPatternMismatch()
     return PR_FALSE;
   }
 
   nsIDocument* doc = GetOwnerDoc();
   if (!doc) {
     return PR_FALSE;
   }
 
-  return !IsPatternMatching(value, pattern, doc);
+  return !nsContentUtils::IsPatternMatching(value, pattern, doc);
 }
 
 void
 nsHTMLInputElement::UpdateTooLongValidityState()
 {
   // TODO: this code will be re-enabled with bug 613016 and bug 613019.
 #if 0
   SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
@@ -4090,50 +4090,16 @@ nsHTMLInputElement::IsValidEmailAddress(
       // The domain characters have to be in this list to be valid.
       return PR_FALSE;
     }
   }
 
   return PR_TRUE;
 }
 
-//static
-PRBool
-nsHTMLInputElement::IsPatternMatching(nsAString& aValue, nsAString& aPattern,
-                                      nsIDocument* aDocument)
-{
-  NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
-  NS_ENSURE_TRUE(aDocument->GetScriptGlobalObject(), PR_TRUE);
-
-  JSContext* ctx = (JSContext*) aDocument->GetScriptGlobalObject()->
-                                  GetContext()->GetNativeContext();
-  NS_ENSURE_TRUE(ctx, PR_TRUE);
-
-  JSAutoRequest ar(ctx);
-
-  // The pattern has to match the entire value.
-  aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
-  aPattern.Append(NS_LITERAL_STRING(")$"));
-
-  JSObject* re = JS_NewUCRegExpObjectNoStatics(ctx, reinterpret_cast<jschar*>
-                                                 (aPattern.BeginWriting()),
-                                                aPattern.Length(), 0);
-  NS_ENSURE_TRUE(re, PR_TRUE);
-
-  jsval rval = JSVAL_NULL;
-  size_t idx = 0;
-  JSBool res;
-
-  res = JS_ExecuteRegExpNoStatics(ctx, re, reinterpret_cast<jschar*>
-                                    (aValue.BeginWriting()),
-                                  aValue.Length(), &idx, JS_TRUE, &rval);
-
-  return res == JS_FALSE || rval != JSVAL_NULL;
-}
-
 NS_IMETHODIMP_(PRBool)
 nsHTMLInputElement::IsSingleLineTextControl() const
 {
   return IsSingleLineTextControl(PR_FALSE);
 }
 
 NS_IMETHODIMP_(PRBool)
 nsHTMLInputElement::IsTextArea() const
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -363,33 +363,16 @@ protected:
    * This is following the HTML5 specification:
    * http://dev.w3.org/html5/spec/forms.html#valid-e-mail-address-list
    *
    * @param aValue  the email address list to check.
    * @result        whether the given string is a valid email address list.
    */
   static PRBool IsValidEmailAddressList(const nsAString& aValue);
 
-  /**
-   * This helper method returns true if the aPattern pattern matches aValue.
-   * aPattern should not contain leading and trailing slashes (/).
-   * The pattern has to match the entire value not just a subset.
-   * aDocument must be a valid pointer (not null).
-   *
-   * This is following the HTML5 specification:
-   * http://dev.w3.org/html5/spec/forms.html#attr-input-pattern
-   *
-   * @param aValue    the string to check.
-   * @param aPattern  the string defining the pattern.
-   * @param aDocument the owner document of the element.
-   * @result          whether the given string is matches the pattern.
-   */
-  static PRBool IsPatternMatching(nsAString& aValue, nsAString& aPattern,
-                                  nsIDocument* aDocument);
-
   // Helper method
   nsresult SetValueInternal(const nsAString& aValue,
                             PRBool aUserInput,
                             PRBool aSetValueChanged);
 
   nsresult GetValueInternal(nsAString& aValue) const;
 
   /**
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -53,16 +53,17 @@ PEGatherMediaNotComma=Expected ',' in me
 PEGatherMediaNotIdent=Expected identifier in media list but found '%1$S'.
 PEImportNotURI=Expected URI in @import rule but found '%1$S'.
 PEImportBadURI=Invalid URI in @import rule: '%1$S'.
 PEImportUnexpected=Found unexpected '%1$S' within @import.
 PEGroupRuleEOF=end of @media or @-moz-document rule
 PEGroupRuleNestedAtRule=%1$S rule not allowed within @media or @-moz-document rule.
 PEMozDocRuleBadFunc=Expected url(), url-prefix(), or domain() in @-moz-document rule but found '%1$S'.
 PEMozDocRuleNotURI=Expected URI in @-moz-document rule but found '%1$S'.
+PEMozDocRuleNotString=Expected string in @-moz-document rule regexp() function but found '%1$S'.
 PEAtNSPrefixEOF=namespace prefix in @namespace rule
 PEAtNSURIEOF=namespace URI in @namespace rule
 PEAtNSUnexpected=Unexpected token within @namespace: '%1$S'.
 PEKeyframeNameEOF=name of @keyframes rule.
 PEKeyframeBadName=Expected identifier for name of @keyframes rule.
 PEKeyframeBrace=Expected opening { of @keyframes rule.
 PESkipDeclBraceEOF=closing } of declaration block
 PESkipRSBraceEOF=closing } of invalid rule set
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -2111,26 +2111,42 @@ CSSParserImpl::ParseMozDocumentRule(Rule
 {
   css::DocumentRule::URL *urls = nsnull;
   css::DocumentRule::URL **next = &urls;
   do {
     if (!GetToken(PR_TRUE) ||
         !(eCSSToken_URL == mToken.mType ||
           (eCSSToken_Function == mToken.mType &&
            (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix") ||
-            mToken.mIdent.LowerCaseEqualsLiteral("domain"))))) {
+            mToken.mIdent.LowerCaseEqualsLiteral("domain") ||
+            mToken.mIdent.LowerCaseEqualsLiteral("regexp"))))) {
       REPORT_UNEXPECTED_TOKEN(PEMozDocRuleBadFunc);
       delete urls;
       return PR_FALSE;
     }
     css::DocumentRule::URL *cur = *next = new css::DocumentRule::URL;
     next = &cur->next;
     if (mToken.mType == eCSSToken_URL) {
       cur->func = css::DocumentRule::eURL;
       CopyUTF16toUTF8(mToken.mIdent, cur->url);
+    } else if (mToken.mIdent.LowerCaseEqualsLiteral("regexp")) {
+      // regexp() is different from url-prefix() and domain() (but
+      // probably the way they *should* have been* in that it requires a
+      // string argument, and doesn't try to behave like url().
+      cur->func = css::DocumentRule::eRegExp;
+      GetToken(PR_TRUE);
+      // copy before we know it's valid (but before ExpectSymbol changes
+      // mToken.mIdent)
+      CopyUTF16toUTF8(mToken.mIdent, cur->url);
+      if (eCSSToken_String != mToken.mType || !ExpectSymbol(')', PR_TRUE)) {
+        REPORT_UNEXPECTED_TOKEN(PEMozDocRuleNotString);
+        SkipUntil(')');
+        delete urls;
+        return PR_FALSE;
+      }
     } else {
       if (mToken.mIdent.LowerCaseEqualsLiteral("url-prefix")) {
         cur->func = css::DocumentRule::eURLPrefix;
       } else if (mToken.mIdent.LowerCaseEqualsLiteral("domain")) {
         cur->func = css::DocumentRule::eDomain;
       }
 
       nsAutoString url;
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -942,16 +942,18 @@ DocumentRule::List(FILE* out, PRInt32 aI
       case eURL:
         str.AppendLiteral("url(\"");
         break;
       case eURLPrefix:
         str.AppendLiteral("url-prefix(\"");
         break;
       case eDomain:
         str.AppendLiteral("domain(\"");
+      case eRegExp:
+        str.AppendLiteral("regexp(\"");
         break;
     }
     nsCAutoString escapedURL(url->url);
     escapedURL.ReplaceSubstring("\"", "\\\""); // escape quotes
     str.Append(escapedURL);
     str.AppendLiteral("\"), ");
   }
   str.Cut(str.Length() - 2, 1); // remove last ,
@@ -993,16 +995,19 @@ DocumentRule::GetCssText(nsAString& aCss
         aCssText.AppendLiteral("url(");
         break;
       case eURLPrefix:
         aCssText.AppendLiteral("url-prefix(");
         break;
       case eDomain:
         aCssText.AppendLiteral("domain(");
         break;
+      case eRegExp:
+        aCssText.AppendLiteral("regexp(");
+        break;
     }
     nsStyleUtil::AppendEscapedCSSString(NS_ConvertUTF8toUTF16(url->url),
                                         aCssText);
     aCssText.AppendLiteral("), ");
   }
   aCssText.Cut(aCssText.Length() - 2, 1); // remove last ,
 
   return GroupRule::AppendRulesToCssText(aCssText);
@@ -1044,19 +1049,20 @@ NS_IMETHODIMP
 DocumentRule::DeleteRule(PRUint32 aIndex)
 {
   return GroupRule::DeleteRule(aIndex);
 }
 
 // GroupRule interface
 /* virtual */ PRBool
 DocumentRule::UseForPresentation(nsPresContext* aPresContext,
-                                      nsMediaQueryResultCacheKey& aKey)
+                                 nsMediaQueryResultCacheKey& aKey)
 {
-  nsIURI *docURI = aPresContext->Document()->GetDocumentURI();
+  nsIDocument *doc = aPresContext->Document();
+  nsIURI *docURI = doc->GetDocumentURI();
   nsCAutoString docURISpec;
   if (docURI)
     docURI->GetSpec(docURISpec);
 
   for (URL *url = mURLs; url; url = url->next) {
     switch (url->func) {
       case eURL: {
         if (docURISpec == url->url)
@@ -1075,16 +1081,23 @@ DocumentRule::UseForPresentation(nsPresC
           if (host == url->url)
             return PR_TRUE;
         } else {
           if (StringEndsWith(host, url->url) &&
               host.CharAt(lenDiff - 1) == '.')
             return PR_TRUE;
         }
       } break;
+      case eRegExp: {
+        NS_ConvertUTF8toUTF16 spec(docURISpec);
+        NS_ConvertUTF8toUTF16 regex(url->url);
+        if (nsContentUtils::IsPatternMatching(spec, regex, doc)) {
+          return PR_TRUE;
+        }
+      } break;
     }
   }
 
   return PR_FALSE;
 }
 
 DocumentRule::URL::~URL()
 {
--- a/layout/style/nsCSSRules.h
+++ b/layout/style/nsCSSRules.h
@@ -151,17 +151,18 @@ public:
 
   // rest of GroupRule
   virtual PRBool UseForPresentation(nsPresContext* aPresContext,
                                     nsMediaQueryResultCacheKey& aKey);
 
   enum Function {
     eURL,
     eURLPrefix,
-    eDomain
+    eDomain,
+    eRegExp
   };
 
   struct URL {
     Function func;
     nsCString url;
     URL *next;
 
     URL() : next(nsnull) {}
--- a/layout/style/test/chrome/Makefile.in
+++ b/layout/style/test/chrome/Makefile.in
@@ -44,13 +44,21 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _CHROME_FILES = \
     test_bug535806.xul \
     bug535806-css.css \
     bug535806-html.html \
     bug535806-xul.xul \
     test_hover.html \
+    test_moz_document_rules.html \
     hover_helper.html \
     $(NULL)
 
+_TEST_FILES = \
+    moz_document_helper.html \
+    $(NULL)
+
 libs:: $(_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/layout/style/test/chrome/moz_document_helper.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<div id="display" style="position: relative"></div>
new file mode 100644
--- /dev/null
+++ b/layout/style/test/chrome/test_moz_document_rules.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for @-moz-document rules</title>
+  <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=398962">Mozilla Bug 398962</a>
+<iframe id="iframe" src="http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html"></iframe>
+<pre id="test">
+<script type="application/javascript; version=1.8">
+
+var [gStyleSheetService, gIOService] = (function() {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    return [
+            Components.classes["@mozilla.org/content/style-sheet-service;1"]
+                .getService(Components.interfaces.nsIStyleSheetService),
+            Components.classes["@mozilla.org/network/io-service;1"]
+                .getService(Components.interfaces.nsIIOService)
+           ];
+})();
+function set_user_sheet(sheeturi)
+{
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var uri = gIOService.newURI(sheeturi, null, null);
+    gStyleSheetService.loadAndRegisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+function remove_user_sheet(sheeturi)
+{
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var uri = gIOService.newURI(sheeturi, null, null);
+    gStyleSheetService.unregisterSheet(uri, gStyleSheetService.USER_SHEET);
+}
+
+function run()
+{
+    var iframe = document.getElementById("iframe");
+    var subdoc = iframe.contentDocument;
+    var subwin = iframe.contentWindow;
+    var cs = subwin.getComputedStyle(subdoc.getElementById("display"), "");
+    var zIndexCounter = 0;
+
+    function test_document_rule(urltests, shouldapply)
+    {
+        var zIndex = ++zIndexCounter;
+        var rule = "@-moz-document " + urltests +
+                   " { #display { z-index: " + zIndex + " } }";
+        var sheeturi = "data:text/css," + encodeURI(rule);
+        set_user_sheet(sheeturi);
+        if (shouldapply) {
+            is(cs.zIndex, zIndex,
+               "@-moz-document " + urltests +
+               " should apply to this document");
+        } else {
+            is(cs.zIndex, "auto",
+               "@-moz-document " + urltests +
+               " should NOT apply to this document");
+        }
+        remove_user_sheet(sheeturi);
+    }
+
+    test_document_rule("domain(mochi.test)", true);
+    test_document_rule("domain(\"mochi.test\")", true);
+    test_document_rule("domain('mochi.test')", true);
+    test_document_rule("domain('test')", true);
+    test_document_rule("domain(.test)", false);
+    test_document_rule("domain('.test')", false);
+    test_document_rule("domain('ochi.test')", false);
+    test_document_rule("domain(ochi.test)", false);
+    test_document_rule("url-prefix(http://moch)", true);
+    test_document_rule("url-prefix(http://och)", false);
+    test_document_rule("url-prefix(http://mochi.test)", true);
+    test_document_rule("url-prefix(http://mochi.test:88)", true);
+    test_document_rule("url-prefix(http://mochi.test:8888)", true);
+    test_document_rule("url-prefix(http://mochi.test:8888/)", true);
+    test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true);
+    test_document_rule("url-prefix('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false);
+    test_document_rule("url(http://mochi.test:8888/)", false);
+    test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.html')", true);
+    test_document_rule("url('http://mochi.test:8888/tests/layout/style/test/chrome/moz_document_helper.htmlx')", false);
+    test_document_rule("regexp(.*ochi.*)", false); // syntax error
+    test_document_rule("regexp('.*ochi.*')", true);
+    test_document_rule("regexp('ochi.*')", false);
+    test_document_rule("regexp('.*ochi')", false);
+    test_document_rule("regexp('http:.*ochi.*')", true);
+    test_document_rule("regexp('http:.*ochi')", false);
+
+    SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>