blob: ee83ce26fadc855efcfe5b60788b01e685bf38d4 [file] [log] [blame]
/*
* 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 "config.h"
#include "core/css/resolver/SharedStyleFinder.h"
#include "HTMLNames.h"
#include "XMLNames.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/css/resolver/StyleResolverState.h"
#include "core/dom/ContainerNode.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/FullscreenElementStack.h"
#include "core/dom/Node.h"
#include "core/dom/NodeRenderStyle.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/QualifiedName.h"
#include "core/dom/SpaceSplitString.h"
#include "core/dom/shadow/ElementShadow.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLOptGroupElement.h"
#include "core/html/track/WebVTTElement.h"
#include "core/rendering/style/RenderStyle.h"
#include "core/svg/SVGElement.h"
#include "wtf/HashSet.h"
#include "wtf/text/AtomicString.h"
namespace WebCore {
using namespace HTMLNames;
static const unsigned cStyleSearchThreshold = 10;
static const unsigned cStyleSearchLevelThreshold = 10;
static inline bool parentElementPreventsSharing(const Element* parentElement)
{
if (!parentElement)
return false;
return parentElement->hasFlagsSetDuringStylingOfChildren();
}
Node* SharedStyleFinder::locateCousinList(Element* parent, unsigned& visitedNodeCount) const
{
if (visitedNodeCount >= cStyleSearchThreshold * cStyleSearchLevelThreshold)
return 0;
if (!parent || !parent->isStyledElement())
return 0;
if (parent->hasScopedHTMLStyleChild())
return 0;
if (parent->inlineStyle())
return 0;
if (parent->isSVGElement() && toSVGElement(parent)->animatedSMILStyleProperties())
return 0;
if (parent->hasID() && m_features.idsInRules.contains(parent->idForStyleResolution().impl()))
return 0;
if (isShadowHost(parent) && parent->shadow()->containsActiveStyles())
return 0;
RenderStyle* parentStyle = parent->renderStyle();
unsigned subcount = 0;
Node* thisCousin = parent;
Node* currentNode = parent->previousSibling();
// Reserve the tries for this level. This effectively makes sure that the algorithm
// will never go deeper than cStyleSearchLevelThreshold levels into recursion.
visitedNodeCount += cStyleSearchThreshold;
while (thisCousin) {
while (currentNode) {
++subcount;
if (!currentNode->hasScopedHTMLStyleChild() && currentNode->renderStyle() == parentStyle && currentNode->lastChild()
&& currentNode->isElementNode() && !parentElementPreventsSharing(toElement(currentNode))
&& !toElement(currentNode)->shadow()
) {
// Adjust for unused reserved tries.
visitedNodeCount -= cStyleSearchThreshold - subcount;
return currentNode->lastChild();
}
if (subcount >= cStyleSearchThreshold)
return 0;
currentNode = currentNode->previousSibling();
}
currentNode = locateCousinList(thisCousin->parentElement(), visitedNodeCount);
thisCousin = currentNode;
}
return 0;
}
bool SharedStyleFinder::canShareStyleWithControl(const ElementResolveContext& context, Element* element) const
{
if (!element->hasTagName(inputTag) || !context.element()->hasTagName(inputTag))
return false;
HTMLInputElement* thisInputElement = toHTMLInputElement(element);
HTMLInputElement* otherInputElement = toHTMLInputElement(context.element());
if (thisInputElement->elementData() != otherInputElement->elementData()) {
if (thisInputElement->fastGetAttribute(typeAttr) != otherInputElement->fastGetAttribute(typeAttr))
return false;
if (thisInputElement->fastGetAttribute(readonlyAttr) != otherInputElement->fastGetAttribute(readonlyAttr))
return false;
}
if (thisInputElement->isAutofilled() != otherInputElement->isAutofilled())
return false;
if (thisInputElement->shouldAppearChecked() != otherInputElement->shouldAppearChecked())
return false;
if (thisInputElement->shouldAppearIndeterminate() != otherInputElement->shouldAppearIndeterminate())
return false;
if (thisInputElement->isRequired() != otherInputElement->isRequired())
return false;
if (element->isDisabledFormControl() != context.element()->isDisabledFormControl())
return false;
if (element->isDefaultButtonForForm() != context.element()->isDefaultButtonForForm())
return false;
if (context.document()->containsValidityStyleRules()) {
bool willValidate = element->willValidate();
if (willValidate != context.element()->willValidate())
return false;
if (willValidate && (element->isValidFormControlElement() != context.element()->isValidFormControlElement()))
return false;
if (element->isInRange() != context.element()->isInRange())
return false;
if (element->isOutOfRange() != context.element()->isOutOfRange())
return false;
}
return true;
}
bool SharedStyleFinder::classNamesAffectedByRules(const SpaceSplitString& classNames) const
{
for (unsigned i = 0; i < classNames.size(); ++i) {
if (m_features.classesInRules.contains(classNames[i].impl()))
return true;
}
return false;
}
static inline bool elementHasDirectionAuto(Element* element)
{
// FIXME: This line is surprisingly hot, we may wish to inline hasDirectionAuto into StyleResolver.
return element->isHTMLElement() && toHTMLElement(element)->hasDirectionAuto();
}
bool SharedStyleFinder::sharingCandidateHasIdenticalStyleAffectingAttributes(const ElementResolveContext& context, Element* sharingCandidate) const
{
if (context.element()->elementData() == sharingCandidate->elementData())
return true;
if (context.element()->fastGetAttribute(XMLNames::langAttr) != sharingCandidate->fastGetAttribute(XMLNames::langAttr))
return false;
if (context.element()->fastGetAttribute(langAttr) != sharingCandidate->fastGetAttribute(langAttr))
return false;
if (!m_elementAffectedByClassRules) {
if (sharingCandidate->hasClass() && classNamesAffectedByRules(sharingCandidate->classNames()))
return false;
} else if (sharingCandidate->hasClass()) {
// SVG elements require a (slow!) getAttribute comparision because "class" is an animatable attribute for SVG.
if (context.element()->isSVGElement()) {
if (context.element()->getAttribute(classAttr) != sharingCandidate->getAttribute(classAttr))
return false;
} else if (context.element()->classNames() != sharingCandidate->classNames()) {
return false;
}
} else {
return false;
}
if (context.element()->presentationAttributeStyle() != sharingCandidate->presentationAttributeStyle())
return false;
if (context.element()->hasTagName(progressTag)) {
if (context.element()->shouldAppearIndeterminate() != sharingCandidate->shouldAppearIndeterminate())
return false;
}
return true;
}
bool SharedStyleFinder::canShareStyleWithElement(const ElementResolveContext& context, Element* element) const
{
RenderStyle* style = element->renderStyle();
if (!style)
return false;
if (style->unique())
return false;
if (style->hasUniquePseudoStyle())
return false;
if (element->tagQName() != context.element()->tagQName())
return false;
if (element->inlineStyle())
return false;
if (element->needsStyleRecalc())
return false;
if (element->isSVGElement() && toSVGElement(element)->animatedSMILStyleProperties())
return false;
if (element->isLink() != context.element()->isLink())
return false;
if (element->hovered() != context.element()->hovered())
return false;
if (element->active() != context.element()->active())
return false;
if (element->focused() != context.element()->focused())
return false;
if (element->shadowPseudoId() != context.element()->shadowPseudoId())
return false;
if (element == element->document()->cssTarget())
return false;
if (!sharingCandidateHasIdenticalStyleAffectingAttributes(context, element))
return false;
if (element->additionalPresentationAttributeStyle() != context.element()->additionalPresentationAttributeStyle())
return false;
if (element->hasID() && m_features.idsInRules.contains(element->idForStyleResolution().impl()))
return false;
if (element->hasScopedHTMLStyleChild())
return false;
if (isShadowHost(element) && element->shadow()->containsActiveStyles())
return 0;
// FIXME: We should share style for option and optgroup whenever possible.
// Before doing so, we need to resolve issues in HTMLSelectElement::recalcListItems
// and RenderMenuList::setText. See also https://bugs.webkit.org/show_bug.cgi?id=88405
if (element->hasTagName(optionTag) || isHTMLOptGroupElement(element))
return false;
bool isControl = element->isFormControlElement();
if (isControl != context.element()->isFormControlElement())
return false;
if (isControl && !canShareStyleWithControl(context, element))
return false;
if (style->transitions() || style->animations())
return false;
// Turn off style sharing for elements that can gain layers for reasons outside of the style system.
// See comments in RenderObject::setStyle().
if (element->hasTagName(iframeTag) || element->hasTagName(frameTag) || element->hasTagName(embedTag) || element->hasTagName(objectTag) || element->hasTagName(appletTag) || element->hasTagName(canvasTag))
return false;
if (elementHasDirectionAuto(element))
return false;
if (element->isLink() && context.elementLinkState() != style->insideLink())
return false;
if (element->isUnresolvedCustomElement() != context.element()->isUnresolvedCustomElement())
return false;
// Deny sharing styles between WebVTT and non-WebVTT nodes.
if (element->isWebVTTElement() != context.element()->isWebVTTElement())
return false;
if (element->isWebVTTElement() && context.element()->isWebVTTElement() && toWebVTTElement(element)->isPastNode() != toWebVTTElement(context.element())->isPastNode())
return false;
if (FullscreenElementStack* fullscreen = FullscreenElementStack::fromIfExists(context.document())) {
if (element == fullscreen->webkitCurrentFullScreenElement() || context.element() == fullscreen->webkitCurrentFullScreenElement())
return false;
}
return true;
}
inline Element* SharedStyleFinder::findSiblingForStyleSharing(const ElementResolveContext& context, Node* node, unsigned& count) const
{
for (; node; node = node->previousSibling()) {
if (!node->isStyledElement())
continue;
if (canShareStyleWithElement(context, toElement(node)))
break;
if (count++ == cStyleSearchThreshold)
return 0;
}
return toElement(node);
}
#ifdef STYLE_STATS
Element* SharedStyleFinder::searchDocumentForSharedStyle(const ElementResolveContext& context) const
{
for (Element* element = context.element()->document()->documentElement(); element; element = ElementTraversal::next(element)) {
if (canShareStyleWithElement(context, element))
return element;
}
return 0;
}
#endif
RenderStyle* SharedStyleFinder::locateSharedStyle(const ElementResolveContext& context, RenderStyle* newStyle)
{
STYLE_STATS_ADD_SEARCH();
if (!context.element() || !context.element()->isStyledElement())
return 0;
// If the element has inline style it is probably unique.
if (context.element()->inlineStyle())
return 0;
if (context.element()->isSVGElement() && toSVGElement(context.element())->animatedSMILStyleProperties())
return 0;
// Ids stop style sharing if they show up in the stylesheets.
if (context.element()->hasID() && m_features.idsInRules.contains(context.element()->idForStyleResolution().impl()))
return 0;
// Active and hovered elements always make a chain towards the document node
// and no siblings or cousins will have the same state.
if (context.element()->hovered())
return 0;
if (context.element()->active())
return 0;
// There is always only one focused element.
if (context.element()->focused())
return 0;
if (parentElementPreventsSharing(context.element()->parentElement()))
return 0;
if (context.element()->hasScopedHTMLStyleChild())
return 0;
if (context.element() == context.document()->cssTarget())
return 0;
if (elementHasDirectionAuto(context.element()))
return 0;
if (context.element()->hasActiveAnimations())
return 0;
// When a dialog is first shown, its style is mutated to center it in the
// viewport. So the styles can't be shared since the viewport position and
// size may be different each time a dialog is opened.
if (context.element()->hasTagName(dialogTag))
return 0;
if (isShadowHost(context.element()) && context.element()->shadow()->containsActiveStyles())
return 0;
STYLE_STATS_ADD_ELEMENT_ELIGIBLE_FOR_SHARING();
// Cache whether context.element() is affected by any known class selectors.
// FIXME: This should be an explicit out parameter, instead of a member variable.
m_elementAffectedByClassRules = context.element() && context.element()->hasClass() && classNamesAffectedByRules(context.element()->classNames());
// Check previous siblings and their cousins.
unsigned count = 0;
unsigned visitedNodeCount = 0;
Element* shareElement = 0;
Node* cousinList = context.element()->previousSibling();
while (cousinList) {
shareElement = findSiblingForStyleSharing(context, cousinList, count);
if (shareElement)
break;
cousinList = locateCousinList(cousinList->parentElement(), visitedNodeCount);
}
#ifdef STYLE_STATS
// FIXME: these stats don't to into account whether or not sibling/attribute
// rules prevent these nodes from actually sharing
if (shareElement) {
STYLE_STATS_ADD_SEARCH_FOUND_SIBLING_FOR_SHARING();
} else {
shareElement = searchDocumentForSharedStyle(context);
if (shareElement)
STYLE_STATS_ADD_SEARCH_MISSED_SHARING();
shareElement = 0;
}
#endif
// If we have exhausted all our budget or our cousins.
if (!shareElement)
return 0;
// Can't share if sibling rules apply. This is checked at the end as it should rarely fail.
if (m_styleResolver->styleSharingCandidateMatchesRuleSet(context, newStyle, m_siblingRuleSet))
return 0;
// Can't share if attribute rules apply.
if (m_styleResolver->styleSharingCandidateMatchesRuleSet(context, newStyle, m_uncommonAttributeRuleSet))
return 0;
// Tracking child index requires unique style for each node. This may get set by the sibling rule match above.
if (parentElementPreventsSharing(context.element()->parentElement()))
return 0;
STYLE_STATS_ADD_STYLE_SHARED();
return shareElement->renderStyle();
}
}