• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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) 2013 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 "core/css/resolver/SharedStyleFinder.h"
31 
32 #include "HTMLNames.h"
33 #include "XMLNames.h"
34 #include "core/css/resolver/StyleResolver.h"
35 #include "core/css/resolver/StyleResolverStats.h"
36 #include "core/dom/ContainerNode.h"
37 #include "core/dom/Document.h"
38 #include "core/dom/ElementTraversal.h"
39 #include "core/dom/FullscreenElementStack.h"
40 #include "core/dom/Node.h"
41 #include "core/dom/NodeRenderStyle.h"
42 #include "core/dom/QualifiedName.h"
43 #include "core/dom/SpaceSplitString.h"
44 #include "core/dom/shadow/ElementShadow.h"
45 #include "core/html/HTMLElement.h"
46 #include "core/html/HTMLInputElement.h"
47 #include "core/html/HTMLOptGroupElement.h"
48 #include "core/rendering/style/RenderStyle.h"
49 #include "core/svg/SVGElement.h"
50 #include "wtf/HashSet.h"
51 #include "wtf/text/AtomicString.h"
52 
53 namespace WebCore {
54 
55 using namespace HTMLNames;
56 
canShareStyleWithControl(Element & candidate) const57 bool SharedStyleFinder::canShareStyleWithControl(Element& candidate) const
58 {
59     if (!candidate.hasTagName(inputTag) || !element().hasTagName(inputTag))
60         return false;
61 
62     HTMLInputElement& candidateInput = toHTMLInputElement(candidate);
63     HTMLInputElement& thisInput = toHTMLInputElement(element());
64 
65     if (candidateInput.isAutofilled() != thisInput.isAutofilled())
66         return false;
67     if (candidateInput.shouldAppearChecked() != thisInput.shouldAppearChecked())
68         return false;
69     if (candidateInput.shouldAppearIndeterminate() != thisInput.shouldAppearIndeterminate())
70         return false;
71     if (candidateInput.isRequired() != thisInput.isRequired())
72         return false;
73 
74     if (candidate.isDisabledFormControl() != element().isDisabledFormControl())
75         return false;
76 
77     if (candidate.isDefaultButtonForForm() != element().isDefaultButtonForForm())
78         return false;
79 
80     if (document().containsValidityStyleRules()) {
81         bool willValidate = candidate.willValidate();
82 
83         if (willValidate != element().willValidate())
84             return false;
85 
86         if (willValidate && (candidate.isValidFormControlElement() != element().isValidFormControlElement()))
87             return false;
88 
89         if (candidate.isInRange() != element().isInRange())
90             return false;
91 
92         if (candidate.isOutOfRange() != element().isOutOfRange())
93             return false;
94     }
95 
96     return true;
97 }
98 
classNamesAffectedByRules(const SpaceSplitString & classNames) const99 bool SharedStyleFinder::classNamesAffectedByRules(const SpaceSplitString& classNames) const
100 {
101     unsigned count = classNames.size();
102     for (unsigned i = 0; i < count; ++i) {
103         if (m_features.classesInRules.contains(classNames[i]))
104             return true;
105     }
106     return false;
107 }
108 
typeAttributeValue(const Element & element)109 static inline const AtomicString& typeAttributeValue(const Element& element)
110 {
111     // type is animatable in SVG so we need to go down the slow path here.
112     return element.isSVGElement() ? element.getAttribute(typeAttr) : element.fastGetAttribute(typeAttr);
113 }
114 
sharingCandidateHasIdenticalStyleAffectingAttributes(Element & candidate) const115 bool SharedStyleFinder::sharingCandidateHasIdenticalStyleAffectingAttributes(Element& candidate) const
116 {
117     if (element().elementData() == candidate.elementData())
118         return true;
119     if (element().fastGetAttribute(XMLNames::langAttr) != candidate.fastGetAttribute(XMLNames::langAttr))
120         return false;
121     if (element().fastGetAttribute(langAttr) != candidate.fastGetAttribute(langAttr))
122         return false;
123 
124     // These two checks must be here since RuleSet has a specail case to allow style sharing between elements
125     // with type and readonly attributes whereas other attribute selectors prevent sharing.
126     if (typeAttributeValue(element()) != typeAttributeValue(candidate))
127         return false;
128     if (element().fastGetAttribute(readonlyAttr) != candidate.fastGetAttribute(readonlyAttr))
129         return false;
130 
131     if (!m_elementAffectedByClassRules) {
132         if (candidate.hasClass() && classNamesAffectedByRules(candidate.classNames()))
133             return false;
134     } else if (candidate.hasClass()) {
135         // SVG elements require a (slow!) getAttribute comparision because "class" is an animatable attribute for SVG.
136         if (element().isSVGElement()) {
137             if (element().getAttribute(classAttr) != candidate.getAttribute(classAttr))
138                 return false;
139         } else if (element().classNames() != candidate.classNames()) {
140             return false;
141         }
142     } else {
143         return false;
144     }
145 
146     if (element().presentationAttributeStyle() != candidate.presentationAttributeStyle())
147         return false;
148 
149     // FIXME: Consider removing this, it's unlikely we'll have so many progress elements
150     // that sharing the style makes sense. Instead we should just not support style sharing
151     // for them.
152     if (element().hasTagName(progressTag)) {
153         if (element().shouldAppearIndeterminate() != candidate.shouldAppearIndeterminate())
154             return false;
155     }
156 
157     return true;
158 }
159 
canShareStyleWithElement(Element & candidate) const160 bool SharedStyleFinder::canShareStyleWithElement(Element& candidate) const
161 {
162     if (element() == candidate)
163         return false;
164     Element* parent = candidate.parentElement();
165     RenderStyle* style = candidate.renderStyle();
166     if (!style)
167         return false;
168     if (!style->isSharable())
169         return false;
170     if (!parent)
171         return false;
172     if (element().parentElement()->renderStyle() != parent->renderStyle())
173         return false;
174     if (candidate.tagQName() != element().tagQName())
175         return false;
176     if (candidate.inlineStyle())
177         return false;
178     if (candidate.needsStyleRecalc())
179         return false;
180     if (candidate.isSVGElement() && toSVGElement(candidate).animatedSMILStyleProperties())
181         return false;
182     if (candidate.isLink() != element().isLink())
183         return false;
184     if (candidate.hovered() != element().hovered())
185         return false;
186     if (candidate.active() != element().active())
187         return false;
188     if (candidate.focused() != element().focused())
189         return false;
190     if (candidate.shadowPseudoId() != element().shadowPseudoId())
191         return false;
192     if (candidate == document().cssTarget())
193         return false;
194     if (!sharingCandidateHasIdenticalStyleAffectingAttributes(candidate))
195         return false;
196     if (candidate.additionalPresentationAttributeStyle() != element().additionalPresentationAttributeStyle())
197         return false;
198     if (candidate.hasID() && m_features.idsInRules.contains(candidate.idForStyleResolution()))
199         return false;
200     if (candidate.hasScopedHTMLStyleChild())
201         return false;
202     if (candidate.shadow() && candidate.shadow()->containsActiveStyles())
203         return 0;
204 
205     bool isControl = candidate.isFormControlElement();
206 
207     if (isControl != element().isFormControlElement())
208         return false;
209 
210     if (isControl && !canShareStyleWithControl(candidate))
211         return false;
212 
213     // FIXME: This line is surprisingly hot, we may wish to inline hasDirectionAuto into StyleResolver.
214     if (candidate.isHTMLElement() && toHTMLElement(candidate).hasDirectionAuto())
215         return false;
216 
217     if (candidate.isLink() && m_context.elementLinkState() != style->insideLink())
218         return false;
219 
220     if (candidate.isUnresolvedCustomElement() != element().isUnresolvedCustomElement())
221         return false;
222 
223     if (element().parentElement() != parent) {
224         if (!parent->isStyledElement())
225             return false;
226         if (parent->hasScopedHTMLStyleChild())
227             return false;
228         if (parent->inlineStyle())
229             return false;
230         if (parent->isSVGElement() && toSVGElement(parent)->animatedSMILStyleProperties())
231             return false;
232         if (parent->hasID() && m_features.idsInRules.contains(parent->idForStyleResolution()))
233             return false;
234         if (!parent->childrenSupportStyleSharing())
235             return false;
236     }
237 
238     return true;
239 }
240 
documentContainsValidCandidate() const241 bool SharedStyleFinder::documentContainsValidCandidate() const
242 {
243     for (Element* element = document().documentElement(); element; element = ElementTraversal::next(*element)) {
244         if (element->supportsStyleSharing() && canShareStyleWithElement(*element))
245             return true;
246     }
247     return false;
248 }
249 
findElementForStyleSharing() const250 inline Element* SharedStyleFinder::findElementForStyleSharing() const
251 {
252     StyleSharingList& styleSharingList = m_styleResolver.styleSharingList();
253     for (StyleSharingList::iterator it = styleSharingList.begin(); it != styleSharingList.end(); ++it) {
254         Element& candidate = **it;
255         if (!canShareStyleWithElement(candidate))
256             continue;
257         if (it != styleSharingList.begin()) {
258             // Move the element to the front of the LRU
259             styleSharingList.remove(it);
260             styleSharingList.prepend(&candidate);
261         }
262         return &candidate;
263     }
264     m_styleResolver.addToStyleSharingList(element());
265     return 0;
266 }
267 
matchesRuleSet(RuleSet * ruleSet)268 bool SharedStyleFinder::matchesRuleSet(RuleSet* ruleSet)
269 {
270     if (!ruleSet)
271         return false;
272     ElementRuleCollector collector(m_context, m_styleResolver.selectorFilter());
273     return collector.hasAnyMatchingRules(ruleSet);
274 }
275 
findSharedStyle()276 RenderStyle* SharedStyleFinder::findSharedStyle()
277 {
278     INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleLookups);
279 
280     if (!element().supportsStyleSharing())
281         return 0;
282 
283     // Cache whether context.element() is affected by any known class selectors.
284     m_elementAffectedByClassRules = element().hasClass() && classNamesAffectedByRules(element().classNames());
285 
286     Element* shareElement = findElementForStyleSharing();
287 
288     if (!shareElement) {
289         if (m_styleResolver.stats() && m_styleResolver.stats()->printMissedCandidateCount && documentContainsValidCandidate())
290             INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleMissed);
291         return 0;
292     }
293 
294     INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleFound);
295 
296     if (matchesRuleSet(m_siblingRuleSet)) {
297         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedBySiblingRules);
298         return 0;
299     }
300 
301     if (matchesRuleSet(m_uncommonAttributeRuleSet)) {
302         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedByUncommonAttributeRules);
303         return 0;
304     }
305 
306     // Tracking child index requires unique style for each node. This may get set by the sibling rule match above.
307     if (!element().parentElement()->childrenSupportStyleSharing()) {
308         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedByParent);
309         return 0;
310     }
311 
312     return shareElement->renderStyle();
313 }
314 
315 }
316