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