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/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 
     58 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 
    100 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 
    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 
    116 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 
    168 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 
    182 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 
    196 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 
    271 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 
    280 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 
    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 
    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