1use super::{
9 ArbitraryDeclarationValue, AttrTaint, ParsedArgument, Substitution, SubstitutionArgument,
10 SubstitutionParserContext, SubstitutionReplacementContext, parse_optional_fallback,
11};
12use crate::custom_properties::{ComputedValue, Name, VariableValue};
13use crate::parser::{Parse, ParserContext};
14use crate::properties_and_values::{
15 syntax::Descriptor as SyntaxDescriptor,
16 value::{
17 AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
18 SpecifiedValue as SpecifiedRegisteredValue,
19 },
20};
21use crate::stylesheets::UrlExtraData;
22use crate::values::generics::calc::SortKey as AttrUnit;
23use crate::values::specified::ParsedNamespace;
24use crate::{Atom, FxHashMap, LocalName, Namespace, Prefix, derives::*};
25use cssparser::{Delimiter, Parser, Token, TokenSerializationType};
26use servo_arc::Arc;
27use std::borrow::Cow;
28use style_traits::{ParseError, StyleParseErrorKind};
29
30impl LocalName {
31 #[cfg(feature = "gecko")]
32 fn with_name<R>(name: &Name, callback: impl FnOnce(&Self) -> R) -> R {
33 callback(Self::cast(name))
34 }
35
36 #[cfg(feature = "servo")]
37 fn with_name<'a, R>(name: &'a Name, callback: impl FnOnce(&Self) -> R) -> R {
38 callback(&name.as_str().into())
39 }
40}
41
42impl From<Name> for LocalName {
43 #[cfg(feature = "gecko")]
44 fn from(name: Name) -> Self {
45 Self::new(name)
46 }
47
48 #[cfg(feature = "servo")]
49 fn from(name: Name) -> Self {
50 name.as_str().into()
51 }
52}
53
54#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
56enum AttrType {
57 None,
59 RawString,
61 Type(SyntaxDescriptor),
63 Unit(AttrUnit),
66}
67
68impl Parse for AttrType {
69 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
72 Ok(input
73 .try_parse(|input| {
74 Ok(match input.next()? {
75 Token::Function(name) if name.eq_ignore_ascii_case("type") => {
76 Self::Type(input.parse_nested_block(SyntaxDescriptor::from_css_parser)?)
77 },
78 Token::Ident(ident) => {
79 if ident.eq_ignore_ascii_case("raw-string") {
80 Self::RawString
81 } else if let Ok(unit) = AttrUnit::from_ident(ident) {
82 Self::Unit(unit)
83 } else {
84 return Err(ParseError::custom(StyleParseErrorKind::UnexpectedIdent));
85 }
86 },
87 Token::Delim('%') => Self::Unit(AttrUnit::Percentage),
88 _ => return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError)),
89 })
90 })
91 .unwrap_or(Self::None))
92 }
93}
94
95#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
97pub struct AttrName {
98 pub name: Name,
100 namespace: ParsedNamespace,
101}
102
103impl AttrName {
104 pub fn value_for_cycle_resolution(
107 &self,
108 context: &mut SubstitutionReplacementContext,
109 url_data: &UrlExtraData,
110 ) -> Result<ComputedRegisteredValue, ()> {
111 let ParsedNamespace::Known(ref namespace) = self.namespace else {
112 return Err(());
113 };
114 let attr = LocalName::with_name(&self.name, |local_name| {
115 context
116 .attribute_tracker
117 .query(local_name, namespace)
118 .ok_or(())
119 })?;
120 let parser_context = context.parser_context(url_data);
121 let mut parser = Parser::new(&attr);
122 let value = VariableValue::parse(&parser_context, &mut parser).map_err(|_| ())?;
124 Ok(ComputedRegisteredValue::universal(Arc::new(value)))
125 }
126}
127
128impl Parse for AttrName {
129 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
130 let mut namespace = ParsedNamespace::Known(Namespace::default());
131 if let Ok(ns) =
132 input.try_parse(|input| ParsedNamespace::parse(&context.namespaces.prefixes, input))
133 {
134 namespace = ns;
135 let prev = input.state();
136 let is_ident = matches!(input.next_including_whitespace(), Ok(Token::Ident(_)));
137 input.reset(&prev);
138 if !is_ident {
139 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
140 }
141 }
142 let name = Atom::from(input.expect_ident()?.as_ref());
143 Ok(AttrName { name, namespace })
144 }
145}
146
147#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
149pub struct AttrArgument {
150 pub name: AttrName,
152 ty: AttrType,
154}
155
156impl AttrArgument {
157 pub fn has_type(&self) -> bool {
159 matches!(self.ty, AttrType::Type(..))
160 }
161}
162
163impl Parse for AttrArgument {
164 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
165 let name = AttrName::parse(context, input)?;
166 let ty = AttrType::parse(context, input)?;
167 Ok(AttrArgument { name, ty })
168 }
169}
170
171#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
173pub struct AttrFunction {
174 pub arg: SubstitutionArgument<AttrArgument>,
176 pub fallback: Option<ArbitraryDeclarationValue>,
178 namespaces: Option<FxHashMap<Prefix, Namespace>>,
180}
181
182impl AttrFunction {
183 pub fn parse(
187 input: &mut Parser,
188 context: &mut SubstitutionParserContext,
189 ) -> Result<Self, ParseError> {
190 let arg = SubstitutionArgument::try_parse(input, context, Delimiter::Comma)?;
191 let namespaces = match arg {
192 SubstitutionArgument::Unparsed(..) => {
193 Some(context.parser_context.namespaces.prefixes.clone())
194 },
195 SubstitutionArgument::Parsed(..) | SubstitutionArgument::Invalid => None,
196 };
197 let fallback = parse_optional_fallback(input, context)?;
198 Ok(Self {
199 arg,
200 namespaces,
201 fallback,
202 })
203 }
204
205 pub fn replace<'a>(
208 &'a self,
209 source: &'a VariableValue,
210 context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
211 ) -> Result<Substitution<'a>, ()> {
212 let arg = self.replace_arg(source, context);
213 let is_type_omitted = arg
214 .value
215 .as_ref()
216 .is_some_and(|arg| arg.ty == AttrType::None);
217 let mut substitution = arg
218 .value
219 .and_then(|arg| {
220 let ParsedNamespace::Known(ref namespace) = arg.name.namespace else {
221 return None;
222 };
223 let attr = LocalName::with_name(&arg.name.name, |local_name| {
224 context.attribute_tracker.query(local_name, namespace)
225 })?;
226 Some((arg, attr))
227 })
228 .map_or_else(
229 || {
230 if self.fallback.is_none() && is_type_omitted {
233 Some(simple_attr_substitution(""))
234 } else {
235 None
236 }
237 },
238 |(arg, attr)| {
239 let attr = if let AttrType::Type(_) = &arg.ty {
240 if context.computed_context.in_container_query {
247 attr
248 } else {
249 context
250 .substitution_functions
251 .get_attr(&arg.name.name)
252 .map(|v| v.to_variable_value())?
253 .css
254 }
255 } else {
256 attr
257 };
258 let mut parser = Parser::new(&attr);
259 match &arg.ty {
260 AttrType::Unit(unit) => {
261 let css = {
262 parser.expect_number().ok()?;
264 let mut s = attr.clone();
265 s.push_str(unit.as_ref());
266 s
267 };
268 let serialization = match unit {
269 AttrUnit::Number => TokenSerializationType::Number,
270 AttrUnit::Percentage => TokenSerializationType::Percentage,
271 _ => TokenSerializationType::Dimension,
272 };
273 let value = ComputedValue::new(
274 css,
275 &source.url_data,
276 serialization,
277 serialization,
278 );
279 Some(Substitution::from_value(
280 value, true,
281 ))
282 },
283 AttrType::Type(syntax) => {
284 let value = SpecifiedRegisteredValue::parse(
285 &mut parser,
286 syntax,
287 &source.url_data,
288 AllowComputationallyDependent::Yes,
289 AttrTaint::default(),
290 context.property_id,
291 )
292 .ok()?;
293 let value = value.to_variable_value();
294 Some(Substitution::from_value(
295 value, true,
296 ))
297 },
298 AttrType::RawString | AttrType::None => {
299 Some(simple_attr_substitution(&attr))
300 },
301 }
302 },
303 )
304 .ok_or(())
305 .or_else(|_| {
306 context.substitute_declaration_value(self.fallback.as_ref().ok_or(())?, source)
307 })?;
308 substitution.attr_tainted |= arg.attr_tainted;
309 Ok(substitution)
310 }
311
312 pub fn replace_arg<'a>(
314 &'a self,
315 source: &'a VariableValue,
316 context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
317 ) -> ParsedArgument<'a, AttrArgument> {
318 self.arg.substitute_and_parse(source, context)
319 }
320
321 pub fn for_each_unparsed_value<F>(&self, mut f: F)
323 where
324 F: FnMut(&ArbitraryDeclarationValue),
325 {
326 if let SubstitutionArgument::Unparsed(ref arg) = self.arg {
327 f(arg);
328 }
329 if let Some(ref fallback) = self.fallback {
330 f(fallback);
331 }
332 }
333}
334
335fn simple_attr_substitution<'a>(s: &str) -> Substitution<'a> {
336 let mut css = String::with_capacity(s.len() + 2);
337 cssparser::serialize_string(s, &mut css).unwrap();
338 Substitution::new(
339 Cow::Owned(css),
340 TokenSerializationType::Nothing,
341 TokenSerializationType::Nothing,
342 true,
343 )
344}