1 /*
2 * Copyright (C) 2012 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "core/css/StyleInvalidationAnalysis.h"
28
29 #include "core/css/CSSSelectorList.h"
30 #include "core/css/StyleRuleImport.h"
31 #include "core/css/StyleSheetContents.h"
32 #include "core/dom/ContainerNode.h"
33 #include "core/dom/Document.h"
34 #include "core/dom/ElementTraversal.h"
35 #include "core/dom/shadow/ShadowRoot.h"
36 #include "core/html/HTMLStyleElement.h"
37
38 namespace WebCore {
39
StyleInvalidationAnalysis(const Vector<StyleSheetContents * > & sheets)40 StyleInvalidationAnalysis::StyleInvalidationAnalysis(const Vector<StyleSheetContents*>& sheets)
41 : m_dirtiesAllStyle(false)
42 {
43 for (unsigned i = 0; i < sheets.size() && !m_dirtiesAllStyle; ++i)
44 analyzeStyleSheet(sheets[i]);
45 }
46
determineSelectorScopes(const CSSSelectorList & selectorList,HashSet<StringImpl * > & idScopes,HashSet<StringImpl * > & classScopes)47 static bool determineSelectorScopes(const CSSSelectorList& selectorList, HashSet<StringImpl*>& idScopes, HashSet<StringImpl*>& classScopes)
48 {
49 for (const CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector)) {
50 const CSSSelector* scopeSelector = 0;
51 // This picks the widest scope, not the narrowest, to minimize the number of found scopes.
52 for (const CSSSelector* current = selector; current; current = current->tagHistory()) {
53 // Prefer ids over classes.
54 if (current->m_match == CSSSelector::Id)
55 scopeSelector = current;
56 else if (current->m_match == CSSSelector::Class && (!scopeSelector || scopeSelector->m_match != CSSSelector::Id))
57 scopeSelector = current;
58 CSSSelector::Relation relation = current->relation();
59 if (relation != CSSSelector::Descendant && relation != CSSSelector::Child && relation != CSSSelector::SubSelector)
60 break;
61 }
62 if (!scopeSelector)
63 return false;
64 ASSERT(scopeSelector->m_match == CSSSelector::Class || scopeSelector->m_match == CSSSelector::Id);
65 if (scopeSelector->m_match == CSSSelector::Id)
66 idScopes.add(scopeSelector->value().impl());
67 else
68 classScopes.add(scopeSelector->value().impl());
69 }
70 return true;
71 }
72
hasDistributedRule(StyleSheetContents * styleSheetContents)73 static bool hasDistributedRule(StyleSheetContents* styleSheetContents)
74 {
75 const Vector<RefPtr<StyleRuleBase> >& rules = styleSheetContents->childRules();
76 for (unsigned i = 0; i < rules.size(); i++) {
77 const StyleRuleBase* rule = rules[i].get();
78 if (!rule->isStyleRule())
79 continue;
80
81 const StyleRule* styleRule = toStyleRule(rule);
82 const CSSSelectorList& selectorList = styleRule->selectorList();
83 for (size_t selectorIndex = 0; selectorIndex != kNotFound; selectorIndex = selectorList.indexOfNextSelectorAfter(selectorIndex)) {
84 if (selectorList.hasShadowDistributedAt(selectorIndex))
85 return true;
86 }
87 }
88 return false;
89 }
90
determineScopingNodeForStyleScoped(HTMLStyleElement * ownerElement,StyleSheetContents * styleSheetContents)91 static Node* determineScopingNodeForStyleScoped(HTMLStyleElement* ownerElement, StyleSheetContents* styleSheetContents)
92 {
93 ASSERT(ownerElement && ownerElement->isRegisteredAsScoped());
94
95 if (ownerElement->isInShadowTree()) {
96 if (hasDistributedRule(styleSheetContents)) {
97 ContainerNode* scope = ownerElement;
98 do {
99 scope = scope->containingShadowRoot()->shadowHost();
100 } while (scope->isInShadowTree());
101
102 return scope;
103 }
104 if (ownerElement->isRegisteredAsScoped())
105 return ownerElement->containingShadowRoot()->shadowHost();
106 }
107
108 return ownerElement->isRegisteredInShadowRoot() ? ownerElement->containingShadowRoot()->shadowHost() : ownerElement->parentNode();
109 }
110
ruleAdditionMightRequireDocumentStyleRecalc(StyleRuleBase * rule)111 static bool ruleAdditionMightRequireDocumentStyleRecalc(StyleRuleBase* rule)
112 {
113 // This funciton is conservative. We only return false when we know that
114 // the added @rule can't require style recalcs.
115 switch (rule->type()) {
116 case StyleRule::Import: // Whatever we import should do its own analysis, we don't need to invalidate the document here!
117 case StyleRule::Keyframes: // Keyframes never cause style invalidations and are handled during sheet insertion.
118 case StyleRule::Page: // Page rules apply only during printing, we force a full-recalc before printing.
119 return false;
120
121 case StyleRule::Media: // If the media rule doesn't apply, we could avoid recalc.
122 case StyleRule::FontFace: // If the fonts aren't in use, we could avoid recalc.
123 case StyleRule::Supports: // If we evaluated the supports-clause we could avoid recalc.
124 case StyleRule::Viewport: // If the viewport doesn't match, we could avoid recalcing.
125 // FIXME: Unclear if any of the rest need to cause style recalc:
126 case StyleRule::Region:
127 case StyleRule::Filter:
128 return true;
129
130 // These should all be impossible to reach:
131 case StyleRule::Unknown:
132 case StyleRule::Charset:
133 case StyleRule::Keyframe:
134 case StyleRule::Style:
135 break;
136 }
137 ASSERT_NOT_REACHED();
138 return true;
139 }
140
analyzeStyleSheet(StyleSheetContents * styleSheetContents)141 void StyleInvalidationAnalysis::analyzeStyleSheet(StyleSheetContents* styleSheetContents)
142 {
143 ASSERT(!styleSheetContents->isLoading());
144
145 // See if all rules on the sheet are scoped to some specific ids or classes.
146 // Then test if we actually have any of those in the tree at the moment.
147 const Vector<RefPtr<StyleRuleImport> >& importRules = styleSheetContents->importRules();
148 for (unsigned i = 0; i < importRules.size(); ++i) {
149 if (!importRules[i]->styleSheet())
150 continue;
151 analyzeStyleSheet(importRules[i]->styleSheet());
152 if (m_dirtiesAllStyle)
153 return;
154 }
155 if (styleSheetContents->hasSingleOwnerNode()) {
156 Node* ownerNode = styleSheetContents->singleOwnerNode();
157 if (ownerNode && ownerNode->hasTagName(HTMLNames::styleTag) && toHTMLStyleElement(ownerNode)->isRegisteredAsScoped()) {
158 m_scopingNodes.append(determineScopingNodeForStyleScoped(toHTMLStyleElement(ownerNode), styleSheetContents));
159 return;
160 }
161 }
162
163 const Vector<RefPtr<StyleRuleBase> >& rules = styleSheetContents->childRules();
164 for (unsigned i = 0; i < rules.size(); i++) {
165 StyleRuleBase* rule = rules[i].get();
166 if (!rule->isStyleRule()) {
167 if (ruleAdditionMightRequireDocumentStyleRecalc(rule)) {
168 m_dirtiesAllStyle = true;
169 return;
170 }
171 continue;
172 }
173 StyleRule* styleRule = toStyleRule(rule);
174 if (!determineSelectorScopes(styleRule->selectorList(), m_idScopes, m_classScopes)) {
175 m_dirtiesAllStyle = true;
176 return;
177 }
178 }
179 }
180
elementMatchesSelectorScopes(const Element * element,const HashSet<StringImpl * > & idScopes,const HashSet<StringImpl * > & classScopes)181 static bool elementMatchesSelectorScopes(const Element* element, const HashSet<StringImpl*>& idScopes, const HashSet<StringImpl*>& classScopes)
182 {
183 if (!idScopes.isEmpty() && element->hasID() && idScopes.contains(element->idForStyleResolution().impl()))
184 return true;
185 if (classScopes.isEmpty() || !element->hasClass())
186 return false;
187 const SpaceSplitString& classNames = element->classNames();
188 for (unsigned i = 0; i < classNames.size(); ++i) {
189 if (classScopes.contains(classNames[i].impl()))
190 return true;
191 }
192 return false;
193 }
194
invalidateStyle(Document & document)195 void StyleInvalidationAnalysis::invalidateStyle(Document& document)
196 {
197 ASSERT(!m_dirtiesAllStyle);
198
199 if (!m_scopingNodes.isEmpty()) {
200 for (unsigned i = 0; i < m_scopingNodes.size(); ++i)
201 m_scopingNodes.at(i)->setNeedsStyleRecalc();
202 }
203
204 if (m_idScopes.isEmpty() && m_classScopes.isEmpty())
205 return;
206 Element* element = ElementTraversal::firstWithin(document);
207 while (element) {
208 if (elementMatchesSelectorScopes(element, m_idScopes, m_classScopes)) {
209 element->setNeedsStyleRecalc();
210 // The whole subtree is now invalidated, we can skip to the next sibling.
211 element = ElementTraversal::nextSkippingChildren(*element);
212 continue;
213 }
214 element = ElementTraversal::next(*element);
215 }
216 }
217
218 }
219