cssom.html (8076B)
1 <!doctype html> 2 <title>Simple CSSOM manipulation of subrules</title> 3 <link rel="author" title="Steinar H. Gunderson" href="mailto:sesse@chromium.org"> 4 <link rel="help" href="https://drafts.csswg.org/css-nesting-1/"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 8 <style id="ss"></style> 9 10 <script> 11 test(() => { 12 assert_equals(CSSStyleRule.__proto__, CSSGroupingRule); 13 }, "CSSStyleRule is a CSSGroupingRule"); 14 15 test(() => { 16 let [ss] = document.styleSheets; 17 assert_equals(ss.cssRules.length, 0); 18 ss.insertRule('.a { color: red; }'); 19 assert_equals(ss.cssRules.length, 1); 20 assert_equals(ss.cssRules[0].cssText, '.a { color: red; }'); 21 22 // Test inserting sub-cssRules, at various positions. 23 ss.cssRules[0].insertRule('& .b { color: green; }'); 24 ss.cssRules[0].insertRule('& .c { color: blue; }', 1); 25 ss.cssRules[0].insertRule('& .d { color: hotpink; }', 1); 26 assert_equals(ss.cssRules[0].cssText, 27 `.a { 28 color: red; 29 & .b { color: green; } 30 & .d { color: hotpink; } 31 & .c { color: blue; } 32 }`, 'inserting should work'); 33 34 // Test deleting a rule. 35 ss.cssRules[0].deleteRule(1); 36 assert_equals(ss.cssRules[0].cssText, 37 `.a { 38 color: red; 39 & .b { color: green; } 40 & .c { color: blue; } 41 }`, 'deleting should work'); 42 }); 43 44 // Test that out-of-bounds throws exceptions and does not affect the stylesheet. 45 const sampleSheetText = 46 `.a { 47 color: red; 48 & .b { color: green; } 49 & .c { color: blue; } 50 }`; 51 52 test(() => { 53 document.getElementById('ss').innerHTML = sampleSheetText; 54 let [ss] = document.styleSheets; 55 assert_throws_dom('IndexSizeError', () => { ss.cssRules[0].insertRule('& .broken {}', 3); }); 56 assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-insert'); 57 }); 58 59 test(() => { 60 document.getElementById('ss').innerHTML = sampleSheetText; 61 let [ss] = document.styleSheets; 62 assert_throws_dom('IndexSizeError', () => { ss.cssRules[0].insertRule('& .broken {}', -1); }); 63 assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-insert'); 64 }); 65 66 test(() => { 67 document.getElementById('ss').innerHTML = sampleSheetText; 68 let [ss] = document.styleSheets; 69 assert_throws_dom('IndexSizeError', () => { ss.cssRules[0].deleteRule(5); }); 70 assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-delete'); 71 }); 72 73 test(() => { 74 document.getElementById('ss').innerHTML = sampleSheetText; 75 let [ss] = document.styleSheets; 76 assert_equals(ss.cssRules[0].cssRules[2], undefined, 'subscript out-of-bounds returns undefined'); 77 assert_equals(ss.cssRules[0].cssRules.item(2), null, 'item() out-of-bounds returns null'); 78 assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after no-access'); 79 }); 80 81 // Test that inserting an invalid rule throws an exception. 82 test(() => { 83 document.getElementById('ss').innerHTML = sampleSheetText; 84 let [ss] = document.styleSheets; 85 let exception; 86 assert_throws_dom('SyntaxError', () => { ss.cssRules[0].insertRule('% {}'); }); 87 assert_equals(ss.cssRules[0].cssText, sampleSheetText, 'unchanged after invalid rule'); 88 }); 89 90 // Test that we can get out single rule through .cssRules. 91 test(() => { 92 document.getElementById('ss').innerHTML = sampleSheetText; 93 let [ss] = document.styleSheets; 94 assert_equals(ss.cssRules[0].cssRules[1].cssText, '& .c { color: blue; }'); 95 }); 96 97 // Test that we can insert a @supports rule, that it serializes in the right place 98 // and has the right parent. Note that the indentation is broken per-spec. 99 test(() => { 100 document.getElementById('ss').innerHTML = sampleSheetText; 101 let [ss] = document.styleSheets; 102 ss.cssRules[0].insertRule('@supports selector(&) { & div { font-size: 10px; }}', 1); 103 assert_equals(ss.cssRules[0].cssText, 104 `.a { 105 color: red; 106 & .b { color: green; } 107 @supports selector(&) { 108 & div { font-size: 10px; } 109 } 110 & .c { color: blue; } 111 }`, '@supports is added'); 112 113 assert_equals(ss.cssRules[0].cssRules[1].parentRule, ss.cssRules[0]); 114 ss.cssRules[0].deleteRule(1); 115 assert_equals(ss.cssRules[0].cssText, sampleSheetText); 116 }); 117 118 // Nested rules are not part of declaration lists, and thus should not 119 // be possible to insert with .style. 120 test(() => { 121 document.getElementById('ss').innerHTML = sampleSheetText; 122 let [ss] = document.styleSheets; 123 ss.cssRules[0].style = 'color: olivedrab; &.d { color: peru; }'; 124 assert_equals(ss.cssRules[0].cssText, 125 `.a { 126 color: olivedrab; 127 & .b { color: green; } 128 & .c { color: blue; } 129 }`, 'color is changed, new rule is ignored'); 130 }); 131 132 test(() => { 133 document.getElementById('ss').innerHTML = sampleSheetText; 134 let [ss] = document.styleSheets; 135 ss.cssRules[0].cssRules[0].selectorText = 'div.b .c &'; // Allowed 136 ss.cssRules[0].cssRules[1].selectorText = '.c div.b &, div &'; // Allowed. 137 ss.cssRules[0].insertRule('div & {}'); // Allowed. 138 assert_equals(ss.cssRules[0].cssText, 139 `.a { 140 color: red; 141 div & { } 142 div.b .c & { color: green; } 143 .c div.b &, div & { color: blue; } 144 }`, 'selectorText and insertRule'); 145 }); 146 147 // Rules that are dropped in forgiving parsing but that contain &, 148 // must still be serialized out as they were. 149 test(() => { 150 const text = '.a { :is(!& .foo, .b) { color: green; } }'; 151 document.getElementById('ss').innerHTML = text; 152 let [ss] = document.styleSheets; 153 assert_equals(ss.cssRules[0].cssText, 154 `.a { 155 :is(!& .foo, .b) { color: green; } 156 }`, 'invalid rule containing ampersand is kept in serialization'); 157 }); 158 159 test((t) => { 160 let main = document.createElement('main'); 161 main.innerHTML = ` 162 <style> 163 .a { 164 & { z-index:1; } 165 & #inner1 { z-index:1; } 166 .stuff, :is(&) #inner2 { z-index:1; } 167 } 168 </style> 169 <div id="outer" class="b"> 170 <div id="inner1"></div> 171 <div id="inner2"></div> 172 </div> 173 `; 174 document.documentElement.append(main); 175 t.add_cleanup(() => main.remove()); 176 177 assert_equals(getComputedStyle(outer).zIndex, 'auto'); 178 assert_equals(getComputedStyle(inner1).zIndex, 'auto'); 179 assert_equals(getComputedStyle(inner2).zIndex, 'auto'); 180 181 // .a => .b 182 main.firstElementChild.sheet.cssRules[0].selectorText = '.b'; 183 184 assert_equals(getComputedStyle(outer).zIndex, '1'); 185 assert_equals(getComputedStyle(inner1).zIndex, '1'); 186 assert_equals(getComputedStyle(inner2).zIndex, '1'); 187 }, 'Mutating the selectorText of outer rule invalidates inner rules'); 188 189 // CSSNestedDeclarations 190 test((t) => { 191 const main = document.createElement('main'); 192 main.innerHTML = ` 193 <style id="main_ss"> 194 div { 195 z-index: 1; 196 &.test { foo:bar; } 197 } 198 </style> 199 <div id="outer" class="test"> 200 </div> 201 `; 202 document.documentElement.append(main); 203 t.add_cleanup(() => main.remove()); 204 assert_equals(getComputedStyle(outer).zIndex, '1'); 205 const main_ss = document.getElementById("main_ss").sheet; 206 const rule = main_ss.cssRules[0]; 207 assert_equals(rule.cssRules.length, 1); 208 rule.insertRule('z-index: 3;'); 209 assert_equals(rule.cssRules.length, 2); 210 assert_equals(getComputedStyle(outer).zIndex, '3'); 211 212 // Throw only when no valid declaration https://github.com/w3c/csswg-drafts/issues/10520 213 assert_throws_dom('SyntaxError', () => { rule.insertRule('nothing-to-insert-because-invalid-property-should-throw: 2;'); }); 214 assert_equals(rule.cssRules.length, 2); 215 216 // Test the insertion of nested declarations inside grouping rule 217 rule.insertRule('@media screen { a { color: blue; }}',2); 218 assert_equals(rule.cssRules.length, 3); 219 const mediaRule = rule.cssRules[2]; 220 mediaRule.insertRule('z-index: 3;'); 221 assert_equals(mediaRule.cssRules.length, 2); 222 assert_throws_dom('SyntaxError', () => { mediaRule.insertRule('nothing-to-insert-because-invalid-property-should-throw: 2;'); }); 223 }, 'Manipulation of nested declarations through CSSOM'); 224 225 </script>