Add modern API for overriding the page's specified viewport configuration
[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 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::addPageRule(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