blob: d760031781df1258946f43cae1c063fabba1fa02 [file] [log] [blame]
/*
* Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
* 1999 Waldo Bastian (bastian@kde.org)
* 2001 Andreas Schlapbach (schlpbch@iam.unibe.ch)
* 2001-2003 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2002, 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights
* reserved.
* Copyright (C) 2008 David Smith (catfish.man@gmail.com)
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "third_party/blink/renderer/core/css/css_selector.h"
#include <algorithm>
#include <memory>
#include "base/stl_util.h"
#include "third_party/blink/renderer/core/css/css_markup.h"
#include "third_party/blink/renderer/core/css/css_selector_list.h"
#include "third_party/blink/renderer/core/css/parser/css_parser_context.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
namespace {
unsigned MaximumSpecificity(const CSSSelectorList* list,
CSSSelector::SpecificityMode mode) {
if (!list)
return 0;
unsigned result = 0;
const CSSSelector* selector;
for (selector = list->First(); selector;
selector = CSSSelectorList::Next(*selector)) {
unsigned specificity = selector->Specificity(mode);
if (result < specificity)
result = specificity;
}
return result;
}
} // namespace
struct SameSizeAsCSSSelector {
unsigned bitfields;
void* pointers[1];
};
ASSERT_SIZE(CSSSelector, SameSizeAsCSSSelector);
void CSSSelector::CreateRareData() {
DCHECK_NE(Match(), kTag);
if (has_rare_data_)
return;
// This transitions the DataUnion from |value_| to |rare_data_| and thus needs to be careful to
// correctly manage explicitly destruction of |value_| followed by placement new of |rare_data_|.
// A straight-assignment will compile and may kinda work, but will be undefined behavior.
auto rare_data = RareData::Create(data_.value_);
data_.value_.~AtomicString();
new (&data_.rare_data_) scoped_refptr<RareData>(std::move(rare_data));
has_rare_data_ = true;
}
unsigned CSSSelector::Specificity(SpecificityMode mode) const {
// make sure the result doesn't overflow
static const unsigned kMaxValueMask = 0xffffff;
static const unsigned kIdMask = 0xff0000;
static const unsigned kClassMask = 0x00ff00;
static const unsigned kElementMask = 0x0000ff;
if (IsForPage())
return SpecificityForPage() & kMaxValueMask;
unsigned total = 0;
unsigned temp = 0;
for (const CSSSelector* selector = this; selector;
selector = selector->TagHistory()) {
temp = total + selector->SpecificityForOneSelector(mode);
// Clamp each component to its max in the case of overflow.
if ((temp & kIdMask) < (total & kIdMask))
total |= kIdMask;
else if ((temp & kClassMask) < (total & kClassMask))
total |= kClassMask;
else if ((temp & kElementMask) < (total & kElementMask))
total |= kElementMask;
else
total = temp;
}
return total;
}
inline unsigned CSSSelector::SpecificityForOneSelector(
SpecificityMode mode) const {
// FIXME: Pseudo-elements and pseudo-classes do not have the same specificity.
// This function isn't quite correct.
// http://www.w3.org/TR/selectors/#specificity
switch (match_) {
case kId:
return kIdSpecificity;
case kPseudoClass:
switch (GetPseudoType()) {
case kPseudoWhere:
return 0;
case kPseudoHost:
case kPseudoHostContext:
if (mode == SpecificityMode::kNormal) {
// We dynamically compute the specificity of :host and :host-context
// during matching.
return 0;
} else {
DCHECK_EQ(SpecificityMode::kIncludeHostPseudos, mode);
return kClassLikeSpecificity +
MaximumSpecificity(SelectorList(), mode);
}
case kPseudoNot:
DCHECK(SelectorList());
FALLTHROUGH;
case kPseudoIs:
return MaximumSpecificity(SelectorList(), mode);
// FIXME: PseudoAny should base the specificity on the sub-selectors.
// See http://lists.w3.org/Archives/Public/www-style/2010Sep/0530.html
case kPseudoAny:
default:
break;
}
return kClassLikeSpecificity;
case kPseudoElement:
switch (GetPseudoType()) {
case kPseudoSlotted:
DCHECK(SelectorList()->HasOneSelector());
return kClassLikeSpecificity + SelectorList()->First()->Specificity();
default:
break;
}
return kClassLikeSpecificity;
case kClass:
case kAttributeExact:
case kAttributeSet:
case kAttributeList:
case kAttributeHyphen:
case kAttributeContain:
case kAttributeBegin:
case kAttributeEnd:
return kClassLikeSpecificity;
case kTag:
if (TagQName().LocalName() == UniversalSelectorAtom())
return 0;
return kTagSpecificity;
case kUnknown:
return 0;
}
NOTREACHED();
return 0;
}
unsigned CSSSelector::SpecificityForPage() const {
// See https://drafts.csswg.org/css-page/#cascading-and-page-context
unsigned s = 0;
for (const CSSSelector* component = this; component;
component = component->TagHistory()) {
switch (component->match_) {
case kTag:
s += TagQName().LocalName() == UniversalSelectorAtom() ? 0 : 4;
break;
case kPagePseudoClass:
switch (component->GetPseudoType()) {
case kPseudoFirstPage:
s += 2;
break;
case kPseudoLeftPage:
case kPseudoRightPage:
s += 1;
break;
default:
NOTREACHED();
}
break;
default:
break;
}
}
return s;
}
PseudoId CSSSelector::GetPseudoId(PseudoType type) {
switch (type) {
case kPseudoFirstLine:
return kPseudoIdFirstLine;
case kPseudoFirstLetter:
return kPseudoIdFirstLetter;
case kPseudoSelection:
return kPseudoIdSelection;
case kPseudoBefore:
return kPseudoIdBefore;
case kPseudoAfter:
return kPseudoIdAfter;
case kPseudoMarker:
return kPseudoIdMarker;
case kPseudoBackdrop:
return kPseudoIdBackdrop;
case kPseudoScrollbar:
return kPseudoIdScrollbar;
case kPseudoScrollbarButton:
return kPseudoIdScrollbarButton;
case kPseudoScrollbarCorner:
return kPseudoIdScrollbarCorner;
case kPseudoScrollbarThumb:
return kPseudoIdScrollbarThumb;
case kPseudoScrollbarTrack:
return kPseudoIdScrollbarTrack;
case kPseudoScrollbarTrackPiece:
return kPseudoIdScrollbarTrackPiece;
case kPseudoResizer:
return kPseudoIdResizer;
case kPseudoTargetText:
return kPseudoIdTargetText;
case kPseudoSpellingError:
return kPseudoIdSpellingError;
case kPseudoGrammarError:
return kPseudoIdGrammarError;
case kPseudoUnknown:
case kPseudoEmpty:
case kPseudoFirstChild:
case kPseudoFirstOfType:
case kPseudoLastChild:
case kPseudoLastOfType:
case kPseudoOnlyChild:
case kPseudoOnlyOfType:
case kPseudoNthChild:
case kPseudoNthOfType:
case kPseudoNthLastChild:
case kPseudoNthLastOfType:
case kPseudoLink:
case kPseudoVisited:
case kPseudoAny:
case kPseudoIs:
case kPseudoWhere:
case kPseudoAnyLink:
case kPseudoWebkitAnyLink:
case kPseudoAutofill:
case kPseudoAutofillPreviewed:
case kPseudoAutofillSelected:
case kPseudoHover:
case kPseudoDrag:
case kPseudoFocus:
case kPseudoFocusVisible:
case kPseudoFocusWithin:
case kPseudoActive:
case kPseudoChecked:
case kPseudoEnabled:
case kPseudoFullPageMedia:
case kPseudoDefault:
case kPseudoDisabled:
case kPseudoOptional:
case kPseudoPlaceholder:
case kPseudoPlaceholderShown:
case kPseudoFileSelectorButton:
case kPseudoRequired:
case kPseudoReadOnly:
case kPseudoReadWrite:
case kPseudoValid:
case kPseudoInvalid:
case kPseudoIndeterminate:
case kPseudoTarget:
case kPseudoLang:
case kPseudoDir:
case kPseudoNot:
case kPseudoRoot:
case kPseudoScope:
case kPseudoWindowInactive:
case kPseudoCornerPresent:
case kPseudoDecrement:
case kPseudoIncrement:
case kPseudoHorizontal:
case kPseudoVertical:
case kPseudoStart:
case kPseudoEnd:
case kPseudoDoubleButton:
case kPseudoSingleButton:
case kPseudoNoButton:
case kPseudoFirstPage:
case kPseudoLeftPage:
case kPseudoRightPage:
case kPseudoInRange:
case kPseudoOutOfRange:
case kPseudoWebKitCustomElement:
case kPseudoBlinkInternalElement:
case kPseudoCue:
case kPseudoFutureCue:
case kPseudoPastCue:
case kPseudoDefined:
case kPseudoHost:
case kPseudoHostContext:
case kPseudoPart:
case kPseudoState:
case kPseudoFullScreen:
case kPseudoFullScreenAncestor:
case kPseudoFullscreen:
case kPseudoPictureInPicture:
case kPseudoSpatialNavigationFocus:
case kPseudoSpatialNavigationInterest:
case kPseudoHasDatalist:
case kPseudoIsHtml:
case kPseudoListBox:
case kPseudoMultiSelectFocus:
case kPseudoHostHasAppearance:
case kPseudoPopupOpen:
case kPseudoSlotted:
case kPseudoVideoPersistent:
case kPseudoVideoPersistentAncestor:
case kPseudoXrOverlay:
case kPseudoModal:
return kPseudoIdNone;
}
NOTREACHED();
return kPseudoIdNone;
}
// Could be made smaller and faster by replacing pointer with an
// offset into a string buffer and making the bit fields smaller but
// that could not be maintained by hand.
struct NameToPseudoStruct {
const char* string;
unsigned type : 8;
};
// These tables should be kept sorted.
const static NameToPseudoStruct kPseudoTypeWithoutArgumentsMap[] = {
{"-internal-autofill-previewed", CSSSelector::kPseudoAutofillPreviewed},
{"-internal-autofill-selected", CSSSelector::kPseudoAutofillSelected},
{"-internal-has-datalist", CSSSelector::kPseudoHasDatalist},
{"-internal-is-html", CSSSelector::kPseudoIsHtml},
{"-internal-list-box", CSSSelector::kPseudoListBox},
{"-internal-media-controls-overlay-cast-button",
CSSSelector::kPseudoWebKitCustomElement},
{"-internal-modal", CSSSelector::kPseudoModal},
{"-internal-multi-select-focus", CSSSelector::kPseudoMultiSelectFocus},
{"-internal-popup-open", CSSSelector::kPseudoPopupOpen},
{"-internal-shadow-host-has-appearance",
CSSSelector::kPseudoHostHasAppearance},
{"-internal-spatial-navigation-focus",
CSSSelector::kPseudoSpatialNavigationFocus},
{"-internal-spatial-navigation-interest",
CSSSelector::kPseudoSpatialNavigationInterest},
{"-internal-video-persistent", CSSSelector::kPseudoVideoPersistent},
{"-internal-video-persistent-ancestor",
CSSSelector::kPseudoVideoPersistentAncestor},
{"-webkit-any-link", CSSSelector::kPseudoWebkitAnyLink},
{"-webkit-autofill", CSSSelector::kPseudoAutofill},
{"-webkit-drag", CSSSelector::kPseudoDrag},
{"-webkit-full-page-media", CSSSelector::kPseudoFullPageMedia},
{"-webkit-full-screen", CSSSelector::kPseudoFullScreen},
{"-webkit-full-screen-ancestor", CSSSelector::kPseudoFullScreenAncestor},
{"-webkit-resizer", CSSSelector::kPseudoResizer},
{"-webkit-scrollbar", CSSSelector::kPseudoScrollbar},
{"-webkit-scrollbar-button", CSSSelector::kPseudoScrollbarButton},
{"-webkit-scrollbar-corner", CSSSelector::kPseudoScrollbarCorner},
{"-webkit-scrollbar-thumb", CSSSelector::kPseudoScrollbarThumb},
{"-webkit-scrollbar-track", CSSSelector::kPseudoScrollbarTrack},
{"-webkit-scrollbar-track-piece", CSSSelector::kPseudoScrollbarTrackPiece},
{"active", CSSSelector::kPseudoActive},
{"after", CSSSelector::kPseudoAfter},
{"any-link", CSSSelector::kPseudoAnyLink},
{"backdrop", CSSSelector::kPseudoBackdrop},
{"before", CSSSelector::kPseudoBefore},
{"checked", CSSSelector::kPseudoChecked},
{"corner-present", CSSSelector::kPseudoCornerPresent},
{"cue", CSSSelector::kPseudoWebKitCustomElement},
{"decrement", CSSSelector::kPseudoDecrement},
{"default", CSSSelector::kPseudoDefault},
{"defined", CSSSelector::kPseudoDefined},
{"disabled", CSSSelector::kPseudoDisabled},
{"double-button", CSSSelector::kPseudoDoubleButton},
{"empty", CSSSelector::kPseudoEmpty},
{"enabled", CSSSelector::kPseudoEnabled},
{"end", CSSSelector::kPseudoEnd},
{"file-selector-button", CSSSelector::kPseudoFileSelectorButton},
{"first", CSSSelector::kPseudoFirstPage},
{"first-child", CSSSelector::kPseudoFirstChild},
{"first-letter", CSSSelector::kPseudoFirstLetter},
{"first-line", CSSSelector::kPseudoFirstLine},
{"first-of-type", CSSSelector::kPseudoFirstOfType},
{"focus", CSSSelector::kPseudoFocus},
{"focus-visible", CSSSelector::kPseudoFocusVisible},
{"focus-within", CSSSelector::kPseudoFocusWithin},
{"fullscreen", CSSSelector::kPseudoFullscreen},
{"future", CSSSelector::kPseudoFutureCue},
{"grammar-error", CSSSelector::kPseudoGrammarError},
{"horizontal", CSSSelector::kPseudoHorizontal},
{"host", CSSSelector::kPseudoHost},
{"hover", CSSSelector::kPseudoHover},
{"in-range", CSSSelector::kPseudoInRange},
{"increment", CSSSelector::kPseudoIncrement},
{"indeterminate", CSSSelector::kPseudoIndeterminate},
{"invalid", CSSSelector::kPseudoInvalid},
{"last-child", CSSSelector::kPseudoLastChild},
{"last-of-type", CSSSelector::kPseudoLastOfType},
{"left", CSSSelector::kPseudoLeftPage},
{"link", CSSSelector::kPseudoLink},
{"marker", CSSSelector::kPseudoMarker},
{"no-button", CSSSelector::kPseudoNoButton},
{"only-child", CSSSelector::kPseudoOnlyChild},
{"only-of-type", CSSSelector::kPseudoOnlyOfType},
{"optional", CSSSelector::kPseudoOptional},
{"out-of-range", CSSSelector::kPseudoOutOfRange},
{"past", CSSSelector::kPseudoPastCue},
{"picture-in-picture", CSSSelector::kPseudoPictureInPicture},
{"placeholder", CSSSelector::kPseudoPlaceholder},
{"placeholder-shown", CSSSelector::kPseudoPlaceholderShown},
{"read-only", CSSSelector::kPseudoReadOnly},
{"read-write", CSSSelector::kPseudoReadWrite},
{"required", CSSSelector::kPseudoRequired},
{"right", CSSSelector::kPseudoRightPage},
{"root", CSSSelector::kPseudoRoot},
{"scope", CSSSelector::kPseudoScope},
{"selection", CSSSelector::kPseudoSelection},
{"single-button", CSSSelector::kPseudoSingleButton},
{"spelling-error", CSSSelector::kPseudoSpellingError},
{"start", CSSSelector::kPseudoStart},
{"target", CSSSelector::kPseudoTarget},
{"target-text", CSSSelector::kPseudoTargetText},
{"valid", CSSSelector::kPseudoValid},
{"vertical", CSSSelector::kPseudoVertical},
{"visited", CSSSelector::kPseudoVisited},
{"window-inactive", CSSSelector::kPseudoWindowInactive},
{"xr-overlay", CSSSelector::kPseudoXrOverlay},
};
const static NameToPseudoStruct kPseudoTypeWithArgumentsMap[] = {
{"-webkit-any", CSSSelector::kPseudoAny},
{"cue", CSSSelector::kPseudoCue},
{"dir", CSSSelector::kPseudoDir},
{"host", CSSSelector::kPseudoHost},
{"host-context", CSSSelector::kPseudoHostContext},
{"is", CSSSelector::kPseudoIs},
{"lang", CSSSelector::kPseudoLang},
{"not", CSSSelector::kPseudoNot},
{"nth-child", CSSSelector::kPseudoNthChild},
{"nth-last-child", CSSSelector::kPseudoNthLastChild},
{"nth-last-of-type", CSSSelector::kPseudoNthLastOfType},
{"nth-of-type", CSSSelector::kPseudoNthOfType},
{"part", CSSSelector::kPseudoPart},
{"slotted", CSSSelector::kPseudoSlotted},
{"where", CSSSelector::kPseudoWhere},
};
static CSSSelector::PseudoType NameToPseudoType(const AtomicString& name,
bool has_arguments) {
if (name.IsNull() || !name.Is8Bit())
return CSSSelector::kPseudoUnknown;
const NameToPseudoStruct* pseudo_type_map;
const NameToPseudoStruct* pseudo_type_map_end;
if (has_arguments) {
pseudo_type_map = kPseudoTypeWithArgumentsMap;
pseudo_type_map_end =
kPseudoTypeWithArgumentsMap + base::size(kPseudoTypeWithArgumentsMap);
} else {
pseudo_type_map = kPseudoTypeWithoutArgumentsMap;
pseudo_type_map_end = kPseudoTypeWithoutArgumentsMap +
base::size(kPseudoTypeWithoutArgumentsMap);
}
const NameToPseudoStruct* match = std::lower_bound(
pseudo_type_map, pseudo_type_map_end, name,
[](const NameToPseudoStruct& entry, const AtomicString& name) -> bool {
DCHECK(name.Is8Bit());
DCHECK(entry.string);
// If strncmp returns 0, then either the keys are equal, or |name| sorts
// before |entry|.
return strncmp(entry.string,
reinterpret_cast<const char*>(name.Characters8()),
name.length()) < 0;
});
if (match == pseudo_type_map_end || match->string != name.GetString())
return CSSSelector::kPseudoUnknown;
if (match->type == CSSSelector::kPseudoDir &&
!RuntimeEnabledFeatures::CSSPseudoDirEnabled())
return CSSSelector::kPseudoUnknown;
if (match->type == CSSSelector::kPseudoFocusVisible &&
!RuntimeEnabledFeatures::CSSFocusVisibleEnabled())
return CSSSelector::kPseudoUnknown;
if (match->type == CSSSelector::kPseudoPictureInPicture &&
!RuntimeEnabledFeatures::CSSPictureInPictureEnabled())
return CSSSelector::kPseudoUnknown;
if (match->type == CSSSelector::kPseudoTargetText &&
!RuntimeEnabledFeatures::CSSTargetTextPseudoElementEnabled()) {
return CSSSelector::kPseudoUnknown;
}
if ((match->type == CSSSelector::kPseudoSpellingError ||
match->type == CSSSelector::kPseudoGrammarError) &&
!RuntimeEnabledFeatures::CSSSpellingGrammarErrorsEnabled()) {
return CSSSelector::kPseudoUnknown;
}
return static_cast<CSSSelector::PseudoType>(match->type);
}
#ifndef NDEBUG
void CSSSelector::Show(int indent) const {
printf("%*sSelectorText(): %s\n", indent, "", SelectorText().Ascii().c_str());
printf("%*smatch_: %d\n", indent, "", match_);
if (match_ != kTag)
printf("%*sValue(): %s\n", indent, "", Value().Ascii().c_str());
printf("%*sGetPseudoType(): %d\n", indent, "", GetPseudoType());
if (match_ == kTag) {
printf("%*sTagQName().LocalName(): %s\n", indent, "",
TagQName().LocalName().Ascii().c_str());
}
printf("%*sIsAttributeSelector(): %d\n", indent, "", IsAttributeSelector());
if (IsAttributeSelector()) {
printf("%*sAttribute(): %s\n", indent, "",
Attribute().LocalName().Ascii().c_str());
}
printf("%*sArgument(): %s\n", indent, "", Argument().Ascii().c_str());
printf("%*sSpecificity(): %u\n", indent, "", Specificity());
if (TagHistory()) {
printf("\n%*s--> (Relation() == %d)\n", indent, "", Relation());
TagHistory()->Show(indent + 2);
} else {
printf("\n%*s--> (Relation() == %d)\n", indent, "", Relation());
}
}
void CSSSelector::Show() const {
printf("\n******* CSSSelector::Show(\"%s\") *******\n",
SelectorText().Ascii().c_str());
Show(2);
printf("******* end *******\n");
}
#endif
CSSSelector::PseudoType CSSSelector::ParsePseudoType(const AtomicString& name,
bool has_arguments) {
PseudoType pseudo_type = NameToPseudoType(name, has_arguments);
if (pseudo_type != kPseudoUnknown)
return pseudo_type;
if (name.StartsWith("-webkit-"))
return kPseudoWebKitCustomElement;
if (name.StartsWith("-internal-"))
return kPseudoBlinkInternalElement;
if (RuntimeEnabledFeatures::CustomStatePseudoClassEnabled() &&
name.StartsWith("--"))
return kPseudoState;
return kPseudoUnknown;
}
PseudoId CSSSelector::ParsePseudoId(const String& name, const Node* parent) {
unsigned name_without_colons_start =
name[0] == ':' ? (name[1] == ':' ? 2 : 1) : 0;
PseudoId pseudo_id = GetPseudoId(ParsePseudoType(
AtomicString(name.Substring(name_without_colons_start)), false));
if (!PseudoElement::IsWebExposed(pseudo_id, parent))
return kPseudoIdNone;
return pseudo_id;
}
void CSSSelector::UpdatePseudoPage(const AtomicString& value) {
DCHECK_EQ(Match(), kPagePseudoClass);
SetValue(value);
PseudoType type = ParsePseudoType(value, false);
if (type != kPseudoFirstPage && type != kPseudoLeftPage &&
type != kPseudoRightPage) {
type = kPseudoUnknown;
}
pseudo_type_ = type;
}
void CSSSelector::UpdatePseudoType(const AtomicString& value,
const CSSParserContext& context,
bool has_arguments,
CSSParserMode mode) {
DCHECK(match_ == kPseudoClass || match_ == kPseudoElement);
AtomicString lower_value = value.LowerASCII();
PseudoType pseudo_type = ParsePseudoType(lower_value, has_arguments);
SetPseudoType(pseudo_type);
SetValue(pseudo_type == kPseudoState ? value : lower_value);
switch (GetPseudoType()) {
case kPseudoAfter:
case kPseudoBefore:
case kPseudoFirstLetter:
case kPseudoFirstLine:
// The spec says some pseudos allow both single and double colons like
// :before for backwards compatability. Single colon becomes PseudoClass,
// but should be PseudoElement like double colon.
if (match_ == kPseudoClass)
match_ = kPseudoElement;
FALLTHROUGH;
// For pseudo elements
case kPseudoBackdrop:
case kPseudoCue:
case kPseudoMarker:
case kPseudoPart:
case kPseudoPlaceholder:
case kPseudoFileSelectorButton:
case kPseudoResizer:
case kPseudoScrollbar:
case kPseudoScrollbarCorner:
case kPseudoScrollbarButton:
case kPseudoScrollbarThumb:
case kPseudoScrollbarTrack:
case kPseudoScrollbarTrackPiece:
case kPseudoSelection:
case kPseudoWebKitCustomElement:
case kPseudoSlotted:
case kPseudoTargetText:
case kPseudoSpellingError:
case kPseudoGrammarError:
if (match_ != kPseudoElement)
pseudo_type_ = kPseudoUnknown;
break;
case kPseudoBlinkInternalElement:
if (match_ != kPseudoElement || mode != kUASheetMode)
pseudo_type_ = kPseudoUnknown;
break;
case kPseudoHasDatalist:
case kPseudoHostHasAppearance:
case kPseudoIsHtml:
case kPseudoListBox:
case kPseudoModal:
case kPseudoMultiSelectFocus:
case kPseudoPopupOpen:
case kPseudoSpatialNavigationFocus:
case kPseudoSpatialNavigationInterest:
case kPseudoVideoPersistent:
case kPseudoVideoPersistentAncestor:
if (mode != kUASheetMode) {
pseudo_type_ = kPseudoUnknown;
break;
}
FALLTHROUGH;
// For pseudo classes
case kPseudoActive:
case kPseudoAny:
case kPseudoAnyLink:
case kPseudoAutofill:
case kPseudoAutofillPreviewed:
case kPseudoAutofillSelected:
case kPseudoChecked:
case kPseudoCornerPresent:
case kPseudoDecrement:
case kPseudoDefault:
case kPseudoDefined:
case kPseudoDisabled:
case kPseudoDir:
case kPseudoDoubleButton:
case kPseudoDrag:
case kPseudoEmpty:
case kPseudoEnabled:
case kPseudoEnd:
case kPseudoFirstChild:
case kPseudoFirstOfType:
case kPseudoFocus:
case kPseudoFocusVisible:
case kPseudoFocusWithin:
case kPseudoFullPageMedia:
case kPseudoFullScreen:
case kPseudoFullScreenAncestor:
case kPseudoFullscreen:
case kPseudoFutureCue:
case kPseudoHorizontal:
case kPseudoHost:
case kPseudoHostContext:
case kPseudoHover:
case kPseudoInRange:
case kPseudoIncrement:
case kPseudoIndeterminate:
case kPseudoInvalid:
case kPseudoWhere:
case kPseudoLang:
case kPseudoLastChild:
case kPseudoLastOfType:
case kPseudoLink:
case kPseudoIs:
case kPseudoNoButton:
case kPseudoNot:
case kPseudoNthChild:
case kPseudoNthLastChild:
case kPseudoNthLastOfType:
case kPseudoNthOfType:
case kPseudoOnlyChild:
case kPseudoOnlyOfType:
case kPseudoOptional:
case kPseudoPictureInPicture:
case kPseudoPlaceholderShown:
case kPseudoOutOfRange:
case kPseudoPastCue:
case kPseudoReadOnly:
case kPseudoReadWrite:
case kPseudoRequired:
case kPseudoRoot:
case kPseudoScope:
case kPseudoSingleButton:
case kPseudoStart:
case kPseudoState:
case kPseudoTarget:
case kPseudoUnknown:
case kPseudoValid:
case kPseudoVertical:
case kPseudoVisited:
case kPseudoWebkitAnyLink:
case kPseudoWindowInactive:
case kPseudoXrOverlay:
if (match_ != kPseudoClass)
pseudo_type_ = kPseudoUnknown;
break;
case kPseudoFirstPage:
case kPseudoLeftPage:
case kPseudoRightPage:
pseudo_type_ = kPseudoUnknown;
break;
}
}
bool CSSSelector::operator==(const CSSSelector& other) const {
const CSSSelector* sel1 = this;
const CSSSelector* sel2 = &other;
while (sel1 && sel2) {
if (sel1->Attribute() != sel2->Attribute() ||
sel1->Relation() != sel2->Relation() || sel1->match_ != sel2->match_ ||
sel1->Value() != sel2->Value() ||
sel1->GetPseudoType() != sel2->GetPseudoType() ||
sel1->Argument() != sel2->Argument()) {
return false;
}
if (sel1->match_ == kTag) {
if (sel1->TagQName() != sel2->TagQName())
return false;
}
sel1 = sel1->TagHistory();
sel2 = sel2->TagHistory();
}
if (sel1 || sel2)
return false;
return true;
}
static void SerializeIdentifierOrAny(const AtomicString& identifier,
const AtomicString& any,
StringBuilder& builder) {
if (identifier != any)
SerializeIdentifier(identifier, builder);
else
builder.Append(g_star_atom);
}
static void SerializeNamespacePrefixIfNeeded(const AtomicString& prefix,
const AtomicString& any,
StringBuilder& builder,
bool is_attribute_selector) {
if (prefix.IsNull() || (prefix.IsEmpty() && is_attribute_selector))
return;
SerializeIdentifierOrAny(prefix, any, builder);
builder.Append('|');
}
const CSSSelector* CSSSelector::SerializeCompound(
StringBuilder& builder) const {
if (match_ == kTag && !tag_is_implicit_) {
SerializeNamespacePrefixIfNeeded(TagQName().Prefix(), g_star_atom, builder,
IsAttributeSelector());
SerializeIdentifierOrAny(TagQName().LocalName(), UniversalSelectorAtom(),
builder);
}
for (const CSSSelector* simple_selector = this; simple_selector;
simple_selector = simple_selector->TagHistory()) {
if (simple_selector->match_ == kId) {
builder.Append('#');
SerializeIdentifier(simple_selector->SerializingValue(), builder);
} else if (simple_selector->match_ == kClass) {
builder.Append('.');
SerializeIdentifier(simple_selector->SerializingValue(), builder);
} else if (simple_selector->match_ == kPseudoClass ||
simple_selector->match_ == kPagePseudoClass) {
if (simple_selector->GetPseudoType() != kPseudoState) {
builder.Append(':');
builder.Append(simple_selector->SerializingValue());
}
switch (simple_selector->GetPseudoType()) {
case kPseudoNthChild:
case kPseudoNthLastChild:
case kPseudoNthOfType:
case kPseudoNthLastOfType: {
builder.Append('(');
// https://drafts.csswg.org/css-syntax/#serializing-anb
int a = simple_selector->data_.rare_data_->NthAValue();
int b = simple_selector->data_.rare_data_->NthBValue();
if (a == 0) {
builder.Append(String::Number(b));
} else {
if (a == 1)
builder.Append('n');
else if (a == -1)
builder.Append("-n");
else
builder.AppendFormat("%dn", a);
if (b < 0)
builder.Append(String::Number(b));
else if (b > 0)
builder.AppendFormat("+%d", b);
}
builder.Append(')');
break;
}
case kPseudoDir:
case kPseudoLang:
builder.Append('(');
SerializeIdentifier(simple_selector->Argument(), builder);
builder.Append(')');
break;
case kPseudoNot:
DCHECK(simple_selector->SelectorList());
break;
case kPseudoState:
builder.Append(':');
SerializeIdentifier(simple_selector->SerializingValue(), builder);
break;
case kPseudoHost:
case kPseudoHostContext:
case kPseudoAny:
case kPseudoIs:
case kPseudoWhere:
break;
default:
break;
}
} else if (simple_selector->match_ == kPseudoElement) {
builder.Append("::");
SerializeIdentifier(simple_selector->SerializingValue(), builder);
switch (simple_selector->GetPseudoType()) {
case kPseudoPart: {
char separator = '(';
for (AtomicString part : *simple_selector->PartNames()) {
builder.Append(separator);
if (separator == '(')
separator = ' ';
SerializeIdentifier(part, builder);
}
builder.Append(')');
break;
}
default:
break;
}
} else if (simple_selector->IsAttributeSelector()) {
builder.Append('[');
SerializeNamespacePrefixIfNeeded(simple_selector->Attribute().Prefix(),
g_star_atom, builder,
simple_selector->IsAttributeSelector());
SerializeIdentifier(simple_selector->Attribute().LocalName(), builder);
switch (simple_selector->match_) {
case kAttributeExact:
builder.Append('=');
break;
case kAttributeSet:
// set has no operator or value, just the attrName
builder.Append(']');
break;
case kAttributeList:
builder.Append("~=");
break;
case kAttributeHyphen:
builder.Append("|=");
break;
case kAttributeBegin:
builder.Append("^=");
break;
case kAttributeEnd:
builder.Append("$=");
break;
case kAttributeContain:
builder.Append("*=");
break;
default:
break;
}
if (simple_selector->match_ != kAttributeSet) {
SerializeString(simple_selector->SerializingValue(), builder);
if (simple_selector->AttributeMatch() ==
AttributeMatchType::kCaseInsensitive) {
builder.Append(" i");
} else if (simple_selector->AttributeMatch() ==
AttributeMatchType::kCaseSensitiveAlways) {
DCHECK(RuntimeEnabledFeatures::CSSCaseSensitiveSelectorEnabled());
builder.Append(" s");
}
builder.Append(']');
}
}
if (simple_selector->SelectorList()) {
builder.Append('(');
const CSSSelector* first_sub_selector =
simple_selector->SelectorList()->FirstForCSSOM();
for (const CSSSelector* sub_selector = first_sub_selector; sub_selector;
sub_selector = CSSSelectorList::Next(*sub_selector)) {
if (sub_selector != first_sub_selector)
builder.Append(", ");
builder.Append(sub_selector->SelectorText());
}
builder.Append(')');
}
if (simple_selector->Relation() != kSubSelector)
return simple_selector;
}
return nullptr;
}
String CSSSelector::SelectorText() const {
String result;
for (const CSSSelector* compound = this; compound;
compound = compound->TagHistory()) {
StringBuilder builder;
compound = compound->SerializeCompound(builder);
if (!compound)
return builder.ToString() + result;
DCHECK(compound->Relation() != kSubSelector);
switch (compound->Relation()) {
case kDescendant:
result = " " + builder.ToString() + result;
break;
case kChild:
result = " > " + builder.ToString() + result;
break;
case kDirectAdjacent:
result = " + " + builder.ToString() + result;
break;
case kIndirectAdjacent:
result = " ~ " + builder.ToString() + result;
break;
case kSubSelector:
NOTREACHED();
break;
case kShadowPart:
case kUAShadow:
case kShadowSlot:
result = builder.ToString() + result;
break;
}
}
NOTREACHED();
return String();
}
void CSSSelector::SetAttribute(const QualifiedName& value,
AttributeMatchType match_type) {
CreateRareData();
data_.rare_data_->attribute_ = value;
data_.rare_data_->bits_.attribute_match_ = match_type;
}
void CSSSelector::SetArgument(const AtomicString& value) {
CreateRareData();
data_.rare_data_->argument_ = value;
}
void CSSSelector::SetSelectorList(
std::unique_ptr<CSSSelectorList> selector_list) {
CreateRareData();
data_.rare_data_->selector_list_ = std::move(selector_list);
}
static bool ValidateSubSelector(const CSSSelector* selector) {
switch (selector->Match()) {
case CSSSelector::kTag:
case CSSSelector::kId:
case CSSSelector::kClass:
case CSSSelector::kAttributeExact:
case CSSSelector::kAttributeSet:
case CSSSelector::kAttributeList:
case CSSSelector::kAttributeHyphen:
case CSSSelector::kAttributeContain:
case CSSSelector::kAttributeBegin:
case CSSSelector::kAttributeEnd:
return true;
case CSSSelector::kPseudoElement:
case CSSSelector::kUnknown:
return false;
case CSSSelector::kPagePseudoClass:
case CSSSelector::kPseudoClass:
break;
}
switch (selector->GetPseudoType()) {
case CSSSelector::kPseudoEmpty:
case CSSSelector::kPseudoLink:
case CSSSelector::kPseudoVisited:
case CSSSelector::kPseudoTarget:
case CSSSelector::kPseudoEnabled:
case CSSSelector::kPseudoDisabled:
case CSSSelector::kPseudoChecked:
case CSSSelector::kPseudoIndeterminate:
case CSSSelector::kPseudoNthChild:
case CSSSelector::kPseudoNthLastChild:
case CSSSelector::kPseudoNthOfType:
case CSSSelector::kPseudoNthLastOfType:
case CSSSelector::kPseudoFirstChild:
case CSSSelector::kPseudoLastChild:
case CSSSelector::kPseudoFirstOfType:
case CSSSelector::kPseudoLastOfType:
case CSSSelector::kPseudoOnlyOfType:
case CSSSelector::kPseudoHost:
case CSSSelector::kPseudoHostContext:
case CSSSelector::kPseudoNot:
case CSSSelector::kPseudoSpatialNavigationFocus:
case CSSSelector::kPseudoSpatialNavigationInterest:
case CSSSelector::kPseudoHasDatalist:
case CSSSelector::kPseudoIsHtml:
case CSSSelector::kPseudoListBox:
case CSSSelector::kPseudoHostHasAppearance:
return true;
default:
return false;
}
}
bool CSSSelector::IsCompound() const {
if (!ValidateSubSelector(this))
return false;
const CSSSelector* prev_sub_selector = this;
const CSSSelector* sub_selector = TagHistory();
while (sub_selector) {
if (prev_sub_selector->Relation() != kSubSelector)
return false;
if (!ValidateSubSelector(sub_selector))
return false;
prev_sub_selector = sub_selector;
sub_selector = sub_selector->TagHistory();
}
return true;
}
bool CSSSelector::HasLinkOrVisited() const {
for (const CSSSelector* current = this; current;
current = current->TagHistory()) {
CSSSelector::PseudoType pseudo = current->GetPseudoType();
if (pseudo == CSSSelector::kPseudoLink ||
pseudo == CSSSelector::kPseudoVisited) {
return true;
}
if (const CSSSelectorList* list = current->SelectorList()) {
for (const CSSSelector* sub_selector = list->First(); sub_selector;
sub_selector = CSSSelectorList::Next(*sub_selector)) {
if (sub_selector->HasLinkOrVisited())
return true;
}
}
}
return false;
}
void CSSSelector::SetNth(int a, int b) {
CreateRareData();
data_.rare_data_->bits_.nth_.a_ = a;
data_.rare_data_->bits_.nth_.b_ = b;
}
bool CSSSelector::MatchNth(unsigned count) const {
DCHECK(has_rare_data_);
return data_.rare_data_->MatchNth(count);
}
bool CSSSelector::MatchesPseudoElement() const {
for (const CSSSelector* current = this; current;
current = current->TagHistory()) {
if (current->Match() == kPseudoElement)
return true;
if (current->Relation() != kSubSelector)
return false;
}
return false;
}
bool CSSSelector::IsTreeAbidingPseudoElement() const {
return Match() == CSSSelector::kPseudoElement &&
(GetPseudoType() == kPseudoBefore || GetPseudoType() == kPseudoAfter ||
GetPseudoType() == kPseudoMarker ||
GetPseudoType() == kPseudoPlaceholder ||
GetPseudoType() == kPseudoFileSelectorButton);
}
bool CSSSelector::IsAllowedAfterPart() const {
if (Match() != CSSSelector::kPseudoElement) {
return false;
}
// Everything that makes sense should work following ::part. This list
// restricts it to what has been tested.
switch (GetPseudoType()) {
case kPseudoBefore:
case kPseudoAfter:
case kPseudoPlaceholder:
case kPseudoFileSelectorButton:
case kPseudoFirstLine:
case kPseudoFirstLetter:
case kPseudoSelection:
case kPseudoTargetText:
case kPseudoSpellingError:
case kPseudoGrammarError:
return true;
default:
return false;
}
}
template <typename Functor>
static bool ForAnyInTagHistory(const Functor& functor,
const CSSSelector& selector) {
for (const CSSSelector* current = &selector; current;
current = current->TagHistory()) {
if (functor(*current))
return true;
if (const CSSSelectorList* selector_list = current->SelectorList()) {
for (const CSSSelector* sub_selector = selector_list->First();
sub_selector; sub_selector = CSSSelectorList::Next(*sub_selector)) {
if (ForAnyInTagHistory(functor, *sub_selector))
return true;
}
}
}
return false;
}
bool CSSSelector::HasSlottedPseudo() const {
return ForAnyInTagHistory(
[](const CSSSelector& selector) -> bool {
return selector.GetPseudoType() == CSSSelector::kPseudoSlotted;
},
*this);
}
bool CSSSelector::FollowsPart() const {
const CSSSelector* previous = TagHistory();
if (!previous)
return false;
return previous->GetPseudoType() == kPseudoPart;
}
String CSSSelector::FormatPseudoTypeForDebugging(PseudoType type) {
for (const auto& s : kPseudoTypeWithoutArgumentsMap) {
if (s.type == type)
return s.string;
}
for (const auto& s : kPseudoTypeWithArgumentsMap) {
if (s.type == type)
return s.string;
}
StringBuilder builder;
builder.Append("pseudo-");
builder.AppendNumber(static_cast<int>(type));
return builder.ToString();
}
CSSSelector::RareData::RareData(const AtomicString& value)
: matching_value_(value),
serializing_value_(value),
bits_(),
attribute_(AnyQName()),
argument_(g_null_atom) {}
CSSSelector::RareData::~RareData() = default;
// a helper function for checking nth-arguments
bool CSSSelector::RareData::MatchNth(unsigned unsigned_count) {
// These very large values for aN + B or count can't ever match, so
// give up immediately if we see them.
int max_value = std::numeric_limits<int>::max() / 2;
int min_value = std::numeric_limits<int>::min() / 2;
if (UNLIKELY(unsigned_count > static_cast<unsigned>(max_value) ||
NthAValue() > max_value || NthAValue() < min_value ||
NthBValue() > max_value || NthBValue() < min_value))
return false;
int count = static_cast<int>(unsigned_count);
if (!NthAValue())
return count == NthBValue();
if (NthAValue() > 0) {
if (count < NthBValue())
return false;
return (count - NthBValue()) % NthAValue() == 0;
}
if (count > NthBValue())
return false;
return (NthBValue() - count) % (-NthAValue()) == 0;
}
void CSSSelector::SetPartNames(
std::unique_ptr<Vector<AtomicString>> part_names) {
CreateRareData();
data_.rare_data_->part_names_ = std::move(part_names);
}
} // namespace blink