1 // Copyright 2017 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "core/fxcrt/css/cfx_cssstylesheet.h"
8
9 #include <memory>
10 #include <vector>
11
12 #include "core/fxcrt/check.h"
13 #include "core/fxcrt/css/cfx_cssdeclaration.h"
14 #include "core/fxcrt/css/cfx_cssenumvalue.h"
15 #include "core/fxcrt/css/cfx_cssnumbervalue.h"
16 #include "core/fxcrt/css/cfx_cssstylerule.h"
17 #include "core/fxcrt/css/cfx_cssvaluelist.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19
20 class CFXCSSStyleSheetTest : public testing::Test {
21 public:
SetUp()22 void SetUp() override {
23 sheet_ = std::make_unique<CFX_CSSStyleSheet>();
24 decl_ = nullptr;
25 }
26
TearDown()27 void TearDown() override { decl_ = nullptr; }
28
VerifyLoadFails(WideStringView buf)29 void VerifyLoadFails(WideStringView buf) {
30 DCHECK(sheet_);
31 EXPECT_FALSE(sheet_->LoadBuffer(buf));
32 }
33
LoadAndVerifyRuleCount(WideStringView buf,size_t rule_count)34 void LoadAndVerifyRuleCount(WideStringView buf, size_t rule_count) {
35 DCHECK(sheet_);
36 EXPECT_TRUE(sheet_->LoadBuffer(buf));
37 EXPECT_EQ(sheet_->CountRules(), rule_count);
38 }
39
LoadAndVerifyDecl(WideStringView buf,const std::vector<WideString> & selectors,size_t decl_count)40 void LoadAndVerifyDecl(WideStringView buf,
41 const std::vector<WideString>& selectors,
42 size_t decl_count) {
43 LoadAndVerifyRuleCount(buf, 1);
44 CFX_CSSStyleRule* style = sheet_->GetRule(0);
45 ASSERT_TRUE(style);
46 EXPECT_EQ(selectors.size(), style->CountSelectorLists());
47
48 for (size_t i = 0; i < selectors.size(); i++) {
49 uint32_t hash = FX_HashCode_GetLoweredW(selectors[i].AsStringView());
50 EXPECT_EQ(hash, style->GetSelectorList(i)->name_hash());
51 }
52
53 decl_ = style->GetDeclaration();
54 ASSERT_TRUE(decl_);
55 EXPECT_EQ(decl_->PropertyCountForTesting(), decl_count);
56 }
57
VerifyFloat(CFX_CSSProperty prop,float val,CFX_CSSNumber::Unit unit)58 void VerifyFloat(CFX_CSSProperty prop, float val, CFX_CSSNumber::Unit unit) {
59 DCHECK(decl_);
60
61 bool important;
62 RetainPtr<CFX_CSSValue> v = decl_->GetProperty(prop, &important);
63 EXPECT_EQ(v->GetType(), CFX_CSSValue::PrimitiveType::kNumber);
64 EXPECT_EQ(v.AsRaw<CFX_CSSNumberValue>()->unit(), unit);
65 EXPECT_EQ(v.AsRaw<CFX_CSSNumberValue>()->value(), val);
66 }
67
VerifyEnum(CFX_CSSProperty prop,CFX_CSSPropertyValue val)68 void VerifyEnum(CFX_CSSProperty prop, CFX_CSSPropertyValue val) {
69 DCHECK(decl_);
70
71 bool important;
72 RetainPtr<CFX_CSSValue> v = decl_->GetProperty(prop, &important);
73 EXPECT_EQ(v->GetType(), CFX_CSSValue::PrimitiveType::kEnum);
74 EXPECT_EQ(v.AsRaw<CFX_CSSEnumValue>()->Value(), val);
75 }
76
VerifyList(CFX_CSSProperty prop,std::vector<CFX_CSSPropertyValue> expected_values)77 void VerifyList(CFX_CSSProperty prop,
78 std::vector<CFX_CSSPropertyValue> expected_values) {
79 DCHECK(decl_);
80
81 bool important;
82 RetainPtr<CFX_CSSValueList> list =
83 decl_->GetProperty(prop, &important).As<CFX_CSSValueList>();
84 ASSERT_TRUE(list);
85 const std::vector<RetainPtr<CFX_CSSValue>>& values = list->values();
86 ASSERT_EQ(values.size(), expected_values.size());
87
88 for (size_t i = 0; i < expected_values.size(); ++i) {
89 const auto& val = values[i];
90 EXPECT_EQ(val->GetType(), CFX_CSSValue::PrimitiveType::kEnum);
91 EXPECT_EQ(val.AsRaw<CFX_CSSEnumValue>()->Value(), expected_values[i]);
92 }
93 }
94
HasSelector(CFX_CSSStyleRule * style,WideStringView selector)95 static bool HasSelector(CFX_CSSStyleRule* style, WideStringView selector) {
96 uint32_t hash = FX_HashCode_GetLoweredW(selector);
97 for (size_t i = 0; i < style->CountSelectorLists(); ++i) {
98 if (style->GetSelectorList(i)->name_hash() == hash)
99 return true;
100 }
101 return false;
102 }
103
104 std::unique_ptr<CFX_CSSStyleSheet> sheet_;
105 CFX_CSSDeclaration* decl_;
106 };
107
TEST_F(CFXCSSStyleSheetTest,ParseEmpty)108 TEST_F(CFXCSSStyleSheetTest, ParseEmpty) {
109 LoadAndVerifyRuleCount(L"", 0);
110 }
111
TEST_F(CFXCSSStyleSheetTest,ParseBlankEmpty)112 TEST_F(CFXCSSStyleSheetTest, ParseBlankEmpty) {
113 LoadAndVerifyRuleCount(L" \n\r\t", 0);
114 }
115
TEST_F(CFXCSSStyleSheetTest,ParseStrayClose1)116 TEST_F(CFXCSSStyleSheetTest, ParseStrayClose1) {
117 VerifyLoadFails(L"}");
118 }
119
TEST_F(CFXCSSStyleSheetTest,ParseStrayClose2)120 TEST_F(CFXCSSStyleSheetTest, ParseStrayClose2) {
121 LoadAndVerifyRuleCount(L"foo }", 0);
122 }
123
TEST_F(CFXCSSStyleSheetTest,ParseStrayClose3)124 TEST_F(CFXCSSStyleSheetTest, ParseStrayClose3) {
125 VerifyLoadFails(L"foo {a: b}}");
126 }
127
TEST_F(CFXCSSStyleSheetTest,ParseEmptySelector)128 TEST_F(CFXCSSStyleSheetTest, ParseEmptySelector) {
129 VerifyLoadFails(L"{a: b}");
130 }
131
TEST_F(CFXCSSStyleSheetTest,ParseEmptyBody)132 TEST_F(CFXCSSStyleSheetTest, ParseEmptyBody) {
133 LoadAndVerifyRuleCount(L"foo {}", 0);
134 }
135
TEST_F(CFXCSSStyleSheetTest,ParseMultipleSelectors)136 TEST_F(CFXCSSStyleSheetTest, ParseMultipleSelectors) {
137 const wchar_t* buf =
138 L"a { border: 10px; }\n"
139 L"bcdef { text-decoration: underline; }\n"
140 L"* { padding: 0; }\n";
141 EXPECT_TRUE(sheet_->LoadBuffer(buf));
142 ASSERT_EQ(3u, sheet_->CountRules());
143
144 CFX_CSSStyleRule* style = sheet_->GetRule(0);
145 ASSERT_TRUE(style);
146 EXPECT_EQ(1u, style->CountSelectorLists());
147 EXPECT_TRUE(HasSelector(style, L"a"));
148
149 decl_ = style->GetDeclaration();
150 ASSERT_TRUE(decl_);
151 EXPECT_EQ(4u, decl_->PropertyCountForTesting());
152
153 VerifyFloat(CFX_CSSProperty::BorderLeftWidth, 10.0f,
154 CFX_CSSNumber::Unit::kPixels);
155 VerifyFloat(CFX_CSSProperty::BorderRightWidth, 10.0f,
156 CFX_CSSNumber::Unit::kPixels);
157 VerifyFloat(CFX_CSSProperty::BorderTopWidth, 10.0f,
158 CFX_CSSNumber::Unit::kPixels);
159 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 10.0f,
160 CFX_CSSNumber::Unit::kPixels);
161
162 style = sheet_->GetRule(1);
163 ASSERT_TRUE(style);
164 EXPECT_EQ(1u, style->CountSelectorLists());
165 EXPECT_TRUE(HasSelector(style, L"bcdef"));
166 EXPECT_FALSE(HasSelector(style, L"bcde"));
167
168 decl_ = style->GetDeclaration();
169 ASSERT_TRUE(decl_);
170 EXPECT_EQ(1u, decl_->PropertyCountForTesting());
171 VerifyList(CFX_CSSProperty::TextDecoration,
172 {CFX_CSSPropertyValue::Underline});
173
174 style = sheet_->GetRule(2);
175 ASSERT_TRUE(style);
176 EXPECT_EQ(1u, style->CountSelectorLists());
177 EXPECT_TRUE(HasSelector(style, L"*"));
178
179 decl_ = style->GetDeclaration();
180 ASSERT_TRUE(decl_);
181 EXPECT_EQ(4u, decl_->PropertyCountForTesting());
182 VerifyFloat(CFX_CSSProperty::PaddingLeft, 0.0f, CFX_CSSNumber::Unit::kNumber);
183 VerifyFloat(CFX_CSSProperty::PaddingRight, 0.0f,
184 CFX_CSSNumber::Unit::kNumber);
185 VerifyFloat(CFX_CSSProperty::PaddingTop, 0.0f, CFX_CSSNumber::Unit::kNumber);
186 VerifyFloat(CFX_CSSProperty::PaddingBottom, 0.0f,
187 CFX_CSSNumber::Unit::kNumber);
188 }
189
TEST_F(CFXCSSStyleSheetTest,ParseChildSelectors)190 TEST_F(CFXCSSStyleSheetTest, ParseChildSelectors) {
191 const wchar_t* buf = L"a b c { border: 10px; }";
192 EXPECT_TRUE(sheet_->LoadBuffer(buf));
193 EXPECT_EQ(1u, sheet_->CountRules());
194
195 CFX_CSSStyleRule* style = sheet_->GetRule(0);
196 ASSERT_TRUE(style);
197 EXPECT_EQ(1u, style->CountSelectorLists());
198
199 const auto* sel = style->GetSelectorList(0);
200 ASSERT_TRUE(sel);
201 EXPECT_EQ(FX_HashCode_GetLoweredW(L"c"), sel->name_hash());
202
203 sel = sel->next_selector();
204 ASSERT_TRUE(sel);
205 EXPECT_EQ(FX_HashCode_GetLoweredW(L"b"), sel->name_hash());
206
207 sel = sel->next_selector();
208 ASSERT_TRUE(sel);
209 EXPECT_EQ(FX_HashCode_GetLoweredW(L"a"), sel->name_hash());
210
211 sel = sel->next_selector();
212 EXPECT_FALSE(sel);
213
214 decl_ = style->GetDeclaration();
215 ASSERT_TRUE(decl_);
216 EXPECT_EQ(4u, decl_->PropertyCountForTesting());
217
218 VerifyFloat(CFX_CSSProperty::BorderLeftWidth, 10.0f,
219 CFX_CSSNumber::Unit::kPixels);
220 VerifyFloat(CFX_CSSProperty::BorderRightWidth, 10.0f,
221 CFX_CSSNumber::Unit::kPixels);
222 VerifyFloat(CFX_CSSProperty::BorderTopWidth, 10.0f,
223 CFX_CSSNumber::Unit::kPixels);
224 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 10.0f,
225 CFX_CSSNumber::Unit::kPixels);
226 }
227
TEST_F(CFXCSSStyleSheetTest,ParseUnhandledSelectors)228 TEST_F(CFXCSSStyleSheetTest, ParseUnhandledSelectors) {
229 const wchar_t* buf = L"a > b { padding: 0; }";
230 EXPECT_TRUE(sheet_->LoadBuffer(buf));
231 EXPECT_EQ(0u, sheet_->CountRules());
232
233 buf = L"a[first] { padding: 0; }";
234 EXPECT_TRUE(sheet_->LoadBuffer(buf));
235 EXPECT_EQ(0u, sheet_->CountRules());
236
237 buf = L"a+b { padding: 0; }";
238 EXPECT_TRUE(sheet_->LoadBuffer(buf));
239 EXPECT_EQ(0u, sheet_->CountRules());
240
241 buf = L"a ^ b { padding: 0; }";
242 EXPECT_TRUE(sheet_->LoadBuffer(buf));
243 EXPECT_EQ(0u, sheet_->CountRules());
244 }
245
TEST_F(CFXCSSStyleSheetTest,ParseMultipleSelectorsCombined)246 TEST_F(CFXCSSStyleSheetTest, ParseMultipleSelectorsCombined) {
247 LoadAndVerifyDecl(L"a, b, c { border: 5px; }", {L"a", L"b", L"c"}, 4);
248 }
249
TEST_F(CFXCSSStyleSheetTest,ParseBorder)250 TEST_F(CFXCSSStyleSheetTest, ParseBorder) {
251 LoadAndVerifyDecl(L"a { border: 5px; }", {L"a"}, 4);
252 VerifyFloat(CFX_CSSProperty::BorderLeftWidth, 5.0,
253 CFX_CSSNumber::Unit::kPixels);
254 VerifyFloat(CFX_CSSProperty::BorderRightWidth, 5.0,
255 CFX_CSSNumber::Unit::kPixels);
256 VerifyFloat(CFX_CSSProperty::BorderTopWidth, 5.0,
257 CFX_CSSNumber::Unit::kPixels);
258 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 5.0,
259 CFX_CSSNumber::Unit::kPixels);
260 }
261
TEST_F(CFXCSSStyleSheetTest,ParseBorderFull)262 TEST_F(CFXCSSStyleSheetTest, ParseBorderFull) {
263 LoadAndVerifyDecl(L"a { border: 5px solid red; }", {L"a"}, 4);
264 VerifyFloat(CFX_CSSProperty::BorderLeftWidth, 5.0,
265 CFX_CSSNumber::Unit::kPixels);
266 VerifyFloat(CFX_CSSProperty::BorderRightWidth, 5.0,
267 CFX_CSSNumber::Unit::kPixels);
268 VerifyFloat(CFX_CSSProperty::BorderTopWidth, 5.0,
269 CFX_CSSNumber::Unit::kPixels);
270 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 5.0,
271 CFX_CSSNumber::Unit::kPixels);
272 }
273
TEST_F(CFXCSSStyleSheetTest,ParseBorderLeft)274 TEST_F(CFXCSSStyleSheetTest, ParseBorderLeft) {
275 LoadAndVerifyDecl(L"a { border-left: 2.5pc; }", {L"a"}, 1);
276 VerifyFloat(CFX_CSSProperty::BorderLeftWidth, 2.5,
277 CFX_CSSNumber::Unit::kPicas);
278 }
279
TEST_F(CFXCSSStyleSheetTest,ParseBorderLeftThick)280 TEST_F(CFXCSSStyleSheetTest, ParseBorderLeftThick) {
281 LoadAndVerifyDecl(L"a { border-left: thick; }", {L"a"}, 1);
282 VerifyEnum(CFX_CSSProperty::BorderLeftWidth, CFX_CSSPropertyValue::Thick);
283 }
284
TEST_F(CFXCSSStyleSheetTest,ParseBorderRight)285 TEST_F(CFXCSSStyleSheetTest, ParseBorderRight) {
286 LoadAndVerifyDecl(L"a { border-right: 2.5pc; }", {L"a"}, 1);
287 VerifyFloat(CFX_CSSProperty::BorderRightWidth, 2.5,
288 CFX_CSSNumber::Unit::kPicas);
289 }
290
TEST_F(CFXCSSStyleSheetTest,ParseBorderTop)291 TEST_F(CFXCSSStyleSheetTest, ParseBorderTop) {
292 LoadAndVerifyDecl(L"a { border-top: 2.5pc; }", {L"a"}, 1);
293 VerifyFloat(CFX_CSSProperty::BorderTopWidth, 2.5,
294 CFX_CSSNumber::Unit::kPicas);
295 }
296
TEST_F(CFXCSSStyleSheetTest,ParseBorderBottom)297 TEST_F(CFXCSSStyleSheetTest, ParseBorderBottom) {
298 LoadAndVerifyDecl(L"a { border-bottom: 2.5pc; }", {L"a"}, 1);
299 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 2.5,
300 CFX_CSSNumber::Unit::kPicas);
301 }
302
TEST_F(CFXCSSStyleSheetTest,ParseWithCommentsInSelector)303 TEST_F(CFXCSSStyleSheetTest, ParseWithCommentsInSelector) {
304 LoadAndVerifyDecl(L"/**{*/a/**}*/ { border-bottom: 2.5pc; }", {L"a"}, 1);
305 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 2.5,
306 CFX_CSSNumber::Unit::kPicas);
307 }
308
TEST_F(CFXCSSStyleSheetTest,ParseWithCommentsInProperty)309 TEST_F(CFXCSSStyleSheetTest, ParseWithCommentsInProperty) {
310 LoadAndVerifyDecl(L"a { /*}*/border-bottom: 2.5pc; }", {L"a"}, 1);
311 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 2.5,
312 CFX_CSSNumber::Unit::kPicas);
313 }
314
TEST_F(CFXCSSStyleSheetTest,ParseWithCommentsInValue)315 TEST_F(CFXCSSStyleSheetTest, ParseWithCommentsInValue) {
316 LoadAndVerifyDecl(L"a { border-bottom: /*;*/2.5pc;/* color:red;*/ }", {L"a"},
317 1);
318 VerifyFloat(CFX_CSSProperty::BorderBottomWidth, 2.5,
319 CFX_CSSNumber::Unit::kPicas);
320 }
321
TEST_F(CFXCSSStyleSheetTest,ParseWithUnterminatedCommentInSelector)322 TEST_F(CFXCSSStyleSheetTest, ParseWithUnterminatedCommentInSelector) {
323 LoadAndVerifyRuleCount(L"a/* { border-bottom: 2.5pc; }", 0);
324 }
325
TEST_F(CFXCSSStyleSheetTest,ParseWithUnterminatedCommentInProperty)326 TEST_F(CFXCSSStyleSheetTest, ParseWithUnterminatedCommentInProperty) {
327 LoadAndVerifyRuleCount(L"a { /*border-bottom: 2.5pc; }", 1);
328 }
329
TEST_F(CFXCSSStyleSheetTest,ParseWithUnterminatedCommentInValue)330 TEST_F(CFXCSSStyleSheetTest, ParseWithUnterminatedCommentInValue) {
331 LoadAndVerifyRuleCount(L"a { border-bottom: /*2.5pc; }", 1);
332 }
333