Home | History | Annotate | Download | only in resolver
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 2004-2005 Allan Sandfeld Jensen (kde (at) carewolf.com)
      4  * Copyright (C) 2006, 2007 Nicholas Shanks (webkit (at) 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 (at) webkit.org>
      7  * Copyright (C) 2007, 2008 Eric Seidel <eric (at) 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 WebCore {
     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().elementData() == candidate.elementData())
    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 
    238     if (isControl != element().isFormControlElement())
    239         return false;
    240 
    241     if (isControl && !canShareStyleWithControl(candidate))
    242         return false;
    243 
    244     // FIXME: This line is surprisingly hot, we may wish to inline hasDirectionAuto into StyleResolver.
    245     if (candidate.isHTMLElement() && toHTMLElement(candidate).hasDirectionAuto())
    246         return false;
    247 
    248     if (candidate.isLink() && m_context.elementLinkState() != style->insideLink())
    249         return false;
    250 
    251     if (candidate.isUnresolvedCustomElement() != element().isUnresolvedCustomElement())
    252         return false;
    253 
    254     if (element().parentOrShadowHostElement() != parent) {
    255         if (!parent->isStyledElement())
    256             return false;
    257         if (parent->inlineStyle())
    258             return false;
    259         if (parent->isSVGElement() && toSVGElement(parent)->animatedSMILStyleProperties())
    260             return false;
    261         if (parent->hasID() && m_features.hasSelectorForId(parent->idForStyleResolution()))
    262             return false;
    263         if (!parent->childrenSupportStyleSharing())
    264             return false;
    265     }
    266 
    267     return true;
    268 }
    269 
    270 bool SharedStyleFinder::documentContainsValidCandidate() const
    271 {
    272     for (Element* element = document().documentElement(); element; element = ElementTraversal::next(*element)) {
    273         if (element->supportsStyleSharing() && canShareStyleWithElement(*element))
    274             return true;
    275     }
    276     return false;
    277 }
    278 
    279 inline Element* SharedStyleFinder::findElementForStyleSharing() const
    280 {
    281     StyleSharingList& styleSharingList = m_styleResolver.styleSharingList();
    282     for (StyleSharingList::iterator it = styleSharingList.begin(); it != styleSharingList.end(); ++it) {
    283         Element& candidate = **it;
    284         if (!canShareStyleWithElement(candidate))
    285             continue;
    286         if (it != styleSharingList.begin()) {
    287             // Move the element to the front of the LRU
    288             styleSharingList.remove(it);
    289             styleSharingList.prepend(&candidate);
    290         }
    291         return &candidate;
    292     }
    293     m_styleResolver.addToStyleSharingList(element());
    294     return 0;
    295 }
    296 
    297 bool SharedStyleFinder::matchesRuleSet(RuleSet* ruleSet)
    298 {
    299     if (!ruleSet)
    300         return false;
    301     ElementRuleCollector collector(m_context, m_styleResolver.selectorFilter());
    302     return collector.hasAnyMatchingRules(ruleSet);
    303 }
    304 
    305 RenderStyle* SharedStyleFinder::findSharedStyle()
    306 {
    307     INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleLookups);
    308 
    309     if (!element().supportsStyleSharing())
    310         return 0;
    311 
    312     // Cache whether context.element() is affected by any known class selectors.
    313     m_elementAffectedByClassRules = element().hasClass() && classNamesAffectedByRules(element().classNames());
    314 
    315     Element* shareElement = findElementForStyleSharing();
    316 
    317     if (!shareElement) {
    318         if (m_styleResolver.stats() && m_styleResolver.stats()->printMissedCandidateCount && documentContainsValidCandidate())
    319             INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleMissed);
    320         return 0;
    321     }
    322 
    323     INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleFound);
    324 
    325     if (matchesRuleSet(m_siblingRuleSet)) {
    326         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedBySiblingRules);
    327         return 0;
    328     }
    329 
    330     if (matchesRuleSet(m_uncommonAttributeRuleSet)) {
    331         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedByUncommonAttributeRules);
    332         return 0;
    333     }
    334 
    335     // Tracking child index requires unique style for each node. This may get set by the sibling rule match above.
    336     if (!element().parentElementOrShadowRoot()->childrenSupportStyleSharing()) {
    337         INCREMENT_STYLE_STATS_COUNTER(m_styleResolver, sharedStyleRejectedByParent);
    338         return 0;
    339     }
    340 
    341     return shareElement->renderStyle();
    342 }
    343 
    344 }
    345