1use super::{
9 ArbitraryDeclarationValue, ParsedArgument, Substitution, SubstitutionArgument,
10 SubstitutionParserContext, SubstitutionReplacementContext, parse_optional_fallback,
11};
12use crate::custom_properties::{Name, VariableValue};
13use crate::device::Device;
14use crate::parser::{Parse, ParserContext};
15use crate::stylesheets::UrlExtraData;
16use crate::{Atom, derives::*};
17use cssparser::{Delimiter, Parser};
18use style_traits::ParseError;
19
20#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
22pub struct EnvName(pub Name);
23
24impl Parse for EnvName {
25 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
26 Ok(EnvName(Atom::from(input.expect_ident()?.as_ref())))
29 }
30}
31
32#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
34pub struct EnvFunction {
35 pub name: SubstitutionArgument<EnvName>,
37 pub fallback: Option<ArbitraryDeclarationValue>,
39}
40
41impl EnvFunction {
42 pub fn parse(
48 input: &mut Parser,
49 context: &mut SubstitutionParserContext,
50 ) -> Result<Self, ParseError> {
51 let name = SubstitutionArgument::try_parse(input, context, Delimiter::Comma)?;
52 let fallback = parse_optional_fallback(input, context)?;
53 Ok(Self { name, fallback })
54 }
55
56 pub fn replace<'a>(
59 &'a self,
60 source: &'a VariableValue,
61 context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
62 ) -> Result<Substitution<'a>, ()> {
63 let name = self.replace_name(source, context);
64 let device = context.stylist.device();
65 let mut substitution = name
66 .value
67 .and_then(|name| {
68 device
69 .environment()
70 .get(&name.0, device, &context.parser_context(&source.url_data))
71 .map(|v| Substitution::from_value(v, false))
72 })
73 .ok_or(())
74 .or_else(|_| {
75 context.substitute_declaration_value(self.fallback.as_ref().ok_or(())?, source)
76 })?;
77 substitution.attr_tainted |= name.attr_tainted;
78 Ok(substitution)
79 }
80
81 pub fn replace_name<'a>(
83 &'a self,
84 source: &'a VariableValue,
85 context: &mut SubstitutionReplacementContext<'a, '_, '_, '_>,
86 ) -> ParsedArgument<'a, EnvName> {
87 self.name.substitute_and_parse(source, context)
88 }
89
90 pub fn for_each_unparsed_value<F>(&self, mut f: F)
92 where
93 F: FnMut(&ArbitraryDeclarationValue),
94 {
95 if let SubstitutionArgument::Unparsed(ref arg) = self.name {
96 f(arg);
97 }
98 if let Some(ref fallback) = self.fallback {
99 f(fallback);
100 }
101 }
102}
103
104#[derive(Debug, MallocSizeOf)]
109pub struct CssEnvironment;
110
111type EnvironmentEvaluator = fn(device: &Device, url_data: &UrlExtraData) -> VariableValue;
112
113struct EnvironmentVariable {
114 name: Atom,
115 evaluator: EnvironmentEvaluator,
116}
117
118macro_rules! make_variable {
119 ($name:expr, $evaluator:expr) => {{
120 EnvironmentVariable {
121 name: $name,
122 evaluator: $evaluator,
123 }
124 }};
125}
126
127fn get_safearea_inset_top(device: &Device, url_data: &UrlExtraData) -> VariableValue {
128 VariableValue::pixels(device.safe_area_insets().top, url_data)
129}
130
131fn get_safearea_inset_bottom(device: &Device, url_data: &UrlExtraData) -> VariableValue {
132 VariableValue::pixels(device.safe_area_insets().bottom, url_data)
133}
134
135fn get_safearea_inset_left(device: &Device, url_data: &UrlExtraData) -> VariableValue {
136 VariableValue::pixels(device.safe_area_insets().left, url_data)
137}
138
139fn get_safearea_inset_right(device: &Device, url_data: &UrlExtraData) -> VariableValue {
140 VariableValue::pixels(device.safe_area_insets().right, url_data)
141}
142
143#[cfg(feature = "gecko")]
144fn get_content_preferred_color_scheme(device: &Device, url_data: &UrlExtraData) -> VariableValue {
145 use crate::queries::values::PrefersColorScheme;
146 let prefers_color_scheme = unsafe {
147 crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
148 device.document(),
149 true,
150 )
151 };
152 VariableValue::ident(
153 match prefers_color_scheme {
154 PrefersColorScheme::Light => "light",
155 PrefersColorScheme::Dark => "dark",
156 },
157 url_data,
158 )
159}
160
161#[cfg(feature = "servo")]
162fn get_content_preferred_color_scheme(_device: &Device, url_data: &UrlExtraData) -> VariableValue {
163 VariableValue::ident("light", url_data)
165}
166
167fn get_scrollbar_inline_size(device: &Device, url_data: &UrlExtraData) -> VariableValue {
168 VariableValue::pixels(device.scrollbar_inline_size().px(), url_data)
169}
170
171fn get_hairline(device: &Device, url_data: &UrlExtraData) -> VariableValue {
172 VariableValue::pixels(
173 app_units::Au(device.app_units_per_device_pixel()).to_f32_px(),
174 url_data,
175 )
176}
177
178static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
179 make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
180 make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
181 make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left),
182 make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
183];
184
185#[cfg(feature = "gecko")]
186macro_rules! lnf_int {
187 ($id:ident) => {
188 unsafe {
189 crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt(
190 crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32,
191 )
192 }
193 };
194}
195
196#[cfg(feature = "servo")]
197macro_rules! lnf_int {
198 ($id:ident) => {
199 0
201 };
202}
203
204macro_rules! lnf_int_variable {
205 ($atom:expr, $id:ident, $ctor:ident) => {{
206 fn __eval(_: &Device, url_data: &UrlExtraData) -> VariableValue {
207 VariableValue::$ctor(lnf_int!($id), url_data)
208 }
209 make_variable!($atom, __eval)
210 }};
211}
212
213fn eval_gtk_csd_titlebar_radius(device: &Device, url_data: &UrlExtraData) -> VariableValue {
214 let int_pixels = lnf_int!(TitlebarRadius);
215 let unzoomed_scale =
216 device.device_pixel_ratio_ignoring_full_zoom().get() / device.device_pixel_ratio().get();
217 VariableValue::pixels(int_pixels as f32 * unzoomed_scale, url_data)
218}
219
220fn get_submit_string(device: &Device, url_data: &UrlExtraData) -> VariableValue {
221 VariableValue::string(device.submit_string(), url_data)
222}
223
224static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 10] = [
225 make_variable!(
226 atom!("-moz-gtk-csd-titlebar-radius"),
227 eval_gtk_csd_titlebar_radius
228 ),
229 lnf_int_variable!(
230 atom!("-moz-gtk-csd-tooltip-radius"),
231 TooltipRadius,
232 int_pixels
233 ),
234 lnf_int_variable!(
235 atom!("-moz-gtk-csd-close-button-position"),
236 GTKCSDCloseButtonPosition,
237 integer
238 ),
239 lnf_int_variable!(
240 atom!("-moz-gtk-csd-minimize-button-position"),
241 GTKCSDMinimizeButtonPosition,
242 integer
243 ),
244 lnf_int_variable!(
245 atom!("-moz-gtk-csd-maximize-button-position"),
246 GTKCSDMaximizeButtonPosition,
247 integer
248 ),
249 lnf_int_variable!(
250 atom!("-moz-overlay-scrollbar-fade-duration"),
251 ScrollbarFadeDuration,
252 int_ms
253 ),
254 make_variable!(
255 atom!("-moz-content-preferred-color-scheme"),
256 get_content_preferred_color_scheme
257 ),
258 make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size),
259 make_variable!(atom!("hairline"), get_hairline),
260 make_variable!(atom!("-moz-submit-string"), get_submit_string),
261];
262
263impl CssEnvironment {
264 #[inline]
266 pub fn get(
267 &self,
268 name: &Atom,
269 device: &Device,
270 parser_context: &ParserContext,
271 ) -> Option<VariableValue> {
272 #[cfg(feature = "gecko")]
273 let is_link_parameter = name.as_slice().starts_with(&[b'-' as u16, b'-' as u16]);
274 #[cfg(feature = "servo")]
275 let is_link_parameter = name.starts_with("--");
276 if is_link_parameter {
277 let param = device
278 .link_parameters()?
279 .0
280 .iter()
281 .find(|p| p.name.0 == *name)?;
282 let mut parser = cssparser::Parser::new(param.value.0.as_ref());
283 return VariableValue::parse(parser_context, &mut parser).ok();
285 }
286
287 if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
288 return Some((var.evaluator)(device, parser_context.url_data));
289 }
290 if !parser_context.chrome_rules_enabled() {
291 return None;
292 }
293 let var = CHROME_ENVIRONMENT_VARIABLES
294 .iter()
295 .find(|var| var.name == *name)?;
296 Some((var.evaluator)(device, parser_context.url_data))
297 }
298}