| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com) |
| * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. |
| * All rights reserved. |
| * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org> |
| * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. |
| * (http://www.torchmobile.com/) |
| * Copyright (c) 2011, Code Aurora Forum. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "core/css/resolver/SharedStyleFinder.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/XMLNames.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/css/resolver/StyleResolverStats.h" |
| #include "core/dom/ContainerNode.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/Node.h" |
| #include "core/dom/NodeComputedStyle.h" |
| #include "core/dom/QualifiedName.h" |
| #include "core/dom/SpaceSplitString.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/dom/shadow/ElementShadow.h" |
| #include "core/dom/shadow/InsertionPoint.h" |
| #include "core/html/HTMLElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLOptGroupElement.h" |
| #include "core/html/HTMLOptionElement.h" |
| #include "core/style/ComputedStyle.h" |
| #include "core/svg/SVGElement.h" |
| #include "platform/tracing/TraceEvent.h" |
| #include "wtf/HashSet.h" |
| #include "wtf/text/AtomicString.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| bool SharedStyleFinder::canShareStyleWithControl(Element& candidate) const { |
| if (!isHTMLInputElement(candidate) || !isHTMLInputElement(element())) |
| return false; |
| |
| HTMLInputElement& candidateInput = toHTMLInputElement(candidate); |
| HTMLInputElement& thisInput = toHTMLInputElement(element()); |
| |
| if (candidateInput.isAutofilled() != thisInput.isAutofilled()) |
| return false; |
| if (candidateInput.shouldAppearChecked() != thisInput.shouldAppearChecked()) |
| return false; |
| if (candidateInput.shouldAppearIndeterminate() != |
| thisInput.shouldAppearIndeterminate()) |
| return false; |
| if (candidateInput.isRequired() != thisInput.isRequired()) |
| return false; |
| |
| if (candidate.isDisabledFormControl() != element().isDisabledFormControl()) |
| return false; |
| |
| if (candidate.matchesDefaultPseudoClass() != |
| element().matchesDefaultPseudoClass()) |
| return false; |
| |
| if (document().containsValidityStyleRules()) { |
| bool willValidate = candidate.willValidate(); |
| |
| if (willValidate != element().willValidate()) |
| return false; |
| |
| if (willValidate && |
| (candidate.isValidElement() != element().isValidElement())) |
| return false; |
| |
| if (candidate.isInRange() != element().isInRange()) |
| return false; |
| |
| if (candidate.isOutOfRange() != element().isOutOfRange()) |
| return false; |
| } |
| |
| if (candidateInput.isPlaceholderVisible() != thisInput.isPlaceholderVisible()) |
| return false; |
| |
| return true; |
| } |
| |
| bool SharedStyleFinder::classNamesAffectedByRules( |
| const SpaceSplitString& classNames) const { |
| unsigned count = classNames.size(); |
| for (unsigned i = 0; i < count; ++i) { |
| if (m_features.hasSelectorForClass(classNames[i])) |
| return true; |
| } |
| return false; |
| } |
| |
| static inline const AtomicString& typeAttributeValue(const Element& element) { |
| // type is animatable in SVG so we need to go down the slow path here. |
| return element.isSVGElement() ? element.getAttribute(typeAttr) |
| : element.fastGetAttribute(typeAttr); |
| } |
| |
| bool SharedStyleFinder::sharingCandidateHasIdenticalStyleAffectingAttributes( |
| Element& candidate) const { |
| if (element().sharesSameElementData(candidate)) |
| return true; |
| if (element().fastGetAttribute(XMLNames::langAttr) != |
| candidate.fastGetAttribute(XMLNames::langAttr)) |
| return false; |
| if (element().fastGetAttribute(langAttr) != |
| candidate.fastGetAttribute(langAttr)) |
| return false; |
| |
| // These two checks must be here since RuleSet has a special case to allow |
| // style sharing between elements with type and readonly attributes whereas |
| // other attribute selectors prevent sharing. |
| if (typeAttributeValue(element()) != typeAttributeValue(candidate)) |
| return false; |
| if (element().fastGetAttribute(readonlyAttr) != |
| candidate.fastGetAttribute(readonlyAttr)) |
| return false; |
| |
| if (!m_elementAffectedByClassRules) { |
| if (candidate.hasClass() && |
| classNamesAffectedByRules(candidate.classNames())) |
| return false; |
| } else if (candidate.hasClass()) { |
| // SVG elements require a (slow!) getAttribute comparision because "class" |
| // is an animatable attribute for SVG. |
| if (element().isSVGElement()) { |
| if (element().getAttribute(classAttr) != |
| candidate.getAttribute(classAttr)) |
| return false; |
| } else if (element().classNames() != candidate.classNames()) { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| |
| if (element().presentationAttributeStyle() != |
| candidate.presentationAttributeStyle()) |
| return false; |
| |
| // FIXME: Consider removing this, it's unlikely we'll have so many progress |
| // elements that sharing the style makes sense. Instead we should just not |
| // support style sharing for them. |
| if (isHTMLProgressElement(element())) { |
| if (element().shouldAppearIndeterminate() != |
| candidate.shouldAppearIndeterminate()) |
| return false; |
| } |
| |
| if (isHTMLOptGroupElement(element()) || isHTMLOptionElement(element())) { |
| if (element().isDisabledFormControl() != candidate.isDisabledFormControl()) |
| return false; |
| if (isHTMLOptionElement(element()) && |
| toHTMLOptionElement(element()).selected() != |
| toHTMLOptionElement(candidate).selected()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool SharedStyleFinder::sharingCandidateCanShareHostStyles( |
| Element& candidate) const { |
| const ElementShadow* elementShadow = element().shadow(); |
| const ElementShadow* candidateShadow = candidate.shadow(); |
| |
| if (!elementShadow && !candidateShadow) |
| return true; |
| |
| if (static_cast<bool>(elementShadow) != static_cast<bool>(candidateShadow)) |
| return false; |
| |
| return elementShadow->hasSameStyles(*candidateShadow); |
| } |
| |
| bool SharedStyleFinder::sharingCandidateDistributedToSameInsertionPoint( |
| Element& candidate) const { |
| HeapVector<Member<InsertionPoint>, 8> insertionPoints, |
| candidateInsertionPoints; |
| collectDestinationInsertionPoints(element(), insertionPoints); |
| collectDestinationInsertionPoints(candidate, candidateInsertionPoints); |
| if (insertionPoints.size() != candidateInsertionPoints.size()) |
| return false; |
| for (size_t i = 0; i < insertionPoints.size(); ++i) { |
| if (insertionPoints[i] != candidateInsertionPoints[i]) |
| return false; |
| } |
| return true; |
| } |
| |
| DISABLE_CFI_PERF |
| bool SharedStyleFinder::canShareStyleWithElement(Element& candidate) const { |
| if (element() == candidate) |
| return false; |
| Element* parent = candidate.parentOrShadowHostElement(); |
| const ComputedStyle* style = candidate.computedStyle(); |
| if (!style) |
| return false; |
| if (!style->isSharable()) |
| return false; |
| if (!parent) |
| return false; |
| if (element().parentOrShadowHostElement()->computedStyle() != |
| parent->computedStyle()) |
| return false; |
| if (candidate.tagQName() != element().tagQName()) |
| return false; |
| if (candidate.inlineStyle()) |
| return false; |
| if (candidate.needsStyleRecalc()) |
| return false; |
| if (candidate.isSVGElement() && |
| toSVGElement(candidate).animatedSMILStyleProperties()) |
| return false; |
| if (candidate.isLink() != element().isLink()) |
| return false; |
| if (candidate.shadowPseudoId() != element().shadowPseudoId()) |
| return false; |
| if (!sharingCandidateHasIdenticalStyleAffectingAttributes(candidate)) |
| return false; |
| if (candidate.additionalPresentationAttributeStyle() != |
| element().additionalPresentationAttributeStyle()) |
| return false; |
| if (candidate.hasID() && |
| m_features.hasSelectorForId(candidate.idForStyleResolution())) |
| return false; |
| if (!sharingCandidateCanShareHostStyles(candidate)) |
| return false; |
| if (!sharingCandidateDistributedToSameInsertionPoint(candidate)) |
| return false; |
| if (candidate.isInTopLayer() != element().isInTopLayer()) |
| return false; |
| |
| bool isControl = candidate.isFormControlElement(); |
| ASSERT(isControl == element().isFormControlElement()); |
| if (isControl && !canShareStyleWithControl(candidate)) |
| return false; |
| |
| if (isHTMLOptionElement(candidate) && isHTMLOptionElement(element()) && |
| (toHTMLOptionElement(candidate).selected() != |
| toHTMLOptionElement(element()).selected() || |
| toHTMLOptionElement(candidate).spatialNavigationFocused() != |
| toHTMLOptionElement(element()).spatialNavigationFocused())) |
| return false; |
| |
| // FIXME: This line is surprisingly hot, we may wish to inline |
| // hasDirectionAuto into StyleResolver. |
| if (candidate.isHTMLElement() && toHTMLElement(candidate).hasDirectionAuto()) |
| return false; |
| |
| if (candidate.isLink() && m_context.elementLinkState() != style->insideLink()) |
| return false; |
| |
| if (candidate.isUnresolvedV0CustomElement() != |
| element().isUnresolvedV0CustomElement()) |
| return false; |
| |
| if (element().parentOrShadowHostElement() != parent) { |
| if (!parent->isStyledElement()) |
| return false; |
| if (parent->inlineStyle()) |
| return false; |
| if (parent->isSVGElement() && |
| toSVGElement(parent)->animatedSMILStyleProperties()) |
| return false; |
| if (parent->hasID() && |
| m_features.hasSelectorForId(parent->idForStyleResolution())) |
| return false; |
| if (!parent->childrenSupportStyleSharing()) |
| return false; |
| } |
| |
| ShadowRoot* root1 = element().containingShadowRoot(); |
| ShadowRoot* root2 = candidate.containingShadowRoot(); |
| if (root1 && root2 && root1->type() != root2->type()) |
| return false; |
| |
| if (document().containsValidityStyleRules()) { |
| if (candidate.isValidElement() != element().isValidElement()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool SharedStyleFinder::documentContainsValidCandidate() const { |
| for (Element& element : |
| ElementTraversal::startsAt(document().documentElement())) { |
| if (element.supportsStyleSharing() && canShareStyleWithElement(element)) |
| return true; |
| } |
| return false; |
| } |
| |
| inline Element* SharedStyleFinder::findElementForStyleSharing() const { |
| StyleSharingList& styleSharingList = m_styleResolver->styleSharingList(); |
| for (StyleSharingList::iterator it = styleSharingList.begin(); |
| it != styleSharingList.end(); ++it) { |
| Element& candidate = **it; |
| if (!canShareStyleWithElement(candidate)) |
| continue; |
| if (it != styleSharingList.begin()) { |
| // Move the element to the front of the LRU |
| styleSharingList.remove(it); |
| styleSharingList.prepend(&candidate); |
| } |
| return &candidate; |
| } |
| m_styleResolver->addToStyleSharingList(element()); |
| return nullptr; |
| } |
| |
| bool SharedStyleFinder::matchesRuleSet(RuleSet* ruleSet) { |
| if (!ruleSet) |
| return false; |
| ElementRuleCollector collector(m_context, m_styleResolver->selectorFilter()); |
| return collector.hasAnyMatchingRules(ruleSet); |
| } |
| |
| ComputedStyle* SharedStyleFinder::findSharedStyle() { |
| INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), sharedStyleLookups, |
| 1); |
| |
| if (!element().supportsStyleSharing()) |
| return nullptr; |
| |
| // Cache whether context.element() is affected by any known class selectors. |
| m_elementAffectedByClassRules = |
| element().hasClass() && classNamesAffectedByRules(element().classNames()); |
| |
| Element* shareElement = findElementForStyleSharing(); |
| |
| if (!shareElement) { |
| if (document().styleEngine().stats() && |
| document().styleEngine().stats()->allCountersEnabled() && |
| documentContainsValidCandidate()) |
| INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), sharedStyleMissed, |
| 1); |
| return nullptr; |
| } |
| |
| INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), sharedStyleFound, 1); |
| |
| if (matchesRuleSet(m_siblingRuleSet)) { |
| INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), |
| sharedStyleRejectedBySiblingRules, 1); |
| return nullptr; |
| } |
| |
| if (matchesRuleSet(m_uncommonAttributeRuleSet)) { |
| INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), |
| sharedStyleRejectedByUncommonAttributeRules, |
| 1); |
| return nullptr; |
| } |
| |
| // Tracking child index requires unique style for each node. This may get set |
| // by the sibling rule match above. |
| if (!element().parentElementOrShadowRoot()->childrenSupportStyleSharing()) { |
| INCREMENT_STYLE_STATS_COUNTER(document().styleEngine(), |
| sharedStyleRejectedByParent, 1); |
| return nullptr; |
| } |
| |
| return shareElement->mutableComputedStyle(); |
| } |
| |
| } // namespace blink |