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
51namespace WebCore {
52
53using namespace HTMLNames;
54
55// -----------------------------------------------------------------
56
57static 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
81static 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
100static inline bool isCommonAttributeSelectorAttribute(const QualifiedName& attribute)
101{
102 // These are explicitly tested for equality in canShareStyleWithElement.
103 return attribute == typeAttr || attribute == readonlyAttr;
104}
105
106static 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
133static inline bool containsUncommonAttributeSelector(const CSSSelector& rootSelector)
134{
135 return containsUncommonAttributeSelector(rootSelector, true);
136}
137
138static 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
151RuleData::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
167RuleSet::RuleSet() = default;
168
169RuleSet::~RuleSet() = default;
170
171void 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
181static 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
188static 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
202void 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
354void RuleSet::addPageRule(StyleRulePage* rule)
355{
356 m_pageRules.append(rule);
357}
358
359void 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
386void 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
402void 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
409bool 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
420static inline void shrinkMapVectorsToFit(RuleSet::AtomRuleMap& map)
421{
422 for (auto& vector : map.values())
423 vector->shrinkToFit();
424}
425
426void 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