mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
Implements a fix for #43036 - Renders content for `::marker` - If content is not present render marker_image/marker_string Here's a small webpage I wrote to test if my implementation works :) <img width="1136" height="880" alt="Screenshot 2026-03-21 at 4 50 43 PM" src="https://github.com/user-attachments/assets/91ccbfd9-2c18-446d-bac5-d559f483b08c" /> # Testing The existing WPT ones should be sufficient, the tests need to be updated. ## (WPT) Stable Unexpected Results that are failing: | Test | Issue | Remark | |--------|--------|--------| | /css/css-lists/marker-dynamic-content-change.html | #43120 | | | /css/css-lists/marker-quotes.html | #30365 | Test File: Ordered List showing '0' for all `li` | | /css/css-pseudo/marker-content-001.html | #30365 | Ref file: Ordered List showing '0' for all `li` | | /css/css-pseudo/marker-content-001b.html | #30365 | Ref file: Ordered List showing '0' for all `li` | | /css/css-pseudo/marker-content-001c.html | #30365 | Ref file: Ordered List showing '0' for all `li` | | /css/css-pseudo/marker-content-020.html | | Test File: JavaScript dynamically toggles the `no-marker` class, but it doesn't update the DOM when called from inside `addEventListener()` and `requestAnimationFrame()` functions. | | /css/selectors/has-style-sharing-pseudo-007.html | #25133 | Test file uses `has` to remove `content` from a `li`; `has` pseudo-class has not been implemented yet | | /css/selectors/has-style-sharing-pseudo-008.html | #25133 | Test file uses `has` to remove `content` from a `li`; `has` pseudo-class has not been implemented yet | --------- Signed-off-by: Niya Gupta <niyabits@gmail.com> Signed-off-by: niya <niyabits@gmail.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
165 lines
7.1 KiB
Rust
165 lines
7.1 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||
|
||
use style::counter_style::{CounterStyle, Symbol, SymbolsType};
|
||
use style::properties::longhands::list_style_type::computed_value::T as ListStyleType;
|
||
use style::values::computed::Image;
|
||
use style::values::generics::counters::Content;
|
||
use stylo_atoms::atom;
|
||
|
||
use crate::context::LayoutContext;
|
||
use crate::dom_traversal::{
|
||
NodeAndStyleInfo, PseudoElementContentItem, generate_pseudo_element_content,
|
||
};
|
||
use crate::replaced::ReplacedContents;
|
||
|
||
/// <https://drafts.csswg.org/css-lists/#content-property>
|
||
pub(crate) fn make_marker<'dom>(
|
||
context: &LayoutContext,
|
||
info: &NodeAndStyleInfo<'dom>,
|
||
) -> Option<(NodeAndStyleInfo<'dom>, Vec<PseudoElementContentItem>)> {
|
||
let marker_info =
|
||
info.with_pseudo_element(context, style::selector_parser::PseudoElement::Marker)?;
|
||
let style = &marker_info.style;
|
||
let list_style = style.get_list();
|
||
|
||
// https://drafts.csswg.org/css-lists/#marker-image
|
||
let marker_image = || match &list_style.list_style_image {
|
||
Image::Url(url) => Some(vec![
|
||
PseudoElementContentItem::Replaced(ReplacedContents::from_image_url(
|
||
marker_info.node,
|
||
context,
|
||
url,
|
||
)?),
|
||
PseudoElementContentItem::Text(" ".into()),
|
||
]),
|
||
// XXX: Non-None image types unimplemented.
|
||
Image::ImageSet(..) |
|
||
Image::Gradient(..) |
|
||
Image::CrossFade(..) |
|
||
Image::PaintWorklet(..) |
|
||
Image::None => None,
|
||
Image::LightDark(..) => unreachable!("light-dark() should be disabled"),
|
||
};
|
||
|
||
let content = match &marker_info.style.get_counters().content {
|
||
Content::Items(_) => generate_pseudo_element_content(&marker_info, context),
|
||
Content::None => return None,
|
||
Content::Normal => marker_image().or_else(|| {
|
||
Some(vec![PseudoElementContentItem::Text(marker_string(
|
||
&list_style.list_style_type,
|
||
)?)])
|
||
})?,
|
||
};
|
||
|
||
Some((marker_info, content))
|
||
}
|
||
|
||
fn symbol_to_string(symbol: &Symbol) -> &str {
|
||
match symbol {
|
||
Symbol::String(string) => string,
|
||
Symbol::Ident(ident) => &ident.0,
|
||
}
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/css-counter-styles-3/#generate-a-counter>
|
||
pub(crate) fn generate_counter_representation(counter_style: &CounterStyle) -> &str {
|
||
// TODO: Most counter styles produce different results depending on the counter value.
|
||
// Since we don't support counter properties yet, assume a value of 0 for now.
|
||
match counter_style {
|
||
CounterStyle::None | CounterStyle::String(_) => unreachable!("Invalid counter style"),
|
||
CounterStyle::Name(name) => match name.0 {
|
||
atom!("disc") => "\u{2022}", /* "•" */
|
||
atom!("circle") => "\u{25E6}", /* "◦" */
|
||
atom!("square") => "\u{25AA}", /* "▪" */
|
||
atom!("disclosure-open") => "\u{25BE}", /* "▾" */
|
||
// TODO: Use U+25C2 "◂" depending on the direction.
|
||
atom!("disclosure-closed") => "\u{25B8}", /* "▸" */
|
||
atom!("decimal-leading-zero") => "00",
|
||
atom!("arabic-indic") => "\u{660}", /* "٠" */
|
||
atom!("bengali") => "\u{9E6}", /* "০" */
|
||
atom!("cambodian") | atom!("khmer") => "\u{17E0}", /* "០" */
|
||
atom!("devanagari") => "\u{966}", /* "०" */
|
||
atom!("gujarati") => "\u{AE6}", /* "૦" */
|
||
atom!("gurmukhi") => "\u{A66}", /* "੦" */
|
||
atom!("kannada") => "\u{CE6}", /* "೦" */
|
||
atom!("lao") => "\u{ED0}", /* "໐" */
|
||
atom!("malayalam") => "\u{D66}", /* "൦" */
|
||
atom!("mongolian") => "\u{1810}", /* "᠐" */
|
||
atom!("myanmar") => "\u{1040}", /* "၀" */
|
||
atom!("oriya") => "\u{B66}", /* "୦" */
|
||
atom!("persian") => "\u{6F0}", /* "۰" */
|
||
atom!("tamil") => "\u{BE6}", /* "௦" */
|
||
atom!("telugu") => "\u{C66}", /* "౦" */
|
||
atom!("thai") => "\u{E50}", /* "๐" */
|
||
atom!("tibetan") => "\u{F20}", /* "༠" */
|
||
atom!("cjk-decimal") |
|
||
atom!("cjk-earthly-branch") |
|
||
atom!("cjk-heavenly-stem") |
|
||
atom!("japanese-informal") => "\u{3007}", /* "〇" */
|
||
atom!("korean-hangul-formal") => "\u{C601}", /* "영" */
|
||
atom!("korean-hanja-informal") |
|
||
atom!("korean-hanja-formal") |
|
||
atom!("japanese-formal") |
|
||
atom!("simp-chinese-informal") |
|
||
atom!("simp-chinese-formal") |
|
||
atom!("trad-chinese-informal") |
|
||
atom!("trad-chinese-formal") |
|
||
atom!("cjk-ideographic") => "\u{96F6}", /* "零" */
|
||
// Fall back to decimal.
|
||
_ => "0",
|
||
},
|
||
CounterStyle::Symbols { ty, symbols } => match ty {
|
||
// For numeric, use the first symbol, which represents the value 0.
|
||
SymbolsType::Numeric => {
|
||
symbol_to_string(symbols.0.first().expect("symbols() should have symbols"))
|
||
},
|
||
// For cyclic, the first symbol represents the value 1. However, it loops back,
|
||
// so the last symbol represents the value 0.
|
||
SymbolsType::Cyclic => {
|
||
symbol_to_string(symbols.0.last().expect("symbols() should have symbols"))
|
||
},
|
||
// For the others, the first symbol represents the value 1, and 0 is out of range.
|
||
// Therefore, fall back to `decimal`.
|
||
SymbolsType::Alphabetic | SymbolsType::Symbolic | SymbolsType::Fixed => "0",
|
||
},
|
||
}
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/css-lists/#marker-string>
|
||
pub(crate) fn marker_string(list_style_type: &ListStyleType) -> Option<String> {
|
||
let suffix = match &list_style_type.0 {
|
||
CounterStyle::None => return None,
|
||
CounterStyle::String(string) => return Some(string.to_string()),
|
||
CounterStyle::Name(name) => match name.0 {
|
||
atom!("disc") |
|
||
atom!("circle") |
|
||
atom!("square") |
|
||
atom!("disclosure-open") |
|
||
atom!("disclosure-closed") => " ",
|
||
atom!("hiragana") |
|
||
atom!("hiragana-iroha") |
|
||
atom!("katakana") |
|
||
atom!("katakana-iroha") |
|
||
atom!("cjk-decimal") |
|
||
atom!("cjk-earthly-branch") |
|
||
atom!("cjk-heavenly-stem") |
|
||
atom!("japanese-informal") |
|
||
atom!("japanese-formal") |
|
||
atom!("simp-chinese-informal") |
|
||
atom!("simp-chinese-formal") |
|
||
atom!("trad-chinese-informal") |
|
||
atom!("trad-chinese-formal") |
|
||
atom!("cjk-ideographic") => "\u{3001}", /* "、" */
|
||
atom!("korean-hangul-formal") |
|
||
atom!("korean-hanja-informal") |
|
||
atom!("korean-hanja-formal") => ", ",
|
||
atom!("ethiopic-numeric") => "/ ",
|
||
_ => ". ",
|
||
},
|
||
CounterStyle::Symbols { .. } => " ",
|
||
};
|
||
Some(generate_counter_representation(&list_style_type.0).to_string() + suffix)
|
||
}
|