blob: 88a96138e477ae490777d5b9ac29e2f6d3af30ee [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 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) 2012 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/SelectorFilter.h"
#include "core/css/CSSSelector.h"
#include "core/dom/Document.h"
#include "wtf/PtrUtil.h"
namespace blink {
// Salt to separate otherwise identical string hashes so a class-selector like
// .article won't match <article> elements.
enum { TagNameSalt = 13, IdAttributeSalt = 17, ClassAttributeSalt = 19 };
static inline void collectElementIdentifierHashes(
const Element& element,
Vector<unsigned, 4>& identifierHashes) {
identifierHashes.append(
element.localNameForSelectorMatching().impl()->existingHash() *
TagNameSalt);
if (element.hasID())
identifierHashes.append(
element.idForStyleResolution().impl()->existingHash() *
IdAttributeSalt);
if (element.isStyledElement() && element.hasClass()) {
const SpaceSplitString& classNames = element.classNames();
size_t count = classNames.size();
for (size_t i = 0; i < count; ++i) {
DCHECK(classNames[i].impl());
// Speculative fix for https://crbug.com/646026
if (classNames[i].impl())
identifierHashes.append(classNames[i].impl()->existingHash() *
ClassAttributeSalt);
}
}
}
void SelectorFilter::pushParentStackFrame(Element& parent) {
ASSERT(m_ancestorIdentifierFilter);
ASSERT(m_parentStack.isEmpty() ||
m_parentStack.last().element == parent.parentOrShadowHostElement());
ASSERT(!m_parentStack.isEmpty() || !parent.parentOrShadowHostElement());
m_parentStack.append(ParentStackFrame(parent));
ParentStackFrame& parentFrame = m_parentStack.last();
// Mix tags, class names and ids into some sort of weird bouillabaisse.
// The filter is used for fast rejection of child and descendant selectors.
collectElementIdentifierHashes(parent, parentFrame.identifierHashes);
size_t count = parentFrame.identifierHashes.size();
for (size_t i = 0; i < count; ++i)
m_ancestorIdentifierFilter->add(parentFrame.identifierHashes[i]);
}
void SelectorFilter::popParentStackFrame() {
ASSERT(!m_parentStack.isEmpty());
ASSERT(m_ancestorIdentifierFilter);
const ParentStackFrame& parentFrame = m_parentStack.last();
size_t count = parentFrame.identifierHashes.size();
for (size_t i = 0; i < count; ++i)
m_ancestorIdentifierFilter->remove(parentFrame.identifierHashes[i]);
m_parentStack.removeLast();
if (m_parentStack.isEmpty()) {
ASSERT(m_ancestorIdentifierFilter->likelyEmpty());
m_ancestorIdentifierFilter.reset();
}
}
void SelectorFilter::pushParent(Element& parent) {
ASSERT(parent.document().inStyleRecalc());
ASSERT(parent.inActiveDocument());
if (m_parentStack.isEmpty()) {
ASSERT(parent == parent.document().documentElement());
ASSERT(!m_ancestorIdentifierFilter);
m_ancestorIdentifierFilter = wrapUnique(new IdentifierFilter);
pushParentStackFrame(parent);
return;
}
ASSERT(m_ancestorIdentifierFilter);
// We may get invoked for some random elements in some wacky cases during
// style resolve. Pause maintaining the stack in this case.
if (m_parentStack.last().element != parent.parentOrShadowHostElement())
return;
pushParentStackFrame(parent);
}
void SelectorFilter::popParent(Element& parent) {
ASSERT(parent.document().inStyleRecalc());
ASSERT(parent.inActiveDocument());
// Note that we may get invoked for some random elements in some wacky cases
// during style resolve. Pause maintaining the stack in this case.
if (!parentStackIsConsistent(&parent))
return;
popParentStackFrame();
}
static inline void collectDescendantSelectorIdentifierHashes(
const CSSSelector& selector,
unsigned*& hash) {
switch (selector.match()) {
case CSSSelector::Id:
if (!selector.value().isEmpty())
(*hash++) = selector.value().impl()->existingHash() * IdAttributeSalt;
break;
case CSSSelector::Class:
if (!selector.value().isEmpty())
(*hash++) =
selector.value().impl()->existingHash() * ClassAttributeSalt;
break;
case CSSSelector::Tag:
if (selector.tagQName().localName() != starAtom)
(*hash++) = selector.tagQName().localName().impl()->existingHash() *
TagNameSalt;
break;
default:
break;
}
}
void SelectorFilter::collectIdentifierHashes(const CSSSelector& selector,
unsigned* identifierHashes,
unsigned maximumIdentifierCount) {
unsigned* hash = identifierHashes;
unsigned* end = identifierHashes + maximumIdentifierCount;
CSSSelector::RelationType relation = selector.relation();
if (selector.relationIsAffectedByPseudoContent()) {
// Disable fastRejectSelector.
*identifierHashes = 0;
return;
}
// Skip the topmost selector. It is handled quickly by the rule hashes.
bool skipOverSubselectors = true;
for (const CSSSelector* current = selector.tagHistory(); current;
current = current->tagHistory()) {
// Only collect identifiers that match ancestors.
switch (relation) {
case CSSSelector::SubSelector:
if (!skipOverSubselectors)
collectDescendantSelectorIdentifierHashes(*current, hash);
break;
case CSSSelector::DirectAdjacent:
case CSSSelector::IndirectAdjacent:
skipOverSubselectors = true;
break;
case CSSSelector::ShadowSlot:
// Disable fastRejectSelector.
*identifierHashes = 0;
return;
case CSSSelector::Descendant:
case CSSSelector::Child:
// Fall through.
case CSSSelector::ShadowPseudo:
case CSSSelector::ShadowDeep:
skipOverSubselectors = false;
collectDescendantSelectorIdentifierHashes(*current, hash);
break;
}
if (hash == end)
return;
relation = current->relation();
if (current->relationIsAffectedByPseudoContent()) {
// Disable fastRejectSelector.
*identifierHashes = 0;
return;
}
}
*hash = 0;
}
DEFINE_TRACE(SelectorFilter::ParentStackFrame) {
visitor->trace(element);
}
DEFINE_TRACE(SelectorFilter) {
visitor->trace(m_parentStack);
}
} // namespace blink