Skip to content

Commit 4348038

Browse files
committed
feat: add ::selection support and pseudo element matching
1 parent 351b38d commit 4348038

File tree

11 files changed

+273
-104
lines changed

11 files changed

+273
-104
lines changed

float-pigment-css/src/parser/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1268,6 +1268,10 @@ pub(crate) fn parse_selector<'a, 't: 'a, 'i: 't>(
12681268
cur_frag.set_pseudo_elements(PseudoElements::After);
12691269
prev_sep = PrevSep::End
12701270
}
1271+
"selection" => {
1272+
cur_frag.set_pseudo_elements(PseudoElements::Selection);
1273+
prev_sep = PrevSep::End
1274+
}
12711275
_ => {
12721276
st.add_warning_with_message(
12731277
WarningKind::UnsupportedPseudoElement,
@@ -1336,6 +1340,16 @@ pub(crate) fn parse_selector<'a, 't: 'a, 'i: 't>(
13361340
parser.current_source_location(),
13371341
);
13381342
}
1343+
"selection" => {
1344+
cur_frag.set_pseudo_elements(PseudoElements::Selection);
1345+
prev_sep = PrevSep::End;
1346+
st.add_warning_with_message(
1347+
WarningKind::InvalidPseudoElement,
1348+
format!("pseudo-elements should begin with double colons (::): {}", parser.slice_from(item_start_pos).trim()),
1349+
item_start_loc,
1350+
parser.current_source_location(),
1351+
);
1352+
}
13391353
_ => {
13401354
st.add_warning_with_message(
13411355
WarningKind::UnsupportedPseudoClass,
@@ -1400,6 +1414,10 @@ pub(crate) fn parse_selector<'a, 't: 'a, 'i: 't>(
14001414
cur_frag.set_pseudo_elements(PseudoElements::After);
14011415
prev_sep = PrevSep::End
14021416
}
1417+
"selection" => {
1418+
cur_frag.set_pseudo_elements(PseudoElements::Selection);
1419+
prev_sep = PrevSep::End
1420+
}
14031421
_ => {
14041422
st.add_warning_with_message(
14051423
WarningKind::UnsupportedPseudoElement,

float-pigment-css/src/query.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::length_num::LengthNum;
77
use crate::property::{
88
NodeProperties, NodePropertiesOrder, Property, PropertyMeta, PropertyValueWithGlobal,
99
};
10-
use crate::sheet::Rule;
10+
use crate::sheet::{PseudoElements, Rule};
1111
use crate::sheet::{RuleWeight, Theme};
1212
use crate::typing::{Length, LengthType};
1313

@@ -181,6 +181,9 @@ pub trait StyleNode {
181181
/// Get an attribute of the node.
182182
fn attribute(&self, name: &str) -> Option<(&str, StyleNodeAttributeCaseSensitivity)>;
183183

184+
/// The pseudo element to query.
185+
fn pseudo_element(&self) -> Option<PseudoElements>;
186+
184187
/// Check if the node has a specified scope.
185188
fn contain_scope(&self, scope: Option<NonZeroUsize>) -> bool {
186189
scope.is_none()
@@ -269,6 +272,10 @@ impl<'a> StyleNode for StyleQuery<'a> {
269272
fn attribute(&self, _name: &str) -> Option<(&str, StyleNodeAttributeCaseSensitivity)> {
270273
None
271274
}
275+
276+
fn pseudo_element(&self) -> Option<PseudoElements> {
277+
None
278+
}
272279
}
273280

274281
impl<'b, 'a: 'b> StyleNode for &'b StyleQuery<'a> {
@@ -305,6 +312,10 @@ impl<'b, 'a: 'b> StyleNode for &'b StyleQuery<'a> {
305312
fn attribute(&self, _name: &str) -> Option<(&str, StyleNodeAttributeCaseSensitivity)> {
306313
None
307314
}
315+
316+
fn pseudo_element(&self) -> Option<PseudoElements> {
317+
None
318+
}
308319
}
309320

310321
/// Represents a matched rule (borrowed form).

float-pigment-css/src/sheet/borrow.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,9 @@ pub enum PseudoElementsType {
559559
Invalid,
560560
Before,
561561
After,
562+
Selection,
562563
}
564+
563565
#[repr(C)]
564566
#[derive(Debug, Serialize, Deserialize)]
565567
#[cfg_attr(debug_assertions, derive(CompatibilityStructCheck))]
@@ -642,6 +644,7 @@ impl SelectorFragment {
642644
Some(x) => match *x {
643645
selector::PseudoElements::Before => PseudoElementsType::Before,
644646
selector::PseudoElements::After => PseudoElementsType::After,
647+
selector::PseudoElements::Selection => PseudoElementsType::Selection,
645648
},
646649
},
647650
attributes: frag

float-pigment-css/src/sheet/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ use crate::parser::Warning;
1515

1616
mod selector;
1717
pub(crate) use selector::{
18-
Attribute, AttributeFlags, AttributeOperator, PseudoClasses, PseudoElements, Selector,
18+
Attribute, AttributeFlags, AttributeOperator, PseudoClasses, Selector,
1919
SelectorFragment, SelectorRelationType, SELECTOR_WHITESPACE,
2020
};
21+
pub use selector::PseudoElements;
2122
mod rule;
2223
pub use rule::Rule;
2324
mod media;

float-pigment-css/src/sheet/selector.rs

Lines changed: 106 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,24 @@ impl core::fmt::Display for PseudoClasses {
7474
}
7575
}
7676

77+
/// The CSS pseudo-elements.
7778
#[cfg_attr(debug_assertions, compatibility_enum_check(selector))]
78-
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
79-
pub(crate) enum PseudoElements {
79+
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
80+
pub enum PseudoElements {
81+
/// The `::before` pseudo-element.
8082
Before,
83+
/// The `::after` pseudo-element.
8184
After,
85+
/// The `::selection` pseudo-element.
86+
Selection,
8287
}
8388

8489
impl core::fmt::Display for PseudoElements {
8590
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8691
let s = match self {
8792
Self::Before => "before",
8893
Self::After => "after",
94+
Self::Selection => "selection",
8995
};
9096
write!(f, "{}", s)
9197
}
@@ -389,24 +395,43 @@ impl Selector {
389395
let mut cur_frag = frag;
390396
loop {
391397
let mut matches = true;
398+
399+
// fails if id/tag_name not matching
392400
if (!cur_frag.id.is_empty()
393401
&& (!same_scope || Some(cur_frag.id.as_str()) != cur_query.id()))
394402
|| (!cur_frag.tag_name.is_empty()
395403
&& (!same_scope || cur_frag.tag_name != cur_query.tag_name()))
396404
{
397405
matches = false
398-
} else if let Some(pc) = cur_frag.pseudo_classes.as_ref() {
399-
match &**pc {
400-
PseudoClasses::Host => {
401-
if sheet_style_scope.is_some()
402-
&& sheet_style_scope != cur_query.host_style_scope()
403-
{
404-
matches = false
406+
}
407+
408+
// fails if pseudo classes not matching
409+
if matches {
410+
if let Some(pc) = cur_frag.pseudo_classes.as_ref() {
411+
// currently we only check `:host`
412+
match &**pc {
413+
PseudoClasses::Host => {
414+
if sheet_style_scope.is_some()
415+
&& sheet_style_scope != cur_query.host_style_scope()
416+
{
417+
matches = false
418+
}
405419
}
420+
_ => matches = false,
406421
}
407-
_ => matches = false,
408422
}
409-
} else {
423+
}
424+
425+
// fails if pseudo classes not matching
426+
if matches {
427+
let pc = cur_frag.pseudo_elements.as_ref().map(|x| (**x).clone());
428+
if pc != cur_query.pseudo_element() {
429+
matches = false
430+
}
431+
}
432+
433+
// fails if any class not matching
434+
if matches {
410435
for class_name in cur_frag.classes.iter() {
411436
if !cur_query.classes().any(|x| {
412437
(sheet_style_scope.is_none() || sheet_style_scope == x.scope())
@@ -415,84 +440,87 @@ impl Selector {
415440
matches = false;
416441
}
417442
}
443+
}
418444

419-
if matches {
420-
if let Some(selector_attributes) = &cur_frag.attributes {
421-
for attribute in selector_attributes.iter() {
422-
let selector_attr_value =
423-
attribute.value.as_deref().unwrap_or_default();
424-
if let Some((element_attr_value, sensitivity)) =
425-
cur_query.attribute(&attribute.name)
426-
{
427-
let sensitivity = match (&attribute.case_insensitive, sensitivity) {
428-
(AttributeFlags::CaseInsensitive, _) | (AttributeFlags::CaseSensitivityDependsOnName, StyleNodeAttributeCaseSensitivity::CaseInsensitive) => {
429-
StyleNodeAttributeCaseSensitivity::CaseInsensitive
430-
}
431-
(AttributeFlags::CaseSensitive, _) | (AttributeFlags::CaseSensitivityDependsOnName, StyleNodeAttributeCaseSensitivity::CaseSensitive) => {
432-
StyleNodeAttributeCaseSensitivity::CaseSensitive
433-
}
434-
};
435-
if !match attribute.operator {
436-
AttributeOperator::Set => true,
437-
AttributeOperator::Exact => sensitivity
438-
.eq(element_attr_value, selector_attr_value),
439-
AttributeOperator::List => {
440-
if selector_attr_value.is_empty() {
441-
false
442-
} else {
443-
element_attr_value
444-
.split(SELECTOR_WHITESPACE)
445-
.any(|x| {
446-
sensitivity
447-
.eq(x, selector_attr_value)
448-
})
449-
}
450-
}
451-
AttributeOperator::Hyphen => {
452-
#[allow(clippy::comparison_chain)]
453-
if element_attr_value.len()
454-
< selector_attr_value.len()
455-
{
456-
false
457-
} else if element_attr_value.len()
458-
== selector_attr_value.len()
459-
{
460-
element_attr_value == selector_attr_value
461-
} else {
462-
sensitivity.starts_with(
463-
element_attr_value,
464-
&alloc::format!(
465-
"{}-",
466-
selector_attr_value
467-
),
468-
)
469-
}
445+
// fails if any attribute not matching
446+
if matches {
447+
if let Some(selector_attributes) = &cur_frag.attributes {
448+
for attribute in selector_attributes.iter() {
449+
let selector_attr_value =
450+
attribute.value.as_deref().unwrap_or_default();
451+
if let Some((element_attr_value, sensitivity)) =
452+
cur_query.attribute(&attribute.name)
453+
{
454+
let sensitivity = match (&attribute.case_insensitive, sensitivity) {
455+
(AttributeFlags::CaseInsensitive, _) | (AttributeFlags::CaseSensitivityDependsOnName, StyleNodeAttributeCaseSensitivity::CaseInsensitive) => {
456+
StyleNodeAttributeCaseSensitivity::CaseInsensitive
457+
}
458+
(AttributeFlags::CaseSensitive, _) | (AttributeFlags::CaseSensitivityDependsOnName, StyleNodeAttributeCaseSensitivity::CaseSensitive) => {
459+
StyleNodeAttributeCaseSensitivity::CaseSensitive
460+
}
461+
};
462+
if !match attribute.operator {
463+
AttributeOperator::Set => true,
464+
AttributeOperator::Exact => sensitivity
465+
.eq(element_attr_value, selector_attr_value),
466+
AttributeOperator::List => {
467+
if selector_attr_value.is_empty() {
468+
false
469+
} else {
470+
element_attr_value
471+
.split(SELECTOR_WHITESPACE)
472+
.any(|x| {
473+
sensitivity
474+
.eq(x, selector_attr_value)
475+
})
470476
}
471-
AttributeOperator::Begin => sensitivity
472-
.starts_with(
477+
}
478+
AttributeOperator::Hyphen => {
479+
#[allow(clippy::comparison_chain)]
480+
if element_attr_value.len()
481+
< selector_attr_value.len()
482+
{
483+
false
484+
} else if element_attr_value.len()
485+
== selector_attr_value.len()
486+
{
487+
element_attr_value == selector_attr_value
488+
} else {
489+
sensitivity.starts_with(
473490
element_attr_value,
474-
selector_attr_value,
475-
),
476-
AttributeOperator::End => sensitivity.ends_with(
477-
element_attr_value,
478-
selector_attr_value,
479-
),
480-
AttributeOperator::Contain => sensitivity.contains(
491+
&alloc::format!(
492+
"{}-",
493+
selector_attr_value
494+
),
495+
)
496+
}
497+
}
498+
AttributeOperator::Begin => sensitivity
499+
.starts_with(
481500
element_attr_value,
482501
selector_attr_value,
483502
),
484-
} {
485-
matches = false;
486-
break;
487-
}
488-
} else {
503+
AttributeOperator::End => sensitivity.ends_with(
504+
element_attr_value,
505+
selector_attr_value,
506+
),
507+
AttributeOperator::Contain => sensitivity.contains(
508+
element_attr_value,
509+
selector_attr_value,
510+
),
511+
} {
489512
matches = false;
490513
break;
491514
}
515+
} else {
516+
matches = false;
517+
break;
492518
}
493519
}
494520
}
495521
}
522+
523+
// check ancestors if not matches
496524
if !matches {
497525
if allow_ancestor {
498526
cur_query = match query.next_back() {
@@ -504,6 +532,8 @@ impl Selector {
504532
}
505533
continue;
506534
}
535+
536+
// check ancestors if the rule requires
507537
if let Some(ref relation) = cur_frag.relation {
508538
cur_query = match query.next_back() {
509539
Some(x) => x,

0 commit comments

Comments
 (0)