use crate::ArenaVec;
use core::fmt;
use bun_alloc::{Arena as Bump, ArenaPtr};
use bun_core::strings;
use bun_css as css;
use bun_css::css_values::ident::{CustomIdent, Ident};
use bun_css::selector::serialize;
use bun_css::{
CSSStringFns, IdentFns, Parser as CssParser, ParserOptions, PrintErr, Printer, SmallList,
Token, TokenList,
};
use bun_wyhash::Wyhash;
use super::impl_;
use super::builder::SelectorBuilder;
pub use bun_css::Printer as PrinterRe;
type CResult<T> = css::Result<T>;
type Str = &'static [u8];
use css::generics::{CssEql, CssHash};
use bun_alloc::core_alloc::AllocVec as _PVec;
use bun_alloc::core_alloc::Global as _PG;
type PlainVec2<T> = _PVec<T, _PG>;
fn small_list_into_box<T, const N: usize>(mut sl: SmallList<T, N>) -> Box<[T]> {
let len = sl.len() as usize;
let mut v: Vec<T> = Vec::with_capacity(len);
{
let src = sl.slice();
for i in 0..len {
unsafe { v.push(core::ptr::read(src.get_unchecked(i))) };
}
}
sl.set_len(0);
v.into_boxed_slice()
}
#[inline]
fn arena_lowercase(bump: &Bump, name: &[u8]) -> *const [u8] {
let buf = bump.alloc_slice_fill_copy(name.len(), 0u8);
let _ = strings::copy_lowercase(name, buf);
std::ptr::from_ref::<[u8]>(buf)
}
#[inline]
fn eql_selector_slice<Impl: BunSelectorImpl>(
a: &[GenericSelector<Impl>],
b: &[GenericSelector<Impl>],
) -> bool {
a.len() == b.len() && a.iter().zip(b).all(|(l, r)| l.eql(r))
}
#[inline]
fn hash_selector_slice<Impl: BunSelectorImpl>(s: &[GenericSelector<Impl>], hasher: &mut Wyhash) {
for sel in s {
sel.hash(hasher);
}
}
#[inline]
fn deep_clone_selector_slice<Impl: BunSelectorImpl>(
s: &[GenericSelector<Impl>],
) -> Box<[GenericSelector<Impl>]> {
s.iter().map(|sel| sel.deep_clone()).collect()
}
pub type Component = GenericComponent<impl_::Selectors>;
pub type Selector = GenericSelector<impl_::Selectors>;
pub type SelectorList = GenericSelectorList<impl_::Selectors>;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ToCssCtx {
Lightning,
Servo,
}
pub const SELECTOR_WHITESPACE: &[u8] = &[b' ', b'\t', b'\n', b'\r', 0x0C];
pub fn valid_selector_impl<T: SelectorImpl>() {
}
pub trait SelectorImpl: Sized {
type ExtraMatchingData;
type AttrValue: Clone;
type Identifier: Clone;
type LocalIdentifier: Clone;
type LocalName: Clone;
type NamespaceUrl: Clone;
type NamespacePrefix: Clone;
type BorrowedNamespaceUrl;
type BorrowedLocalName;
type NonTSPseudoClass: Clone;
type VendorPrefix: Clone;
type PseudoElement: Clone;
}
pub trait BunSelectorImpl:
SelectorImpl<
AttrValue = css::CSSString,
Identifier = Ident,
LocalIdentifier = css::css_values::ident::IdentOrRef,
LocalName = Ident,
NamespaceUrl = Str,
NamespacePrefix = Ident,
NonTSPseudoClass = PseudoClass,
PseudoElement = PseudoElement,
VendorPrefix = css::VendorPrefix,
>
{
}
impl BunSelectorImpl for impl_::Selectors {}
pub mod attrs {
use super::*;
#[derive(Clone)]
pub struct NamespaceUrl<Impl: SelectorImpl> {
pub prefix: Impl::NamespacePrefix,
pub url: Impl::NamespaceUrl,
}
impl<Impl: BunSelectorImpl> NamespaceUrl<Impl> {
pub fn eql(&self, rhs: &Self) -> bool {
self.prefix.eql(&rhs.prefix) && strings::eql(self.url, rhs.url)
}
pub fn deep_clone(&self) -> Self {
Self {
prefix: self.prefix,
url: self.url,
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
self.prefix.hash(hasher);
hasher.update(self.url);
}
}
#[derive(Clone)]
pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> {
pub namespace: Option<NamespaceConstraint<NamespaceUrl<Impl>>>,
pub local_name: Impl::LocalName,
pub local_name_lower: Impl::LocalName,
pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
pub never_matches: bool,
}
impl<Impl: BunSelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
dest.write_char(b'[')?;
if let Some(nsp) = &self.namespace {
match nsp {
NamespaceConstraint::Specific(v) => {
IdentFns::to_css(&v.prefix, dest)?;
dest.write_char(b'|')?;
}
NamespaceConstraint::Any => {
dest.write_str("*|")?;
}
}
}
IdentFns::to_css(&self.local_name, dest)?;
match &self.operation {
ParsedAttrSelectorOperation::Exists => {}
ParsedAttrSelectorOperation::WithValue {
operator,
case_sensitivity,
expected_value,
} => {
operator.to_css(dest)?;
CSSStringFns::to_css(expected_value, dest)?;
match case_sensitivity {
ParsedCaseSensitivity::CaseSensitive
| ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}
ParsedCaseSensitivity::AsciiCaseInsensitive => {
dest.write_str(" i")?;
}
ParsedCaseSensitivity::ExplicitCaseSensitive => {
dest.write_str(" s")?;
}
}
}
}
dest.write_char(b']')
}
pub fn eql(&self, rhs: &Self) -> bool {
match (&self.namespace, &rhs.namespace) {
(None, None) => true,
(Some(a), Some(b)) => a.eql(b),
_ => return false,
}
.then_some(())
.is_some()
&& self.local_name.eql(&rhs.local_name)
&& self.local_name_lower.eql(&rhs.local_name_lower)
&& self.operation.eql(&rhs.operation)
&& self.never_matches == rhs.never_matches
}
pub fn deep_clone(&self) -> Self {
Self {
namespace: self.namespace.as_ref().map(|n| n.deep_clone()),
local_name: self.local_name,
local_name_lower: self.local_name_lower,
operation: self.operation.deep_clone(),
never_matches: self.never_matches,
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
if let Some(ns) = &self.namespace {
ns.hash(hasher);
}
self.local_name.hash(hasher);
self.local_name_lower.hash(hasher);
self.operation.hash(hasher);
hasher.update(&[self.never_matches as u8]);
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum NamespaceConstraint<NamespaceUrl> {
Any,
Specific(NamespaceUrl),
}
impl<Impl: BunSelectorImpl> NamespaceConstraint<NamespaceUrl<Impl>> {
pub fn eql(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::Any, Self::Any) => true,
(Self::Specific(a), Self::Specific(b)) => a.eql(b),
_ => false,
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
match self {
Self::Any => hasher.update(&0u32.to_ne_bytes()),
Self::Specific(n) => {
hasher.update(&1u32.to_ne_bytes());
n.hash(hasher);
}
}
}
pub fn deep_clone(&self) -> Self {
match self {
Self::Any => Self::Any,
Self::Specific(n) => Self::Specific(n.deep_clone()),
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum ParsedAttrSelectorOperation<AttrValue> {
Exists,
WithValue {
operator: AttrSelectorOperator,
case_sensitivity: ParsedCaseSensitivity,
expected_value: AttrValue,
},
}
impl ParsedAttrSelectorOperation<css::CSSString> {
pub fn deep_clone(&self) -> Self {
self.clone()
}
pub fn eql(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::Exists, Self::Exists) => true,
(
Self::WithValue {
operator: ao,
case_sensitivity: ac,
expected_value: av,
},
Self::WithValue {
operator: bo,
case_sensitivity: bc,
expected_value: bv,
},
) => {
ao == bo
&& ac == bc
&& unsafe { strings::eql(&**av, &**bv) }
}
_ => false,
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
match self {
Self::Exists => hasher.update(&0u32.to_ne_bytes()),
Self::WithValue {
operator,
case_sensitivity,
expected_value,
} => {
hasher.update(&1u32.to_ne_bytes());
operator.hash(hasher);
case_sensitivity.hash(hasher);
hasher.update(unsafe { crate::arena_str(*expected_value) });
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, CssEql, CssHash, css::generics::DeepClone)]
pub enum AttrSelectorOperator {
Equal,
Includes,
DashMatch,
Prefix,
Substring,
Suffix,
}
impl AttrSelectorOperator {
pub fn to_css(self, dest: &mut Printer) -> Result<(), PrintErr> {
dest.write_str(match self {
Self::Equal => "=",
Self::Includes => "~=",
Self::DashMatch => "|=",
Self::Prefix => "^=",
Self::Substring => "*=",
Self::Suffix => "$=",
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrSelectorOperation {
Equal,
Includes,
DashMatch,
Prefix,
Substring,
Suffix,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, CssEql, CssHash, css::generics::DeepClone)]
pub enum ParsedCaseSensitivity {
ExplicitCaseSensitive,
AsciiCaseInsensitive,
CaseSensitive,
AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
}
}
#[derive(Clone, Copy, Default)]
pub struct Specificity {
pub id_selectors: u32,
pub class_like_selectors: u32,
pub element_selectors: u32,
}
impl Specificity {
const MAX_10BIT: u32 = (1 << 10) - 1;
pub fn to_u32(self) -> u32 {
(self.id_selectors.min(Self::MAX_10BIT) << 20)
| (self.class_like_selectors.min(Self::MAX_10BIT) << 10)
| self.element_selectors.min(Self::MAX_10BIT)
}
pub fn from_u32(value: u32) -> Specificity {
debug_assert!(value <= (Self::MAX_10BIT << 20 | Self::MAX_10BIT << 10 | Self::MAX_10BIT));
Specificity {
id_selectors: value >> 20,
class_like_selectors: (value >> 10) & Self::MAX_10BIT,
element_selectors: value & Self::MAX_10BIT,
}
}
pub fn add(&mut self, rhs: Specificity) {
self.id_selectors += rhs.id_selectors;
self.element_selectors += rhs.element_selectors;
self.class_like_selectors += rhs.class_like_selectors;
}
}
pub fn compute_specificity<Impl: BunSelectorImpl>(iter: &[GenericComponent<Impl>]) -> u32 {
let spec = compute_complex_selector_specificity::<Impl>(iter);
spec.to_u32()
}
fn compute_complex_selector_specificity<Impl: BunSelectorImpl>(
iter: &[GenericComponent<Impl>],
) -> Specificity {
let mut specificity = Specificity::default();
for simple_selector in iter {
compute_simple_selector_specificity::<Impl>(simple_selector, &mut specificity);
}
specificity
}
fn compute_simple_selector_specificity<Impl: BunSelectorImpl>(
simple_selector: &GenericComponent<Impl>,
specificity: &mut Specificity,
) {
use GenericComponent as C;
match simple_selector {
C::Combinator(_) => {
unreachable!("Found combinator in simple selectors vector?");
}
C::Part(_) | C::PseudoElement(_) | C::LocalName(_) => {
specificity.element_selectors += 1;
}
C::Slotted(selector) => {
specificity.element_selectors += 1;
specificity.add(Specificity::from_u32(selector.specificity()));
}
C::Host(maybe_selector) => {
specificity.class_like_selectors += 1;
if let Some(selector) = maybe_selector {
specificity.add(Specificity::from_u32(selector.specificity()));
}
}
C::Id(_) => {
specificity.id_selectors += 1;
}
C::Class(_)
| C::AttributeInNoNamespace { .. }
| C::AttributeInNoNamespaceExists { .. }
| C::AttributeOther(_)
| C::Root
| C::Empty
| C::Scope
| C::Nth(_)
| C::NonTsPseudoClass(_) => {
specificity.class_like_selectors += 1;
}
C::NthOf(nth_of_data) => {
specificity.class_like_selectors += 1;
let mut max: u32 = 0;
for selector in nth_of_data.selectors.iter() {
max = selector.specificity().max(max);
}
specificity.add(Specificity::from_u32(max));
}
C::Negation(_) | C::Is(_) | C::Any { .. } => {
let list: &[GenericSelector<Impl>] = match simple_selector {
C::Negation(list) => list,
C::Is(list) => list,
C::Any { selectors, .. } => selectors,
_ => unreachable!(),
};
let mut max: u32 = 0;
for selector in list {
max = selector.specificity().max(max);
}
specificity.add(Specificity::from_u32(max));
}
C::Where(_)
| C::Has(_)
| C::ExplicitUniversalType
| C::ExplicitAnyNamespace
| C::ExplicitNoNamespace
| C::DefaultNamespace(_)
| C::Namespace { .. } => {
}
C::Nesting => {
}
}
}
fn parse_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
nesting_requirement: NestingRequirement,
) -> CResult<GenericSelector<Impl>> {
if nesting_requirement == NestingRequirement::Prefixed {
let parser_state = input.state();
if !input.expect_delim(b'&').is_ok() {
return Err(input.new_custom_error(
SelectorParseErrorKind::MissingNestingPrefix.into_default_parser_error(),
));
}
input.reset(&parser_state);
}
let mut builder = SelectorBuilder::<Impl>::init_in(ArenaPtr::new(input.arena()));
'outer_loop: loop {
let empty = parse_compound_selector::<Impl>(parser, state, input, &mut builder)?;
if empty {
let kind: SelectorParseErrorKind = if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
} else {
SelectorParseErrorKind::EmptySelector
};
return Err(input.new_custom_error(kind.into_default_parser_error()));
}
if state.after_any_pseudo() {
let source_location = input.current_source_location();
if let Ok(next) = input.next() {
return Err(source_location.new_custom_error(
SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(next.clone())
.into_default_parser_error(),
));
}
break;
}
let combinator: Combinator;
let mut any_whitespace = false;
loop {
let before_this_token = input.state();
let tok: &Token = match input.next_including_whitespace() {
Ok(vv) => vv,
Err(_) => break 'outer_loop,
};
match tok {
Token::Whitespace(_) => {
any_whitespace = true;
continue;
}
Token::Delim(d) => match u8::try_from(*d).ok() {
Some(b'>') => {
if parser.deep_combinator_enabled()
&& input
.try_parse(|i: &mut CssParser| -> CResult<()> {
i.expect_delim(b'>')?;
i.expect_delim(b'>')
})
.is_ok()
{
combinator = Combinator::DeepDescendant;
} else {
combinator = Combinator::Child;
}
break;
}
Some(b'+') => {
combinator = Combinator::NextSibling;
break;
}
Some(b'~') => {
combinator = Combinator::LaterSibling;
break;
}
Some(b'/') => {
if parser.deep_combinator_enabled() {
if input
.try_parse(|i: &mut CssParser| -> CResult<()> {
i.expect_ident_matching(b"deep")?;
i.expect_delim(b'/')
})
.is_ok()
{
combinator = Combinator::Deep;
break;
} else {
break 'outer_loop;
}
}
}
_ => {}
},
_ => {}
}
input.reset(&before_this_token);
if any_whitespace {
combinator = Combinator::Descendant;
break;
} else {
break 'outer_loop;
}
}
if !state.allows_combinators() {
return Err(input.new_custom_error(
SelectorParseErrorKind::InvalidState.into_default_parser_error(),
));
}
builder.push_combinator(combinator);
}
if !state.contains(SelectorParsingState::AFTER_NESTING) {
match nesting_requirement {
NestingRequirement::Implicit => {
builder.add_nesting_prefix();
}
NestingRequirement::Contained | NestingRequirement::Prefixed => {
return Err(input.new_custom_error(
SelectorParseErrorKind::MissingNestingSelector.into_default_parser_error(),
));
}
_ => {}
}
}
let has_pseudo_element = state.contains(SelectorParsingState::AFTER_PSEUDO_ELEMENT)
|| state.contains(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT);
let slotted = state.contains(SelectorParsingState::AFTER_SLOTTED);
let part = state.contains(SelectorParsingState::AFTER_PART);
let result = builder.build(has_pseudo_element, slotted, part);
Ok(GenericSelector {
specificity_and_flags: result.specificity_and_flags,
components: result.components,
})
}
fn parse_compound_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
state: &mut SelectorParsingState,
input: &mut CssParser,
builder: &mut SelectorBuilder<Impl>,
) -> CResult<bool> {
input.skip_whitespace();
let mut empty: bool = true;
if parser.is_nesting_allowed() && input.try_parse(|i| i.expect_delim(b'&')).is_ok() {
state.insert(SelectorParsingState::AFTER_NESTING);
builder.push_simple_selector(GenericComponent::Nesting);
empty = false;
}
if parse_type_selector::<Impl>(parser, input, *state, builder).is_ok() {
empty = false;
}
loop {
let result: SimpleSelectorParseResult<Impl> = {
let ret = parse_one_simple_selector::<Impl>(parser, input, state)?;
match ret {
Some(result) => result,
None => break,
}
};
if empty {
if let Some(url) = parser.default_namespace() {
let ignore_default_ns = state
.contains(SelectorParsingState::SKIP_DEFAULT_NAMESPACE)
|| matches!(
result,
SimpleSelectorParseResult::SimpleSelector(GenericComponent::Host(_))
);
if !ignore_default_ns {
builder.push_simple_selector(GenericComponent::DefaultNamespace(url));
}
}
}
empty = false;
match result {
SimpleSelectorParseResult::SimpleSelector(s) => {
builder.push_simple_selector(s);
}
SimpleSelectorParseResult::PartPseudo(selector) => {
state.insert(SelectorParsingState::AFTER_PART);
builder.push_combinator(Combinator::Part);
builder.push_simple_selector(GenericComponent::Part(selector));
}
SimpleSelectorParseResult::SlottedPseudo(selector) => {
state.insert(SelectorParsingState::AFTER_SLOTTED);
builder.push_combinator(Combinator::SlotAssignment);
builder.push_simple_selector(GenericComponent::Slotted(selector));
}
SimpleSelectorParseResult::PseudoElement(p) => {
if !p.is_unknown() {
state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
builder.push_combinator(Combinator::PseudoElement);
} else {
state.insert(SelectorParsingState::AFTER_UNKNOWN_PSEUDO_ELEMENT);
}
if !p.accepts_state_pseudo_classes() {
state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT);
}
if p.is_webkit_scrollbar() {
state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR);
}
if p.is_view_transition() {
state.insert(SelectorParsingState::AFTER_VIEW_TRANSITION);
}
builder.push_simple_selector(GenericComponent::PseudoElement(p));
}
}
}
Ok(empty)
}
fn parse_relative_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
nesting_requirement_: NestingRequirement,
) -> CResult<GenericSelector<Impl>> {
let mut nesting_requirement = nesting_requirement_;
let s = input.state();
let combinator: Option<Combinator> = 'combinator: {
let tok = input.next()?;
if let Token::Delim(c) = tok {
match u8::try_from(*c).ok() {
Some(b'>') => break 'combinator Some(Combinator::Child),
Some(b'+') => break 'combinator Some(Combinator::NextSibling),
Some(b'~') => break 'combinator Some(Combinator::LaterSibling),
_ => {}
}
}
input.reset(&s);
None
};
let scope: GenericComponent<Impl> = if nesting_requirement == NestingRequirement::Implicit {
GenericComponent::Nesting
} else {
GenericComponent::Scope
};
if combinator.is_some() {
nesting_requirement = NestingRequirement::None;
}
let mut selector = parse_selector::<Impl>(parser, input, state, nesting_requirement)?;
if let Some(wombo_combo) = combinator {
selector
.components
.push(GenericComponent::Combinator(wombo_combo));
selector.components.push(scope);
}
Ok(selector)
}
pub fn valid_selector_parser<T>() {
}
pub use css::css_properties::text::Direction;
#[derive(Clone, CssEql, CssHash)]
pub enum PseudoClass {
Lang {
languages: PlainVec2<Str>,
},
Dir {
direction: Direction,
},
Hover,
Active,
Focus,
FocusVisible,
FocusWithin,
Current,
Past,
Future,
Playing,
Paused,
Seeking,
Buffering,
Stalled,
Muted,
VolumeLocked,
Fullscreen(css::VendorPrefix),
Open,
Closed,
Modal,
PictureInPicture,
PopoverOpen,
Defined,
AnyLink(css::VendorPrefix),
Link,
LocalLink,
Target,
TargetWithin,
Visited,
Enabled,
Disabled,
ReadOnly(css::VendorPrefix),
ReadWrite(css::VendorPrefix),
PlaceholderShown(css::VendorPrefix),
Default,
Checked,
Indeterminate,
Blank,
Valid,
Invalid,
InRange,
OutOfRange,
Required,
Optional,
UserValid,
UserInvalid,
Autofill(css::VendorPrefix),
Local {
selector: Box<Selector>,
},
Global {
selector: Box<Selector>,
},
WebkitScrollbar(WebKitScrollbarPseudoClass),
Custom {
name: Str,
},
CustomFunction {
name: Str,
arguments: TokenList,
},
}
impl PseudoClass {
pub fn is_equivalent(&self, other: &PseudoClass) -> bool {
use PseudoClass as P;
if matches!(self, P::Fullscreen(_)) && matches!(other, P::Fullscreen(_)) {
return true;
}
if matches!(self, P::AnyLink(_)) && matches!(other, P::AnyLink(_)) {
return true;
}
if matches!(self, P::ReadOnly(_)) && matches!(other, P::ReadOnly(_)) {
return true;
}
if matches!(self, P::ReadWrite(_)) && matches!(other, P::ReadWrite(_)) {
return true;
}
if matches!(self, P::PlaceholderShown(_)) && matches!(other, P::PlaceholderShown(_)) {
return true;
}
if matches!(self, P::Autofill(_)) && matches!(other, P::Autofill(_)) {
return true;
}
self.eql(other)
}
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
serialize::serialize_pseudo_class(self, dest, None)
}
pub fn deep_clone(&self) -> Self {
self.clone()
}
pub fn get_prefix(&self) -> css::VendorPrefix {
use PseudoClass as P;
match self {
P::Fullscreen(p)
| P::AnyLink(p)
| P::ReadOnly(p)
| P::ReadWrite(p)
| P::PlaceholderShown(p)
| P::Autofill(p) => *p,
_ => css::VendorPrefix::empty(),
}
}
pub fn get_necessary_prefixes(&mut self, targets: &css::targets::Targets) -> css::VendorPrefix {
use PseudoClass as P;
use css::prefixes::Feature as F;
let (p, feature): (&mut css::VendorPrefix, F) = match self {
P::Fullscreen(p) => (p, F::PseudoClassFullscreen),
P::AnyLink(p) => (p, F::PseudoClassAnyLink),
P::ReadOnly(p) => (p, F::PseudoClassReadOnly),
P::ReadWrite(p) => (p, F::PseudoClassReadWrite),
P::PlaceholderShown(p) => (p, F::PseudoClassPlaceholderShown),
P::Autofill(p) => (p, F::PseudoClassAutofill),
_ => return css::VendorPrefix::empty(),
};
*p = targets.prefixes(*p, feature);
*p
}
pub fn is_user_action_state(&self) -> bool {
use PseudoClass as P;
matches!(
self,
P::Active | P::Hover | P::Focus | P::FocusWithin | P::FocusVisible
)
}
pub fn is_valid_before_webkit_scrollbar(&self) -> bool {
!matches!(self, PseudoClass::WebkitScrollbar(_))
}
pub fn is_valid_after_webkit_scrollbar(&self) -> bool {
use PseudoClass as P;
matches!(
self,
P::WebkitScrollbar(_) | P::Enabled | P::Disabled | P::Hover | P::Active
)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, CssEql, CssHash, css::generics::DeepClone)]
pub enum WebKitScrollbarPseudoClass {
Horizontal,
Vertical,
Decrement,
Increment,
Start,
End,
DoubleButton,
SingleButton,
NoButton,
CornerPresent,
WindowInactive,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, CssHash)]
pub enum WebKitScrollbarPseudoElement {
Scrollbar,
Button,
Track,
TrackPiece,
Thumb,
Corner,
Resizer,
}
impl WebKitScrollbarPseudoElement {
#[inline]
pub fn eql(self, rhs: Self) -> bool {
self == rhs
}
}
pub struct SelectorParser<'a> {
pub is_nesting_allowed: bool,
pub options: &'a ParserOptions<'a>,
}
pub type SelectorParserImpl = impl_::Selectors;
impl<'a> SelectorParser<'a> {
pub fn new_local_identifier(
&mut self,
input: &mut CssParser,
tag: css::CssRefTag,
raw: Str,
loc: usize,
) -> <impl_::Selectors as SelectorImpl>::LocalIdentifier {
if input.flags.css_modules() {
return <impl_::Selectors as SelectorImpl>::LocalIdentifier::from_ref(
input.add_symbol_for_name(
raw,
tag,
bun_ast::Loc {
start: i32::try_from(loc).expect("int cast"),
},
),
crate::values::ident::debug_ident(raw, input.arena()),
);
}
let _ = (input, tag, loc);
<impl_::Selectors as SelectorImpl>::LocalIdentifier::from_ident(Ident {
v: std::ptr::from_ref::<[u8]>(raw),
})
}
pub fn namespace_for_prefix(&mut self, prefix: Ident) -> Option<Str> {
let _ = self;
Some(unsafe { crate::arena_str(prefix.v) })
}
pub fn parse_functional_pseudo_element(
&mut self,
name: Str,
input: &mut CssParser,
) -> CResult<PseudoElement> {
match name.len() {
3 if name == b"cue" => {
return Ok(PseudoElement::CueFunction {
selector: Box::new(Selector::parse(self, input)?),
});
}
10 if name == b"cue-region" => {
return Ok(PseudoElement::CueRegionFunction {
selector: Box::new(Selector::parse(self, input)?),
});
}
19 => match name {
b"view-transition-old" => {
return Ok(PseudoElement::ViewTransitionOld {
part_name: ViewTransitionPartName::parse(input)?,
});
}
b"view-transition-new" => {
return Ok(PseudoElement::ViewTransitionNew {
part_name: ViewTransitionPartName::parse(input)?,
});
}
_ => {}
},
21 if name == b"view-transition-group" => {
return Ok(PseudoElement::ViewTransitionGroup {
part_name: ViewTransitionPartName::parse(input)?,
});
}
26 if name == b"view-transition-image-pair" => {
return Ok(PseudoElement::ViewTransitionImagePair {
part_name: ViewTransitionPartName::parse(input)?,
});
}
_ => {}
}
if !strings::starts_with(name, b"-") {
self.options.warn(
&input.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)
.into_default_parser_error(),
),
);
}
{
let mut args: PlainVec2<css::css_properties::custom::TokenOrValue> = PlainVec2::new();
TokenList::parse_raw(input, &mut args, self.options, 0)?;
return Ok(PseudoElement::CustomFunction {
name,
arguments: TokenList { v: args },
});
}
}
fn parse_is_and_where(&self) -> bool {
let _ = self;
true
}
fn parse_any_prefix(&self, name: &[u8]) -> Option<css::VendorPrefix> {
crate::match_ignore_ascii_case! { name, {
b"-webkit-any" => Some(css::VendorPrefix::WEBKIT),
b"-moz-any" => Some(css::VendorPrefix::MOZ),
_ => None,
}}
}
pub fn parse_non_ts_pseudo_class(
&mut self,
loc: css::SourceLocation,
name: Str,
) -> CResult<PseudoClass> {
let pseudo_class: PseudoClass = 'pseudo_class: {
if let Some(pseudo) = lookup_non_ts_pseudo_class(name) {
break 'pseudo_class pseudo;
}
if strings::starts_with_char(name, b'_') {
self.options.warn(&loc.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
));
} else if (self.options.css_modules.is_some()
&& strings::eql_case_insensitive_ascii_check_length(name, b"local"))
|| strings::eql_case_insensitive_ascii_check_length(name, b"global")
{
return Err(
loc.new_custom_error(SelectorParseErrorKind::AmbiguousCssModuleClass(name))
);
}
return Ok(PseudoClass::Custom { name });
};
Ok(pseudo_class)
}
pub fn parse_host(&mut self) -> bool {
true
}
pub fn parse_non_ts_functional_pseudo_class(
&mut self,
name: Str,
parser: &mut CssParser,
) -> CResult<PseudoClass> {
let pseudo_class = crate::match_ignore_ascii_case! { name, {
b"lang" => {
let languages = parser.parse_comma_separated(|p| -> CResult<Str> {
let loc = p.current_source_location();
let tok = p.next()?.clone();
match tok {
Token::Ident(i) | Token::QuotedString(i) => Ok(i),
t => Err(loc.new_unexpected_token_error(t)),
}
})?;
return Ok(PseudoClass::Lang { languages });
},
b"dir" => PseudoClass::Dir {
direction: Direction::parse(parser)?,
},
b"local" if self.options.css_modules.is_some() => PseudoClass::Local {
selector: Box::new(Selector::parse(self, parser)?),
},
b"global" if self.options.css_modules.is_some() => PseudoClass::Global {
selector: Box::new(Selector::parse(self, parser)?),
},
_ => {
if !strings::starts_with_char(name, b'-') {
self.options.warn(
&parser.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name)
.into_default_parser_error(),
),
);
}
let mut args: PlainVec2<css::css_properties::custom::TokenOrValue> = PlainVec2::new();
css::TokenListFns::parse_raw(parser, &mut args, self.options, 0)?;
PseudoClass::CustomFunction {
name,
arguments: TokenList { v: args },
}
},
}};
Ok(pseudo_class)
}
pub fn is_nesting_allowed(&self) -> bool {
self.is_nesting_allowed
}
pub fn deep_combinator_enabled(&self) -> bool {
self.options
.flags
.contains(css::ParserFlags::DEEP_SELECTOR_COMBINATOR)
}
pub fn default_namespace(&self) -> Option<<impl_::Selectors as SelectorImpl>::NamespaceUrl> {
let _ = self;
None
}
pub fn parse_part(&self) -> bool {
let _ = self;
true
}
pub fn parse_slotted(&self) -> bool {
let _ = self;
true
}
fn is_and_where_error_recovery(&self) -> ParseErrorRecovery {
let _ = self;
ParseErrorRecovery::IgnoreInvalidSelector
}
pub fn parse_pseudo_element(
&mut self,
loc: css::SourceLocation,
name: Str,
) -> CResult<PseudoElement> {
let pseudo_element = lookup_pseudo_element(name).unwrap_or_else(|| {
if !strings::starts_with_char(name, b'-') {
self.options.warn(&loc.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name),
));
}
PseudoElement::Custom { name }
});
Ok(pseudo_element)
}
}
fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option<PseudoClass> {
use PseudoClass as P;
use WebKitScrollbarPseudoClass as WS;
use css::VendorPrefix as VP;
Some(crate::match_ignore_ascii_case! { name, {
b"hover" => P::Hover,
b"active" => P::Active,
b"focus" => P::Focus,
b"focus-visible" => P::FocusVisible,
b"focus-within" => P::FocusWithin,
b"current" => P::Current,
b"past" => P::Past,
b"future" => P::Future,
b"playing" => P::Playing,
b"paused" => P::Paused,
b"seeking" => P::Seeking,
b"buffering" => P::Buffering,
b"stalled" => P::Stalled,
b"muted" => P::Muted,
b"volume-locked" => P::VolumeLocked,
b"fullscreen" => P::Fullscreen(VP::NONE),
b"-webkit-full-screen" => P::Fullscreen(VP::WEBKIT),
b"-moz-full-screen" => P::Fullscreen(VP::MOZ),
b"-ms-fullscreen" => P::Fullscreen(VP::MS),
b"open" => P::Open,
b"closed" => P::Closed,
b"modal" => P::Modal,
b"picture-in-picture" => P::PictureInPicture,
b"popover-open" => P::PopoverOpen,
b"defined" => P::Defined,
b"any-link" => P::AnyLink(VP::NONE),
b"-webkit-any-link" => P::AnyLink(VP::WEBKIT),
b"-moz-any-link" => P::AnyLink(VP::MOZ),
b"link" => P::Link,
b"local-link" => P::LocalLink,
b"target" => P::Target,
b"target-within" => P::TargetWithin,
b"visited" => P::Visited,
b"enabled" => P::Enabled,
b"disabled" => P::Disabled,
b"read-only" => P::ReadOnly(VP::NONE),
b"-moz-read-only" => P::ReadOnly(VP::MOZ),
b"read-write" => P::ReadWrite(VP::NONE),
b"-moz-read-write" => P::ReadWrite(VP::MOZ),
b"placeholder-shown" => P::PlaceholderShown(VP::NONE),
b"-moz-placeholder-shown" => P::PlaceholderShown(VP::MOZ),
b"-ms-placeholder-shown" => P::PlaceholderShown(VP::MS),
b"default" => P::Default,
b"checked" => P::Checked,
b"indeterminate" => P::Indeterminate,
b"blank" => P::Blank,
b"valid" => P::Valid,
b"invalid" => P::Invalid,
b"in-range" => P::InRange,
b"out-of-range" => P::OutOfRange,
b"required" => P::Required,
b"optional" => P::Optional,
b"user-valid" => P::UserValid,
b"user-invalid" => P::UserInvalid,
b"autofill" => P::Autofill(VP::NONE),
b"-webkit-autofill" => P::Autofill(VP::WEBKIT),
b"-o-autofill" => P::Autofill(VP::O),
b"horizontal" => P::WebkitScrollbar(WS::Horizontal),
b"vertical" => P::WebkitScrollbar(WS::Vertical),
b"decrement" => P::WebkitScrollbar(WS::Decrement),
b"increment" => P::WebkitScrollbar(WS::Increment),
b"start" => P::WebkitScrollbar(WS::Start),
b"end" => P::WebkitScrollbar(WS::End),
b"double-button" => P::WebkitScrollbar(WS::DoubleButton),
b"single-button" => P::WebkitScrollbar(WS::SingleButton),
b"no-button" => P::WebkitScrollbar(WS::NoButton),
b"corner-present" => P::WebkitScrollbar(WS::CornerPresent),
b"window-inactive" => P::WebkitScrollbar(WS::WindowInactive),
_ => return None,
} })
}
fn lookup_pseudo_element(name: &[u8]) -> Option<PseudoElement> {
use PseudoElement as PE;
use WebKitScrollbarPseudoElement as WS;
use css::VendorPrefix as VP;
Some(crate::match_ignore_ascii_case! { name, {
b"before" => PE::Before,
b"after" => PE::After,
b"first-line" => PE::FirstLine,
b"first-letter" => PE::FirstLetter,
b"cue" => PE::Cue,
b"cue-region" => PE::CueRegion,
b"selection" => PE::Selection(VP::NONE),
b"-moz-selection" => PE::Selection(VP::MOZ),
b"placeholder" => PE::Placeholder(VP::NONE),
b"-webkit-input-placeholder" => PE::Placeholder(VP::WEBKIT),
b"-moz-placeholder" => PE::Placeholder(VP::MOZ),
b"-ms-input-placeholder" => PE::Placeholder(VP::MS),
b"marker" => PE::Marker,
b"backdrop" => PE::Backdrop(VP::NONE),
b"-webkit-backdrop" => PE::Backdrop(VP::WEBKIT),
b"file-selector-button" => PE::FileSelectorButton(VP::NONE),
b"-webkit-file-upload-button" => PE::FileSelectorButton(VP::WEBKIT),
b"-ms-browse" => PE::FileSelectorButton(VP::MS),
b"-webkit-scrollbar" => PE::WebkitScrollbar(WS::Scrollbar),
b"-webkit-scrollbar-button" => PE::WebkitScrollbar(WS::Button),
b"-webkit-scrollbar-track" => PE::WebkitScrollbar(WS::Track),
b"-webkit-scrollbar-track-piece" => PE::WebkitScrollbar(WS::TrackPiece),
b"-webkit-scrollbar-thumb" => PE::WebkitScrollbar(WS::Thumb),
b"-webkit-scrollbar-corner" => PE::WebkitScrollbar(WS::Corner),
b"-webkit-resizer" => PE::WebkitScrollbar(WS::Resizer),
b"view-transition" => PE::ViewTransition,
_ => return None,
} })
}
pub struct GenericSelectorList<Impl: SelectorImpl> {
pub v: SmallList<GenericSelector<Impl>, 1>,
}
impl<Impl: SelectorImpl> Default for GenericSelectorList<Impl> {
fn default() -> Self {
Self {
v: SmallList::default(),
}
}
}
impl<Impl: SelectorImpl> Default for GenericSelector<Impl> {
fn default() -> Self {
Self {
specificity_and_flags: SpecificityAndFlags {
specificity: 0,
flags: SelectorFlags::empty(),
},
components: ArenaVec::new_in(ArenaPtr::global()),
}
}
}
impl<Impl: SelectorImpl> GenericSelectorList<Impl> {
#[inline]
pub fn into_boxed_selectors(self) -> Box<[GenericSelector<Impl>]> {
small_list_into_box(self.v)
}
}
pub struct SelectorListDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelectorList<Impl>);
impl<'a, Impl: BunSelectorImpl> fmt::Display for SelectorListDebugFmt<'a, Impl> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !cfg!(debug_assertions) {
return Ok(());
}
writeln!(f, "SelectorList[")?;
let last = (self.0.v.len() as usize).saturating_sub(1);
for (i, sel) in self.0.v.slice().iter().enumerate() {
if i != last {
writeln!(f, " {}", sel.debug())?;
} else {
writeln!(f, " {},", sel.debug())?;
}
}
writeln!(f, "]")
}
}
impl<Impl: BunSelectorImpl> GenericSelectorList<Impl> {
pub fn debug(&self) -> SelectorListDebugFmt<'_, Impl> {
SelectorListDebugFmt(self)
}
pub fn any_has_pseudo_element(&self) -> bool {
for sel in self.v.slice() {
if sel.has_pseudo_element() {
return true;
}
}
false
}
pub fn specifities_all_equal(&self) -> bool {
if self.v.len() == 0 {
return true;
}
if self.v.len() == 1 {
return true;
}
let value = self.v.at(0).specificity();
for sel in &self.v.slice()[1..] {
if sel.specificity() != value {
return false;
}
}
true
}
#[deprecated = "use serializer::serialize_selector_list()"]
pub fn to_css(&self, _dest: &mut Printer) -> Result<(), PrintErr> {
unreachable!("use serializer::serialize_selector_list()");
}
pub fn parse_with_options(input: &mut CssParser, options: &ParserOptions) -> CResult<Self> {
let mut parser = SelectorParser {
options,
is_nesting_allowed: true,
};
Self::parse(
&mut parser,
input,
ParseErrorRecovery::DiscardList,
NestingRequirement::None,
)
}
pub fn parse(
parser: &mut SelectorParser,
input: &mut CssParser,
error_recovery: ParseErrorRecovery,
nesting_requirement: NestingRequirement,
) -> CResult<Self> {
let mut state = SelectorParsingState::empty();
Self::parse_with_state(
parser,
input,
&mut state,
error_recovery,
nesting_requirement,
)
}
pub fn parse_relative(
parser: &mut SelectorParser,
input: &mut CssParser,
error_recovery: ParseErrorRecovery,
nesting_requirement: NestingRequirement,
) -> CResult<Self> {
let mut state = SelectorParsingState::empty();
Self::parse_relative_with_state(
parser,
input,
&mut state,
error_recovery,
nesting_requirement,
)
}
pub fn parse_with_state(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
recovery: ParseErrorRecovery,
nesting_requirement: NestingRequirement,
) -> CResult<Self> {
let original_state = *state;
let mut values: SmallList<GenericSelector<Impl>, 1> = SmallList::default();
loop {
let mut saw_nesting = false;
let selector =
input.parse_until_before(css::Delimiters::COMMA, |input2: &mut CssParser| {
let mut selector_state = original_state;
let result = parse_selector::<Impl>(
parser,
input2,
&mut selector_state,
nesting_requirement,
);
if selector_state.contains(SelectorParsingState::AFTER_NESTING) {
saw_nesting = true;
}
result
});
if saw_nesting {
state.insert(SelectorParsingState::AFTER_NESTING);
}
let was_ok = selector.is_ok();
match selector {
Ok(sel) => {
values.append(sel);
}
Err(e) => match recovery {
ParseErrorRecovery::DiscardList => return Err(e),
ParseErrorRecovery::IgnoreInvalidSelector => {}
},
}
if let Ok(tok) = input.next() {
if matches!(tok, Token::Comma) {
continue;
}
debug_assert!(!was_ok);
}
return Ok(Self { v: values });
}
}
pub fn parse_relative_with_state(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
recovery: ParseErrorRecovery,
nesting_requirement: NestingRequirement,
) -> CResult<Self> {
let original_state = *state;
let mut values: SmallList<GenericSelector<Impl>, 1> = SmallList::default();
loop {
let mut saw_nesting = false;
let selector =
input.parse_until_before(css::Delimiters::COMMA, |input2: &mut CssParser| {
let mut selector_state = original_state;
let result = parse_relative_selector::<Impl>(
parser,
input2,
&mut selector_state,
nesting_requirement,
);
if selector_state.contains(SelectorParsingState::AFTER_NESTING) {
saw_nesting = true;
}
result
});
if saw_nesting {
state.insert(SelectorParsingState::AFTER_NESTING);
}
let was_ok = selector.is_ok();
match selector {
Ok(sel) => {
values.append(sel);
}
Err(e) => match recovery {
ParseErrorRecovery::DiscardList => return Err(e),
ParseErrorRecovery::IgnoreInvalidSelector => {}
},
}
if let Ok(tok) = input.next() {
if matches!(tok, Token::Comma) {
continue;
}
debug_assert!(!was_ok);
}
return Ok(Self { v: values });
}
}
pub fn from_selector(selector: GenericSelector<Impl>) -> Self {
let mut result = Self::default();
result.v.append(selector);
result
}
pub fn deep_clone(&self) -> Self {
let mut v = SmallList::<GenericSelector<Impl>, 1>::init_capacity(self.v.len());
for sel in self.v.slice() {
v.append(sel.deep_clone());
}
Self { v }
}
pub fn eql(&self, rhs: &Self) -> bool {
eql_selector_slice(self.v.slice(), rhs.v.slice())
}
pub fn hash(&self, hasher: &mut Wyhash) {
hash_selector_slice(self.v.slice(), hasher);
}
}
impl<Impl: BunSelectorImpl> CssEql for GenericSelectorList<Impl> {
#[inline]
fn eql(&self, other: &Self) -> bool {
self.eql(other)
}
}
impl<Impl: BunSelectorImpl> CssHash for GenericSelectorList<Impl> {
#[inline]
fn hash(&self, hasher: &mut Wyhash) {
self.hash(hasher)
}
}
#[derive(Clone)]
pub struct GenericSelector<Impl: SelectorImpl> {
pub specificity_and_flags: SpecificityAndFlags,
pub components: ArenaVec<GenericComponent<Impl>>,
}
pub struct SelectorDebugFmt<'a, Impl: SelectorImpl>(pub &'a GenericSelector<Impl>);
impl<'a, Impl: SelectorImpl> fmt::Display for SelectorDebugFmt<'a, Impl> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !cfg!(debug_assertions) {
return Ok(());
}
write!(f, "Selector(<{} components>)", self.0.components.len())
}
}
impl<Impl: BunSelectorImpl> GenericSelector<Impl> {
pub fn debug(&self) -> SelectorDebugFmt<'_, Impl> {
SelectorDebugFmt(self)
}
pub fn parse(parser: &mut SelectorParser, input: &mut CssParser) -> CResult<Self> {
let mut state = SelectorParsingState::empty();
parse_selector::<Impl>(parser, input, &mut state, NestingRequirement::None)
}
#[deprecated = "use serializer::serialize_selector()"]
pub fn to_css(&self, _dest: &mut Printer) -> Result<(), PrintErr> {
unreachable!("use serializer::serialize_selector()");
}
pub fn append(&mut self, component: GenericComponent<Impl>) {
let index = 'index: {
for (i, comp) in self.components.iter().enumerate() {
match comp {
GenericComponent::Combinator(_) | GenericComponent::PseudoElement(_) => {
break 'index i;
}
_ => {}
}
}
self.components.len()
};
self.components.insert(index, component);
}
pub fn deep_clone(&self) -> Self {
let alloc = *self.components.allocator();
let mut components = ArenaVec::with_capacity_in(self.components.len(), alloc);
components.extend(self.components.iter().map(|c| c.deep_clone()));
Self {
specificity_and_flags: self.specificity_and_flags,
components,
}
}
pub fn eql(&self, other: &Self) -> bool {
self.specificity_and_flags.eql(&other.specificity_and_flags)
&& self.components.len() == other.components.len()
&& self
.components
.iter()
.zip(other.components.iter())
.all(|(a, b)| a.eql(b))
}
pub fn has_combinator(&self) -> bool {
for c in &self.components {
if let GenericComponent::Combinator(comb) = c {
if comb.is_tree_combinator() {
return true;
}
}
}
false
}
pub fn has_pseudo_element(&self) -> bool {
self.specificity_and_flags.has_pseudo_element()
}
pub fn len(&self) -> usize {
self.components.len()
}
pub fn from_component(component: GenericComponent<Impl>) -> Self {
Self::from_component_in(component, ArenaPtr::global())
}
pub fn from_component_in(component: GenericComponent<Impl>, alloc: ArenaPtr) -> Self {
let mut builder = SelectorBuilder::<Impl>::init_in(alloc);
if let Some(combinator) = component.as_combinator() {
builder.push_combinator(combinator);
} else {
builder.push_simple_selector(component);
}
let result = builder.build(false, false, false);
Self {
specificity_and_flags: result.specificity_and_flags,
components: result.components,
}
}
pub fn specificity(&self) -> u32 {
self.specificity_and_flags.specificity
}
pub fn parse_with_options(input: &mut CssParser, options: &ParserOptions) -> CResult<Self> {
let mut selector_parser = SelectorParser {
is_nesting_allowed: true,
options,
};
Self::parse(&mut selector_parser, input)
}
pub fn iter_raw_match_order(&self) -> RawMatchOrderIterator<'_, Impl> {
RawMatchOrderIterator {
slice: &self.components,
i: 0,
}
}
pub fn iter_raw_parse_order_from(&self, offset: usize) -> RawParseOrderFromIter<'_, Impl> {
RawParseOrderFromIter {
slice: &self.components[0..self.components.len() - offset],
i: 0,
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
self.specificity_and_flags.hash(hasher);
for c in &self.components {
c.hash(hasher);
}
}
}
impl<Impl: BunSelectorImpl> CssEql for GenericSelector<Impl> {
#[inline]
fn eql(&self, other: &Self) -> bool {
self.eql(other)
}
}
impl<Impl: BunSelectorImpl> CssHash for GenericSelector<Impl> {
#[inline]
fn hash(&self, hasher: &mut Wyhash) {
self.hash(hasher)
}
}
pub struct RawMatchOrderIterator<'a, Impl: SelectorImpl> {
slice: &'a [GenericComponent<Impl>],
i: usize,
}
impl<'a, Impl: SelectorImpl> Iterator for RawMatchOrderIterator<'a, Impl> {
type Item = &'a GenericComponent<Impl>;
fn next(&mut self) -> Option<Self::Item> {
if self.i >= self.slice.len() {
return None;
}
let result = &self.slice[self.i];
self.i += 1;
Some(result)
}
}
pub struct RawParseOrderFromIter<'a, Impl: SelectorImpl> {
slice: &'a [GenericComponent<Impl>],
i: usize,
}
impl<'a, Impl: SelectorImpl> Iterator for RawParseOrderFromIter<'a, Impl> {
type Item = &'a GenericComponent<Impl>;
fn next(&mut self) -> Option<Self::Item> {
if !(self.i < self.slice.len()) {
return None;
}
let result = &self.slice[self.slice.len() - 1 - self.i];
self.i += 1;
Some(result)
}
}
#[derive(Clone)]
pub enum GenericComponent<Impl: SelectorImpl> {
Combinator(Combinator),
ExplicitAnyNamespace,
ExplicitNoNamespace,
DefaultNamespace(Impl::NamespaceUrl),
Namespace {
prefix: Impl::NamespacePrefix,
url: Impl::NamespaceUrl,
},
ExplicitUniversalType,
LocalName(LocalName<Impl>),
Id(Impl::LocalIdentifier),
Class(Impl::LocalIdentifier),
AttributeInNoNamespaceExists {
local_name: Impl::LocalName,
local_name_lower: Impl::LocalName,
},
AttributeInNoNamespace {
local_name: Impl::LocalName,
operator: attrs::AttrSelectorOperator,
value: Impl::AttrValue,
case_sensitivity: attrs::ParsedCaseSensitivity,
never_matches: bool,
},
AttributeOther(Box<attrs::AttrSelectorWithOptionalNamespace<Impl>>),
Negation(Box<[GenericSelector<Impl>]>),
Root,
Empty,
Scope,
Nth(NthSelectorData),
NthOf(NthOfSelectorData<Impl>),
NonTsPseudoClass(Impl::NonTSPseudoClass),
Slotted(GenericSelector<Impl>),
Part(Box<[Impl::Identifier]>),
Host(Option<GenericSelector<Impl>>),
Where(Box<[GenericSelector<Impl>]>),
Is(Box<[GenericSelector<Impl>]>),
Any {
vendor_prefix: Impl::VendorPrefix,
selectors: Box<[GenericSelector<Impl>]>,
},
Has(Box<[GenericSelector<Impl>]>),
PseudoElement(Impl::PseudoElement),
Nesting,
}
impl<Impl: BunSelectorImpl> GenericComponent<Impl> {
pub fn is_locally_scoped(&self) -> bool {
matches!(self, Self::Id(_) | Self::Class(_))
}
pub fn as_class(&self) -> Option<&Impl::LocalIdentifier> {
match self {
Self::Class(v) => Some(v),
_ => None,
}
}
pub fn deep_clone(&self) -> Self {
use GenericComponent as C;
match self {
C::Combinator(c) => C::Combinator(*c),
C::ExplicitAnyNamespace => C::ExplicitAnyNamespace,
C::ExplicitNoNamespace => C::ExplicitNoNamespace,
C::DefaultNamespace(u) => C::DefaultNamespace(*u),
C::Namespace { prefix, url } => C::Namespace {
prefix: *prefix,
url: *url,
},
C::ExplicitUniversalType => C::ExplicitUniversalType,
C::LocalName(ln) => C::LocalName(ln.deep_clone()),
C::Id(i) => C::Id(*i),
C::Class(i) => C::Class(*i),
C::AttributeInNoNamespaceExists {
local_name,
local_name_lower,
} => C::AttributeInNoNamespaceExists {
local_name: *local_name,
local_name_lower: *local_name_lower,
},
C::AttributeInNoNamespace {
local_name,
operator,
value,
case_sensitivity,
never_matches,
} => C::AttributeInNoNamespace {
local_name: *local_name,
operator: *operator,
value: *value,
case_sensitivity: *case_sensitivity,
never_matches: *never_matches,
},
C::AttributeOther(a) => C::AttributeOther(Box::new(a.deep_clone())),
C::Negation(s) => C::Negation(deep_clone_selector_slice(s)),
C::Root => C::Root,
C::Empty => C::Empty,
C::Scope => C::Scope,
C::Nth(n) => C::Nth(*n),
C::NthOf(n) => C::NthOf(n.deep_clone()),
C::NonTsPseudoClass(p) => C::NonTsPseudoClass(p.deep_clone()),
C::Slotted(s) => C::Slotted(s.deep_clone()),
C::Part(p) => C::Part(p.iter().cloned().collect()),
C::Host(h) => C::Host(h.as_ref().map(|s| s.deep_clone())),
C::Where(s) => C::Where(deep_clone_selector_slice(s)),
C::Is(s) => C::Is(deep_clone_selector_slice(s)),
C::Any {
vendor_prefix,
selectors,
} => C::Any {
vendor_prefix: *vendor_prefix,
selectors: deep_clone_selector_slice(selectors),
},
C::Has(s) => C::Has(deep_clone_selector_slice(s)),
C::PseudoElement(pe) => C::PseudoElement(pe.deep_clone()),
C::Nesting => C::Nesting,
}
}
pub fn eql(&self, rhs: &Self) -> bool {
use GenericComponent as C;
match (self, rhs) {
(C::Combinator(a), C::Combinator(b)) => a.eql(b),
(C::ExplicitAnyNamespace, C::ExplicitAnyNamespace)
| (C::ExplicitNoNamespace, C::ExplicitNoNamespace)
| (C::ExplicitUniversalType, C::ExplicitUniversalType)
| (C::Root, C::Root)
| (C::Empty, C::Empty)
| (C::Scope, C::Scope)
| (C::Nesting, C::Nesting) => true,
(C::DefaultNamespace(a), C::DefaultNamespace(b)) => strings::eql(a, b),
(
C::Namespace {
prefix: ap,
url: au,
},
C::Namespace {
prefix: bp,
url: bu,
},
) => ap.eql(bp) && strings::eql(au, bu),
(C::LocalName(a), C::LocalName(b)) => a.eql(b),
(C::Id(a), C::Id(b)) | (C::Class(a), C::Class(b)) => a.eql(b),
(
C::AttributeInNoNamespaceExists {
local_name: an,
local_name_lower: al,
},
C::AttributeInNoNamespaceExists {
local_name: bn,
local_name_lower: bl,
},
) => an.eql(bn) && al.eql(bl),
(
C::AttributeInNoNamespace {
local_name: an,
operator: ao,
value: av,
case_sensitivity: ac,
never_matches: am,
},
C::AttributeInNoNamespace {
local_name: bn,
operator: bo,
value: bv,
case_sensitivity: bc,
never_matches: bm,
},
) => {
an.eql(bn)
&& ao == bo
&& ac == bc
&& am == bm
&& unsafe { strings::eql(&**av, &**bv) }
}
(C::AttributeOther(a), C::AttributeOther(b)) => a.eql(b),
(C::Negation(a), C::Negation(b))
| (C::Where(a), C::Where(b))
| (C::Is(a), C::Is(b))
| (C::Has(a), C::Has(b)) => eql_selector_slice(a, b),
(C::Nth(a), C::Nth(b)) => a.eql(b),
(C::NthOf(a), C::NthOf(b)) => a.eql(b),
(C::NonTsPseudoClass(a), C::NonTsPseudoClass(b)) => a.eql(b),
(C::Slotted(a), C::Slotted(b)) => a.eql(b),
(C::Part(a), C::Part(b)) => {
a.len() == b.len() && a.iter().zip(b.iter()).all(|(l, r)| l.eql(r))
}
(C::Host(a), C::Host(b)) => match (a, b) {
(None, None) => true,
(Some(a), Some(b)) => a.eql(b),
_ => false,
},
(
C::Any {
vendor_prefix: ap,
selectors: asel,
},
C::Any {
vendor_prefix: bp,
selectors: bsel,
},
) => ap == bp && eql_selector_slice(asel, bsel),
(C::PseudoElement(a), C::PseudoElement(b)) => a.eql(b),
_ => false,
}
}
pub fn as_combinator(&self) -> Option<Combinator> {
if let Self::Combinator(c) = self {
Some(*c)
} else {
None
}
}
pub fn convert_helper_is(s: Box<[GenericSelector<Impl>]>) -> Self {
Self::Is(s)
}
pub fn convert_helper_where(s: Box<[GenericSelector<Impl>]>) -> Self {
Self::Where(s)
}
pub fn convert_helper_any(s: Box<[GenericSelector<Impl>]>, prefix: Impl::VendorPrefix) -> Self {
Self::Any {
vendor_prefix: prefix,
selectors: s,
}
}
pub fn is_combinator(&self) -> bool {
matches!(self, Self::Combinator(_))
}
#[deprecated = "use serializer::serialize_component()"]
pub fn to_css(&self, _dest: &mut Printer) -> Result<(), PrintErr> {
unreachable!("use serializer::serialize_component()");
}
pub fn hash(&self, hasher: &mut Wyhash) {
use GenericComponent as C;
macro_rules! tag {
($n:expr) => {
hasher.update(&($n as u32).to_ne_bytes())
};
}
match self {
C::Combinator(c) => {
tag!(0);
CssHash::hash(c, hasher);
}
C::ExplicitAnyNamespace => tag!(1),
C::ExplicitNoNamespace => tag!(2),
C::DefaultNamespace(u) => {
tag!(3);
hasher.update(u);
}
C::Namespace { prefix, url } => {
tag!(4);
prefix.hash(hasher);
hasher.update(url);
}
C::ExplicitUniversalType => tag!(5),
C::LocalName(ln) => {
tag!(6);
ln.hash(hasher);
}
C::Id(i) => {
tag!(7);
i.hash(hasher);
}
C::Class(i) => {
tag!(8);
i.hash(hasher);
}
C::AttributeInNoNamespaceExists {
local_name,
local_name_lower,
} => {
tag!(9);
local_name.hash(hasher);
local_name_lower.hash(hasher);
}
C::AttributeInNoNamespace {
local_name,
operator,
value,
case_sensitivity,
never_matches,
} => {
tag!(10);
local_name.hash(hasher);
CssHash::hash(operator, hasher);
hasher.update(unsafe { crate::arena_str(*value) });
CssHash::hash(case_sensitivity, hasher);
hasher.update(&[*never_matches as u8]);
}
C::AttributeOther(a) => {
tag!(11);
a.hash(hasher);
}
C::Negation(s) => {
tag!(12);
hash_selector_slice(s, hasher);
}
C::Root => tag!(13),
C::Empty => tag!(14),
C::Scope => tag!(15),
C::Nth(n) => {
tag!(16);
n.hash(hasher);
}
C::NthOf(n) => {
tag!(17);
n.hash(hasher);
}
C::NonTsPseudoClass(p) => {
tag!(18);
CssHash::hash(p, hasher);
}
C::Slotted(s) => {
tag!(19);
s.hash(hasher);
}
C::Part(p) => {
tag!(20);
for id in p.iter() {
id.hash(hasher);
}
}
C::Host(h) => {
tag!(21);
if let Some(s) = h {
s.hash(hasher);
}
}
C::Where(s) => {
tag!(22);
hash_selector_slice(s, hasher);
}
C::Is(s) => {
tag!(23);
hash_selector_slice(s, hasher);
}
C::Any {
vendor_prefix,
selectors,
} => {
tag!(24);
CssHash::hash(vendor_prefix, hasher);
hash_selector_slice(selectors, hasher);
}
C::Has(s) => {
tag!(25);
hash_selector_slice(s, hasher);
}
C::PseudoElement(pe) => {
tag!(26);
CssHash::hash(pe, hasher);
}
C::Nesting => tag!(27),
}
}
}
impl<Impl: BunSelectorImpl> CssEql for GenericComponent<Impl> {
#[inline]
fn eql(&self, other: &Self) -> bool {
self.eql(other)
}
}
impl<Impl: BunSelectorImpl> CssHash for GenericComponent<Impl> {
#[inline]
fn hash(&self, hasher: &mut Wyhash) {
self.hash(hasher)
}
}
impl<Impl: BunSelectorImpl> fmt::Display for GenericComponent<Impl> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LocalName(ln) => write!(f, "local_name={}", bstr::BStr::new(ln.name.v())),
Self::Combinator(c) => write!(f, "combinator='{}'", c),
Self::PseudoElement(_) => write!(f, "pseudo_element=<..>"),
Self::Class(_) => write!(f, "class=<..>"),
_ => write!(f, "<component>"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct NthSelectorData {
pub ty: NthType,
pub is_function: bool,
pub a: i32,
pub b: i32,
}
impl NthSelectorData {
pub fn only(of_type: bool) -> NthSelectorData {
NthSelectorData {
ty: if of_type {
NthType::OnlyOfType
} else {
NthType::OnlyChild
},
is_function: false,
a: 0,
b: 1,
}
}
pub fn first(of_type: bool) -> NthSelectorData {
NthSelectorData {
ty: if of_type {
NthType::OfType
} else {
NthType::Child
},
is_function: false,
a: 0,
b: 1,
}
}
pub fn last(of_type: bool) -> NthSelectorData {
NthSelectorData {
ty: if of_type {
NthType::LastOfType
} else {
NthType::LastChild
},
is_function: false,
a: 0,
b: 1,
}
}
pub fn write_start(&self, dest: &mut Printer, is_function: bool) -> Result<(), PrintErr> {
dest.write_str(match self.ty {
NthType::Child => {
if is_function {
":nth-child("
} else {
":first-child"
}
}
NthType::LastChild => {
if is_function {
":nth-last-child("
} else {
":last-child"
}
}
NthType::OfType => {
if is_function {
":nth-of-type("
} else {
":first-of-type"
}
}
NthType::LastOfType => {
if is_function {
":nth-last-of-type("
} else {
":last-of-type"
}
}
NthType::OnlyChild => ":only-child",
NthType::OnlyOfType => ":only-of-type",
NthType::Col => ":nth-col(",
NthType::LastCol => ":nth-last-col(",
})
}
pub fn is_function_(&self) -> bool {
self.a != 0 || self.b != 1
}
fn number_sign(num: i32) -> &'static str {
if num >= 0 { "+" } else { "" }
}
pub fn write_affine(&self, dest: &mut Printer) -> Result<(), PrintErr> {
if self.a == 0 && self.b == 0 {
dest.write_char(b'0')
} else if self.a == 1 && self.b == 0 {
dest.write_char(b'n')
} else if self.a == -1 && self.b == 0 {
dest.write_str("-n")
} else if self.b == 0 {
dest.write_fmt(format_args!("{}n", self.a))
} else if self.a == 2 && self.b == 1 {
dest.write_str("odd")
} else if self.a == 0 {
dest.write_fmt(format_args!("{}", self.b))
} else if self.a == 1 {
dest.write_fmt(format_args!("n{}{}", Self::number_sign(self.b), self.b))
} else if self.a == -1 {
dest.write_fmt(format_args!("-n{}{}", Self::number_sign(self.b), self.b))
} else {
dest.write_fmt(format_args!(
"{}n{}{}",
self.a,
Self::number_sign(self.b),
self.b
))
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
hasher.update(&(self.ty as u32).to_ne_bytes());
hasher.update(&[self.is_function as u8]);
hasher.update(&self.a.to_ne_bytes());
hasher.update(&self.b.to_ne_bytes());
}
#[inline]
pub fn deep_clone(&self) -> Self {
*self
}
}
#[derive(Clone)]
pub struct NthOfSelectorData<Impl: SelectorImpl> {
pub data: NthSelectorData,
pub selectors: Box<[GenericSelector<Impl>]>,
}
impl<Impl: BunSelectorImpl> NthOfSelectorData<Impl> {
pub fn eql(&self, rhs: &Self) -> bool {
self.data.eql(&rhs.data) && eql_selector_slice(&self.selectors, &rhs.selectors)
}
pub fn hash(&self, hasher: &mut Wyhash) {
self.data.hash(hasher);
hash_selector_slice(&self.selectors, hasher);
}
pub fn deep_clone(&self) -> Self {
Self {
data: self.data,
selectors: deep_clone_selector_slice(&self.selectors),
}
}
pub fn nth_data(&self) -> NthSelectorData {
self.data
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct SelectorParsingState: u16 {
const SKIP_DEFAULT_NAMESPACE = 1 << 0;
const AFTER_SLOTTED = 1 << 1;
const AFTER_PART = 1 << 2;
const AFTER_PSEUDO_ELEMENT = 1 << 3;
const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
const DISALLOW_COMBINATORS = 1 << 5;
const DISALLOW_PSEUDOS = 1 << 6;
const AFTER_NESTING = 1 << 7;
const AFTER_WEBKIT_SCROLLBAR = 1 << 8;
const AFTER_VIEW_TRANSITION = 1 << 9;
const AFTER_UNKNOWN_PSEUDO_ELEMENT = 1 << 10;
}
}
impl SelectorParsingState {
pub fn after_any_pseudo(self) -> bool {
self.intersects(Self::AFTER_PART | Self::AFTER_SLOTTED | Self::AFTER_PSEUDO_ELEMENT)
}
pub fn allows_pseudos(self) -> bool {
!self.contains(Self::AFTER_PSEUDO_ELEMENT) && !self.contains(Self::DISALLOW_PSEUDOS)
}
pub fn allows_part(self) -> bool {
!self.contains(Self::DISALLOW_PSEUDOS) && !self.after_any_pseudo()
}
pub fn allows_slotted(self) -> bool {
self.allows_part()
}
pub fn allows_tree_structural_pseudo_classes(self) -> bool {
!self.after_any_pseudo()
}
pub fn allows_non_functional_pseudo_classes(self) -> bool {
!self.contains(Self::AFTER_SLOTTED)
&& !self.contains(Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
}
pub fn allows_combinators(self) -> bool {
!self.contains(Self::DISALLOW_COMBINATORS)
}
pub fn allows_custom_functional_pseudo_classes(self) -> bool {
!self.after_any_pseudo()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct SpecificityAndFlags {
pub specificity: u32,
pub flags: SelectorFlags,
}
impl SpecificityAndFlags {
pub fn has_pseudo_element(self) -> bool {
self.flags.contains(SelectorFlags::HAS_PSEUDO)
}
pub fn hash(self, hasher: &mut Wyhash) {
hasher.update(&self.specificity.to_ne_bytes());
hasher.update(&[self.flags.bits()]);
}
pub fn deep_clone(self) -> Self {
self
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct SelectorFlags: u8 {
const HAS_PSEUDO = 1 << 0;
const HAS_SLOTTED = 1 << 1;
const HAS_PART = 1 << 2;
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ParseErrorRecovery {
DiscardList,
IgnoreInvalidSelector,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum NestingRequirement {
None,
Prefixed,
Contained,
Implicit,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, strum::IntoStaticStr, CssHash)]
pub enum Combinator {
Child, Descendant, NextSibling, LaterSibling, PseudoElement,
SlotAssignment,
Part,
DeepDescendant,
Deep,
}
impl Combinator {
#[deprecated = "use serializer::serialize_combinator()"]
pub fn to_css(self, _dest: &mut Printer) -> Result<(), PrintErr> {
unreachable!("use serializer::serialize_combinator()");
}
pub fn is_tree_combinator(self) -> bool {
matches!(
self,
Self::Child | Self::Descendant | Self::NextSibling | Self::LaterSibling
)
}
}
impl fmt::Display for Combinator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Child => write!(f, ">"),
Self::Descendant => write!(f, "`descendant` (space)"),
Self::NextSibling => write!(f, "+"),
Self::LaterSibling => write!(f, "~"),
other => write!(f, "{}", <&'static str>::from(*other)),
}
}
}
#[derive(Clone)]
pub enum SelectorParseErrorKind {
InvalidState,
ClassNeedsIdent(Token),
PseudoElementExpectedIdent(Token),
UnsupportedPseudoClassOrElement(Str),
NoQualifiedNameInAttributeSelector(Token),
UnexpectedTokenInAttributeSelector(Token),
UnexpectedSelectorAfterPseudoElement(Token),
InvalidQualNameInAttr(Token),
ExpectedBarInAttr(Token),
EmptySelector,
DanglingCombinator,
InvalidPseudoClassBeforeWebkitScrollbar,
InvalidPseudoClassAfterWebkitScrollbar,
InvalidPseudoClassAfterPseudoElement,
MissingNestingSelector,
MissingNestingPrefix,
ExpectedNamespace(Str),
BadValueInAttr(Token),
ExplicitNamespaceUnexpectedToken(Token),
UnexpectedIdent(Str),
AmbiguousCssModuleClass(Str),
}
impl SelectorParseErrorKind {
pub fn into_default_parser_error(self) -> css::ParserError {
css::ParserError::selector_error(self.into_selector_error())
}
pub fn into_selector_error(self) -> css::SelectorError {
use SelectorParseErrorKind as K;
use css::SelectorError as S;
match self {
K::InvalidState => S::invalid_state,
K::ClassNeedsIdent(token) => S::class_needs_ident(token),
K::PseudoElementExpectedIdent(token) => S::pseudo_element_expected_ident(token),
K::UnsupportedPseudoClassOrElement(name) => {
S::unsupported_pseudo_class_or_element(name)
}
K::NoQualifiedNameInAttributeSelector(token) => {
S::no_qualified_name_in_attribute_selector(token)
}
K::UnexpectedTokenInAttributeSelector(token) => {
S::unexpected_token_in_attribute_selector(token)
}
K::InvalidQualNameInAttr(token) => S::invalid_qual_name_in_attr(token),
K::ExpectedBarInAttr(token) => S::expected_bar_in_attr(token),
K::EmptySelector => S::empty_selector,
K::DanglingCombinator => S::dangling_combinator,
K::InvalidPseudoClassBeforeWebkitScrollbar => {
S::invalid_pseudo_class_before_webkit_scrollbar
}
K::InvalidPseudoClassAfterWebkitScrollbar => {
S::invalid_pseudo_class_after_webkit_scrollbar
}
K::InvalidPseudoClassAfterPseudoElement => S::invalid_pseudo_class_after_pseudo_element,
K::MissingNestingSelector => S::missing_nesting_selector,
K::MissingNestingPrefix => S::missing_nesting_prefix,
K::ExpectedNamespace(name) => S::expected_namespace(name),
K::BadValueInAttr(token) => S::bad_value_in_attr(token),
K::ExplicitNamespaceUnexpectedToken(token) => {
S::explicit_namespace_unexpected_token(token)
}
K::UnexpectedIdent(ident) => S::unexpected_ident(ident),
K::UnexpectedSelectorAfterPseudoElement(tok) => {
S::unexpected_selector_after_pseudo_element(tok)
}
K::AmbiguousCssModuleClass(name) => S::ambiguous_css_module_class(name),
}
}
}
impl css::IntoParserError for SelectorParseErrorKind {
fn into_parser_error(self) -> css::ParserError {
self.into_default_parser_error()
}
}
pub enum SimpleSelectorParseResult<Impl: SelectorImpl> {
SimpleSelector(GenericComponent<Impl>),
PseudoElement(Impl::PseudoElement),
SlottedPseudo(GenericSelector<Impl>),
PartPseudo(Box<[Impl::Identifier]>),
}
#[derive(Clone, CssEql, CssHash)]
pub enum PseudoElement {
After,
Before,
FirstLine,
FirstLetter,
Selection(css::VendorPrefix),
Placeholder(css::VendorPrefix),
Marker,
Backdrop(css::VendorPrefix),
FileSelectorButton(css::VendorPrefix),
WebkitScrollbar(WebKitScrollbarPseudoElement),
Cue,
CueRegion,
CueFunction {
selector: Box<Selector>,
},
CueRegionFunction {
selector: Box<Selector>,
},
ViewTransition,
ViewTransitionGroup {
part_name: ViewTransitionPartName,
},
ViewTransitionImagePair {
part_name: ViewTransitionPartName,
},
ViewTransitionOld {
part_name: ViewTransitionPartName,
},
ViewTransitionNew {
part_name: ViewTransitionPartName,
},
Custom {
name: Str,
},
CustomFunction {
name: Str,
arguments: TokenList,
},
}
impl PseudoElement {
pub fn is_equivalent(&self, other: &PseudoElement) -> bool {
use PseudoElement as PE;
if matches!(self, PE::Selection(_)) && matches!(other, PE::Selection(_)) {
return true;
}
if matches!(self, PE::Placeholder(_)) && matches!(other, PE::Placeholder(_)) {
return true;
}
if matches!(self, PE::Backdrop(_)) && matches!(other, PE::Backdrop(_)) {
return true;
}
if matches!(self, PE::FileSelectorButton(_)) && matches!(other, PE::FileSelectorButton(_)) {
return true;
}
self.eql(other)
}
pub fn deep_clone(&self) -> Self {
self.clone()
}
pub fn get_necessary_prefixes(&mut self, targets: &css::targets::Targets) -> css::VendorPrefix {
use PseudoElement as PE;
use css::prefixes::Feature as F;
let (p, feature): (&mut css::VendorPrefix, F) = match self {
PE::Selection(p) => (p, F::PseudoElementSelection),
PE::Placeholder(p) => (p, F::PseudoElementPlaceholder),
PE::Backdrop(p) => (p, F::PseudoElementBackdrop),
PE::FileSelectorButton(p) => (p, F::PseudoElementFileSelectorButton),
_ => return css::VendorPrefix::empty(),
};
*p = targets.prefixes(*p, feature);
*p
}
pub fn get_prefix(&self) -> css::VendorPrefix {
use PseudoElement as PE;
match self {
PE::Selection(p) | PE::Placeholder(p) | PE::Backdrop(p) | PE::FileSelectorButton(p) => {
*p
}
_ => css::VendorPrefix::empty(),
}
}
pub fn valid_after_slotted(&self) -> bool {
use PseudoElement as PE;
matches!(
self,
PE::Before | PE::After | PE::Marker | PE::Placeholder(_) | PE::FileSelectorButton(_)
)
}
pub fn is_unknown(&self) -> bool {
matches!(
self,
PseudoElement::Custom { .. } | PseudoElement::CustomFunction { .. }
)
}
pub fn accepts_state_pseudo_classes(&self) -> bool {
let _ = self;
true
}
pub fn is_webkit_scrollbar(&self) -> bool {
matches!(self, PseudoElement::WebkitScrollbar(_))
}
pub fn is_view_transition(&self) -> bool {
use PseudoElement as PE;
matches!(
self,
PE::ViewTransitionGroup { .. }
| PE::ViewTransitionImagePair { .. }
| PE::ViewTransitionNew { .. }
| PE::ViewTransitionOld { .. }
)
}
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
serialize::serialize_pseudo_element(self, dest, None)
}
}
impl fmt::Display for PseudoElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<pseudo_element>")
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum NthType {
Child,
LastChild,
OnlyChild,
OfType,
LastOfType,
OnlyOfType,
Col,
LastCol,
}
impl NthType {
pub fn is_only(self) -> bool {
self == NthType::OnlyChild || self == NthType::OnlyOfType
}
pub fn is_of_type(self) -> bool {
self == NthType::OfType || self == NthType::LastOfType || self == NthType::OnlyOfType
}
pub fn is_from_end(self) -> bool {
self == NthType::LastChild || self == NthType::LastOfType || self == NthType::LastCol
}
pub fn allows_of_selector(self) -> bool {
self == NthType::Child || self == NthType::LastChild
}
}
pub fn parse_type_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: SelectorParsingState,
sink: &mut SelectorBuilder<Impl>,
) -> CResult<bool> {
let result = match parse_qualified_name::<Impl>(parser, input, false) {
Ok(v) => v,
Err(e) => {
if matches!(
e.kind,
css::ParseErrorKind::basic(css::BasicParseErrorKind::end_of_input)
) {
return Ok(false);
}
return Err(e);
}
};
let (namespace, local_name) = match result {
OptionalQName::None(_) => return Ok(false),
OptionalQName::Some(ns, ln) => (ns, ln),
};
if state.after_any_pseudo() {
return Err(input
.new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
}
match namespace {
QNamePrefix::ImplicitAnyNamespace => {}
QNamePrefix::ImplicitDefaultNamespace(url) => {
sink.push_simple_selector(GenericComponent::DefaultNamespace(url));
}
QNamePrefix::ExplicitNamespace(prefix, url) => {
let component: GenericComponent<Impl> = 'component: {
if let Some(default_url) = parser.default_namespace() {
if url == default_url {
break 'component GenericComponent::DefaultNamespace(url);
}
}
GenericComponent::Namespace { prefix, url }
};
sink.push_simple_selector(component);
}
QNamePrefix::ExplicitNoNamespace => {
sink.push_simple_selector(GenericComponent::ExplicitNoNamespace);
}
QNamePrefix::ExplicitAnyNamespace => {
sink.push_simple_selector(GenericComponent::ExplicitAnyNamespace);
}
QNamePrefix::ImplicitNoNamespace => {
unreachable!("Should not be returned with in_attr_selector = false");
}
}
if let Some(name) = local_name {
sink.push_simple_selector(GenericComponent::LocalName(LocalName {
lower_name: {
Ident {
v: arena_lowercase(input.arena(), name),
}
},
name: Ident {
v: std::ptr::from_ref::<[u8]>(name),
},
}));
} else {
sink.push_simple_selector(GenericComponent::ExplicitUniversalType);
}
Ok(true)
}
pub fn parse_one_simple_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
) -> CResult<Option<SimpleSelectorParseResult<Impl>>> {
type S<Impl> = SimpleSelectorParseResult<Impl>;
let start = input.state();
let token_location = input.current_source_location();
let token_loc = input.position();
let token = match input.next_including_whitespace() {
Ok(v) => v.clone(),
Err(_) => {
input.reset(&start);
return Ok(None);
}
};
match token {
Token::IdHash(id) => {
if state.after_any_pseudo() {
return Err(token_location.new_custom_error(
SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::IdHash(id))
.into_default_parser_error(),
));
}
let component = GenericComponent::Id(parser.new_local_identifier(
input,
css::CssRefTag::ID,
id,
token_loc,
));
return Ok(Some(S::SimpleSelector(component)));
}
Token::OpenSquare => {
if state.after_any_pseudo() {
return Err(token_location.new_custom_error(
SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::OpenSquare)
.into_default_parser_error(),
));
}
let attr = input.parse_nested_block(|input2: &mut CssParser| {
parse_attribute_selector::<Impl>(parser, input2)
})?;
return Ok(Some(S::SimpleSelector(attr)));
}
Token::Colon => {
let location = input.current_source_location();
let (is_single_colon, next_token): (bool, Token) =
match input.next_including_whitespace()?.clone() {
Token::Colon => (false, input.next_including_whitespace()?.clone()),
t => (true, t),
};
let (name, is_functional): (Str, bool) = match next_token {
Token::Ident(name) => (name, false),
Token::Function(name) => (name, true),
t => {
let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t);
return Err(input.new_custom_error(e.into_default_parser_error()));
}
};
let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(name);
if is_pseudo_element {
if !state.allows_pseudos() {
return Err(input.new_custom_error(
SelectorParseErrorKind::InvalidState.into_default_parser_error(),
));
}
let pseudo_element: Impl::PseudoElement = if is_functional {
if parser.parse_part()
&& strings::eql_case_insensitive_ascii_check_length(name, b"part")
{
if !state.allows_part() {
return Err(input.new_custom_error(
SelectorParseErrorKind::InvalidState.into_default_parser_error(),
));
}
let names = input.parse_nested_block(
|input2: &mut CssParser| -> CResult<Box<[Impl::Identifier]>> {
let mut result: Vec<Impl::Identifier> = Vec::with_capacity(1);
result.push(Ident {
v: input2.expect_ident()?,
});
while !input2.is_exhausted() {
result.push(Ident {
v: input2.expect_ident()?,
});
}
Ok(result.into_boxed_slice())
},
)?;
return Ok(Some(S::PartPseudo(names)));
}
if parser.parse_slotted()
&& strings::eql_case_insensitive_ascii_check_length(name, b"slotted")
{
if !state.allows_slotted() {
return Err(input.new_custom_error(
SelectorParseErrorKind::InvalidState.into_default_parser_error(),
));
}
let selector = input.parse_nested_block(|input2: &mut CssParser| {
parse_inner_compound_selector::<Impl>(parser, input2, state)
})?;
return Ok(Some(S::SlottedPseudo(selector)));
}
input.parse_nested_block(|i: &mut CssParser| {
parser.parse_functional_pseudo_element(name, i)
})?
} else {
parser.parse_pseudo_element(location, name)?
};
if state.contains(SelectorParsingState::AFTER_SLOTTED)
&& pseudo_element.valid_after_slotted()
{
return Ok(Some(S::PseudoElement(pseudo_element)));
}
return Ok(Some(S::PseudoElement(pseudo_element)));
} else {
let pseudo_class: GenericComponent<Impl> = if is_functional {
input.parse_nested_block(|input2: &mut CssParser| {
parse_functional_pseudo_class::<Impl>(parser, input2, name, state)
})?
} else {
parse_simple_pseudo_class::<Impl>(parser, location, name, *state)?
};
return Ok(Some(S::SimpleSelector(pseudo_class)));
}
}
Token::Delim(d) => match u8::try_from(d).ok() {
Some(b'.') => {
if state.after_any_pseudo() {
return Err(token_location.new_custom_error(
SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(Token::Delim(
b'.' as u32,
))
.into_default_parser_error(),
));
}
let location = input.current_source_location();
let class = match input.next_including_whitespace()?.clone() {
Token::Ident(class) => class,
t => {
let e = SelectorParseErrorKind::ClassNeedsIdent(t);
return Err(location.new_custom_error(e.into_default_parser_error()));
}
};
return Ok(Some(S::SimpleSelector(GenericComponent::Class(
parser.new_local_identifier(input, css::CssRefTag::CLASS, class, token_loc),
))));
}
Some(b'&') => {
if parser.is_nesting_allowed() {
state.insert(SelectorParsingState::AFTER_NESTING);
return Ok(Some(S::SimpleSelector(GenericComponent::Nesting)));
}
}
_ => {}
},
_ => {}
}
input.reset(&start);
Ok(None)
}
pub fn parse_attribute_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
) -> CResult<GenericComponent<Impl>> {
type N<Impl> = attrs::NamespaceConstraint<attrs::NamespaceUrl<Impl>>;
let (namespace, local_name): (Option<N<Impl>>, Str) = 'brk: {
input.skip_whitespace();
let qname = parse_qualified_name::<Impl>(parser, input, true)?;
match qname {
OptionalQName::None(t) => {
return Err(input.new_custom_error(
SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t)
.into_default_parser_error(),
));
}
OptionalQName::Some(ns, ln) => {
let ln = ln.unwrap_or_else(|| unreachable!());
break 'brk (
match ns {
QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None,
QNamePrefix::ExplicitNamespace(prefix, url) => {
Some(attrs::NamespaceConstraint::Specific(attrs::NamespaceUrl {
prefix,
url,
}))
}
QNamePrefix::ExplicitAnyNamespace => Some(attrs::NamespaceConstraint::Any),
QNamePrefix::ImplicitAnyNamespace
| QNamePrefix::ImplicitDefaultNamespace(_) => {
unreachable!("Not returned with in_attr_selector = true");
}
},
ln,
);
}
}
};
let location = input.current_source_location();
let operator: attrs::AttrSelectorOperator = 'operator: {
let tok = match input.next() {
Ok(v) => v.clone(),
Err(_) => {
let local_name_lower: *const [u8] = arena_lowercase(input.arena(), local_name);
if let Some(ns) = namespace {
let x = attrs::AttrSelectorWithOptionalNamespace::<Impl> {
namespace: Some(ns),
local_name: Ident { v: local_name },
local_name_lower: Ident {
v: local_name_lower,
},
never_matches: false,
operation: attrs::ParsedAttrSelectorOperation::Exists,
};
return Ok(GenericComponent::AttributeOther(Box::new(x)));
} else {
return Ok(GenericComponent::AttributeInNoNamespaceExists {
local_name: Ident { v: local_name },
local_name_lower: Ident {
v: local_name_lower,
},
});
}
}
};
match tok {
Token::Delim(d) if d == b'=' as u32 => {
break 'operator attrs::AttrSelectorOperator::Equal;
}
Token::IncludeMatch => break 'operator attrs::AttrSelectorOperator::Includes,
Token::DashMatch => break 'operator attrs::AttrSelectorOperator::DashMatch,
Token::PrefixMatch => break 'operator attrs::AttrSelectorOperator::Prefix,
Token::SubstringMatch => break 'operator attrs::AttrSelectorOperator::Substring,
Token::SuffixMatch => break 'operator attrs::AttrSelectorOperator::Suffix,
_ => {}
}
return Err(location.new_custom_error(
SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(tok)
.into_default_parser_error(),
));
};
let value_str: Str = {
let value_loc = input.current_source_location();
let tok = input.next()?.clone();
match tok {
Token::Ident(v) | Token::QuotedString(v) => v,
t => {
return Err(value_loc.new_custom_error(
SelectorParseErrorKind::BadValueInAttr(t).into_default_parser_error(),
));
}
}
};
let never_matches = match operator {
attrs::AttrSelectorOperator::Equal | attrs::AttrSelectorOperator::DashMatch => false,
attrs::AttrSelectorOperator::Includes => {
value_str.is_empty() || strings::index_of_any(value_str, SELECTOR_WHITESPACE).is_some()
}
attrs::AttrSelectorOperator::Prefix
| attrs::AttrSelectorOperator::Substring
| attrs::AttrSelectorOperator::Suffix => value_str.is_empty(),
};
let attribute_flags = parse_attribute_flags(input)?;
let value: Impl::AttrValue = std::ptr::from_ref::<[u8]>(value_str);
let (local_name_lower, local_name_is_ascii_lowercase): (Impl::LocalName, bool) = 'brk: {
let first_uppercase = 'a: {
for (i, &b) in local_name.iter().enumerate() {
if b >= b'A' && b <= b'Z' {
break 'a Some(i);
}
}
None
};
if let Some(first_uppercase) = first_uppercase {
let str_ = &local_name[first_uppercase..];
let lowered: *const [u8] = arena_lowercase(input.arena(), str_);
break 'brk (Ident { v: lowered }, false);
} else {
break 'brk (
Ident {
v: std::ptr::from_ref::<[u8]>(local_name),
},
true,
);
}
};
let case_sensitivity: attrs::ParsedCaseSensitivity =
attribute_flags.to_case_sensitivity(local_name_lower.v(), namespace.is_some());
if namespace.is_some() && !local_name_is_ascii_lowercase {
Ok(GenericComponent::AttributeOther(Box::new(
attrs::AttrSelectorWithOptionalNamespace::<Impl> {
namespace,
local_name: Ident {
v: std::ptr::from_ref::<[u8]>(local_name),
},
local_name_lower,
never_matches,
operation: attrs::ParsedAttrSelectorOperation::WithValue {
operator,
case_sensitivity,
expected_value: value,
},
},
)))
} else {
Ok(GenericComponent::AttributeInNoNamespace {
local_name: Ident { v: local_name },
operator,
value,
case_sensitivity,
never_matches,
})
}
}
pub fn is_css2_pseudo_element(name: &[u8]) -> bool {
crate::match_ignore_ascii_case! { name, {
b"before" | b"after" | b"first-line" | b"first-letter" => true,
_ => false,
}}
}
pub fn parse_inner_compound_selector<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
) -> CResult<GenericSelector<Impl>> {
let mut child_state = {
let mut child_state = *state;
child_state.insert(SelectorParsingState::DISALLOW_PSEUDOS);
child_state.insert(SelectorParsingState::DISALLOW_COMBINATORS);
child_state
};
let result = parse_selector::<Impl>(parser, input, &mut child_state, NestingRequirement::None)?;
if child_state.contains(SelectorParsingState::AFTER_NESTING) {
state.insert(SelectorParsingState::AFTER_NESTING);
}
Ok(result)
}
pub fn parse_functional_pseudo_class<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
name: Str,
state: &mut SelectorParsingState,
) -> CResult<GenericComponent<Impl>> {
crate::match_ignore_ascii_case! { name, {
b"nth-child" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::Child),
b"nth-of-type" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::OfType),
b"nth-last-child" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::LastChild),
b"nth-last-of-type" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::LastOfType),
b"nth-col" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::Col),
b"nth-last-col" => return parse_nth_pseudo_class::<Impl>(parser, input, *state, NthType::LastCol),
b"is" => if parser.parse_is_and_where() {
return parse_is_or_where::<Impl, _>(parser, input, state, |s| GenericComponent::convert_helper_is(s));
},
b"where" => if parser.parse_is_and_where() {
return parse_is_or_where::<Impl, _>(parser, input, state, |s| GenericComponent::convert_helper_where(s));
},
b"has" => return parse_has::<Impl>(parser, input, state),
b"host" => {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input.new_custom_error(
SelectorParseErrorKind::InvalidState.into_default_parser_error(),
));
}
return Ok(GenericComponent::Host(Some(
parse_inner_compound_selector::<Impl>(parser, input, state)?,
)));
},
b"not" => return parse_negation::<Impl>(parser, input, state),
_ => {},
} }
if let Some(prefix) = parser.parse_any_prefix(name) {
return parse_is_or_where::<Impl, _>(parser, input, state, move |s| {
GenericComponent::convert_helper_any(s, prefix)
});
}
if !state.allows_custom_functional_pseudo_classes() {
return Err(input
.new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
}
let result = parser.parse_non_ts_functional_pseudo_class(name, input)?;
Ok(GenericComponent::NonTsPseudoClass(result))
}
pub fn parse_simple_pseudo_class<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
location: css::SourceLocation,
name: Str,
state: SelectorParsingState,
) -> CResult<GenericComponent<Impl>> {
if !state.allows_non_functional_pseudo_classes() {
return Err(location
.new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
}
if state.allows_tree_structural_pseudo_classes() {
crate::match_ignore_ascii_case! { name, {
b"first-child" => return Ok(GenericComponent::Nth(NthSelectorData::first(false))),
b"last-child" => return Ok(GenericComponent::Nth(NthSelectorData::last(false))),
b"only-child" => return Ok(GenericComponent::Nth(NthSelectorData::only(false))),
b"root" => return Ok(GenericComponent::Root),
b"empty" => return Ok(GenericComponent::Empty),
b"scope" => return Ok(GenericComponent::Scope),
b"host" => if parser.parse_host() {
return Ok(GenericComponent::Host(None));
},
b"first-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::first(true))),
b"last-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::last(true))),
b"only-of-type" => return Ok(GenericComponent::Nth(NthSelectorData::only(true))),
_ => {},
} }
}
if state.contains(SelectorParsingState::AFTER_VIEW_TRANSITION) {
if strings::eql_case_insensitive_ascii_check_length(name, b"only-child") {
return Ok(GenericComponent::Nth(NthSelectorData::only(false)));
}
}
let pseudo_class = parser.parse_non_ts_pseudo_class(location, name)?;
if state.contains(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) {
if !pseudo_class.is_valid_after_webkit_scrollbar() {
return Err(location.new_custom_error(
SelectorParseErrorKind::InvalidPseudoClassAfterWebkitScrollbar
.into_default_parser_error(),
));
}
} else if state.contains(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {
if !pseudo_class.is_user_action_state() {
return Err(location.new_custom_error(
SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement
.into_default_parser_error(),
));
}
} else if !pseudo_class.is_valid_before_webkit_scrollbar() {
return Err(location.new_custom_error(
SelectorParseErrorKind::InvalidPseudoClassBeforeWebkitScrollbar
.into_default_parser_error(),
));
}
Ok(GenericComponent::NonTsPseudoClass(pseudo_class))
}
pub fn parse_nth_pseudo_class<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: SelectorParsingState,
ty: NthType,
) -> CResult<GenericComponent<Impl>> {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input
.new_custom_error(SelectorParseErrorKind::InvalidState.into_default_parser_error()));
}
let (a, b) = css::nth::parse_nth(input)?;
let nth_data = NthSelectorData {
ty,
is_function: true,
a,
b,
};
if !ty.allows_of_selector() {
return Ok(GenericComponent::Nth(nth_data));
}
if input.try_parse(|i| i.expect_ident_matching(b"of")).is_err() {
return Ok(GenericComponent::Nth(nth_data));
}
let mut child_state = {
let mut s = state;
s.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE);
s.insert(SelectorParsingState::DISALLOW_PSEUDOS);
s
};
let selectors = GenericSelectorList::<Impl>::parse_with_state(
parser,
input,
&mut child_state,
ParseErrorRecovery::IgnoreInvalidSelector,
NestingRequirement::None,
)?;
Ok(GenericComponent::NthOf(NthOfSelectorData {
data: nth_data,
selectors: selectors.into_boxed_selectors(),
}))
}
pub fn parse_is_or_where<Impl: BunSelectorImpl, F>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
func: F,
) -> CResult<GenericComponent<Impl>>
where
F: FnOnce(Box<[GenericSelector<Impl>]>) -> GenericComponent<Impl>,
{
debug_assert!(parser.parse_is_and_where());
let mut child_state = {
let mut child_state = *state;
child_state.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE);
child_state.insert(SelectorParsingState::DISALLOW_PSEUDOS);
child_state
};
let inner = GenericSelectorList::<Impl>::parse_with_state(
parser,
input,
&mut child_state,
parser.is_and_where_error_recovery(),
NestingRequirement::None,
)?;
if child_state.contains(SelectorParsingState::AFTER_NESTING) {
state.insert(SelectorParsingState::AFTER_NESTING);
}
let selector_slice = inner.into_boxed_selectors();
let result = func(selector_slice);
Ok(result)
}
pub fn parse_has<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
) -> CResult<GenericComponent<Impl>> {
let mut child_state = *state;
let inner = GenericSelectorList::<Impl>::parse_relative_with_state(
parser,
input,
&mut child_state,
parser.is_and_where_error_recovery(),
NestingRequirement::None,
)?;
if child_state.contains(SelectorParsingState::AFTER_NESTING) {
state.insert(SelectorParsingState::AFTER_NESTING);
}
Ok(GenericComponent::Has(inner.into_boxed_selectors()))
}
pub fn parse_negation<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
state: &mut SelectorParsingState,
) -> CResult<GenericComponent<Impl>> {
let mut child_state = *state;
child_state.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE);
child_state.insert(SelectorParsingState::DISALLOW_PSEUDOS);
let list = GenericSelectorList::<Impl>::parse_with_state(
parser,
input,
&mut child_state,
ParseErrorRecovery::DiscardList,
NestingRequirement::None,
)?;
if child_state.contains(SelectorParsingState::AFTER_NESTING) {
state.insert(SelectorParsingState::AFTER_NESTING);
}
Ok(GenericComponent::Negation(list.into_boxed_selectors()))
}
pub enum OptionalQName<Impl: SelectorImpl> {
Some(QNamePrefix<Impl>, Option<Str>),
None(Token),
}
pub enum QNamePrefix<Impl: SelectorImpl> {
ImplicitNoNamespace, ImplicitAnyNamespace, ImplicitDefaultNamespace(Impl::NamespaceUrl), ExplicitNoNamespace, ExplicitAnyNamespace, ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), }
pub fn parse_qualified_name<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
input: &mut CssParser,
in_attr_selector: bool,
) -> CResult<OptionalQName<Impl>> {
let start = input.state();
let tok = match input.next_including_whitespace() {
Ok(v) => v.clone(),
Err(e) => {
input.reset(&start);
return Err(e);
}
};
match &tok {
Token::Ident(value) => {
let value = *value;
let after_ident = input.state();
let n = if let Ok(t) = input.next_including_whitespace() {
matches!(t, Token::Delim(d) if *d == b'|' as u32)
} else {
false
};
if n {
let prefix: Impl::NamespacePrefix = Ident { v: value };
let result: Option<Impl::NamespaceUrl> =
parser.namespace_for_prefix(Ident { v: value });
let url: Impl::NamespaceUrl = match result {
Some(url) => url,
None => {
return Err(input.new_custom_error(
SelectorParseErrorKind::UnsupportedPseudoClassOrElement(value)
.into_default_parser_error(),
));
}
};
return parse_qualified_name_eplicit_namespace_helper::<Impl>(
input,
QNamePrefix::ExplicitNamespace(prefix, url),
in_attr_selector,
);
} else {
input.reset(&after_ident);
if in_attr_selector {
return Ok(OptionalQName::Some(
QNamePrefix::ImplicitNoNamespace,
Some(value),
));
}
return Ok(parse_qualified_name_default_namespace_helper::<Impl>(
parser,
Some(value),
));
}
}
Token::Delim(c) => match u8::try_from(*c).ok() {
Some(b'*') => {
let after_star = input.state();
let result = input.next_including_whitespace();
if let Ok(t) = &result {
if matches!(t, Token::Delim(d) if *d == b'|' as u32) {
return parse_qualified_name_eplicit_namespace_helper::<Impl>(
input,
QNamePrefix::ExplicitAnyNamespace,
in_attr_selector,
);
}
}
let result_cloned = result.cloned();
input.reset(&after_star);
if in_attr_selector {
let t = result_cloned?;
return Err(after_star
.source_location()
.new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)));
} else {
return Ok(parse_qualified_name_default_namespace_helper::<Impl>(
parser, None,
));
}
}
Some(b'|') => {
return parse_qualified_name_eplicit_namespace_helper::<Impl>(
input,
QNamePrefix::ExplicitNoNamespace,
in_attr_selector,
);
}
_ => {}
},
_ => {}
}
input.reset(&start);
Ok(OptionalQName::None(tok))
}
fn parse_qualified_name_default_namespace_helper<Impl: BunSelectorImpl>(
parser: &mut SelectorParser,
local_name: Option<Str>,
) -> OptionalQName<Impl> {
let namespace: QNamePrefix<Impl> = if let Some(url) = parser.default_namespace() {
QNamePrefix::ImplicitDefaultNamespace(url)
} else {
QNamePrefix::ImplicitAnyNamespace
};
OptionalQName::Some(namespace, local_name)
}
fn parse_qualified_name_eplicit_namespace_helper<Impl: BunSelectorImpl>(
input: &mut CssParser,
namespace: QNamePrefix<Impl>,
in_attr_selector: bool,
) -> CResult<OptionalQName<Impl>> {
let location = input.current_source_location();
let t = input.next_including_whitespace()?.clone();
match &t {
Token::Ident(local_name) => return Ok(OptionalQName::Some(namespace, Some(*local_name))),
Token::Delim(c) if *c == b'*' as u32 && !in_attr_selector => {
return Ok(OptionalQName::Some(namespace, None));
}
_ => {}
}
if in_attr_selector {
let e = SelectorParseErrorKind::InvalidQualNameInAttr(t);
return Err(location.new_custom_error(e));
}
Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t)))
}
#[derive(Clone, PartialEq)]
pub struct LocalName<Impl: SelectorImpl> {
pub name: Impl::LocalName,
pub lower_name: Impl::LocalName,
}
impl<Impl: BunSelectorImpl> LocalName<Impl> {
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
IdentFns::to_css(&self.name, dest)
}
pub fn eql(&self, rhs: &Self) -> bool {
self.name.eql(&rhs.name) && self.lower_name.eql(&rhs.lower_name)
}
pub fn hash(&self, hasher: &mut Wyhash) {
self.name.hash(hasher);
self.lower_name.hash(hasher);
}
pub fn deep_clone(&self) -> Self {
Self {
name: self.name,
lower_name: self.lower_name,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum AttributeFlags {
CaseSensitive,
AsciiCaseInsensitive,
CaseSensitivityDependsOnName,
}
impl AttributeFlags {
pub fn to_case_sensitivity(
self,
local_name: &[u8],
have_namespace: bool,
) -> attrs::ParsedCaseSensitivity {
match self {
AttributeFlags::CaseSensitive => attrs::ParsedCaseSensitivity::ExplicitCaseSensitive,
AttributeFlags::AsciiCaseInsensitive => {
attrs::ParsedCaseSensitivity::AsciiCaseInsensitive
}
AttributeFlags::CaseSensitivityDependsOnName => {
if !have_namespace && is_html_case_insensitive_attribute(local_name) {
return attrs::ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument;
}
attrs::ParsedCaseSensitivity::CaseSensitive
}
}
}
}
#[inline]
fn is_html_case_insensitive_attribute(name: &[u8]) -> bool {
match name.len() {
3 => match name[0] {
b'd' => name == b"dir",
b'r' => matches!(name, b"rel" | b"rev"),
_ => false,
},
4 => match name[0] {
b'a' => name == b"axis",
b'f' => name == b"face",
b'l' => matches!(name, b"lang" | b"link"),
b't' => matches!(name, b"text" | b"type"),
_ => false,
},
5 => match name[0] {
b'a' => matches!(name, b"align" | b"alink"),
b'c' => matches!(name, b"clear" | b"color"),
b'd' => name == b"defer",
b'f' => name == b"frame",
b'm' => name == b"media",
b'r' => name == b"rules",
b's' => matches!(name, b"scope" | b"shape"),
b'v' => name == b"vlink",
_ => false,
},
6 => match name[0] {
b'a' => name == b"accept",
b'm' => name == b"method",
b'n' => matches!(name, b"nohref" | b"nowrap"),
b't' => name == b"target",
b'v' => name == b"valign",
_ => false,
},
7 => match name[0] {
b'b' => name == b"bgcolor",
b'c' => matches!(name, b"charset" | b"checked" | b"compact"),
b'd' => name == b"declare",
b'e' => name == b"enctype",
b'n' => name == b"noshade",
_ => false,
},
8 => match name[0] {
b'c' => name == b"codetype",
b'd' => name == b"disabled",
b'h' => name == b"hreflang",
b'l' => name == b"language",
b'm' => name == b"multiple",
b'n' => name == b"noresize",
b'r' => name == b"readonly",
b's' => name == b"selected",
_ => false,
},
9 => match name[0] {
b'd' => name == b"direction",
b's' => name == b"scrolling",
b'v' => name == b"valuetype",
_ => false,
},
10 => name == b"http_equiv",
14 => name == b"accept_charset",
_ => false,
}
}
#[derive(Clone)]
pub enum ViewTransitionPartName {
All,
Name(CustomIdent),
Class(CustomIdent),
}
impl ViewTransitionPartName {
pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> {
let write_ci = |name: &CustomIdent, dest: &mut Printer| -> Result<(), PrintErr> {
dest.serialize_identifier(name.v())
};
match self {
Self::All => dest.write_str("*"),
Self::Name(name) => write_ci(name, dest),
Self::Class(name) => {
dest.write_char(b'.')?;
write_ci(name, dest)
}
}
}
pub fn parse(input: &mut CssParser) -> CResult<ViewTransitionPartName> {
if input.try_parse(|i| i.expect_delim(b'*')).is_ok() {
return Ok(Self::All);
}
if input.try_parse(|i| i.expect_delim(b'.')).is_ok() {
return Ok(Self::Class(CustomIdent::parse(input)?));
}
Ok(Self::Name(CustomIdent::parse(input)?))
}
pub fn eql(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::All, Self::All) => true,
(Self::Name(a), Self::Name(b)) | (Self::Class(a), Self::Class(b)) => a.eql(b),
_ => false,
}
}
pub fn hash(&self, hasher: &mut Wyhash) {
match self {
Self::All => hasher.update(&0u32.to_ne_bytes()),
Self::Name(n) => {
hasher.update(&1u32.to_ne_bytes());
n.hash(hasher);
}
Self::Class(n) => {
hasher.update(&2u32.to_ne_bytes());
n.hash(hasher);
}
}
}
pub fn deep_clone(&self) -> Self {
self.clone()
}
}
pub fn parse_attribute_flags(input: &mut CssParser) -> CResult<AttributeFlags> {
let location = input.current_source_location();
let token = match input.next() {
Ok(v) => v.clone(),
Err(_) => {
return Ok(AttributeFlags::CaseSensitivityDependsOnName);
}
};
let ident = if let Token::Ident(ident) = &token {
*ident
} else {
return Err(location.new_basic_unexpected_token_error(token));
};
if strings::eql_case_insensitive_ascii_check_length(ident, b"i") {
Ok(AttributeFlags::AsciiCaseInsensitive)
} else if strings::eql_case_insensitive_ascii_check_length(ident, b"s") {
Ok(AttributeFlags::CaseSensitive)
} else {
Err(location.new_basic_unexpected_token_error(token))
}
}
crate::css_eql_partialeq!(NthSelectorData, SpecificityAndFlags, Combinator);