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