Bug 779917 - Implement CSS.supports(). r=dbaron f=bz
authorCameron McCormack <cam@mcc.id.au>
Sun, 25 Nov 2012 11:26:07 +1100
changeset 126890 a981b50c1d43e18b121e86ce6e763e8a5961fdbf
parent 126889 18a3309af87e03227e987c2be6929744751a1262
child 126891 0b0b040b16d8a1a3ba3192834dcd78a5c243cc8f
push idunknown
push userunknown
push dateunknown
reviewersdbaron
bugs779917
milestone20.0a1
Bug 779917 - Implement CSS.supports(). r=dbaron f=bz
dom/bindings/Bindings.conf
dom/webidl/CSS.webidl
dom/webidl/WebIDL.mk
layout/style/CSS.cpp
layout/style/CSS.h
layout/style/Makefile.in
layout/style/nsCSSParser.cpp
layout/style/nsCSSParser.h
layout/style/test/Makefile.in
layout/style/test/test_css_supports.html
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -139,16 +139,20 @@ DOMInterfaces = {
 },
 
 'ClientRectList': {
     'nativeType': 'nsClientRectList',
     'headerFile': 'nsClientRect.h',
     'resultNotAddRefed': [ 'item' ]
 },
 
+'CSS': {
+    'concrete': False,
+},
+
 'CSS2Properties': {
   'nativeType': 'nsDOMCSSDeclaration'
 },
 
 "CSSPrimitiveValue": {
     "nativeType": "nsROCSSPrimitiveValue",
     "resultNotAddRefed": ["GetRGBColorValue"]
 },
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CSS.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dev.w3.org/csswg/css3-conditional/
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[PrefControlled]
+interface CSS {
+  [Throws, Pref="layout.css.supports-rule.enabled"]
+  static boolean supports(DOMString property, DOMString value);
+
+  [Throws, Pref="layout.css.supports-rule.enabled"]
+  static boolean supports(DOMString conditionText);
+};
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -16,16 +16,17 @@ webidl_files = \
   AudioListener.webidl \
   AudioNode.webidl \
   AudioParam.webidl \
   AudioSourceNode.webidl \
   BiquadFilterNode.webidl \
   Blob.webidl \
   CanvasRenderingContext2D.webidl \
   ClientRectList.webidl \
+  CSS.webidl \
   CSSPrimitiveValue.webidl \
   CSSStyleDeclaration.webidl \
   CSSValue.webidl \
   CSSValueList.webidl \
   DelayNode.webidl \
   Document.webidl \
   DocumentFragment.webidl \
   DOMImplementation.webidl \
new file mode 100644
--- /dev/null
+++ b/layout/style/CSS.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object holding utility CSS functions */
+
+#include "CSS.h"
+
+#include "nsCSSParser.h"
+#include "nsGlobalWindow.h"
+#include "nsIDOMDocument.h"
+#include "nsIDocument.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+
+namespace mozilla {
+namespace dom {
+
+struct SupportsParsingInfo
+{
+  nsIURI* mDocURI;
+  nsIURI* mBaseURI;
+  nsIPrincipal* mPrincipal;
+};
+
+static nsresult
+GetParsingInfo(nsISupports* aGlobal,
+               SupportsParsingInfo& aInfo)
+{
+  nsGlobalWindow* win = nsGlobalWindow::FromSupports(aGlobal);
+  nsCOMPtr<nsIDocument> doc = win->GetDoc();
+  if (!doc) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aInfo.mDocURI = nsCOMPtr<nsIURI>(doc->GetDocumentURI());
+  aInfo.mBaseURI = nsCOMPtr<nsIURI>(doc->GetBaseURI());
+  aInfo.mPrincipal = win->GetPrincipal();
+  return NS_OK;
+}
+
+/* static */ bool
+CSS::Supports(nsISupports* aGlobal,
+              const nsAString& aProperty,
+              const nsAString& aValue,
+              ErrorResult& aRv)
+{
+  nsCSSParser parser;
+  SupportsParsingInfo info;
+
+  nsresult rv = GetParsingInfo(aGlobal, info);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return false;
+  }
+
+  return parser.EvaluateSupportsDeclaration(aProperty, aValue, info.mDocURI,
+                                            info.mBaseURI, info.mPrincipal);
+}
+
+/* static */ bool
+CSS::Supports(nsISupports* aGlobal,
+              const nsAString& aCondition,
+              ErrorResult& aRv)
+{
+  nsCSSParser parser;
+  SupportsParsingInfo info;
+
+  nsresult rv = GetParsingInfo(aGlobal, info);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return false;
+  }
+
+  return parser.EvaluateSupportsCondition(aCondition, info.mDocURI,
+                                          info.mBaseURI, info.mPrincipal);
+}
+
+} // dom
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/layout/style/CSS.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* DOM object holding utility CSS functions */
+
+#ifndef mozilla_dom_CSS_h_
+#define mozilla_dom_CSS_h_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+namespace dom {
+
+class CSS {
+private:
+  CSS() MOZ_DELETE;
+
+public:
+  static bool Supports(nsISupports* aGlobal,
+                       const nsAString& aProperty,
+                       const nsAString& aValue,
+                       ErrorResult& aRv);
+
+  static bool Supports(nsISupports* aGlobal,
+                       const nsAString& aDeclaration,
+                       ErrorResult& aRv);
+
+  static bool PrefEnabled()
+  {
+    return Preferences::GetBool("layout.css.supports-rule.enabled");
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CSS_h_
--- a/layout/style/Makefile.in
+++ b/layout/style/Makefile.in
@@ -38,17 +38,17 @@ EXPORTS		= \
 		nsCSSPseudoClasses.h \
 		nsCSSPseudoElementList.h \
 		nsCSSPseudoElements.h \
 		nsCSSRuleProcessor.h \
 		nsCSSStyleSheet.h \
 		nsCSSValue.h \
 		nsDOMCSSAttrDeclaration.h \
 		nsDOMCSSDeclaration.h \
-	 nsDOMCSSRGBColor.h \
+		nsDOMCSSRGBColor.h \
 		nsDOMMediaQueryList.h \
 		nsICSSDeclaration.h \
 		nsICSSLoaderObserver.h \
 		nsICSSPseudoComparator.h \
 		nsICSSRuleList.h \
 		nsICSSStyleRuleDOMWrapper.h \
 		nsIStyleRule.h \
 		nsIStyleRuleProcessor.h \
@@ -78,21 +78,23 @@ EXPORTS_mozilla/css = \
 		ImportRule.h \
 		Loader.h \
 		NameSpaceRule.h \
 		Rule.h \
 		StyleRule.h \
 		$(NULL)
 
 EXPORTS_mozilla/dom = \
-  CSSValue.h \
-  $(null)
+		CSS.h \
+		CSSValue.h \
+		$(NULL)
 
 CPPSRCS		= \
 		AnimationCommon.cpp \
+		CSS.cpp \
 		nsCSSAnonBoxes.cpp \
 		nsCSSDataBlock.cpp \
 		Declaration.cpp \
 		ErrorReporter.cpp \
 		nsCSSKeywords.cpp \
 		ImageLoader.cpp \
 		Loader.cpp \
 		nsAnimationManager.cpp \
@@ -135,16 +137,17 @@ CPPSRCS		= \
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 
 LOCAL_INCLUDES	+= \
 		-I$(srcdir)/../base \
+		-I$(topsrcdir)/dom/base \
 		-I$(srcdir)/../generic \
 		-I$(srcdir)/../xul/base/src \
 		-I$(srcdir)/../../content/base/src \
 		-I$(srcdir)/../../content/html/content/src \
 		-I$(srcdir)/../../content/xbl/src \
 		-I$(srcdir)/../../content/xul/document/src \
 		$(NULL)
 
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -224,16 +224,27 @@ public:
                     nsIURI*            aURL,
                     uint32_t           aLineNumber);
 
   bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
                                    nsIURI* aURL, // for error reporting
                                    uint32_t aLineNumber, // for error reporting
                                    InfallibleTArray<float>& aSelectorList);
 
+  bool EvaluateSupportsDeclaration(const nsAString& aProperty,
+                                   const nsAString& aValue,
+                                   nsIURI* aDocURL,
+                                   nsIURI* aBaseURL,
+                                   nsIPrincipal* aDocPrincipal);
+
+  bool EvaluateSupportsCondition(const nsAString& aCondition,
+                                 nsIURI* aDocURL,
+                                 nsIURI* aBaseURL,
+                                 nsIPrincipal* aDocPrincipal);
+
 protected:
   class nsAutoParseCompoundProperty;
   friend class nsAutoParseCompoundProperty;
 
   class nsAutoFailingSupportsRule;
   friend class nsAutoFailingSupportsRule;
 
   class nsAutoSuppressErrors;
@@ -294,17 +305,17 @@ protected:
 
   /**
    * Auto class to set aParser->mSuppressErrors to the specified value
    * and restore it to its original value later.
    */
   class nsAutoSuppressErrors {
     public:
       nsAutoSuppressErrors(CSSParserImpl* aParser,
-                           bool aSuppressErrors)
+                           bool aSuppressErrors = true)
         : mParser(aParser),
           mOriginalValue(aParser->mSuppressErrors)
       {
         mParser->mSuppressErrors = aSuppressErrors;
       }
 
       ~nsAutoSuppressErrors()
       {
@@ -1345,16 +1356,65 @@ CSSParserImpl::ParseKeyframeSelectorStri
     NS_ASSERTION(!aSelectorList.IsEmpty(), "should not be empty");
   } else {
     aSelectorList.Clear();
   }
 
   return success;
 }
 
+bool
+CSSParserImpl::EvaluateSupportsDeclaration(const nsAString& aProperty,
+                                           const nsAString& aValue,
+                                           nsIURI* aDocURL,
+                                           nsIURI* aBaseURL,
+                                           nsIPrincipal* aDocPrincipal)
+{
+  nsCSSProperty propID = nsCSSProps::LookupProperty(aProperty,
+                                                    nsCSSProps::eEnabled);
+  if (propID == eCSSProperty_UNKNOWN) {
+    return false;
+  }
+
+  nsCSSScanner scanner(aValue, 0);
+  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
+  InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
+  nsAutoSuppressErrors suppressErrors(this);
+
+  bool parsedOK = ParseProperty(propID) && !GetToken(true);
+
+  CLEAR_ERROR();
+  ReleaseScanner();
+
+  mTempData.ClearProperty(propID);
+  mTempData.AssertInitialState();
+
+  return parsedOK;
+}
+
+bool
+CSSParserImpl::EvaluateSupportsCondition(const nsAString& aDeclaration,
+                                         nsIURI* aDocURL,
+                                         nsIURI* aBaseURL,
+                                         nsIPrincipal* aDocPrincipal)
+{
+  nsCSSScanner scanner(aDeclaration, 0);
+  css::ErrorReporter reporter(scanner, mSheet, mChildLoader, aDocURL);
+  InitScanner(scanner, reporter, aDocURL, aBaseURL, aDocPrincipal);
+  nsAutoSuppressErrors suppressErrors(this);
+
+  bool conditionMet;
+  bool parsedOK = ParseSupportsCondition(conditionMet) && !GetToken(true);
+
+  CLEAR_ERROR();
+  ReleaseScanner();
+
+  return parsedOK && conditionMet;
+}
+
 //----------------------------------------------------------------------
 
 bool
 CSSParserImpl::GetToken(bool aSkipWS)
 {
   for (;;) {
     if (!mHavePushBack) {
       if (!mScanner->Next(mToken)) {
@@ -10209,8 +10269,30 @@ nsCSSParser::ParseKeyframeSelectorString
                                          nsIURI*            aURI,
                                          uint32_t           aLineNumber,
                                          InfallibleTArray<float>& aSelectorList)
 {
   return static_cast<CSSParserImpl*>(mImpl)->
     ParseKeyframeSelectorString(aSelectorString, aURI, aLineNumber,
                                 aSelectorList);
 }
+
+bool
+nsCSSParser::EvaluateSupportsDeclaration(const nsAString& aProperty,
+                                         const nsAString& aValue,
+                                         nsIURI* aDocURL,
+                                         nsIURI* aBaseURL,
+                                         nsIPrincipal* aDocPrincipal)
+{
+  return static_cast<CSSParserImpl*>(mImpl)->
+    EvaluateSupportsDeclaration(aProperty, aValue, aDocURL, aBaseURL,
+                                aDocPrincipal);
+}
+
+bool
+nsCSSParser::EvaluateSupportsCondition(const nsAString& aCondition,
+                                       nsIURI* aDocURL,
+                                       nsIURI* aBaseURL,
+                                       nsIPrincipal* aDocPrincipal)
+{
+  return static_cast<CSSParserImpl*>(mImpl)->
+    EvaluateSupportsCondition(aCondition, aDocURL, aBaseURL, aDocPrincipal);
+}
--- a/layout/style/nsCSSParser.h
+++ b/layout/style/nsCSSParser.h
@@ -175,16 +175,35 @@ public:
    * Parse a selector list for a keyframe rule.  Return whether
    * the parse succeeded.
    */
   bool ParseKeyframeSelectorString(const nsSubstring& aSelectorString,
                                    nsIURI*            aURL,
                                    uint32_t           aLineNumber,
                                    InfallibleTArray<float>& aSelectorList);
 
+  /**
+   * Parse a property and value and return whether the property/value pair
+   * is supported.
+   */
+  bool EvaluateSupportsDeclaration(const nsAString& aProperty,
+                                   const nsAString& aValue,
+                                   nsIURI* aDocURL,
+                                   nsIURI* aBaseURL,
+                                   nsIPrincipal* aDocPrincipal);
+
+  /**
+   * Parse an @supports condition and returns the result of evaluating the
+   * condition.
+   */
+  bool EvaluateSupportsCondition(const nsAString& aCondition,
+                                 nsIURI* aDocURL,
+                                 nsIURI* aBaseURL,
+                                 nsIPrincipal* aDocPrincipal);
+
 protected:
   // This is a CSSParserImpl*, but if we expose that type name in this
   // header, we can't put the type definition (in nsCSSParser.cpp) in
   // the anonymous namespace.
   void* mImpl;
 };
 
 #endif /* nsCSSParser_h___ */
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -93,16 +93,17 @@ MOCHITEST_FILES =	test_acid3_test46.html
 		test_compute_data_with_start_struct.html \
 		test_computed_style.html \
 		test_computed_style_no_pseudo.html \
 		test_condition_text.html \
 		test_condition_text_assignment.html \
 		test_default_computed_style.html \
 		test_css_cross_domain.html \
 		test_css_eof_handling.html \
+		test_css_supports.html \
 		test_default_bidi_css.html \
 		test_descriptor_storage.html \
 		test_descriptor_syntax_errors.html \
 		test_dont_use_document_colors.html \
 		test_font_face_parser.html \
 		test_garbage_at_end_of_declarations.html \
 		test_html_attribute_computed_values.html \
 		test_ident_escaping.html \
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_css_supports.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=779917
+-->
+<head>
+  <title>Test for Bug 779917</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=779917">Mozilla Bug 779917</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 779917 **/
+
+function runTest()
+{
+  var passingConditions = [
+    "(color: green)",
+    "((color: green))",
+    "(color: green !important)",
+    "(color: rainbow) or (color: green)",
+    "(color: green) or (color: rainbow)",
+    "(color: green) and (color: blue)",
+    "(color: rainbow) or (color: iridescent) or (color: green)",
+    "(color: red) and (color: green) and (color: blue)",
+    "(color:green)",
+    "not (color: rainbow)",
+    "not (not (color: green))",
+    "(unknown:) or (color: green)",
+    "(unknown) or (color: green)",
+    "(font: 16px serif)",
+    "(color:) or (color: green)",
+    "not (@page)",
+    "not ({ something @with [ balanced ] brackets })",
+    "an-extension(of some kind) or (color: green)",
+    "not ()",
+    "( Font:  20px serif ! Important) ",
+    "(color: /* comment */ green)",
+    "(/* comment */ color: green)",
+    "(color: green /* comment */)",
+    "(color: green) /* comment */",
+    "/* comment */ (color: green)",
+    "(color /* comment */: green)",
+    "(color: green) /* unclosed comment",
+    "(color: green",
+    "(((((((color: green",
+    "(font-family: 'Helvetica"
+  ];
+
+  var failingConditions = [
+    "(color: rainbow)",
+    "(color: rainbow) and (color: green)",
+    "(color: blue) and (color: rainbow)",
+    "(color: green) and (color: green) or (color: green)",
+    "(color: green) or (color: green) and (color: green)",
+    "not not (color: green)",
+    "not (color: rainbow) and not (color: iridescent)",
+    "not (color: rainbow) or (color: green)",
+    "(not (color: rainbow) or (color: green))",
+    "(unknown: green)",
+    "not ({ something @with (unbalanced brackets })",
+    "(color: green) or an-extension(that is [unbalanced)",
+    "not(unknown: unknown)",
+    "(color: green) or(color: blue)",
+    "color: green",
+    "(color: green;)",
+    "(font-family: 'Helvetica\n",
+    "(font-family: 'Helvetica\n')",
+    "()",
+    ""
+  ];
+
+  var passingDeclarations = [
+    ["color", "green"],
+    ["color", " green "],
+    ["Color", "Green"],
+    ["color", "green /* comment */"],
+    ["color", "/* comment */ green"],
+    ["color", "green /* unclosed comment"],
+    ["font", "16px serif"],
+    ["font", "16px /* comment */ serif"],
+    ["font", "16px\nserif"],
+    ["color", "\\0067reen"]
+  ];
+
+  var failingDeclarations = [
+    ["color ", "green"],
+    ["color", "rainbow"],
+    ["color", "green green"],
+    ["color", "green !important"],
+    ["\\0063olor", "green"],
+    ["/* comment */color", "green"],
+    ["color/* comment */", "green"],
+    ["font-family", "'Helvetica\n"],
+    ["font-family", "'Helvetica\n'"],
+    ["color", "green;"],
+    ["color", ""],
+    ["unknown", "unknown"],
+    ["", "green"],
+    ["", ""]
+  ];
+
+  passingConditions.forEach(function(aCondition) {
+    is(CSS.supports(aCondition), true, "CSS.supports returns true for passing condition \"" + aCondition + "\"");
+  });
+
+  failingConditions.forEach(function(aCondition) {
+    is(CSS.supports(aCondition), false, "CSS.supports returns false for failing condition \"" + aCondition + "\"");
+  });
+
+  passingDeclarations.forEach(function(aDeclaration) {
+    is(CSS.supports(aDeclaration[0], aDeclaration[1]), true, "CSS.supports returns true for supported declaration \"" + aDeclaration.join(":") + "\"");
+  });
+
+  failingDeclarations.forEach(function(aDeclaration) {
+    is(CSS.supports(aDeclaration[0], aDeclaration[1]), false, "CSS.supports returns false for unsupported declaration \"" + aDeclaration.join(":") + "\"");
+  });
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.supports-rule.enabled", true]] }, runTest);
+</script>
+</pre>
+</body>
+</html>