Bug 816720 - Allow CSSRule.insertRule to insert non-style rules. r=bz
authorCameron McCormack <cam@mcc.id.au>
Sat, 01 Dec 2012 15:10:45 +1100
changeset 115051 447dff867909781e0716c7098bcd8e53101b0093
parent 115050 3d1c2eec5a9b43fbd586c16ea370a53a4e994a06
child 115052 87c471673853a6c97145bfcd0483d4111b17f4c2
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersbz
bugs816720
milestone20.0a1
Bug 816720 - Allow CSSRule.insertRule to insert non-style rules. r=bz
layout/style/nsCSSStyleSheet.cpp
layout/style/test/Makefile.in
layout/style/test/test_rule_insertion.html
--- a/layout/style/nsCSSStyleSheet.cpp
+++ b/layout/style/nsCSSStyleSheet.cpp
@@ -2137,20 +2137,35 @@ nsCSSStyleSheet::InsertRuleIntoGroup(con
   if (rulecount == 0) {
     // Since we know aRule was not an empty string, just throw
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
   int32_t counter;
   css::Rule* rule;
   for (counter = 0; counter < rulecount; counter++) {
-    // Only rulesets are allowed in a group as of CSS2
     rule = rules.ObjectAt(counter);
-    if (rule->GetType() != css::Rule::STYLE_RULE) {
-      return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+    switch (rule->GetType()) {
+      case css::Rule::STYLE_RULE:
+      case css::Rule::MEDIA_RULE:
+      case css::Rule::FONT_FACE_RULE:
+      case css::Rule::PAGE_RULE:
+      case css::Rule::KEYFRAMES_RULE:
+      case css::Rule::DOCUMENT_RULE:
+      case css::Rule::SUPPORTS_RULE:
+        // these types are OK to insert into a group
+        break;
+      case css::Rule::CHARSET_RULE:
+      case css::Rule::IMPORT_RULE:
+      case css::Rule::NAMESPACE_RULE:
+        // these aren't
+        return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
+      default:
+        NS_NOTREACHED("unexpected rule type");
+        return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
     }
   }
   
   result = aGroup->InsertStyleRulesAt(aIndex, rules);
   NS_ENSURE_SUCCESS(result, result);
   DidDirty();
   for (counter = 0; counter < rulecount; counter++) {
     rule = rules.ObjectAt(counter);
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -122,16 +122,17 @@ MOCHITEST_FILES =	test_acid3_test46.html
 		test_parse_url.html \
 		test_parser_diagnostics_unprintables.html \
 		test_pixel_lengths.html \
 		test_pointer-events.html \
 		test_property_database.html \
 		test_priority_preservation.html \
 		test_property_syntax_errors.html \
 		test_rem_unit.html \
+		test_rule_insertion.html \
 		test_rule_serialization.html \
 		test_rules_out_of_sheets.html \
 		test_selectors.html \
 		test_selectors_on_anonymous_content.html \
 		test_shorthand_property_getters.html \
 		test_style_struct_copy_constructors.html \
 		test_supports_rules.html \
 		test_system_font_serialization.html \
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_rule_insertion.html
@@ -0,0 +1,213 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=816720
+-->
+<head>
+  <title>Test for Bug 816720</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" id="style"></style>
+</head>
+<body>
+
+<pre id="test"></pre>
+
+<p><span id=control-serif>.</span></p>
+<p><span id=control-monospace>.</span></p>
+<p><span id=test-font>.</span></p>
+
+<style id=other-styles>
+  #test { font-size: 16px; animation: test 1s both }
+  #control-serif { font: 16px serif }
+  #test-font { font: 16px UnlikelyFontName, serif }
+</style>
+
+<script type="application/javascript">
+
+// Monospace fonts available on all the platforms we're testing on.
+//
+// XXX Once bug 817220 is fixed we could instead use the value of
+// font.name.monospace.x-western as the monospace font to use.
+var MONOSPACE_FONTS = [
+  "Courier",
+  "Courier New",
+  "Monaco",
+  "DejaVu Sans Mono",
+  "Droid Sans Mono"
+];
+
+var test = document.getElementById("test");
+var controlSerif = document.getElementById("control-serif");
+var controlMonospace = document.getElementById("control-monospace");
+var testFont = document.getElementById("test-font");
+var otherStyles = document.getElementById("other-styles");
+
+otherStyles.sheet.insertRule("#control-monospace { font: 16px " +
+                             MONOSPACE_FONTS + ", serif }", 0);
+
+var monospaceWidth = controlMonospace.getBoundingClientRect().width;
+var serifWidth = controlSerif.getBoundingClientRect().width;
+
+// [at-rule type, passing condition, failing condition]
+var outerRuleInfo = [
+  ["@media", "all", "not all"],
+  ["@-moz-document", "url-prefix('')", "url-prefix('zzz')"],
+  ["@supports", "(color: green)", "(unknown: unknown)"]
+];
+
+// [rule, function to test whether the rule was successfully inserted and applied]
+var innerRuleInfo = [
+  ["#test { text-decoration: underline; }",
+   function(aApplied, aParent, aException) {
+     return !aException &&
+            window.getComputedStyle(test, "").textDecoration ==
+               (aApplied ? "underline" : "none");
+   }],
+  ["@page { margin: 4cm; }",
+   function(aApplied, aParent, aException) {
+     // just test whether it threw
+     return !aException;
+   }],
+  ["@keyframes test { from { font-size: 100px; } to { font-size: 100px; } }",
+   function(aApplied, aParent, aException) {
+     return !aException &&
+            window.getComputedStyle(test, "").fontSize ==
+                (aApplied ? "100px" : "16px")
+   }],
+  ["@font-face { font-family: UnlikelyFontName; src: " +
+     MONOSPACE_FONTS.map(function(s) { return "local('" + s + "')" }).join(", ") + "; }",
+   function(aApplied, aParent, aException) {
+     var width = testFont.getBoundingClientRect().width;
+     if (aException) {
+       return false;
+     }
+     if (navigator.oscpu.match(/Linux/)) {
+       return true;
+    }
+    return width == (aApplied ? monospaceWidth : serifWidth) ||
+           navigator.oscpu.match(/Android/); // bug 769194 prevents local()
+                                             // fonts working on Android
+   }],
+  ["@charset 'UTF-8';",
+   function(aApplied, aParent, aException) {
+     // just test whether it threw
+     return aParent instanceof CSSRule ? aException : !aException;
+   }],
+  ["@import url(nothing.css);",
+   function(aApplied, aParent, aException) {
+     // just test whether it threw
+     return aParent instanceof CSSRule ? aException : !aException;
+   }],
+  ["@namespace test url(http://example.org);",
+   function(aApplied, aParent, aException) {
+     // just test whether it threw
+     return aParent instanceof CSSRule ? aException : !aException;
+   }],
+];
+
+function runTest()
+{
+  // First, assert that our assumed available fonts are indeed available
+  // and have expected metrics.
+  ok(monospaceWidth > 0, "monospace text has width");
+  ok(serifWidth > 0, "serif text has width");
+  isnot(monospaceWidth, serifWidth, "monospace and serif text have different widths");
+
+  // And that the #test-font element starts off using the "serif" font.
+  var initialFontTestWidth = testFont.getBoundingClientRect().width;
+  is(initialFontTestWidth, serifWidth);
+
+  // We construct a style sheet with zero, one or two levels of conditional
+  // grouping rules (taken from outerRuleInfo), with one of the inner rules
+  // at the deepest level.
+  var style = document.getElementById("style");
+
+  // For each of the outer rule types...
+  for (var outerRule1 = 0; outerRule1 < outerRuleInfo.length; outerRule1++) {
+    // For each of { 0 = don't create an outer rule,
+    //               1 = create an outer rule with a passing condition,
+    //               2 = create an outer rule with a failing condition }...
+    for (var outerRuleCondition1 = 0; outerRuleCondition1 <= 2; outerRuleCondition1++) {
+
+      // For each of the outer rule types again...
+      for (var outerRule2 = 0; outerRule2 < outerRuleInfo.length; outerRule2++) {
+        // For each of { 0 = don't create an outer rule,
+        //               1 = create an outer rule with a passing condition,
+        //               2 = create an outer rule with a failing condition } again...
+        for (var outerRuleCondition2 = 0; outerRuleCondition2 <= 2; outerRuleCondition2++) {
+
+          // For each of the inner rule types...
+          for (var innerRule = 0; innerRule < innerRuleInfo.length; innerRule++) {
+
+            // Clear rules
+            var object = style.sheet;
+            while (object.cssRules.length) {
+              object.deleteRule(0);
+            }
+
+            // We'll record whether the inner rule should have been applied,
+            // according to whether we put passing or failing conditional
+            // grouping rules around it.
+            var applied = true;
+
+            if (outerRuleCondition1) {
+              // Create an outer conditional rule.
+              object.insertRule([outerRuleInfo[outerRule1][0],
+                                 outerRuleInfo[outerRule1][outerRuleCondition1],
+                                 "{}"].join(" "), 0);
+              object = object.cssRules[0];
+
+              if (outerRuleCondition1 == 2) {
+                // If we used a failing condition, we don't expect the inner
+                // rule to be applied.
+                applied = false;
+              }
+            }
+
+            if (outerRuleCondition2) {
+              // Create another outer conditional rule as a child of the first
+              // outer conditional rule (or the style sheet, if we didn't create
+              // a first outer conditional rule).
+              object.insertRule([outerRuleInfo[outerRule2][0],
+                                 outerRuleInfo[outerRule2][outerRuleCondition2],
+                                 "{}"].join(" "), 0);
+              object = object.cssRules[0];
+
+              if (outerRuleCondition2 == 2) {
+                // If we used a failing condition, we don't expect the inner
+                // rule to be applied.
+                applied = false;
+              }
+            }
+
+            var outer = object instanceof CSSRule ? object.cssText : "style sheet";
+            var inner = innerRuleInfo[innerRule][0];
+
+            // Insert the inner rule.
+            var exception = null;
+            try {
+              object.insertRule(inner, 0);
+            } catch (e) {
+              exception = e;
+            }
+
+            ok(innerRuleInfo[innerRule][1](applied, object, exception),
+               "<" + [outerRule1, outerRuleCondition1, outerRule2,
+                      outerRuleCondition2, innerRule].join(",") + "> " +
+               "inserting " + inner + " into " + outer.replace(/ *\n */g, ' '));
+          }
+        }
+      }
+    }
+  }
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({ "set": [["layout.css.supports-rule.enabled", true]] }, runTest);
+
+</script>
+</body>
+</html>