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