Bug 955860. Implement the CSS.escape API for escaping CSS identifiers from script. r=bzbarsky
authorSrirakshith Betageri <sbetageri111@gmail.com>
Thu, 20 Mar 2014 23:19:43 -0400
changeset 194255 1826224359f344944a90ee72bb9e78c971a6d7ff
parent 194254 d5dd0ed17d95f95b3ed8ae607e9694dbf0690a31
child 194256 550f66e6106ee9a3b4a0ab2bd04d24d28c47691e
push idunknown
push userunknown
push dateunknown
reviewersbzbarsky
bugs955860
milestone31.0a1
Bug 955860. Implement the CSS.escape API for escaping CSS identifiers from script. r=bzbarsky
dom/webidl/CSS.webidl
layout/style/CSS.cpp
layout/style/CSS.h
layout/style/nsStyleUtil.cpp
layout/style/nsStyleUtil.h
layout/style/test/mochitest.ini
layout/style/test/test_css_escape_api.html
--- a/dom/webidl/CSS.webidl
+++ b/dom/webidl/CSS.webidl
@@ -13,8 +13,14 @@
 [Pref="layout.css.supports-rule.enabled"]
 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);
 };
+
+// http://dev.w3.org/csswg/cssom/#the-css.escape%28%29-method
+partial interface CSS {
+  [Throws]
+  static DOMString escape(DOMString ident);
+};
--- a/layout/style/CSS.cpp
+++ b/layout/style/CSS.cpp
@@ -7,16 +7,17 @@
 
 #include "CSS.h"
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "nsCSSParser.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "nsIURI.h"
+#include "nsStyleUtil.h"
 
 namespace mozilla {
 namespace dom {
 
 struct SupportsParsingInfo
 {
   nsIURI* mDocURI;
   nsIURI* mBaseURI;
@@ -75,10 +76,23 @@ CSS::Supports(const GlobalObject& aGloba
     aRv.Throw(rv);
     return false;
   }
 
   return parser.EvaluateSupportsCondition(aCondition, info.mDocURI,
                                           info.mBaseURI, info.mPrincipal);
 }
 
+/* static */ void
+CSS::Escape(const GlobalObject& aGlobal,
+            const nsAString& aIdent,
+            nsAString& aReturn,
+            ErrorResult& aRv)
+{
+  bool success = nsStyleUtil::AppendEscapedCSSIdent(aIdent, aReturn);
+
+  if (!success) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+  }
+}
+
 } // dom
 } // mozilla
--- a/layout/style/CSS.h
+++ b/layout/style/CSS.h
@@ -27,14 +27,19 @@ public:
   static bool Supports(const GlobalObject& aGlobal,
                        const nsAString& aProperty,
                        const nsAString& aValue,
                        ErrorResult& aRv);
 
   static bool Supports(const GlobalObject& aGlobal,
                        const nsAString& aDeclaration,
                        ErrorResult& aRv);
+
+  static void Escape(const GlobalObject& aGlobal,
+                     const nsAString& aIdent,
+                     nsAString& aReturn,
+                     ErrorResult& aRv);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_CSS_h_
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -72,33 +72,33 @@ void nsStyleUtil::AppendEscapedCSSString
       }
       aReturn.Append(*in);
     }
   }
 
   aReturn.Append(quoteChar);
 }
 
-/* static */ void
+/* static */ bool
 nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn)
 {
   // The relevant parts of the CSS grammar are:
   //   ident    [-]?{nmstart}{nmchar}*
   //   nmstart  [_a-z]|{nonascii}|{escape}
   //   nmchar   [_a-z0-9-]|{nonascii}|{escape}
   //   nonascii [^\0-\177]
   //   escape   {unicode}|\\[^\n\r\f0-9a-f]
   //   unicode  \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
   // from http://www.w3.org/TR/CSS21/syndata.html#tokenization
 
   const char16_t* in = aIdent.BeginReading();
   const char16_t* const end = aIdent.EndReading();
 
   if (in == end)
-    return;
+    return true;
 
   // A leading dash does not need to be escaped as long as it is not the
   // *only* character in the identifier.
   if (in + 1 != end && *in == '-') {
     aReturn.Append(char16_t('-'));
     ++in;
   }
 
@@ -115,32 +115,36 @@ nsStyleUtil::AppendEscapedCSSIdent(const
     } else {
       aReturn.AppendPrintf("\\%hX ", *in);
     }
     ++in;
   }
 
   for (; in != end; ++in) {
     char16_t ch = *in;
+    if (ch == 0x00) {
+      return false;
+    }
     if (ch < 0x20 || (0x7F <= ch && ch < 0xA0)) {
       // Escape U+0000 through U+001F and U+007F through U+009F numerically.
       aReturn.AppendPrintf("\\%hX ", *in);
     } else {
       // Escape ASCII non-identifier printables as a backslash plus
       // the character.
       if (ch < 0x7F &&
           ch != '_' && ch != '-' &&
           (ch < '0' || '9' < ch) &&
           (ch < 'A' || 'Z' < ch) &&
           (ch < 'a' || 'z' < ch)) {
         aReturn.Append(char16_t('\\'));
       }
       aReturn.Append(ch);
     }
   }
+  return true;
 }
 
 /* static */ void
 nsStyleUtil::AppendBitmaskCSSValue(nsCSSProperty aProperty,
                                    int32_t aMaskedValue,
                                    int32_t aFirstMask,
                                    int32_t aLastMask,
                                    nsAString& aResult)
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -32,17 +32,19 @@ public:
   // to aResult.  'quoteChar' must be ' or ".
   static void AppendEscapedCSSString(const nsAString& aString,
                                      nsAString& aResult,
                                      char16_t quoteChar = '"');
 
   // Append the identifier given by |aIdent| to |aResult|, with
   // appropriate escaping so that it can be reparsed to the same
   // identifier.
-  static void AppendEscapedCSSIdent(const nsAString& aIdent,
+  // Returns false if |aIdent| contains U+0000
+  // Returns true for all other cases
+  static bool AppendEscapedCSSIdent(const nsAString& aIdent,
                                     nsAString& aResult);
 
   // Append a bitmask-valued property's value(s) (space-separated) to aResult.
   static void AppendBitmaskCSSValue(nsCSSProperty aProperty,
                                     int32_t aMaskedValue,
                                     int32_t aFirstMask,
                                     int32_t aLastMask,
                                     nsAString& aResult);
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -105,16 +105,17 @@ skip-if = toolkit == 'android'
 [test_computed_style_no_pseudo.html]
 [test_computed_style_prefs.html]
 [test_condition_text.html]
 [test_condition_text_assignment.html]
 [test_default_computed_style.html]
 [test_css_cross_domain.html]
 skip-if = toolkit == 'android' #bug 536603
 [test_css_eof_handling.html]
+[test_css_escape_api.html]
 [test_css_function_mismatched_parenthesis.html]
 [test_css_supports.html]
 [test_css_supports_variables.html]
 [test_default_bidi_css.html]
 [test_descriptor_storage.html]
 [test_descriptor_syntax_errors.html]
 [test_dont_use_document_colors.html]
 [test_extra_inherit_initial.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_css_escape_api.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=955860
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 955860</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=955860">Mozilla Bug 955860</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script>
+SimpleTest.doesThrow(() => CSS.escape('\0'), "InvalidCharacterError Character :\\0");
+SimpleTest.doesThrow(() => CSS.escape('a\0'), "InvalidCharacterError Character : a\\0");
+SimpleTest.doesThrow(() => CSS.escape('\0b'), "InvalidCharacterError Character : \\0b");
+SimpleTest.doesThrow(() => CSS.escape('a\0b'), "InvalidCharacterError Character : a\\0b");
+SimpleTest.doesThrow(() => CSS.escape(), 'undefined');
+is(CSS.escape(true), 'true', "escapingFailed Character : true(bool)");
+is(CSS.escape(false), 'false', "escapingFailed Character : false(bool)");
+is(CSS.escape(null), 'null', "escapingFailed Character : null");
+is(CSS.escape(''), '', "escapingFailed Character : '' ");
+
+is(CSS.escape('\x01\x02\x1E\x1F'), '\\1 \\2 \\1E \\1F ',"escapingFailed Char: \\x01\\x02\\x1E\\x1F");
+
+is(CSS.escape('0a'), '\\30 a', "escapingFailed Char: 0a");
+is(CSS.escape('1a'), '\\31 a', "escapingFailed Char: 1a");
+is(CSS.escape('2a'), '\\32 a', "escapingFailed Char: 2a");
+is(CSS.escape('3a'), '\\33 a', "escapingFailed Char: 3a");
+is(CSS.escape('4a'), '\\34 a', "escapingFailed Char: 4a");
+is(CSS.escape('5a'), '\\35 a', "escapingFailed Char: 5a");
+is(CSS.escape('6a'), '\\36 a', "escapingFailed Char: 6a");
+is(CSS.escape('7a'), '\\37 a', "escapingFailed Char: 7a");
+is(CSS.escape('8a'), '\\38 a', "escapingFailed Char: 8a");
+is(CSS.escape('9a'), '\\39 a', "escapingFailed Char: 9a");
+
+is(CSS.escape('a0b'), 'a0b', "escapingFailed Char: a0b");
+is(CSS.escape('a1b'), 'a1b', "escapingFailed Char: a1b");
+is(CSS.escape('a2b'), 'a2b', "escapingFailed Char: a2b");
+is(CSS.escape('a3b'), 'a3b', "escapingFailed Char: a3b");
+is(CSS.escape('a4b'), 'a4b', "escapingFailed Char: a4b");
+is(CSS.escape('a5b'), 'a5b', "escapingFailed Char: a5b");
+is(CSS.escape('a6b'), 'a6b', "escapingFailed Char: a6b");
+is(CSS.escape('a7b'), 'a7b', "escapingFailed Char: a7b");
+is(CSS.escape('a8b'), 'a8b', "escapingFailed Char: a8b");
+is(CSS.escape('a9b'), 'a9b', "escapingFailed Char: a9b");
+
+is(CSS.escape('-0a'), '-\\30 a', "escapingFailed Char: -0a");
+is(CSS.escape('-1a'), '-\\31 a', "escapingFailed Char: -1a");
+is(CSS.escape('-2a'), '-\\32 a', "escapingFailed Char: -2a");
+is(CSS.escape('-3a'), '-\\33 a', "escapingFailed Char: -3a");
+is(CSS.escape('-4a'), '-\\34 a', "escapingFailed Char: -4a");
+is(CSS.escape('-5a'), '-\\35 a', "escapingFailed Char: -5a");
+is(CSS.escape('-6a'), '-\\36 a', "escapingFailed Char: -6a");
+is(CSS.escape('-7a'), '-\\37 a', "escapingFailed Char: -7a");
+is(CSS.escape('-8a'), '-\\38 a', "escapingFailed Char: -8a");
+is(CSS.escape('-9a'), '-\\39 a', "escapingFailed Char: -9a");
+
+is(CSS.escape('--a'), '-\\-a', "escapingFailed Char: --a");
+
+is(CSS.escape('\x80\x2D\x5F\xA9'), '\\80 \x2D\x5F\xA9', "escapingFailed Char: \\x80\\x2D\\x5F\\xA9");
+is(CSS.escape('\xA0\xA1\xA2'), '\xA0\xA1\xA2', "escapingFailed Char: \\xA0\\xA1\\xA2");
+is(CSS.escape('a0123456789b'), 'a0123456789b', "escapingFailed Char: a0123465789");
+is(CSS.escape('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz', "escapingFailed Char: abcdefghijklmnopqrstuvwxyz");
+is(CSS.escape('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', "escapingFailed Char: ABCDEFGHIJKLMNOPQRSTUVWXYZBCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+is(CSS.escape('\x20\x21\x78\x79'), '\\ \\!xy', "escapingFailed Char: \\x20\\x21\\x78\\x79");
+
+// astral symbol (U+1D306 TETRAGRAM FOR CENTRE)
+is(CSS.escape('\uD834\uDF06'), '\uD834\uDF06', "escapingFailed Char:\\uD834\\uDF06");
+// lone surrogates
+is(CSS.escape('\uDF06'), '\uDF06', "escapingFailed Char: \\uDF06");
+is(CSS.escape('\uD834'), '\uD834', "escapingFailed Char: \\uD834");
+</script>
+</pre>
+</body>
+</html>