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