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