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