diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index fc28e8a5994..5e8d6544073 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -12,7 +12,7 @@ pub struct Value { /// In CSS syntax pub value: String, - /// Custom property names in var() functions + /// Custom property names in var() functions. Do not include the `--` prefix. pub references: HashSet, } @@ -109,6 +109,8 @@ fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>, references: &mut HashS Ok(()) } +/// Add one custom property declaration to a map, +/// unless another with the same name was already there. pub fn cascade(custom_properties: &mut Option, inherited_custom_properties: &Option>, name: &Atom, value: &Value) { let map = match *custom_properties { @@ -123,3 +125,54 @@ pub fn cascade(custom_properties: &mut Option, inherited_custom_properties: }; map.entry(name.clone()).or_insert(value.clone()); } + +/// If any custom property declarations where found for this element (`custom_properties.is_some()`) +/// remove cycles and move the map into an `Arc`. +/// Otherwise, default to the inherited map. +pub fn finish_cascade(custom_properties: Option, + inherited_custom_properties: &Option>) + -> Option> { + if let Some(mut map) = custom_properties { + remove_cycles(&mut map); + Some(Arc::new(map)) + } else { + inherited_custom_properties.clone() + } +} + +/// https://drafts.csswg.org/css-variables/#cycles +fn remove_cycles(map: &mut Map) { + let mut to_remove = HashSet::new(); + { + let mut visited = HashSet::new(); + let mut stack = Vec::new(); + for name in map.keys() { + walk(map, name, &mut stack, &mut visited, &mut to_remove); + + fn walk<'a>(map: &'a Map, name: &'a Atom, stack: &mut Vec<&'a Atom>, + visited: &mut HashSet<&'a Atom>, to_remove: &mut HashSet) { + let was_not_already_present = visited.insert(name); + if !was_not_already_present { + return + } + if let Some(value) = map.get(name) { + stack.push(name); + for next in &value.references { + if let Some(position) = stack.position_elem(&next) { + // Found a cycle + for in_cycle in &stack[position..] { + to_remove.insert((**in_cycle).clone()); + } + } else { + walk(map, next, stack, visited, to_remove); + } + } + stack.pop(); + } + } + } + } + for name in &to_remove { + map.remove(name); + } +} diff --git a/components/style/lib.rs b/components/style/lib.rs index 1d7622ce088..0822c67dd91 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -9,6 +9,7 @@ #![feature(custom_attribute)] #![feature(custom_derive)] #![feature(plugin)] +#![feature(slice_position_elem)] #![feature(vec_push_all)] #![plugin(serde_macros)] diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index f0ddef0c503..38dd602a75e 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -6174,8 +6174,8 @@ fn cascade_with_cached_declarations( % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: style_${style_struct.ident}, % endfor - custom_properties: custom_properties - .map(Arc::new).or_else(|| parent_style.custom_properties.clone()), + custom_properties: ::custom_properties::finish_cascade( + custom_properties, &parent_style.custom_properties), shareable: shareable, root_font_size: parent_style.root_font_size, } @@ -6451,8 +6451,8 @@ pub fn cascade(viewport_size: Size2D, % for style_struct in STYLE_STRUCTS: ${style_struct.ident}: style.${style_struct.ident}, % endfor - custom_properties: custom_properties - .map(Arc::new).or_else(|| inherited_style.custom_properties.clone()), + custom_properties: ::custom_properties::finish_cascade( + custom_properties, &inherited_style.custom_properties), shareable: shareable, root_font_size: context.root_font_size, }, cacheable)