6d927d367171705f8f6dd70c5ecf855a7478bfd4
[WebKit-https.git] / Source / WebCore / css / RuleSet.cpp
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 AddRuleFlags addRuleFlags, const CSSSelector* selector)
139 {
140     if (addRuleFlags & RuleIsInRegionRule)
141         return PropertyWhitelistRegion;
142 #if ENABLE(VIDEO_TRACK)
143     for (const CSSSelector* component = selector; component; component = component->tagHistory()) {
144         if (component->match() == CSSSelector::PseudoElement && (component->pseudoElementType() == CSSSelector::PseudoElementCue || component->value() == TextTrackCue::cueShadowPseudoId()))
145             return PropertyWhitelistCue;
146     }
147 #else
148     UNUSED_PARAM(selector);
149 #endif
150     return PropertyWhitelistNone;
151 }
152
153 RuleData::RuleData(StyleRule* rule, unsigned selectorIndex, unsigned position, AddRuleFlags addRuleFlags)
154     : m_rule(rule)
155     , m_selectorIndex(selectorIndex)
156     , m_hasDocumentSecurityOrigin(addRuleFlags & RuleHasDocumentSecurityOrigin)
157     , m_position(position)
158     , m_matchBasedOnRuleHash(static_cast<unsigned>(computeMatchBasedOnRuleHash(*selector())))
159     , m_canMatchPseudoElement(selectorCanMatchPseudoElement(*selector()))
160     , m_containsUncommonAttributeSelector(WebCore::containsUncommonAttributeSelector(*selector()))
161     , m_linkMatchType(SelectorChecker::determineLinkMatchType(selector()))
162     , m_propertyWhitelistType(determinePropertyWhitelistType(addRuleFlags, selector()))
163 #if ENABLE(CSS_SELECTOR_JIT) && CSS_SELECTOR_JIT_PROFILING
164     , m_compiledSelectorUseCount(0)
165 #endif
166 {
167     ASSERT(m_position == position);
168     ASSERT(m_selectorIndex == selectorIndex);
169     SelectorFilter::collectIdentifierHashes(selector(), m_descendantSelectorIdentifierHashes, maximumIdentifierCount);
170 }
171
172 RuleSet::RuleSet()
173 {
174 }
175
176 RuleSet::~RuleSet()
177 {
178 }
179
180 void RuleSet::addToRuleSet(const AtomicString& key, AtomRuleMap& map, const RuleData& ruleData)
181 {
182     if (key.isNull())
183         return;
184     auto& rules = map.add(key, nullptr).iterator->value;
185     if (!rules)
186         rules = std::make_unique<RuleDataVector>();
187     rules->append(ruleData);
188 }
189
190 static unsigned rulesCountForName(const RuleSet::AtomRuleMap& map, const AtomicString& name)
191 {
192     if (const auto* rules = map.get(name))
193         return rules->size();
194     return 0;
195 }
196
197 static bool isHostSelectorMatchingInShadowTree(const CSSSelector& startSelector)
198 {
199     auto* leftmostSelector = &startSelector;
200     bool hasDescendantOrChildRelation = false;
201     while (auto* previous = leftmostSelector->tagHistory()) {
202         hasDescendantOrChildRelation = leftmostSelector->hasDescendantOrChildRelation();
203         leftmostSelector = previous;
204     }
205     if (!hasDescendantOrChildRelation)
206         return false;
207
208     return leftmostSelector->match() == CSSSelector::PseudoClass && leftmostSelector->pseudoClassType() == CSSSelector::PseudoClassHost;
209 }
210
211 void RuleSet::addRule(StyleRule* rule, unsigned selectorIndex, AddRuleFlags addRuleFlags)
212 {
213     RuleData ruleData(rule, selectorIndex, m_ruleCount++, addRuleFlags);
214     m_features.collectFeatures(ruleData);
215
216     unsigned classBucketSize = 0;
217     const CSSSelector* idSelector = nullptr;
218     const CSSSelector* tagSelector = nullptr;
219     const CSSSelector* classSelector = nullptr;
220     const CSSSelector* linkSelector = nullptr;
221     const CSSSelector* focusSelector = nullptr;
222     const CSSSelector* hostPseudoClassSelector = nullptr;
223     const CSSSelector* customPseudoElementSelector = nullptr;
224     const CSSSelector* slottedPseudoElementSelector = nullptr;
225 #if ENABLE(VIDEO_TRACK)
226     const CSSSelector* cuePseudoElementSelector = nullptr;
227 #endif
228     const CSSSelector* selector = ruleData.selector();
229     do {
230         switch (selector->match()) {
231         case CSSSelector::Id:
232             idSelector = selector;
233             break;
234         case CSSSelector::Class: {
235             auto& className = selector->value();
236             if (!classSelector) {
237                 classSelector = selector;
238                 classBucketSize = rulesCountForName(m_classRules, className);
239             } else if (classBucketSize) {
240                 unsigned newClassBucketSize = rulesCountForName(m_classRules, className);
241                 if (newClassBucketSize < classBucketSize) {
242                     classSelector = selector;
243                     classBucketSize = newClassBucketSize;
244                 }
245             }
246             break;
247         }
248         case CSSSelector::Tag:
249             if (selector->tagQName().localName() != starAtom)
250                 tagSelector = selector;
251             break;
252         case CSSSelector::PseudoElement:
253             switch (selector->pseudoElementType()) {
254             case CSSSelector::PseudoElementUserAgentCustom:
255             case CSSSelector::PseudoElementWebKitCustom:
256             case CSSSelector::PseudoElementWebKitCustomLegacyPrefixed:
257                 customPseudoElementSelector = selector;
258                 break;
259             case CSSSelector::PseudoElementSlotted:
260                 slottedPseudoElementSelector = selector;
261                 break;
262 #if ENABLE(VIDEO_TRACK)
263             case CSSSelector::PseudoElementCue:
264                 cuePseudoElementSelector = selector;
265                 break;
266 #endif
267             default:
268                 break;
269             }
270             break;
271         case CSSSelector::PseudoClass:
272             switch (selector->pseudoClassType()) {
273             case CSSSelector::PseudoClassLink:
274             case CSSSelector::PseudoClassVisited:
275             case CSSSelector::PseudoClassAnyLink:
276             case CSSSelector::PseudoClassAnyLinkDeprecated:
277                 linkSelector = selector;
278                 break;
279             case CSSSelector::PseudoClassFocus:
280                 focusSelector = selector;
281                 break;
282             case CSSSelector::PseudoClassHost:
283                 hostPseudoClassSelector = selector;
284                 break;
285             default:
286                 break;
287             }
288             break;
289         case CSSSelector::Unknown:
290         case CSSSelector::Exact:
291         case CSSSelector::Set:
292         case CSSSelector::List:
293         case CSSSelector::Hyphen:
294         case CSSSelector::Contain:
295         case CSSSelector::Begin:
296         case CSSSelector::End:
297         case CSSSelector::PagePseudoClass:
298             break;
299         }
300         if (selector->relation() != CSSSelector::Subselector)
301             break;
302         selector = selector->tagHistory();
303     } while (selector);
304
305 #if ENABLE(VIDEO_TRACK)
306     if (cuePseudoElementSelector) {
307         m_cuePseudoRules.append(ruleData);
308         return;
309     }
310 #endif
311
312     if (slottedPseudoElementSelector) {
313         // ::slotted pseudo elements work accross shadow boundary making filtering difficult.
314         ruleData.disableSelectorFiltering();
315         m_slottedPseudoElementRules.append(ruleData);
316         return;
317     }
318
319     if (customPseudoElementSelector) {
320         // FIXME: Custom pseudo elements are handled by the shadow tree's selector filter. It doesn't know about the main DOM.
321         ruleData.disableSelectorFiltering();
322         addToRuleSet(customPseudoElementSelector->value(), m_shadowPseudoElementRules, ruleData);
323         return;
324     }
325
326     if (!m_hasHostPseudoClassRulesMatchingInShadowTree)
327         m_hasHostPseudoClassRulesMatchingInShadowTree = isHostSelectorMatchingInShadowTree(*ruleData.selector());
328
329     if (hostPseudoClassSelector) {
330         m_hostPseudoClassRules.append(ruleData);
331         return;
332     }
333
334     if (idSelector) {
335         addToRuleSet(idSelector->value(), m_idRules, ruleData);
336         return;
337     }
338
339     if (classSelector) {
340         addToRuleSet(classSelector->value(), m_classRules, ruleData);
341         return;
342     }
343
344     if (linkSelector) {
345         m_linkPseudoClassRules.append(ruleData);
346         return;
347     }
348
349     if (focusSelector) {
350         m_focusPseudoClassRules.append(ruleData);
351         return;
352     }
353
354     if (tagSelector) {
355         addToRuleSet(tagSelector->tagQName().localName(), m_tagLocalNameRules, ruleData);
356         addToRuleSet(tagSelector->tagLowercaseLocalName(), m_tagLowercaseLocalNameRules, ruleData);
357         return;
358     }
359
360     // If we didn't find a specialized map to stick it in, file under universal rules.
361     m_universalRules.append(ruleData);
362 }
363
364 void RuleSet::addPageRule(StyleRulePage* rule)
365 {
366     m_pageRules.append(rule);
367 }
368
369 void RuleSet::addRegionRule(StyleRuleRegion* regionRule, bool hasDocumentSecurityOrigin)
370 {
371     auto regionRuleSet = std::make_unique<RuleSet>();
372     // The region rule set should take into account the position inside the parent rule set.
373     // Otherwise, the rules inside region block might be incorrectly positioned before other similar rules from
374     // the stylesheet that contains the region block.
375     regionRuleSet->m_ruleCount = m_ruleCount;
376
377     // Collect the region rules into a rule set
378     // FIXME: Should this add other types of rules? (i.e. use addChildRules() directly?)
379     const Vector<RefPtr<StyleRuleBase>>& childRules = regionRule->childRules();
380     AddRuleFlags addRuleFlags = hasDocumentSecurityOrigin ? RuleHasDocumentSecurityOrigin : RuleHasNoSpecialState;
381     addRuleFlags = static_cast<AddRuleFlags>(addRuleFlags | RuleIsInRegionRule);
382     for (auto& childRule : childRules) {
383         if (is<StyleRule>(*childRule))
384             regionRuleSet->addStyleRule(downcast<StyleRule>(childRule.get()), addRuleFlags);
385     }
386     // Update the "global" rule count so that proper order is maintained
387     m_ruleCount = regionRuleSet->m_ruleCount;
388
389     m_regionSelectorsAndRuleSets.append(RuleSetSelectorPair(regionRule->selectorList().first(), WTFMove(regionRuleSet)));
390 }
391
392 void RuleSet::addChildRules(const Vector<RefPtr<StyleRuleBase>>& rules, const MediaQueryEvaluator& medium, StyleResolver* resolver, bool hasDocumentSecurityOrigin, bool isInitiatingElementInUserAgentShadowTree, AddRuleFlags addRuleFlags)
393 {
394     for (auto& rule : rules) {
395         if (is<StyleRule>(*rule))
396             addStyleRule(downcast<StyleRule>(rule.get()), addRuleFlags);
397         else if (is<StyleRulePage>(*rule))
398             addPageRule(downcast<StyleRulePage>(rule.get()));
399         else if (is<StyleRuleMedia>(*rule)) {
400             auto& mediaRule = downcast<StyleRuleMedia>(*rule);
401             if ((!mediaRule.mediaQueries() || medium.evaluate(*mediaRule.mediaQueries(), resolver)))
402                 addChildRules(mediaRule.childRules(), medium, resolver, hasDocumentSecurityOrigin, isInitiatingElementInUserAgentShadowTree, addRuleFlags);
403         } else if (is<StyleRuleFontFace>(*rule) && resolver) {
404             // Add this font face to our set.
405             resolver->document().fontSelector().addFontFaceRule(downcast<StyleRuleFontFace>(*rule.get()), isInitiatingElementInUserAgentShadowTree);
406             resolver->invalidateMatchedPropertiesCache();
407         } else if (is<StyleRuleKeyframes>(*rule) && resolver)
408             resolver->addKeyframeStyle(downcast<StyleRuleKeyframes>(*rule));
409         else if (is<StyleRuleSupports>(*rule) && downcast<StyleRuleSupports>(*rule).conditionIsSupported())
410             addChildRules(downcast<StyleRuleSupports>(*rule).childRules(), medium, resolver, hasDocumentSecurityOrigin, isInitiatingElementInUserAgentShadowTree, addRuleFlags);
411 #if ENABLE(CSS_REGIONS)
412         else if (is<StyleRuleRegion>(*rule) && resolver) {
413             addRegionRule(downcast<StyleRuleRegion>(rule.get()), hasDocumentSecurityOrigin);
414         }
415 #endif
416 #if ENABLE(CSS_DEVICE_ADAPTATION)
417         else if (is<StyleRuleViewport>(*rule) && resolver) {
418             resolver->viewportStyleResolver()->addViewportRule(downcast<StyleRuleViewport>(rule.get()));
419         }
420 #endif
421     }
422 }
423
424 void RuleSet::addRulesFromSheet(StyleSheetContents& sheet, const MediaQueryEvaluator& medium, StyleResolver* resolver)
425 {
426     for (auto& rule : sheet.importRules()) {
427         if (rule->styleSheet() && (!rule->mediaQueries() || medium.evaluate(*rule->mediaQueries(), resolver)))
428             addRulesFromSheet(*rule->styleSheet(), medium, resolver);
429     }
430
431     bool hasDocumentSecurityOrigin = resolver && resolver->document().securityOrigin().canRequest(sheet.baseURL());
432     AddRuleFlags addRuleFlags = static_cast<AddRuleFlags>((hasDocumentSecurityOrigin ? RuleHasDocumentSecurityOrigin : 0));
433
434     // FIXME: Skip Content Security Policy check when stylesheet is in a user agent shadow tree.
435     // See <https://bugs.webkit.org/show_bug.cgi?id=146663>.
436     bool isInitiatingElementInUserAgentShadowTree = false;
437     addChildRules(sheet.childRules(), medium, resolver, hasDocumentSecurityOrigin, isInitiatingElementInUserAgentShadowTree, addRuleFlags);
438
439     if (m_autoShrinkToFitEnabled)
440         shrinkToFit();
441 }
442
443 void RuleSet::addStyleRule(StyleRule* rule, AddRuleFlags addRuleFlags)
444 {
445     for (size_t selectorIndex = 0; selectorIndex != notFound; selectorIndex = rule->selectorList().indexOfNextSelectorAfter(selectorIndex))
446         addRule(rule, selectorIndex, addRuleFlags);
447 }
448
449 bool RuleSet::hasShadowPseudoElementRules() const
450 {
451     if (!m_shadowPseudoElementRules.isEmpty())
452         return true;
453 #if ENABLE(VIDEO_TRACK)
454     if (!m_cuePseudoRules.isEmpty())
455         return true;
456 #endif
457     return false;
458 }
459
460 static inline void shrinkMapVectorsToFit(RuleSet::AtomRuleMap& map)
461 {
462     for (auto& vector : map.values())
463         vector->shrinkToFit();
464 }
465
466 void RuleSet::shrinkToFit()
467 {
468     shrinkMapVectorsToFit(m_idRules);
469     shrinkMapVectorsToFit(m_classRules);
470     shrinkMapVectorsToFit(m_tagLocalNameRules);
471     shrinkMapVectorsToFit(m_tagLowercaseLocalNameRules);
472     shrinkMapVectorsToFit(m_shadowPseudoElementRules);
473     m_linkPseudoClassRules.shrinkToFit();
474 #if ENABLE(VIDEO_TRACK)
475     m_cuePseudoRules.shrinkToFit();
476 #endif
477     m_hostPseudoClassRules.shrinkToFit();
478     m_slottedPseudoElementRules.shrinkToFit();
479     m_focusPseudoClassRules.shrinkToFit();
480     m_universalRules.shrinkToFit();
481     m_pageRules.shrinkToFit();
482     m_features.shrinkToFit();
483     m_regionSelectorsAndRuleSets.shrinkToFit();
484 }
485
486 } // namespace WebCore