1 | /* |
2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) |
4 | * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com) |
5 | * Copyright (C) 2005-2014 Apple Inc. All rights reserved. |
6 | * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org> |
7 | * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org> |
8 | * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
9 | * Copyright (c) 2011, Code Aurora Forum. All rights reserved. |
10 | * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
11 | * Copyright (C) 2012 Google Inc. All rights reserved. |
12 | * |
13 | * This library is free software; you can redistribute it and/or |
14 | * modify it under the terms of the GNU Library General Public |
15 | * License as published by the Free Software Foundation; either |
16 | * version 2 of the License, or (at your option) any later version. |
17 | * |
18 | * This library is distributed in the hope that it will be useful, |
19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
21 | * Library General Public License for more details. |
22 | * |
23 | * You should have received a copy of the GNU Library General Public License |
24 | * along with this library; see the file COPYING.LIB. If not, write to |
25 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
26 | * Boston, MA 02110-1301, USA. |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "RuleSet.h" |
31 | |
32 | #include "CSSFontSelector.h" |
33 | #include "CSSKeyframesRule.h" |
34 | #include "CSSSelector.h" |
35 | #include "CSSSelectorList.h" |
36 | #include "HTMLNames.h" |
37 | #include "MediaQueryEvaluator.h" |
38 | #include "SecurityOrigin.h" |
39 | #include "SelectorChecker.h" |
40 | #include "SelectorFilter.h" |
41 | #include "StyleResolver.h" |
42 | #include "StyleRule.h" |
43 | #include "StyleRuleImport.h" |
44 | #include "StyleSheetContents.h" |
45 | #include "ViewportStyleResolver.h" |
46 | |
47 | #if ENABLE(VIDEO_TRACK) |
48 | #include "TextTrackCue.h" |
49 | #endif |
50 | |
51 | namespace WebCore { |
52 | |
53 | using namespace HTMLNames; |
54 | |
55 | // ----------------------------------------------------------------- |
56 | |
57 | static inline MatchBasedOnRuleHash computeMatchBasedOnRuleHash(const CSSSelector& selector) |
58 | { |
59 | if (selector.tagHistory()) |
60 | return MatchBasedOnRuleHash::None; |
61 | |
62 | if (selector.match() == CSSSelector::Tag) { |
63 | const QualifiedName& tagQualifiedName = selector.tagQName(); |
64 | const AtomicString& selectorNamespace = tagQualifiedName.namespaceURI(); |
65 | if (selectorNamespace == starAtom() || selectorNamespace == xhtmlNamespaceURI) { |
66 | if (tagQualifiedName == anyQName()) |
67 | return MatchBasedOnRuleHash::Universal; |
68 | return MatchBasedOnRuleHash::ClassC; |
69 | } |
70 | return MatchBasedOnRuleHash::None; |
71 | } |
72 | if (SelectorChecker::isCommonPseudoClassSelector(&selector)) |
73 | return MatchBasedOnRuleHash::ClassB; |
74 | if (selector.match() == CSSSelector::Id) |
75 | return MatchBasedOnRuleHash::ClassA; |
76 | if (selector.match() == CSSSelector::Class) |
77 | return MatchBasedOnRuleHash::ClassB; |
78 | return MatchBasedOnRuleHash::None; |
79 | } |
80 | |
81 | static bool selectorCanMatchPseudoElement(const CSSSelector& rootSelector) |
82 | { |
83 | const CSSSelector* selector = &rootSelector; |
84 | do { |
85 | if (selector->matchesPseudoElement()) |
86 | return true; |
87 | |
88 | if (const CSSSelectorList* selectorList = selector->selectorList()) { |
89 | for (const CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) { |
90 | if (selectorCanMatchPseudoElement(*subSelector)) |
91 | return true; |
92 | } |
93 | } |
94 | |
95 | selector = selector->tagHistory(); |
96 | } while (selector); |
97 | return false; |
98 | } |
99 | |
100 | static inline bool isCommonAttributeSelectorAttribute(const QualifiedName& attribute) |
101 | { |
102 | // These are explicitly tested for equality in canShareStyleWithElement. |
103 | return attribute == typeAttr || attribute == readonlyAttr; |
104 | } |
105 | |
106 | static bool containsUncommonAttributeSelector(const CSSSelector& rootSelector, bool matchesRightmostElement) |
107 | { |
108 | const CSSSelector* selector = &rootSelector; |
109 | do { |
110 | if (selector->isAttributeSelector()) { |
111 | // FIXME: considering non-rightmost simple selectors is necessary because of the style sharing of cousins. |
112 | // It is a primitive solution which disable a lot of style sharing on pages that rely on attributes for styling. |
113 | // We should investigate better ways of doing this. |
114 | if (!isCommonAttributeSelectorAttribute(selector->attribute()) || !matchesRightmostElement) |
115 | return true; |
116 | } |
117 | |
118 | if (const CSSSelectorList* selectorList = selector->selectorList()) { |
119 | for (const CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = CSSSelectorList::next(subSelector)) { |
120 | if (containsUncommonAttributeSelector(*subSelector, matchesRightmostElement)) |
121 | return true; |
122 | } |
123 | } |
124 | |
125 | if (selector->relation() != CSSSelector::Subselector) |
126 | matchesRightmostElement = false; |
127 | |
128 | selector = selector->tagHistory(); |
129 | } while (selector); |
130 | return false; |
131 | } |
132 | |
133 | static inline bool containsUncommonAttributeSelector(const CSSSelector& rootSelector) |
134 | { |
135 | return containsUncommonAttributeSelector(rootSelector, true); |
136 | } |
137 | |
138 | static inline PropertyWhitelistType determinePropertyWhitelistType(const CSSSelector* selector) |
139 | { |
140 | for (const CSSSelector* component = selector; component; component = component->tagHistory()) { |
141 | #if ENABLE(VIDEO_TRACK) |
142 | if (component->match() == CSSSelector::PseudoElement && (component->pseudoElementType() == CSSSelector::PseudoElementCue || component->value() == TextTrackCue::cueShadowPseudoId())) |
143 | return PropertyWhitelistCue; |
144 | #endif |
145 | if (component->match() == CSSSelector::PseudoElement && component->pseudoElementType() == CSSSelector::PseudoElementMarker) |
146 | return PropertyWhitelistMarker; |
147 | } |
148 | return PropertyWhitelistNone; |
149 | } |
150 | |
151 | RuleData::RuleData(StyleRule* rule, unsigned selectorIndex, unsigned selectorListIndex, unsigned position) |
152 | : m_rule(rule) |
153 | , m_selectorIndex(selectorIndex) |
154 | , m_selectorListIndex(selectorListIndex) |
155 | , m_position(position) |
156 | , m_matchBasedOnRuleHash(static_cast<unsigned>(computeMatchBasedOnRuleHash(*selector()))) |
157 | , m_canMatchPseudoElement(selectorCanMatchPseudoElement(*selector())) |
158 | , m_containsUncommonAttributeSelector(WebCore::containsUncommonAttributeSelector(*selector())) |
159 | , m_linkMatchType(SelectorChecker::determineLinkMatchType(selector())) |
160 | , m_propertyWhitelistType(determinePropertyWhitelistType(selector())) |
161 | , m_descendantSelectorIdentifierHashes(SelectorFilter::collectHashes(*selector())) |
162 | { |
163 | ASSERT(m_position == position); |
164 | ASSERT(m_selectorIndex == selectorIndex); |
165 | } |
166 | |
167 | RuleSet::RuleSet() = default; |
168 | |
169 | RuleSet::~RuleSet() = default; |
170 | |
171 | void RuleSet::addToRuleSet(const AtomicString& key, AtomRuleMap& map, const RuleData& ruleData) |
172 | { |
173 | if (key.isNull()) |
174 | return; |
175 | auto& rules = map.add(key, nullptr).iterator->value; |
176 | if (!rules) |
177 | rules = std::make_unique<RuleDataVector>(); |
178 | rules->append(ruleData); |
179 | } |
180 | |
181 | static unsigned rulesCountForName(const RuleSet::AtomRuleMap& map, const AtomicString& name) |
182 | { |
183 | if (const auto* rules = map.get(name)) |
184 | return rules->size(); |
185 | return 0; |
186 | } |
187 | |
188 | static bool isHostSelectorMatchingInShadowTree(const CSSSelector& startSelector) |
189 | { |
190 | auto* leftmostSelector = &startSelector; |
191 | bool hasDescendantOrChildRelation = false; |
192 | while (auto* previous = leftmostSelector->tagHistory()) { |
193 | hasDescendantOrChildRelation = leftmostSelector->hasDescendantOrChildRelation(); |
194 | leftmostSelector = previous; |
195 | } |
196 | if (!hasDescendantOrChildRelation) |
197 | return false; |
198 | |
199 | return leftmostSelector->match() == CSSSelector::PseudoClass && leftmostSelector->pseudoClassType() == CSSSelector::PseudoClassHost; |
200 | } |
201 | |
202 | void RuleSet::addRule(StyleRule* rule, unsigned selectorIndex, unsigned selectorListIndex) |
203 | { |
204 | RuleData ruleData(rule, selectorIndex, selectorListIndex, m_ruleCount++); |
205 | m_features.collectFeatures(ruleData); |
206 | |
207 | unsigned classBucketSize = 0; |
208 | const CSSSelector* idSelector = nullptr; |
209 | const CSSSelector* tagSelector = nullptr; |
210 | const CSSSelector* classSelector = nullptr; |
211 | const CSSSelector* linkSelector = nullptr; |
212 | const CSSSelector* focusSelector = nullptr; |
213 | const CSSSelector* hostPseudoClassSelector = nullptr; |
214 | const CSSSelector* customPseudoElementSelector = nullptr; |
215 | const CSSSelector* slottedPseudoElementSelector = nullptr; |
216 | #if ENABLE(VIDEO_TRACK) |
217 | const CSSSelector* cuePseudoElementSelector = nullptr; |
218 | #endif |
219 | const CSSSelector* selector = ruleData.selector(); |
220 | do { |
221 | switch (selector->match()) { |
222 | case CSSSelector::Id: |
223 | idSelector = selector; |
224 | break; |
225 | case CSSSelector::Class: { |
226 | auto& className = selector->value(); |
227 | if (!classSelector) { |
228 | classSelector = selector; |
229 | classBucketSize = rulesCountForName(m_classRules, className); |
230 | } else if (classBucketSize) { |
231 | unsigned newClassBucketSize = rulesCountForName(m_classRules, className); |
232 | if (newClassBucketSize < classBucketSize) { |
233 | classSelector = selector; |
234 | classBucketSize = newClassBucketSize; |
235 | } |
236 | } |
237 | break; |
238 | } |
239 | case CSSSelector::Tag: |
240 | if (selector->tagQName().localName() != starAtom()) |
241 | tagSelector = selector; |
242 | break; |
243 | case CSSSelector::PseudoElement: |
244 | switch (selector->pseudoElementType()) { |
245 | case CSSSelector::PseudoElementWebKitCustom: |
246 | case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed: |
247 | customPseudoElementSelector = selector; |
248 | break; |
249 | case CSSSelector::PseudoElementSlotted: |
250 | slottedPseudoElementSelector = selector; |
251 | break; |
252 | #if ENABLE(VIDEO_TRACK) |
253 | case CSSSelector::PseudoElementCue: |
254 | cuePseudoElementSelector = selector; |
255 | break; |
256 | #endif |
257 | default: |
258 | break; |
259 | } |
260 | break; |
261 | case CSSSelector::PseudoClass: |
262 | switch (selector->pseudoClassType()) { |
263 | case CSSSelector::PseudoClassLink: |
264 | case CSSSelector::PseudoClassVisited: |
265 | case CSSSelector::PseudoClassAnyLink: |
266 | case CSSSelector::PseudoClassAnyLinkDeprecated: |
267 | linkSelector = selector; |
268 | break; |
269 | case CSSSelector::PseudoClassFocus: |
270 | focusSelector = selector; |
271 | break; |
272 | case CSSSelector::PseudoClassHost: |
273 | hostPseudoClassSelector = selector; |
274 | break; |
275 | default: |
276 | break; |
277 | } |
278 | break; |
279 | case CSSSelector::Unknown: |
280 | case CSSSelector::Exact: |
281 | case CSSSelector::Set: |
282 | case CSSSelector::List: |
283 | case CSSSelector::Hyphen: |
284 | case CSSSelector::Contain: |
285 | case CSSSelector::Begin: |
286 | case CSSSelector::End: |
287 | case CSSSelector::PagePseudoClass: |
288 | break; |
289 | } |
290 | if (selector->relation() != CSSSelector::Subselector) |
291 | break; |
292 | selector = selector->tagHistory(); |
293 | } while (selector); |
294 | |
295 | #if ENABLE(VIDEO_TRACK) |
296 | if (cuePseudoElementSelector) { |
297 | m_cuePseudoRules.append(ruleData); |
298 | return; |
299 | } |
300 | #endif |
301 | |
302 | if (slottedPseudoElementSelector) { |
303 | // ::slotted pseudo elements work accross shadow boundary making filtering difficult. |
304 | ruleData.disableSelectorFiltering(); |
305 | m_slottedPseudoElementRules.append(ruleData); |
306 | return; |
307 | } |
308 | |
309 | if (customPseudoElementSelector) { |
310 | // FIXME: Custom pseudo elements are handled by the shadow tree's selector filter. It doesn't know about the main DOM. |
311 | ruleData.disableSelectorFiltering(); |
312 | addToRuleSet(customPseudoElementSelector->value(), m_shadowPseudoElementRules, ruleData); |
313 | return; |
314 | } |
315 | |
316 | if (!m_hasHostPseudoClassRulesMatchingInShadowTree) |
317 | m_hasHostPseudoClassRulesMatchingInShadowTree = isHostSelectorMatchingInShadowTree(*ruleData.selector()); |
318 | |
319 | if (hostPseudoClassSelector) { |
320 | m_hostPseudoClassRules.append(ruleData); |
321 | return; |
322 | } |
323 | |
324 | if (idSelector) { |
325 | addToRuleSet(idSelector->value(), m_idRules, ruleData); |
326 | return; |
327 | } |
328 | |
329 | if (classSelector) { |
330 | addToRuleSet(classSelector->value(), m_classRules, ruleData); |
331 | return; |
332 | } |
333 | |
334 | if (linkSelector) { |
335 | m_linkPseudoClassRules.append(ruleData); |
336 | return; |
337 | } |
338 | |
339 | if (focusSelector) { |
340 | m_focusPseudoClassRules.append(ruleData); |
341 | return; |
342 | } |
343 | |
344 | if (tagSelector) { |
345 | addToRuleSet(tagSelector->tagQName().localName(), m_tagLocalNameRules, ruleData); |
346 | addToRuleSet(tagSelector->tagLowercaseLocalName(), m_tagLowercaseLocalNameRules, ruleData); |
347 | return; |
348 | } |
349 | |
350 | // If we didn't find a specialized map to stick it in, file under universal rules. |
351 | m_universalRules.append(ruleData); |
352 | } |
353 | |
354 | void RuleSet::(StyleRulePage* rule) |
355 | { |
356 | m_pageRules.append(rule); |
357 | } |
358 | |
359 | void RuleSet::addChildRules(const Vector<RefPtr<StyleRuleBase>>& rules, const MediaQueryEvaluator& medium, StyleResolver* resolver, bool isInitiatingElementInUserAgentShadowTree) |
360 | { |
361 | for (auto& rule : rules) { |
362 | if (is<StyleRule>(*rule)) |
363 | addStyleRule(downcast<StyleRule>(rule.get())); |
364 | else if (is<StyleRulePage>(*rule)) |
365 | addPageRule(downcast<StyleRulePage>(rule.get())); |
366 | else if (is<StyleRuleMedia>(*rule)) { |
367 | auto& mediaRule = downcast<StyleRuleMedia>(*rule); |
368 | if ((!mediaRule.mediaQueries() || medium.evaluate(*mediaRule.mediaQueries(), resolver))) |
369 | addChildRules(mediaRule.childRules(), medium, resolver, isInitiatingElementInUserAgentShadowTree); |
370 | } else if (is<StyleRuleFontFace>(*rule) && resolver) { |
371 | // Add this font face to our set. |
372 | resolver->document().fontSelector().addFontFaceRule(downcast<StyleRuleFontFace>(*rule.get()), isInitiatingElementInUserAgentShadowTree); |
373 | resolver->invalidateMatchedPropertiesCache(); |
374 | } else if (is<StyleRuleKeyframes>(*rule) && resolver) |
375 | resolver->addKeyframeStyle(downcast<StyleRuleKeyframes>(*rule)); |
376 | else if (is<StyleRuleSupports>(*rule) && downcast<StyleRuleSupports>(*rule).conditionIsSupported()) |
377 | addChildRules(downcast<StyleRuleSupports>(*rule).childRules(), medium, resolver, isInitiatingElementInUserAgentShadowTree); |
378 | #if ENABLE(CSS_DEVICE_ADAPTATION) |
379 | else if (is<StyleRuleViewport>(*rule) && resolver) { |
380 | resolver->viewportStyleResolver()->addViewportRule(downcast<StyleRuleViewport>(rule.get())); |
381 | } |
382 | #endif |
383 | } |
384 | } |
385 | |
386 | void RuleSet::addRulesFromSheet(StyleSheetContents& sheet, const MediaQueryEvaluator& medium, StyleResolver* resolver) |
387 | { |
388 | for (auto& rule : sheet.importRules()) { |
389 | if (rule->styleSheet() && (!rule->mediaQueries() || medium.evaluate(*rule->mediaQueries(), resolver))) |
390 | addRulesFromSheet(*rule->styleSheet(), medium, resolver); |
391 | } |
392 | |
393 | // FIXME: Skip Content Security Policy check when stylesheet is in a user agent shadow tree. |
394 | // See <https://bugs.webkit.org/show_bug.cgi?id=146663>. |
395 | bool isInitiatingElementInUserAgentShadowTree = false; |
396 | addChildRules(sheet.childRules(), medium, resolver, isInitiatingElementInUserAgentShadowTree); |
397 | |
398 | if (m_autoShrinkToFitEnabled) |
399 | shrinkToFit(); |
400 | } |
401 | |
402 | void RuleSet::addStyleRule(StyleRule* rule) |
403 | { |
404 | unsigned selectorListIndex = 0; |
405 | for (size_t selectorIndex = 0; selectorIndex != notFound; selectorIndex = rule->selectorList().indexOfNextSelectorAfter(selectorIndex)) |
406 | addRule(rule, selectorIndex, selectorListIndex++); |
407 | } |
408 | |
409 | bool RuleSet::hasShadowPseudoElementRules() const |
410 | { |
411 | if (!m_shadowPseudoElementRules.isEmpty()) |
412 | return true; |
413 | #if ENABLE(VIDEO_TRACK) |
414 | if (!m_cuePseudoRules.isEmpty()) |
415 | return true; |
416 | #endif |
417 | return false; |
418 | } |
419 | |
420 | static inline void shrinkMapVectorsToFit(RuleSet::AtomRuleMap& map) |
421 | { |
422 | for (auto& vector : map.values()) |
423 | vector->shrinkToFit(); |
424 | } |
425 | |
426 | void RuleSet::shrinkToFit() |
427 | { |
428 | shrinkMapVectorsToFit(m_idRules); |
429 | shrinkMapVectorsToFit(m_classRules); |
430 | shrinkMapVectorsToFit(m_tagLocalNameRules); |
431 | shrinkMapVectorsToFit(m_tagLowercaseLocalNameRules); |
432 | shrinkMapVectorsToFit(m_shadowPseudoElementRules); |
433 | m_linkPseudoClassRules.shrinkToFit(); |
434 | #if ENABLE(VIDEO_TRACK) |
435 | m_cuePseudoRules.shrinkToFit(); |
436 | #endif |
437 | m_hostPseudoClassRules.shrinkToFit(); |
438 | m_slottedPseudoElementRules.shrinkToFit(); |
439 | m_focusPseudoClassRules.shrinkToFit(); |
440 | m_universalRules.shrinkToFit(); |
441 | m_pageRules.shrinkToFit(); |
442 | m_features.shrinkToFit(); |
443 | } |
444 | |
445 | } // namespace WebCore |
446 | |