blob: bf5803d2078ef7197406aee06ec83780b2206e85 [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.
*
* 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 "SelectorChecker.h"
#include "CSSSelector.h"
#include "CSSSelectorList.h"
#include "Document.h"
#include "DocumentStyleSheetCollection.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "HTMLAnchorElement.h"
#include "HTMLFrameElementBase.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLOptionElement.h"
#include "HTMLProgressElement.h"
#include "HTMLStyleElement.h"
#include "InspectorInstrumentation.h"
#include "NodeRenderStyle.h"
#include "Page.h"
#include "PageGroup.h"
#include "RenderObject.h"
#include "RenderScrollbar.h"
#include "RenderStyle.h"
#include "ScrollableArea.h"
#include "ScrollbarTheme.h"
#include "StyledElement.h"
#include "Text.h"
#include "XLinkNames.h"
#if USE(PLATFORM_STRATEGIES)
#include "PlatformStrategies.h"
#include "VisitedLinkStrategy.h"
#endif
namespace WebCore {
using namespace HTMLNames;
static bool htmlAttributeHasCaseInsensitiveValue(const QualifiedName& attr);
SelectorChecker::SelectorChecker(Document* document, bool strictParsing)
: m_document(document)
, m_strictParsing(strictParsing)
, m_documentIsHTML(document->isHTMLDocument())
, m_mode(ResolvingStyle)
{
}
// 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->localName().impl()->existingHash() * TagNameSalt);
if (element->hasID())
identifierHashes.append(element->idForStyleResolution().impl()->existingHash() * IdAttributeSalt);
const StyledElement* styledElement = element->isStyledElement() ? static_cast<const StyledElement*>(element) : 0;
if (styledElement && styledElement->hasClass()) {
const SpaceSplitString& classNames = styledElement->classNames();
size_t count = classNames.size();
for (size_t i = 0; i < count; ++i)
identifierHashes.append(classNames[i].impl()->existingHash() * ClassAttributeSalt);
}
}
void SelectorChecker::pushParentStackFrame(Element* parent)
{
ASSERT(m_ancestorIdentifierFilter);
ASSERT(m_parentStack.isEmpty() || m_parentStack.last().element == parent->parentOrHostElement());
ASSERT(!m_parentStack.isEmpty() || !parent->parentOrHostElement());
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 SelectorChecker::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.clear();
}
}
void SelectorChecker::setupParentStack(Element* parent)
{
ASSERT(m_parentStack.isEmpty() == !m_ancestorIdentifierFilter);
// Kill whatever we stored before.
m_parentStack.shrink(0);
m_ancestorIdentifierFilter = adoptPtr(new BloomFilter<bloomFilterKeyBits>);
// Fast version if parent is a root element:
if (!parent->parentOrHostNode()) {
pushParentStackFrame(parent);
return;
}
// Otherwise climb up the tree.
Vector<Element*, 30> ancestors;
for (Element* ancestor = parent; ancestor; ancestor = ancestor->parentOrHostElement())
ancestors.append(ancestor);
for (size_t n = ancestors.size(); n; --n)
pushParentStackFrame(ancestors[n - 1]);
}
void SelectorChecker::pushParent(Element* parent)
{
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->parentOrHostElement())
return;
pushParentStackFrame(parent);
}
static inline void collectDescendantSelectorIdentifierHashes(const CSSSelector* selector, unsigned*& hash, const unsigned* end)
{
switch (selector->m_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;
default:
break;
}
if (hash == end)
return;
const AtomicString& localName = selector->tag().localName();
if (localName != starAtom)
(*hash++) = localName.impl()->existingHash() * TagNameSalt;
}
void SelectorChecker::collectIdentifierHashes(const CSSSelector* selector, unsigned* identifierHashes, unsigned maximumIdentifierCount)
{
unsigned* hash = identifierHashes;
unsigned* end = identifierHashes + maximumIdentifierCount;
CSSSelector::Relation relation = selector->relation();
// Skip the topmost selector. It is handled quickly by the rule hashes.
bool skipOverSubselectors = true;
for (selector = selector->tagHistory(); selector; selector = selector->tagHistory()) {
// Only collect identifiers that match ancestors.
switch (relation) {
case CSSSelector::SubSelector:
if (!skipOverSubselectors)
collectDescendantSelectorIdentifierHashes(selector, hash, end);
break;
case CSSSelector::DirectAdjacent:
case CSSSelector::IndirectAdjacent:
case CSSSelector::ShadowDescendant:
skipOverSubselectors = true;
break;
case CSSSelector::Descendant:
case CSSSelector::Child:
skipOverSubselectors = false;
collectDescendantSelectorIdentifierHashes(selector, hash, end);
break;
}
if (hash == end)
return;
relation = selector->relation();
}
*hash = 0;
}
static inline const AtomicString* linkAttribute(Node* node)
{
if (!node->isLink())
return 0;
ASSERT(node->isElementNode());
Element* element = static_cast<Element*>(node);
if (element->isHTMLElement())
return &element->fastGetAttribute(hrefAttr);
if (element->isSVGElement())
return &element->getAttribute(XLinkNames::hrefAttr);
return 0;
}
EInsideLink SelectorChecker::determineLinkStateSlowCase(Element* element) const
{
ASSERT(element->isLink());
const AtomicString* attribute = linkAttribute(element);
if (!attribute || attribute->isNull())
return NotInsideLink;
// An empty href refers to the document itself which is always visited. It is useful to check this explicitly so
// that visited links can be tested in platform independent manner, without explicit support in the test harness.
if (attribute->isEmpty())
return InsideVisitedLink;
LinkHash hash;
if (element->hasTagName(aTag))
hash = static_cast<HTMLAnchorElement*>(element)->visitedLinkHash();
else
hash = visitedLinkHash(m_document->baseURL(), *attribute);
if (!hash)
return InsideUnvisitedLink;
Frame* frame = m_document->frame();
if (!frame)
return InsideUnvisitedLink;
Page* page = frame->page();
if (!page)
return InsideUnvisitedLink;
m_linksCheckedForVisitedState.add(hash);
#if USE(PLATFORM_STRATEGIES)
return platformStrategies()->visitedLinkStrategy()->isLinkVisited(page, hash, m_document->baseURL(), *attribute) ? InsideVisitedLink : InsideUnvisitedLink;
#else
return page->group().isLinkVisited(hash) ? InsideVisitedLink : InsideUnvisitedLink;
#endif
}
bool SelectorChecker::checkSelector(CSSSelector* sel, Element* element, bool isFastCheckableSelector) const
{
if (isFastCheckableSelector && !element->isSVGElement()) {
if (!fastCheckRightmostSelector(sel, element, VisitedMatchDisabled))
return false;
return fastCheckSelector(sel, element);
}
PseudoId ignoreDynamicPseudo = NOPSEUDO;
bool hasUnknownPseudoElements = false;
return checkSelector(SelectorCheckingContext(sel, element, SelectorChecker::VisitedMatchDisabled), ignoreDynamicPseudo, hasUnknownPseudoElements) == SelectorMatches;
}
namespace {
template <bool checkValue(const Element*, AtomicStringImpl*, const QualifiedName&), bool initAttributeName>
inline bool fastCheckSingleSelector(const CSSSelector*& selector, const Element*& element, const CSSSelector*& topChildOrSubselector, const Element*& topChildOrSubselectorMatchElement)
{
AtomicStringImpl* value = selector->value().impl();
const QualifiedName& attribute = initAttributeName ? selector->attribute() : anyQName();
for (; element; element = element->parentElement()) {
if (checkValue(element, value, attribute) && SelectorChecker::tagMatches(element, selector)) {
if (selector->relation() == CSSSelector::Descendant)
topChildOrSubselector = 0;
else if (!topChildOrSubselector) {
ASSERT(selector->relation() == CSSSelector::Child || selector->relation() == CSSSelector::SubSelector);
topChildOrSubselector = selector;
topChildOrSubselectorMatchElement = element;
}
if (selector->relation() != CSSSelector::SubSelector)
element = element->parentElement();
selector = selector->tagHistory();
return true;
}
if (topChildOrSubselector) {
// Child or subselector check failed.
// If the match element is null, topChildOrSubselector was also the very topmost selector and had to match
// the original element we were checking.
if (!topChildOrSubselectorMatchElement)
return false;
// There may be other matches down the ancestor chain.
// Rewind to the topmost child or subselector and the element it matched, continue checking ancestors.
selector = topChildOrSubselector;
element = topChildOrSubselectorMatchElement->parentElement();
topChildOrSubselector = 0;
return true;
}
}
return false;
}
inline bool checkClassValue(const Element* element, AtomicStringImpl* value, const QualifiedName&)
{
return element->hasClass() && static_cast<const StyledElement*>(element)->classNames().contains(value);
}
inline bool checkIDValue(const Element* element, AtomicStringImpl* value, const QualifiedName&)
{
return element->hasID() && element->idForStyleResolution().impl() == value;
}
inline bool checkExactAttributeValue(const Element* element, AtomicStringImpl* value, const QualifiedName& attributeName)
{
return SelectorChecker::checkExactAttribute(element, attributeName, value);
}
inline bool checkTagValue(const Element*, AtomicStringImpl*, const QualifiedName&)
{
return true;
}
}
inline bool SelectorChecker::fastCheckRightmostSelector(const CSSSelector* selector, const Element* element, VisitedMatchType visitedMatchType) const
{
ASSERT(isFastCheckableSelector(selector));
if (!SelectorChecker::tagMatches(element, selector))
return false;
switch (selector->m_match) {
case CSSSelector::None:
return true;
case CSSSelector::Class:
return checkClassValue(element, selector->value().impl(), anyQName());
case CSSSelector::Id:
return checkIDValue(element, selector->value().impl(), anyQName());
case CSSSelector::Exact:
case CSSSelector::Set:
return checkExactAttributeValue(element, selector->value().impl(), selector->attribute());
case CSSSelector::PseudoClass:
return commonPseudoClassSelectorMatches(element, selector, visitedMatchType);
default:
ASSERT_NOT_REACHED();
}
return false;
}
bool SelectorChecker::fastCheckSelector(const CSSSelector* selector, const Element* element) const
{
ASSERT(fastCheckRightmostSelector(selector, element, VisitedMatchEnabled));
const CSSSelector* topChildOrSubselector = 0;
const Element* topChildOrSubselectorMatchElement = 0;
if (selector->relation() == CSSSelector::Child || selector->relation() == CSSSelector::SubSelector)
topChildOrSubselector = selector;
if (selector->relation() != CSSSelector::SubSelector)
element = element->parentElement();
selector = selector->tagHistory();
// We know this compound selector has descendant, child and subselector combinators only and all components are simple.
while (selector) {
switch (selector->m_match) {
case CSSSelector::Class:
if (!fastCheckSingleSelector<checkClassValue, false>(selector, element, topChildOrSubselector, topChildOrSubselectorMatchElement))
return false;
break;
case CSSSelector::Id:
if (!fastCheckSingleSelector<checkIDValue, false>(selector, element, topChildOrSubselector, topChildOrSubselectorMatchElement))
return false;
break;
case CSSSelector::None:
if (!fastCheckSingleSelector<checkTagValue, false>(selector, element, topChildOrSubselector, topChildOrSubselectorMatchElement))
return false;
break;
case CSSSelector::Set:
case CSSSelector::Exact:
if (!fastCheckSingleSelector<checkExactAttributeValue, true>(selector, element, topChildOrSubselector, topChildOrSubselectorMatchElement))
return false;
break;
default:
ASSERT_NOT_REACHED();
}
}
return true;
}
static inline bool isFastCheckableRelation(CSSSelector::Relation relation)
{
return relation == CSSSelector::Descendant || relation == CSSSelector::Child || relation == CSSSelector::SubSelector;
}
static inline bool isFastCheckableMatch(const CSSSelector* selector)
{
if (selector->m_match == CSSSelector::Set) {
// Style attribute is generated lazily but the fast path doesn't trigger it.
// Disallow them here rather than making the fast path more branchy.
return selector->attribute() != styleAttr;
}
if (selector->m_match == CSSSelector::Exact)
return selector->attribute() != styleAttr && !htmlAttributeHasCaseInsensitiveValue(selector->attribute());
return selector->m_match == CSSSelector::None || selector->m_match == CSSSelector::Id || selector->m_match == CSSSelector::Class;
}
static inline bool isFastCheckableRightmostSelector(const CSSSelector* selector)
{
if (!isFastCheckableRelation(selector->relation()))
return false;
return isFastCheckableMatch(selector) || SelectorChecker::isCommonPseudoClassSelector(selector);
}
bool SelectorChecker::isFastCheckableSelector(const CSSSelector* selector)
{
if (!isFastCheckableRightmostSelector(selector))
return false;
for (selector = selector->tagHistory(); selector; selector = selector->tagHistory()) {
if (!isFastCheckableRelation(selector->relation()))
return false;
if (!isFastCheckableMatch(selector))
return false;
}
return true;
}
// Recursive check of selectors and combinators
// It can return 4 different values:
// * SelectorMatches - the selector matches the element e
// * SelectorFailsLocally - the selector fails for the element e
// * SelectorFailsAllSiblings - the selector fails for e and any sibling of e
// * SelectorFailsCompletely - the selector fails for e and any sibling or ancestor of e
template<typename CheckingContext>
SelectorChecker::SelectorMatch SelectorChecker::checkSelector(const CheckingContext& context, PseudoId& dynamicPseudo, bool& hasUnknownPseudoElements) const
{
// first selector has to match
if (!checkOneSelector(context))
return SelectorFailsLocally;
if (context.selector->m_match == CSSSelector::PseudoElement) {
if (context.selector->isUnknownPseudoElement()) {
hasUnknownPseudoElements = true;
if (context.element->shadowPseudoId() != context.selector->value())
return SelectorFailsLocally;
} else {
if ((!context.elementStyle && m_mode == ResolvingStyle) || m_mode == QueryingRules)
return SelectorFailsLocally;
PseudoId pseudoId = CSSSelector::pseudoId(context.selector->pseudoType());
if (pseudoId == FIRST_LETTER) {
if (Document* document = context.element->document())
document->styleSheetCollection()->setUsesFirstLetterRules(true);
}
if (pseudoId != NOPSEUDO)
dynamicPseudo = pseudoId;
}
}
// The rest of the selectors has to match
CSSSelector::Relation relation = context.selector->relation();
// Prepare next selector
CSSSelector* historySelector = context.selector->tagHistory();
if (!historySelector)
return SelectorMatches;
CheckingContext nextContext(context);
nextContext.selector = historySelector;
PseudoId ignoreDynamicPseudo = NOPSEUDO;
if (relation != CSSSelector::SubSelector) {
// Abort if the next selector would exceed the scope.
if (context.element == context.scope)
return SelectorFailsCompletely;
// Bail-out if this selector is irrelevant for the pseudoStyle
if (context.pseudoStyle != NOPSEUDO && context.pseudoStyle != dynamicPseudo)
return SelectorFailsCompletely;
// Disable :visited matching when we see the first link or try to match anything else than an ancestors.
if (!context.isSubSelector && (context.element->isLink() || (relation != CSSSelector::Descendant && relation != CSSSelector::Child)))
nextContext.visitedMatchType = VisitedMatchDisabled;
nextContext.pseudoStyle = NOPSEUDO;
}
switch (relation) {
case CSSSelector::Descendant:
nextContext.element = context.element->parentElement();
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
nextContext.elementParentStyle = 0;
for (; nextContext.element; nextContext.element = nextContext.element->parentElement()) {
SelectorMatch match = checkSelector(nextContext, ignoreDynamicPseudo, hasUnknownPseudoElements);
if (match == SelectorMatches || match == SelectorFailsCompletely)
return match;
if (nextContext.element == nextContext.scope)
return SelectorFailsCompletely;
}
return SelectorFailsCompletely;
case CSSSelector::Child:
nextContext.element = context.element->parentElement();
if (!nextContext.element)
return SelectorFailsCompletely;
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
nextContext.elementParentStyle = 0;
return checkSelector(nextContext, ignoreDynamicPseudo, hasUnknownPseudoElements);
case CSSSelector::DirectAdjacent:
if (m_mode == ResolvingStyle && context.element->parentElement()) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : context.element->parentNode()->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByDirectAdjacentRules();
}
nextContext.element = context.element->previousElementSibling();
if (!nextContext.element)
return SelectorFailsAllSiblings;
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
nextContext.elementParentStyle = 0;
return checkSelector(nextContext, ignoreDynamicPseudo, hasUnknownPseudoElements);
case CSSSelector::IndirectAdjacent:
if (m_mode == ResolvingStyle && context.element->parentElement()) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : context.element->parentNode()->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByForwardPositionalRules();
}
nextContext.element = context.element->previousElementSibling();
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
nextContext.elementParentStyle = 0;
for (; nextContext.element; nextContext.element = nextContext.element->previousElementSibling()) {
SelectorMatch match = checkSelector(nextContext, ignoreDynamicPseudo, hasUnknownPseudoElements);
if (match == SelectorMatches || match == SelectorFailsAllSiblings || match == SelectorFailsCompletely)
return match;
};
return SelectorFailsAllSiblings;
case CSSSelector::SubSelector:
// a selector is invalid if something follows a pseudo-element
// We make an exception for scrollbar pseudo elements and allow a set of pseudo classes (but nothing else)
// to follow the pseudo elements.
nextContext.hasScrollbarPseudo = RenderScrollbar::scrollbarForStyleResolve() || dynamicPseudo == SCROLLBAR_CORNER || dynamicPseudo == RESIZER;
nextContext.hasSelectionPseudo = dynamicPseudo == SELECTION;
if ((context.elementStyle || m_mode == CollectingRules || m_mode == QueryingRules) && dynamicPseudo != NOPSEUDO
&& !nextContext.hasSelectionPseudo
&& !(nextContext.hasScrollbarPseudo && nextContext.selector->m_match == CSSSelector::PseudoClass))
return SelectorFailsCompletely;
nextContext.isSubSelector = true;
return checkSelector(nextContext, dynamicPseudo, hasUnknownPseudoElements);
case CSSSelector::ShadowDescendant:
{
// If we're in the same tree-scope as the scoping element, then following a shadow descendant combinator would escape that and thus the scope.
if (context.scope && context.scope->treeScope() == context.element->treeScope())
return SelectorFailsCompletely;
Element* shadowHostNode = context.element->shadowHost();
if (!shadowHostNode)
return SelectorFailsCompletely;
nextContext.element = shadowHostNode;
nextContext.isSubSelector = false;
nextContext.elementStyle = 0;
nextContext.elementParentStyle = 0;
return checkSelector(nextContext, ignoreDynamicPseudo, hasUnknownPseudoElements);
}
}
ASSERT_NOT_REACHED();
return SelectorFailsCompletely;
}
static void addLocalNameToSet(HashSet<AtomicStringImpl*>* set, const QualifiedName& qName)
{
set->add(qName.localName().impl());
}
static HashSet<AtomicStringImpl*>* createHtmlCaseInsensitiveAttributesSet()
{
// This is the list of attributes in HTML 4.01 with values marked as "[CI]" or case-insensitive
// Mozilla treats all other values as case-sensitive, thus so do we.
HashSet<AtomicStringImpl*>* attrSet = new HashSet<AtomicStringImpl*>;
addLocalNameToSet(attrSet, accept_charsetAttr);
addLocalNameToSet(attrSet, acceptAttr);
addLocalNameToSet(attrSet, alignAttr);
addLocalNameToSet(attrSet, alinkAttr);
addLocalNameToSet(attrSet, axisAttr);
addLocalNameToSet(attrSet, bgcolorAttr);
addLocalNameToSet(attrSet, charsetAttr);
addLocalNameToSet(attrSet, checkedAttr);
addLocalNameToSet(attrSet, clearAttr);
addLocalNameToSet(attrSet, codetypeAttr);
addLocalNameToSet(attrSet, colorAttr);
addLocalNameToSet(attrSet, compactAttr);
addLocalNameToSet(attrSet, declareAttr);
addLocalNameToSet(attrSet, deferAttr);
addLocalNameToSet(attrSet, dirAttr);
addLocalNameToSet(attrSet, disabledAttr);
addLocalNameToSet(attrSet, enctypeAttr);
addLocalNameToSet(attrSet, faceAttr);
addLocalNameToSet(attrSet, frameAttr);
addLocalNameToSet(attrSet, hreflangAttr);
addLocalNameToSet(attrSet, http_equivAttr);
addLocalNameToSet(attrSet, langAttr);
addLocalNameToSet(attrSet, languageAttr);
addLocalNameToSet(attrSet, linkAttr);
addLocalNameToSet(attrSet, mediaAttr);
addLocalNameToSet(attrSet, methodAttr);
addLocalNameToSet(attrSet, multipleAttr);
addLocalNameToSet(attrSet, nohrefAttr);
addLocalNameToSet(attrSet, noresizeAttr);
addLocalNameToSet(attrSet, noshadeAttr);
addLocalNameToSet(attrSet, nowrapAttr);
addLocalNameToSet(attrSet, readonlyAttr);
addLocalNameToSet(attrSet, relAttr);
addLocalNameToSet(attrSet, revAttr);
addLocalNameToSet(attrSet, rulesAttr);
addLocalNameToSet(attrSet, scopeAttr);
addLocalNameToSet(attrSet, scrollingAttr);
addLocalNameToSet(attrSet, selectedAttr);
addLocalNameToSet(attrSet, shapeAttr);
addLocalNameToSet(attrSet, targetAttr);
addLocalNameToSet(attrSet, textAttr);
addLocalNameToSet(attrSet, typeAttr);
addLocalNameToSet(attrSet, valignAttr);
addLocalNameToSet(attrSet, valuetypeAttr);
addLocalNameToSet(attrSet, vlinkAttr);
return attrSet;
}
bool htmlAttributeHasCaseInsensitiveValue(const QualifiedName& attr)
{
static HashSet<AtomicStringImpl*>* htmlCaseInsensitiveAttributesSet = createHtmlCaseInsensitiveAttributesSet();
bool isPossibleHTMLAttr = !attr.hasPrefix() && (attr.namespaceURI() == nullAtom);
return isPossibleHTMLAttr && htmlCaseInsensitiveAttributesSet->contains(attr.localName().impl());
}
static bool attributeValueMatches(const Attribute* attributeItem, CSSSelector::Match match, const AtomicString& selectorValue, bool caseSensitive)
{
const AtomicString& value = attributeItem->value();
if (value.isNull())
return false;
switch (match) {
case CSSSelector::Exact:
if (caseSensitive ? selectorValue != value : !equalIgnoringCase(selectorValue, value))
return false;
break;
case CSSSelector::List:
{
// Ignore empty selectors or selectors containing spaces
if (selectorValue.contains(' ') || selectorValue.isEmpty())
return false;
unsigned startSearchAt = 0;
while (true) {
size_t foundPos = value.find(selectorValue, startSearchAt, caseSensitive);
if (foundPos == notFound)
return false;
if (!foundPos || value[foundPos - 1] == ' ') {
unsigned endStr = foundPos + selectorValue.length();
if (endStr == value.length() || value[endStr] == ' ')
break; // We found a match.
}
// No match. Keep looking.
startSearchAt = foundPos + 1;
}
break;
}
case CSSSelector::Contain:
if (!value.contains(selectorValue, caseSensitive) || selectorValue.isEmpty())
return false;
break;
case CSSSelector::Begin:
if (!value.startsWith(selectorValue, caseSensitive) || selectorValue.isEmpty())
return false;
break;
case CSSSelector::End:
if (!value.endsWith(selectorValue, caseSensitive) || selectorValue.isEmpty())
return false;
break;
case CSSSelector::Hyphen:
if (value.length() < selectorValue.length())
return false;
if (!value.startsWith(selectorValue, caseSensitive))
return false;
// It they start the same, check for exact match or following '-':
if (value.length() != selectorValue.length() && value[selectorValue.length()] != '-')
return false;
break;
case CSSSelector::PseudoClass:
case CSSSelector::PseudoElement:
default:
break;
}
return true;
}
static bool anyAttributeMatches(Element* element, CSSSelector::Match match, const QualifiedName& selectorAttr, const AtomicString& selectorValue, bool caseSensitive)
{
ASSERT(element->hasAttributesWithoutUpdate());
for (size_t i = 0; i < element->attributeCount(); ++i) {
const Attribute* attributeItem = element->attributeItem(i);
if (!SelectorChecker::attributeNameMatches(attributeItem, selectorAttr))
continue;
if (attributeValueMatches(attributeItem, match, selectorValue, caseSensitive))
return true;
}
return false;
}
template<typename CheckingContext>
bool SelectorChecker::checkOneSelector(const CheckingContext& context) const
{
Element* const & element = context.element;
CSSSelector* const & selector = context.selector;
ASSERT(element);
ASSERT(selector);
if (!SelectorChecker::tagMatches(element, selector))
return false;
if (selector->m_match == CSSSelector::Class)
return element->hasClass() && static_cast<StyledElement*>(element)->classNames().contains(selector->value());
if (selector->m_match == CSSSelector::Id)
return element->hasID() && element->idForStyleResolution() == selector->value();
if (selector->isAttributeSelector()) {
const QualifiedName& attr = selector->attribute();
if (!element->hasAttributes())
return false;
bool caseSensitive = !m_documentIsHTML || !htmlAttributeHasCaseInsensitiveValue(attr);
if (!anyAttributeMatches(element, static_cast<CSSSelector::Match>(selector->m_match), attr, selector->value(), caseSensitive))
return false;
}
if (selector->m_match == CSSSelector::PseudoClass) {
// Handle :not up front.
if (selector->pseudoType() == CSSSelector::PseudoNot) {
CSSSelectorList* selectorList = selector->selectorList();
// FIXME: We probably should fix the parser and make it never produce :not rules with missing selector list.
if (!selectorList)
return false;
CheckingContext subContext(context);
subContext.isSubSelector = true;
for (subContext.selector = selectorList->first(); subContext.selector; subContext.selector = subContext.selector->tagHistory()) {
// :not cannot nest. I don't really know why this is a
// restriction in CSS3, but it is, so let's honor it.
// the parser enforces that this never occurs
ASSERT(subContext.selector->pseudoType() != CSSSelector::PseudoNot);
// We select between :visited and :link when applying. We don't know which one applied (or not) yet.
if (subContext.selector->pseudoType() == CSSSelector::PseudoVisited || (subContext.selector->pseudoType() == CSSSelector::PseudoLink && subContext.visitedMatchType == VisitedMatchEnabled))
return true;
if (!checkOneSelector(subContext))
return true;
}
} else if (context.hasScrollbarPseudo) {
// CSS scrollbars match a specific subset of pseudo classes, and they have specialized rules for each
// (since there are no elements involved).
return checkScrollbarPseudoClass(selector);
} else if (context.hasSelectionPseudo) {
if (selector->pseudoType() == CSSSelector::PseudoWindowInactive)
return !m_document->page()->focusController()->isActive();
}
// Normal element pseudo class checking.
switch (selector->pseudoType()) {
// Pseudo classes:
case CSSSelector::PseudoNot:
break; // Already handled up above.
case CSSSelector::PseudoEmpty:
{
bool result = true;
for (Node* n = element->firstChild(); n; n = n->nextSibling()) {
if (n->isElementNode()) {
result = false;
break;
}
if (n->isTextNode()) {
Text* textNode = toText(n);
if (!textNode->data().isEmpty()) {
result = false;
break;
}
}
}
if (m_mode == ResolvingStyle) {
if (context.elementStyle)
context.elementStyle->setEmptyState(result);
else if (element->renderStyle() && (element->document()->styleSheetCollection()->usesSiblingRules() || element->renderStyle()->unique()))
element->renderStyle()->setEmptyState(result);
}
return result;
}
case CSSSelector::PseudoFirstChild:
// first-child matches the first child that is an element
if (element->parentElement()) {
bool result = DOMTraversalStrategy<CheckingContext>::isFirstChild(context, element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle();
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : element->parentNode()->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByFirstChildRules();
if (result && childStyle)
childStyle->setFirstChildState();
}
return result;
}
break;
case CSSSelector::PseudoFirstOfType:
// first-of-type matches the first element of its type
if (element->parentElement()) {
bool result = DOMTraversalStrategy<CheckingContext>::isFirstOfType(context, element, element->tagQName());
if (m_mode == ResolvingStyle) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : element->parentNode()->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByForwardPositionalRules();
}
return result;
}
break;
case CSSSelector::PseudoLastChild:
// last-child matches the last child that is an element
if (Element* parentElement = element->parentElement()) {
bool result = parentElement->isFinishedParsingChildren() && DOMTraversalStrategy<CheckingContext>::isLastChild(context, element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle();
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByLastChildRules();
if (result && childStyle)
childStyle->setLastChildState();
}
return result;
}
break;
case CSSSelector::PseudoLastOfType:
// last-of-type matches the last element of its type
if (Element* parentElement = element->parentElement()) {
if (m_mode == ResolvingStyle) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByBackwardPositionalRules();
}
if (!parentElement->isFinishedParsingChildren())
return false;
return DOMTraversalStrategy<CheckingContext>::isLastOfType(context, element, element->tagQName());
}
break;
case CSSSelector::PseudoOnlyChild:
if (Element* parentElement = element->parentElement()) {
bool firstChild = DOMTraversalStrategy<CheckingContext>::isFirstChild(context, element);
bool onlyChild = firstChild && parentElement->isFinishedParsingChildren() && DOMTraversalStrategy<CheckingContext>::isLastChild(context, element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle();
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle) {
parentStyle->setChildrenAffectedByFirstChildRules();
parentStyle->setChildrenAffectedByLastChildRules();
}
if (firstChild && childStyle)
childStyle->setFirstChildState();
if (onlyChild && childStyle)
childStyle->setLastChildState();
}
return onlyChild;
}
break;
case CSSSelector::PseudoOnlyOfType:
// FIXME: This selector is very slow.
if (Element* parentElement = element->parentElement()) {
if (m_mode == ResolvingStyle) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle) {
parentStyle->setChildrenAffectedByForwardPositionalRules();
parentStyle->setChildrenAffectedByBackwardPositionalRules();
}
}
if (!parentElement->isFinishedParsingChildren())
return false;
return DOMTraversalStrategy<CheckingContext>::isFirstOfType(context, element, element->tagQName()) && DOMTraversalStrategy<CheckingContext>::isLastOfType(context, element, element->tagQName());
}
break;
case CSSSelector::PseudoNthChild:
if (!selector->parseNth())
break;
if (Element* parentElement = element->parentElement()) {
int count = 1 + DOMTraversalStrategy<CheckingContext>::countElementsBefore(context, element);
if (m_mode == ResolvingStyle) {
RenderStyle* childStyle = context.elementStyle ? context.elementStyle : element->renderStyle();
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (childStyle)
childStyle->setChildIndex(count);
if (parentStyle)
parentStyle->setChildrenAffectedByForwardPositionalRules();
}
if (selector->matchNth(count))
return true;
}
break;
case CSSSelector::PseudoNthOfType:
if (!selector->parseNth())
break;
if (Element* parentElement = element->parentElement()) {
int count = 1 + DOMTraversalStrategy<CheckingContext>::countElementsOfTypeBefore(context, element, element->tagQName());
if (m_mode == ResolvingStyle) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByForwardPositionalRules();
}
if (selector->matchNth(count))
return true;
}
break;
case CSSSelector::PseudoNthLastChild:
if (!selector->parseNth())
break;
if (Element* parentElement = element->parentElement()) {
if (m_mode == ResolvingStyle) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByBackwardPositionalRules();
}
if (!parentElement->isFinishedParsingChildren())
return false;
int count = 1 + DOMTraversalStrategy<CheckingContext>::countElementsAfter(context, element);
if (selector->matchNth(count))
return true;
}
break;
case CSSSelector::PseudoNthLastOfType:
if (!selector->parseNth())
break;
if (Element* parentElement = element->parentElement()) {
if (m_mode == ResolvingStyle) {
RenderStyle* parentStyle = context.elementStyle ? context.elementParentStyle : parentElement->renderStyle();
if (parentStyle)
parentStyle->setChildrenAffectedByBackwardPositionalRules();
}
if (!parentElement->isFinishedParsingChildren())
return false;
int count = 1 + DOMTraversalStrategy<CheckingContext>::countElementsOfTypeAfter(context, element, element->tagQName());
if (selector->matchNth(count))
return true;
}
break;
case CSSSelector::PseudoTarget:
if (element == element->document()->cssTarget())
return true;
break;
case CSSSelector::PseudoAny:
{
CheckingContext subContext(context);
subContext.isSubSelector = true;
bool hasUnknownPseudoElements = false;
PseudoId ignoreDynamicPseudo = NOPSEUDO;
for (subContext.selector = selector->selectorList()->first(); subContext.selector; subContext.selector = CSSSelectorList::next(subContext.selector)) {
if (checkSelector(subContext, ignoreDynamicPseudo, hasUnknownPseudoElements) == SelectorMatches)
return true;
}
}
break;
case CSSSelector::PseudoAutofill:
if (!element || !element->isFormControlElement())
break;
if (HTMLInputElement* inputElement = element->toInputElement())
return inputElement->isAutofilled();
break;
case CSSSelector::PseudoAnyLink:
case CSSSelector::PseudoLink:
// :visited and :link matches are separated later when applying the style. Here both classes match all links...
return element->isLink();
case CSSSelector::PseudoVisited:
// ...except if :visited matching is disabled for ancestor/sibling matching.
return element->isLink() && context.visitedMatchType == VisitedMatchEnabled;
case CSSSelector::PseudoDrag:
if (context.elementStyle)
context.elementStyle->setAffectedByDragRules(true);
else if (element->renderStyle())
element->renderStyle()->setAffectedByDragRules(true);
if (element->renderer() && element->renderer()->isDragging())
return true;
break;
case CSSSelector::PseudoFocus:
return matchesFocusPseudoClass(element);
case CSSSelector::PseudoHover:
// If we're in quirks mode, then hover should never match anchors with no
// href and *:hover should not match anything. This is important for sites like wsj.com.
if (m_strictParsing || context.isSubSelector || (selector->hasTag() && !element->hasTagName(aTag)) || element->isLink()) {
if (context.elementStyle)
context.elementStyle->setAffectedByHoverRules(true);
else if (element->renderStyle())
element->renderStyle()->setAffectedByHoverRules(true);
if (element->hovered() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoHover))
return true;
}
break;
case CSSSelector::PseudoActive:
// If we're in quirks mode, then :active should never match anchors with no
// href and *:active should not match anything.
if (m_strictParsing || context.isSubSelector || (selector->hasTag() && !element->hasTagName(aTag)) || element->isLink()) {
if (context.elementStyle)
context.elementStyle->setAffectedByActiveRules(true);
else if (element->renderStyle())
element->renderStyle()->setAffectedByActiveRules(true);
if (element->active() || InspectorInstrumentation::forcePseudoState(element, CSSSelector::PseudoActive))
return true;
}
break;
case CSSSelector::PseudoEnabled:
if (element && (element->isFormControlElement() || element->hasTagName(optionTag) || element->hasTagName(optgroupTag)))
return element->isEnabledFormControl();
break;
case CSSSelector::PseudoFullPageMedia:
return element && element->document() && element->document()->isMediaDocument();
break;
case CSSSelector::PseudoDefault:
return element && element->isDefaultButtonForForm();
case CSSSelector::PseudoDisabled:
if (element && (element->isFormControlElement() || element->hasTagName(optionTag) || element->hasTagName(optgroupTag)))
return !element->isEnabledFormControl();
break;
case CSSSelector::PseudoReadOnly:
if (!element || !element->isFormControlElement())
return false;
return element->isTextFormControl() && element->shouldMatchReadOnlySelector();
case CSSSelector::PseudoReadWrite:
if (!element || !element->isFormControlElement())
return false;
return element->isTextFormControl() && element->shouldMatchReadWriteSelector();
case CSSSelector::PseudoOptional:
return element && element->isOptionalFormControl();
case CSSSelector::PseudoRequired:
return element && element->isRequiredFormControl();
case CSSSelector::PseudoValid:
if (!element)
return false;
element->document()->setContainsValidityStyleRules();
return element->willValidate() && element->isValidFormControlElement();
case CSSSelector::PseudoInvalid:
if (!element)
return false;
element->document()->setContainsValidityStyleRules();
return (element->willValidate() && !element->isValidFormControlElement()) || element->hasUnacceptableValue();
case CSSSelector::PseudoChecked:
{
if (!element)
break;
// Even though WinIE allows checked and indeterminate to co-exist, the CSS selector spec says that
// you can't be both checked and indeterminate. We will behave like WinIE behind the scenes and just
// obey the CSS spec here in the test for matching the pseudo.
HTMLInputElement* inputElement = element->toInputElement();
if (inputElement && inputElement->shouldAppearChecked() && !inputElement->isIndeterminate())
return true;
if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected())
return true;
break;
}
case CSSSelector::PseudoIndeterminate:
{
if (!element)
break;
#if ENABLE(PROGRESS_ELEMENT)
if (element->hasTagName(progressTag)) {
HTMLProgressElement* progress = static_cast<HTMLProgressElement*>(element);
if (progress && !progress->isDeterminate())
return true;
break;
}
#endif
HTMLInputElement* inputElement = element->toInputElement();
if (inputElement && inputElement->isIndeterminate())
return true;
break;
}
case CSSSelector::PseudoRoot:
if (element == element->document()->documentElement())
return true;
break;
case CSSSelector::PseudoLang:
{
AtomicString value = element->computeInheritedLanguage();
const AtomicString& argument = selector->argument();
if (value.isEmpty() || !value.startsWith(argument, false))
break;
if (value.length() != argument.length() && value[argument.length()] != '-')
break;
return true;
}
#if ENABLE(FULLSCREEN_API)
case CSSSelector::PseudoFullScreen:
// While a Document is in the fullscreen state, and the document's current fullscreen
// element is an element in the document, the 'full-screen' pseudoclass applies to
// that element. Also, an <iframe>, <object> or <embed> element whose child browsing
// context's Document is in the fullscreen state has the 'full-screen' pseudoclass applied.
if (element->isFrameElementBase() && static_cast<HTMLFrameElementBase*>(element)->containsFullScreenElement())
return true;
if (!element->document()->webkitIsFullScreen())
return false;
return element == element->document()->webkitCurrentFullScreenElement();
case CSSSelector::PseudoAnimatingFullScreenTransition:
if (element != element->document()->webkitCurrentFullScreenElement())
return false;
return element->document()->isAnimatingFullScreen();
case CSSSelector::PseudoFullScreenAncestor:
return element->containsFullScreenElement();
case CSSSelector::PseudoFullScreenDocument:
// While a Document is in the fullscreen state, the 'full-screen-document' pseudoclass applies
// to all elements of that Document.
if (!element->document()->webkitIsFullScreen())
return false;
return true;
#endif
case CSSSelector::PseudoInRange:
if (!element)
return false;
element->document()->setContainsValidityStyleRules();
return element->isInRange();
case CSSSelector::PseudoOutOfRange:
if (!element)
return false;
element->document()->setContainsValidityStyleRules();
return element->isOutOfRange();
case CSSSelector::PseudoUnknown:
case CSSSelector::PseudoNotParsed:
default:
ASSERT_NOT_REACHED();
break;
}
return false;
}
// ### add the rest of the checks...
return true;
}
bool SelectorChecker::checkScrollbarPseudoClass(CSSSelector* sel) const
{
RenderScrollbar* scrollbar = RenderScrollbar::scrollbarForStyleResolve();
ScrollbarPart part = RenderScrollbar::partForStyleResolve();
// FIXME: This is a temporary hack for resizers and scrollbar corners. Eventually :window-inactive should become a real
// pseudo class and just apply to everything.
if (sel->pseudoType() == CSSSelector::PseudoWindowInactive)
return !m_document->page()->focusController()->isActive();
if (!scrollbar)
return false;
ASSERT(sel->m_match == CSSSelector::PseudoClass);
switch (sel->pseudoType()) {
case CSSSelector::PseudoEnabled:
return scrollbar->enabled();
case CSSSelector::PseudoDisabled:
return !scrollbar->enabled();
case CSSSelector::PseudoHover:
{
ScrollbarPart hoveredPart = scrollbar->hoveredPart();
if (part == ScrollbarBGPart)
return hoveredPart != NoPart;
if (part == TrackBGPart)
return hoveredPart == BackTrackPart || hoveredPart == ForwardTrackPart || hoveredPart == ThumbPart;
return part == hoveredPart;
}
case CSSSelector::PseudoActive:
{
ScrollbarPart pressedPart = scrollbar->pressedPart();
if (part == ScrollbarBGPart)
return pressedPart != NoPart;
if (part == TrackBGPart)
return pressedPart == BackTrackPart || pressedPart == ForwardTrackPart || pressedPart == ThumbPart;
return part == pressedPart;
}
case CSSSelector::PseudoHorizontal:
return scrollbar->orientation() == HorizontalScrollbar;
case CSSSelector::PseudoVertical:
return scrollbar->orientation() == VerticalScrollbar;
case CSSSelector::PseudoDecrement:
return part == BackButtonStartPart || part == BackButtonEndPart || part == BackTrackPart;
case CSSSelector::PseudoIncrement:
return part == ForwardButtonStartPart || part == ForwardButtonEndPart || part == ForwardTrackPart;
case CSSSelector::PseudoStart:
return part == BackButtonStartPart || part == ForwardButtonStartPart || part == BackTrackPart;
case CSSSelector::PseudoEnd:
return part == BackButtonEndPart || part == ForwardButtonEndPart || part == ForwardTrackPart;
case CSSSelector::PseudoDoubleButton:
{
ScrollbarButtonsPlacement buttonsPlacement = scrollbar->theme()->buttonsPlacement();
if (part == BackButtonStartPart || part == ForwardButtonStartPart || part == BackTrackPart)
return buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth;
if (part == BackButtonEndPart || part == ForwardButtonEndPart || part == ForwardTrackPart)
return buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth;
return false;
}
case CSSSelector::PseudoSingleButton:
{
ScrollbarButtonsPlacement buttonsPlacement = scrollbar->theme()->buttonsPlacement();
if (part == BackButtonStartPart || part == ForwardButtonEndPart || part == BackTrackPart || part == ForwardTrackPart)
return buttonsPlacement == ScrollbarButtonsSingle;
return false;
}
case CSSSelector::PseudoNoButton:
{
ScrollbarButtonsPlacement buttonsPlacement = scrollbar->theme()->buttonsPlacement();
if (part == BackTrackPart)
return buttonsPlacement == ScrollbarButtonsNone || buttonsPlacement == ScrollbarButtonsDoubleEnd;
if (part == ForwardTrackPart)
return buttonsPlacement == ScrollbarButtonsNone || buttonsPlacement == ScrollbarButtonsDoubleStart;
return false;
}
case CSSSelector::PseudoCornerPresent:
return scrollbar->scrollableArea()->isScrollCornerVisible();
default:
return false;
}
}
void SelectorChecker::allVisitedStateChanged()
{
if (m_linksCheckedForVisitedState.isEmpty())
return;
for (Node* node = m_document; node; node = node->traverseNextNode()) {
if (node->isLink())
node->setNeedsStyleRecalc();
}
}
void SelectorChecker::visitedStateChanged(LinkHash visitedHash)
{
if (!m_linksCheckedForVisitedState.contains(visitedHash))
return;
for (Node* node = m_document; node; node = node->traverseNextNode()) {
LinkHash hash = 0;
if (node->hasTagName(aTag))
hash = static_cast<HTMLAnchorElement*>(node)->visitedLinkHash();
else if (const AtomicString* attr = linkAttribute(node))
hash = visitedLinkHash(m_document->baseURL(), *attr);
if (hash == visitedHash)
node->setNeedsStyleRecalc();
}
}
bool SelectorChecker::commonPseudoClassSelectorMatches(const Element* element, const CSSSelector* selector, VisitedMatchType visitedMatchType) const
{
ASSERT(isCommonPseudoClassSelector(selector));
switch (selector->pseudoType()) {
case CSSSelector::PseudoLink:
case CSSSelector::PseudoAnyLink:
return element->isLink();
case CSSSelector::PseudoVisited:
return element->isLink() && visitedMatchType == VisitedMatchEnabled;
case CSSSelector::PseudoFocus:
return matchesFocusPseudoClass(element);
default:
ASSERT_NOT_REACHED();
}
return true;
}
unsigned SelectorChecker::determineLinkMatchType(const CSSSelector* selector)
{
unsigned linkMatchType = MatchAll;
// Statically determine if this selector will match a link in visited, unvisited or any state, or never.
// :visited never matches other elements than the innermost link element.
for (; selector; selector = selector->tagHistory()) {
switch (selector->pseudoType()) {
case CSSSelector::PseudoNot:
{
// :not(:visited) is equivalent to :link. Parser enforces that :not can't nest.
CSSSelectorList* selectorList = selector->selectorList();
if (!selectorList)
break;
for (CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = subSelector->tagHistory()) {
CSSSelector::PseudoType subType = subSelector->pseudoType();
if (subType == CSSSelector::PseudoVisited)
linkMatchType &= ~SelectorChecker::MatchVisited;
else if (subType == CSSSelector::PseudoLink)
linkMatchType &= ~SelectorChecker::MatchLink;
}
}
break;
case CSSSelector::PseudoLink:
linkMatchType &= ~SelectorChecker::MatchVisited;
break;
case CSSSelector::PseudoVisited:
linkMatchType &= ~SelectorChecker::MatchLink;
break;
default:
// We don't support :link and :visited inside :-webkit-any.
break;
}
CSSSelector::Relation relation = selector->relation();
if (relation == CSSSelector::SubSelector)
continue;
if (relation != CSSSelector::Descendant && relation != CSSSelector::Child)
return linkMatchType;
if (linkMatchType != MatchAll)
return linkMatchType;
}
return linkMatchType;
}
bool SelectorChecker::isFrameFocused(const Element* element)
{
return element->document()->frame() && element->document()->frame()->selection()->isFocusedAndActive();
}
bool SelectorChecker::determineSelectorScopes(const CSSSelectorList& selectorList, HashSet<AtomicStringImpl*>& idScopes, HashSet<AtomicStringImpl*>& classScopes)
{
for (CSSSelector* selector = selectorList.first(); selector; selector = CSSSelectorList::next(selector)) {
CSSSelector* scopeSelector = 0;
// This picks the widest scope, not the narrowest, to minimize the number of found scopes.
for (CSSSelector* current = selector; current; current = current->tagHistory()) {
// Prefer ids over classes.
if (current->m_match == CSSSelector::Id)
scopeSelector = current;
else if (current->m_match == CSSSelector::Class && (!scopeSelector || scopeSelector->m_match != CSSSelector::Id))
scopeSelector = current;
CSSSelector::Relation relation = current->relation();
if (relation != CSSSelector::Descendant && relation != CSSSelector::Child && relation != CSSSelector::SubSelector)
break;
}
if (!scopeSelector)
return false;
ASSERT(scopeSelector->m_match == CSSSelector::Class || scopeSelector->m_match == CSSSelector::Id);
if (scopeSelector->m_match == CSSSelector::Id)
idScopes.add(scopeSelector->value().impl());
else
classScopes.add(scopeSelector->value().impl());
}
return true;
}
template<>
inline bool SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::isFirstChild(const SelectorChecker::SelectorCheckingContext&, Element* element)
{
return !element->previousElementSibling();
}
template<>
inline bool SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::isLastChild(const SelectorChecker::SelectorCheckingContext&, Element* element)
{
return !element->nextElementSibling();
}
template<>
inline bool SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::isFirstOfType(const SelectorChecker::SelectorCheckingContext&, Element* element, const QualifiedName& type)
{
for (const Element* sibling = element->previousElementSibling(); sibling; sibling = sibling->previousElementSibling()) {
if (sibling->hasTagName(type))
return false;
}
return true;
}
template<>
inline bool SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::isLastOfType(const SelectorChecker::SelectorCheckingContext&, Element* element, const QualifiedName& type)
{
for (const Element* sibling = element->nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
if (sibling->hasTagName(type))
return false;
}
return true;
}
template<>
inline int SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::countElementsBefore(const SelectorChecker::SelectorCheckingContext&, Element* element)
{
int count = 0;
for (const Element* sibling = element->previousElementSibling(); sibling; sibling = sibling->previousElementSibling()) {
RenderStyle* s = sibling->renderStyle();
unsigned index = s ? s->childIndex() : 0;
if (index) {
count += index;
break;
}
count++;
}
return count;
}
template<>
inline int SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::countElementsOfTypeBefore(const SelectorChecker::SelectorCheckingContext&, Element* element, const QualifiedName& type)
{
int count = 0;
for (const Element* sibling = element->previousElementSibling(); sibling; sibling = sibling->previousElementSibling()) {
if (sibling->hasTagName(type))
++count;
}
return count;
}
template<>
inline int SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::countElementsAfter(const SelectorChecker::SelectorCheckingContext&, Element* element)
{
int count = 0;
for (const Element* sibling = element->nextElementSibling(); sibling; sibling = sibling->nextElementSibling())
++count;
return count;
}
template<>
inline int SelectorChecker::DOMTraversalStrategy<SelectorChecker::SelectorCheckingContext>::countElementsOfTypeAfter(const SelectorChecker::SelectorCheckingContext&, Element* element, const QualifiedName& type)
{
int count = 0;
for (const Element* sibling = element->nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
if (sibling->hasTagName(type))
++count;
}
return count;
}
template
SelectorChecker::SelectorMatch SelectorChecker::checkSelector<SelectorChecker::SelectorCheckingContext>(const SelectorCheckingContext&, PseudoId&, bool&) const;
}