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