style/values/specified/
random.rs1use crate::Atom;
9use crate::derives::*;
10use crate::parser::{Parse, ParserContext};
11use crate::properties::PropertyIdRef;
12use crate::values::computed::{Context, ToComputedValue};
13use crate::values::specified::calc::PercentageContext;
14use crate::values::specified::number::{Number, parse_number_with_clamping_mode};
15use crate::values::{CSSFloat, CustomIdent, DashedIdent};
16use cssparser::{Parser, match_ignore_ascii_case};
17use selectors::parser::SelectorParseErrorKind;
18use std::fmt::{self, Write};
19use style_traits::values::SequenceWriter;
20use style_traits::values::specified::AllowedNumericType;
21use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
22
23#[repr(C)]
28#[derive(Clone, Debug, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)]
29pub struct RandomUaIdent(CustomIdent);
30
31impl RandomUaIdent {
32 fn ua_prefixed(property_id: PropertyIdRef) -> String {
33 let mut ua_ident = "ua-".to_string();
34 match property_id {
35 PropertyIdRef::NonCustom(non_custom) => {
36 ua_ident += non_custom.name();
37 },
38 PropertyIdRef::Custom(custom) => {
39 ua_ident += "--";
40 #[cfg(feature = "gecko")]
41 {
42 custom.with_str(|name| ua_ident += name);
43 }
44 #[cfg(feature = "servo")]
45 {
46 ua_ident += custom;
47 }
48 },
49 };
50 ua_ident
51 }
52
53 pub fn from_property(property_id: PropertyIdRef) -> Self {
55 Self(CustomIdent(Atom::from(Self::ua_prefixed(property_id))))
56 }
57
58 pub fn from_property_and_index(property_id: PropertyIdRef, index: i32) -> Self {
62 let mut ua_ident = Self::ua_prefixed(property_id);
63 write!(ua_ident, "-{index}").unwrap();
64 Self(CustomIdent(Atom::from(ua_ident)))
65 }
66
67 pub fn empty() -> Self {
69 Self(CustomIdent(atom!("")))
70 }
71
72 pub fn is_empty(&self) -> bool {
74 self.0.0 == atom!("")
75 }
76}
77
78impl Parse for RandomUaIdent {
79 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
80 let ident = input.expect_ident()?;
81
82 if ident.eq_ignore_ascii_case("property-scoped") {
83 let Some(property_id) = context.property_declaration_context.property_id() else {
84 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
85 };
86 return Ok(RandomUaIdent::from_property(property_id));
87 }
88
89 if ident.eq_ignore_ascii_case("property-index-scoped") {
90 let Some(property_id) = context.property_declaration_context.property_id() else {
91 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
92 };
93 let index = context.property_declaration_context.current_random_index();
94 return Ok(RandomUaIdent::from_property_and_index(property_id, index));
95 }
96
97 if !ident.starts_with("ua-") {
98 return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
99 }
100 CustomIdent::from_ident(ident, &[]).map(RandomUaIdent)
101 }
102}
103
104#[repr(C)]
108#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
109pub struct RandomCacheKey {
110 pub name: DashedIdent,
112 pub ua_ident: RandomUaIdent,
115 pub is_element_scoped: bool,
117}
118
119impl Parse for RandomCacheKey {
120 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
121 let mut key = RandomCacheKey {
122 name: DashedIdent::empty(),
123 ua_ident: RandomUaIdent::empty(),
124 is_element_scoped: false,
125 };
126
127 loop {
128 if key.name.is_empty()
129 && let Ok(name) = input.try_parse(|input| DashedIdent::parse(context, input))
130 {
131 key.name = name;
132 continue;
133 }
134
135 if !key.is_element_scoped
136 && input
137 .try_parse(|input| input.expect_ident_matching("element-scoped"))
138 .is_ok()
139 {
140 if !context.has_element_context() {
141 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
142 }
143
144 key.is_element_scoped = true;
145 continue;
146 }
147
148 if key.ua_ident.is_empty()
149 && let Ok(ua_ident) = input.try_parse(|input| RandomUaIdent::parse(context, input))
150 {
151 key.ua_ident = ua_ident;
152 continue;
153 }
154
155 break;
156 }
157
158 if key.name.is_empty() && !key.is_element_scoped && key.ua_ident.is_empty() {
159 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
160 }
161
162 Ok(key)
163 }
164}
165
166impl ToCss for RandomCacheKey {
167 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
168 where
169 W: Write,
170 {
171 let mut writer = SequenceWriter::new(dest, " ");
172 if !self.name.is_empty() {
173 writer.item(&self.name)?;
174 }
175 if self.is_element_scoped {
176 writer.raw_item("element-scoped")?;
177 }
178 if !self.ua_ident.is_empty() {
179 writer.item(&self.ua_ident)?;
180 }
181 Ok(())
182 }
183}
184
185#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
192#[repr(u8)]
193pub enum RandomKey {
194 Fixed(Number),
198 CacheKey(RandomCacheKey),
200}
201
202impl RandomKey {
203 pub fn auto(context: &ParserContext) -> Result<Self, ParseError> {
206 if !context.has_element_context() {
207 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
208 }
209
210 let Some(property_id) = context.property_declaration_context.property_id() else {
211 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
212 };
213 let index = context.property_declaration_context.current_random_index();
214
215 Ok(Self::CacheKey(RandomCacheKey {
216 name: DashedIdent::empty(),
217 ua_ident: RandomUaIdent::from_property_and_index(property_id, index),
218 is_element_scoped: true,
219 }))
220 }
221}
222
223impl Parse for RandomKey {
224 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
225 if let Ok(key) = input.try_parse(|input| RandomCacheKey::parse(context, input)) {
226 return Ok(Self::CacheKey(key));
227 }
228
229 let ident = input.expect_ident()?;
230 match_ignore_ascii_case! { &ident,
231 "auto" => Self::auto(context),
232 "fixed" => {
233 let fixed = parse_number_with_clamping_mode(
234 context,
235 input,
236 AllowedNumericType::ZeroToOne,
237 PercentageContext::not_allowed(),
238 )?;
239 Ok(Self::Fixed(fixed))
240 },
241 _ => Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent)),
242 }
243 }
244}
245
246impl ToComputedValue for RandomKey {
247 type ComputedValue = CSSFloat;
248
249 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
250 match self {
251 Self::Fixed(number) => number.to_computed_value(context),
252 Self::CacheKey(cache_key) => context.random_base_value(cache_key),
253 }
254 }
255
256 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
257 Self::Fixed(Number::from_computed_value(computed))
258 }
259}
260
261impl ToCss for RandomKey {
262 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
263 where
264 W: Write,
265 {
266 match self {
267 RandomKey::Fixed(number) => {
268 dest.write_str("fixed ")?;
269 number.to_css(dest)
270 },
271 RandomKey::CacheKey(cache_key) => cache_key.to_css(dest),
272 }
273 }
274}