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