1 /*
2  * Copyright (c) 2014, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  * 3. Neither the name of Opera Software ASA nor the names of its
13  *    contributors may be used to endorse or promote products derived
14  *    from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27  * OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "third_party/blink/renderer/core/css/rule_set.h"
31 
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "third_party/blink/renderer/core/css/css_test_helpers.h"
34 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
35 
36 namespace blink {
37 
38 namespace {
39 
CreateDummyStyleRule()40 StyleRule* CreateDummyStyleRule() {
41   css_test_helpers::TestStyleSheet sheet;
42   sheet.AddCSSRules("#id { color: tomato; }");
43   const RuleSet& rule_set = sheet.GetRuleSet();
44   const HeapVector<Member<const RuleData>>* rules = rule_set.IdRules("id");
45   DCHECK_EQ(1u, rules->size());
46   return rules->at(0)->Rule();
47 }
48 
49 }  // namespace
50 
TEST(RuleSetTest,findBestRuleSetAndAdd_CustomPseudoElements)51 TEST(RuleSetTest, findBestRuleSetAndAdd_CustomPseudoElements) {
52   css_test_helpers::TestStyleSheet sheet;
53 
54   sheet.AddCSSRules("summary::-webkit-details-marker { }");
55   RuleSet& rule_set = sheet.GetRuleSet();
56   AtomicString str("-webkit-details-marker");
57   const HeapVector<Member<const RuleData>>* rules =
58       rule_set.ShadowPseudoElementRules(str);
59   ASSERT_EQ(1u, rules->size());
60   ASSERT_EQ(str, rules->at(0)->Selector().Value());
61 }
62 
TEST(RuleSetTest,findBestRuleSetAndAdd_Id)63 TEST(RuleSetTest, findBestRuleSetAndAdd_Id) {
64   css_test_helpers::TestStyleSheet sheet;
65 
66   sheet.AddCSSRules("#id { }");
67   RuleSet& rule_set = sheet.GetRuleSet();
68   AtomicString str("id");
69   const HeapVector<Member<const RuleData>>* rules = rule_set.IdRules(str);
70   ASSERT_EQ(1u, rules->size());
71   ASSERT_EQ(str, rules->at(0)->Selector().Value());
72 }
73 
TEST(RuleSetTest,findBestRuleSetAndAdd_NthChild)74 TEST(RuleSetTest, findBestRuleSetAndAdd_NthChild) {
75   css_test_helpers::TestStyleSheet sheet;
76 
77   sheet.AddCSSRules("div:nth-child(2) { }");
78   RuleSet& rule_set = sheet.GetRuleSet();
79   AtomicString str("div");
80   const HeapVector<Member<const RuleData>>* rules = rule_set.TagRules(str);
81   ASSERT_EQ(1u, rules->size());
82   ASSERT_EQ(str, rules->at(0)->Selector().TagQName().LocalName());
83 }
84 
TEST(RuleSetTest,findBestRuleSetAndAdd_ClassThenId)85 TEST(RuleSetTest, findBestRuleSetAndAdd_ClassThenId) {
86   css_test_helpers::TestStyleSheet sheet;
87 
88   sheet.AddCSSRules(".class#id { }");
89   RuleSet& rule_set = sheet.GetRuleSet();
90   AtomicString str("id");
91   // id is prefered over class even if class preceeds it in the selector.
92   const HeapVector<Member<const RuleData>>* rules = rule_set.IdRules(str);
93   ASSERT_EQ(1u, rules->size());
94   AtomicString class_str("class");
95   ASSERT_EQ(class_str, rules->at(0)->Selector().Value());
96 }
97 
TEST(RuleSetTest,findBestRuleSetAndAdd_IdThenClass)98 TEST(RuleSetTest, findBestRuleSetAndAdd_IdThenClass) {
99   css_test_helpers::TestStyleSheet sheet;
100 
101   sheet.AddCSSRules("#id.class { }");
102   RuleSet& rule_set = sheet.GetRuleSet();
103   AtomicString str("id");
104   const HeapVector<Member<const RuleData>>* rules = rule_set.IdRules(str);
105   ASSERT_EQ(1u, rules->size());
106   ASSERT_EQ(str, rules->at(0)->Selector().Value());
107 }
108 
TEST(RuleSetTest,findBestRuleSetAndAdd_AttrThenId)109 TEST(RuleSetTest, findBestRuleSetAndAdd_AttrThenId) {
110   css_test_helpers::TestStyleSheet sheet;
111 
112   sheet.AddCSSRules("[attr]#id { }");
113   RuleSet& rule_set = sheet.GetRuleSet();
114   AtomicString str("id");
115   const HeapVector<Member<const RuleData>>* rules = rule_set.IdRules(str);
116   ASSERT_EQ(1u, rules->size());
117   AtomicString attr_str("attr");
118   ASSERT_EQ(attr_str, rules->at(0)->Selector().Attribute().LocalName());
119 }
120 
TEST(RuleSetTest,findBestRuleSetAndAdd_TagThenAttrThenId)121 TEST(RuleSetTest, findBestRuleSetAndAdd_TagThenAttrThenId) {
122   css_test_helpers::TestStyleSheet sheet;
123 
124   sheet.AddCSSRules("div[attr]#id { }");
125   RuleSet& rule_set = sheet.GetRuleSet();
126   AtomicString str("id");
127   const HeapVector<Member<const RuleData>>* rules = rule_set.IdRules(str);
128   ASSERT_EQ(1u, rules->size());
129   AtomicString tag_str("div");
130   ASSERT_EQ(tag_str, rules->at(0)->Selector().TagQName().LocalName());
131 }
132 
TEST(RuleSetTest,findBestRuleSetAndAdd_DivWithContent)133 TEST(RuleSetTest, findBestRuleSetAndAdd_DivWithContent) {
134   css_test_helpers::TestStyleSheet sheet;
135 
136   sheet.AddCSSRules("div::content { }");
137   RuleSet& rule_set = sheet.GetRuleSet();
138   AtomicString str("div");
139   const HeapVector<Member<const RuleData>>* rules = rule_set.TagRules(str);
140   ASSERT_EQ(1u, rules->size());
141   AtomicString value_str("content");
142   ASSERT_EQ(value_str, rules->at(0)->Selector().TagHistory()->Value());
143 }
144 
TEST(RuleSetTest,findBestRuleSetAndAdd_Host)145 TEST(RuleSetTest, findBestRuleSetAndAdd_Host) {
146   css_test_helpers::TestStyleSheet sheet;
147 
148   sheet.AddCSSRules(":host { }");
149   RuleSet& rule_set = sheet.GetRuleSet();
150   const HeapVector<Member<const RuleData>>* rules = rule_set.ShadowHostRules();
151   ASSERT_EQ(1u, rules->size());
152 }
153 
TEST(RuleSetTest,findBestRuleSetAndAdd_HostWithId)154 TEST(RuleSetTest, findBestRuleSetAndAdd_HostWithId) {
155   css_test_helpers::TestStyleSheet sheet;
156 
157   sheet.AddCSSRules(":host(#x) { }");
158   RuleSet& rule_set = sheet.GetRuleSet();
159   const HeapVector<Member<const RuleData>>* rules = rule_set.ShadowHostRules();
160   ASSERT_EQ(1u, rules->size());
161 }
162 
TEST(RuleSetTest,findBestRuleSetAndAdd_HostContext)163 TEST(RuleSetTest, findBestRuleSetAndAdd_HostContext) {
164   css_test_helpers::TestStyleSheet sheet;
165 
166   sheet.AddCSSRules(":host-context(*) { }");
167   RuleSet& rule_set = sheet.GetRuleSet();
168   const HeapVector<Member<const RuleData>>* rules = rule_set.ShadowHostRules();
169   ASSERT_EQ(1u, rules->size());
170 }
171 
TEST(RuleSetTest,findBestRuleSetAndAdd_HostContextWithId)172 TEST(RuleSetTest, findBestRuleSetAndAdd_HostContextWithId) {
173   css_test_helpers::TestStyleSheet sheet;
174 
175   sheet.AddCSSRules(":host-context(#x) { }");
176   RuleSet& rule_set = sheet.GetRuleSet();
177   const HeapVector<Member<const RuleData>>* rules = rule_set.ShadowHostRules();
178   ASSERT_EQ(1u, rules->size());
179 }
180 
TEST(RuleSetTest,findBestRuleSetAndAdd_HostAndHostContextNotInRightmost)181 TEST(RuleSetTest, findBestRuleSetAndAdd_HostAndHostContextNotInRightmost) {
182   css_test_helpers::TestStyleSheet sheet;
183 
184   sheet.AddCSSRules(":host-context(#x) .y, :host(.a) > #b  { }");
185   RuleSet& rule_set = sheet.GetRuleSet();
186   const HeapVector<Member<const RuleData>>* shadow_rules =
187       rule_set.ShadowHostRules();
188   const HeapVector<Member<const RuleData>>* id_rules = rule_set.IdRules("b");
189   const HeapVector<Member<const RuleData>>* class_rules =
190       rule_set.ClassRules("y");
191   ASSERT_EQ(0u, shadow_rules->size());
192   ASSERT_EQ(1u, id_rules->size());
193   ASSERT_EQ(1u, class_rules->size());
194 }
195 
TEST(RuleSetTest,findBestRuleSetAndAdd_HostAndClass)196 TEST(RuleSetTest, findBestRuleSetAndAdd_HostAndClass) {
197   css_test_helpers::TestStyleSheet sheet;
198 
199   sheet.AddCSSRules(".foo:host { }");
200   RuleSet& rule_set = sheet.GetRuleSet();
201   const HeapVector<Member<const RuleData>>* rules = rule_set.ShadowHostRules();
202   ASSERT_EQ(0u, rules->size());
203 }
204 
TEST(RuleSetTest,findBestRuleSetAndAdd_HostContextAndClass)205 TEST(RuleSetTest, findBestRuleSetAndAdd_HostContextAndClass) {
206   css_test_helpers::TestStyleSheet sheet;
207 
208   sheet.AddCSSRules(".foo:host-context(*) { }");
209   RuleSet& rule_set = sheet.GetRuleSet();
210   const HeapVector<Member<const RuleData>>* rules = rule_set.ShadowHostRules();
211   ASSERT_EQ(0u, rules->size());
212 }
213 
TEST(RuleSetTest,findBestRuleSetAndAdd_Focus)214 TEST(RuleSetTest, findBestRuleSetAndAdd_Focus) {
215   css_test_helpers::TestStyleSheet sheet;
216 
217   sheet.AddCSSRules(":focus { }");
218   sheet.AddCSSRules("[attr]:focus { }");
219   RuleSet& rule_set = sheet.GetRuleSet();
220   const HeapVector<Member<const RuleData>>* rules =
221       rule_set.FocusPseudoClassRules();
222   ASSERT_EQ(2u, rules->size());
223 }
224 
TEST(RuleSetTest,findBestRuleSetAndAdd_LinkVisited)225 TEST(RuleSetTest, findBestRuleSetAndAdd_LinkVisited) {
226   css_test_helpers::TestStyleSheet sheet;
227 
228   sheet.AddCSSRules(":link { }");
229   sheet.AddCSSRules("[attr]:link { }");
230   sheet.AddCSSRules(":visited { }");
231   sheet.AddCSSRules("[attr]:visited { }");
232   sheet.AddCSSRules(":-webkit-any-link { }");
233   sheet.AddCSSRules("[attr]:-webkit-any-link { }");
234   RuleSet& rule_set = sheet.GetRuleSet();
235   const HeapVector<Member<const RuleData>>* rules =
236       rule_set.LinkPseudoClassRules();
237   ASSERT_EQ(6u, rules->size());
238 }
239 
TEST(RuleSetTest,findBestRuleSetAndAdd_Cue)240 TEST(RuleSetTest, findBestRuleSetAndAdd_Cue) {
241   css_test_helpers::TestStyleSheet sheet;
242 
243   sheet.AddCSSRules("::cue(b) { }");
244   sheet.AddCSSRules("video::cue(u) { }");
245   RuleSet& rule_set = sheet.GetRuleSet();
246   const HeapVector<Member<const RuleData>>* rules = rule_set.CuePseudoRules();
247   ASSERT_EQ(2u, rules->size());
248 }
249 
TEST(RuleSetTest,findBestRuleSetAndAdd_PlaceholderPseudo)250 TEST(RuleSetTest, findBestRuleSetAndAdd_PlaceholderPseudo) {
251   css_test_helpers::TestStyleSheet sheet;
252 
253   sheet.AddCSSRules("::placeholder { }");
254   sheet.AddCSSRules("input::placeholder { }");
255   RuleSet& rule_set = sheet.GetRuleSet();
256   auto* rules = rule_set.ShadowPseudoElementRules("-webkit-input-placeholder");
257   ASSERT_EQ(2u, rules->size());
258 }
259 
TEST(RuleSetTest,findBestRuleSetAndAdd_PseudoIs)260 TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoIs) {
261   css_test_helpers::TestStyleSheet sheet;
262 
263   sheet.AddCSSRules(".a :is(.b+.c, .d>:is(.e, .f)) { }");
264   RuleSet& rule_set = sheet.GetRuleSet();
265   {
266     AtomicString str("c");
267     const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
268     ASSERT_EQ(1u, rules->size());
269     ASSERT_EQ(str, rules->at(0)->Selector().Value());
270   }
271   {
272     AtomicString str("e");
273     const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
274     ASSERT_EQ(1u, rules->size());
275     ASSERT_EQ(str, rules->at(0)->Selector().Value());
276   }
277   {
278     AtomicString str("f");
279     const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
280     ASSERT_EQ(1u, rules->size());
281     ASSERT_EQ(str, rules->at(0)->Selector().Value());
282   }
283 }
284 
TEST(RuleSetTest,findBestRuleSetAndAdd_PseudoWhere)285 TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoWhere) {
286   css_test_helpers::TestStyleSheet sheet;
287 
288   sheet.AddCSSRules(".a :where(.b+.c, .d>:where(.e, .f)) { }");
289   RuleSet& rule_set = sheet.GetRuleSet();
290   {
291     AtomicString str("c");
292     const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
293     ASSERT_EQ(1u, rules->size());
294     ASSERT_EQ(str, rules->at(0)->Selector().Value());
295   }
296   {
297     AtomicString str("e");
298     const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
299     ASSERT_EQ(1u, rules->size());
300     ASSERT_EQ(str, rules->at(0)->Selector().Value());
301   }
302   {
303     AtomicString str("f");
304     const HeapVector<Member<const RuleData>>* rules = rule_set.ClassRules(str);
305     ASSERT_EQ(1u, rules->size());
306     ASSERT_EQ(str, rules->at(0)->Selector().Value());
307   }
308 }
309 
TEST(RuleSetTest,findBestRuleSetAndAdd_PartPseudoElements)310 TEST(RuleSetTest, findBestRuleSetAndAdd_PartPseudoElements) {
311   css_test_helpers::TestStyleSheet sheet;
312 
313   sheet.AddCSSRules("::part(dummy):focus, #id::part(dummy) { }");
314   RuleSet& rule_set = sheet.GetRuleSet();
315   const HeapVector<Member<const RuleData>>* rules = rule_set.PartPseudoRules();
316   ASSERT_EQ(2u, rules->size());
317 }
318 
TEST(RuleSetTest,findBestRuleSetAndAdd_PseudoIsTooLarge)319 TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoIsTooLarge) {
320   // RuleData cannot support selectors at index 8192 or beyond so the expansion
321   // is limited to this size
322   css_test_helpers::TestStyleSheet sheet;
323 
324   sheet.AddCSSRules(
325       ":is(.a#a, .b#b, .c#c, .d#d) + "
326       ":is(.e#e, .f#f, .g#g, .h#h) + "
327       ":is(.i#i, .j#j, .k#k, .l#l) + "
328       ":is(.m#m, .n#n, .o#o, .p#p) + "
329       ":is(.q#q, .r#r, .s#s, .t#t) + "
330       ":is(.u#u, .v#v, .w#w, .x#x) { }",
331       true);
332 
333   RuleSet& rule_set = sheet.GetRuleSet();
334   ASSERT_EQ(0u, rule_set.RuleCount());
335 }
336 
TEST(RuleSetTest,findBestRuleSetAndAdd_PseudoWhereTooLarge)337 TEST(RuleSetTest, findBestRuleSetAndAdd_PseudoWhereTooLarge) {
338   // RuleData cannot support selectors at index 8192 or beyond so the expansion
339   // is limited to this size
340   css_test_helpers::TestStyleSheet sheet;
341 
342   sheet.AddCSSRules(
343       ":where(.a#a, .b#b, .c#c, .d#d) + :where(.e#e, .f#f, .g#g, .h#h) + "
344       ":where(.i#i, .j#j, .k#k, .l#l) + :where(.m#m, .n#n, .o#o, .p#p) + "
345       ":where(.q#q, .r#r, .s#s, .t#t) + :where(.u#u, .v#v, .w#w, .x#x) { }",
346       true);
347 
348   RuleSet& rule_set = sheet.GetRuleSet();
349   ASSERT_EQ(0u, rule_set.RuleCount());
350 }
351 
TEST(RuleSetTest,SelectorIndexLimit)352 TEST(RuleSetTest, SelectorIndexLimit) {
353   // It's not feasible to run this test for a large number of bits. If the
354   // number of bits have increased to a large number, consider removing this
355   // test and making do with RuleSetTest.RuleDataSelectorIndexLimit.
356   static_assert(
357       RuleData::kSelectorIndexBits == 13,
358       "Please manually consider whether this test should be removed.");
359 
360   StringBuilder builder;
361 
362   // We use 13 bits to storing the selector start index in RuleData. This is a
363   // test to check that we don't regress. We WONTFIX issues asking for more
364   // since 2^13 simple selectors in a style rule is already excessive.
365   for (unsigned i = 0; i < 8191; i++)
366     builder.Append("div,");
367 
368   builder.Append("b,span {}");
369 
370   css_test_helpers::TestStyleSheet sheet;
371   sheet.AddCSSRules(builder.ToString());
372   const RuleSet& rule_set = sheet.GetRuleSet();
373   const HeapVector<Member<const RuleData>>* rules = rule_set.TagRules("b");
374   ASSERT_EQ(1u, rules->size());
375   EXPECT_EQ("b", rules->at(0)->Selector().TagQName().LocalName());
376   EXPECT_FALSE(rule_set.TagRules("span"));
377 }
378 
TEST(RuleSetTest,RuleDataSelectorIndexLimit)379 TEST(RuleSetTest, RuleDataSelectorIndexLimit) {
380   StyleRule* rule = CreateDummyStyleRule();
381   AddRuleFlags flags = kRuleHasNoSpecialState;
382   const unsigned position = 0;
383   EXPECT_TRUE(RuleData::MaybeCreate(rule, 0, position, flags));
384   EXPECT_FALSE(RuleData::MaybeCreate(rule, (1 << RuleData::kSelectorIndexBits),
385                                      position, flags));
386   EXPECT_FALSE(RuleData::MaybeCreate(
387       rule, (1 << RuleData::kSelectorIndexBits) + 1, position, flags));
388 }
389 
TEST(RuleSetTest,RuleDataPositionLimit)390 TEST(RuleSetTest, RuleDataPositionLimit) {
391   StyleRule* rule = CreateDummyStyleRule();
392   AddRuleFlags flags = kRuleHasNoSpecialState;
393   const unsigned selector_index = 0;
394   EXPECT_TRUE(RuleData::MaybeCreate(rule, selector_index, 0, flags));
395   EXPECT_FALSE(RuleData::MaybeCreate(rule, selector_index,
396                                      (1 << RuleData::kPositionBits), flags));
397   EXPECT_FALSE(RuleData::MaybeCreate(
398       rule, selector_index, (1 << RuleData::kPositionBits) + 1, flags));
399 }
400 
TEST(RuleSetTest,RuleCountNotIncreasedByInvalidRuleData)401 TEST(RuleSetTest, RuleCountNotIncreasedByInvalidRuleData) {
402   auto* rule_set = MakeGarbageCollected<RuleSet>();
403   EXPECT_EQ(0u, rule_set->RuleCount());
404 
405   AddRuleFlags flags = kRuleHasNoSpecialState;
406   StyleRule* rule = CreateDummyStyleRule();
407 
408   // Add with valid selector_index=0.
409   rule_set->AddRule(rule, 0, flags);
410   EXPECT_EQ(1u, rule_set->RuleCount());
411 
412   // Adding with invalid selector_index should not lead to a change in count.
413   rule_set->AddRule(rule, 1 << RuleData::kSelectorIndexBits, flags);
414   EXPECT_EQ(1u, rule_set->RuleCount());
415 }
416 
417 }  // namespace blink
418