Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5
//! Support for [custom properties for cascading variables][custom].
6
//!
8
9
use crate::hash::map::Entry;
10
use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue};
11
use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher};
12
use crate::stylesheets::{Origin, PerOrigin};
13
use crate::Atom;
14
use cssparser::{Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType};
15
use indexmap::IndexMap;
16
use selectors::parser::SelectorParseErrorKind;
17
use servo_arc::Arc;
18
use smallvec::SmallVec;
19
use std::borrow::Cow;
20
use std::cmp;
21
use std::fmt::{self, Write};
22
use std::hash::BuildHasherDefault;
23
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
24
25
/// The environment from which to get `env` function values.
26
///
27
/// TODO(emilio): If this becomes a bit more complex we should probably move it
28
/// to the `media_queries` module, or something.
29
#[derive(Debug, MallocSizeOf)]
30
pub struct CssEnvironment;
31
32
struct EnvironmentVariable {
33
name: Atom,
34
value: VariableValue,
35
}
36
37
macro_rules! make_variable {
38
($name:expr, $value:expr) => {{
39
EnvironmentVariable {
40
name: $name,
41
value: {
42
// TODO(emilio): We could make this be more efficient (though a
43
// bit less convenient).
44
let mut input = ParserInput::new($value);
45
let mut input = Parser::new(&mut input);
46
47
let (first_token_type, css, last_token_type) =
48
parse_self_contained_declaration_value(&mut input, None).unwrap();
49
50
VariableValue {
51
css: css.into_owned(),
52
first_token_type,
53
last_token_type,
54
references: Default::default(),
55
references_environment: false,
56
}
57
},
58
}
59
}};
60
}
61
62
lazy_static! {
63
static ref ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
64
make_variable!(atom!("safe-area-inset-top"), "0px"),
65
make_variable!(atom!("safe-area-inset-bottom"), "0px"),
66
make_variable!(atom!("safe-area-inset-left"), "0px"),
67
make_variable!(atom!("safe-area-inset-right"), "0px"),
68
];
69
}
70
71
impl CssEnvironment {
72
#[inline]
73
fn get(&self, name: &Atom) -> Option<&VariableValue> {
74
let var = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?;
75
Some(&var.value)
76
}
77
}
78
79
/// A custom property name is just an `Atom`.
80
///
81
/// Note that this does not include the `--` prefix
82
pub type Name = Atom;
83
84
/// Parse a custom property name.
85
///
87
pub fn parse_name(s: &str) -> Result<&str, ()> {
88
if s.starts_with("--") {
89
Ok(&s[2..])
90
} else {
91
Err(())
92
}
93
}
94
95
/// A value for a custom property is just a set of tokens.
96
///
97
/// We preserve the original CSS for serialization, and also the variable
98
/// references to other custom property names.
99
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
100
pub struct VariableValue {
101
css: String,
102
103
first_token_type: TokenSerializationType,
104
last_token_type: TokenSerializationType,
105
106
/// Whether a variable value has a reference to an environment variable.
107
///
108
/// If this is the case, we need to perform variable substitution on the
109
/// value.
110
references_environment: bool,
111
112
/// Custom property names in var() functions.
113
references: Box<[Name]>,
114
}
115
116
impl ToCss for SpecifiedValue {
117
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
118
where
119
W: Write,
120
{
121
dest.write_str(&self.css)
122
}
123
}
124
125
/// A map from CSS variable names to CSS variable computed values, used for
126
/// resolving.
127
///
128
/// A consistent ordering is required for CSSDeclaration objects in the
129
/// DOM. CSSDeclarations expose property names as indexed properties, which
130
/// need to be stable. So we keep an array of property names which order is
131
/// determined on the order that they are added to the name-value map.
132
///
133
/// The variable values are guaranteed to not have references to other
134
/// properties.
135
pub type CustomPropertiesMap =
136
IndexMap<Name, Arc<VariableValue>, BuildHasherDefault<PrecomputedHasher>>;
137
138
/// Both specified and computed values are VariableValues, the difference is
139
/// whether var() functions are expanded.
140
pub type SpecifiedValue = VariableValue;
141
/// Both specified and computed values are VariableValues, the difference is
142
/// whether var() functions are expanded.
143
pub type ComputedValue = VariableValue;
144
145
/// A struct holding information about the external references to that a custom
146
/// property value may have.
147
#[derive(Default)]
148
struct VarOrEnvReferences {
149
custom_property_references: PrecomputedHashSet<Name>,
150
references_environment: bool,
151
}
152
153
impl VariableValue {
154
fn empty() -> Self {
155
Self {
156
css: String::new(),
157
last_token_type: TokenSerializationType::nothing(),
158
first_token_type: TokenSerializationType::nothing(),
159
references: Default::default(),
160
references_environment: false,
161
}
162
}
163
164
fn push<'i>(
165
&mut self,
166
input: &Parser<'i, '_>,
167
css: &str,
168
css_first_token_type: TokenSerializationType,
169
css_last_token_type: TokenSerializationType,
170
) -> Result<(), ParseError<'i>> {
171
/// Prevent values from getting terribly big since you can use custom
172
/// properties exponentially.
173
///
174
/// This number (1MB) is somewhat arbitrary, but silly enough that no
175
/// sane page would hit it. We could limit by number of total
176
/// substitutions, but that was very easy to work around in practice
177
/// (just choose a larger initial value and boom).
178
const MAX_VALUE_LENGTH_IN_BYTES: usize = 1024 * 1024;
179
180
if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES {
181
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
182
}
183
184
// This happens e.g. between two subsequent var() functions:
185
// `var(--a)var(--b)`.
186
//
187
// In that case, css_*_token_type is nonsensical.
188
if css.is_empty() {
189
return Ok(());
190
}
191
192
self.first_token_type.set_if_nothing(css_first_token_type);
193
// If self.first_token_type was nothing,
194
// self.last_token_type is also nothing and this will be false:
195
if self
196
.last_token_type
197
.needs_separator_when_before(css_first_token_type)
198
{
199
self.css.push_str("/**/")
200
}
201
self.css.push_str(css);
202
self.last_token_type = css_last_token_type;
203
Ok(())
204
}
205
206
fn push_from<'i>(
207
&mut self,
208
input: &Parser<'i, '_>,
209
position: (SourcePosition, TokenSerializationType),
210
last_token_type: TokenSerializationType,
211
) -> Result<(), ParseError<'i>> {
212
self.push(
213
input,
214
input.slice_from(position.0),
215
position.1,
216
last_token_type,
217
)
218
}
219
220
fn push_variable<'i>(
221
&mut self,
222
input: &Parser<'i, '_>,
223
variable: &ComputedValue,
224
) -> Result<(), ParseError<'i>> {
225
debug_assert!(variable.references.is_empty());
226
self.push(
227
input,
228
&variable.css,
229
variable.first_token_type,
230
variable.last_token_type,
231
)
232
}
233
234
/// Parse a custom property value.
235
pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Arc<Self>, ParseError<'i>> {
236
let mut references = VarOrEnvReferences::default();
237
238
let (first_token_type, css, last_token_type) =
239
parse_self_contained_declaration_value(input, Some(&mut references))?;
240
241
let custom_property_references = references
242
.custom_property_references
243
.into_iter()
244
.collect::<Vec<_>>()
245
.into_boxed_slice();
246
247
Ok(Arc::new(VariableValue {
248
css: css.into_owned(),
249
first_token_type,
250
last_token_type,
251
references: custom_property_references,
252
references_environment: references.references_environment,
253
}))
254
}
255
}
256
257
/// Parse the value of a non-custom property that contains `var()` references.
258
pub fn parse_non_custom_with_var<'i, 't>(
259
input: &mut Parser<'i, 't>,
260
) -> Result<(TokenSerializationType, Cow<'i, str>), ParseError<'i>> {
261
let (first_token_type, css, _) = parse_self_contained_declaration_value(input, None)?;
262
Ok((first_token_type, css))
263
}
264
265
fn parse_self_contained_declaration_value<'i, 't>(
266
input: &mut Parser<'i, 't>,
267
references: Option<&mut VarOrEnvReferences>,
268
) -> Result<(TokenSerializationType, Cow<'i, str>, TokenSerializationType), ParseError<'i>> {
269
let start_position = input.position();
270
let mut missing_closing_characters = String::new();
271
let (first, last) =
272
parse_declaration_value(input, references, &mut missing_closing_characters)?;
273
let mut css: Cow<str> = input.slice_from(start_position).into();
274
if !missing_closing_characters.is_empty() {
275
// Unescaped backslash at EOF in a quoted string is ignored.
276
if css.ends_with("\\") && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') {
277
css.to_mut().pop();
278
}
279
css.to_mut().push_str(&missing_closing_characters);
280
}
281
Ok((first, css, last))
282
}
283
285
fn parse_declaration_value<'i, 't>(
286
input: &mut Parser<'i, 't>,
287
references: Option<&mut VarOrEnvReferences>,
288
missing_closing_characters: &mut String,
289
) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
290
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
291
// Need at least one token
292
let start = input.state();
293
input.next_including_whitespace()?;
294
input.reset(&start);
295
296
parse_declaration_value_block(input, references, missing_closing_characters)
297
})
298
}
299
300
/// Like parse_declaration_value, but accept `!` and `;` since they are only
301
/// invalid at the top level
302
fn parse_declaration_value_block<'i, 't>(
303
input: &mut Parser<'i, 't>,
304
mut references: Option<&mut VarOrEnvReferences>,
305
missing_closing_characters: &mut String,
306
) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> {
307
let mut token_start = input.position();
308
let mut token = match input.next_including_whitespace_and_comments() {
309
// FIXME: remove clone() when borrows are non-lexical
310
Ok(token) => token.clone(),
311
Err(_) => {
312
return Ok((
313
TokenSerializationType::nothing(),
314
TokenSerializationType::nothing(),
315
));
316
},
317
};
318
let first_token_type = token.serialization_type();
319
loop {
320
macro_rules! nested {
321
() => {
322
input.parse_nested_block(|input| {
323
parse_declaration_value_block(
324
input,
325
references.as_mut().map(|r| &mut **r),
326
missing_closing_characters,
327
)
328
})?
329
};
330
}
331
macro_rules! check_closed {
332
($closing:expr) => {
333
if !input.slice_from(token_start).ends_with($closing) {
334
missing_closing_characters.push_str($closing)
335
}
336
};
337
}
338
let last_token_type = match token {
339
Token::Comment(_) => {
340
let token_slice = input.slice_from(token_start);
341
if !token_slice.ends_with("*/") {
342
missing_closing_characters.push_str(if token_slice.ends_with('*') {
343
"/"
344
} else {
345
"*/"
346
})
347
}
348
token.serialization_type()
349
},
350
Token::BadUrl(u) => {
351
let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u);
352
return Err(input.new_custom_error(e));
353
},
354
Token::BadString(s) => {
355
let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s);
356
return Err(input.new_custom_error(e));
357
},
358
Token::CloseParenthesis => {
359
let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock;
360
return Err(input.new_custom_error(e));
361
},
362
Token::CloseSquareBracket => {
363
let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock;
364
return Err(input.new_custom_error(e));
365
},
366
Token::CloseCurlyBracket => {
367
let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock;
368
return Err(input.new_custom_error(e));
369
},
370
Token::Function(ref name) => {
371
if name.eq_ignore_ascii_case("var") {
372
let args_start = input.state();
373
input.parse_nested_block(|input| {
374
parse_var_function(input, references.as_mut().map(|r| &mut **r))
375
})?;
376
input.reset(&args_start);
377
} else if name.eq_ignore_ascii_case("env") {
378
let args_start = input.state();
379
input.parse_nested_block(|input| {
380
parse_env_function(input, references.as_mut().map(|r| &mut **r))
381
})?;
382
input.reset(&args_start);
383
}
384
nested!();
385
check_closed!(")");
386
Token::CloseParenthesis.serialization_type()
387
},
388
Token::ParenthesisBlock => {
389
nested!();
390
check_closed!(")");
391
Token::CloseParenthesis.serialization_type()
392
},
393
Token::CurlyBracketBlock => {
394
nested!();
395
check_closed!("}");
396
Token::CloseCurlyBracket.serialization_type()
397
},
398
Token::SquareBracketBlock => {
399
nested!();
400
check_closed!("]");
401
Token::CloseSquareBracket.serialization_type()
402
},
403
Token::QuotedString(_) => {
404
let token_slice = input.slice_from(token_start);
405
let quote = &token_slice[..1];
406
debug_assert!(matches!(quote, "\"" | "'"));
407
if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
408
missing_closing_characters.push_str(quote)
409
}
410
token.serialization_type()
411
},
412
Token::Ident(ref value) |
413
Token::AtKeyword(ref value) |
414
Token::Hash(ref value) |
415
Token::IDHash(ref value) |
416
Token::UnquotedUrl(ref value) |
417
Token::Dimension {
418
unit: ref value, ..
419
} => {
420
if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
421
// Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
422
// Check the value in case the final backslash was itself escaped.
423
// Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
424
// (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
425
missing_closing_characters.push_str("�")
426
}
427
if matches!(token, Token::UnquotedUrl(_)) {
428
check_closed!(")");
429
}
430
token.serialization_type()
431
},
432
_ => token.serialization_type(),
433
};
434
435
token_start = input.position();
436
token = match input.next_including_whitespace_and_comments() {
437
// FIXME: remove clone() when borrows are non-lexical
438
Ok(token) => token.clone(),
439
Err(..) => return Ok((first_token_type, last_token_type)),
440
};
441
}
442
}
443
444
fn parse_fallback<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> {
445
// Exclude `!` and `;` at the top level
447
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
448
// At least one non-comment token.
449
input.next_including_whitespace()?;
450
// Skip until the end.
451
while let Ok(_) = input.next_including_whitespace_and_comments() {}
452
Ok(())
453
})
454
}
455
456
// If the var function is valid, return Ok((custom_property_name, fallback))
457
fn parse_var_function<'i, 't>(
458
input: &mut Parser<'i, 't>,
459
references: Option<&mut VarOrEnvReferences>,
460
) -> Result<(), ParseError<'i>> {
461
let name = input.expect_ident_cloned()?;
462
let name = parse_name(&name).map_err(|()| {
463
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))
464
})?;
465
if input.try(|input| input.expect_comma()).is_ok() {
466
parse_fallback(input)?;
467
}
468
if let Some(refs) = references {
469
refs.custom_property_references.insert(Atom::from(name));
470
}
471
Ok(())
472
}
473
474
fn parse_env_function<'i, 't>(
475
input: &mut Parser<'i, 't>,
476
references: Option<&mut VarOrEnvReferences>,
477
) -> Result<(), ParseError<'i>> {
478
// TODO(emilio): This should be <custom-ident> per spec, but no other
479
// browser does that, see https://github.com/w3c/csswg-drafts/issues/3262.
480
input.expect_ident()?;
481
if input.try(|input| input.expect_comma()).is_ok() {
482
parse_fallback(input)?;
483
}
484
if let Some(references) = references {
485
references.references_environment = true;
486
}
487
Ok(())
488
}
489
490
/// A struct that takes care of encapsulating the cascade process for custom
491
/// properties.
492
pub struct CustomPropertiesBuilder<'a> {
493
seen: PrecomputedHashSet<&'a Name>,
494
reverted: PerOrigin<PrecomputedHashSet<&'a Name>>,
495
may_have_cycles: bool,
496
custom_properties: Option<CustomPropertiesMap>,
497
inherited: Option<&'a Arc<CustomPropertiesMap>>,
498
environment: &'a CssEnvironment,
499
}
500
501
impl<'a> CustomPropertiesBuilder<'a> {
502
/// Create a new builder, inheriting from a given custom properties map.
503
pub fn new(
504
inherited: Option<&'a Arc<CustomPropertiesMap>>,
505
environment: &'a CssEnvironment,
506
) -> Self {
507
Self {
508
seen: PrecomputedHashSet::default(),
509
reverted: Default::default(),
510
may_have_cycles: false,
511
custom_properties: None,
512
inherited,
513
environment,
514
}
515
}
516
517
/// Cascade a given custom property declaration.
518
pub fn cascade(&mut self, declaration: &'a CustomDeclaration, origin: Origin) {
519
let CustomDeclaration {
520
ref name,
521
ref value,
522
} = *declaration;
523
524
if self.reverted.borrow_for_origin(&origin).contains(&name) {
525
return;
526
}
527
528
let was_already_present = !self.seen.insert(name);
529
if was_already_present {
530
return;
531
}
532
533
if !self.value_may_affect_style(name, value) {
534
return;
535
}
536
537
if self.custom_properties.is_none() {
538
self.custom_properties = Some(match self.inherited {
539
Some(inherited) => (**inherited).clone(),
540
None => CustomPropertiesMap::default(),
541
});
542
}
543
544
let map = self.custom_properties.as_mut().unwrap();
545
match *value {
546
CustomDeclarationValue::Value(ref unparsed_value) => {
547
let has_references = !unparsed_value.references.is_empty();
548
self.may_have_cycles |= has_references;
549
550
// If the variable value has no references and it has an
551
// environment variable here, perform substitution here instead
552
// of forcing a full traversal in `substitute_all` afterwards.
553
let value = if !has_references && unparsed_value.references_environment {
554
let result =
555
substitute_references_in_value(unparsed_value, &map, &self.environment);
556
match result {
557
Ok(new_value) => Arc::new(new_value),
558
Err(..) => {
559
map.remove(name);
560
return;
561
},
562
}
563
} else {
564
(*unparsed_value).clone()
565
};
566
map.insert(name.clone(), value);
567
},
568
CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
569
CSSWideKeyword::Revert => {
570
self.seen.remove(name);
571
for origin in origin.following_including() {
572
self.reverted.borrow_mut_for_origin(&origin).insert(name);
573
}
574
},
575
CSSWideKeyword::Initial => {
576
map.remove(name);
577
},
578
// handled in value_may_affect_style
579
CSSWideKeyword::Unset | CSSWideKeyword::Inherit => unreachable!(),
580
},
581
}
582
}
583
584
fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool {
585
match *value {
586
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) |
587
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
588
// Custom properties are inherited by default. So
589
// explicit 'inherit' or 'unset' means we can just use
590
// any existing value in the inherited CustomPropertiesMap.
591
return false;
592
},
593
_ => {},
594
}
595
596
let existing_value = self
597
.custom_properties
598
.as_ref()
599
.and_then(|m| m.get(name))
600
.or_else(|| self.inherited.and_then(|m| m.get(name)));
601
602
match (existing_value, value) {
603
(None, &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) => {
604
// The initial value of a custom property is the same as it
605
// not existing in the map.
606
return false;
607
},
608
(Some(existing_value), &CustomDeclarationValue::Value(ref value)) => {
609
// Don't bother overwriting an existing inherited value with
610
// the same specified value.
611
if existing_value == value {
612
return false;
613
}
614
},
615
_ => {},
616
}
617
618
true
619
}
620
621
/// Returns the final map of applicable custom properties.
622
///
623
/// If there was any specified property, we've created a new map and now we
624
/// need to remove any potential cycles, and wrap it in an arc.
625
///
626
/// Otherwise, just use the inherited custom properties map.
627
pub fn build(mut self) -> Option<Arc<CustomPropertiesMap>> {
628
let mut map = match self.custom_properties.take() {
629
Some(m) => m,
630
None => return self.inherited.cloned(),
631
};
632
if self.may_have_cycles {
633
substitute_all(&mut map, self.environment);
634
}
635
Some(Arc::new(map))
636
}
637
}
638
639
/// Resolve all custom properties to either substituted or invalid.
640
///
641
/// It does cycle dependencies removal at the same time as substitution.
642
fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: &CssEnvironment) {
643
// The cycle dependencies removal in this function is a variant
644
// of Tarjan's algorithm. It is mostly based on the pseudo-code
645
// listed in
647
// title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495
648
649
/// Struct recording necessary information for each variable.
650
#[derive(Debug)]
651
struct VarInfo {
652
/// The name of the variable. It will be taken to save addref
653
/// when the corresponding variable is popped from the stack.
654
/// This also serves as a mark for whether the variable is
655
/// currently in the stack below.
656
name: Option<Name>,
657
/// If the variable is in a dependency cycle, lowlink represents
658
/// a smaller index which corresponds to a variable in the same
659
/// strong connected component, which is known to be accessible
660
/// from this variable. It is not necessarily the root, though.
661
lowlink: usize,
662
}
663
/// Context struct for traversing the variable graph, so that we can
664
/// avoid referencing all the fields multiple times.
665
#[derive(Debug)]
666
struct Context<'a> {
667
/// Number of variables visited. This is used as the order index
668
/// when we visit a new unresolved variable.
669
count: usize,
670
/// The map from custom property name to its order index.
671
index_map: PrecomputedHashMap<Name, usize>,
672
/// Information of each variable indexed by the order index.
673
var_info: SmallVec<[VarInfo; 5]>,
674
/// The stack of order index of visited variables. It contains
675
/// all unfinished strong connected components.
676
stack: SmallVec<[usize; 5]>,
677
map: &'a mut CustomPropertiesMap,
678
/// The environment to substitute `env()` variables.
679
environment: &'a CssEnvironment,
680
}
681
682
/// This function combines the traversal for cycle removal and value
683
/// substitution. It returns either a signal None if this variable
684
/// has been fully resolved (to either having no reference or being
685
/// marked invalid), or the order index for the given name.
686
///
687
/// When it returns, the variable corresponds to the name would be
688
/// in one of the following states:
689
/// * It is still in context.stack, which means it is part of an
690
/// potentially incomplete dependency circle.
691
/// * It has been removed from the map. It can be either that the
692
/// substitution failed, or it is inside a dependency circle.
693
/// When this function removes a variable from the map because
694
/// of dependency circle, it would put all variables in the same
695
/// strong connected component to the set together.
696
/// * It doesn't have any reference, because either this variable
697
/// doesn't have reference at all in specified value, or it has
698
/// been completely resolved.
699
/// * There is no such variable at all.
700
fn traverse<'a>(name: Name, context: &mut Context<'a>) -> Option<usize> {
701
// Some shortcut checks.
702
let (name, value) = {
703
let value = context.map.get(&name)?;
704
705
// Nothing to resolve.
706
if value.references.is_empty() {
707
debug_assert!(
708
!value.references_environment,
709
"Should've been handled earlier"
710
);
711
return None;
712
}
713
714
// Whether this variable has been visited in this traversal.
715
let key;
716
match context.index_map.entry(name) {
717
Entry::Occupied(entry) => {
718
return Some(*entry.get());
719
},
720
Entry::Vacant(entry) => {
721
key = entry.key().clone();
722
entry.insert(context.count);
723
},
724
}
725
726
// Hold a strong reference to the value so that we don't
727
// need to keep reference to context.map.
728
(key, value.clone())
729
};
730
731
// Add new entry to the information table.
732
let index = context.count;
733
context.count += 1;
734
debug_assert_eq!(index, context.var_info.len());
735
context.var_info.push(VarInfo {
736
name: Some(name),
737
lowlink: index,
738
});
739
context.stack.push(index);
740
741
let mut self_ref = false;
742
let mut lowlink = index;
743
for next in value.references.iter() {
744
let next_index = match traverse(next.clone(), context) {
745
Some(index) => index,
746
// There is nothing to do if the next variable has been
747
// fully resolved at this point.
748
None => {
749
continue;
750
},
751
};
752
let next_info = &context.var_info[next_index];
753
if next_index > index {
754
// The next variable has a larger index than us, so it
755
// must be inserted in the recursive call above. We want
756
// to get its lowlink.
757
lowlink = cmp::min(lowlink, next_info.lowlink);
758
} else if next_index == index {
759
self_ref = true;
760
} else if next_info.name.is_some() {
761
// The next variable has a smaller order index and it is
762
// in the stack, so we are at the same component.
763
lowlink = cmp::min(lowlink, next_index);
764
}
765
}
766
767
context.var_info[index].lowlink = lowlink;
768
if lowlink != index {
769
// This variable is in a loop, but it is not the root of
770
// this strong connected component. We simply return for
771
// now, and the root would remove it from the map.
772
//
773
// This cannot be removed from the map here, because
774
// otherwise the shortcut check at the beginning of this
775
// function would return the wrong value.
776
return Some(index);
777
}
778
779
// This is the root of a strong-connected component.
780
let mut in_loop = self_ref;
781
let name;
782
loop {
783
let var_index = context
784
.stack
785
.pop()
786
.expect("The current variable should still be in stack");
787
let var_info = &mut context.var_info[var_index];
788
// We should never visit the variable again, so it's safe
789
// to take the name away, so that we don't do additional
790
// reference count.
791
let var_name = var_info
792
.name
793
.take()
794
.expect("Variable should not be poped from stack twice");
795
if var_index == index {
796
name = var_name;
797
break;
798
}
799
// Anything here is in a loop which can traverse to the
800
// variable we are handling, so remove it from the map, it's invalid
801
// at computed-value time.
802
context.map.remove(&var_name);
803
in_loop = true;
804
}
805
if in_loop {
806
// This variable is in loop. Resolve to invalid.
807
context.map.remove(&name);
808
return None;
809
}
810
811
// Now we have shown that this variable is not in a loop, and
812
// all of its dependencies should have been resolved. We can
813
// start substitution now.
814
let result = substitute_references_in_value(&value, &context.map, &context.environment);
815
816
match result {
817
Ok(computed_value) => {
818
context.map.insert(name, Arc::new(computed_value));
819
},
820
Err(..) => {
821
context.map.remove(&name);
822
},
823
}
824
825
// All resolved, so return the signal value.
826
None
827
}
828
829
// We have to clone the names so that we can mutably borrow the map
830
// in the context we create for traversal.
831
let names: Vec<_> = custom_properties_map.keys().cloned().collect();
832
for name in names.into_iter() {
833
let mut context = Context {
834
count: 0,
835
index_map: PrecomputedHashMap::default(),
836
stack: SmallVec::new(),
837
var_info: SmallVec::new(),
838
map: custom_properties_map,
839
environment,
840
};
841
traverse(name, &mut context);
842
}
843
}
844
845
/// Replace `var()` and `env()` functions in a pre-existing variable value.
846
fn substitute_references_in_value<'i>(
847
value: &'i VariableValue,
848
custom_properties: &CustomPropertiesMap,
849
environment: &CssEnvironment,
850
) -> Result<ComputedValue, ParseError<'i>> {
851
debug_assert!(!value.references.is_empty() || value.references_environment);
852
853
let mut input = ParserInput::new(&value.css);
854
let mut input = Parser::new(&mut input);
855
let mut position = (input.position(), value.first_token_type);
856
let mut computed_value = ComputedValue::empty();
857
858
let last_token_type = substitute_block(
859
&mut input,
860
&mut position,
861
&mut computed_value,
862
custom_properties,
863
environment,
864
)?;
865
866
computed_value.push_from(&input, position, last_token_type)?;
867
Ok(computed_value)
868
}
869
870
/// Replace `var()` functions in an arbitrary bit of input.
871
///
872
/// If the variable has its initial value, the callback should return `Err(())`
873
/// and leave `partial_computed_value` unchanged.
874
///
875
/// Otherwise, it should push the value of the variable (with its own `var()` functions replaced)
876
/// to `partial_computed_value` and return `Ok(last_token_type of what was pushed)`
877
///
878
/// Return `Err(())` if `input` is invalid at computed-value time.
879
/// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise.
880
fn substitute_block<'i>(
881
input: &mut Parser<'i, '_>,
882
position: &mut (SourcePosition, TokenSerializationType),
883
partial_computed_value: &mut ComputedValue,
884
custom_properties: &CustomPropertiesMap,
885
env: &CssEnvironment,
886
) -> Result<TokenSerializationType, ParseError<'i>> {
887
let mut last_token_type = TokenSerializationType::nothing();
888
let mut set_position_at_next_iteration = false;
889
loop {
890
let before_this_token = input.position();
891
// FIXME: remove clone() when borrows are non-lexical
892
let next = input
893
.next_including_whitespace_and_comments()
894
.map(|t| t.clone());
895
if set_position_at_next_iteration {
896
*position = (
897
before_this_token,
898
match next {
899
Ok(ref token) => token.serialization_type(),
900
Err(_) => TokenSerializationType::nothing(),
901
},
902
);
903
set_position_at_next_iteration = false;
904
}
905
let token = match next {
906
Ok(token) => token,
907
Err(..) => break,
908
};
909
match token {
910
Token::Function(ref name)
911
if name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env") =>
912
{
913
let is_env = name.eq_ignore_ascii_case("env");
914
915
partial_computed_value.push(
916
input,
917
input.slice(position.0..before_this_token),
918
position.1,
919
last_token_type,
920
)?;
921
input.parse_nested_block(|input| {
922
// parse_var_function() / parse_env_function() ensure neither .unwrap() will fail.
923
let name = {
924
let name = input.expect_ident().unwrap();
925
if is_env {
926
Atom::from(&**name)
927
} else {
928
Atom::from(parse_name(&name).unwrap())
929
}
930
};
931
932
let value = if is_env {
933
env.get(&name)
934
} else {
935
custom_properties.get(&name).map(|v| &**v)
936
};
937
938
if let Some(v) = value {
939
last_token_type = v.last_token_type;
940
partial_computed_value.push_variable(input, v)?;
941
// Skip over the fallback, as `parse_nested_block` would return `Err`
942
// if we don't consume all of `input`.
943
// FIXME: Add a specialized method to cssparser to do this with less work.
944
while input.next().is_ok() {}
945
} else {
946
input.expect_comma()?;
947
let after_comma = input.state();
948
let first_token_type = input
949
.next_including_whitespace_and_comments()
950
// parse_var_function() ensures that .unwrap() will not fail.
951
.unwrap()
952
.serialization_type();
953
input.reset(&after_comma);
954
let mut position = (after_comma.position(), first_token_type);
955
last_token_type = substitute_block(
956
input,
957
&mut position,
958
partial_computed_value,
959
custom_properties,
960
env,
961
)?;
962
partial_computed_value.push_from(input, position, last_token_type)?;
963
}
964
Ok(())
965
})?;
966
set_position_at_next_iteration = true
967
}
968
Token::Function(_) |
969
Token::ParenthesisBlock |
970
Token::CurlyBracketBlock |
971
Token::SquareBracketBlock => {
972
input.parse_nested_block(|input| {
973
substitute_block(
974
input,
975
position,
976
partial_computed_value,
977
custom_properties,
978
env,
979
)
980
})?;
981
// It's the same type for CloseCurlyBracket and CloseSquareBracket.
982
last_token_type = Token::CloseParenthesis.serialization_type();
983
},
984
985
_ => last_token_type = token.serialization_type(),
986
}
987
}
988
// FIXME: deal with things being implicitly closed at the end of the input. E.g.
989
// ```html
990
// <div style="--color: rgb(0,0,0">
991
// <p style="background: var(--color) var(--image) top left; --image: url('a.png"></p>
992
// </div>
993
// ```
994
Ok(last_token_type)
995
}
996
997
/// Replace `var()` and `env()` functions for a non-custom property.
998
///
999
/// Return `Err(())` for invalid at computed time.
1000
pub fn substitute<'i>(
1001
input: &'i str,
1002
first_token_type: TokenSerializationType,
1003
computed_values_map: Option<&Arc<CustomPropertiesMap>>,
1004
env: &CssEnvironment,
1005
) -> Result<String, ParseError<'i>> {
1006
let mut substituted = ComputedValue::empty();
1007
let mut input = ParserInput::new(input);
1008
let mut input = Parser::new(&mut input);
1009
let mut position = (input.position(), first_token_type);
1010
let empty_map = CustomPropertiesMap::default();
1011
let custom_properties = match computed_values_map {
1012
Some(m) => &**m,
1013
None => &empty_map,
1014
};
1015
let last_token_type = substitute_block(
1016
&mut input,
1017
&mut position,
1018
&mut substituted,
1019
&custom_properties,
1020
env,
1021
)?;
1022
substituted.push_from(&input, position, last_token_type)?;
1023
Ok(substituted.css)
1024
}