Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / css / resolver / SharedStyleFinder.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, 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 "core/HTMLNames.h"
33 #include "core/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/dom/shadow/InsertionPoint.h"
46 #include "core/html/HTMLElement.h"
47 #include "core/html/HTMLInputElement.h"
48 #include "core/html/HTMLOptGroupElement.h"
49 #include "core/html/HTMLOptionElement.h"
50 #include "core/rendering/style/RenderStyle.h"
51 #include "core/svg/SVGElement.h"
52 #include "wtf/HashSet.h"
53 #include "wtf/text/AtomicString.h"
54
55 namespace blink {
56
57 using namespace HTMLNames;
58
59 bool SharedStyleFinder::canShareStyleWithControl(Element& candidate) const
60 {
61     if (!isHTMLInputElement(candidate) || !isHTMLInputElement(element()))
62         return false;
63
64     HTMLInputElement& candidateInput = toHTMLInputElement(candidate);
65     HTMLInputElement& thisInput = toHTMLInputElement(element());
66
67     if (candidateInput.isAutofilled() != thisInput.isAutofilled())
68         return false;
69     if (candidateInput.shouldAppearChecked() != thisInput.shouldAppearChecked())
70         return false;
71     if (candidateInput.shouldAppearIndeterminate() != thisInput.shouldAppearIndeterminate())
72         return false;
73     if (candidateInput.isRequired() != thisInput.isRequired())
74         return false;
75
76     if (candidate.isDisabledFormControl() != element().isDisabledFormControl())
77         return false;
78
79     if (candidate.isDefaultButtonForForm() != element().isDefaultButtonForForm())
80         return false;
81
82     if (document().containsValidityStyleRules()) {
83         bool willValidate = candidate.willValidate();
84
85         if (willValidate != element().willValidate())
86             return false;
87
88         if (willValidate && (candidate.isValidFormControlElement() != element().isValidFormControlElement()))
89             return false;
90
91         if (candidate.isInRange() != element().isInRange())
92             return false;
93
94         if (candidate.isOutOfRange() != element().isOutOfRange())
95             return false;
96     }
97
98     return true;
99 }
100
101 bool SharedStyleFinder::classNamesAffectedByRules(const SpaceSplitString& classNames) const
102 {
103     unsigned count = classNames.size();
104     for (unsigned i = 0; i < count; ++i) {
105         if (m_features.hasSelectorForClass(classNames[i]))
106             return true;
107     }
108     return false;
109 }
110
111 static inline const AtomicString& typeAttributeValue(const Element& element)
112 {
113     // type is animatable in SVG so we need to go down the slow path here.
114     return element.isSVGElement() ? element.getAttribute(typeAttr) : element.fastGetAttribute(typeAttr);
115 }
116
117 bool SharedStyleFinder::sharingCandidateHasIdenticalStyleAffectingAttributes(Element& candidate) const
118 {
119     if (element().sharesSameElementData(candidate))
120         return true;
121     if (element().fastGetAttribute(XMLNames::langAttr) != candidate.fastGetAttribute(XMLNames::langAttr))
122         return false;
123     if (element().fastGetAttribute(langAttr) != candidate.fastGetAttribute(langAttr))
124         return false;
125
126     // These two checks must be here since RuleSet has a special case to allow style sharing between elements
127     // with type and readonly attributes whereas other attribute selectors prevent sharing.
128     if (typeAttributeValue(element()) != typeAttributeValue(candidate))
129         return false;
130     if (element().fastGetAttribute(readonlyAttr) != candidate.fastGetAttribute(readonlyAttr))
131         return false;
132
133     if (!m_elementAffectedByClassRules) {
134         if (candidate.hasClass() && classNamesAffectedByRules(candidate.classNames()))
135             return false;
136     } else if (candidate.hasClass()) {
137         // SVG elements require a (slow!) getAttribute comparision because "class" is an animatable attribute for SVG.
138         if (element().isSVGElement()) {
139             if (element().getAttribute(classAttr) != candidate.getAttribute(classAttr))
140                 return false;
141         } else if (element().classNames() != candidate.classNames()) {
142             return false;
143         }
144     } else {
145         return false;
146     }
147
148     if (element().presentationAttributeStyle() != candidate.presentationAttributeStyle())
149         return false;
150
151     // FIXME: Consider removing this, it's unlikely we'll have so many progress elements
152     // that sharing the style makes sense. Instead we should just not support style sharing
153     // for them.
154     if (isHTMLProgressElement(element())) {
155         if (element().shouldAppearIndeterminate() != candidate.shouldAppearIndeterminate())
156             return false;
157     }
158
159     if (isHTMLOptGroupElement(element()) || isHTMLOptionElement(element())) {
160         if (element().isDisabledFormControl() != candidate.isDisabledFormControl())
161             return false;
162         if (isHTMLOptionElement(element()) && toHTMLOptionElement(element()).selected() != toHTMLOptionElement(candidate).selected())
163             return false;
164     }
165
166     return true;
167 }
168
169 bool SharedStyleFinder::sharingCandidateCanShareHostStyles(Element& candidate) const
170 {
171     const ElementShadow* elementShadow = element().shadow();
172     const ElementShadow* candidateShadow = candidate.shadow();
173
174     if (!elementShadow && !candidateShadow)
175         return true;
176
177     if (static_cast<bool>(elementShadow) != static_cast<bool>(candidateShadow))
178         return false;
179
180     return elementShadow->hasSameStyles(candidateShadow);
181 }
182
183 bool SharedStyleFinder::sharingCandidateDistributedToSameInsertionPoint(Element& candidate) const
184 {
185     WillBeHeapVector<RawPtrWillBeMember<InsertionPoint>, 8> insertionPoints, candidateInsertionPoints;
186     collectDestinationInsertionPoints(element(), insertionPoints);
187     collectDestinationInsertionPoints(candidate, candidateInsertionPoints);
188     if (insertionPoints.size() != candidateInsertionPoints.size())
189         return false;
190     for (size_t i = 0; i < insertionPoints.size(); ++i) {
191         if (insertionPoints[i] != candidateInsertionPoints[i])
192             return false;
193     }
194     return true;
195 }
196
197 bool SharedStyleFinder::canShareStyleWithElement(Element& candidate) const
198 {
199     if (element() == candidate)
200         return false;
201     Element* parent = candidate.parentOrShadowHostElement();
202     RenderStyle* style = candidate.renderStyle();
203     if (!style)
204         return false;
205     if (!style->isSharable())
206         return false;
207     if (!parent)
208         return false;
209     if (element().parentOrShadowHostElement()->renderStyle() != parent->renderStyle())
210         return false;
211     if (candidate.tagQName() != element().tagQName())
212         return false;
213     if (candidate.inlineStyle())
214         return false;
215     if (candidate.needsStyleRecalc())
216         return false;
217     if (candidate.isSVGElement() && toSVGElement(candidate).animatedSMILStyleProperties())
218         return false;
219     if (candidate.isLink() != element().isLink())
220         return false;
221     if (candidate.shadowPseudoId() != element().shadowPseudoId())
222         return false;
223     if (!sharingCandidateHasIdenticalStyleAffectingAttributes(candidate))
224         return false;
225     if (candidate.additionalPresentationAttributeStyle() != element().additionalPresentationAttributeStyle())
226         return false;
227     if (candidate.hasID() && m_features.hasSelectorForId(candidate.idForStyleResolution()))
228         return false;
229     if (!sharingCandidateCanShareHostStyles(candidate))
230         return false;
231     if (!sharingCandidateDistributedToSameInsertionPoint(candidate))
232         return false;
233     if (candidate.isInTopLayer() != element().isInTopLayer())
234         return false;
235
236     bool isControl = candidate.isFormControlElement();
237     ASSERT(isControl == element().isFormControlElement());
238     if (isControl && !canShareStyleWithControl(candidate))
239         return false;
240
241     if (isHTMLOptionElement(candidate) && isHTMLOptionElement(element())
242         && (toHTMLOptionElement(candidate).selected() != toHTMLOptionElement(element()).selected()
243         || toHTMLOptionElement(candidate).spatialNavigationFocused() != toHTMLOptionElement(element()).spatialNavigationFocused()))
244         return false;
245
246     // FIXME: This line is surprisingly hot, we may wish to inline hasDirectionAuto into StyleResolver.
247     if (candidate.isHTMLElement() && toHTMLElement(candidate).hasDirectionAuto())
248         return false;
249
250     if (candidate.isLink() && m_context.elementLinkState() != style->insideLink())
251         return false;
252
253     if (candidate.isUnresolvedCustomElement() != element().isUnresolvedCustomElement())
254         return false;
255
256     if (element().parentOrShadowHostElement() != parent) {
257         if (!parent->isStyledElement())
258             return false;
259         if (parent->inlineStyle())
260             return false;
261         if (parent->isSVGElement() && toSVGElement(parent)->animatedSMILStyleProperties())
262             return false;
263         if (parent->hasID() && m_features.hasSelectorForId(parent->idForStyleResolution()))
264             return false;
265         if (!parent->childrenSupportStyleSharing())
266             return false;
267     }
268
269     return true;
270 }
271
272 bool SharedStyleFinder::documentContainsValidCandidate() const
273 {
274     for (Element* element = document().documentElement(); element; element = ElementTraversal::next(*element)) {
275         if (element->supportsStyleSharing() && canShareStyleWithElement(*element))
276             return true;
277     }
278     return false;
279 }
280
281 inline Element* SharedStyleFinder::findElementForStyleSharing() const
282 {
283     StyleSharingList& styleSharingList = m_styleResolver.styleSharingList();
284     for (StyleSharingList::iterator it = styleSharingList.begin(); it != styleSharingList.end(); ++it) {
285         Element& candidate = **it;
286         if (!canShareStyleWithElement(candidate))
287             continue;
288         if (it != styleSharingList.begin()) {
289             // Move the element to the front of the LRU
290             styleSharingList.remove(it);
291             styleSharingList.prepend(&candidate);
292         }
293         return &candidate;
294     }
295     m_styleResolver.addToStyleSharingList(element());
296     return 0;
297 }
298
299 bool SharedStyleFinder::matchesRuleSet(RuleSet* ruleSet)
300 {
301     if (!ruleSet)
302         return false;
303     ElementRuleCollector collector(m_context, m_styleResolver.selectorFilter());
304     return collector.hasAnyMatchingRules(ruleSet);
305 }
306
307 RenderStyle* SharedStyleFinder::findSharedStyle()
308 {
309     INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleLookups);
310
311     if (!element().supportsStyleSharing())
312         return 0;
313
314     // Cache whether context.element() is affected by any known class selectors.
315     m_elementAffectedByClassRules = element().hasClass() && classNamesAffectedByRules(element().classNames());
316
317     Element* shareElement = findElementForStyleSharing();
318
319     if (!shareElement) {
320         if (m_styleResolver.stats() && m_styleResolver.stats()->printMissedCandidateCount && documentContainsValidCandidate())
321             INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleMissed);
322         return 0;
323     }
324
325     INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleFound);
326
327     if (matchesRuleSet(m_siblingRuleSet)) {
328         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedBySiblingRules);
329         return 0;
330     }
331
332     if (matchesRuleSet(m_uncommonAttributeRuleSet)) {
333         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedByUncommonAttributeRules);
334         return 0;
335     }
336
337     // Tracking child index requires unique style for each node. This may get set by the sibling rule match above.
338     if (!element().parentElementOrShadowRoot()->childrenSupportStyleSharing()) {
339         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedByParent);
340         return 0;
341     }
342
343     return shareElement->renderStyle();
344 }
345
346 }