/*
 * 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 "ElementTraversal.h"
#include "FocusController.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "HTMLDocument.h"
#include "HTMLFrameElementBase.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLOptionElement.h"
#include "HTMLParserIdioms.h"
#include "HTMLProgressElement.h"
#include "HTMLStyleElement.h"
#include "InspectorInstrumentation.h"
#include "Page.h"
#include "RenderElement.h"
#include "RenderScrollbar.h"
#include "ScrollableArea.h"
#include "ScrollbarTheme.h"
#include "ShadowRoot.h"
#include "StyledElement.h"
#include "Text.h"

#if ENABLE(VIDEO_TRACK)
#include "WebVTTElement.h"
#endif

namespace WebCore {

using namespace HTMLNames;

enum class VisitedMatchType : unsigned char {
    Disabled, Enabled
};

struct SelectorChecker::LocalContext {
    LocalContext(const CSSSelector& selector, const Element& element, VisitedMatchType visitedMatchType, PseudoId pseudoId)
        : selector(&selector)
        , element(&element)
        , visitedMatchType(visitedMatchType)
        , firstSelectorOfTheFragment(&selector)
        , pseudoId(pseudoId)
		, isMatchElement(true)
        , inFunctionalPseudoClass(false)
    	, pseudoElementEffective(true)
        , hasScrollbarPseudo(false)
        , hasSelectionPseudo(false)
    { }

    const CSSSelector* selector;
    const Element* element;
    VisitedMatchType visitedMatchType;
    const CSSSelector* firstSelectorOfTheFragment;
    PseudoId pseudoId;
    bool isMatchElement;
    bool inFunctionalPseudoClass;
    bool pseudoElementEffective;
    bool hasScrollbarPseudo;
    bool hasSelectionPseudo;
};

static inline void addStyleRelation(SelectorChecker::CheckingContext& checkingContext, const Element& element, SelectorChecker::StyleRelation::Type type, unsigned value = 1)
{
    ASSERT(value == 1 || type == SelectorChecker::StyleRelation::NthChildIndex || type == SelectorChecker::StyleRelation::AffectedByEmpty);
    if (checkingContext.resolvingMode != SelectorChecker::Mode::ResolvingStyle)
        return;
    checkingContext.styleRelations.append(SelectorChecker::StyleRelation(const_cast<Element&>(element), type, value));
}

static ALWAYS_INLINE bool isFirstChildElement(const Element* element)
{
    return !element->previousElementSibling();
}

static ALWAYS_INLINE bool isLastChildElement(const Element* element)
{
    return !element->nextElementSibling();
}

static inline bool isFirstOfType(SelectorChecker::CheckingContext& checkingContext, const Element& element, const QualifiedName& type)
{
    for (const Element* sibling = ElementTraversal::previousSibling(&element); sibling; sibling = ElementTraversal::previousSibling(sibling)) {
        addStyleRelation(checkingContext, *sibling, SelectorChecker::StyleRelation::AffectsNextSibling);
        if (sibling->hasTagName(type))
            return false;
    }
    return true;
}

static inline bool isLastOfType(const Element* element, const QualifiedName& type)
{
    for (const Element* sibling = element->nextElementSibling(); sibling; sibling = sibling->nextElementSibling()) {
        if (sibling->hasTagName(type))
            return false;
    }
    return true;
}

static inline int countElementsBefore(SelectorChecker::CheckingContext& checkingContext, const Element& element)
{
    int count = 0;
    for (const Element* sibling = ElementTraversal::previousSibling(&element); sibling; sibling = ElementTraversal::previousSibling(sibling)) {

        addStyleRelation(checkingContext, *sibling, SelectorChecker::StyleRelation::AffectsNextSibling);

        unsigned index = sibling->childIndex();
        if (index) {
            count += index;
            break;
        }
        count++;
    }
    return count;
}

static inline int countElementsOfTypeBefore(SelectorChecker::CheckingContext& checkingContext, const Element& element, const QualifiedName& type)
{
    int count = 0;
    for (const Element* sibling = ElementTraversal::previousSibling(&element); sibling; sibling = ElementTraversal::previousSibling(sibling)) {
        addStyleRelation(checkingContext, *sibling, SelectorChecker::StyleRelation::AffectsNextSibling);

        if (sibling->hasTagName(type))
            ++count;
    }
    return count;
}

static inline int countElementsAfter(const Element* element)
{
    int count = 0;
    for (const Element* sibling = element->nextElementSibling(); sibling; sibling = sibling->nextElementSibling())
        ++count;
    return count;
}

static inline int countElementsOfTypeAfter(const 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;
}

SelectorChecker::SelectorChecker(Document* document)
	: m_strictParsing(!document->inQuirksMode())
    , m_documentIsHTML(document->isHTMLDocument())
{
}

bool SelectorChecker::match(const CSSSelector& selector, const Element& element, CheckingContext& checkingContext, unsigned& specificity) const
{
    specificity = 0;

    LocalContext context(selector, element, checkingContext.resolvingMode == SelectorChecker::Mode::QueryingRules ? VisitedMatchType::Disabled : VisitedMatchType::Enabled, checkingContext.pseudoId);
    PseudoIdSet pseudoIdSet;
    MatchResult result = matchRecursively(checkingContext, context, pseudoIdSet, specificity);
    if (result.match != Match::SelectorMatches)
        return false;
    if (checkingContext.pseudoId != NOPSEUDO && !pseudoIdSet.has(checkingContext.pseudoId))
        return false;

    if (checkingContext.pseudoId == NOPSEUDO && pseudoIdSet) {
        PseudoIdSet publicPseudoIdSet = pseudoIdSet & PseudoIdSet::fromMask(PUBLIC_PSEUDOID_MASK);
        if (checkingContext.resolvingMode == Mode::ResolvingStyle && publicPseudoIdSet)
            checkingContext.pseudoIDSet = publicPseudoIdSet;

        // When ignoring virtual pseudo elements, the context's pseudo should also be NOPSEUDO but that does
        // not cause a failure.
        return checkingContext.resolvingMode == Mode::CollectingRulesIgnoringVirtualPseudoElements || result.matchType == MatchType::Element;
    }
    return true;
}

inline static bool hasScrollbarPseudoElement(const PseudoIdSet& dynamicPseudoIdSet)
{
    PseudoIdSet scrollbarIdSet;

    scrollbarIdSet.add(SCROLLBAR);
    scrollbarIdSet.add(SCROLLBAR_THUMB);
    scrollbarIdSet.add(SCROLLBAR_BUTTON);
    scrollbarIdSet.add(SCROLLBAR_TRACK);
    scrollbarIdSet.add(SCROLLBAR_TRACK_PIECE);
    scrollbarIdSet.add(SCROLLBAR_CORNER);

    if (dynamicPseudoIdSet & scrollbarIdSet)
        return true;

    // RESIZER does not always have a scrollbar but it is a scrollbar-like pseudo element
    // because it can have more than one pseudo element.
    return dynamicPseudoIdSet.has(RESIZER);
}

static SelectorChecker::LocalContext localContextForParent(const SelectorChecker::LocalContext& context)
{
    SelectorChecker::LocalContext updatedContext(context);
    // Disable :visited matching when we see the first link.
    if (context.element->isLink())
        updatedContext.visitedMatchType = VisitedMatchType::Disabled;
    updatedContext.element = context.element->parentElement();
    updatedContext.isMatchElement = false;
    return updatedContext;
}

// 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
SelectorChecker::MatchResult SelectorChecker::matchRecursively(CheckingContext& checkingContext, const LocalContext& context, PseudoIdSet& dynamicPseudoIdSet, unsigned& specificity) const
{
    MatchType matchType = MatchType::Element;

    // The first selector has to match.
    if (!checkOne(checkingContext, context, dynamicPseudoIdSet, matchType, specificity))
        return MatchResult::fails(Match::SelectorFailsLocally);

    if (context.selector->match() == CSSSelector::PseudoElement) {
        if (context.selector->isCustomPseudoElement()) {
            // In functional pseudo class, custom pseudo elements are always disabled.
            // FIXME: We should accept custom pseudo elements inside :matches().
            if (context.inFunctionalPseudoClass)
                return MatchResult::fails(Match::SelectorFailsCompletely);
            if (ShadowRoot* root = context.element->containingShadowRoot()) {
                if (context.element->shadowPseudoId() != context.selector->value())
                    return MatchResult::fails(Match::SelectorFailsLocally);

                if (context.selector->pseudoElementType() == CSSSelector::PseudoElementWebKitCustom && root->type() != ShadowRoot::Type::UserAgent)
                    return MatchResult::fails(Match::SelectorFailsLocally);
            } else
                return MatchResult::fails(Match::SelectorFailsLocally);
        } else {
            if (!context.pseudoElementEffective)
                return MatchResult::fails(Match::SelectorFailsCompletely);

            if (checkingContext.resolvingMode == Mode::QueryingRules)
                return MatchResult::fails(Match::SelectorFailsCompletely);

            PseudoId pseudoId = CSSSelector::pseudoId(context.selector->pseudoElementType());
            if (pseudoId != NOPSEUDO)
                dynamicPseudoIdSet.add(pseudoId);
            matchType = MatchType::VirtualPseudoElementOnly;
        }
    }

    // The rest of the selectors has to match
    CSSSelector::Relation relation = context.selector->relation();

    // Prepare next selector
    const CSSSelector* historySelector = context.selector->tagHistory();
    if (!historySelector)
        return MatchResult::matches(matchType);

    LocalContext nextContext(context);
    nextContext.selector = historySelector;

    if (relation != CSSSelector::SubSelector) {
        // Bail-out if this selector is irrelevant for the pseudoId
        if (context.pseudoId != NOPSEUDO && !dynamicPseudoIdSet.has(context.pseudoId))
            return MatchResult::fails(Match::SelectorFailsCompletely);

        // Disable :visited matching when we try to match anything else than an ancestors.
        if (relation != CSSSelector::Descendant && relation != CSSSelector::Child)
            nextContext.visitedMatchType = VisitedMatchType::Disabled;

        nextContext.pseudoId = NOPSEUDO;
        // Virtual pseudo element is only effective in the rightmost fragment.
        nextContext.pseudoElementEffective = false;
        nextContext.isMatchElement = false;
    }

    switch (relation) {
    case CSSSelector::Descendant:
        nextContext = localContextForParent(nextContext);
        nextContext.firstSelectorOfTheFragment = nextContext.selector;
        for (; nextContext.element; nextContext = localContextForParent(nextContext)) {
        	PseudoIdSet ignoreDynamicPseudo;
            unsigned descendantsSpecificity = 0;
			MatchResult result = matchRecursively(checkingContext, nextContext, ignoreDynamicPseudo, descendantsSpecificity);
			ASSERT(!nextContext.pseudoElementEffective && !ignoreDynamicPseudo);

            if (result.match == Match::SelectorMatches)
                specificity = CSSSelector::addSpecificities(specificity, descendantsSpecificity);

			if (result.match == Match::SelectorMatches || result.match == Match::SelectorFailsCompletely)
				return MatchResult::updateWithMatchType(result, matchType);
		}
		return MatchResult::fails(Match::SelectorFailsCompletely);

    case CSSSelector::Child:
    {
        nextContext = localContextForParent(nextContext);
        if (!nextContext.element)
            return MatchResult::fails(Match::SelectorFailsCompletely);
        nextContext.firstSelectorOfTheFragment = nextContext.selector;
        PseudoIdSet ignoreDynamicPseudo;
        unsigned childSpecificity = 0;
        MatchResult result = matchRecursively(checkingContext, nextContext, ignoreDynamicPseudo, childSpecificity);
        ASSERT(!nextContext.pseudoElementEffective && !ignoreDynamicPseudo);

        if (result.match == Match::SelectorMatches)
            specificity = CSSSelector::addSpecificities(specificity, childSpecificity);

        if (result.match == Match::SelectorMatches || result.match == Match::SelectorFailsCompletely)
            return MatchResult::updateWithMatchType(result, matchType);
        return MatchResult::fails(Match::SelectorFailsAllSiblings);
    }

    case CSSSelector::DirectAdjacent:
    {
        addStyleRelation(checkingContext, *context.element, StyleRelation::AffectedByPreviousSibling);

        Element* previousElement = context.element->previousElementSibling();
        if (!previousElement)
            return MatchResult::fails(Match::SelectorFailsAllSiblings);

        addStyleRelation(checkingContext, *previousElement, StyleRelation::AffectsNextSibling);

        nextContext.element = previousElement;
        nextContext.firstSelectorOfTheFragment = nextContext.selector;
        PseudoIdSet ignoreDynamicPseudo;
        unsigned adjacentSpecificity = 0;
        MatchResult result = matchRecursively(checkingContext, nextContext, ignoreDynamicPseudo, adjacentSpecificity);
        ASSERT(!nextContext.pseudoElementEffective && !ignoreDynamicPseudo);

        if (result.match == Match::SelectorMatches)
            specificity = CSSSelector::addSpecificities(specificity, adjacentSpecificity);

        return MatchResult::updateWithMatchType(result, matchType);
    }
    case CSSSelector::IndirectAdjacent:
        addStyleRelation(checkingContext, *context.element, StyleRelation::AffectedByPreviousSibling);

        nextContext.element = context.element->previousElementSibling();
        nextContext.firstSelectorOfTheFragment = nextContext.selector;
        for (; nextContext.element; nextContext.element = nextContext.element->previousElementSibling()) {
        	addStyleRelation(checkingContext, *nextContext.element, StyleRelation::AffectsNextSibling);

            PseudoIdSet ignoreDynamicPseudo;
            unsigned indirectAdjacentSpecificity = 0;
            MatchResult result = matchRecursively(checkingContext, nextContext, ignoreDynamicPseudo, indirectAdjacentSpecificity);
            ASSERT(!nextContext.pseudoElementEffective && !ignoreDynamicPseudo);

            if (result.match == Match::SelectorMatches)
                specificity = CSSSelector::addSpecificities(specificity, indirectAdjacentSpecificity);

            if (result.match == Match::SelectorMatches || result.match == Match::SelectorFailsAllSiblings || result.match == Match::SelectorFailsCompletely)
                return MatchResult::updateWithMatchType(result, matchType);
        };
        return MatchResult::fails(Match::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 = hasScrollbarPseudoElement(dynamicPseudoIdSet);
        nextContext.hasSelectionPseudo = dynamicPseudoIdSet.has(SELECTION);
        if ((context.isMatchElement || checkingContext.resolvingMode == Mode::CollectingRules) && dynamicPseudoIdSet
            && !nextContext.hasSelectionPseudo
            && !(nextContext.hasScrollbarPseudo && nextContext.selector->match() == CSSSelector::PseudoClass))
            return MatchResult::fails(Match::SelectorFailsCompletely);

        unsigned subselectorSpecificity = 0;
        MatchResult result = matchRecursively(checkingContext, nextContext, dynamicPseudoIdSet, subselectorSpecificity);

        if (result.match == Match::SelectorMatches)
            specificity = CSSSelector::addSpecificities(specificity, subselectorSpecificity);

        return MatchResult::updateWithMatchType(result, matchType);
    }

    case CSSSelector::ShadowDescendant:
        {
            Element* shadowHostNode = context.element->shadowHost();
            if (!shadowHostNode)
                return MatchResult::fails(Match::SelectorFailsCompletely);
            nextContext.element = shadowHostNode;
            nextContext.firstSelectorOfTheFragment = nextContext.selector;
            PseudoIdSet ignoreDynamicPseudo;
            unsigned shadowDescendantSpecificity = 0;
            MatchResult result = matchRecursively(checkingContext, nextContext, ignoreDynamicPseudo, shadowDescendantSpecificity);

            if (result.match == Match::SelectorMatches)
                specificity = CSSSelector::addSpecificities(specificity, shadowDescendantSpecificity);

            return MatchResult::updateWithMatchType(result, matchType);
        }
    }

    ASSERT_NOT_REACHED();
    return MatchResult::fails(Match::SelectorFailsCompletely);
}

static bool attributeValueMatches(const Attribute& attribute, CSSSelector::Match match, const AtomicString& selectorValue, bool caseSensitive)
{
    const AtomicString& value = attribute.value();
    if (value.isNull())
        return false;

    switch (match) {
    case CSSSelector::Exact:
        if (caseSensitive ? selectorValue != value : !equalIgnoringASCIICase(selectorValue, value))
            return false;
        break;
    case CSSSelector::List:
        {
            // Ignore empty selectors or selectors containing spaces.
            if (selectorValue.isEmpty() || selectorValue.find(isHTMLSpace<UChar>) != notFound)
                return false;

            unsigned startSearchAt = 0;
            while (true) {
                size_t foundPos = value.find(selectorValue, startSearchAt, caseSensitive);
                if (foundPos == notFound)
                    return false;
                if (!foundPos || isHTMLSpace(value[foundPos - 1])) {
                    unsigned endStr = foundPos + selectorValue.length();
                    if (endStr == value.length() || isHTMLSpace(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 (selectorValue.isEmpty())
            return false;
        if (caseSensitive) {
            if (!value.startsWith(selectorValue))
                return false;
        } else {
            if (!value.startsWithIgnoringASCIICase(selectorValue))
                return false;
        }
        break;
    case CSSSelector::End:
        if (selectorValue.isEmpty())
            return false;
        if (caseSensitive) {
            if (!value.endsWith(selectorValue))
                return false;
        } else {
            if (!value.endsWithIgnoringASCIICase(selectorValue))
                return false;
        }
        break;
    case CSSSelector::Hyphen:
        if (value.length() < selectorValue.length())
            return false;
        if (caseSensitive) {
            if (!value.startsWith(selectorValue))
                return false;
        } else {
            if (!value.startsWithIgnoringASCIICase(selectorValue))
                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(const Element* element, const CSSSelector* selector, const QualifiedName& selectorAttr, bool caseSensitive)
{
    ASSERT(element->hasAttributesWithoutUpdate());
    for (unsigned i = 0, count = element->attributeCount(); i < count; ++i) {
        const Attribute& attribute = element->attributeAt(i);

        if (!attribute.matches(selectorAttr.prefix(), element->isHTMLElement() ? selector->attributeCanonicalLocalName() : selectorAttr.localName(), selectorAttr.namespaceURI()))
            continue;

        if (attributeValueMatches(attribute, static_cast<CSSSelector::Match>(selector->m_match), selector->value(), caseSensitive))
            return true;
    }

    return false;
}

bool SelectorChecker::attributeSelectorMatches(const Element& element, const QualifiedName& attributeName, const AtomicString& attributeValue, const CSSSelector& selector)
{
    ASSERT(selector.isAttributeSelector());
    auto& selectorAttribute = selector.attribute();
    auto& selectorName = element.isHTMLElement() ? selector.attributeCanonicalLocalName() : selectorAttribute.localName();
    if (!Attribute::nameMatchesFilter(attributeName, selectorAttribute.prefix(), selectorName, selectorAttribute.namespaceURI()))
        return false;
    bool caseSensitive = true;
    if (selector.attributeValueMatchingIsCaseInsensitive())
        caseSensitive = false;
    else if (element.document()->isHTMLDocument() && element.isHTMLElement() && !HTMLDocument::isCaseSensitiveAttribute(selector.attribute()))
        caseSensitive = false;
    return attributeValueMatches(Attribute(attributeName, attributeValue), selector.match(), selector.value(), caseSensitive);
}

static bool canMatchHoverOrActiveInQuirksMode(const SelectorChecker::LocalContext& context)
{
    // For quirks mode, follow this: http://quirks.spec.whatwg.org/#the-:active-and-:hover-quirk
    // In quirks mode, a compound selector 'selector' that matches the following conditions must not match elements that would not also match the ':any-link' selector.
    //
    //    selector uses the ':active' or ':hover' pseudo-classes.
    //    selector does not use a type selector.
    //    selector does not use an attribute selector.
    //    selector does not use an ID selector.
    //    selector does not use a class selector.
    //    selector does not use a pseudo-class selector other than ':active' and ':hover'.
    //    selector does not use a pseudo-element selector.
    //    selector is not part of an argument to a functional pseudo-class or pseudo-element.
    if (context.inFunctionalPseudoClass)
        return true;

    for (const CSSSelector* selector = context.firstSelectorOfTheFragment; selector; selector = selector->tagHistory()) {
        switch (selector->m_match) {
        case CSSSelector::Tag:
            if (selector->tagQName() != anyQName())
                return true;
            break;
        case CSSSelector::PseudoClass: {
            CSSSelector::PseudoClassType pseudoClassType = selector->pseudoClassType();
            if (pseudoClassType != CSSSelector::PseudoClassHover && pseudoClassType != CSSSelector::PseudoClassActive)
                return true;
            break;
        }
        case CSSSelector::Id:
        case CSSSelector::Class:
        case CSSSelector::Exact:
        case CSSSelector::Set:
        case CSSSelector::List:
        case CSSSelector::Hyphen:
        case CSSSelector::Contain:
        case CSSSelector::Begin:
        case CSSSelector::End:
        case CSSSelector::PagePseudoClass:
        case CSSSelector::PseudoElement:
            return true;
        case CSSSelector::Unknown:
            ASSERT_NOT_REACHED();
            break;
        }

        CSSSelector::Relation relation = selector->relation();
        if (relation == CSSSelector::ShadowDescendant)
            return true;

        if (relation != CSSSelector::SubSelector)
            return false;
    }
    return false;
}

bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalContext& context, PseudoIdSet& dynamicPseudoIdSet, MatchType& matchType, unsigned& specificity) const
{
    const Element* element = context.element;
    const CSSSelector* const & selector = context.selector;
    ASSERT(element);
    ASSERT(selector);

    specificity = CSSSelector::addSpecificities(specificity, selector->simpleSelectorSpecificity());

    if (selector->m_match == CSSSelector::Tag)
        return SelectorChecker::tagMatches(element, selector->tagQName());

    if (selector->m_match == CSSSelector::Class)
        return element->hasClass() && static_cast<const StyledElement*>(element)->classNames().contains(selector->value());

    if (selector->m_match == CSSSelector::Id)
        return element->hasID() && element->idForStyleResolution() == selector->value();

    if (selector->isAttributeSelector()) {
        if (!element->hasAttributes())
            return false;

        const QualifiedName& attr = selector->attribute();
        bool caseSensitive = !m_documentIsHTML || HTMLDocument::isCaseSensitiveAttribute(attr);

        return anyAttributeMatches(element, selector, attr, caseSensitive);
    }

    if (selector->m_match == CSSSelector::PseudoClass) {
        // Handle :not up front.
        if (selector->pseudoClassType() == CSSSelector::PseudoClassNot) {
        	const CSSSelectorList* selectorList = selector->selectorList();

#if !ENABLE(CSS_SELECTORS_LEVEL4)
            // FIXME: We probably should fix the parser and make it never produce :not rules with missing selector list.
            if (!selectorList)
                return false;
#endif

            for (const CSSSelector* subselector = selectorList->first(); subselector; subselector = CSSSelectorList::next(subselector)) {
            	LocalContext subcontext(context);
				subcontext.inFunctionalPseudoClass = true;
				subcontext.pseudoElementEffective = false;
				subcontext.selector = subselector;
				subcontext.firstSelectorOfTheFragment = selectorList->first();
				PseudoIdSet ignoreDynamicPseudo;

                unsigned ignoredSpecificity;
#if ENABLE(CSS_SELECTORS_LEVEL4)
				if (matchRecursively(checkingContext, subcontext, ignoreDynamicPseudo, ignoredSpecificity).match == Match::SelectorMatches) {
					ASSERT(!ignoreDynamicPseudo);
					return false;
				}
#else
            	if (subcontext.selector->match() == CSSSelector::PseudoClass) {
					// :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->pseudoClassType() != CSSSelector::PseudoClassNot);
					// We select between :visited and :link when applying. We don't know which one applied (or not) yet.
					if (subcontext.selector->pseudoClassType() == CSSSelector::PseudoClassVisited || (subcontext.selector->pseudoClassType() == CSSSelector::PseudoClassLink && subcontext.visitedMatchType == VisitedMatchType::Enabled))
						return true;
            	}
                // Since :not cannot contain pseudo elements, there's no effect on matchType.
                MatchType ignoreMatchType = MatchType::Element;
                if (!checkOne(checkingContext, subcontext, ignoreDynamicPseudo, ignoreMatchType, ignoredSpecificity))
                    return true;
#endif
            }
#if ENABLE(CSS_SELECTORS_LEVEL4)
            return true;
#endif
        } 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(checkingContext, *context.element, selector);
        } else if (context.hasSelectionPseudo) {
            if (selector->pseudoClassType() == CSSSelector::PseudoClassWindowInactive)
                return !element->document()->page()->focusController()->isActive();
        }

        // Normal element pseudo class checking.
        switch (selector->pseudoClassType()) {
            // Pseudo classes:
        case CSSSelector::PseudoClassNot:
            break; // Already handled up above.
        case CSSSelector::PseudoClassEmpty:
            {
                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;
                        }
                    }
                }
                addStyleRelation(checkingContext, *context.element, StyleRelation::AffectedByEmpty, result);
                return result;
            }
        case CSSSelector::PseudoClassFirstChild:
            // first-child matches the first child that is an element
        	if (const Element* parentElement = element->parentElement()) {
				bool isFirstChild = isFirstChildElement(element);
				if (isFirstChild)
					addStyleRelation(checkingContext, *element, StyleRelation::FirstChild);
				addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByFirstChildRules);
				return isFirstChild;
			}
            break;
        case CSSSelector::PseudoClassFirstOfType:
            // first-of-type matches the first element of its type
        	if (element->parentElement()) {
        		addStyleRelation(checkingContext, *element, StyleRelation::AffectedByPreviousSibling);
				return isFirstOfType(checkingContext, *element, element->tagQName());
			}
            break;
        case CSSSelector::PseudoClassLastChild:
            // last-child matches the last child that is an element
            if (const Element* parentElement = element->parentElement()) {
                bool isLastChild = parentElement->isFinishedParsingChildren() && isLastChildElement(element);
                if (isLastChild)
                    addStyleRelation(checkingContext, *element, StyleRelation::LastChild);
                addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByLastChildRules);
                return isLastChild;
            }
            break;
        case CSSSelector::PseudoClassLastOfType:
            // last-of-type matches the last element of its type
            if (Element* parentElement = element->parentElement()) {
                addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByBackwardPositionalRules);
                if (!parentElement->isFinishedParsingChildren())
                    return false;
                return isLastOfType(element, element->tagQName());
            }
            break;
        case CSSSelector::PseudoClassOnlyChild:
            if (Element* parentElement = element->parentElement()) {
            	bool firstChild = isFirstChildElement(element);
				bool onlyChild = firstChild && parentElement->isFinishedParsingChildren() && isLastChildElement(element);
				addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByFirstChildRules);
				addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByLastChildRules);
				if (firstChild)
					addStyleRelation(checkingContext, *element, StyleRelation::FirstChild);
				if (onlyChild)
					addStyleRelation(checkingContext, *element, StyleRelation::LastChild);
                return onlyChild;
            }
            break;
        case CSSSelector::PseudoClassOnlyOfType:
            // FIXME: This selector is very slow.
            if (Element* parentElement = element->parentElement()) {
            	addStyleRelation(checkingContext, *element, StyleRelation::AffectedByPreviousSibling);
				addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByBackwardPositionalRules);
                if (!parentElement->isFinishedParsingChildren())
                    return false;
                return isFirstOfType(checkingContext, *element, element->tagQName()) && isLastOfType(element, element->tagQName());
            }
            break;
#if ENABLE(CSS_SELECTORS_LEVEL4)
        case CSSSelector::PseudoClassMatches:
            {
                bool hasMatchedAnything = false;
                unsigned maxSpecificity = 0;

                MatchType localMatchType = MatchType::VirtualPseudoElementOnly;
                for (const CSSSelector* subselector = selector->selectorList()->first(); subselector; subselector = CSSSelectorList::next(subselector)) {
                	LocalContext subcontext(context);
                    subcontext.inFunctionalPseudoClass = true;
                    subcontext.pseudoElementEffective = context.pseudoElementEffective;
                    subcontext.selector = subselector;
                    subcontext.firstSelectorOfTheFragment = subselector;
                    PseudoIdSet localDynamicPseudoIdSet;
                    unsigned localSpecificity = 0;
                    MatchResult result = matchRecursively(checkingContext, subcontext, localDynamicPseudoIdSet, localSpecificity);
                    if (result.match == Match::SelectorMatches) {
                        maxSpecificity = std::max(maxSpecificity, localSpecificity);

                        if (result.matchType == MatchType::Element)
                            localMatchType = MatchType::Element;

                        dynamicPseudoIdSet.merge(localDynamicPseudoIdSet);
                        hasMatchedAnything = true;
                    }
                }
                if (hasMatchedAnything) {
                    matchType = localMatchType;
                    specificity = CSSSelector::addSpecificities(specificity, maxSpecificity);
                }
                return hasMatchedAnything;
            }

        case CSSSelector::PseudoClassPlaceholderShown:
            if (isHTMLTextFormControlElement(*element)) {
                addStyleRelation(checkingContext, *element, StyleRelation::Unique);
                return toHTMLTextFormControlElement(*element).isPlaceholderVisible();
            }
            return false;
#endif
        case CSSSelector::PseudoClassNthChild:
            if (!selector->parseNth())
                break;

            if (element->parentElement()) {
#if ENABLE(CSS_SELECTORS_LEVEL4)
                if (const CSSSelectorList* selectorList = selector->selectorList()) {
                    unsigned selectorListSpecificity;
                    if (matchSelectorList(checkingContext, context, *element, *selectorList, selectorListSpecificity))
                        specificity = CSSSelector::addSpecificities(specificity, selectorListSpecificity);
                    else
                        return false;
                }
#endif

                addStyleRelation(checkingContext, *element, StyleRelation::AffectedByPreviousSibling);

                int count = 1;
#if ENABLE(CSS_SELECTORS_LEVEL4)
                if (const CSSSelectorList* selectorList = selector->selectorList()) {
                    for (Element* sibling = ElementTraversal::previousSibling(element); sibling; sibling = ElementTraversal::previousSibling(sibling)) {
                    	addStyleRelation(checkingContext, *sibling, StyleRelation::AffectsNextSibling);

                        unsigned ignoredSpecificity;
                        if (matchSelectorList(checkingContext, context, *sibling, *selectorList, ignoredSpecificity))
                            ++count;
                    }
                } else
#endif
                {
                    count += countElementsBefore(checkingContext, *element);
                    addStyleRelation(checkingContext, *element, StyleRelation::NthChildIndex, count);
                }

                if (selector->matchNth(count))
                    return true;
            }
            break;
        case CSSSelector::PseudoClassNthOfType:
            if (!selector->parseNth())
                break;

            if (element->parentElement()) {
                addStyleRelation(checkingContext, *element, StyleRelation::AffectedByPreviousSibling);

                int count = 1 + countElementsOfTypeBefore(checkingContext, *element, element->tagQName());
                if (selector->matchNth(count))
                    return true;
            }
            break;
        case CSSSelector::PseudoClassNthLastChild:
            if (!selector->parseNth())
                break;
            if (Element* parentElement = element->parentElement()) {
                if (const CSSSelectorList* selectorList = selector->selectorList()) {
                    unsigned selectorListSpecificity;
                    if (matchSelectorList(checkingContext, context, *element, *selectorList, selectorListSpecificity))
                        specificity = CSSSelector::addSpecificities(specificity, selectorListSpecificity);
                    else
                        return false;

                    addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByPropertyBasedBackwardPositionalRules);
                } else
                    addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByBackwardPositionalRules);

                if (!parentElement->isFinishedParsingChildren())
                    return false;

                int count = 1;
                if (const CSSSelectorList* selectorList = selector->selectorList()) {
                    for (Element* sibling = ElementTraversal::nextSibling(element); sibling; sibling = ElementTraversal::nextSibling(sibling)) {
                        unsigned ignoredSpecificity;
                        if (matchSelectorList(checkingContext, context, *sibling, *selectorList, ignoredSpecificity))
                            ++count;
                    }
                } else
                    count += countElementsAfter(element);

                if (selector->matchNth(count))
                    return true;
            }
            break;
        case CSSSelector::PseudoClassNthLastOfType:
            if (!selector->parseNth())
                break;
            if (Element* parentElement = element->parentElement()) {
                addStyleRelation(checkingContext, *parentElement, StyleRelation::ChildrenAffectedByBackwardPositionalRules);

                if (!parentElement->isFinishedParsingChildren())
                    return false;
                int count = 1 + countElementsOfTypeAfter(element, element->tagQName());
                if (selector->matchNth(count))
                    return true;
            }
            break;
        case CSSSelector::PseudoClassTarget:
            if (element == element->document()->cssTarget())
                return true;
            break;
        case CSSSelector::PseudoClassAny:
            {
                LocalContext subcontext(context);
                subcontext.inFunctionalPseudoClass = true;
                subcontext.pseudoElementEffective = false;
                for (subcontext.selector = selector->selectorList()->first(); subcontext.selector; subcontext.selector = CSSSelectorList::next(subcontext.selector)) {
                	subcontext.firstSelectorOfTheFragment = subcontext.selector;
                	PseudoIdSet ignoreDynamicPseudo;
                    unsigned ingoredSpecificity = 0;
					if (matchRecursively(checkingContext, subcontext, ignoreDynamicPseudo, ingoredSpecificity).match == Match::SelectorMatches)
						return true;
                }
            }
            break;
        case CSSSelector::PseudoClassAutofill:
            if (!element || !element->isFormControlElement())
                break;
            if (HTMLInputElement* inputElement = const_cast<Element*>(element)->toInputElement())
                return inputElement->isAutofilled();
            break;
#if ENABLE(CSS_SELECTORS_LEVEL4)
        case CSSSelector::PseudoClassAnyLink:
#endif
        case CSSSelector::PseudoClassAnyLinkDeprecated:
        case CSSSelector::PseudoClassLink:
            // :visited and :link matches are separated later when applying the style. Here both classes match all links...
            return element->isLink();
        case CSSSelector::PseudoClassVisited:
            // ...except if :visited matching is disabled for ancestor/sibling matching.
        	// Inside functional pseudo class except for :not, :visited never matches.
			if (context.inFunctionalPseudoClass)
				return false;
            return element->isLink() && context.visitedMatchType == VisitedMatchType::Enabled;
        case CSSSelector::PseudoClassDrag:
            addStyleRelation(checkingContext, *element, StyleRelation::AffectedByDrag);

            if (element->renderer() && element->renderer()->isDragging())
                return true;
            break;
        case CSSSelector::PseudoClassFocus:
            return matchesFocusPseudoClass(element);
        case CSSSelector::PseudoClassHover:
        	if (m_strictParsing || element->isLink() || canMatchHoverOrActiveInQuirksMode(context)) {
        		addStyleRelation(checkingContext, *element, StyleRelation::AffectedByHover);

                // See the comment in generateElementIsHovered() in SelectorCompiler.
        		// :hover always matches when not in rightmost position when collecting rules for descendant style invalidation optimization.
				// Resolving style for a matching descendant will set parent childrenAffectedByHover bit even when the element is not currently hovered.
				// This bit has to be set for the event based :hover invalidation to work.
				// FIXME: We should just collect style relation bits and apply them as needed when computing style invalidation optimization.
                if (checkingContext.resolvingMode == SelectorChecker::Mode::CollectingRulesIgnoringVirtualPseudoElements && !context.isMatchElement)
                    return true;

				if (element->hovered() || InspectorInstrumentation::forcePseudoState(const_cast<Element*>(element), CSSSelector::PseudoClassHover))
                    return true;
            }
            break;
        case CSSSelector::PseudoClassActive:
        	if (m_strictParsing || element->isLink() || canMatchHoverOrActiveInQuirksMode(context)) {
        		addStyleRelation(checkingContext, *element, StyleRelation::AffectedByActive);

				if (element->active() || InspectorInstrumentation::forcePseudoState(const_cast<Element*>(element), CSSSelector::PseudoClassActive))
                    return true;
            }
            break;
        case CSSSelector::PseudoClassEnabled:
            if (element && (element->isFormControlElement() || element->hasTagName(optionTag) || element->hasTagName(optgroupTag)))
                return !element->isDisabledFormControl();
            break;
        case CSSSelector::PseudoClassFullPageMedia:
            return element && element->document() && element->document()->isMediaDocument();
            break;
        case CSSSelector::PseudoClassDefault:
            return element && element->isDefaultButtonForForm();
        case CSSSelector::PseudoClassDisabled:
            if (element && (element->isFormControlElement() || element->hasTagName(optionTag) || element->hasTagName(optgroupTag)))
                return element->isDisabledFormControl();
            break;
        case CSSSelector::PseudoClassReadOnly:
            return element && !element->matchesReadWritePseudoClass();
        case CSSSelector::PseudoClassReadWrite:
            return element && element->matchesReadWritePseudoClass();
        case CSSSelector::PseudoClassOptional:
            return element && element->isOptionalFormControl();
        case CSSSelector::PseudoClassRequired:
            return element && element->isRequiredFormControl();
        case CSSSelector::PseudoClassValid:
            if (!element)
                return false;
            return element->matchesValidPseudoClass();
        case CSSSelector::PseudoClassInvalid:
            if (!element)
                return false;
            return element->matchesInvalidPseudoClass();
        case CSSSelector::PseudoClassChecked:
            {
                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 = const_cast<Element*>(element)->toInputElement();
                if (inputElement && inputElement->shouldAppearChecked() && !inputElement->isIndeterminate())
                    return true;
                if (element->hasTagName(optionTag) && toHTMLOptionElement(const_cast<Element*>(element))->selected())
                    return true;
                break;
            }
        case CSSSelector::PseudoClassIndeterminate:
            {
                if (!element)
                    break;
#if ENABLE(PROGRESS_ELEMENT)
                if (element->hasTagName(progressTag)) {
                    const HTMLProgressElement* progress = static_cast<const HTMLProgressElement*>(element);
                    if (progress && !progress->isDeterminate())
                        return true;
                    break;
                }
#endif
                HTMLInputElement* inputElement = const_cast<Element*>(element)->toInputElement();
                if (inputElement && inputElement->isIndeterminate())
                    return true;
                break;
            }
        case CSSSelector::PseudoClassScope:
        {
			const Node* contextualReferenceNode = !checkingContext.scope ? element->document()->documentElement() : checkingContext.scope;
			if (element == contextualReferenceNode)
				return true;
			break;
		}
        case CSSSelector::PseudoClassRoot:
            if (element == element->document()->documentElement())
                return true;
            break;
        case CSSSelector::PseudoClassLang:
            {
                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::PseudoClassFullScreen:
            // 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<const HTMLFrameElementBase*>(element)->containsFullScreenElement())
                return true;
            if (!element->document()->webkitIsFullScreen())
                return false;
            return element == element->document()->webkitCurrentFullScreenElement();
        case CSSSelector::PseudoClassAnimatingFullScreenTransition:
            if (element != element->document()->webkitCurrentFullScreenElement())
                return false;
            return element->document()->isAnimatingFullScreen();
        case CSSSelector::PseudoClassFullScreenAncestor:
            return element->containsFullScreenElement();
        case CSSSelector::PseudoClassFullScreenDocument:
            // 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::PseudoClassInRange:
            if (!element)
                return false;
            return element->isInRange();
        case CSSSelector::PseudoClassOutOfRange:
            if (!element)
                return false;
            return element->isOutOfRange();
#if ENABLE(VIDEO_TRACK)
        case CSSSelector::PseudoClassFuture:
        	return (element->isWebVTTElement() && !toWebVTTElement(element)->isPastNode());
        case CSSSelector::PseudoClassPast:
			return (element->isWebVTTElement() && toWebVTTElement(element)->isPastNode());
#endif
        case CSSSelector::PseudoClassUnknown:
        default:
            ASSERT_NOT_REACHED();
            break;
        }
        return false;
    }
#if ENABLE(VIDEO_TRACK)
    else if (selector->m_match == CSSSelector::PseudoElement && selector->pseudoElementType() == CSSSelector::PseudoElementCue) {
    	LocalContext subcontext(context);

        const CSSSelector* const & selector = context.selector;
        for (subcontext.selector = selector->selectorList()->first(); subcontext.selector; subcontext.selector = CSSSelectorList::next(subcontext.selector)) {
        	subcontext.firstSelectorOfTheFragment = subcontext.selector;
        	subcontext.inFunctionalPseudoClass = true;
        	subcontext.pseudoElementEffective = false;
            PseudoIdSet ignoredDynamicPseudo;
            unsigned ignoredSpecificity = 0;
            if (matchRecursively(checkingContext, subcontext, ignoredDynamicPseudo, ignoredSpecificity).match == Match::SelectorMatches)
                return true;
        }
        return false;
    }
#endif
    // ### add the rest of the checks...
    return true;
}

bool SelectorChecker::matchSelectorList(CheckingContext& checkingContext, const LocalContext& context, const Element& element, const CSSSelectorList& selectorList, unsigned& specificity) const
{
    specificity = 0;
    bool hasMatchedAnything = false;

    for (const CSSSelector* subselector = selectorList.first(); subselector; subselector = CSSSelectorList::next(subselector)) {
    	LocalContext subcontext(context);
        subcontext.element = &element;
        subcontext.selector = subselector;
        subcontext.inFunctionalPseudoClass = true;
        subcontext.pseudoElementEffective = false;
        subcontext.firstSelectorOfTheFragment = subselector;
        PseudoIdSet ignoreDynamicPseudo;
        unsigned localSpecificity = 0;
        if (matchRecursively(checkingContext, subcontext, ignoreDynamicPseudo, localSpecificity).match == Match::SelectorMatches) {
            ASSERT(!ignoreDynamicPseudo);

            hasMatchedAnything = true;
            specificity = std::max(specificity, localSpecificity);
        }
    }
    return hasMatchedAnything;
}

bool SelectorChecker::checkScrollbarPseudoClass(const CheckingContext& checkingContext, const Element& element, const CSSSelector* selector) const
{
    ASSERT(selector->m_match == CSSSelector::PseudoClass);

    RenderScrollbar* scrollbar = checkingContext.scrollbar;
    ScrollbarPart part = checkingContext.scrollbarPart;

    // 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 (selector->pseudoClassType() == CSSSelector::PseudoClassWindowInactive)
        return !element.document()->page()->focusController()->isActive();

    if (!scrollbar)
        return false;

    switch (selector->pseudoClassType()) {
    case CSSSelector::PseudoClassEnabled:
        return scrollbar->enabled();
    case CSSSelector::PseudoClassDisabled:
        return !scrollbar->enabled();
    case CSSSelector::PseudoClassHover:
        {
            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::PseudoClassActive:
        {
            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::PseudoClassHorizontal:
        return scrollbar->orientation() == HorizontalScrollbar;
    case CSSSelector::PseudoClassVertical:
        return scrollbar->orientation() == VerticalScrollbar;
    case CSSSelector::PseudoClassDecrement:
        return part == BackButtonStartPart || part == BackButtonEndPart || part == BackTrackPart;
    case CSSSelector::PseudoClassIncrement:
        return part == ForwardButtonStartPart || part == ForwardButtonEndPart || part == ForwardTrackPart;
    case CSSSelector::PseudoClassStart:
        return part == BackButtonStartPart || part == ForwardButtonStartPart || part == BackTrackPart;
    case CSSSelector::PseudoClassEnd:
        return part == BackButtonEndPart || part == ForwardButtonEndPart || part == ForwardTrackPart;
    case CSSSelector::PseudoClassDoubleButton:
        {
            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::PseudoClassSingleButton:
        {
            ScrollbarButtonsPlacement buttonsPlacement = scrollbar->theme()->buttonsPlacement();
            if (part == BackButtonStartPart || part == ForwardButtonEndPart || part == BackTrackPart || part == ForwardTrackPart)
                return buttonsPlacement == ScrollbarButtonsSingle;
            return false;
        }
    case CSSSelector::PseudoClassNoButton:
        {
            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::PseudoClassCornerPresent:
        return scrollbar->scrollableArea()->isScrollCornerVisible();
    default:
        return false;
    }
}

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()) {
        if (selector->m_match == CSSSelector::PseudoClass) {
			switch (selector->pseudoClassType()) {
#if! ENABLE(CSS_SELECTORS_LEVEL4)
			case CSSSelector::PseudoClassNot:
				{
					// :not(:visited) is equivalent to :link. Parser enforces that :not can't nest.
					const CSSSelectorList* selectorList = selector->selectorList();
					if (!selectorList)
						break;

					for (const CSSSelector* subSelector = selectorList->first(); subSelector; subSelector = subSelector->tagHistory()) {
						if (subSelector->m_match == CSSSelector::PseudoClass) {
							CSSSelector::PseudoClassType subType = subSelector->pseudoClassType();
							if (subType == CSSSelector::PseudoClassVisited)
								linkMatchType &= ~SelectorChecker::MatchVisited;
							else if (subType == CSSSelector::PseudoClassLink)
								linkMatchType &= ~SelectorChecker::MatchLink;
						}
					}
				}
				break;
#endif
			case CSSSelector::PseudoClassLink:
				linkMatchType &= ~SelectorChecker::MatchVisited;
				break;
			case CSSSelector::PseudoClassVisited:
				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;
}

static bool isFrameFocused(const Element* element)
{
    return element->document()->frame() && element->document()->frame()->selection().isFocusedAndActive();
}

bool SelectorChecker::matchesFocusPseudoClass(const Element* element)
{
    if (InspectorInstrumentation::forcePseudoState(const_cast<Element*>(element), CSSSelector::PseudoClassFocus))
        return true;
    return element->focused() && isFrameFocused(element);
}

}
