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
54namespace blink {
55
56using namespace HTMLNames;
57
58bool 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
100bool 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
110static 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
116bool 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
168bool 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
182bool 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
196bool 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
271bool 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
280inline 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
298bool 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
306RenderStyle* 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