Bug 779917 - Implement CSS.supports(). r=dbaron f=bz
authorCameron McCormack <cam@mcc.id.au>
Sun, 25 Nov 2012 11:26:07 +1100
changeset 126135 a981b50c1d43e18b121e86ce6e763e8a5961fdbf
parent 126134 18a3309af87e03227e987c2be6929744751a1262
child 126136 0b0b040b16d8a1a3ba3192834dcd78a5c243cc8f
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs779917
milestone20.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 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>