diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /servo/components/style/properties | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style/properties')
51 files changed, 20961 insertions, 0 deletions
diff --git a/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl b/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl Binary files differnew file mode 100644 index 0000000000..9593025a47 --- /dev/null +++ b/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl diff --git a/servo/components/style/properties/build.py b/servo/components/style/properties/build.py new file mode 100644 index 0000000000..6c3ee0cf66 --- /dev/null +++ b/servo/components/style/properties/build.py @@ -0,0 +1,176 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +import json +import os.path +import re +import sys + +BASE = os.path.dirname(__file__.replace("\\", "/")) +sys.path.insert(0, os.path.join(BASE, "Mako-1.1.2-py2.py3-none-any.whl")) +sys.path.insert(0, BASE) # For importing `data.py` + +from mako import exceptions +from mako.lookup import TemplateLookup +from mako.template import Template + +import data + +RE_PYTHON_ADDR = re.compile(r"<.+? object at 0x[0-9a-fA-F]+>") + +OUT_DIR = os.environ.get("OUT_DIR", "") + +STYLE_STRUCT_LIST = [ + "background", + "border", + "box", + "column", + "counters", + "effects", + "font", + "inherited_box", + "inherited_svg", + "inherited_table", + "inherited_text", + "inherited_ui", + "list", + "margin", + "outline", + "page", + "padding", + "position", + "svg", + "table", + "text", + "ui", + "xul", +] + + +def main(): + usage = ( + "Usage: %s [ servo-2013 | servo-2020 | gecko ] [ style-crate | geckolib <template> | html ]" + % sys.argv[0] + ) + if len(sys.argv) < 3: + abort(usage) + engine = sys.argv[1] + output = sys.argv[2] + + if engine not in ["servo-2013", "servo-2020", "gecko"] or output not in [ + "style-crate", + "geckolib", + "html", + ]: + abort(usage) + + properties = data.PropertiesData(engine=engine) + files = {} + for kind in ["longhands", "shorthands"]: + files[kind] = {} + for struct in STYLE_STRUCT_LIST: + file_name = os.path.join(BASE, kind, "{}.mako.rs".format(struct)) + if kind == "shorthands" and not os.path.exists(file_name): + files[kind][struct] = "" + continue + files[kind][struct] = render( + file_name, + engine=engine, + data=properties, + ) + properties_template = os.path.join(BASE, "properties.mako.rs") + files["properties"] = render( + properties_template, + engine=engine, + data=properties, + __file__=properties_template, + OUT_DIR=OUT_DIR, + ) + if output == "style-crate": + write(OUT_DIR, "properties.rs", files["properties"]) + for kind in ["longhands", "shorthands"]: + for struct in files[kind]: + write( + os.path.join(OUT_DIR, kind), + "{}.rs".format(struct), + files[kind][struct], + ) + + if engine == "gecko": + template = os.path.join(BASE, "gecko.mako.rs") + rust = render(template, data=properties) + write(OUT_DIR, "gecko_properties.rs", rust) + + if engine in ["servo-2013", "servo-2020"]: + if engine == "servo-2013": + pref_attr = "servo_2013_pref" + if engine == "servo-2020": + pref_attr = "servo_2020_pref" + properties_dict = { + kind: { + p.name: {"pref": getattr(p, pref_attr)} + for prop in properties_list + if prop.enabled_in_content() + for p in [prop] + prop.alias + } + for kind, properties_list in [ + ("longhands", properties.longhands), + ("shorthands", properties.shorthands), + ] + } + as_html = render( + os.path.join(BASE, "properties.html.mako"), properties=properties_dict + ) + as_json = json.dumps(properties_dict, indent=4, sort_keys=True) + doc_servo = os.path.join(BASE, "..", "..", "..", "target", "doc", "servo") + write(doc_servo, "css-properties.html", as_html) + write(doc_servo, "css-properties.json", as_json) + write(OUT_DIR, "css-properties.json", as_json) + elif output == "geckolib": + if len(sys.argv) < 4: + abort(usage) + template = sys.argv[3] + header = render(template, data=properties) + sys.stdout.write(header) + + +def abort(message): + print(message, file=sys.stderr) + sys.exit(1) + + +def render(filename, **context): + try: + lookup = TemplateLookup( + directories=[BASE], input_encoding="utf8", strict_undefined=True + ) + template = Template( + open(filename, "rb").read(), + filename=filename, + input_encoding="utf8", + lookup=lookup, + strict_undefined=True, + ) + # Uncomment to debug generated Python code: + # write("/tmp", "mako_%s.py" % os.path.basename(filename), template.code) + return template.render(**context) + except Exception: + # Uncomment to see a traceback in generated Python code: + # raise + abort(exceptions.text_error_template().render()) + + +def write(directory, filename, content): + if not os.path.exists(directory): + os.makedirs(directory) + full_path = os.path.join(directory, filename) + open(full_path, "w", encoding="utf-8").write(content) + + python_addr = RE_PYTHON_ADDR.search(content) + if python_addr: + abort('Found "{}" in {} ({})'.format(python_addr.group(0), filename, full_path)) + + +if __name__ == "__main__": + main() diff --git a/servo/components/style/properties/cascade.rs b/servo/components/style/properties/cascade.rs new file mode 100644 index 0000000000..59a8a65876 --- /dev/null +++ b/servo/components/style/properties/cascade.rs @@ -0,0 +1,1381 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! The main cascading algorithm of the style system. + +use crate::applicable_declarations::CascadePriority; +use crate::color::AbsoluteColor; +use crate::computed_value_flags::ComputedValueFlags; +use crate::custom_properties::{ + CustomPropertiesBuilder, DeferFontRelativeCustomPropertyResolution, +}; +use crate::dom::TElement; +use crate::font_metrics::FontMetricsOrientation; +use crate::logical_geometry::WritingMode; +use crate::properties::{ + property_counts, CSSWideKeyword, ComputedValues, DeclarationImportanceIterator, Importance, + LonghandId, LonghandIdSet, PrioritaryPropertyId, PropertyDeclaration, PropertyDeclarationId, + PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY, +}; +use crate::rule_cache::{RuleCache, RuleCacheConditions}; +use crate::rule_tree::{CascadeLevel, StrongRuleNode}; +use crate::selector_parser::PseudoElement; +use crate::shared_lock::StylesheetGuards; +use crate::style_adjuster::StyleAdjuster; +use crate::stylesheets::container_rule::ContainerSizeQuery; +use crate::stylesheets::{layer_rule::LayerOrder, Origin}; +use crate::stylist::Stylist; +use crate::values::specified::length::FontBaseSize; +use crate::values::{computed, specified}; +use fxhash::FxHashMap; +use servo_arc::Arc; +use smallvec::SmallVec; +use std::borrow::Cow; +use std::mem; + +/// Whether we're resolving a style with the purposes of reparenting for ::first-line. +#[derive(Copy, Clone)] +#[allow(missing_docs)] +pub enum FirstLineReparenting<'a> { + No, + Yes { + /// The style we're re-parenting for ::first-line. ::first-line only affects inherited + /// properties so we use this to avoid some work and also ensure correctness by copying the + /// reset structs from this style. + style_to_reparent: &'a ComputedValues, + }, +} + +/// Performs the CSS cascade, computing new styles for an element from its parent style. +/// +/// The arguments are: +/// +/// * `device`: Used to get the initial viewport and other external state. +/// +/// * `rule_node`: The rule node in the tree that represent the CSS rules that +/// matched. +/// +/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. +/// +/// Returns the computed values. +/// * `flags`: Various flags. +/// +pub fn cascade<E>( + stylist: &Stylist, + pseudo: Option<&PseudoElement>, + rule_node: &StrongRuleNode, + guards: &StylesheetGuards, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + first_line_reparenting: FirstLineReparenting, + visited_rules: Option<&StrongRuleNode>, + cascade_input_flags: ComputedValueFlags, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, +{ + cascade_rules( + stylist, + pseudo, + rule_node, + guards, + parent_style, + layout_parent_style, + first_line_reparenting, + CascadeMode::Unvisited { visited_rules }, + cascade_input_flags, + rule_cache, + rule_cache_conditions, + element, + ) +} + +struct DeclarationIterator<'a> { + // Global to the iteration. + guards: &'a StylesheetGuards<'a>, + restriction: Option<PropertyFlags>, + // The rule we're iterating over. + current_rule_node: Option<&'a StrongRuleNode>, + // Per rule state. + declarations: DeclarationImportanceIterator<'a>, + origin: Origin, + importance: Importance, + priority: CascadePriority, +} + +impl<'a> DeclarationIterator<'a> { + #[inline] + fn new( + rule_node: &'a StrongRuleNode, + guards: &'a StylesheetGuards, + pseudo: Option<&PseudoElement>, + ) -> Self { + let restriction = pseudo.and_then(|p| p.property_restriction()); + let mut iter = Self { + guards, + current_rule_node: Some(rule_node), + origin: Origin::UserAgent, + importance: Importance::Normal, + priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), + declarations: DeclarationImportanceIterator::default(), + restriction, + }; + iter.update_for_node(rule_node); + iter + } + + fn update_for_node(&mut self, node: &'a StrongRuleNode) { + self.priority = node.cascade_priority(); + let level = self.priority.cascade_level(); + self.origin = level.origin(); + self.importance = level.importance(); + let guard = match self.origin { + Origin::Author => self.guards.author, + Origin::User | Origin::UserAgent => self.guards.ua_or_user, + }; + self.declarations = match node.style_source() { + Some(source) => source.read(guard).declaration_importance_iter(), + None => DeclarationImportanceIterator::default(), + }; + } +} + +impl<'a> Iterator for DeclarationIterator<'a> { + type Item = (&'a PropertyDeclaration, CascadePriority); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + loop { + if let Some((decl, importance)) = self.declarations.next_back() { + if self.importance != importance { + continue; + } + + if let Some(restriction) = self.restriction { + // decl.id() is either a longhand or a custom + // property. Custom properties are always allowed, but + // longhands are only allowed if they have our + // restriction flag set. + if let PropertyDeclarationId::Longhand(id) = decl.id() { + if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { + continue; + } + } + } + + return Some((decl, self.priority)); + } + + let next_node = self.current_rule_node.take()?.parent()?; + self.current_rule_node = Some(next_node); + self.update_for_node(next_node); + } + } +} + +fn cascade_rules<E>( + stylist: &Stylist, + pseudo: Option<&PseudoElement>, + rule_node: &StrongRuleNode, + guards: &StylesheetGuards, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + first_line_reparenting: FirstLineReparenting, + cascade_mode: CascadeMode, + cascade_input_flags: ComputedValueFlags, + rule_cache: Option<&RuleCache>, + rule_cache_conditions: &mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement, +{ + apply_declarations( + stylist, + pseudo, + rule_node, + guards, + DeclarationIterator::new(rule_node, guards, pseudo), + parent_style, + layout_parent_style, + first_line_reparenting, + cascade_mode, + cascade_input_flags, + rule_cache, + rule_cache_conditions, + element, + ) +} + +/// Whether we're cascading for visited or unvisited styles. +#[derive(Clone, Copy)] +pub enum CascadeMode<'a, 'b> { + /// We're cascading for unvisited styles. + Unvisited { + /// The visited rules that should match the visited style. + visited_rules: Option<&'a StrongRuleNode>, + }, + /// We're cascading for visited styles. + Visited { + /// The cascade for our unvisited style. + unvisited_context: &'a computed::Context<'b>, + }, +} + +fn iter_declarations<'builder, 'decls: 'builder>( + iter: impl Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>, + declarations: &mut Declarations<'decls>, + mut custom_builder: Option<&mut CustomPropertiesBuilder<'builder, 'decls>>, +) { + for (declaration, priority) in iter { + if let PropertyDeclaration::Custom(ref declaration) = *declaration { + if let Some(ref mut builder) = custom_builder { + builder.cascade(declaration, priority); + } + } else { + let id = declaration.id().as_longhand().unwrap(); + declarations.note_declaration(declaration, priority, id); + if let Some(ref mut builder) = custom_builder { + if let PropertyDeclaration::WithVariables(ref v) = declaration { + builder.note_potentially_cyclic_non_custom_dependency(id, v); + } + } + } + } +} + +/// NOTE: This function expects the declaration with more priority to appear +/// first. +pub fn apply_declarations<'a, E, I>( + stylist: &'a Stylist, + pseudo: Option<&'a PseudoElement>, + rules: &StrongRuleNode, + guards: &StylesheetGuards, + iter: I, + parent_style: Option<&'a ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + first_line_reparenting: FirstLineReparenting<'a>, + cascade_mode: CascadeMode, + cascade_input_flags: ComputedValueFlags, + rule_cache: Option<&'a RuleCache>, + rule_cache_conditions: &'a mut RuleCacheConditions, + element: Option<E>, +) -> Arc<ComputedValues> +where + E: TElement + 'a, + I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>, +{ + debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); + let device = stylist.device(); + let inherited_style = parent_style.unwrap_or(device.default_computed_values()); + let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root()); + + let container_size_query = + ContainerSizeQuery::for_option_element(element, Some(inherited_style), pseudo.is_some()); + + let mut context = computed::Context::new( + // We'd really like to own the rules here to avoid refcount traffic, but + // animation's usage of `apply_declarations` make this tricky. See bug + // 1375525. + StyleBuilder::new( + device, + Some(stylist), + parent_style, + pseudo, + Some(rules.clone()), + is_root_element, + ), + stylist.quirks_mode(), + rule_cache_conditions, + container_size_query, + ); + + context.style().add_flags(cascade_input_flags); + + let using_cached_reset_properties; + let ignore_colors = !context.builder.device.use_document_colors(); + let mut cascade = Cascade::new(first_line_reparenting, ignore_colors); + let mut declarations = Default::default(); + let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); + let properties_to_apply = match cascade_mode { + CascadeMode::Visited { unvisited_context } => { + context.builder.custom_properties = unvisited_context.builder.custom_properties.clone(); + context.builder.writing_mode = unvisited_context.builder.writing_mode; + // We never insert visited styles into the cache so we don't need to try looking it up. + // It also wouldn't be super-profitable, only a handful :visited properties are + // non-inherited. + using_cached_reset_properties = false; + // TODO(bug 1859385): If we match the same rules when visited and unvisited, we could + // try to avoid gathering the declarations. That'd be: + // unvisited_context.builder.rules.as_ref() == Some(rules) + iter_declarations(iter, &mut declarations, None); + + LonghandIdSet::visited_dependent() + }, + CascadeMode::Unvisited { visited_rules } => { + let deferred_custom_properties = { + let mut builder = CustomPropertiesBuilder::new(stylist, &mut context); + iter_declarations(iter, &mut declarations, Some(&mut builder)); + // Detect cycles, remove properties participating in them, and resolve properties, except: + // * Registered custom properties that depend on font-relative properties (Resolved) + // when prioritary properties are resolved), and + // * Any property that, in turn, depend on properties like above. + builder.build(DeferFontRelativeCustomPropertyResolution::Yes) + }; + + // Resolve prioritary properties - Guaranteed to not fall into a cycle with existing custom + // properties. + cascade.apply_prioritary_properties(&mut context, &declarations, &mut shorthand_cache); + + // Resolve the deferred custom properties. + if let Some(deferred) = deferred_custom_properties { + CustomPropertiesBuilder::build_deferred(deferred, stylist, &mut context); + } + + if let Some(visited_rules) = visited_rules { + cascade.compute_visited_style_if_needed( + &mut context, + element, + parent_style, + layout_parent_style, + visited_rules, + guards, + ); + } + + using_cached_reset_properties = cascade.try_to_use_cached_reset_properties( + &mut context.builder, + rule_cache, + guards, + ); + + if using_cached_reset_properties { + LonghandIdSet::late_group_only_inherited() + } else { + LonghandIdSet::late_group() + } + }, + }; + + cascade.apply_non_prioritary_properties( + &mut context, + &declarations.longhand_declarations, + &mut shorthand_cache, + &properties_to_apply, + ); + + cascade.finished_applying_properties(&mut context.builder); + + std::mem::drop(cascade); + + context.builder.clear_modified_reset(); + + if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { + StyleAdjuster::new(&mut context.builder) + .adjust(layout_parent_style.unwrap_or(inherited_style), element); + } + + if context.builder.modified_reset() || using_cached_reset_properties { + // If we adjusted any reset structs, we can't cache this ComputedValues. + // + // Also, if we re-used existing reset structs, don't bother caching it back again. (Aside + // from being wasted effort, it will be wrong, since context.rule_cache_conditions won't be + // set appropriately if we didn't compute those reset properties.) + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + } + + context.builder.build() +} + +/// For ignored colors mode, we sometimes want to do something equivalent to +/// "revert-or-initial", where we `revert` for a given origin, but then apply a +/// given initial value if nothing in other origins did override it. +/// +/// This is a bit of a clunky way of achieving this. +type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>; + +fn tweak_when_ignoring_colors( + context: &computed::Context, + longhand_id: LonghandId, + origin: Origin, + declaration: &mut Cow<PropertyDeclaration>, + declarations_to_apply_unless_overridden: &mut DeclarationsToApplyUnlessOverriden, +) { + use crate::values::computed::ToComputedValue; + use crate::values::specified::Color; + + if !longhand_id.ignored_when_document_colors_disabled() { + return; + } + + let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent); + if is_ua_or_user_rule { + return; + } + + // Always honor colors if forced-color-adjust is set to none. + let forced = context + .builder + .get_inherited_text() + .clone_forced_color_adjust(); + if forced == computed::ForcedColorAdjust::None { + return; + } + + // Don't override background-color on ::-moz-color-swatch. It is set as an + // author style (via the style attribute), but it's pretty important for it + // to show up for obvious reasons :) + if context + .builder + .pseudo + .map_or(false, |p| p.is_color_swatch()) && + longhand_id == LonghandId::BackgroundColor + { + return; + } + + fn alpha_channel(color: &Color, context: &computed::Context) -> f32 { + // We assume here currentColor is opaque. + color + .to_computed_value(context) + .resolve_to_absolute(&AbsoluteColor::BLACK) + .alpha + } + + // A few special-cases ahead. + match **declaration { + PropertyDeclaration::BackgroundColor(ref color) => { + // We honor system colors and transparent colors unconditionally. + // + // NOTE(emilio): We honor transparent unconditionally, like we do + // for color, even though it causes issues like bug 1625036. The + // reasoning is that the conditions that trigger that (having + // mismatched widget and default backgrounds) are both uncommon, and + // broken in other applications as well, and not honoring + // transparent makes stuff uglier or break unconditionally + // (bug 1666059, bug 1755713). + if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) { + return; + } + // For background-color, we revert or initial-with-preserved-alpha + // otherwise, this is needed to preserve semi-transparent + // backgrounds. + let alpha = alpha_channel(color, context); + if alpha == 0.0 { + return; + } + let mut color = context.builder.device.default_background_color(); + color.alpha = alpha; + declarations_to_apply_unless_overridden + .push(PropertyDeclaration::BackgroundColor(color.into())) + }, + PropertyDeclaration::Color(ref color) => { + // We honor color: transparent and system colors. + if color + .0 + .honored_in_forced_colors_mode(/* allow_transparent = */ true) + { + return; + } + // If the inherited color would be transparent, but we would + // override this with a non-transparent color, then override it with + // the default color. Otherwise just let it inherit through. + if context + .builder + .get_parent_inherited_text() + .clone_color() + .alpha == + 0.0 + { + let color = context.builder.device.default_color(); + declarations_to_apply_unless_overridden.push(PropertyDeclaration::Color( + specified::ColorPropertyValue(color.into()), + )) + } + }, + // We honor url background-images if backplating. + #[cfg(feature = "gecko")] + PropertyDeclaration::BackgroundImage(ref bkg) => { + use crate::values::generics::image::Image; + if static_prefs::pref!("browser.display.permit_backplate") { + if bkg + .0 + .iter() + .all(|image| matches!(*image, Image::Url(..) | Image::None)) + { + return; + } + } + }, + _ => { + // We honor system colors more generally for all colors. + // + // We used to honor transparent but that causes accessibility + // regressions like bug 1740924. + // + // NOTE(emilio): This doesn't handle caret-color and accent-color + // because those use a slightly different syntax (<color> | auto for + // example). + // + // That's probably fine though, as using a system color for + // caret-color doesn't make sense (using currentColor is fine), and + // we ignore accent-color in high-contrast-mode anyways. + if let Some(color) = declaration.color_value() { + if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) { + return; + } + } + }, + } + + *declaration.to_mut() = + PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert); +} + +/// We track the index only for prioritary properties. For other properties we can just iterate. +type DeclarationIndex = u16; + +/// "Prioritary" properties are properties that other properties depend on in one way or another. +/// +/// We keep track of their position in the declaration vector, in order to be able to cascade them +/// separately in precise order. +#[derive(Copy, Clone)] +struct PrioritaryDeclarationPosition { + // DeclarationIndex::MAX signals no index. + most_important: DeclarationIndex, + least_important: DeclarationIndex, +} + +impl Default for PrioritaryDeclarationPosition { + fn default() -> Self { + Self { + most_important: DeclarationIndex::MAX, + least_important: DeclarationIndex::MAX, + } + } +} + +#[derive(Copy, Clone)] +struct Declaration<'a> { + decl: &'a PropertyDeclaration, + priority: CascadePriority, + next_index: DeclarationIndex, +} + +/// The set of property declarations from our rules. +#[derive(Default)] +struct Declarations<'a> { + /// Whether we have any prioritary property. This is just a minor optimization. + has_prioritary_properties: bool, + /// A list of all the applicable longhand declarations. + longhand_declarations: SmallVec<[Declaration<'a>; 32]>, + /// The prioritary property position data. + prioritary_positions: [PrioritaryDeclarationPosition; property_counts::PRIORITARY], +} + +impl<'a> Declarations<'a> { + fn note_prioritary_property(&mut self, id: PrioritaryPropertyId) { + let new_index = self.longhand_declarations.len(); + if new_index >= DeclarationIndex::MAX as usize { + // This prioritary property is past the amount of declarations we can track. Let's give + // up applying it to prevent getting confused. + return; + } + + self.has_prioritary_properties = true; + let new_index = new_index as DeclarationIndex; + let position = &mut self.prioritary_positions[id as usize]; + if position.most_important == DeclarationIndex::MAX { + // We still haven't seen this property, record the current position as the most + // prioritary index. + position.most_important = new_index; + } else { + // Let the previous item in the list know about us. + self.longhand_declarations[position.least_important as usize].next_index = new_index; + } + position.least_important = new_index; + } + + fn note_declaration( + &mut self, + decl: &'a PropertyDeclaration, + priority: CascadePriority, + id: LonghandId, + ) { + if let Some(id) = PrioritaryPropertyId::from_longhand(id) { + self.note_prioritary_property(id); + } + self.longhand_declarations.push(Declaration { + decl, + priority, + next_index: 0, + }); + } +} + +struct Cascade<'b> { + first_line_reparenting: FirstLineReparenting<'b>, + ignore_colors: bool, + seen: LonghandIdSet, + author_specified: LonghandIdSet, + reverted_set: LonghandIdSet, + reverted: FxHashMap<LonghandId, (CascadePriority, bool)>, + declarations_to_apply_unless_overridden: DeclarationsToApplyUnlessOverriden, +} + +impl<'b> Cascade<'b> { + fn new(first_line_reparenting: FirstLineReparenting<'b>, ignore_colors: bool) -> Self { + Self { + first_line_reparenting, + ignore_colors, + seen: LonghandIdSet::default(), + author_specified: LonghandIdSet::default(), + reverted_set: Default::default(), + reverted: Default::default(), + declarations_to_apply_unless_overridden: Default::default(), + } + } + + fn substitute_variables_if_needed<'cache, 'decl>( + &self, + context: &mut computed::Context, + shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, + declaration: &'decl PropertyDeclaration, + ) -> Cow<'decl, PropertyDeclaration> + where + 'cache: 'decl, + { + let declaration = match *declaration { + PropertyDeclaration::WithVariables(ref declaration) => declaration, + ref d => return Cow::Borrowed(d), + }; + + if !declaration.id.inherited() { + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + + // NOTE(emilio): We only really need to add the `display` / + // `content` flag if the CSS variable has not been specified on our + // declarations, but we don't have that information at this point, + // and it doesn't seem like an important enough optimization to + // warrant it. + match declaration.id { + LonghandId::Display => { + context + .builder + .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); + }, + LonghandId::Content => { + context + .builder + .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); + }, + _ => {}, + } + } + + debug_assert!( + context.builder.stylist.is_some(), + "Need a Stylist to substitute variables!" + ); + declaration.value.substitute_variables( + declaration.id, + context.builder.custom_properties(), + context.builder.stylist.unwrap(), + context, + shorthand_cache, + ) + } + + fn apply_one_prioritary_property( + &mut self, + context: &mut computed::Context, + decls: &Declarations, + cache: &mut ShorthandsWithPropertyReferencesCache, + id: PrioritaryPropertyId, + ) -> bool { + let mut index = decls.prioritary_positions[id as usize].most_important; + if index == DeclarationIndex::MAX { + return false; + } + + let longhand_id = id.to_longhand(); + debug_assert!( + !longhand_id.is_logical(), + "That could require more book-keeping" + ); + loop { + let decl = decls.longhand_declarations[index as usize]; + self.apply_one_longhand(context, longhand_id, decl.decl, decl.priority, cache); + if self.seen.contains(longhand_id) { + return true; // Common case, we're done. + } + debug_assert!( + self.reverted_set.contains(longhand_id), + "How else can we fail to apply a prioritary property?" + ); + debug_assert!( + decl.next_index == 0 || decl.next_index > index, + "should make progress! {} -> {}", + index, + decl.next_index, + ); + index = decl.next_index; + if index == 0 { + break; + } + } + false + } + + fn apply_prioritary_properties( + &mut self, + context: &mut computed::Context, + decls: &Declarations, + cache: &mut ShorthandsWithPropertyReferencesCache, + ) { + // Keeps apply_one_prioritary_property calls readable, considering the repititious + // arguments. + macro_rules! apply { + ($prop:ident) => { + self.apply_one_prioritary_property( + context, + decls, + cache, + PrioritaryPropertyId::$prop, + ) + }; + } + + if !decls.has_prioritary_properties { + return; + } + + let has_writing_mode = apply!(WritingMode) | apply!(Direction) | apply!(TextOrientation); + if has_writing_mode { + self.compute_writing_mode(context); + } + + if apply!(Zoom) { + self.compute_zoom(context); + } + + // Compute font-family. + let has_font_family = apply!(FontFamily); + let has_lang = apply!(XLang); + if has_lang { + self.recompute_initial_font_family_if_needed(&mut context.builder); + } + if has_font_family { + self.prioritize_user_fonts_if_needed(&mut context.builder); + } + + // Compute font-size. + if apply!(XTextScale) { + self.unzoom_fonts_if_needed(&mut context.builder); + } + let has_font_size = apply!(FontSize); + let has_math_depth = apply!(MathDepth); + let has_min_font_size_ratio = apply!(MozMinFontSizeRatio); + + if has_math_depth && has_font_size { + self.recompute_math_font_size_if_needed(context); + } + if has_lang || has_font_family { + self.recompute_keyword_font_size_if_needed(context); + } + if has_font_size || has_min_font_size_ratio || has_lang || has_font_family { + self.constrain_font_size_if_needed(&mut context.builder); + } + + // Compute the rest of the first-available-font-affecting properties. + apply!(FontWeight); + apply!(FontStretch); + apply!(FontStyle); + apply!(FontSizeAdjust); + + apply!(ColorScheme); + apply!(ForcedColorAdjust); + + // Compute the line height. + apply!(LineHeight); + } + + fn apply_non_prioritary_properties( + &mut self, + context: &mut computed::Context, + longhand_declarations: &[Declaration], + shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, + properties_to_apply: &LonghandIdSet, + ) { + debug_assert!(!properties_to_apply.contains_any(LonghandIdSet::prioritary_properties())); + debug_assert!(self.declarations_to_apply_unless_overridden.is_empty()); + for declaration in &*longhand_declarations { + let mut longhand_id = declaration.decl.id().as_longhand().unwrap(); + if !properties_to_apply.contains(longhand_id) { + continue; + } + debug_assert!(PrioritaryPropertyId::from_longhand(longhand_id).is_none()); + let is_logical = longhand_id.is_logical(); + if is_logical { + let wm = context.builder.writing_mode; + context + .rule_cache_conditions + .borrow_mut() + .set_writing_mode_dependency(wm); + longhand_id = longhand_id.to_physical(wm); + } + self.apply_one_longhand( + context, + longhand_id, + declaration.decl, + declaration.priority, + shorthand_cache, + ); + } + if !self.declarations_to_apply_unless_overridden.is_empty() { + debug_assert!(self.ignore_colors); + for declaration in std::mem::take(&mut self.declarations_to_apply_unless_overridden) { + let longhand_id = declaration.id().as_longhand().unwrap(); + debug_assert!(!longhand_id.is_logical()); + if !self.seen.contains(longhand_id) { + unsafe { + self.do_apply_declaration(context, longhand_id, &declaration); + } + } + } + } + } + + fn apply_one_longhand( + &mut self, + context: &mut computed::Context, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + priority: CascadePriority, + cache: &mut ShorthandsWithPropertyReferencesCache, + ) { + debug_assert!(!longhand_id.is_logical()); + let origin = priority.cascade_level().origin(); + if self.seen.contains(longhand_id) { + return; + } + + if self.reverted_set.contains(longhand_id) { + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&longhand_id) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + return; + } + } + } + + let mut declaration = self.substitute_variables_if_needed(context, cache, declaration); + + // When document colors are disabled, do special handling of + // properties that are marked as ignored in that mode. + if self.ignore_colors { + tweak_when_ignoring_colors( + context, + longhand_id, + origin, + &mut declaration, + &mut self.declarations_to_apply_unless_overridden, + ); + } + + let is_unset = match declaration.get_css_wide_keyword() { + Some(keyword) => match keyword { + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; + // We intentionally don't want to insert it into `self.seen`, `reverted` takes + // care of rejecting other declarations as needed. + self.reverted_set.insert(longhand_id); + self.reverted.insert(longhand_id, (priority, origin_revert)); + return; + }, + CSSWideKeyword::Unset => true, + CSSWideKeyword::Inherit => longhand_id.inherited(), + CSSWideKeyword::Initial => !longhand_id.inherited(), + }, + None => false, + }; + + self.seen.insert(longhand_id); + if origin == Origin::Author { + self.author_specified.insert(longhand_id); + } + + if is_unset { + return; + } + + unsafe { self.do_apply_declaration(context, longhand_id, &declaration) } + } + + #[inline] + unsafe fn do_apply_declaration( + &self, + context: &mut computed::Context, + longhand_id: LonghandId, + declaration: &PropertyDeclaration, + ) { + debug_assert!(!longhand_id.is_logical()); + // We could (and used to) use a pattern match here, but that bloats this + // function to over 100K of compiled code! + // + // To improve i-cache behavior, we outline the individual functions and + // use virtual dispatch instead. + (CASCADE_PROPERTY[longhand_id as usize])(&declaration, context); + } + + fn compute_zoom(&self, context: &mut computed::Context) { + context.builder.effective_zoom = context + .builder + .inherited_effective_zoom() + .compute_effective(context.builder.specified_zoom()); + } + + fn compute_writing_mode(&self, context: &mut computed::Context) { + context.builder.writing_mode = WritingMode::new(context.builder.get_inherited_box()) + } + + fn compute_visited_style_if_needed<E>( + &self, + context: &mut computed::Context, + element: Option<E>, + parent_style: Option<&ComputedValues>, + layout_parent_style: Option<&ComputedValues>, + visited_rules: &StrongRuleNode, + guards: &StylesheetGuards, + ) where + E: TElement, + { + let is_link = context.builder.pseudo.is_none() && element.unwrap().is_link(); + + macro_rules! visited_parent { + ($parent:expr) => { + if is_link { + $parent + } else { + $parent.map(|p| p.visited_style().unwrap_or(p)) + } + }; + } + + // We could call apply_declarations directly, but that'd cause + // another instantiation of this function which is not great. + let style = cascade_rules( + context.builder.stylist.unwrap(), + context.builder.pseudo, + visited_rules, + guards, + visited_parent!(parent_style), + visited_parent!(layout_parent_style), + self.first_line_reparenting, + CascadeMode::Visited { + unvisited_context: &*context, + }, + // Cascade input flags don't matter for the visited style, they are + // in the main (unvisited) style. + Default::default(), + // The rule cache doesn't care about caching :visited + // styles, we cache the unvisited style instead. We still do + // need to set the caching dependencies properly if present + // though, so the cache conditions need to match. + None, // rule_cache + &mut *context.rule_cache_conditions.borrow_mut(), + element, + ); + context.builder.visited_style = Some(style); + } + + fn finished_applying_properties(&self, builder: &mut StyleBuilder) { + #[cfg(feature = "gecko")] + { + if let Some(bg) = builder.get_background_if_mutated() { + bg.fill_arrays(); + } + + if let Some(svg) = builder.get_svg_if_mutated() { + svg.fill_arrays(); + } + } + + if self + .author_specified + .contains_any(LonghandIdSet::border_background_properties()) + { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); + } + + if self.author_specified.contains(LonghandId::FontFamily) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY); + } + + if self.author_specified.contains(LonghandId::Color) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_TEXT_COLOR); + } + + if self.author_specified.contains(LonghandId::LetterSpacing) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING); + } + + if self.author_specified.contains(LonghandId::WordSpacing) { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING); + } + + if self + .author_specified + .contains(LonghandId::FontSynthesisWeight) + { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT); + } + + if self + .author_specified + .contains(LonghandId::FontSynthesisStyle) + { + builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE); + } + + #[cfg(feature = "servo")] + { + if let Some(font) = builder.get_font_if_mutated() { + font.compute_font_hash(); + } + } + } + + fn try_to_use_cached_reset_properties( + &self, + builder: &mut StyleBuilder<'b>, + cache: Option<&'b RuleCache>, + guards: &StylesheetGuards, + ) -> bool { + let style = match self.first_line_reparenting { + FirstLineReparenting::Yes { style_to_reparent } => style_to_reparent, + FirstLineReparenting::No => { + let Some(cache) = cache else { return false }; + let Some(style) = cache.find(guards, builder) else { + return false; + }; + style + }, + }; + + builder.copy_reset_from(style); + + // We're using the same reset style as another element, and we'll skip + // applying the relevant properties. So we need to do the relevant + // bookkeeping here to keep these bits correct. + // + // Note that the border/background properties are non-inherited, so we + // don't need to do anything else other than just copying the bits over. + // + // When using this optimization, we also need to copy whether the old + // style specified viewport units / used font-relative lengths, this one + // would as well. It matches the same rules, so it is the right thing + // to do anyways, even if it's only used on inherited properties. + let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | + ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS | + ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS | + ComputedValueFlags::USES_CONTAINER_UNITS | + ComputedValueFlags::USES_VIEWPORT_UNITS; + builder.add_flags(style.flags & bits_to_copy); + + true + } + + /// The initial font depends on the current lang group so we may need to + /// recompute it if the language changed. + #[inline] + #[cfg(feature = "gecko")] + fn recompute_initial_font_family_if_needed(&self, builder: &mut StyleBuilder) { + use crate::gecko_bindings::bindings; + use crate::values::computed::font::FontFamily; + + let default_font_type = { + let font = builder.get_font(); + + if !font.mFont.family.is_initial { + return; + } + + let default_font_type = unsafe { + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( + builder.device.document(), + font.mLanguage.mRawPtr, + ) + }; + + let initial_generic = font.mFont.family.families.single_generic(); + debug_assert!( + initial_generic.is_some(), + "Initial font should be just one generic font" + ); + if initial_generic == Some(default_font_type) { + return; + } + + default_font_type + }; + + // NOTE: Leaves is_initial untouched. + builder.mutate_font().mFont.family.families = + FontFamily::generic(default_font_type).families.clone(); + } + + /// Prioritize user fonts if needed by pref. + #[inline] + #[cfg(feature = "gecko")] + fn prioritize_user_fonts_if_needed(&self, builder: &mut StyleBuilder) { + use crate::gecko_bindings::bindings; + + // Check the use_document_fonts setting for content, but for chrome + // documents they're treated as always enabled. + if static_prefs::pref!("browser.display.use_document_fonts") != 0 || + builder.device.chrome_rules_enabled_for_document() + { + return; + } + + let default_font_type = { + let font = builder.get_font(); + + if font.mFont.family.is_system_font { + return; + } + + if !font.mFont.family.families.needs_user_font_prioritization() { + return; + } + + unsafe { + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( + builder.device.document(), + font.mLanguage.mRawPtr, + ) + } + }; + + let font = builder.mutate_font(); + font.mFont + .family + .families + .prioritize_first_generic_or_prepend(default_font_type); + } + + /// Some keyword sizes depend on the font family and language. + #[cfg(feature = "gecko")] + fn recompute_keyword_font_size_if_needed(&self, context: &mut computed::Context) { + use crate::values::computed::ToComputedValue; + + if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { + return; + } + + let new_size = { + let font = context.builder.get_font(); + let info = font.clone_font_size().keyword_info; + let new_size = match info.kw { + specified::FontSizeKeyword::None => return, + _ => { + context.for_non_inherited_property = false; + specified::FontSize::Keyword(info).to_computed_value(context) + }, + }; + + if font.mScriptUnconstrainedSize == new_size.computed_size { + return; + } + + new_size + }; + + context.builder.mutate_font().set_font_size(new_size); + } + + /// Some properties, plus setting font-size itself, may make us go out of + /// our minimum font-size range. + #[cfg(feature = "gecko")] + fn constrain_font_size_if_needed(&self, builder: &mut StyleBuilder) { + use crate::gecko_bindings::bindings; + use crate::values::generics::NonNegative; + + let min_font_size = { + let font = builder.get_font(); + let min_font_size = unsafe { + bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document()) + }; + + if font.mFont.size.0 >= min_font_size { + return; + } + + NonNegative(min_font_size) + }; + + builder.mutate_font().mFont.size = min_font_size; + } + + /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up + /// the struct when this happens by unzooming its contained font values, which will have been + /// zoomed in the parent. + /// + /// FIXME(emilio): Why doing this _before_ handling font-size? That sounds wrong. + #[cfg(feature = "gecko")] + fn unzoom_fonts_if_needed(&self, builder: &mut StyleBuilder) { + debug_assert!(self.seen.contains(LonghandId::XTextScale)); + + let parent_text_scale = builder.get_parent_font().clone__x_text_scale(); + let text_scale = builder.get_font().clone__x_text_scale(); + if parent_text_scale == text_scale { + return; + } + debug_assert_ne!( + parent_text_scale.text_zoom_enabled(), + text_scale.text_zoom_enabled(), + "There's only one value that disables it" + ); + debug_assert!( + !text_scale.text_zoom_enabled(), + "We only ever disable text zoom (in svg:text), never enable it" + ); + let device = builder.device; + builder.mutate_font().unzoom_fonts(device); + } + + /// Special handling of font-size: math (used for MathML). + /// https://w3c.github.io/mathml-core/#the-math-script-level-property + /// TODO: Bug: 1548471: MathML Core also does not specify a script min size + /// should we unship that feature or standardize it? + #[cfg(feature = "gecko")] + fn recompute_math_font_size_if_needed(&self, context: &mut computed::Context) { + use crate::values::generics::NonNegative; + + // Do not do anything if font-size: math or math-depth is not set. + if context.builder.get_font().clone_font_size().keyword_info.kw != + specified::FontSizeKeyword::Math + { + return; + } + + const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71; + + // Helper function that calculates the scale factor applied to font-size + // when math-depth goes from parent_math_depth to computed_math_depth. + // This function is essentially a modification of the MathML3's formula + // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor + // of parent_script_percent_scale_down is applied when math-depth goes + // from 0 to 1 and parent_script_script_percent_scale_down is applied + // when math-depth goes from 0 to 2. This is also a straightforward + // implementation of the specification's algorithm: + // https://w3c.github.io/mathml-core/#the-math-script-level-property + fn scale_factor_for_math_depth_change( + parent_math_depth: i32, + computed_math_depth: i32, + parent_script_percent_scale_down: Option<f32>, + parent_script_script_percent_scale_down: Option<f32>, + ) -> f32 { + let mut a = parent_math_depth; + let mut b = computed_math_depth; + let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE; + let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c); + let scale_between_0_and_2 = + parent_script_script_percent_scale_down.unwrap_or_else(|| c * c); + let mut s = 1.0; + let mut invert_scale_factor = false; + if a == b { + return s; + } + if b < a { + mem::swap(&mut a, &mut b); + invert_scale_factor = true; + } + let mut e = b - a; + if a <= 0 && b >= 2 { + s *= scale_between_0_and_2; + e -= 2; + } else if a == 1 { + s *= scale_between_0_and_2 / scale_between_0_and_1; + e -= 1; + } else if b == 1 { + s *= scale_between_0_and_1; + e -= 1; + } + s *= (c as f32).powi(e); + if invert_scale_factor { + 1.0 / s.max(f32::MIN_POSITIVE) + } else { + s + } + } + + let (new_size, new_unconstrained_size) = { + let builder = &context.builder; + let font = builder.get_font(); + let parent_font = builder.get_parent_font(); + + let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth); + + if delta == 0 { + return; + } + + let mut min = parent_font.mScriptMinSize; + if font.mXTextScale.text_zoom_enabled() { + min = builder.device.zoom_text(min); + } + + // Calculate scale factor following MathML Core's algorithm. + let scale = { + // Script scale factors are independent of orientation. + let font_metrics = context.query_font_metrics( + FontBaseSize::InheritedStyle, + FontMetricsOrientation::Horizontal, + /* retrieve_math_scales = */ true, + ); + scale_factor_for_math_depth_change( + parent_font.mMathDepth as i32, + font.mMathDepth as i32, + font_metrics.script_percent_scale_down, + font_metrics.script_script_percent_scale_down, + ) + }; + + let parent_size = parent_font.mSize.0; + let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0; + let new_size = parent_size.scale_by(scale); + let new_unconstrained_size = parent_unconstrained_size.scale_by(scale); + + if scale <= 1. { + // The parent size can be smaller than scriptminsize, e.g. if it + // was specified explicitly. Don't scale in this case, but we + // don't want to set it to scriptminsize either since that will + // make it larger. + if parent_size <= min { + (parent_size, new_unconstrained_size) + } else { + (min.max(new_size), new_unconstrained_size) + } + } else { + // If the new unconstrained size is larger than the min size, + // this means we have escaped the grasp of scriptminsize and can + // revert to using the unconstrained size. + // However, if the new size is even larger (perhaps due to usage + // of em units), use that instead. + ( + new_size.min(new_unconstrained_size.max(min)), + new_unconstrained_size, + ) + } + }; + let font = context.builder.mutate_font(); + font.mFont.size = NonNegative(new_size); + font.mSize = NonNegative(new_size); + font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size); + } +} diff --git a/servo/components/style/properties/computed_value_flags.rs b/servo/components/style/properties/computed_value_flags.rs new file mode 100644 index 0000000000..0952cb1799 --- /dev/null +++ b/servo/components/style/properties/computed_value_flags.rs @@ -0,0 +1,194 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Misc information about a given computed style. + +bitflags! { + /// Misc information about a given computed style. + /// + /// All flags are currently inherited for text, pseudo elements, and + /// anonymous boxes, see StyleBuilder::for_inheritance and its callsites. + /// If we ever want to add some flags that shouldn't inherit for them, + /// we might want to add a function to handle this. + #[repr(C)] + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub struct ComputedValueFlags: u32 { + /// Whether the style or any of the ancestors has a text-decoration-line + /// property that should get propagated to descendants. + /// + /// text-decoration-line is a reset property, but gets propagated in the + /// frame/box tree. + const HAS_TEXT_DECORATION_LINES = 1 << 0; + + /// Whether line break inside should be suppressed. + /// + /// If this flag is set, the line should not be broken inside, + /// which means inlines act as if nowrap is set, <br> element is + /// suppressed, and blocks are inlinized. + /// + /// This bit is propagated to all children of line participants. + /// It is currently used by ruby to make its content unbreakable. + const SHOULD_SUPPRESS_LINEBREAK = 1 << 1; + + /// A flag used to mark text that that has text-combine-upright. + /// + /// This is used from Gecko's layout engine. + const IS_TEXT_COMBINED = 1 << 2; + + /// A flag used to mark styles under a relevant link that is also + /// visited. + const IS_RELEVANT_LINK_VISITED = 1 << 3; + + /// A flag used to mark styles which are a pseudo-element or under one. + const IS_IN_PSEUDO_ELEMENT_SUBTREE = 1 << 4; + + /// A flag used to mark styles which have contain:style or under one. + const SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE = 1 << 5; + + /// Whether this style's `display` property depends on our parent style. + /// + /// This is important because it may affect our optimizations to avoid + /// computing the style of pseudo-elements, given whether the + /// pseudo-element is generated depends on the `display` value. + const DISPLAY_DEPENDS_ON_INHERITED_STYLE = 1 << 6; + + /// Whether this style's `content` depends on our parent style. + /// + /// Important because of the same reason. + const CONTENT_DEPENDS_ON_INHERITED_STYLE = 1 << 7; + + /// Whether the child explicitly inherits any reset property. + const INHERITS_RESET_STYLE = 1 << 8; + + /// Whether any value on our style is font-metric-dependent on our + /// primary font. + const DEPENDS_ON_SELF_FONT_METRICS = 1 << 9; + + /// Whether any value on our style is font-metric-dependent on the + /// primary font of our parent. + const DEPENDS_ON_INHERITED_FONT_METRICS = 1 << 10; + + /// Whether the style or any of the ancestors has a multicol style. + /// + /// Only used in Servo. + const CAN_BE_FRAGMENTED = 1 << 11; + + /// Whether this style is the style of the document element. + const IS_ROOT_ELEMENT_STYLE = 1 << 12; + + /// Whether this element is inside an `opacity: 0` subtree. + const IS_IN_OPACITY_ZERO_SUBTREE = 1 << 13; + + /// Whether there are author-specified rules for border-* properties + /// (except border-image-*), background-color, or background-image. + /// + /// TODO(emilio): Maybe do include border-image, see: + /// + /// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845 + const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14; + + /// Whether there are author-specified rules for `font-family`. + const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16; + + /// Whether there are author-specified rules for `font-synthesis-weight`. + const HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT = 1 << 17; + + /// Whether there are author-specified rules for `font-synthesis-style`. + const HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE = 1 << 18; + + // (There's also font-synthesis-small-caps and font-synthesis-position, + // but we don't currently need to keep track of those.) + + /// Whether there are author-specified rules for `letter-spacing`. + const HAS_AUTHOR_SPECIFIED_LETTER_SPACING = 1 << 19; + + /// Whether there are author-specified rules for `word-spacing`. + const HAS_AUTHOR_SPECIFIED_WORD_SPACING = 1 << 20; + + /// Whether the style depends on viewport units. + const USES_VIEWPORT_UNITS = 1 << 21; + + /// Whether the style depends on viewport units on container queries. + /// + /// This needs to be a separate flag from `USES_VIEWPORT_UNITS` because + /// it causes us to re-match the style (rather than re-cascascading it, + /// which is enough for other uses of viewport units). + const USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES = 1 << 22; + + /// A flag used to mark styles which have `container-type` of `size` or + /// `inline-size`, or under one. + const SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE = 1 << 23; + + /// Whether the style evaluated any relative selector. + const CONSIDERED_RELATIVE_SELECTOR = 1 << 24; + + /// Whether the style evaluated the matched element to be an anchor of + /// a relative selector. + const ANCHORS_RELATIVE_SELECTOR = 1 << 25; + + /// Whether the style uses container query units, in which case the style depends on the + /// container's size and we can't reuse it across cousins (without double-checking the + /// container at least). + const USES_CONTAINER_UNITS = 1 << 26; + + /// Whether there are author-specific rules for text `color`. + const HAS_AUTHOR_SPECIFIED_TEXT_COLOR = 1 << 27; + } +} + +impl Default for ComputedValueFlags { + #[inline] + fn default() -> Self { + Self::empty() + } +} + +impl ComputedValueFlags { + /// Flags that are unconditionally propagated to descendants. + #[inline] + fn inherited_flags() -> Self { + Self::IS_RELEVANT_LINK_VISITED | + Self::CAN_BE_FRAGMENTED | + Self::IS_IN_PSEUDO_ELEMENT_SUBTREE | + Self::HAS_TEXT_DECORATION_LINES | + Self::IS_IN_OPACITY_ZERO_SUBTREE | + Self::SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE | + Self::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE + } + + /// Flags that may be propagated to descendants. + #[inline] + fn maybe_inherited_flags() -> Self { + Self::inherited_flags() | Self::SHOULD_SUPPRESS_LINEBREAK + } + + /// Flags that are an input to the cascade. + #[inline] + fn cascade_input_flags() -> Self { + Self::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES | + Self::CONSIDERED_RELATIVE_SELECTOR | + Self::ANCHORS_RELATIVE_SELECTOR + } + + /// Returns the flags that are always propagated to descendants. + /// + /// See StyleAdjuster::set_bits and StyleBuilder. + #[inline] + pub fn inherited(self) -> Self { + self & Self::inherited_flags() + } + + /// Flags that are conditionally propagated to descendants, just to handle + /// properly style invalidation. + #[inline] + pub fn maybe_inherited(self) -> Self { + self & Self::maybe_inherited_flags() + } + + /// Flags that are an input to the cascade. + #[inline] + pub fn for_cascade_inputs(self) -> Self { + self & Self::cascade_input_flags() + } +} diff --git a/servo/components/style/properties/counted_unknown_properties.py b/servo/components/style/properties/counted_unknown_properties.py new file mode 100644 index 0000000000..b1b800812d --- /dev/null +++ b/servo/components/style/properties/counted_unknown_properties.py @@ -0,0 +1,110 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +COUNTED_UNKNOWN_PROPERTIES = [ + "-webkit-font-smoothing", + "-webkit-tap-highlight-color", + "speak", + "text-size-adjust", + "-webkit-font-feature-settings", + "-webkit-user-drag", + "orphans", + "widows", + "-webkit-user-modify", + "-webkit-margin-before", + "-webkit-margin-after", + "-webkit-margin-start", + "-webkit-column-break-inside", + "-webkit-padding-start", + "-webkit-margin-end", + "-webkit-box-reflect", + "-webkit-print-color-adjust", + "-webkit-mask-box-image", + "-webkit-line-break", + "alignment-baseline", + "-webkit-writing-mode", + "baseline-shift", + "-webkit-hyphenate-character", + "-webkit-highlight", + "background-repeat-x", + "-webkit-padding-end", + "background-repeat-y", + "-webkit-text-emphasis-color", + "-webkit-margin-top-collapse", + "-webkit-rtl-ordering", + "-webkit-padding-before", + "-webkit-text-decorations-in-effect", + "-webkit-border-vertical-spacing", + "-webkit-locale", + "-webkit-padding-after", + "-webkit-border-horizontal-spacing", + "color-rendering", + "-webkit-column-break-before", + "-webkit-transform-origin-x", + "-webkit-transform-origin-y", + "-webkit-text-emphasis-position", + "buffered-rendering", + "-webkit-text-orientation", + "-webkit-text-combine", + "-webkit-text-emphasis-style", + "-webkit-text-emphasis", + "-webkit-mask-box-image-width", + "-webkit-mask-box-image-source", + "-webkit-mask-box-image-outset", + "-webkit-mask-box-image-slice", + "-webkit-mask-box-image-repeat", + "-webkit-margin-after-collapse", + "-webkit-border-before-color", + "-webkit-border-before-width", + "-webkit-perspective-origin-x", + "-webkit-perspective-origin-y", + "-webkit-margin-before-collapse", + "-webkit-border-before-style", + "-webkit-margin-bottom-collapse", + "-webkit-ruby-position", + "-webkit-column-break-after", + "-webkit-margin-collapse", + "-webkit-border-before", + "-webkit-border-end", + "-webkit-border-after", + "-webkit-border-start", + "-webkit-min-logical-width", + "-webkit-logical-height", + "-webkit-transform-origin-z", + "-webkit-font-size-delta", + "-webkit-logical-width", + "-webkit-max-logical-width", + "-webkit-min-logical-height", + "-webkit-max-logical-height", + "-webkit-border-end-color", + "-webkit-border-end-width", + "-webkit-border-start-color", + "-webkit-border-start-width", + "-webkit-border-after-color", + "-webkit-border-after-width", + "-webkit-border-end-style", + "-webkit-border-after-style", + "-webkit-border-start-style", + "-webkit-mask-repeat-x", + "-webkit-mask-repeat-y", + "user-zoom", + "min-zoom", + "-webkit-box-decoration-break", + "orientation", + "max-zoom", + "-webkit-app-region", + "-webkit-column-rule", + "-webkit-column-span", + "-webkit-column-gap", + "-webkit-shape-outside", + "-webkit-column-rule-width", + "-webkit-column-count", + "-webkit-opacity", + "-webkit-column-width", + "-webkit-shape-image-threshold", + "-webkit-column-rule-style", + "-webkit-columns", + "-webkit-column-rule-color", + "-webkit-shape-margin", +] diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py new file mode 100644 index 0000000000..093f1cb75f --- /dev/null +++ b/servo/components/style/properties/data.py @@ -0,0 +1,1083 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +import re +from counted_unknown_properties import COUNTED_UNKNOWN_PROPERTIES + +# It is important that the order of these physical / logical variants matches +# the order of the enum variants in logical_geometry.rs +PHYSICAL_SIDES = ["top", "right", "bottom", "left"] +PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"] +PHYSICAL_AXES = ["y", "x"] +PHYSICAL_SIZES = ["height", "width"] +LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"] +LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"] +LOGICAL_SIZES = ["block-size", "inline-size"] +LOGICAL_AXES = ["block", "inline"] + +# bool is True when logical +ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [ + (side, True) for side in LOGICAL_SIDES +] +ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [ + (size, True) for size in LOGICAL_SIZES +] +ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [ + (corner, True) for corner in LOGICAL_CORNERS +] +ALL_AXES = [(axis, False) for axis in PHYSICAL_AXES] + [ + (axis, True) for axis in LOGICAL_AXES +] + +SYSTEM_FONT_LONGHANDS = """font_family font_size font_style + font_stretch font_weight""".split() + +PRIORITARY_PROPERTIES = set( + [ + # The writing-mode group has the most priority of all property groups, as + # sizes like font-size can depend on it. + "writing-mode", + "direction", + "text-orientation", + # The fonts and colors group has the second priority, as all other lengths + # and colors depend on them. + # + # There are some interdependencies between these, but we fix them up in + # Cascade::fixup_font_stuff. + # Needed to properly compute the zoomed font-size. + "-x-text-scale", + # Needed to do font-size computation in a language-dependent way. + "-x-lang", + # Needed for ruby to respect language-dependent min-font-size + # preferences properly, see bug 1165538. + "-moz-min-font-size-ratio", + # font-size depends on math-depth's computed value. + "math-depth", + # Needed to compute the first available font and its used size, + # in order to compute font-relative units correctly. + "font-size", + "font-size-adjust", + "font-weight", + "font-stretch", + "font-style", + "font-family", + # color-scheme affects how system colors resolve. + "color-scheme", + # forced-color-adjust affects whether colors are adjusted. + "forced-color-adjust", + # Zoom affects all absolute lengths. + "zoom", + # Line height lengths depend on this. + "line-height", + ] +) + +VISITED_DEPENDENT_PROPERTIES = set( + [ + "column-rule-color", + "text-emphasis-color", + "-webkit-text-fill-color", + "-webkit-text-stroke-color", + "text-decoration-color", + "fill", + "stroke", + "caret-color", + "background-color", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-block-start-color", + "border-inline-end-color", + "border-block-end-color", + "border-inline-start-color", + "outline-color", + "color", + ] +) + +# Bitfield values for all rule types which can have property declarations. +STYLE_RULE = 1 << 0 +PAGE_RULE = 1 << 1 +KEYFRAME_RULE = 1 << 2 + +ALL_RULES = STYLE_RULE | PAGE_RULE | KEYFRAME_RULE +DEFAULT_RULES = STYLE_RULE | KEYFRAME_RULE +DEFAULT_RULES_AND_PAGE = DEFAULT_RULES | PAGE_RULE +DEFAULT_RULES_EXCEPT_KEYFRAME = STYLE_RULE + +# Rule name to value dict +RULE_VALUES = { + "Style": STYLE_RULE, + "Page": PAGE_RULE, + "Keyframe": KEYFRAME_RULE, +} + + +def rule_values_from_arg(that): + if isinstance(that, int): + return that + mask = 0 + for rule in that.split(): + mask |= RULE_VALUES[rule] + return mask + + +def maybe_moz_logical_alias(engine, side, prop): + if engine == "gecko" and side[1]: + axis, dir = side[0].split("-") + if axis == "inline": + return prop % dir + return None + + +def to_rust_ident(name): + name = name.replace("-", "_") + if name in ["static", "super", "box", "move"]: # Rust keywords + name += "_" + return name + + +def to_snake_case(ident): + return re.sub("([A-Z]+)", lambda m: "_" + m.group(1).lower(), ident).strip("_") + + +def to_camel_case(ident): + return re.sub( + "(^|_|-)([a-z0-9])", lambda m: m.group(2).upper(), ident.strip("_").strip("-") + ) + + +def to_camel_case_lower(ident): + camel = to_camel_case(ident) + return camel[0].lower() + camel[1:] + + +# https://drafts.csswg.org/cssom/#css-property-to-idl-attribute +def to_idl_name(ident): + return re.sub("-([a-z])", lambda m: m.group(1).upper(), ident) + + +def parse_aliases(value): + aliases = {} + for pair in value.split(): + [a, v] = pair.split("=") + aliases[a] = v + return aliases + + +class Keyword(object): + def __init__( + self, + name, + values, + gecko_constant_prefix=None, + gecko_enum_prefix=None, + custom_consts=None, + extra_gecko_values=None, + extra_servo_2013_values=None, + extra_servo_2020_values=None, + gecko_aliases=None, + servo_2013_aliases=None, + servo_2020_aliases=None, + gecko_strip_moz_prefix=None, + gecko_inexhaustive=None, + ): + self.name = name + self.values = values.split() + if gecko_constant_prefix and gecko_enum_prefix: + raise TypeError( + "Only one of gecko_constant_prefix and gecko_enum_prefix " + "can be specified" + ) + self.gecko_constant_prefix = ( + gecko_constant_prefix or "NS_STYLE_" + self.name.upper().replace("-", "_") + ) + self.gecko_enum_prefix = gecko_enum_prefix + self.extra_gecko_values = (extra_gecko_values or "").split() + self.extra_servo_2013_values = (extra_servo_2013_values or "").split() + self.extra_servo_2020_values = (extra_servo_2020_values or "").split() + self.gecko_aliases = parse_aliases(gecko_aliases or "") + self.servo_2013_aliases = parse_aliases(servo_2013_aliases or "") + self.servo_2020_aliases = parse_aliases(servo_2020_aliases or "") + self.consts_map = {} if custom_consts is None else custom_consts + self.gecko_strip_moz_prefix = ( + True if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix + ) + self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None) + + def values_for(self, engine): + if engine == "gecko": + return self.values + self.extra_gecko_values + elif engine == "servo-2013": + return self.values + self.extra_servo_2013_values + elif engine == "servo-2020": + return self.values + self.extra_servo_2020_values + else: + raise Exception("Bad engine: " + engine) + + def aliases_for(self, engine): + if engine == "gecko": + return self.gecko_aliases + elif engine == "servo-2013": + return self.servo_2013_aliases + elif engine == "servo-2020": + return self.servo_2020_aliases + else: + raise Exception("Bad engine: " + engine) + + def gecko_constant(self, value): + moz_stripped = ( + value.replace("-moz-", "") + if self.gecko_strip_moz_prefix + else value.replace("-moz-", "moz-") + ) + mapped = self.consts_map.get(value) + if self.gecko_enum_prefix: + parts = moz_stripped.replace("-", "_").split("_") + parts = mapped if mapped else [p.title() for p in parts] + return self.gecko_enum_prefix + "::" + "".join(parts) + else: + suffix = mapped if mapped else moz_stripped.replace("-", "_") + return self.gecko_constant_prefix + "_" + suffix.upper() + + def needs_cast(self): + return self.gecko_enum_prefix is None + + def maybe_cast(self, type_str): + return "as " + type_str if self.needs_cast() else "" + + def casted_constant_name(self, value, cast_type): + if cast_type is None: + raise TypeError("We should specify the cast_type.") + + if self.gecko_enum_prefix is None: + return cast_type.upper() + "_" + self.gecko_constant(value) + else: + return ( + cast_type.upper() + + "_" + + self.gecko_constant(value).upper().replace("::", "_") + ) + + +def arg_to_bool(arg): + if isinstance(arg, bool): + return arg + assert arg in ["True", "False"], "Unexpected value for boolean arguement: " + repr( + arg + ) + return arg == "True" + + +def parse_property_aliases(alias_list): + result = [] + if alias_list: + for alias in alias_list.split(): + (name, _, pref) = alias.partition(":") + result.append((name, pref)) + return result + + +def to_phys(name, logical, physical): + return name.replace(logical, physical).replace("inset-", "") + + +class Property(object): + def __init__( + self, + name, + spec, + servo_2013_pref, + servo_2020_pref, + gecko_pref, + enabled_in, + rule_types_allowed, + aliases, + extra_prefixes, + flags, + ): + self.name = name + if not spec: + raise TypeError("Spec should be specified for " + name) + self.spec = spec + self.ident = to_rust_ident(name) + self.camel_case = to_camel_case(self.ident) + self.servo_2013_pref = servo_2013_pref + self.servo_2020_pref = servo_2020_pref + self.gecko_pref = gecko_pref + self.rule_types_allowed = rule_values_from_arg(rule_types_allowed) + # For enabled_in, the setup is as follows: + # It needs to be one of the four values: ["", "ua", "chrome", "content"] + # * "chrome" implies "ua", and implies that they're explicitly + # enabled. + # * "" implies the property will never be parsed. + # * "content" implies the property is accessible unconditionally, + # modulo a pref, set via servo_pref / gecko_pref. + assert enabled_in in ("", "ua", "chrome", "content") + self.enabled_in = enabled_in + self.aliases = parse_property_aliases(aliases) + self.extra_prefixes = parse_property_aliases(extra_prefixes) + self.flags = flags.split() if flags else [] + + def rule_types_allowed_names(self): + for name in RULE_VALUES: + if self.rule_types_allowed & RULE_VALUES[name] != 0: + yield name + + def experimental(self, engine): + if engine == "gecko": + return bool(self.gecko_pref) + elif engine == "servo-2013": + return bool(self.servo_2013_pref) + elif engine == "servo-2020": + return bool(self.servo_2020_pref) + else: + raise Exception("Bad engine: " + engine) + + def explicitly_enabled_in_ua_sheets(self): + return self.enabled_in in ("ua", "chrome") + + def explicitly_enabled_in_chrome(self): + return self.enabled_in == "chrome" + + def enabled_in_content(self): + return self.enabled_in == "content" + + def is_visited_dependent(self): + return self.name in VISITED_DEPENDENT_PROPERTIES + + def is_prioritary(self): + return self.name in PRIORITARY_PROPERTIES + + def nscsspropertyid(self): + return "nsCSSPropertyID::eCSSProperty_" + self.ident + + +class Longhand(Property): + def __init__( + self, + style_struct, + name, + spec=None, + animation_value_type=None, + keyword=None, + predefined_type=None, + servo_2013_pref=None, + servo_2020_pref=None, + gecko_pref=None, + enabled_in="content", + need_index=False, + gecko_ffi_name=None, + has_effect_on_gecko_scrollbars=None, + rule_types_allowed=DEFAULT_RULES, + cast_type="u8", + logical=False, + logical_group=None, + aliases=None, + extra_prefixes=None, + boxed=False, + flags=None, + allow_quirks="No", + ignored_when_colors_disabled=False, + simple_vector_bindings=False, + vector=False, + servo_restyle_damage="repaint", + affects=None, + ): + Property.__init__( + self, + name=name, + spec=spec, + servo_2013_pref=servo_2013_pref, + servo_2020_pref=servo_2020_pref, + gecko_pref=gecko_pref, + enabled_in=enabled_in, + rule_types_allowed=rule_types_allowed, + aliases=aliases, + extra_prefixes=extra_prefixes, + flags=flags, + ) + + self.affects = affects + self.flags += self.affects_flags() + + self.keyword = keyword + self.predefined_type = predefined_type + self.style_struct = style_struct + self.has_effect_on_gecko_scrollbars = has_effect_on_gecko_scrollbars + assert ( + has_effect_on_gecko_scrollbars in [None, False, True] + and not style_struct.inherited + or (gecko_pref is None and enabled_in != "") + == (has_effect_on_gecko_scrollbars is None) + ), ( + "Property " + + name + + ": has_effect_on_gecko_scrollbars must be " + + "specified, and must have a value of True or False, iff a " + + "property is inherited and is behind a Gecko pref or internal" + ) + self.need_index = need_index + self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case + self.cast_type = cast_type + self.logical = arg_to_bool(logical) + self.logical_group = logical_group + if self.logical: + assert logical_group, "Property " + name + " must have a logical group" + + self.boxed = arg_to_bool(boxed) + self.allow_quirks = allow_quirks + self.ignored_when_colors_disabled = ignored_when_colors_disabled + self.is_vector = vector + self.simple_vector_bindings = simple_vector_bindings + + # This is done like this since just a plain bool argument seemed like + # really random. + if animation_value_type is None: + raise TypeError( + "animation_value_type should be specified for (" + name + ")" + ) + self.animation_value_type = animation_value_type + + self.animatable = animation_value_type != "none" + self.is_animatable_with_computed_value = ( + animation_value_type == "ComputedValue" + or animation_value_type == "discrete" + ) + + # See compute_damage for the various values this can take + self.servo_restyle_damage = servo_restyle_damage + + def affects_flags(self): + # Layout is the stronger hint. This property animation affects layout + # or frame construction. `display` or `width` are examples that should + # use this. + if self.affects == "layout": + return ["AFFECTS_LAYOUT"] + # This property doesn't affect layout, but affects overflow. + # `transform` and co. are examples of this. + if self.affects == "overflow": + return ["AFFECTS_OVERFLOW"] + # This property affects the rendered output but doesn't affect layout. + # `opacity`, `color`, or `z-index` are examples of this. + if self.affects == "paint": + return ["AFFECTS_PAINT"] + # This property doesn't affect rendering in any way. + # `user-select` is an example of this. + assert self.affects == "", ( + "Property " + + self.name + + ': affects must be specified and be one of ["layout", "overflow", "paint", ""], see Longhand.affects_flags for documentation' + ) + return [] + + @staticmethod + def type(): + return "longhand" + + # For a given logical property, return the kind of mapping we need to + # perform, and which logical value we represent, in a tuple. + def logical_mapping_data(self, data): + if not self.logical: + return [] + # Sizes and axes are basically the same for mapping, we just need + # slightly different replacements (block-size -> height, etc rather + # than -x/-y) below. + for [ty, logical_items, physical_items] in [ + ["Side", LOGICAL_SIDES, PHYSICAL_SIDES], + ["Corner", LOGICAL_CORNERS, PHYSICAL_CORNERS], + ["Axis", LOGICAL_SIZES, PHYSICAL_SIZES], + ["Axis", LOGICAL_AXES, PHYSICAL_AXES], + ]: + candidate = [s for s in logical_items if s in self.name] + if candidate: + assert len(candidate) == 1 + return [ty, candidate[0], logical_items, physical_items] + assert False, "Don't know how to deal with " + self.name + + def logical_mapping_kind(self, data): + assert self.logical + [kind, item, _, _] = self.logical_mapping_data(data) + return "LogicalMappingKind::{}(Logical{}::{})".format( + kind, kind, to_camel_case(item.replace("-size", "")) + ) + + # For a given logical property return all the physical property names + # corresponding to it. + def all_physical_mapped_properties(self, data): + if not self.logical: + return [] + [_, logical_side, _, physical_items] = self.logical_mapping_data(data) + return [ + data.longhands_by_name[to_phys(self.name, logical_side, physical_side)] + for physical_side in physical_items + ] + + def may_be_disabled_in(self, shorthand, engine): + if engine == "gecko": + return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref + elif engine == "servo-2013": + return ( + self.servo_2013_pref + and self.servo_2013_pref != shorthand.servo_2013_pref + ) + elif engine == "servo-2020": + return ( + self.servo_2020_pref + and self.servo_2020_pref != shorthand.servo_2020_pref + ) + else: + raise Exception("Bad engine: " + engine) + + def base_type(self): + if self.predefined_type and not self.is_vector: + return "crate::values::specified::{}".format(self.predefined_type) + return "longhands::{}::SpecifiedValue".format(self.ident) + + def specified_type(self): + if self.predefined_type and not self.is_vector: + ty = "crate::values::specified::{}".format(self.predefined_type) + else: + ty = "longhands::{}::SpecifiedValue".format(self.ident) + if self.boxed: + ty = "Box<{}>".format(ty) + return ty + + def specified_is_copy(self): + if self.is_vector or self.boxed: + return False + if self.predefined_type: + return self.predefined_type in { + "AlignContent", + "AlignItems", + "AlignSelf", + "Appearance", + "AnimationComposition", + "AnimationDirection", + "AnimationFillMode", + "AnimationPlayState", + "AspectRatio", + "BaselineSource", + "BreakBetween", + "BreakWithin", + "BackgroundRepeat", + "BorderImageRepeat", + "BorderStyle", + "table::CaptionSide", + "Clear", + "ColumnCount", + "Contain", + "ContentVisibility", + "ContainerType", + "Display", + "FillRule", + "Float", + "FontLanguageOverride", + "FontSizeAdjust", + "FontStretch", + "FontStyle", + "FontSynthesis", + "FontVariantEastAsian", + "FontVariantLigatures", + "FontVariantNumeric", + "FontWeight", + "GreaterThanOrEqualToOneNumber", + "GridAutoFlow", + "ImageRendering", + "InitialLetter", + "Integer", + "JustifyContent", + "JustifyItems", + "JustifySelf", + "LineBreak", + "LineClamp", + "MasonryAutoFlow", + "ui::MozTheme", + "BoolInteger", + "text::MozControlCharacterVisibility", + "MathDepth", + "MozScriptMinSize", + "MozScriptSizeMultiplier", + "TransformBox", + "TextDecorationSkipInk", + "NonNegativeNumber", + "OffsetRotate", + "Opacity", + "OutlineStyle", + "Overflow", + "OverflowAnchor", + "OverflowClipBox", + "OverflowWrap", + "OverscrollBehavior", + "PageOrientation", + "Percentage", + "PrintColorAdjust", + "ForcedColorAdjust", + "Resize", + "RubyPosition", + "SVGOpacity", + "SVGPaintOrder", + "ScrollbarGutter", + "ScrollSnapAlign", + "ScrollSnapAxis", + "ScrollSnapStop", + "ScrollSnapStrictness", + "ScrollSnapType", + "TextAlign", + "TextAlignLast", + "TextDecorationLine", + "TextEmphasisPosition", + "TextJustify", + "TextTransform", + "TextUnderlinePosition", + "TouchAction", + "TransformStyle", + "UserSelect", + "WordBreak", + "XSpan", + "XTextScale", + "ZIndex", + "Zoom", + } + if self.name == "overflow-y": + return True + return bool(self.keyword) + + def animated_type(self): + assert self.animatable + computed = "<{} as ToComputedValue>::ComputedValue".format(self.base_type()) + if self.is_animatable_with_computed_value: + return computed + return "<{} as ToAnimatedValue>::AnimatedValue".format(computed) + + +class Shorthand(Property): + def __init__( + self, + name, + sub_properties, + spec=None, + servo_2013_pref=None, + servo_2020_pref=None, + gecko_pref=None, + enabled_in="content", + rule_types_allowed=DEFAULT_RULES, + aliases=None, + extra_prefixes=None, + flags=None, + ): + Property.__init__( + self, + name=name, + spec=spec, + servo_2013_pref=servo_2013_pref, + servo_2020_pref=servo_2020_pref, + gecko_pref=gecko_pref, + enabled_in=enabled_in, + rule_types_allowed=rule_types_allowed, + aliases=aliases, + extra_prefixes=extra_prefixes, + flags=flags, + ) + self.sub_properties = sub_properties + + def get_animatable(self): + for sub in self.sub_properties: + if sub.animatable: + return True + return False + + animatable = property(get_animatable) + + @staticmethod + def type(): + return "shorthand" + + +class Alias(object): + def __init__(self, name, original, gecko_pref): + self.name = name + self.ident = to_rust_ident(name) + self.camel_case = to_camel_case(self.ident) + self.original = original + self.enabled_in = original.enabled_in + self.animatable = original.animatable + self.servo_2013_pref = original.servo_2013_pref + self.servo_2020_pref = original.servo_2020_pref + self.gecko_pref = gecko_pref + self.rule_types_allowed = original.rule_types_allowed + self.flags = original.flags + + @staticmethod + def type(): + return "alias" + + def rule_types_allowed_names(self): + for name in RULE_VALUES: + if self.rule_types_allowed & RULE_VALUES[name] != 0: + yield name + + def experimental(self, engine): + if engine == "gecko": + return bool(self.gecko_pref) + elif engine == "servo-2013": + return bool(self.servo_2013_pref) + elif engine == "servo-2020": + return bool(self.servo_2020_pref) + else: + raise Exception("Bad engine: " + engine) + + def explicitly_enabled_in_ua_sheets(self): + return self.enabled_in in ["ua", "chrome"] + + def explicitly_enabled_in_chrome(self): + return self.enabled_in == "chrome" + + def enabled_in_content(self): + return self.enabled_in == "content" + + def nscsspropertyid(self): + return "nsCSSPropertyID::eCSSPropertyAlias_%s" % self.ident + + +class Method(object): + def __init__(self, name, return_type=None, arg_types=None, is_mut=False): + self.name = name + self.return_type = return_type + self.arg_types = arg_types or [] + self.is_mut = is_mut + + def arg_list(self): + args = ["_: " + x for x in self.arg_types] + args = ["&mut self" if self.is_mut else "&self"] + args + return ", ".join(args) + + def signature(self): + sig = "fn %s(%s)" % (self.name, self.arg_list()) + if self.return_type: + sig = sig + " -> " + self.return_type + return sig + + def declare(self): + return self.signature() + ";" + + def stub(self): + return self.signature() + "{ unimplemented!() }" + + +class StyleStruct(object): + def __init__(self, name, inherited, gecko_name=None, additional_methods=None): + self.gecko_struct_name = "Gecko" + name + self.name = name + self.name_lower = to_snake_case(name) + self.ident = to_rust_ident(self.name_lower) + self.longhands = [] + self.inherited = inherited + self.gecko_name = gecko_name or name + self.gecko_ffi_name = "nsStyle" + self.gecko_name + self.additional_methods = additional_methods or [] + self.document_dependent = self.gecko_name in ["Font", "Visibility", "Text"] + + +class PropertiesData(object): + def __init__(self, engine): + self.engine = engine + self.style_structs = [] + self.current_style_struct = None + self.longhands = [] + self.longhands_by_name = {} + self.longhands_by_logical_group = {} + self.longhand_aliases = [] + self.shorthands = [] + self.shorthands_by_name = {} + self.shorthand_aliases = [] + self.counted_unknown_properties = [ + CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES + ] + + def new_style_struct(self, *args, **kwargs): + style_struct = StyleStruct(*args, **kwargs) + self.style_structs.append(style_struct) + self.current_style_struct = style_struct + + def active_style_structs(self): + return [s for s in self.style_structs if s.additional_methods or s.longhands] + + def add_prefixed_aliases(self, property): + # FIXME Servo's DOM architecture doesn't support vendor-prefixed properties. + # See servo/servo#14941. + if self.engine == "gecko": + for prefix, pref in property.extra_prefixes: + property.aliases.append(("-%s-%s" % (prefix, property.name), pref)) + + def declare_longhand(self, name, engines=None, **kwargs): + engines = engines.split() + if self.engine not in engines: + return + + longhand = Longhand(self.current_style_struct, name, **kwargs) + self.add_prefixed_aliases(longhand) + longhand.aliases = [Alias(xp[0], longhand, xp[1]) for xp in longhand.aliases] + self.longhand_aliases += longhand.aliases + self.current_style_struct.longhands.append(longhand) + self.longhands.append(longhand) + self.longhands_by_name[name] = longhand + if longhand.logical_group: + self.longhands_by_logical_group.setdefault( + longhand.logical_group, [] + ).append(longhand) + + return longhand + + def declare_shorthand(self, name, sub_properties, engines, *args, **kwargs): + engines = engines.split() + if self.engine not in engines: + return + + sub_properties = [self.longhands_by_name[s] for s in sub_properties] + shorthand = Shorthand(name, sub_properties, *args, **kwargs) + self.add_prefixed_aliases(shorthand) + shorthand.aliases = [Alias(xp[0], shorthand, xp[1]) for xp in shorthand.aliases] + self.shorthand_aliases += shorthand.aliases + self.shorthands.append(shorthand) + self.shorthands_by_name[name] = shorthand + return shorthand + + def shorthands_except_all(self): + return [s for s in self.shorthands if s.name != "all"] + + def all_aliases(self): + return self.longhand_aliases + self.shorthand_aliases + + +def _add_logical_props(data, props): + groups = set() + for prop in props: + if prop not in data.longhands_by_name: + assert data.engine in ["servo-2013", "servo-2020"] + continue + prop = data.longhands_by_name[prop] + if prop.logical_group: + groups.add(prop.logical_group) + for group in groups: + for prop in data.longhands_by_logical_group[group]: + props.add(prop.name) + + +# These are probably Gecko bugs and should be supported per spec. +def _remove_common_first_line_and_first_letter_properties(props, engine): + if engine == "gecko": + props.remove("tab-size") + props.remove("hyphens") + props.remove("line-break") + props.remove("text-align-last") + props.remove("text-emphasis-position") + props.remove("text-emphasis-style") + props.remove("text-emphasis-color") + + props.remove("overflow-wrap") + props.remove("text-align") + props.remove("text-justify") + props.remove("white-space-collapse") + props.remove("text-wrap-mode") + props.remove("text-wrap-style") + props.remove("word-break") + props.remove("text-indent") + + +class PropertyRestrictions: + @staticmethod + def logical_group(data, group): + return [p.name for p in data.longhands_by_logical_group[group]] + + @staticmethod + def shorthand(data, shorthand): + if shorthand not in data.shorthands_by_name: + return [] + return [p.name for p in data.shorthands_by_name[shorthand].sub_properties] + + @staticmethod + def spec(data, spec_path): + return [p.name for p in data.longhands if spec_path in p.spec] + + # https://svgwg.org/svg2-draft/propidx.html + @staticmethod + def svg_text_properties(): + props = set( + [ + "fill", + "fill-opacity", + "fill-rule", + "paint-order", + "stroke", + "stroke-dasharray", + "stroke-dashoffset", + "stroke-linecap", + "stroke-linejoin", + "stroke-miterlimit", + "stroke-opacity", + "stroke-width", + "text-rendering", + "vector-effect", + ] + ) + return props + + @staticmethod + def webkit_text_properties(): + props = set( + [ + # Kinda like css-text? + "-webkit-text-stroke-width", + "-webkit-text-fill-color", + "-webkit-text-stroke-color", + ] + ) + return props + + # https://drafts.csswg.org/css-pseudo/#first-letter-styling + @staticmethod + def first_letter(data): + props = set( + [ + "color", + "opacity", + "float", + "initial-letter", + # Kinda like css-fonts? + "-moz-osx-font-smoothing", + "vertical-align", + # Will become shorthand of vertical-align (Bug 1830771) + "baseline-source", + "line-height", + # Kinda like css-backgrounds? + "background-blend-mode", + ] + + PropertyRestrictions.shorthand(data, "padding") + + PropertyRestrictions.shorthand(data, "margin") + + PropertyRestrictions.spec(data, "css-fonts") + + PropertyRestrictions.spec(data, "css-backgrounds") + + PropertyRestrictions.spec(data, "css-text") + + PropertyRestrictions.spec(data, "css-shapes") + + PropertyRestrictions.spec(data, "css-text-decor") + ) + props = props.union(PropertyRestrictions.svg_text_properties()) + props = props.union(PropertyRestrictions.webkit_text_properties()) + + _add_logical_props(data, props) + + _remove_common_first_line_and_first_letter_properties(props, data.engine) + return props + + # https://drafts.csswg.org/css-pseudo/#first-line-styling + @staticmethod + def first_line(data): + props = set( + [ + # Per spec. + "color", + "opacity", + # Kinda like css-fonts? + "-moz-osx-font-smoothing", + "vertical-align", + # Will become shorthand of vertical-align (Bug 1830771) + "baseline-source", + "line-height", + # Kinda like css-backgrounds? + "background-blend-mode", + ] + + PropertyRestrictions.spec(data, "css-fonts") + + PropertyRestrictions.spec(data, "css-backgrounds") + + PropertyRestrictions.spec(data, "css-text") + + PropertyRestrictions.spec(data, "css-text-decor") + ) + props = props.union(PropertyRestrictions.svg_text_properties()) + props = props.union(PropertyRestrictions.webkit_text_properties()) + + # These are probably Gecko bugs and should be supported per spec. + for prop in PropertyRestrictions.shorthand(data, "border"): + props.remove(prop) + for prop in PropertyRestrictions.shorthand(data, "border-radius"): + props.remove(prop) + props.remove("box-shadow") + + _remove_common_first_line_and_first_letter_properties(props, data.engine) + return props + + # https://drafts.csswg.org/css-pseudo/#placeholder + # + # The spec says that placeholder and first-line have the same restrictions, + # but that's not true in Gecko and we also allow a handful other properties + # for ::placeholder. + @staticmethod + def placeholder(data): + props = PropertyRestrictions.first_line(data) + props.add("opacity") + props.add("text-overflow") + props.add("text-align") + props.add("text-justify") + for p in PropertyRestrictions.shorthand(data, "text-wrap"): + props.add(p) + for p in PropertyRestrictions.shorthand(data, "white-space"): + props.add(p) + # ::placeholder can't be SVG text + props -= PropertyRestrictions.svg_text_properties() + + return props + + # https://drafts.csswg.org/css-pseudo/#marker-pseudo + @staticmethod + def marker(data): + return set( + [ + "color", + "text-combine-upright", + "text-transform", + "unicode-bidi", + "direction", + "content", + "line-height", + "-moz-osx-font-smoothing", + ] + + PropertyRestrictions.shorthand(data, "text-wrap") + + PropertyRestrictions.shorthand(data, "white-space") + + PropertyRestrictions.spec(data, "css-fonts") + + PropertyRestrictions.spec(data, "css-animations") + + PropertyRestrictions.spec(data, "css-transitions") + ) + + # https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element + @staticmethod + def cue(data): + return set( + [ + "color", + "opacity", + "visibility", + "text-shadow", + "text-combine-upright", + "ruby-position", + # XXX Should these really apply to cue? + "-moz-osx-font-smoothing", + # FIXME(emilio): background-blend-mode should be part of the + # background shorthand, and get reset, per + # https://drafts.fxtf.org/compositing/#background-blend-mode + "background-blend-mode", + ] + + PropertyRestrictions.shorthand(data, "text-decoration") + + PropertyRestrictions.shorthand(data, "text-wrap") + + PropertyRestrictions.shorthand(data, "white-space") + + PropertyRestrictions.shorthand(data, "background") + + PropertyRestrictions.shorthand(data, "outline") + + PropertyRestrictions.shorthand(data, "font") + + PropertyRestrictions.shorthand(data, "font-synthesis") + ) + + +class CountedUnknownProperty: + def __init__(self, name): + self.name = name + self.ident = to_rust_ident(name) + self.camel_case = to_camel_case(self.ident) diff --git a/servo/components/style/properties/declaration_block.rs b/servo/components/style/properties/declaration_block.rs new file mode 100644 index 0000000000..81d7148e62 --- /dev/null +++ b/servo/components/style/properties/declaration_block.rs @@ -0,0 +1,1642 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A property declaration block. + +#![deny(missing_docs)] + +use super::{ + property_counts, AllShorthand, ComputedValues, LogicalGroupSet, LonghandIdSet, + LonghandIdSetIterator, NonCustomPropertyIdSet, PropertyDeclaration, PropertyDeclarationId, + PropertyId, ShorthandId, SourcePropertyDeclaration, SourcePropertyDeclarationDrain, + SubpropertiesVec, +}; +use crate::context::QuirksMode; +use crate::custom_properties; +use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; +use crate::parser::ParserContext; +use crate::properties::{ + animated_properties::{AnimationValue, AnimationValueMap}, + StyleBuilder, +}; +use crate::rule_cache::RuleCacheConditions; +use crate::selector_map::PrecomputedHashSet; +use crate::selector_parser::SelectorImpl; +use crate::shared_lock::Locked; +use crate::str::{CssString, CssStringWriter}; +use crate::stylesheets::container_rule::ContainerSizeQuery; +use crate::stylesheets::{CssRuleType, Origin, UrlExtraData}; +use crate::stylist::Stylist; +use crate::values::computed::Context; +use cssparser::{ + parse_important, AtRuleParser, CowRcStr, DeclarationParser, Delimiter, ParseErrorKind, Parser, + ParserInput, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, +}; +use itertools::Itertools; +use selectors::SelectorList; +use servo_arc::Arc; +use smallbitvec::{self, SmallBitVec}; +use smallvec::SmallVec; +use std::fmt::{self, Write}; +use std::iter::{DoubleEndedIterator, Zip}; +use std::slice::Iter; +use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss}; +use thin_vec::ThinVec; + +/// A set of property declarations including animations and transitions. +#[derive(Default)] +pub struct AnimationDeclarations { + /// Declarations for animations. + pub animations: Option<Arc<Locked<PropertyDeclarationBlock>>>, + /// Declarations for transitions. + pub transitions: Option<Arc<Locked<PropertyDeclarationBlock>>>, +} + +impl AnimationDeclarations { + /// Whether or not this `AnimationDeclarations` is empty. + pub fn is_empty(&self) -> bool { + self.animations.is_none() && self.transitions.is_none() + } +} + +/// An enum describes how a declaration should update +/// the declaration block. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum DeclarationUpdate { + /// The given declaration doesn't update anything. + None, + /// The given declaration is new, and should be append directly. + Append, + /// The given declaration can be updated in-place at the given position. + UpdateInPlace { pos: usize }, + /// The given declaration cannot be updated in-place, and an existing + /// one needs to be removed at the given position. + AppendAndRemove { pos: usize }, +} + +/// A struct describes how a declaration block should be updated by +/// a `SourcePropertyDeclaration`. +#[derive(Default)] +pub struct SourcePropertyDeclarationUpdate { + updates: SubpropertiesVec<DeclarationUpdate>, + new_count: usize, + any_removal: bool, +} + +/// A declaration [importance][importance]. +/// +/// [importance]: https://drafts.csswg.org/css-cascade/#importance +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub enum Importance { + /// Indicates a declaration without `!important`. + Normal, + + /// Indicates a declaration with `!important`. + Important, +} + +impl Default for Importance { + fn default() -> Self { + Self::Normal + } +} + +impl Importance { + /// Return whether this is an important declaration. + pub fn important(self) -> bool { + match self { + Self::Normal => false, + Self::Important => true, + } + } +} + +/// A set of properties. +#[derive(Clone, Debug, ToShmem, Default, MallocSizeOf)] +pub struct PropertyDeclarationIdSet { + longhands: LonghandIdSet, + custom: PrecomputedHashSet<custom_properties::Name>, +} + +impl PropertyDeclarationIdSet { + /// Add the given property to the set. + pub fn insert(&mut self, id: PropertyDeclarationId) -> bool { + match id { + PropertyDeclarationId::Longhand(id) => { + if self.longhands.contains(id) { + return false; + } + self.longhands.insert(id); + return true; + }, + PropertyDeclarationId::Custom(name) => self.custom.insert((*name).clone()), + } + } + + /// Return whether the given property is in the set. + pub fn contains(&self, id: PropertyDeclarationId) -> bool { + match id { + PropertyDeclarationId::Longhand(id) => self.longhands.contains(id), + PropertyDeclarationId::Custom(name) => self.custom.contains(name), + } + } + + /// Remove the given property from the set. + pub fn remove(&mut self, id: PropertyDeclarationId) { + match id { + PropertyDeclarationId::Longhand(id) => self.longhands.remove(id), + PropertyDeclarationId::Custom(name) => { + self.custom.remove(name); + }, + } + } + + /// Remove all properties from the set. + pub fn clear(&mut self) { + self.longhands.clear(); + self.custom.clear(); + } + + /// Returns whether the set is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.longhands.is_empty() && self.custom.is_empty() + } + /// Returns whether this set contains any reset longhand. + #[inline] + pub fn contains_any_reset(&self) -> bool { + self.longhands.contains_any_reset() + } + + /// Returns whether this set contains all longhands in the specified set. + #[inline] + pub fn contains_all_longhands(&self, longhands: &LonghandIdSet) -> bool { + self.longhands.contains_all(longhands) + } + + /// Returns whether this set contains all properties in the specified set. + #[inline] + pub fn contains_all(&self, properties: &PropertyDeclarationIdSet) -> bool { + if !self.longhands.contains_all(&properties.longhands) { + return false; + } + if properties.custom.len() > self.custom.len() { + return false; + } + properties + .custom + .iter() + .all(|item| self.custom.contains(item)) + } + + /// Iterate over the current property declaration id set. + pub fn iter(&self) -> PropertyDeclarationIdSetIterator { + PropertyDeclarationIdSetIterator { + longhands: self.longhands.iter(), + custom: self.custom.iter(), + } + } +} + +/// An iterator over a set of longhand ids. +pub struct PropertyDeclarationIdSetIterator<'a> { + longhands: LonghandIdSetIterator<'a>, + custom: std::collections::hash_set::Iter<'a, custom_properties::Name>, +} + +impl<'a> Iterator for PropertyDeclarationIdSetIterator<'a> { + type Item = PropertyDeclarationId<'a>; + + fn next(&mut self) -> Option<Self::Item> { + // LonghandIdSetIterator's implementation always returns None + // after it did it once, so the code below will then continue + // to iterate over the custom properties. + match self.longhands.next() { + Some(id) => Some(PropertyDeclarationId::Longhand(id)), + None => match self.custom.next() { + Some(a) => Some(PropertyDeclarationId::Custom(a)), + None => None, + }, + } + } +} + +/// Overridden declarations are skipped. +#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] +#[derive(Clone, ToShmem, Default)] +pub struct PropertyDeclarationBlock { + /// The group of declarations, along with their importance. + /// + /// Only deduplicated declarations appear here. + declarations: ThinVec<PropertyDeclaration>, + + /// The "important" flag for each declaration in `declarations`. + declarations_importance: SmallBitVec, + + /// The set of properties that are present in the block. + property_ids: PropertyDeclarationIdSet, +} + +/// Iterator over `(PropertyDeclaration, Importance)` pairs. +pub struct DeclarationImportanceIterator<'a> { + iter: Zip<Iter<'a, PropertyDeclaration>, smallbitvec::Iter<'a>>, +} + +impl<'a> Default for DeclarationImportanceIterator<'a> { + fn default() -> Self { + Self { + iter: [].iter().zip(smallbitvec::Iter::default()), + } + } +} + +impl<'a> DeclarationImportanceIterator<'a> { + /// Constructor. + fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self { + DeclarationImportanceIterator { + iter: declarations.iter().zip(important.iter()), + } + } +} + +impl<'a> Iterator for DeclarationImportanceIterator<'a> { + type Item = (&'a PropertyDeclaration, Importance); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + self.iter.next().map(|(decl, important)| { + ( + decl, + if important { + Importance::Important + } else { + Importance::Normal + }, + ) + }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option<usize>) { + self.iter.size_hint() + } +} + +impl<'a> DoubleEndedIterator for DeclarationImportanceIterator<'a> { + #[inline(always)] + fn next_back(&mut self) -> Option<Self::Item> { + self.iter.next_back().map(|(decl, important)| { + ( + decl, + if important { + Importance::Important + } else { + Importance::Normal + }, + ) + }) + } +} + +/// Iterator for AnimationValue to be generated from PropertyDeclarationBlock. +pub struct AnimationValueIterator<'a, 'cx, 'cx_a: 'cx> { + iter: DeclarationImportanceIterator<'a>, + context: &'cx mut Context<'cx_a>, + default_values: &'a ComputedValues, +} + +impl<'a, 'cx, 'cx_a: 'cx> AnimationValueIterator<'a, 'cx, 'cx_a> { + fn new( + declarations: &'a PropertyDeclarationBlock, + context: &'cx mut Context<'cx_a>, + default_values: &'a ComputedValues, + ) -> AnimationValueIterator<'a, 'cx, 'cx_a> { + AnimationValueIterator { + iter: declarations.declaration_importance_iter(), + context, + default_values, + } + } +} + +impl<'a, 'cx, 'cx_a: 'cx> Iterator for AnimationValueIterator<'a, 'cx, 'cx_a> { + type Item = AnimationValue; + #[inline] + fn next(&mut self) -> Option<Self::Item> { + loop { + let (decl, importance) = self.iter.next()?; + + if importance.important() { + continue; + } + + let animation = + AnimationValue::from_declaration(decl, &mut self.context, self.default_values); + + if let Some(anim) = animation { + return Some(anim); + } + } + } +} + +impl fmt::Debug for PropertyDeclarationBlock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.declarations.fmt(f) + } +} + +impl PropertyDeclarationBlock { + /// Returns the number of declarations in the block. + #[inline] + pub fn len(&self) -> usize { + self.declarations.len() + } + + /// Returns whether the block is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.declarations.is_empty() + } + + /// Create an empty block + #[inline] + pub fn new() -> Self { + PropertyDeclarationBlock { + declarations: ThinVec::new(), + declarations_importance: SmallBitVec::new(), + property_ids: PropertyDeclarationIdSet::default(), + } + } + + /// Create a block with a single declaration + pub fn with_one(declaration: PropertyDeclaration, importance: Importance) -> Self { + let mut property_ids = PropertyDeclarationIdSet::default(); + property_ids.insert(declaration.id()); + let mut declarations = ThinVec::with_capacity(1); + declarations.push(declaration); + PropertyDeclarationBlock { + declarations, + declarations_importance: SmallBitVec::from_elem(1, importance.important()), + property_ids, + } + } + + /// The declarations in this block + #[inline] + pub fn declarations(&self) -> &[PropertyDeclaration] { + &self.declarations + } + + /// The `important` flags for declarations in this block + #[inline] + pub fn declarations_importance(&self) -> &SmallBitVec { + &self.declarations_importance + } + + /// Iterate over `(PropertyDeclaration, Importance)` pairs + #[inline] + pub fn declaration_importance_iter(&self) -> DeclarationImportanceIterator { + DeclarationImportanceIterator::new(&self.declarations, &self.declarations_importance) + } + + /// Iterate over `PropertyDeclaration` for Importance::Normal + #[inline] + pub fn normal_declaration_iter<'a>( + &'a self, + ) -> impl DoubleEndedIterator<Item = &'a PropertyDeclaration> { + self.declaration_importance_iter() + .filter(|(_, importance)| !importance.important()) + .map(|(declaration, _)| declaration) + } + + /// Return an iterator of (AnimatableLonghand, AnimationValue). + #[inline] + pub fn to_animation_value_iter<'a, 'cx, 'cx_a: 'cx>( + &'a self, + context: &'cx mut Context<'cx_a>, + default_values: &'a ComputedValues, + ) -> AnimationValueIterator<'a, 'cx, 'cx_a> { + AnimationValueIterator::new(self, context, default_values) + } + + /// Returns whether this block contains any declaration with `!important`. + /// + /// This is based on the `declarations_importance` bit-vector, + /// which should be maintained whenever `declarations` is changed. + #[inline] + pub fn any_important(&self) -> bool { + !self.declarations_importance.all_false() + } + + /// Returns whether this block contains any declaration without `!important`. + /// + /// This is based on the `declarations_importance` bit-vector, + /// which should be maintained whenever `declarations` is changed. + #[inline] + pub fn any_normal(&self) -> bool { + !self.declarations_importance.all_true() + } + + /// Returns a `PropertyDeclarationIdSet` representing the properties that are changed in + /// this block. + #[inline] + pub fn property_ids(&self) -> &PropertyDeclarationIdSet { + &self.property_ids + } + + /// Returns whether this block contains a declaration of a given property id. + #[inline] + pub fn contains(&self, id: PropertyDeclarationId) -> bool { + self.property_ids.contains(id) + } + + /// Returns whether this block contains any reset longhand. + #[inline] + pub fn contains_any_reset(&self) -> bool { + self.property_ids.contains_any_reset() + } + + /// Get a declaration for a given property. + /// + /// NOTE: This is linear time in the case of custom properties or in the + /// case the longhand is actually in the declaration block. + #[inline] + pub fn get( + &self, + property: PropertyDeclarationId, + ) -> Option<(&PropertyDeclaration, Importance)> { + if !self.contains(property) { + return None; + } + self.declaration_importance_iter() + .find(|(declaration, _)| declaration.id() == property) + } + + /// Tries to serialize a given shorthand from the declarations in this + /// block. + pub fn shorthand_to_css( + &self, + shorthand: ShorthandId, + dest: &mut CssStringWriter, + ) -> fmt::Result { + // Step 1.2.1 of + // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue + let mut list = SmallVec::<[&_; 10]>::new(); + let mut important_count = 0; + + // Step 1.2.2 + for longhand in shorthand.longhands() { + // Step 1.2.2.1 + let declaration = self.get(PropertyDeclarationId::Longhand(longhand)); + + // Step 1.2.2.2 & 1.2.2.3 + match declaration { + Some((declaration, importance)) => { + list.push(declaration); + if importance.important() { + important_count += 1; + } + }, + None => return Ok(()), + } + } + + // If there is one or more longhand with important, and one or more + // without important, we don't serialize it as a shorthand. + if important_count > 0 && important_count != list.len() { + return Ok(()); + } + + // Step 1.2.3 + // We don't print !important when serializing individual properties, + // so we treat this as a normal-importance property + match shorthand.get_shorthand_appendable_value(&list) { + Some(appendable_value) => append_declaration_value(dest, appendable_value), + None => return Ok(()), + } + } + + /// Find the value of the given property in this block and serialize it + /// + /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue> + pub fn property_value_to_css( + &self, + property: &PropertyId, + dest: &mut CssStringWriter, + ) -> fmt::Result { + // Step 1.1: done when parsing a string to PropertyId + + // Step 1.2 + let longhand_or_custom = match property.as_shorthand() { + Ok(shorthand) => return self.shorthand_to_css(shorthand, dest), + Err(longhand_or_custom) => longhand_or_custom, + }; + + if let Some((value, _importance)) = self.get(longhand_or_custom) { + // Step 2 + value.to_css(dest) + } else { + // Step 3 + Ok(()) + } + } + + /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority> + pub fn property_priority(&self, property: &PropertyId) -> Importance { + // Step 1: done when parsing a string to PropertyId + + // Step 2 + match property.as_shorthand() { + Ok(shorthand) => { + // Step 2.1 & 2.2 & 2.3 + if shorthand.longhands().all(|l| { + self.get(PropertyDeclarationId::Longhand(l)) + .map_or(false, |(_, importance)| importance.important()) + }) { + Importance::Important + } else { + Importance::Normal + } + }, + Err(longhand_or_custom) => { + // Step 3 + self.get(longhand_or_custom) + .map_or(Importance::Normal, |(_, importance)| importance) + }, + } + } + + /// Adds or overrides the declaration for a given property in this block. + /// + /// See the documentation of `push` to see what impact `source` has when the + /// property is already there. + pub fn extend( + &mut self, + mut drain: SourcePropertyDeclarationDrain, + importance: Importance, + ) -> bool { + let all_shorthand_len = match drain.all_shorthand { + AllShorthand::NotSet => 0, + AllShorthand::CSSWideKeyword(_) | AllShorthand::WithVariables(_) => { + property_counts::ALL_SHORTHAND_EXPANDED + }, + }; + let push_calls_count = drain.declarations.len() + all_shorthand_len; + + // With deduplication the actual length increase may be less than this. + self.declarations.reserve(push_calls_count); + + let mut changed = false; + for decl in &mut drain.declarations { + changed |= self.push(decl, importance); + } + drain + .all_shorthand + .declarations() + .fold(changed, |changed, decl| { + changed | self.push(decl, importance) + }) + } + + /// Adds or overrides the declaration for a given property in this block. + /// + /// Returns whether the declaration has changed. + /// + /// This is only used for parsing and internal use. + pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) -> bool { + let id = declaration.id(); + if !self.property_ids.insert(id) { + let mut index_to_remove = None; + for (i, slot) in self.declarations.iter_mut().enumerate() { + if slot.id() != id { + continue; + } + + let important = self.declarations_importance[i]; + + // For declarations from parsing, non-important declarations + // shouldn't override existing important one. + if important && !importance.important() { + return false; + } + + index_to_remove = Some(i); + break; + } + + if let Some(index) = index_to_remove { + self.declarations.remove(index); + self.declarations_importance.remove(index); + self.declarations.push(declaration); + self.declarations_importance.push(importance.important()); + return true; + } + } + + self.declarations.push(declaration); + self.declarations_importance.push(importance.important()); + true + } + + /// Prepares updating this declaration block with the given + /// `SourcePropertyDeclaration` and importance, and returns whether + /// there is something to update. + pub fn prepare_for_update( + &self, + source_declarations: &SourcePropertyDeclaration, + importance: Importance, + updates: &mut SourcePropertyDeclarationUpdate, + ) -> bool { + debug_assert!(updates.updates.is_empty()); + // Check whether we are updating for an all shorthand change. + if !matches!(source_declarations.all_shorthand, AllShorthand::NotSet) { + debug_assert!(source_declarations.declarations.is_empty()); + return source_declarations + .all_shorthand + .declarations() + .any(|decl| { + !self.contains(decl.id()) || + self.declarations + .iter() + .enumerate() + .find(|&(_, ref d)| d.id() == decl.id()) + .map_or(true, |(i, d)| { + let important = self.declarations_importance[i]; + *d != decl || important != importance.important() + }) + }); + } + // Fill `updates` with update information. + let mut any_update = false; + let new_count = &mut updates.new_count; + let any_removal = &mut updates.any_removal; + let updates = &mut updates.updates; + updates.extend( + source_declarations + .declarations + .iter() + .map(|declaration| { + if !self.contains(declaration.id()) { + return DeclarationUpdate::Append; + } + let longhand_id = declaration.id().as_longhand(); + if let Some(longhand_id) = longhand_id { + if let Some(logical_group) = longhand_id.logical_group() { + let mut needs_append = false; + for (pos, decl) in self.declarations.iter().enumerate().rev() { + let id = match decl.id().as_longhand() { + Some(id) => id, + None => continue, + }; + if id == longhand_id { + if needs_append { + return DeclarationUpdate::AppendAndRemove { pos }; + } + let important = self.declarations_importance[pos]; + if decl == declaration && important == importance.important() { + return DeclarationUpdate::None; + } + return DeclarationUpdate::UpdateInPlace { pos }; + } + if !needs_append && + id.logical_group() == Some(logical_group) && + id.is_logical() != longhand_id.is_logical() + { + needs_append = true; + } + } + unreachable!("Longhand should be found in loop above"); + } + } + self.declarations + .iter() + .enumerate() + .find(|&(_, ref decl)| decl.id() == declaration.id()) + .map_or(DeclarationUpdate::Append, |(pos, decl)| { + let important = self.declarations_importance[pos]; + if decl == declaration && important == importance.important() { + DeclarationUpdate::None + } else { + DeclarationUpdate::UpdateInPlace { pos } + } + }) + }) + .inspect(|update| { + if matches!(update, DeclarationUpdate::None) { + return; + } + any_update = true; + match update { + DeclarationUpdate::Append => { + *new_count += 1; + }, + DeclarationUpdate::AppendAndRemove { .. } => { + *any_removal = true; + }, + _ => {}, + } + }), + ); + any_update + } + + /// Update this declaration block with the given data. + pub fn update( + &mut self, + drain: SourcePropertyDeclarationDrain, + importance: Importance, + updates: &mut SourcePropertyDeclarationUpdate, + ) { + let important = importance.important(); + if !matches!(drain.all_shorthand, AllShorthand::NotSet) { + debug_assert!(updates.updates.is_empty()); + for decl in drain.all_shorthand.declarations() { + let id = decl.id(); + if self.property_ids.insert(id) { + self.declarations.push(decl); + self.declarations_importance.push(important); + } else { + let (idx, slot) = self + .declarations + .iter_mut() + .enumerate() + .find(|&(_, ref d)| d.id() == decl.id()) + .unwrap(); + *slot = decl; + self.declarations_importance.set(idx, important); + } + } + return; + } + + self.declarations.reserve(updates.new_count); + if updates.any_removal { + // Prepare for removal and fixing update positions. + struct UpdateOrRemoval<'a> { + item: &'a mut DeclarationUpdate, + pos: usize, + remove: bool, + } + let mut updates_and_removals: SubpropertiesVec<UpdateOrRemoval> = updates + .updates + .iter_mut() + .filter_map(|item| { + let (pos, remove) = match *item { + DeclarationUpdate::UpdateInPlace { pos } => (pos, false), + DeclarationUpdate::AppendAndRemove { pos } => (pos, true), + _ => return None, + }; + Some(UpdateOrRemoval { item, pos, remove }) + }) + .collect(); + // Execute removals. It's important to do it in reverse index order, + // so that removing doesn't invalidate following positions. + updates_and_removals.sort_unstable_by_key(|update| update.pos); + updates_and_removals + .iter() + .rev() + .filter(|update| update.remove) + .for_each(|update| { + self.declarations.remove(update.pos); + self.declarations_importance.remove(update.pos); + }); + // Fixup pos field for updates. + let mut removed_count = 0; + for update in updates_and_removals.iter_mut() { + if update.remove { + removed_count += 1; + continue; + } + debug_assert_eq!( + *update.item, + DeclarationUpdate::UpdateInPlace { pos: update.pos } + ); + *update.item = DeclarationUpdate::UpdateInPlace { + pos: update.pos - removed_count, + }; + } + } + // Execute updates and appends. + for (decl, update) in drain.declarations.zip_eq(updates.updates.iter()) { + match *update { + DeclarationUpdate::None => {}, + DeclarationUpdate::Append | DeclarationUpdate::AppendAndRemove { .. } => { + self.property_ids.insert(decl.id()); + self.declarations.push(decl); + self.declarations_importance.push(important); + }, + DeclarationUpdate::UpdateInPlace { pos } => { + self.declarations[pos] = decl; + self.declarations_importance.set(pos, important); + }, + } + } + updates.updates.clear(); + } + + /// Returns the first declaration that would be removed by removing + /// `property`. + #[inline] + pub fn first_declaration_to_remove(&self, property: &PropertyId) -> Option<usize> { + if let Err(longhand_or_custom) = property.as_shorthand() { + if !self.contains(longhand_or_custom) { + return None; + } + } + + self.declarations + .iter() + .position(|declaration| declaration.id().is_or_is_longhand_of(property)) + } + + /// Removes a given declaration at a given index. + #[inline] + fn remove_declaration_at(&mut self, i: usize) { + self.property_ids.remove(self.declarations[i].id()); + self.declarations_importance.remove(i); + self.declarations.remove(i); + } + + /// Clears all the declarations from this block. + #[inline] + pub fn clear(&mut self) { + self.declarations_importance.clear(); + self.declarations.clear(); + self.property_ids.clear(); + } + + /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty> + /// + /// `first_declaration` needs to be the result of + /// `first_declaration_to_remove`. + #[inline] + pub fn remove_property(&mut self, property: &PropertyId, first_declaration: usize) { + debug_assert_eq!( + Some(first_declaration), + self.first_declaration_to_remove(property) + ); + debug_assert!(self.declarations[first_declaration] + .id() + .is_or_is_longhand_of(property)); + + self.remove_declaration_at(first_declaration); + + let shorthand = match property.as_shorthand() { + Ok(s) => s, + Err(_longhand_or_custom) => return, + }; + + let mut i = first_declaration; + let mut len = self.len(); + while i < len { + if !self.declarations[i].id().is_longhand_of(shorthand) { + i += 1; + continue; + } + + self.remove_declaration_at(i); + len -= 1; + } + } + + /// Take a declaration block known to contain a single property and serialize it. + pub fn single_value_to_css( + &self, + property: &PropertyId, + dest: &mut CssStringWriter, + computed_values: Option<&ComputedValues>, + stylist: &Stylist, + ) -> fmt::Result { + if let Ok(shorthand) = property.as_shorthand() { + return self.shorthand_to_css(shorthand, dest); + } + + // FIXME(emilio): Should this assert, or assert that the declaration is + // the property we expect? + let declaration = match self.declarations.get(0) { + Some(d) => d, + None => return Err(fmt::Error), + }; + + let mut rule_cache_conditions = RuleCacheConditions::default(); + let mut context = Context::new( + StyleBuilder::new( + stylist.device(), + Some(stylist), + computed_values, + None, + None, + false, + ), + stylist.quirks_mode(), + &mut rule_cache_conditions, + ContainerSizeQuery::none(), + ); + + if let Some(cv) = computed_values { + context.builder.custom_properties = cv.custom_properties.clone(); + }; + + match (declaration, computed_values) { + // If we have a longhand declaration with variables, those variables + // will be stored as unparsed values. + // + // As a temporary measure to produce sensible results in Gecko's + // getKeyframes() implementation for CSS animations, if + // |computed_values| is supplied, we use it to expand such variable + // declarations. This will be fixed properly in Gecko bug 1391537. + (&PropertyDeclaration::WithVariables(ref declaration), Some(_)) => declaration + .value + .substitute_variables( + declaration.id, + &context.builder.custom_properties, + stylist, + &context, + &mut Default::default(), + ) + .to_css(dest), + (ref d, _) => d.to_css(dest), + } + } + + /// Convert AnimationValueMap to PropertyDeclarationBlock. + pub fn from_animation_value_map(animation_value_map: &AnimationValueMap) -> Self { + let len = animation_value_map.len(); + let mut declarations = ThinVec::with_capacity(len); + let mut property_ids = PropertyDeclarationIdSet::default(); + + for (property, animation_value) in animation_value_map.iter() { + property_ids.insert(property.as_borrowed()); + declarations.push(animation_value.uncompute()); + } + + PropertyDeclarationBlock { + declarations, + property_ids, + declarations_importance: SmallBitVec::from_elem(len, false), + } + } + + /// Returns true if the declaration block has a CSSWideKeyword for the given + /// property. + pub fn has_css_wide_keyword(&self, property: &PropertyId) -> bool { + if let Err(longhand_or_custom) = property.as_shorthand() { + if !self.property_ids.contains(longhand_or_custom) { + return false; + } + } + self.declarations.iter().any(|decl| { + decl.id().is_or_is_longhand_of(property) && decl.get_css_wide_keyword().is_some() + }) + } + + /// Like the method on ToCss, but without the type parameter to avoid + /// accidentally monomorphizing this large function multiple times for + /// different writers. + /// + /// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block + pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { + let mut is_first_serialization = true; // trailing serializations should have a prepended space + + // Step 1 -> dest = result list + + // Step 2 + // + // NOTE(emilio): We reuse this set for both longhands and shorthands + // with subtly different meaning. For longhands, only longhands that + // have actually been serialized (either by themselves, or as part of a + // shorthand) appear here. For shorthands, all the shorthands that we've + // attempted to serialize appear here. + let mut already_serialized = NonCustomPropertyIdSet::new(); + + // Step 3 + 'declaration_loop: for (declaration, importance) in self.declaration_importance_iter() { + // Step 3.1 + let property = declaration.id(); + let longhand_id = match property { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => { + // Given the invariants that there are no duplicate + // properties in a declaration block, and that custom + // properties can't be part of a shorthand, we can just care + // about them here. + append_serialization( + dest, + &property, + AppendableValue::Declaration(declaration), + importance, + &mut is_first_serialization, + )?; + continue; + }, + }; + + // Step 3.2 + if already_serialized.contains(longhand_id.into()) { + continue; + } + + // Steps 3.3 & 3.4 + for shorthand in longhand_id.shorthands() { + // We already attempted to serialize this shorthand before. + if already_serialized.contains(shorthand.into()) { + continue; + } + already_serialized.insert(shorthand.into()); + + if shorthand.is_legacy_shorthand() { + continue; + } + + // Step 3.3.1: + // Let longhands be an array consisting of all CSS + // declarations in declaration block’s declarations that + // that are not in already serialized and have a property + // name that maps to one of the shorthand properties in + // shorthands. + let longhands = { + // TODO(emilio): This could just index in an array if we + // remove pref-controlled longhands. + let mut ids = LonghandIdSet::new(); + for longhand in shorthand.longhands() { + ids.insert(longhand); + } + ids + }; + + // Step 3.4.2 + // If all properties that map to shorthand are not present + // in longhands, continue with the steps labeled shorthand + // loop. + if !self.property_ids.contains_all_longhands(&longhands) { + continue; + } + + // Step 3.4.3: + // Let current longhands be an empty array. + let mut current_longhands = SmallVec::<[&_; 10]>::new(); + let mut logical_groups = LogicalGroupSet::new(); + let mut saw_one = false; + let mut logical_mismatch = false; + let mut seen = LonghandIdSet::new(); + let mut important_count = 0; + + // Step 3.4.4: + // Append all CSS declarations in longhands that have a + // property name that maps to shorthand to current longhands. + for (declaration, importance) in self.declaration_importance_iter() { + let longhand = match declaration.id() { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => continue, + }; + + if longhands.contains(longhand) { + saw_one = true; + if importance.important() { + important_count += 1; + } + current_longhands.push(declaration); + if shorthand != ShorthandId::All { + // All is special because it contains both physical + // and logical longhands. + if let Some(g) = longhand.logical_group() { + logical_groups.insert(g); + } + seen.insert(longhand); + if seen == longhands { + break; + } + } + } else if saw_one { + if let Some(g) = longhand.logical_group() { + if logical_groups.contains(g) { + logical_mismatch = true; + break; + } + } + } + } + + // 3.4.5: + // If there is one or more CSS declarations in current + // longhands have their important flag set and one or more + // with it unset, continue with the steps labeled shorthand + // loop. + let is_important = important_count > 0; + if is_important && important_count != current_longhands.len() { + continue; + } + + // 3.4.6: + // If there’s any declaration in declaration block in between + // the first and the last longhand in current longhands which + // belongs to the same logical property group, but has a + // different mapping logic as any of the longhands in current + // longhands, and is not in current longhands, continue with + // the steps labeled shorthand loop. + if logical_mismatch { + continue; + } + + let importance = if is_important { + Importance::Important + } else { + Importance::Normal + }; + + // 3.4.7: + // Let value be the result of invoking serialize a CSS value + // of current longhands. + let appendable_value = + match shorthand.get_shorthand_appendable_value(¤t_longhands) { + None => continue, + Some(appendable_value) => appendable_value, + }; + + // We avoid re-serializing if we're already an + // AppendableValue::Css. + let mut v = CssString::new(); + let value = match appendable_value { + AppendableValue::Css(css) => { + debug_assert!(!css.is_empty()); + appendable_value + }, + other => { + append_declaration_value(&mut v, other)?; + + // 3.4.8: + // If value is the empty string, continue with the + // steps labeled shorthand loop. + if v.is_empty() { + continue; + } + + AppendableValue::Css({ + // Safety: serialization only generates valid utf-8. + #[cfg(feature = "gecko")] + unsafe { + v.as_str_unchecked() + } + #[cfg(feature = "servo")] + &v + }) + }, + }; + + // 3.4.9: + // Let serialized declaration be the result of invoking + // serialize a CSS declaration with property name shorthand, + // value value, and the important flag set if the CSS + // declarations in current longhands have their important + // flag set. + // + // 3.4.10: + // Append serialized declaration to list. + append_serialization( + dest, + &shorthand, + value, + importance, + &mut is_first_serialization, + )?; + + // 3.4.11: + // Append the property names of all items of current + // longhands to already serialized. + for current_longhand in ¤t_longhands { + let longhand_id = match current_longhand.id() { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => unreachable!(), + }; + + // Substep 9 + already_serialized.insert(longhand_id.into()); + } + + // 3.4.12: + // Continue with the steps labeled declaration loop. + continue 'declaration_loop; + } + + // Steps 3.5, 3.6 & 3.7: + // Let value be the result of invoking serialize a CSS value of + // declaration. + // + // Let serialized declaration be the result of invoking + // serialize a CSS declaration with property name property, + // value value, and the important flag set if declaration has + // its important flag set. + // + // Append serialized declaration to list. + append_serialization( + dest, + &property, + AppendableValue::Declaration(declaration), + importance, + &mut is_first_serialization, + )?; + + // Step 3.8: + // Append property to already serialized. + already_serialized.insert(longhand_id.into()); + } + + // Step 4 + Ok(()) + } +} + +/// A convenient enum to represent different kinds of stuff that can represent a +/// _value_ in the serialization of a property declaration. +pub enum AppendableValue<'a, 'b: 'a> { + /// A given declaration, of which we'll serialize just the value. + Declaration(&'a PropertyDeclaration), + /// A set of declarations for a given shorthand. + /// + /// FIXME: This needs more docs, where are the shorthands expanded? We print + /// the property name before-hand, don't we? + DeclarationsForShorthand(ShorthandId, &'a [&'b PropertyDeclaration]), + /// A raw CSS string, coming for example from a property with CSS variables, + /// or when storing a serialized shorthand value before appending directly. + Css(&'a str), +} + +/// Potentially appends whitespace after the first (property: value;) pair. +fn handle_first_serialization<W>(dest: &mut W, is_first_serialization: &mut bool) -> fmt::Result +where + W: Write, +{ + if !*is_first_serialization { + dest.write_char(' ') + } else { + *is_first_serialization = false; + Ok(()) + } +} + +/// Append a given kind of appendable value to a serialization. +pub fn append_declaration_value<'a, 'b: 'a>( + dest: &mut CssStringWriter, + appendable_value: AppendableValue<'a, 'b>, +) -> fmt::Result { + match appendable_value { + AppendableValue::Css(css) => dest.write_str(css), + AppendableValue::Declaration(decl) => decl.to_css(dest), + AppendableValue::DeclarationsForShorthand(shorthand, decls) => { + shorthand.longhands_to_css(decls, dest) + }, + } +} + +/// Append a given property and value pair to a serialization. +pub fn append_serialization<'a, 'b: 'a, N>( + dest: &mut CssStringWriter, + property_name: &N, + appendable_value: AppendableValue<'a, 'b>, + importance: Importance, + is_first_serialization: &mut bool, +) -> fmt::Result +where + N: ToCss, +{ + handle_first_serialization(dest, is_first_serialization)?; + + property_name.to_css(&mut CssWriter::new(dest))?; + dest.write_str(": ")?; + + append_declaration_value(dest, appendable_value)?; + + if importance.important() { + dest.write_str(" !important")?; + } + + dest.write_char(';') +} + +/// A helper to parse the style attribute of an element, in order for this to be +/// shared between Servo and Gecko. +/// +/// Inline because we call this cross-crate. +#[inline] +pub fn parse_style_attribute( + input: &str, + url_data: &UrlExtraData, + error_reporter: Option<&dyn ParseErrorReporter>, + quirks_mode: QuirksMode, + rule_type: CssRuleType, +) -> PropertyDeclarationBlock { + let context = ParserContext::new( + Origin::Author, + url_data, + Some(rule_type), + ParsingMode::DEFAULT, + quirks_mode, + /* namespaces = */ Default::default(), + error_reporter, + None, + ); + + let mut input = ParserInput::new(input); + parse_property_declaration_list(&context, &mut Parser::new(&mut input), &[]) +} + +/// Parse a given property declaration. Can result in multiple +/// `PropertyDeclaration`s when expanding a shorthand, for example. +/// +/// This does not attempt to parse !important at all. +#[inline] +pub fn parse_one_declaration_into( + declarations: &mut SourcePropertyDeclaration, + id: PropertyId, + input: &str, + origin: Origin, + url_data: &UrlExtraData, + error_reporter: Option<&dyn ParseErrorReporter>, + parsing_mode: ParsingMode, + quirks_mode: QuirksMode, + rule_type: CssRuleType, +) -> Result<(), ()> { + let context = ParserContext::new( + origin, + url_data, + Some(rule_type), + parsing_mode, + quirks_mode, + /* namespaces = */ Default::default(), + error_reporter, + None, + ); + + let property_id_for_error_reporting = if context.error_reporting_enabled() { + Some(id.clone()) + } else { + None + }; + + let mut input = ParserInput::new(input); + let mut parser = Parser::new(&mut input); + let start_position = parser.position(); + parser + .parse_entirely(|parser| { + PropertyDeclaration::parse_into(declarations, id, &context, parser) + }) + .map_err(|err| { + if context.error_reporting_enabled() { + report_one_css_error( + &context, + None, + &[], + err, + parser.slice_from(start_position), + property_id_for_error_reporting, + ) + } + }) +} + +/// A struct to parse property declarations. +struct PropertyDeclarationParser<'a, 'b: 'a, 'i> { + context: &'a ParserContext<'b>, + state: &'a mut DeclarationParserState<'i>, +} + +/// The state needed to parse a declaration block. +/// +/// It stores declarations in output_block. +#[derive(Default)] +pub struct DeclarationParserState<'i> { + /// The output block where results are stored. + output_block: PropertyDeclarationBlock, + /// Declarations from the last declaration parsed. (note that a shorthand might expand to + /// multiple declarations). + declarations: SourcePropertyDeclaration, + /// The importance from the last declaration parsed. + importance: Importance, + /// A list of errors that have happened so far. Not all of them might be reported. + errors: SmallParseErrorVec<'i>, + /// The last parsed property id, if any. + last_parsed_property_id: Option<PropertyId>, +} + +impl<'i> DeclarationParserState<'i> { + /// Returns whether any parsed declarations have been parsed so far. + pub fn has_parsed_declarations(&self) -> bool { + !self.output_block.is_empty() + } + + /// Takes the parsed declarations. + pub fn take_declarations(&mut self) -> PropertyDeclarationBlock { + std::mem::take(&mut self.output_block) + } + + /// Parse a single declaration value. + pub fn parse_value<'t>( + &mut self, + context: &ParserContext, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + let id = match PropertyId::parse(&name, context) { + Ok(id) => id, + Err(..) => { + return Err(input.new_custom_error(StyleParseErrorKind::UnknownProperty(name))); + }, + }; + if context.error_reporting_enabled() { + self.last_parsed_property_id = Some(id.clone()); + } + input.parse_until_before(Delimiter::Bang, |input| { + PropertyDeclaration::parse_into(&mut self.declarations, id, context, input) + })?; + self.importance = match input.try_parse(parse_important) { + Ok(()) => Importance::Important, + Err(_) => Importance::Normal, + }; + // In case there is still unparsed text in the declaration, we should roll back. + input.expect_exhausted()?; + self.output_block + .extend(self.declarations.drain(), self.importance); + // We've successfully parsed a declaration, so forget about + // `last_parsed_property_id`. It'd be wrong to associate any + // following error with this property. + self.last_parsed_property_id = None; + Ok(()) + } + + /// Reports any CSS errors that have ocurred if needed. + #[inline] + pub fn report_errors_if_needed( + &mut self, + context: &ParserContext, + selectors: &[SelectorList<SelectorImpl>], + ) { + if self.errors.is_empty() { + return; + } + self.do_report_css_errors(context, selectors); + } + + #[cold] + fn do_report_css_errors( + &mut self, + context: &ParserContext, + selectors: &[SelectorList<SelectorImpl>], + ) { + for (error, slice, property) in self.errors.drain(..) { + report_one_css_error( + context, + Some(&self.output_block), + selectors, + error, + slice, + property, + ) + } + } + + /// Resets the declaration parser state, and reports the error if needed. + #[inline] + pub fn did_error(&mut self, context: &ParserContext, error: ParseError<'i>, slice: &'i str) { + self.declarations.clear(); + if !context.error_reporting_enabled() { + return; + } + let property = self.last_parsed_property_id.take(); + self.errors.push((error, slice, property)); + } +} + +/// Default methods reject all at rules. +impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> { + type Prelude = (); + type AtRule = (); + type Error = StyleParseErrorKind<'i>; +} + +/// Default methods reject all rules. +impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> { + type Prelude = (); + type QualifiedRule = (); + type Error = StyleParseErrorKind<'i>; +} + +/// Based on NonMozillaVendorIdentifier from Gecko's CSS parser. +fn is_non_mozilla_vendor_identifier(name: &str) -> bool { + (name.starts_with("-") && !name.starts_with("-moz-")) || name.starts_with("_") +} + +impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> { + type Declaration = (); + type Error = StyleParseErrorKind<'i>; + + fn parse_value<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + self.state.parse_value(self.context, name, input) + } +} + +impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> + for PropertyDeclarationParser<'a, 'b, 'i> +{ + fn parse_declarations(&self) -> bool { + true + } + // TODO(emilio): Nesting. + fn parse_qualified(&self) -> bool { + false + } +} + +type SmallParseErrorVec<'i> = SmallVec<[(ParseError<'i>, &'i str, Option<PropertyId>); 2]>; + +fn alias_of_known_property(name: &str) -> Option<PropertyId> { + let mut prefixed = String::with_capacity(name.len() + 5); + prefixed.push_str("-moz-"); + prefixed.push_str(name); + PropertyId::parse_enabled_for_all_content(&prefixed).ok() +} + +#[cold] +fn report_one_css_error<'i>( + context: &ParserContext, + block: Option<&PropertyDeclarationBlock>, + selectors: &[SelectorList<SelectorImpl>], + mut error: ParseError<'i>, + slice: &str, + property: Option<PropertyId>, +) { + debug_assert!(context.error_reporting_enabled()); + + fn all_properties_in_block(block: &PropertyDeclarationBlock, property: &PropertyId) -> bool { + match property.as_shorthand() { + Ok(id) => id + .longhands() + .all(|longhand| block.contains(PropertyDeclarationId::Longhand(longhand))), + Err(longhand_or_custom) => block.contains(longhand_or_custom), + } + } + + if let ParseErrorKind::Custom(StyleParseErrorKind::UnknownProperty(ref name)) = error.kind { + if is_non_mozilla_vendor_identifier(name) { + // If the unrecognized property looks like a vendor-specific property, + // silently ignore it instead of polluting the error output. + return; + } + if let Some(alias) = alias_of_known_property(name) { + // This is an unknown property, but its -moz-* version is known. + // We don't want to report error if the -moz-* version is already + // specified. + if let Some(block) = block { + if all_properties_in_block(block, &alias) { + return; + } + } + } + } + + if let Some(ref property) = property { + if let Some(block) = block { + if all_properties_in_block(block, property) { + return; + } + } + error = match *property { + PropertyId::Custom(ref c) => { + StyleParseErrorKind::new_invalid(format!("--{}", c), error) + }, + _ => StyleParseErrorKind::new_invalid(property.non_custom_id().unwrap().name(), error), + }; + } + + let location = error.location; + let error = ContextualParseError::UnsupportedPropertyDeclaration(slice, error, selectors); + context.log_css_error(location, error); +} + +/// Parse a list of property declarations and return a property declaration +/// block. +pub fn parse_property_declaration_list( + context: &ParserContext, + input: &mut Parser, + selectors: &[SelectorList<SelectorImpl>], +) -> PropertyDeclarationBlock { + let mut state = DeclarationParserState::default(); + let mut parser = PropertyDeclarationParser { + context, + state: &mut state, + }; + let mut iter = RuleBodyParser::new(input, &mut parser); + while let Some(declaration) = iter.next() { + match declaration { + Ok(()) => {}, + Err((error, slice)) => iter.parser.state.did_error(context, error, slice), + } + } + parser.state.report_errors_if_needed(context, selectors); + state.output_block +} diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs new file mode 100644 index 0000000000..f5ae0cade3 --- /dev/null +++ b/servo/components/style/properties/gecko.mako.rs @@ -0,0 +1,1806 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// `data` comes from components/style/properties.mako.rs; see build.rs for more details. + +<%! + from data import to_camel_case, to_camel_case_lower + from data import Keyword +%> +<%namespace name="helpers" file="/helpers.mako.rs" /> + +use crate::Atom; +use app_units::Au; +use crate::computed_value_flags::*; +use crate::custom_properties::ComputedCustomProperties; +use crate::gecko_bindings::bindings; +% for style_struct in data.style_structs: +use crate::gecko_bindings::bindings::Gecko_Construct_Default_${style_struct.gecko_ffi_name}; +use crate::gecko_bindings::bindings::Gecko_CopyConstruct_${style_struct.gecko_ffi_name}; +use crate::gecko_bindings::bindings::Gecko_Destroy_${style_struct.gecko_ffi_name}; +% endfor +use crate::gecko_bindings::bindings::Gecko_CopyCounterStyle; +use crate::gecko_bindings::bindings::Gecko_EnsureImageLayersLength; +use crate::gecko_bindings::bindings::Gecko_nsStyleFont_SetLang; +use crate::gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom; +use crate::gecko_bindings::structs; +use crate::gecko_bindings::structs::mozilla::PseudoStyleType; +use crate::gecko::data::PerDocumentStyleData; +use crate::logical_geometry::WritingMode; +use crate::media_queries::Device; +use crate::properties::longhands; +use crate::rule_tree::StrongRuleNode; +use crate::selector_parser::PseudoElement; +use servo_arc::{Arc, UniqueArc}; +use std::mem::{forget, MaybeUninit, ManuallyDrop}; +use std::{cmp, ops, ptr}; +use crate::values; +use crate::values::computed::{BorderStyle, Percentage, Time, Zoom}; +use crate::values::computed::font::FontSize; +use crate::values::generics::column::ColumnCount; + + +pub mod style_structs { + % for style_struct in data.style_structs: + pub use super::${style_struct.gecko_struct_name} as ${style_struct.name}; + + unsafe impl Send for ${style_struct.name} {} + unsafe impl Sync for ${style_struct.name} {} + % endfor +} + +/// FIXME(emilio): This is completely duplicated with the other properties code. +pub type ComputedValuesInner = structs::ServoComputedData; + +#[repr(C)] +pub struct ComputedValues(structs::mozilla::ComputedStyle); + +impl ComputedValues { + #[inline] + pub (crate) fn as_gecko_computed_style(&self) -> &structs::ComputedStyle { + &self.0 + } + + pub fn new( + pseudo: Option<<&PseudoElement>, + custom_properties: ComputedCustomProperties, + writing_mode: WritingMode, + effective_zoom: Zoom, + flags: ComputedValueFlags, + rules: Option<StrongRuleNode>, + visited_style: Option<Arc<ComputedValues>>, + % for style_struct in data.style_structs: + ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, + % endfor + ) -> Arc<Self> { + ComputedValuesInner::new( + custom_properties, + writing_mode, + effective_zoom, + flags, + rules, + visited_style, + % for style_struct in data.style_structs: + ${style_struct.ident}, + % endfor + ).to_outer(pseudo) + } + + pub fn default_values(doc: &structs::Document) -> Arc<Self> { + ComputedValuesInner::new( + ComputedCustomProperties::default(), + WritingMode::empty(), // FIXME(bz): This seems dubious + Zoom::ONE, + ComputedValueFlags::empty(), + /* rules = */ None, + /* visited_style = */ None, + % for style_struct in data.style_structs: + style_structs::${style_struct.name}::default(doc), + % endfor + ).to_outer(None) + } + + /// Converts the computed values to an Arc<> from a reference. + pub fn to_arc(&self) -> Arc<Self> { + // SAFETY: We're guaranteed to be allocated as an Arc<> since the + // functions above are the only ones that create ComputedValues + // instances in Gecko (and that must be the case since ComputedValues' + // member is private). + unsafe { Arc::from_raw_addrefed(self) } + } + + #[inline] + pub fn is_pseudo_style(&self) -> bool { + self.0.mPseudoType != PseudoStyleType::NotPseudo + } + + #[inline] + pub fn pseudo(&self) -> Option<PseudoElement> { + if !self.is_pseudo_style() { + return None; + } + PseudoElement::from_pseudo_type(self.0.mPseudoType, None) + } + + #[inline] + pub fn is_first_line_style(&self) -> bool { + self.pseudo() == Some(PseudoElement::FirstLine) + } + + /// Returns true if the display property is changed from 'none' to others. + pub fn is_display_property_changed_from_none( + &self, + old_values: Option<<&ComputedValues> + ) -> bool { + use crate::properties::longhands::display::computed_value::T as Display; + + old_values.map_or(false, |old| { + let old_display_style = old.get_box().clone_display(); + let new_display_style = self.get_box().clone_display(); + old_display_style == Display::None && + new_display_style != Display::None + }) + } + +} + +impl Drop for ComputedValues { + fn drop(&mut self) { + // XXX this still relies on the destructor of ComputedValuesInner to run on the rust side, + // that's pretty wild. + unsafe { + bindings::Gecko_ComputedStyle_Destroy(&mut self.0); + } + } +} + +unsafe impl Sync for ComputedValues {} +unsafe impl Send for ComputedValues {} + +impl Clone for ComputedValues { + fn clone(&self) -> Self { + unreachable!() + } +} + +impl Clone for ComputedValuesInner { + fn clone(&self) -> Self { + ComputedValuesInner { + % for style_struct in data.style_structs: + ${style_struct.gecko_name}: Arc::into_raw(unsafe { Arc::from_raw_addrefed(self.${style_struct.name_lower}_ptr()) }) as *const _, + % endfor + custom_properties: self.custom_properties.clone(), + writing_mode: self.writing_mode.clone(), + flags: self.flags.clone(), + effective_zoom: self.effective_zoom, + rules: self.rules.clone(), + visited_style: if self.visited_style.is_null() { + ptr::null() + } else { + Arc::into_raw(unsafe { Arc::from_raw_addrefed(self.visited_style_ptr()) }) as *const _ + }, + } + } +} + + +impl Drop for ComputedValuesInner { + fn drop(&mut self) { + % for style_struct in data.style_structs: + let _ = unsafe { Arc::from_raw(self.${style_struct.name_lower}_ptr()) }; + % endfor + if !self.visited_style.is_null() { + let _ = unsafe { Arc::from_raw(self.visited_style_ptr()) }; + } + } +} + +impl ComputedValuesInner { + pub fn new( + custom_properties: ComputedCustomProperties, + writing_mode: WritingMode, + effective_zoom: Zoom, + flags: ComputedValueFlags, + rules: Option<StrongRuleNode>, + visited_style: Option<Arc<ComputedValues>>, + % for style_struct in data.style_structs: + ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, + % endfor + ) -> Self { + Self { + custom_properties, + writing_mode, + rules, + visited_style: visited_style.map_or(ptr::null(), |p| Arc::into_raw(p)) as *const _, + flags, + effective_zoom, + % for style_struct in data.style_structs: + ${style_struct.gecko_name}: Arc::into_raw(${style_struct.ident}) as *const _, + % endfor + } + } + + fn to_outer(self, pseudo: Option<<&PseudoElement>) -> Arc<ComputedValues> { + let pseudo_ty = match pseudo { + Some(p) => p.pseudo_type(), + None => structs::PseudoStyleType::NotPseudo, + }; + unsafe { + let mut arc = UniqueArc::<ComputedValues>::new_uninit(); + bindings::Gecko_ComputedStyle_Init( + arc.as_mut_ptr() as *mut _, + &self, + pseudo_ty, + ); + // We're simulating move semantics by having C++ do a memcpy and + // then forgetting it on this end. + forget(self); + UniqueArc::assume_init(arc).shareable() + } + } +} + +impl ops::Deref for ComputedValues { + type Target = ComputedValuesInner; + #[inline] + fn deref(&self) -> &ComputedValuesInner { + &self.0.mSource + } +} + +impl ops::DerefMut for ComputedValues { + #[inline] + fn deref_mut(&mut self) -> &mut ComputedValuesInner { + &mut self.0.mSource + } +} + +impl ComputedValuesInner { + /// Returns true if the value of the `content` property would make a + /// pseudo-element not rendered. + #[inline] + pub fn ineffective_content_property(&self) -> bool { + self.get_counters().ineffective_content_property() + } + + #[inline] + fn visited_style_ptr(&self) -> *const ComputedValues { + self.visited_style as *const _ + } + + /// Returns the visited style, if any. + pub fn visited_style(&self) -> Option<<&ComputedValues> { + unsafe { self.visited_style_ptr().as_ref() } + } + + % for style_struct in data.style_structs: + #[inline] + fn ${style_struct.name_lower}_ptr(&self) -> *const style_structs::${style_struct.name} { + // This is sound because the wrapper we create is repr(transparent). + self.${style_struct.gecko_name} as *const _ + } + + #[inline] + pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> { + unsafe { Arc::from_raw_addrefed(self.${style_struct.name_lower}_ptr()) } + } + #[inline] + pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + unsafe { &*self.${style_struct.name_lower}_ptr() } + } + + #[inline] + pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { + unsafe { + let mut arc = Arc::from_raw(self.${style_struct.name_lower}_ptr()); + let ptr = Arc::make_mut(&mut arc) as *mut _; + // Sound for the same reason _ptr() is sound. + self.${style_struct.gecko_name} = Arc::into_raw(arc) as *const _; + &mut *ptr + } + } + % endfor +} + +<%def name="impl_simple_setter(ident, gecko_ffi_name)"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + ${set_gecko_property(gecko_ffi_name, "From::from(v)")} + } +</%def> + +<%def name="impl_simple_clone(ident, gecko_ffi_name)"> + #[allow(non_snake_case)] + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + From::from(self.${gecko_ffi_name}.clone()) + } +</%def> + +<%def name="impl_simple_copy(ident, gecko_ffi_name, *kwargs)"> + #[allow(non_snake_case)] + pub fn copy_${ident}_from(&mut self, other: &Self) { + self.${gecko_ffi_name} = other.${gecko_ffi_name}.clone(); + } + + #[allow(non_snake_case)] + pub fn reset_${ident}(&mut self, other: &Self) { + self.copy_${ident}_from(other) + } +</%def> + +<%! +def get_gecko_property(ffi_name, self_param = "self"): + return "%s.%s" % (self_param, ffi_name) + +def set_gecko_property(ffi_name, expr): + return "self.%s = %s;" % (ffi_name, expr) +%> + +<%def name="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type='u8')"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + use crate::properties::longhands::${ident}::computed_value::T as Keyword; + // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts + let result = match v { + % for value in keyword.values_for('gecko'): + Keyword::${to_camel_case(value)} => + structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)}, + % endfor + }; + ${set_gecko_property(gecko_ffi_name, "result")} + } +</%def> + +<%def name="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type='u8')"> + #[allow(non_snake_case)] + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + use crate::properties::longhands::${ident}::computed_value::T as Keyword; + // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts + + // Some constant macros in the gecko are defined as negative integer(e.g. font-stretch). + // And they are convert to signed integer in Rust bindings. We need to cast then + // as signed type when we have both signed/unsigned integer in order to use them + // as match's arms. + // Also, to use same implementation here we use casted constant if we have only singed values. + % if keyword.gecko_enum_prefix is None: + % for value in keyword.values_for('gecko'): + const ${keyword.casted_constant_name(value, cast_type)} : ${cast_type} = + structs::${keyword.gecko_constant(value)} as ${cast_type}; + % endfor + + match ${get_gecko_property(gecko_ffi_name)} as ${cast_type} { + % for value in keyword.values_for('gecko'): + ${keyword.casted_constant_name(value, cast_type)} => Keyword::${to_camel_case(value)}, + % endfor + % if keyword.gecko_inexhaustive: + _ => panic!("Found unexpected value in style struct for ${ident} property"), + % endif + } + % else: + match ${get_gecko_property(gecko_ffi_name)} { + % for value in keyword.values_for('gecko'): + structs::${keyword.gecko_constant(value)} => Keyword::${to_camel_case(value)}, + % endfor + % if keyword.gecko_inexhaustive: + _ => panic!("Found unexpected value in style struct for ${ident} property"), + % endif + } + % endif + } +</%def> + +<%def name="impl_keyword(ident, gecko_ffi_name, keyword, cast_type='u8', **kwargs)"> +<%call expr="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type, **kwargs)"></%call> +<%call expr="impl_simple_copy(ident, gecko_ffi_name, **kwargs)"></%call> +<%call expr="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type)"></%call> +</%def> + +<%def name="impl_simple(ident, gecko_ffi_name)"> +<%call expr="impl_simple_setter(ident, gecko_ffi_name)"></%call> +<%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call> +<%call expr="impl_simple_clone(ident, gecko_ffi_name)"></%call> +</%def> + +<%def name="impl_border_width(ident, gecko_ffi_name, inherit_from)"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: Au) { + let value = v.0; + self.${inherit_from} = value; + self.${gecko_ffi_name} = value; + } + + #[allow(non_snake_case)] + pub fn copy_${ident}_from(&mut self, other: &Self) { + self.${inherit_from} = other.${inherit_from}; + // NOTE: This is needed to easily handle the `unset` and `initial` + // keywords, which are implemented calling this function. + // + // In practice, this means that we may have an incorrect value here, but + // we'll adjust that properly in the style fixup phase. + // + // FIXME(emilio): We could clean this up a bit special-casing the reset_ + // function below. + self.${gecko_ffi_name} = other.${inherit_from}; + } + + #[allow(non_snake_case)] + pub fn reset_${ident}(&mut self, other: &Self) { + self.copy_${ident}_from(other) + } + + #[allow(non_snake_case)] + pub fn clone_${ident}(&self) -> Au { + Au(self.${gecko_ffi_name}) + } +</%def> + +<%def name="impl_split_style_coord(ident, gecko_ffi_name, index)"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + self.${gecko_ffi_name}.${index} = v; + } + #[allow(non_snake_case)] + pub fn copy_${ident}_from(&mut self, other: &Self) { + self.${gecko_ffi_name}.${index} = + other.${gecko_ffi_name}.${index}.clone(); + } + #[allow(non_snake_case)] + pub fn reset_${ident}(&mut self, other: &Self) { + self.copy_${ident}_from(other) + } + + #[allow(non_snake_case)] + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + self.${gecko_ffi_name}.${index}.clone() + } +</%def> + +<%def name="copy_sides_style_coord(ident)"> + <% gecko_ffi_name = "m" + to_camel_case(ident) %> + #[allow(non_snake_case)] + pub fn copy_${ident}_from(&mut self, other: &Self) { + % for side in SIDES: + self.${gecko_ffi_name}.data_at_mut(${side.index}) + .copy_from(&other.${gecko_ffi_name}.data_at(${side.index})); + % endfor + ${ caller.body() } + } + + #[allow(non_snake_case)] + pub fn reset_${ident}(&mut self, other: &Self) { + self.copy_${ident}_from(other) + } +</%def> + +<%def name="impl_corner_style_coord(ident, gecko_ffi_name, corner)"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + self.${gecko_ffi_name}.${corner} = v; + } + #[allow(non_snake_case)] + pub fn copy_${ident}_from(&mut self, other: &Self) { + self.${gecko_ffi_name}.${corner} = + other.${gecko_ffi_name}.${corner}.clone(); + } + #[allow(non_snake_case)] + pub fn reset_${ident}(&mut self, other: &Self) { + self.copy_${ident}_from(other) + } + #[allow(non_snake_case)] + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + self.${gecko_ffi_name}.${corner}.clone() + } +</%def> + +<%def name="impl_style_struct(style_struct)"> +/// A wrapper for ${style_struct.gecko_ffi_name}, to be able to manually construct / destruct / +/// clone it. +#[repr(transparent)] +pub struct ${style_struct.gecko_struct_name}(ManuallyDrop<structs::${style_struct.gecko_ffi_name}>); + +impl ops::Deref for ${style_struct.gecko_struct_name} { + type Target = structs::${style_struct.gecko_ffi_name}; + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ops::DerefMut for ${style_struct.gecko_struct_name} { + #[inline] + fn deref_mut(&mut self) -> &mut <Self as ops::Deref>::Target { + &mut self.0 + } +} + +impl ${style_struct.gecko_struct_name} { + #[allow(dead_code, unused_variables)] + pub fn default(document: &structs::Document) -> Arc<Self> { +% if style_struct.document_dependent: + unsafe { + let mut result = UniqueArc::<Self>::new_uninit(); + Gecko_Construct_Default_${style_struct.gecko_ffi_name}( + result.as_mut_ptr() as *mut _, + document, + ); + UniqueArc::assume_init(result).shareable() + } +% else: + lazy_static! { + static ref DEFAULT: Arc<${style_struct.gecko_struct_name}> = unsafe { + let mut result = UniqueArc::<${style_struct.gecko_struct_name}>::new_uninit(); + Gecko_Construct_Default_${style_struct.gecko_ffi_name}( + result.as_mut_ptr() as *mut _, + std::ptr::null(), + ); + let arc = UniqueArc::assume_init(result).shareable(); + arc.mark_as_intentionally_leaked(); + arc + }; + }; + DEFAULT.clone() +% endif + } +} + +impl Drop for ${style_struct.gecko_struct_name} { + fn drop(&mut self) { + unsafe { + Gecko_Destroy_${style_struct.gecko_ffi_name}(&mut **self); + } + } +} +impl Clone for ${style_struct.gecko_struct_name} { + fn clone(&self) -> Self { + unsafe { + let mut result = MaybeUninit::<Self>::uninit(); + // FIXME(bug 1595895): Zero the memory to keep valgrind happy, but + // these looks like Valgrind false-positives at a quick glance. + ptr::write_bytes::<Self>(result.as_mut_ptr(), 0, 1); + Gecko_CopyConstruct_${style_struct.gecko_ffi_name}(result.as_mut_ptr() as *mut _, &**self); + result.assume_init() + } + } +} +</%def> + +<%def name="impl_simple_type_with_conversion(ident, gecko_ffi_name)"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + self.${gecko_ffi_name} = From::from(v) + } + + <% impl_simple_copy(ident, gecko_ffi_name) %> + + #[allow(non_snake_case)] + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + From::from(self.${gecko_ffi_name}) + } +</%def> + +<%def name="impl_font_settings(ident, gecko_type, tag_type, value_type, gecko_value_type)"> + <% + gecko_ffi_name = to_camel_case_lower(ident) + %> + + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + let iter = v.0.iter().map(|other| structs::${gecko_type} { + mTag: other.tag.0, + mValue: other.value as ${gecko_value_type}, + }); + self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter); + } + + pub fn copy_${ident}_from(&mut self, other: &Self) { + let iter = other.mFont.${gecko_ffi_name}.iter().map(|s| *s); + self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter); + } + + pub fn reset_${ident}(&mut self, other: &Self) { + self.copy_${ident}_from(other) + } + + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + use crate::values::generics::font::{FontSettings, FontTag, ${tag_type}}; + + FontSettings( + self.mFont.${gecko_ffi_name}.iter().map(|gecko_font_setting| { + ${tag_type} { + tag: FontTag(gecko_font_setting.mTag), + value: gecko_font_setting.mValue as ${value_type}, + } + }).collect::<Vec<_>>().into_boxed_slice() + ) + } +</%def> + +<%def name="impl_trait(style_struct_name, skip_longhands='')"> +<% + style_struct = next(x for x in data.style_structs if x.name == style_struct_name) + longhands = [x for x in style_struct.longhands + if not (skip_longhands == "*" or x.name in skip_longhands.split())] + + def longhand_method(longhand): + args = dict(ident=longhand.ident, gecko_ffi_name=longhand.gecko_ffi_name) + + if longhand.logical: + return + # get the method and pass additional keyword or type-specific arguments + if longhand.keyword: + method = impl_keyword + args.update(keyword=longhand.keyword) + if "font" in longhand.ident: + args.update(cast_type=longhand.cast_type) + else: + method = impl_simple + + method(**args) +%> +impl ${style_struct.gecko_struct_name} { + /* + * Manually-Implemented Methods. + */ + ${caller.body().strip()} + + /* + * Auto-Generated Methods. + */ + <% + for longhand in longhands: + longhand_method(longhand) + %> +} +</%def> + +<%! +class Side(object): + def __init__(self, name, index): + self.name = name + self.ident = name.lower() + self.index = index + +SIDES = [Side("Top", 0), Side("Right", 1), Side("Bottom", 2), Side("Left", 3)] +CORNERS = ["top_left", "top_right", "bottom_right", "bottom_left"] +%> + +#[allow(dead_code)] +fn static_assert() { + // Note: using the above technique with an enum hits a rust bug when |structs| is in a different crate. + % for side in SIDES: + { const DETAIL: u32 = [0][(structs::Side::eSide${side.name} as usize != ${side.index}) as usize]; let _ = DETAIL; } + % endfor +} + + +<% skip_border_longhands = " ".join(["border-{0}-{1}".format(x.ident, y) + for x in SIDES + for y in ["style", "width"]] + + ["border-{0}-radius".format(x.replace("_", "-")) + for x in CORNERS]) %> + +<%self:impl_trait style_struct_name="Border" + skip_longhands="${skip_border_longhands} border-image-repeat"> + % for side in SIDES: + pub fn set_border_${side.ident}_style(&mut self, v: BorderStyle) { + self.mBorderStyle[${side.index}] = v; + + // This is needed because the initial mComputedBorder value is set to + // zero. + // + // In order to compute stuff, we start from the initial struct, and keep + // going down the tree applying properties. + // + // That means, effectively, that when we set border-style to something + // non-hidden, we should use the initial border instead. + // + // Servo stores the initial border-width in the initial struct, and then + // adjusts as needed in the fixup phase. This means that the initial + // struct is technically not valid without fixups, and that you lose + // pretty much any sharing of the initial struct, which is kind of + // unfortunate. + // + // Gecko has two fields for this, one that stores the "specified" + // border, and other that stores the actual computed one. That means + // that when we set border-style, border-width may change and we need to + // sync back to the specified one. This is what this function does. + // + // Note that this doesn't impose any dependency in the order of + // computation of the properties. This is only relevant if border-style + // is specified, but border-width isn't. If border-width is specified at + // some point, the two mBorder and mComputedBorder fields would be the + // same already. + // + // Once we're here, we know that we'll run style fixups, so it's fine to + // just copy the specified border here, we'll adjust it if it's + // incorrect later. + self.mComputedBorder.${side.ident} = self.mBorder.${side.ident}; + } + + pub fn copy_border_${side.ident}_style_from(&mut self, other: &Self) { + self.set_border_${side.ident}_style(other.mBorderStyle[${side.index}]); + } + + pub fn reset_border_${side.ident}_style(&mut self, other: &Self) { + self.copy_border_${side.ident}_style_from(other); + } + + #[inline] + pub fn clone_border_${side.ident}_style(&self) -> BorderStyle { + self.mBorderStyle[${side.index}] + } + + ${impl_border_width("border_%s_width" % side.ident, "mComputedBorder.%s" % side.ident, "mBorder.%s" % side.ident)} + + pub fn border_${side.ident}_has_nonzero_width(&self) -> bool { + self.mComputedBorder.${side.ident} != 0 + } + % endfor + + % for corner in CORNERS: + <% impl_corner_style_coord("border_%s_radius" % corner, + "mBorderRadius", + corner) %> + % endfor + + <% + border_image_repeat_keywords = ["Stretch", "Repeat", "Round", "Space"] + %> + + pub fn set_border_image_repeat(&mut self, v: longhands::border_image_repeat::computed_value::T) { + use crate::values::specified::border::BorderImageRepeatKeyword; + use crate::gecko_bindings::structs::StyleBorderImageRepeat; + + % for i, side in enumerate(["H", "V"]): + self.mBorderImageRepeat${side} = match v.${i} { + % for keyword in border_image_repeat_keywords: + BorderImageRepeatKeyword::${keyword} => StyleBorderImageRepeat::${keyword}, + % endfor + }; + % endfor + } + + pub fn copy_border_image_repeat_from(&mut self, other: &Self) { + self.mBorderImageRepeatH = other.mBorderImageRepeatH; + self.mBorderImageRepeatV = other.mBorderImageRepeatV; + } + + pub fn reset_border_image_repeat(&mut self, other: &Self) { + self.copy_border_image_repeat_from(other) + } + + pub fn clone_border_image_repeat(&self) -> longhands::border_image_repeat::computed_value::T { + use crate::values::specified::border::BorderImageRepeatKeyword; + use crate::gecko_bindings::structs::StyleBorderImageRepeat; + + % for side in ["H", "V"]: + let servo_${side.lower()} = match self.mBorderImageRepeat${side} { + % for keyword in border_image_repeat_keywords: + StyleBorderImageRepeat::${keyword} => BorderImageRepeatKeyword::${keyword}, + % endfor + }; + % endfor + longhands::border_image_repeat::computed_value::T(servo_h, servo_v) + } +</%self:impl_trait> + +<% skip_scroll_margin_longhands = " ".join(["scroll-margin-%s" % x.ident for x in SIDES]) %> +<% skip_margin_longhands = " ".join(["margin-%s" % x.ident for x in SIDES]) %> +<%self:impl_trait style_struct_name="Margin" + skip_longhands="${skip_margin_longhands} + ${skip_scroll_margin_longhands}"> + % for side in SIDES: + <% impl_split_style_coord("margin_%s" % side.ident, + "mMargin", + side.index) %> + <% impl_split_style_coord("scroll_margin_%s" % side.ident, + "mScrollMargin", + side.index) %> + % endfor +</%self:impl_trait> + +<% skip_scroll_padding_longhands = " ".join(["scroll-padding-%s" % x.ident for x in SIDES]) %> +<% skip_padding_longhands = " ".join(["padding-%s" % x.ident for x in SIDES]) %> +<%self:impl_trait style_struct_name="Padding" + skip_longhands="${skip_padding_longhands} + ${skip_scroll_padding_longhands}"> + + % for side in SIDES: + <% impl_split_style_coord("padding_%s" % side.ident, + "mPadding", + side.index) %> + <% impl_split_style_coord("scroll_padding_%s" % side.ident, "mScrollPadding", side.index) %> + % endfor +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Page"> +</%self:impl_trait> + +<% skip_position_longhands = " ".join(x.ident for x in SIDES) %> +<%self:impl_trait style_struct_name="Position" + skip_longhands="${skip_position_longhands} + masonry-auto-flow"> + % for side in SIDES: + <% impl_split_style_coord(side.ident, "mOffset", side.index) %> + % endfor + pub fn set_computed_justify_items(&mut self, v: values::specified::JustifyItems) { + debug_assert_ne!(v.0, crate::values::specified::align::AlignFlags::LEGACY); + self.mJustifyItems.computed = v; + } + + ${impl_simple_type_with_conversion("masonry_auto_flow", "mMasonryAutoFlow")} +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Outline" + skip_longhands="outline-style outline-width"> + + pub fn set_outline_style(&mut self, v: longhands::outline_style::computed_value::T) { + self.mOutlineStyle = v; + // NB: This is needed to correctly handling the initial value of + // outline-width when outline-style changes, see the + // update_border_${side.ident} comment for more details. + self.mActualOutlineWidth = self.mOutlineWidth; + } + + pub fn copy_outline_style_from(&mut self, other: &Self) { + self.set_outline_style(other.mOutlineStyle); + } + + pub fn reset_outline_style(&mut self, other: &Self) { + self.copy_outline_style_from(other) + } + + pub fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T { + self.mOutlineStyle.clone() + } + + ${impl_border_width("outline_width", "mActualOutlineWidth", "mOutlineWidth")} + + pub fn outline_has_nonzero_width(&self) -> bool { + self.mActualOutlineWidth != 0 + } +</%self:impl_trait> + +<% skip_font_longhands = """font-family font-size font-size-adjust font-weight + font-style font-stretch -x-lang + font-variant-alternates font-variant-east-asian + font-variant-ligatures font-variant-numeric + font-language-override font-feature-settings + font-variation-settings -moz-min-font-size-ratio""" %> +<%self:impl_trait style_struct_name="Font" + skip_longhands="${skip_font_longhands}"> + + // Negative numbers are invalid at parse time, but <integer> is still an + // i32. + <% impl_font_settings("font_feature_settings", "gfxFontFeature", "FeatureTagValue", "i32", "u32") %> + <% impl_font_settings("font_variation_settings", "gfxFontVariation", "VariationValue", "f32", "f32") %> + + pub fn unzoom_fonts(&mut self, device: &Device) { + use crate::values::generics::NonNegative; + self.mSize = NonNegative(device.unzoom_text(self.mSize.0)); + self.mScriptUnconstrainedSize = NonNegative(device.unzoom_text(self.mScriptUnconstrainedSize.0)); + self.mFont.size = NonNegative(device.unzoom_text(self.mFont.size.0)); + } + + pub fn copy_font_size_from(&mut self, other: &Self) { + self.mScriptUnconstrainedSize = other.mScriptUnconstrainedSize; + + self.mSize = other.mScriptUnconstrainedSize; + // NOTE: Intentionally not copying from mFont.size. The cascade process + // recomputes the used size as needed. + self.mFont.size = other.mSize; + self.mFontSizeKeyword = other.mFontSizeKeyword; + + // TODO(emilio): Should we really copy over these two? + self.mFontSizeFactor = other.mFontSizeFactor; + self.mFontSizeOffset = other.mFontSizeOffset; + } + + pub fn reset_font_size(&mut self, other: &Self) { + self.copy_font_size_from(other) + } + + pub fn set_font_size(&mut self, v: FontSize) { + let computed_size = v.computed_size; + self.mScriptUnconstrainedSize = computed_size; + + // These two may be changed from Cascade::fixup_font_stuff. + self.mSize = computed_size; + // NOTE: Intentionally not copying from used_size. The cascade process + // recomputes the used size as needed. + self.mFont.size = computed_size; + + self.mFontSizeKeyword = v.keyword_info.kw; + self.mFontSizeFactor = v.keyword_info.factor; + self.mFontSizeOffset = v.keyword_info.offset; + } + + pub fn clone_font_size(&self) -> FontSize { + use crate::values::specified::font::KeywordInfo; + + FontSize { + computed_size: self.mSize, + used_size: self.mFont.size, + keyword_info: KeywordInfo { + kw: self.mFontSizeKeyword, + factor: self.mFontSizeFactor, + offset: self.mFontSizeOffset, + } + } + } + + ${impl_simple('font_weight', 'mFont.weight')} + ${impl_simple('font_stretch', 'mFont.stretch')} + ${impl_simple('font_style', 'mFont.style')} + + ${impl_simple("font_variant_alternates", "mFont.variantAlternates")} + + ${impl_simple("font_size_adjust", "mFont.sizeAdjust")} + + ${impl_simple("font_family", "mFont.family")} + + #[allow(non_snake_case)] + pub fn set__x_lang(&mut self, v: longhands::_x_lang::computed_value::T) { + let ptr = v.0.as_ptr(); + forget(v); + unsafe { + Gecko_nsStyleFont_SetLang(&mut **self, ptr); + } + } + + #[allow(non_snake_case)] + pub fn copy__x_lang_from(&mut self, other: &Self) { + unsafe { + Gecko_nsStyleFont_CopyLangFrom(&mut **self, &**other); + } + } + + #[allow(non_snake_case)] + pub fn reset__x_lang(&mut self, other: &Self) { + self.copy__x_lang_from(other) + } + + #[allow(non_snake_case)] + pub fn clone__x_lang(&self) -> longhands::_x_lang::computed_value::T { + longhands::_x_lang::computed_value::T(unsafe { + Atom::from_raw(self.mLanguage.mRawPtr) + }) + } + + + ${impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride")} + ${impl_simple_type_with_conversion("font_variant_ligatures", "mFont.variantLigatures")} + ${impl_simple_type_with_conversion("font_variant_east_asian", "mFont.variantEastAsian")} + ${impl_simple_type_with_conversion("font_variant_numeric", "mFont.variantNumeric")} + + #[allow(non_snake_case)] + pub fn clone__moz_min_font_size_ratio( + &self, + ) -> longhands::_moz_min_font_size_ratio::computed_value::T { + Percentage(self.mMinFontSizeRatio as f32 / 100.) + } + + #[allow(non_snake_case)] + pub fn set__moz_min_font_size_ratio(&mut self, v: longhands::_moz_min_font_size_ratio::computed_value::T) { + let scaled = v.0 * 100.; + let percentage = if scaled > 255. { + 255. + } else if scaled < 0. { + 0. + } else { + scaled + }; + + self.mMinFontSizeRatio = percentage as u8; + } + + ${impl_simple_copy('_moz_min_font_size_ratio', 'mMinFontSizeRatio')} +</%self:impl_trait> + +<%def name="impl_coordinated_property_copy(type, ident, gecko_ffi_name)"> + #[allow(non_snake_case)] + pub fn copy_${type}_${ident}_from(&mut self, other: &Self) { + self.m${to_camel_case(type)}s.ensure_len(other.m${to_camel_case(type)}s.len()); + + let count = other.m${to_camel_case(type)}${gecko_ffi_name}Count; + self.m${to_camel_case(type)}${gecko_ffi_name}Count = count; + + let iter = self.m${to_camel_case(type)}s.iter_mut().take(count as usize).zip( + other.m${to_camel_case(type)}s.iter() + ); + + for (ours, others) in iter { + ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone(); + } + } + #[allow(non_snake_case)] + pub fn reset_${type}_${ident}(&mut self, other: &Self) { + self.copy_${type}_${ident}_from(other) + } +</%def> + +<%def name="impl_coordinated_property_count(type, ident, gecko_ffi_name)"> + #[allow(non_snake_case)] + pub fn ${type}_${ident}_count(&self) -> usize { + self.m${to_camel_case(type)}${gecko_ffi_name}Count as usize + } +</%def> + +<%def name="impl_coordinated_property(type, ident, gecko_ffi_name)"> + #[allow(non_snake_case)] + pub fn set_${type}_${ident}<I>(&mut self, v: I) + where + I: IntoIterator<Item = longhands::${type}_${ident}::computed_value::single_value::T>, + I::IntoIter: ExactSizeIterator + Clone + { + let v = v.into_iter(); + debug_assert_ne!(v.len(), 0); + let input_len = v.len(); + self.m${to_camel_case(type)}s.ensure_len(input_len); + + self.m${to_camel_case(type)}${gecko_ffi_name}Count = input_len as u32; + for (gecko, servo) in self.m${to_camel_case(type)}s.iter_mut().take(input_len as usize).zip(v) { + gecko.m${gecko_ffi_name} = servo; + } + } + #[allow(non_snake_case)] + pub fn ${type}_${ident}_at(&self, index: usize) + -> longhands::${type}_${ident}::computed_value::SingleComputedValue { + self.m${to_camel_case(type)}s[index % self.${type}_${ident}_count()].m${gecko_ffi_name}.clone() + } + ${impl_coordinated_property_copy(type, ident, gecko_ffi_name)} + ${impl_coordinated_property_count(type, ident, gecko_ffi_name)} +</%def> + +<% skip_box_longhands= """display contain""" %> +<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}"> + #[inline] + pub fn set_display(&mut self, v: longhands::display::computed_value::T) { + self.mDisplay = v; + self.mOriginalDisplay = v; + } + + #[inline] + pub fn copy_display_from(&mut self, other: &Self) { + self.set_display(other.mDisplay); + } + + #[inline] + pub fn reset_display(&mut self, other: &Self) { + self.copy_display_from(other) + } + + #[inline] + pub fn set_adjusted_display( + &mut self, + v: longhands::display::computed_value::T, + _is_item_or_root: bool + ) { + self.mDisplay = v; + } + + #[inline] + pub fn clone_display(&self) -> longhands::display::computed_value::T { + self.mDisplay + } + + #[inline] + pub fn set_contain(&mut self, v: longhands::contain::computed_value::T) { + self.mContain = v; + self.mEffectiveContainment = v; + } + + #[inline] + pub fn copy_contain_from(&mut self, other: &Self) { + self.set_contain(other.mContain); + } + + #[inline] + pub fn reset_contain(&mut self, other: &Self) { + self.copy_contain_from(other) + } + + #[inline] + pub fn clone_contain(&self) -> longhands::contain::computed_value::T { + self.mContain + } + + #[inline] + pub fn set_effective_containment( + &mut self, + v: longhands::contain::computed_value::T + ) { + self.mEffectiveContainment = v; + } + + #[inline] + pub fn clone_effective_containment(&self) -> longhands::contain::computed_value::T { + self.mEffectiveContainment + } +</%self:impl_trait> + +<%def name="simple_image_array_property(name, shorthand, field_name)"> + <% + image_layers_field = "mImage" if shorthand == "background" else "mMask" + copy_simple_image_array_property(name, shorthand, image_layers_field, field_name) + %> + + pub fn set_${shorthand}_${name}<I>(&mut self, v: I) + where I: IntoIterator<Item=longhands::${shorthand}_${name}::computed_value::single_value::T>, + I::IntoIter: ExactSizeIterator + { + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + let v = v.into_iter(); + + unsafe { + Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, v.len(), + LayerType::${shorthand.title()}); + } + + self.${image_layers_field}.${field_name}Count = v.len() as u32; + for (servo, geckolayer) in v.zip(self.${image_layers_field}.mLayers.iter_mut()) { + geckolayer.${field_name} = { + ${caller.body()} + }; + } + } +</%def> + +<%def name="copy_simple_image_array_property(name, shorthand, layers_field_name, field_name)"> + pub fn copy_${shorthand}_${name}_from(&mut self, other: &Self) { + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + + let count = other.${layers_field_name}.${field_name}Count; + unsafe { + Gecko_EnsureImageLayersLength(&mut self.${layers_field_name}, + count as usize, + LayerType::${shorthand.title()}); + } + // FIXME(emilio): This may be bogus in the same way as bug 1426246. + for (layer, other) in self.${layers_field_name}.mLayers.iter_mut() + .zip(other.${layers_field_name}.mLayers.iter()) + .take(count as usize) { + layer.${field_name} = other.${field_name}.clone(); + } + self.${layers_field_name}.${field_name}Count = count; + } + + pub fn reset_${shorthand}_${name}(&mut self, other: &Self) { + self.copy_${shorthand}_${name}_from(other) + } +</%def> + +<%def name="impl_simple_image_array_property(name, shorthand, layer_field_name, field_name, struct_name)"> + <% + ident = "%s_%s" % (shorthand, name) + style_struct = next(x for x in data.style_structs if x.name == struct_name) + longhand = next(x for x in style_struct.longhands if x.ident == ident) + keyword = longhand.keyword + %> + + <% copy_simple_image_array_property(name, shorthand, layer_field_name, field_name) %> + + pub fn set_${ident}<I>(&mut self, v: I) + where + I: IntoIterator<Item=longhands::${ident}::computed_value::single_value::T>, + I::IntoIter: ExactSizeIterator, + { + use crate::properties::longhands::${ident}::single_value::computed_value::T as Keyword; + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + + let v = v.into_iter(); + + unsafe { + Gecko_EnsureImageLayersLength(&mut self.${layer_field_name}, v.len(), + LayerType::${shorthand.title()}); + } + + self.${layer_field_name}.${field_name}Count = v.len() as u32; + for (servo, geckolayer) in v.zip(self.${layer_field_name}.mLayers.iter_mut()) { + geckolayer.${field_name} = { + match servo { + % for value in keyword.values_for("gecko"): + Keyword::${to_camel_case(value)} => + structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast('u8')}, + % endfor + } + }; + } + } + + pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { + use crate::properties::longhands::${ident}::single_value::computed_value::T as Keyword; + + % if keyword.needs_cast(): + % for value in keyword.values_for('gecko'): + const ${keyword.casted_constant_name(value, "u8")} : u8 = + structs::${keyword.gecko_constant(value)} as u8; + % endfor + % endif + + longhands::${ident}::computed_value::List( + self.${layer_field_name}.mLayers.iter() + .take(self.${layer_field_name}.${field_name}Count as usize) + .map(|ref layer| { + match layer.${field_name} { + % for value in longhand.keyword.values_for("gecko"): + % if keyword.needs_cast(): + ${keyword.casted_constant_name(value, "u8")} + % else: + structs::${keyword.gecko_constant(value)} + % endif + => Keyword::${to_camel_case(value)}, + % endfor + % if keyword.gecko_inexhaustive: + _ => panic!("Found unexpected value in style struct for ${ident} property"), + % endif + } + }).collect() + ) + } +</%def> + +<%def name="impl_common_image_layer_properties(shorthand)"> + <% + if shorthand == "background": + image_layers_field = "mImage" + struct_name = "Background" + else: + image_layers_field = "mMask" + struct_name = "SVG" + %> + + <%self:simple_image_array_property name="repeat" shorthand="${shorthand}" field_name="mRepeat"> + use crate::values::specified::background::BackgroundRepeatKeyword; + use crate::gecko_bindings::structs::nsStyleImageLayers_Repeat; + use crate::gecko_bindings::structs::StyleImageLayerRepeat; + + fn to_ns(repeat: BackgroundRepeatKeyword) -> StyleImageLayerRepeat { + match repeat { + BackgroundRepeatKeyword::Repeat => StyleImageLayerRepeat::Repeat, + BackgroundRepeatKeyword::Space => StyleImageLayerRepeat::Space, + BackgroundRepeatKeyword::Round => StyleImageLayerRepeat::Round, + BackgroundRepeatKeyword::NoRepeat => StyleImageLayerRepeat::NoRepeat, + } + } + + let repeat_x = to_ns(servo.0); + let repeat_y = to_ns(servo.1); + nsStyleImageLayers_Repeat { + mXRepeat: repeat_x, + mYRepeat: repeat_y, + } + </%self:simple_image_array_property> + + pub fn clone_${shorthand}_repeat(&self) -> longhands::${shorthand}_repeat::computed_value::T { + use crate::properties::longhands::${shorthand}_repeat::single_value::computed_value::T; + use crate::values::specified::background::BackgroundRepeatKeyword; + use crate::gecko_bindings::structs::StyleImageLayerRepeat; + + fn to_servo(repeat: StyleImageLayerRepeat) -> BackgroundRepeatKeyword { + match repeat { + StyleImageLayerRepeat::Repeat => BackgroundRepeatKeyword::Repeat, + StyleImageLayerRepeat::Space => BackgroundRepeatKeyword::Space, + StyleImageLayerRepeat::Round => BackgroundRepeatKeyword::Round, + StyleImageLayerRepeat::NoRepeat => BackgroundRepeatKeyword::NoRepeat, + _ => panic!("Found unexpected value in style struct for ${shorthand}_repeat property"), + } + } + + longhands::${shorthand}_repeat::computed_value::List( + self.${image_layers_field}.mLayers.iter() + .take(self.${image_layers_field}.mRepeatCount as usize) + .map(|ref layer| { + T(to_servo(layer.mRepeat.mXRepeat), to_servo(layer.mRepeat.mYRepeat)) + }).collect() + ) + } + + <% impl_simple_image_array_property("clip", shorthand, image_layers_field, "mClip", struct_name) %> + <% impl_simple_image_array_property("origin", shorthand, image_layers_field, "mOrigin", struct_name) %> + + % for (orientation, keyword) in [("x", "horizontal"), ("y", "vertical")]: + pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) { + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + + let count = other.${image_layers_field}.mPosition${orientation.upper()}Count; + + unsafe { + Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, + count as usize, + LayerType::${shorthand.capitalize()}); + } + + for (layer, other) in self.${image_layers_field}.mLayers.iter_mut() + .zip(other.${image_layers_field}.mLayers.iter()) + .take(count as usize) { + layer.mPosition.${keyword} = other.mPosition.${keyword}.clone(); + } + self.${image_layers_field}.mPosition${orientation.upper()}Count = count; + } + + pub fn reset_${shorthand}_position_${orientation}(&mut self, other: &Self) { + self.copy_${shorthand}_position_${orientation}_from(other) + } + + pub fn clone_${shorthand}_position_${orientation}(&self) + -> longhands::${shorthand}_position_${orientation}::computed_value::T { + longhands::${shorthand}_position_${orientation}::computed_value::List( + self.${image_layers_field}.mLayers.iter() + .take(self.${image_layers_field}.mPosition${orientation.upper()}Count as usize) + .map(|position| position.mPosition.${keyword}.clone()) + .collect() + ) + } + + pub fn set_${shorthand}_position_${orientation[0]}<I>(&mut self, + v: I) + where I: IntoIterator<Item = longhands::${shorthand}_position_${orientation[0]} + ::computed_value::single_value::T>, + I::IntoIter: ExactSizeIterator + { + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + + let v = v.into_iter(); + + unsafe { + Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, v.len(), + LayerType::${shorthand.capitalize()}); + } + + self.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.len() as u32; + for (servo, geckolayer) in v.zip(self.${image_layers_field} + .mLayers.iter_mut()) { + geckolayer.mPosition.${keyword} = servo; + } + } + % endfor + + <%self:simple_image_array_property name="size" shorthand="${shorthand}" field_name="mSize"> + servo + </%self:simple_image_array_property> + + pub fn clone_${shorthand}_size(&self) -> longhands::${shorthand}_size::computed_value::T { + longhands::${shorthand}_size::computed_value::List( + self.${image_layers_field}.mLayers.iter().map(|layer| layer.mSize.clone()).collect() + ) + } + + pub fn copy_${shorthand}_image_from(&mut self, other: &Self) { + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + unsafe { + let count = other.${image_layers_field}.mImageCount; + Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, + count as usize, + LayerType::${shorthand.capitalize()}); + + for (layer, other) in self.${image_layers_field}.mLayers.iter_mut() + .zip(other.${image_layers_field}.mLayers.iter()) + .take(count as usize) { + layer.mImage = other.mImage.clone(); + } + self.${image_layers_field}.mImageCount = count; + } + } + + pub fn reset_${shorthand}_image(&mut self, other: &Self) { + self.copy_${shorthand}_image_from(other) + } + + #[allow(unused_variables)] + pub fn set_${shorthand}_image<I>(&mut self, images: I) + where I: IntoIterator<Item = longhands::${shorthand}_image::computed_value::single_value::T>, + I::IntoIter: ExactSizeIterator + { + use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; + + let images = images.into_iter(); + + unsafe { + Gecko_EnsureImageLayersLength( + &mut self.${image_layers_field}, + images.len(), + LayerType::${shorthand.title()}, + ); + } + + self.${image_layers_field}.mImageCount = images.len() as u32; + for (image, geckoimage) in images.zip(self.${image_layers_field} + .mLayers.iter_mut()) { + geckoimage.mImage = image; + } + } + + pub fn clone_${shorthand}_image(&self) -> longhands::${shorthand}_image::computed_value::T { + longhands::${shorthand}_image::computed_value::List( + self.${image_layers_field}.mLayers.iter() + .take(self.${image_layers_field}.mImageCount as usize) + .map(|layer| layer.mImage.clone()) + .collect() + ) + } + + <% + fill_fields = "mRepeat mClip mOrigin mPositionX mPositionY mImage mSize" + if shorthand == "background": + fill_fields += " mAttachment mBlendMode" + else: + # mSourceURI uses mImageCount + fill_fields += " mMaskMode mComposite" + %> + pub fn fill_arrays(&mut self) { + use crate::gecko_bindings::bindings::Gecko_FillAllImageLayers; + use std::cmp; + let mut max_len = 1; + % for member in fill_fields.split(): + max_len = cmp::max(max_len, self.${image_layers_field}.${member}Count); + % endfor + unsafe { + // While we could do this manually, we'd need to also manually + // run all the copy constructors, so we just delegate to gecko + Gecko_FillAllImageLayers(&mut self.${image_layers_field}, max_len); + } + } +</%def> + +// TODO: Gecko accepts lists in most background-related properties. We just use +// the first element (which is the common case), but at some point we want to +// add support for parsing these lists in servo and pushing to nsTArray's. +<% skip_background_longhands = """background-repeat + background-image background-clip + background-origin background-attachment + background-size background-position + background-blend-mode + background-position-x + background-position-y""" %> +<%self:impl_trait style_struct_name="Background" + skip_longhands="${skip_background_longhands}"> + + <% impl_common_image_layer_properties("background") %> + <% impl_simple_image_array_property("attachment", "background", "mImage", "mAttachment", "Background") %> + <% impl_simple_image_array_property("blend_mode", "background", "mImage", "mBlendMode", "Background") %> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="List" skip_longhands="list-style-type"> + pub fn set_list_style_type(&mut self, v: longhands::list_style_type::computed_value::T) { + use nsstring::{nsACString, nsCStr}; + use self::longhands::list_style_type::computed_value::T; + match v { + T::None => unsafe { + bindings::Gecko_SetCounterStyleToNone(&mut self.mCounterStyle) + } + T::CounterStyle(s) => s.to_gecko_value(&mut self.mCounterStyle), + T::String(s) => unsafe { + bindings::Gecko_SetCounterStyleToString( + &mut self.mCounterStyle, + &nsCStr::from(&s) as &nsACString, + ) + } + } + } + + pub fn copy_list_style_type_from(&mut self, other: &Self) { + unsafe { + Gecko_CopyCounterStyle(&mut self.mCounterStyle, &other.mCounterStyle); + } + } + + pub fn reset_list_style_type(&mut self, other: &Self) { + self.copy_list_style_type_from(other) + } + + pub fn clone_list_style_type(&self) -> longhands::list_style_type::computed_value::T { + use self::longhands::list_style_type::computed_value::T; + use crate::values::Either; + use crate::values::generics::CounterStyle; + use crate::gecko_bindings::bindings; + + let name = unsafe { + bindings::Gecko_CounterStyle_GetName(&self.mCounterStyle) + }; + if !name.is_null() { + let name = unsafe { Atom::from_raw(name) }; + if name == atom!("none") { + return T::None; + } + } + let result = CounterStyle::from_gecko_value(&self.mCounterStyle); + match result { + Either::First(counter_style) => T::CounterStyle(counter_style), + Either::Second(string) => T::String(string), + } + } +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Table"> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Effects"> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="InheritedBox"> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="InheritedTable" + skip_longhands="border-spacing"> + + pub fn set_border_spacing(&mut self, v: longhands::border_spacing::computed_value::T) { + self.mBorderSpacingCol = v.horizontal().0; + self.mBorderSpacingRow = v.vertical().0; + } + + pub fn copy_border_spacing_from(&mut self, other: &Self) { + self.mBorderSpacingCol = other.mBorderSpacingCol; + self.mBorderSpacingRow = other.mBorderSpacingRow; + } + + pub fn reset_border_spacing(&mut self, other: &Self) { + self.copy_border_spacing_from(other) + } + + pub fn clone_border_spacing(&self) -> longhands::border_spacing::computed_value::T { + longhands::border_spacing::computed_value::T::new( + Au(self.mBorderSpacingCol).into(), + Au(self.mBorderSpacingRow).into() + ) + } +</%self:impl_trait> + + +<%self:impl_trait style_struct_name="InheritedText"> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Text" skip_longhands="initial-letter"> + pub fn set_initial_letter(&mut self, v: longhands::initial_letter::computed_value::T) { + use crate::values::generics::text::InitialLetter; + match v { + InitialLetter::Normal => { + self.mInitialLetterSize = 0.; + self.mInitialLetterSink = 0; + }, + InitialLetter::Specified(size, sink) => { + self.mInitialLetterSize = size; + if let Some(sink) = sink { + self.mInitialLetterSink = sink; + } else { + self.mInitialLetterSink = size.floor() as i32; + } + } + } + } + + pub fn copy_initial_letter_from(&mut self, other: &Self) { + self.mInitialLetterSize = other.mInitialLetterSize; + self.mInitialLetterSink = other.mInitialLetterSink; + } + + pub fn reset_initial_letter(&mut self, other: &Self) { + self.copy_initial_letter_from(other) + } + + pub fn clone_initial_letter(&self) -> longhands::initial_letter::computed_value::T { + use crate::values::generics::text::InitialLetter; + + if self.mInitialLetterSize == 0. && self.mInitialLetterSink == 0 { + InitialLetter::Normal + } else if self.mInitialLetterSize.floor() as i32 == self.mInitialLetterSink { + InitialLetter::Specified(self.mInitialLetterSize, None) + } else { + InitialLetter::Specified(self.mInitialLetterSize, Some(self.mInitialLetterSink)) + } + } +</%self:impl_trait> + +<% skip_svg_longhands = """ +mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask-position-y mask-size mask-image +""" +%> +<%self:impl_trait style_struct_name="SVG" + skip_longhands="${skip_svg_longhands}"> + <% impl_common_image_layer_properties("mask") %> + <% impl_simple_image_array_property("mode", "mask", "mMask", "mMaskMode", "SVG") %> + <% impl_simple_image_array_property("composite", "mask", "mMask", "mComposite", "SVG") %> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="InheritedSVG"> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="InheritedUI"> +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Column" + skip_longhands="column-count column-rule-width column-rule-style"> + + #[allow(unused_unsafe)] + pub fn set_column_count(&mut self, v: longhands::column_count::computed_value::T) { + use crate::gecko_bindings::structs::{nsStyleColumn_kColumnCountAuto, nsStyleColumn_kMaxColumnCount}; + + self.mColumnCount = match v { + ColumnCount::Integer(integer) => { + cmp::min(integer.0 as u32, unsafe { nsStyleColumn_kMaxColumnCount }) + }, + ColumnCount::Auto => nsStyleColumn_kColumnCountAuto + }; + } + + ${impl_simple_copy('column_count', 'mColumnCount')} + + pub fn clone_column_count(&self) -> longhands::column_count::computed_value::T { + use crate::gecko_bindings::structs::{nsStyleColumn_kColumnCountAuto, nsStyleColumn_kMaxColumnCount}; + if self.mColumnCount != nsStyleColumn_kColumnCountAuto { + debug_assert!(self.mColumnCount >= 1 && + self.mColumnCount <= nsStyleColumn_kMaxColumnCount); + ColumnCount::Integer((self.mColumnCount as i32).into()) + } else { + ColumnCount::Auto + } + } + + pub fn set_column_rule_style(&mut self, v: longhands::column_rule_style::computed_value::T) { + self.mColumnRuleStyle = v; + // NB: This is needed to correctly handling the initial value of + // column-rule-width when colun-rule-style changes, see the + // update_border_${side.ident} comment for more details. + self.mActualColumnRuleWidth = self.mColumnRuleWidth; + } + + pub fn copy_column_rule_style_from(&mut self, other: &Self) { + self.set_column_rule_style(other.mColumnRuleStyle); + } + + pub fn reset_column_rule_style(&mut self, other: &Self) { + self.copy_column_rule_style_from(other) + } + + pub fn clone_column_rule_style(&self) -> longhands::column_rule_style::computed_value::T { + self.mColumnRuleStyle.clone() + } + + ${impl_border_width("column_rule_width", "mActualColumnRuleWidth", "mColumnRuleWidth")} + + pub fn column_rule_has_nonzero_width(&self) -> bool { + self.mActualColumnRuleWidth != 0 + } +</%self:impl_trait> + +<%self:impl_trait style_struct_name="Counters"> + pub fn ineffective_content_property(&self) -> bool { + !self.mContent.is_items() + } +</%self:impl_trait> + +<% skip_ui_longhands = """animation-name animation-delay animation-duration + animation-direction animation-fill-mode + animation-play-state animation-iteration-count + animation-timing-function animation-composition animation-timeline + transition-duration transition-delay + transition-timing-function transition-property + scroll-timeline-name scroll-timeline-axis + view-timeline-name view-timeline-axis view-timeline-inset""" %> + +<%self:impl_trait style_struct_name="UI" skip_longhands="${skip_ui_longhands}"> + ${impl_coordinated_property('transition', 'delay', 'Delay')} + ${impl_coordinated_property('transition', 'duration', 'Duration')} + ${impl_coordinated_property('transition', 'timing_function', 'TimingFunction')} + ${impl_coordinated_property('transition', 'property', 'Property')} + + pub fn transition_combined_duration_at(&self, index: usize) -> Time { + // https://drafts.csswg.org/css-transitions/#transition-combined-duration + Time::from_seconds( + self.transition_duration_at(index).seconds().max(0.0) + + self.transition_delay_at(index).seconds() + ) + } + + /// Returns whether there are any transitions specified. + pub fn specifies_transitions(&self) -> bool { + if self.mTransitionPropertyCount == 1 && + self.transition_combined_duration_at(0).seconds() <= 0.0f32 { + return false; + } + self.mTransitionPropertyCount > 0 + } + + pub fn animations_equals(&self, other: &Self) -> bool { + return self.mAnimationNameCount == other.mAnimationNameCount + && self.mAnimationDelayCount == other.mAnimationDelayCount + && self.mAnimationDirectionCount == other.mAnimationDirectionCount + && self.mAnimationDurationCount == other.mAnimationDurationCount + && self.mAnimationFillModeCount == other.mAnimationFillModeCount + && self.mAnimationIterationCountCount == other.mAnimationIterationCountCount + && self.mAnimationPlayStateCount == other.mAnimationPlayStateCount + && self.mAnimationTimingFunctionCount == other.mAnimationTimingFunctionCount + && self.mAnimationCompositionCount == other.mAnimationCompositionCount + && self.mAnimationTimelineCount == other.mAnimationTimelineCount + && unsafe { bindings::Gecko_StyleAnimationsEquals(&self.mAnimations, &other.mAnimations) } + } + + ${impl_coordinated_property('animation', 'name', 'Name')} + ${impl_coordinated_property('animation', 'delay', 'Delay')} + ${impl_coordinated_property('animation', 'duration', 'Duration')} + ${impl_coordinated_property('animation', 'direction', 'Direction')} + ${impl_coordinated_property('animation', 'fill_mode', 'FillMode')} + ${impl_coordinated_property('animation', 'play_state', 'PlayState')} + ${impl_coordinated_property('animation', 'composition', 'Composition')} + ${impl_coordinated_property('animation', 'iteration_count', 'IterationCount')} + ${impl_coordinated_property('animation', 'timeline', 'Timeline')} + ${impl_coordinated_property('animation', 'timing_function', 'TimingFunction')} + + ${impl_coordinated_property('scroll_timeline', 'name', 'Name')} + ${impl_coordinated_property('scroll_timeline', 'axis', 'Axis')} + + pub fn scroll_timelines_equals(&self, other: &Self) -> bool { + self.mScrollTimelineNameCount == other.mScrollTimelineNameCount + && self.mScrollTimelineAxisCount == other.mScrollTimelineAxisCount + && unsafe { + bindings::Gecko_StyleScrollTimelinesEquals( + &self.mScrollTimelines, + &other.mScrollTimelines, + ) + } + } + + ${impl_coordinated_property('view_timeline', 'name', 'Name')} + ${impl_coordinated_property('view_timeline', 'axis', 'Axis')} + ${impl_coordinated_property('view_timeline', 'inset', 'Inset')} + + pub fn view_timelines_equals(&self, other: &Self) -> bool { + self.mViewTimelineNameCount == other.mViewTimelineNameCount + && self.mViewTimelineAxisCount == other.mViewTimelineAxisCount + && self.mViewTimelineInsetCount == other.mViewTimelineInsetCount + && unsafe { + bindings::Gecko_StyleViewTimelinesEquals( + &self.mViewTimelines, + &other.mViewTimelines, + ) + } + } +</%self:impl_trait> + +<%self:impl_trait style_struct_name="XUL"> +</%self:impl_trait> + +% for style_struct in data.style_structs: +${impl_style_struct(style_struct)} +% endfor + +/// Assert that the initial values set in Gecko style struct constructors +/// match the values returned by `get_initial_value()` for each longhand. +#[cfg(feature = "gecko")] +#[inline] +pub fn assert_initial_values_match(data: &PerDocumentStyleData) { + if cfg!(debug_assertions) { + let data = data.borrow(); + let cv = data.stylist.device().default_computed_values(); + <% + # Skip properties with initial values that change at computed + # value time, or whose initial value depends on the document + # / other prefs. + SKIPPED = [ + "border-top-width", + "border-bottom-width", + "border-left-width", + "border-right-width", + "column-rule-width", + "font-family", + "font-size", + "outline-width", + "color", + ] + TO_TEST = [p for p in data.longhands if p.enabled_in != "" and not p.logical and not p.name in SKIPPED] + %> + % for property in TO_TEST: + assert_eq!( + cv.clone_${property.ident}(), + longhands::${property.ident}::get_initial_value(), + concat!( + "initial value in Gecko style struct for ", + stringify!(${property.ident}), + " must match longhands::", + stringify!(${property.ident}), + "::get_initial_value()" + ) + ); + % endfor + } +} diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs new file mode 100644 index 0000000000..968a97aa00 --- /dev/null +++ b/servo/components/style/properties/helpers.mako.rs @@ -0,0 +1,909 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%! + from data import Keyword, to_rust_ident, to_phys, to_camel_case, SYSTEM_FONT_LONGHANDS + from data import (LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, + PHYSICAL_SIDES, LOGICAL_SIZES, LOGICAL_AXES) +%> + +<%def name="predefined_type(name, type, initial_value, parse_method='parse', + vector=False, none_value=None, initial_specified_value=None, + allow_quirks='No', **kwargs)"> + <%def name="predefined_type_inner(name, type, initial_value, parse_method)"> + #[allow(unused_imports)] + use app_units::Au; + #[allow(unused_imports)] + use crate::values::specified::AllowQuirks; + #[allow(unused_imports)] + use crate::Zero; + #[allow(unused_imports)] + use smallvec::SmallVec; + pub use crate::values::specified::${type} as SpecifiedValue; + pub mod computed_value { + pub use crate::values::computed::${type} as T; + } + % if initial_value: + #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} } + % endif + % if initial_specified_value: + #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} } + % endif + #[allow(unused_variables)] + #[inline] + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SpecifiedValue, ParseError<'i>> { + % if allow_quirks != "No": + specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::${allow_quirks}) + % elif parse_method != "parse": + specified::${type}::${parse_method}(context, input) + % else: + <specified::${type} as crate::parser::Parse>::parse(context, input) + % endif + } + </%def> + % if vector: + <%call + expr="vector_longhand(name, predefined_type=type, allow_empty=not initial_value, none_value=none_value, **kwargs)" + > + ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif + </%call> + % else: + <%call expr="longhand(name, predefined_type=type, **kwargs)"> + ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif + </%call> + % endif +</%def> + +// The setup here is roughly: +// +// * UnderlyingList is the list that is stored in the computed value. This may +// be a shared ArcSlice if the property is inherited. +// * UnderlyingOwnedList is the list that is used for animation. +// * Specified values always use OwnedSlice, since it's more compact. +// * computed_value::List is just a convenient alias that you can use for the +// computed value list, since this is in the computed_value module. +// +// If simple_vector_bindings is true, then we don't use the complex iterator +// machinery and set_foo_from, and just compute the value like any other +// longhand. +<%def name="vector_longhand(name, animation_value_type=None, + vector_animation_type=None, allow_empty=False, + none_value=None, + simple_vector_bindings=False, + separator='Comma', + **kwargs)"> + <%call expr="longhand(name, animation_value_type=animation_value_type, vector=True, + simple_vector_bindings=simple_vector_bindings, **kwargs)"> + #[allow(unused_imports)] + use smallvec::SmallVec; + + pub mod single_value { + #[allow(unused_imports)] + use cssparser::{Parser, BasicParseError}; + #[allow(unused_imports)] + use crate::parser::{Parse, ParserContext}; + #[allow(unused_imports)] + use crate::properties::ShorthandId; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use crate::values::computed::{Context, ToComputedValue}; + #[allow(unused_imports)] + use crate::values::{computed, specified}; + ${caller.body()} + } + + /// The definition of the computed value for ${name}. + pub mod computed_value { + #[allow(unused_imports)] + use crate::values::animated::ToAnimatedValue; + #[allow(unused_imports)] + use crate::values::resolved::ToResolvedValue; + pub use super::single_value::computed_value as single_value; + pub use self::single_value::T as SingleComputedValue; + % if not allow_empty: + use smallvec::SmallVec; + % endif + use crate::values::computed::ComputedVecIter; + + <% + is_shared_list = allow_empty and \ + data.longhands_by_name[name].style_struct.inherited + %> + + // FIXME(emilio): Add an OwnedNonEmptySlice type, and figure out + // something for transition-name, which is the only remaining user + // of NotInitial. + pub type UnderlyingList<T> = + % if allow_empty: + % if data.longhands_by_name[name].style_struct.inherited: + crate::ArcSlice<T>; + % else: + crate::OwnedSlice<T>; + % endif + % else: + SmallVec<[T; 1]>; + % endif + + pub type UnderlyingOwnedList<T> = + % if allow_empty: + crate::OwnedSlice<T>; + % else: + SmallVec<[T; 1]>; + % endif + + + /// The generic type defining the animated and resolved values for + /// this property. + /// + /// Making this type generic allows the compiler to figure out the + /// animated value for us, instead of having to implement it + /// manually for every type we care about. + #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToResolvedValue, ToCss)] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct OwnedList<T>( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + pub UnderlyingOwnedList<T>, + ); + + /// The computed value for this property. + % if not is_shared_list: + pub type ComputedList = OwnedList<single_value::T>; + pub use self::OwnedList as List; + % else: + pub use self::ComputedList as List; + + #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct ComputedList( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + % if is_shared_list: + #[ignore_malloc_size_of = "Arc"] + % endif + pub UnderlyingList<single_value::T>, + ); + + type ResolvedList = OwnedList<<single_value::T as ToResolvedValue>::ResolvedValue>; + impl ToResolvedValue for ComputedList { + type ResolvedValue = ResolvedList; + + fn to_resolved_value(self, context: &crate::values::resolved::Context) -> Self::ResolvedValue { + OwnedList( + self.0 + .iter() + .cloned() + .map(|v| v.to_resolved_value(context)) + .collect() + ) + } + + fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { + % if not is_shared_list: + use std::iter::FromIterator; + % endif + let iter = + resolved.0.into_iter().map(ToResolvedValue::from_resolved_value); + ComputedList(UnderlyingList::from_iter(iter)) + } + } + % endif + + % if simple_vector_bindings: + impl From<ComputedList> for UnderlyingList<single_value::T> { + #[inline] + fn from(l: ComputedList) -> Self { + l.0 + } + } + impl From<UnderlyingList<single_value::T>> for ComputedList { + #[inline] + fn from(l: UnderlyingList<single_value::T>) -> Self { + List(l) + } + } + % endif + + % if vector_animation_type: + % if not animation_value_type: + Sorry, this is stupid but needed for now. + % endif + + use crate::values::animated::{Animate, ToAnimatedZero, Procedure, lists}; + use crate::values::distance::{SquaredDistance, ComputeSquaredDistance}; + + // FIXME(emilio): For some reason rust thinks that this alias is + // unused, even though it's clearly used below? + #[allow(unused)] + type AnimatedList = OwnedList<<single_value::T as ToAnimatedValue>::AnimatedValue>; + + % if is_shared_list: + impl ToAnimatedValue for ComputedList { + type AnimatedValue = AnimatedList; + + fn to_animated_value(self) -> Self::AnimatedValue { + OwnedList( + self.0.iter().map(|v| v.clone().to_animated_value()).collect() + ) + } + + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + let iter = + animated.0.into_iter().map(ToAnimatedValue::from_animated_value); + ComputedList(UnderlyingList::from_iter(iter)) + } + } + % endif + + impl ToAnimatedZero for AnimatedList { + fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) } + } + + impl Animate for AnimatedList { + fn animate( + &self, + other: &Self, + procedure: Procedure, + ) -> Result<Self, ()> { + Ok(OwnedList( + lists::${vector_animation_type}::animate(&self.0, &other.0, procedure)? + )) + } + } + impl ComputeSquaredDistance for AnimatedList { + fn compute_squared_distance( + &self, + other: &Self, + ) -> Result<SquaredDistance, ()> { + lists::${vector_animation_type}::squared_distance(&self.0, &other.0) + } + } + % endif + + /// The computed value, effectively a list of single values. + pub use self::ComputedList as T; + + pub type Iter<'a, 'cx, 'cx_a> = ComputedVecIter<'a, 'cx, 'cx_a, super::single_value::SpecifiedValue>; + } + + /// The specified value of ${name}. + #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] + % if none_value: + #[value_info(other_values = "none")] + % endif + % if separator == "Comma": + #[css(comma)] + % endif + pub struct SpecifiedValue( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + pub crate::OwnedSlice<single_value::SpecifiedValue>, + ); + + pub fn get_initial_value() -> computed_value::T { + % if allow_empty: + computed_value::List(Default::default()) + % else: + let mut v = SmallVec::new(); + v.push(single_value::get_initial_value()); + computed_value::List(v) + % endif + } + + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SpecifiedValue, ParseError<'i>> { + use style_traits::Separator; + + % if allow_empty or none_value: + if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + % if allow_empty: + return Ok(SpecifiedValue(Default::default())) + % else: + return Ok(SpecifiedValue(crate::OwnedSlice::from(vec![${none_value}]))) + % endif + } + % endif + + let v = style_traits::${separator}::parse(input, |parser| { + single_value::parse(context, parser) + })?; + Ok(SpecifiedValue(v.into())) + } + + pub use self::single_value::SpecifiedValue as SingleSpecifiedValue; + + % if not simple_vector_bindings and engine == "gecko": + impl SpecifiedValue { + fn compute_iter<'a, 'cx, 'cx_a>( + &'a self, + context: &'cx Context<'cx_a>, + ) -> computed_value::Iter<'a, 'cx, 'cx_a> { + computed_value::Iter::new(context, &self.0) + } + } + % endif + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_value::T { + % if not is_shared_list: + use std::iter::FromIterator; + % endif + computed_value::List(computed_value::UnderlyingList::from_iter( + self.0.iter().map(|i| i.to_computed_value(context)) + )) + } + + #[inline] + fn from_computed_value(computed: &computed_value::T) -> Self { + let iter = computed.0.iter().map(ToComputedValue::from_computed_value); + SpecifiedValue(iter.collect()) + } + } + </%call> +</%def> +<%def name="longhand(*args, **kwargs)"> + <% + property = data.declare_longhand(*args, **kwargs) + if property is None: + return "" + %> + /// ${property.spec} + pub mod ${property.ident} { + #[allow(unused_imports)] + use cssparser::{Parser, BasicParseError, Token}; + #[allow(unused_imports)] + use crate::parser::{Parse, ParserContext}; + #[allow(unused_imports)] + use crate::properties::{UnparsedValue, ShorthandId}; + #[allow(unused_imports)] + use crate::error_reporting::ParseErrorReporter; + #[allow(unused_imports)] + use crate::properties::longhands; + #[allow(unused_imports)] + use crate::properties::{LonghandId, LonghandIdSet}; + #[allow(unused_imports)] + use crate::properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration}; + #[allow(unused_imports)] + use crate::properties::style_structs; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use servo_arc::Arc; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use crate::values::computed::{Context, ToComputedValue}; + #[allow(unused_imports)] + use crate::values::{computed, generics, specified}; + #[allow(unused_imports)] + use crate::Atom; + ${caller.body()} + #[allow(unused_variables)] + pub unsafe fn cascade_property( + declaration: &PropertyDeclaration, + context: &mut computed::Context, + ) { + % if property.logical: + declaration.debug_crash("Should physicalize before entering here"); + % else: + context.for_non_inherited_property = ${"false" if property.style_struct.inherited else "true"}; + % if property.logical_group: + debug_assert_eq!( + declaration.id().as_longhand().unwrap().logical_group(), + LonghandId::${property.camel_case}.logical_group(), + ); + % else: + debug_assert_eq!( + declaration.id().as_longhand().unwrap(), + LonghandId::${property.camel_case}, + ); + % endif + let specified_value = match *declaration { + PropertyDeclaration::CSSWideKeyword(ref wk) => { + match wk.keyword { + % if not property.style_struct.inherited: + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Initial => { + % if not property.style_struct.inherited: + declaration.debug_crash("Unexpected initial or unset for non-inherited property"); + % else: + context.builder.reset_${property.ident}(); + % endif + }, + % if property.style_struct.inherited: + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Inherit => { + % if property.style_struct.inherited: + declaration.debug_crash("Unexpected inherit or unset for inherited property"); + % else: + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + context.builder.inherit_${property.ident}(); + % endif + } + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Revert => { + declaration.debug_crash("Found revert/revert-layer not deal with"); + }, + } + return; + }, + #[cfg(debug_assertions)] + PropertyDeclaration::WithVariables(..) => { + declaration.debug_crash("Found variables not substituted"); + return; + }, + _ => unsafe { + declaration.unchecked_value_as::<${property.specified_type()}>() + }, + }; + + % if property.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko": + if let Some(sf) = specified_value.get_system() { + longhands::system_font::resolve_system_font(sf, context); + } + % endif + + % if property.is_vector and not property.simple_vector_bindings and engine == "gecko": + // In the case of a vector property we want to pass down an + // iterator so that this can be computed without allocation. + // + // However, computing requires a context, but the style struct + // being mutated is on the context. We temporarily remove it, + // mutate it, and then put it back. Vector longhands cannot + // touch their own style struct whilst computing, else this will + // panic. + let mut s = + context.builder.take_${data.current_style_struct.name_lower}(); + { + let iter = specified_value.compute_iter(context); + s.set_${property.ident}(iter); + } + context.builder.put_${data.current_style_struct.name_lower}(s); + % else: + % if property.boxed: + let computed = (**specified_value).to_computed_value(context); + % else: + let computed = specified_value.to_computed_value(context); + % endif + context.builder.set_${property.ident}(computed) + % endif + % endif + } + + pub fn parse_declared<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>> { + % if property.allow_quirks != "No": + parse_quirky(context, input, specified::AllowQuirks::${property.allow_quirks}) + % else: + parse(context, input) + % endif + % if property.boxed: + .map(Box::new) + % endif + .map(PropertyDeclaration::${property.camel_case}) + } + } +</%def> + +<%def name="gecko_keyword_conversion(keyword, values=None, type='SpecifiedValue', cast_to=None)"> + <% + if not values: + values = keyword.values_for(engine) + maybe_cast = "as %s" % cast_to if cast_to else "" + const_type = cast_to if cast_to else "u32" + %> + #[cfg(feature = "gecko")] + impl ${type} { + /// Obtain a specified value from a Gecko keyword value + /// + /// Intended for use with presentation attributes, not style structs + pub fn from_gecko_keyword(kw: u32) -> Self { + use crate::gecko_bindings::structs; + % for value in values: + // We can't match on enum values if we're matching on a u32 + const ${to_rust_ident(value).upper()}: ${const_type} + = structs::${keyword.gecko_constant(value)} as ${const_type}; + % endfor + match kw ${maybe_cast} { + % for value in values: + ${to_rust_ident(value).upper()} => ${type}::${to_camel_case(value)}, + % endfor + _ => panic!("Found unexpected value in style struct for ${keyword.name} property"), + } + } + } +</%def> + +<%def name="gecko_bitflags_conversion(bit_map, gecko_bit_prefix, type, kw_type='u8')"> + #[cfg(feature = "gecko")] + impl ${type} { + /// Obtain a specified value from a Gecko keyword value + /// + /// Intended for use with presentation attributes, not style structs + pub fn from_gecko_keyword(kw: ${kw_type}) -> Self { + % for gecko_bit in bit_map.values(): + use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits = ${type}::empty(); + % for servo_bit, gecko_bit in bit_map.items(): + if kw & (${gecko_bit_prefix}${gecko_bit} as ${kw_type}) != 0 { + bits |= ${servo_bit}; + } + % endfor + bits + } + + pub fn to_gecko_keyword(self) -> ${kw_type} { + % for gecko_bit in bit_map.values(): + use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits: ${kw_type} = 0; + // FIXME: if we ensure that the Servo bitflags storage is the same + // as Gecko's one, we can just copy it. + % for servo_bit, gecko_bit in bit_map.items(): + if self.contains(${servo_bit}) { + bits |= ${gecko_bit_prefix}${gecko_bit} as ${kw_type}; + } + % endfor + bits + } + } +</%def> + +<%def name="single_keyword(name, values, vector=False, + needs_conversion=False, **kwargs)"> + <% + keyword_kwargs = {a: kwargs.pop(a, None) for a in [ + 'gecko_constant_prefix', + 'gecko_enum_prefix', + 'extra_gecko_values', + 'extra_servo_2013_values', + 'extra_servo_2020_values', + 'gecko_aliases', + 'servo_2013_aliases', + 'servo_2020_aliases', + 'custom_consts', + 'gecko_inexhaustive', + 'gecko_strip_moz_prefix', + ]} + %> + + <%def name="inner_body(keyword, needs_conversion=False)"> + pub use self::computed_value::T as SpecifiedValue; + pub mod computed_value { + #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] + #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + pub enum T { + % for variant in keyword.values_for(engine): + <% + aliases = [] + for alias, v in keyword.aliases_for(engine).items(): + if variant == v: + aliases.append(alias) + %> + % if aliases: + #[parse(aliases = "${','.join(sorted(aliases))}")] + % endif + ${to_camel_case(variant)}, + % endfor + } + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::${to_camel_case(values.split()[0])} + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::${to_camel_case(values.split()[0])} + } + #[inline] + pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) + -> Result<SpecifiedValue, ParseError<'i>> { + SpecifiedValue::parse(input) + } + + % if needs_conversion: + <% + conversion_values = keyword.values_for(engine) + list(keyword.aliases_for(engine).keys()) + %> + ${gecko_keyword_conversion(keyword, values=conversion_values)} + % endif + </%def> + % if vector: + <%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + ${inner_body(Keyword(name, values, **keyword_kwargs))} + % if caller: + ${caller.body()} + % endif + </%call> + % else: + <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + ${inner_body(Keyword(name, values, **keyword_kwargs), + needs_conversion=needs_conversion)} + % if caller: + ${caller.body()} + % endif + </%call> + % endif +</%def> + +<%def name="shorthand(name, sub_properties, derive_serialize=False, + derive_value_info=True, **kwargs)"> +<% + shorthand = data.declare_shorthand(name, sub_properties.split(), **kwargs) + # mako doesn't accept non-string value in parameters with <% %> form, so + # we have to workaround it this way. + if not isinstance(derive_value_info, bool): + derive_value_info = eval(derive_value_info) +%> + % if shorthand: + /// ${shorthand.spec} + pub mod ${shorthand.ident} { + use cssparser::Parser; + use crate::parser::ParserContext; + use crate::properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed, longhands}; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use std::fmt::{self, Write}; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use style_traits::{CssWriter, KeywordsCollectFn, SpecifiedValueInfo, ToCss}; + + % if derive_value_info: + #[derive(SpecifiedValueInfo)] + % endif + pub struct Longhands { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: + % if sub_property.boxed: + Box< + % endif + longhands::${sub_property.ident}::SpecifiedValue + % if sub_property.boxed: + > + % endif + , + % endfor + } + + /// Represents a serializable set of all of the longhand properties that + /// correspond to a shorthand. + % if derive_serialize: + #[derive(ToCss)] + % endif + pub struct LonghandsToSerialize<'a> { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: + % if sub_property.may_be_disabled_in(shorthand, engine): + Option< + % endif + &'a longhands::${sub_property.ident}::SpecifiedValue, + % if sub_property.may_be_disabled_in(shorthand, engine): + >, + % endif + % endfor + } + + impl<'a> LonghandsToSerialize<'a> { + /// Tries to get a serializable set of longhands given a set of + /// property declarations. + pub fn from_iter(iter: impl Iterator<Item = &'a PropertyDeclaration>) -> Result<Self, ()> { + // Define all of the expected variables that correspond to the shorthand + % for sub_property in shorthand.sub_properties: + let mut ${sub_property.ident} = + None::< &'a longhands::${sub_property.ident}::SpecifiedValue>; + % endfor + + // Attempt to assign the incoming declarations to the expected variables + for declaration in iter { + match *declaration { + % for sub_property in shorthand.sub_properties: + PropertyDeclaration::${sub_property.camel_case}(ref value) => { + ${sub_property.ident} = Some(value) + }, + % endfor + _ => {} + }; + } + + // If any of the expected variables are missing, return an error + match ( + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + ) { + + ( + % for sub_property in shorthand.sub_properties: + % if sub_property.may_be_disabled_in(shorthand, engine): + ${sub_property.ident}, + % else: + Some(${sub_property.ident}), + % endif + % endfor + ) => + Ok(LonghandsToSerialize { + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + }), + _ => Err(()) + } + } + } + + /// Parse the given shorthand and fill the result into the + /// `declarations` vector. + pub fn parse_into<'i, 't>( + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + #[allow(unused_imports)] + use crate::properties::{NonCustomPropertyId, LonghandId}; + input.parse_entirely(|input| parse_value(context, input)).map(|longhands| { + % for sub_property in shorthand.sub_properties: + % if sub_property.may_be_disabled_in(shorthand, engine): + if NonCustomPropertyId::from(LonghandId::${sub_property.camel_case}) + .allowed_in_ignoring_rule_type(context) { + % endif + declarations.push(PropertyDeclaration::${sub_property.camel_case}( + longhands.${sub_property.ident} + )); + % if sub_property.may_be_disabled_in(shorthand, engine): + } + % endif + % endfor + }) + } + + /// Try to serialize a given shorthand to a string. + pub fn to_css(declarations: &[&PropertyDeclaration], dest: &mut crate::str::CssStringWriter) -> fmt::Result { + match LonghandsToSerialize::from_iter(declarations.iter().cloned()) { + Ok(longhands) => longhands.to_css(&mut CssWriter::new(dest)), + Err(_) => Ok(()) + } + } + + ${caller.body()} + } + % endif +</%def> + +// A shorthand of kind `<property-1> <property-2>?` where both properties have +// the same type. +<%def name="two_properties_shorthand( + name, + first_property, + second_property, + parser_function='crate::parser::Parse::parse', + **kwargs +)"> +<%call expr="self.shorthand(name, sub_properties=' '.join([first_property, second_property]), **kwargs)"> + #[allow(unused_imports)] + use crate::parser::Parse; + #[allow(unused_imports)] + use crate::values::specified; + + fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let parse_one = |c: &ParserContext, input: &mut Parser<'i, 't>| -> Result< + crate::properties::longhands::${to_rust_ident(first_property)}::SpecifiedValue, + ParseError<'i> + > { + ${parser_function}(c, input) + }; + + let first = parse_one(context, input)?; + let second = + input.try_parse(|input| parse_one(context, input)).unwrap_or_else(|_| first.clone()); + Ok(expanded! { + ${to_rust_ident(first_property)}: first, + ${to_rust_ident(second_property)}: second, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let first = &self.${to_rust_ident(first_property)}; + let second = &self.${to_rust_ident(second_property)}; + + first.to_css(dest)?; + if first != second { + dest.write_char(' ')?; + second.to_css(dest)?; + } + Ok(()) + } + } +</%call> +</%def> + +<%def name="four_sides_shorthand(name, sub_property_pattern, + parser_function='crate::parser::Parse::parse', + allow_quirks='No', **kwargs)"> + <% sub_properties=' '.join(sub_property_pattern % side for side in PHYSICAL_SIDES) %> + <%call expr="self.shorthand(name, sub_properties=sub_properties, **kwargs)"> + #[allow(unused_imports)] + use crate::parser::Parse; + use crate::values::generics::rect::Rect; + #[allow(unused_imports)] + use crate::values::specified; + + fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let rect = Rect::parse_with(context, input, |c, i| -> Result< + crate::properties::longhands::${to_rust_ident(sub_property_pattern % "top")}::SpecifiedValue, + ParseError<'i> + > { + % if allow_quirks != "No": + ${parser_function}_quirky(c, i, specified::AllowQuirks::${allow_quirks}) + % else: + ${parser_function}(c, i) + % endif + })?; + Ok(expanded! { + % for index, side in enumerate(["top", "right", "bottom", "left"]): + ${to_rust_ident(sub_property_pattern % side)}: rect.${index}, + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + let rect = Rect::new( + % for side in ["top", "right", "bottom", "left"]: + &self.${to_rust_ident(sub_property_pattern % side)}, + % endfor + ); + rect.to_css(dest) + } + } + </%call> +</%def> diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs new file mode 100644 index 0000000000..290684cdab --- /dev/null +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -0,0 +1,785 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% + from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case + from itertools import groupby +%> + +#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID; +use crate::properties::{ + longhands::{ + self, content_visibility::computed_value::T as ContentVisibility, + visibility::computed_value::T as Visibility, + }, + CSSWideKeyword, NonCustomPropertyId, LonghandId, NonCustomPropertyIterator, + PropertyDeclaration, PropertyDeclarationId, +}; +use std::ptr; +use std::mem; +use fxhash::FxHashMap; +use super::ComputedValues; +use crate::properties::OwnedPropertyDeclarationId; +use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; +use crate::values::animated::effects::AnimatedFilter; +#[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty; +use crate::values::computed::{ClipRect, Context}; +use crate::values::computed::ToComputedValue; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::effects::Filter; +use void::{self, Void}; +use crate::properties_and_values::value::CustomAnimatedValue; + +/// Convert nsCSSPropertyID to TransitionProperty +#[cfg(feature = "gecko")] +#[allow(non_upper_case_globals)] +impl From<nsCSSPropertyID> for TransitionProperty { + fn from(property: nsCSSPropertyID) -> TransitionProperty { + TransitionProperty::NonCustom(NonCustomPropertyId::from_nscsspropertyid(property).unwrap()) + } +} + +/// A collection of AnimationValue that were composed on an element. +/// This HashMap stores the values that are the last AnimationValue to be +/// composed for each TransitionProperty. +pub type AnimationValueMap = FxHashMap<OwnedPropertyDeclarationId, AnimationValue>; + +/// An enum to represent a single computed value belonging to an animated +/// property in order to be interpolated with another one. When interpolating, +/// both values need to belong to the same property. +#[derive(Debug, MallocSizeOf)] +#[repr(u16)] +pub enum AnimationValue { + % for prop in data.longhands: + /// `${prop.name}` + % if prop.animatable and not prop.logical: + ${prop.camel_case}(${prop.animated_type()}), + % else: + ${prop.camel_case}(Void), + % endif + % endfor + /// A custom property. + Custom(CustomAnimatedValue), +} + +<% + animated = [] + unanimated = [] + animated_with_logical = [] + for prop in data.longhands: + if prop.animatable: + animated_with_logical.append(prop) + if prop.animatable and not prop.logical: + animated.append(prop) + else: + unanimated.append(prop) +%> + +#[repr(C)] +struct AnimationValueVariantRepr<T> { + tag: u16, + value: T +} + +impl Clone for AnimationValue { + #[inline] + fn clone(&self) -> Self { + use self::AnimationValue::*; + + <% + [copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())] + %> + + let self_tag = unsafe { *(self as *const _ as *const u16) }; + if self_tag <= LonghandId::${copy[-1].camel_case} as u16 { + #[derive(Clone, Copy)] + #[repr(u16)] + enum CopyVariants { + % for prop in copy: + _${prop.camel_case}(${prop.animated_type()}), + % endfor + } + + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut CopyVariants, + *(self as *const _ as *const CopyVariants), + ); + return out.assume_init(); + } + } + + match *self { + % for ty, props in groupby(others, key=lambda x: x.animated_type()): + <% props = list(props) %> + ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => { + % if len(props) == 1: + ${props[0].camel_case}(value.clone()) + % else: + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>, + AnimationValueVariantRepr { + tag: *(self as *const _ as *const u16), + value: value.clone(), + }, + ); + out.assume_init() + } + % endif + } + % endfor + Custom(ref animated_value) => Custom(animated_value.clone()), + _ => unsafe { debug_unreachable!() } + } + } +} + +impl PartialEq for AnimationValue { + #[inline] + fn eq(&self, other: &Self) -> bool { + use self::AnimationValue::*; + + unsafe { + let this_tag = *(self as *const _ as *const u16); + let other_tag = *(other as *const _ as *const u16); + if this_tag != other_tag { + return false; + } + + match *self { + % for ty, props in groupby(animated, key=lambda x: x.animated_type()): + ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { + let other_repr = + &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); + *this == other_repr.value + } + % endfor + ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { + void::unreachable(void) + }, + AnimationValue::Custom(ref this) => { + let other_repr = + &*(other as *const _ as *const AnimationValueVariantRepr<CustomAnimatedValue>); + *this == other_repr.value + }, + } + } + } +} + +impl AnimationValue { + /// Returns the longhand id this animated value corresponds to. + #[inline] + pub fn id(&self) -> PropertyDeclarationId { + if let AnimationValue::Custom(animated_value) = self { + return PropertyDeclarationId::Custom(&animated_value.name); + } + + let id = unsafe { *(self as *const _ as *const LonghandId) }; + debug_assert_eq!(id, match *self { + % for prop in data.longhands: + % if prop.animatable and not prop.logical: + AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case}, + % else: + AnimationValue::${prop.camel_case}(void) => void::unreachable(void), + % endif + % endfor + AnimationValue::Custom(..) => unsafe { debug_unreachable!() }, + }); + PropertyDeclarationId::Longhand(id) + } + + /// Returns whether this value is interpolable with another one. + pub fn interpolable_with(&self, other: &Self) -> bool { + self.animate(other, Procedure::Interpolate { progress: 0.5 }).is_ok() + } + + /// "Uncompute" this animation value in order to be used inside the CSS + /// cascade. + pub fn uncompute(&self) -> PropertyDeclaration { + use crate::properties::longhands; + use self::AnimationValue::*; + + use super::PropertyDeclarationVariantRepr; + + match *self { + <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %> + % for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc): + <% props = list(props) %> + ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => { + % if not computed: + let ref value = ToAnimatedValue::from_animated_value(value.clone()); + % endif + let value = ${ty}::from_computed_value(&value); + % if boxed: + let value = Box::new(value); + % endif + % if len(props) == 1: + PropertyDeclaration::${props[0].camel_case}(value) + % else: + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>, + PropertyDeclarationVariantRepr { + tag: *(self as *const _ as *const u16), + value, + }, + ); + out.assume_init() + } + % endif + } + % endfor + ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { + void::unreachable(void) + }, + Custom(ref animated_value) => animated_value.to_declaration(), + } + } + + /// Construct an AnimationValue from a property declaration. + pub fn from_declaration( + decl: &PropertyDeclaration, + context: &mut Context, + initial: &ComputedValues, + ) -> Option<Self> { + use super::PropertyDeclarationVariantRepr; + + <% + keyfunc = lambda x: ( + x.specified_type(), + x.animated_type(), + x.boxed, + not x.is_animatable_with_computed_value, + x.style_struct.inherited, + x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko", + ) + %> + + let animatable = match *decl { + % for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc): + ${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => { + let decl_repr = unsafe { + &*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>) + }; + let longhand_id = unsafe { + *(&decl_repr.tag as *const u16 as *const LonghandId) + }; + context.for_non_inherited_property = ${"false" if inherit else "true"}; + % if system: + if let Some(sf) = value.get_system() { + longhands::system_font::resolve_system_font(sf, context) + } + % endif + % if boxed: + let value = (**value).to_computed_value(context); + % else: + let value = value.to_computed_value(context); + % endif + % if to_animated: + let value = value.to_animated_value(); + % endif + + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>, + AnimationValueVariantRepr { + tag: longhand_id.to_physical(context.builder.writing_mode) as u16, + value, + }, + ); + out.assume_init() + } + } + % endfor + PropertyDeclaration::CSSWideKeyword(ref declaration) => { + match declaration.id.to_physical(context.builder.writing_mode) { + // We put all the animatable properties first in the hopes + // that it might increase match locality. + % for prop in data.longhands: + % if prop.animatable and not prop.logical: + LonghandId::${prop.camel_case} => { + // FIXME(emilio, bug 1533327): I think revert (and + // revert-layer) handling is not fine here, but what to + // do instead? + // + // Seems we'd need the computed value as if it was + // revert, somehow. Treating it as `unset` seems fine + // for now... + let style_struct = match declaration.keyword { + % if not prop.style_struct.inherited: + CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Initial => { + initial.get_${prop.style_struct.name_lower}() + }, + % if prop.style_struct.inherited: + CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Inherit => { + context.builder + .get_parent_${prop.style_struct.name_lower}() + }, + }; + let computed = style_struct + % if prop.logical: + .clone_${prop.ident}(context.builder.writing_mode); + % else: + .clone_${prop.ident}(); + % endif + + % if not prop.is_animatable_with_computed_value: + let computed = computed.to_animated_value(); + % endif + AnimationValue::${prop.camel_case}(computed) + }, + % endif + % endfor + % for prop in data.longhands: + % if not prop.animatable or prop.logical: + LonghandId::${prop.camel_case} => return None, + % endif + % endfor + } + }, + PropertyDeclaration::WithVariables(ref declaration) => { + let mut cache = Default::default(); + let substituted = { + let custom_properties = &context.style().custom_properties(); + + debug_assert!( + context.builder.stylist.is_some(), + "Need a Stylist to substitute variables!" + ); + declaration.value.substitute_variables( + declaration.id, + custom_properties, + context.builder.stylist.unwrap(), + context, + &mut cache, + ) + }; + return AnimationValue::from_declaration( + &substituted, + context, + initial, + ) + }, + PropertyDeclaration::Custom(ref declaration) => { + AnimationValue::Custom(CustomAnimatedValue::from_declaration( + declaration, + context, + initial, + )?) + }, + _ => return None // non animatable properties will get included because of shorthands. ignore. + }; + Some(animatable) + } + + /// Get an AnimationValue for an declaration id from a given computed values. + pub fn from_computed_values( + property: PropertyDeclarationId, + style: &ComputedValues, + ) -> Option<Self> { + let property = match property { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(ref name) => { + // FIXME(bug 1869476): This should use a stylist to determine whether the name + // corresponds to an inherited custom property and then choose the + // inherited/non_inherited map accordingly. + let p = &style.custom_properties(); + let value = p.inherited.get(*name).or_else(|| p.non_inherited.get(*name))?; + return Some(AnimationValue::Custom(CustomAnimatedValue::from_computed(name, value))) + } + }; + + Some(match property { + % for prop in data.longhands: + % if prop.animatable and not prop.logical: + LonghandId::${prop.camel_case} => { + let computed = style.clone_${prop.ident}(); + AnimationValue::${prop.camel_case}( + % if prop.is_animatable_with_computed_value: + computed + % else: + computed.to_animated_value() + % endif + ) + } + % endif + % endfor + _ => return None, + }) + } + + /// Update `style` with the value of this `AnimationValue`. + /// + /// SERVO ONLY: This doesn't properly handle things like updating 'em' units + /// when animated font-size. + #[cfg(feature = "servo")] + pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) { + match self { + % for prop in data.longhands: + % if prop.animatable and not prop.logical: + AnimationValue::${prop.camel_case}(ref value) => { + % if not prop.is_animatable_with_computed_value: + let value: longhands::${prop.ident}::computed_value::T = + ToAnimatedValue::from_animated_value(value.clone()); + style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value); + % else: + style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value.clone()); + % endif + } + % else: + AnimationValue::${prop.camel_case}(..) => unreachable!(), + % endif + % endfor + AnimationValue::Custom(..) => unreachable!(), + } + } + + /// As above, but a stub for Gecko. + #[cfg(feature = "gecko")] + pub fn set_in_style_for_servo(&self, _: &mut ComputedValues) { + } +} + +fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> { + if let Procedure::Interpolate { progress } = procedure { + Ok(if progress < 0.5 { this.clone() } else { other.clone() }) + } else { + Err(()) + } +} + +impl Animate for AnimationValue { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + Ok(unsafe { + use self::AnimationValue::*; + + let this_tag = *(self as *const _ as *const u16); + let other_tag = *(other as *const _ as *const u16); + if this_tag != other_tag { + panic!("Unexpected AnimationValue::animate call"); + } + + match *self { + <% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %> + % for (ty, discrete), props in groupby(animated, key=keyfunc): + ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { + let other_repr = + &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); + % if discrete: + let value = animate_discrete(this, &other_repr.value, procedure)?; + % else: + let value = this.animate(&other_repr.value, procedure)?; + % endif + + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>, + AnimationValueVariantRepr { + tag: this_tag, + value, + }, + ); + out.assume_init() + }, + % endfor + ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { + void::unreachable(void) + }, + Custom(ref self_value) => { + let Custom(ref other_value) = *other else { unreachable!() }; + Custom(self_value.animate(other_value, procedure)?) + }, + } + }) + } +} + +<% + nondiscrete = [] + for prop in animated: + if prop.animation_value_type != "discrete": + nondiscrete.append(prop) +%> + +impl ComputeSquaredDistance for AnimationValue { + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + unsafe { + use self::AnimationValue::*; + + let this_tag = *(self as *const _ as *const u16); + let other_tag = *(other as *const _ as *const u16); + if this_tag != other_tag { + panic!("Unexpected AnimationValue::compute_squared_distance call"); + } + + match *self { + % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()): + ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { + let other_repr = + &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); + + this.compute_squared_distance(&other_repr.value) + } + % endfor + _ => Err(()), + } + } + } +} + +impl ToAnimatedZero for AnimationValue { + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + match *self { + % for prop in data.longhands: + % if prop.animatable and not prop.logical and prop.animation_value_type != "discrete": + AnimationValue::${prop.camel_case}(ref base) => { + Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?)) + }, + % endif + % endfor + AnimationValue::Custom(..) => { + // TODO(bug 1869185): For some non-universal registered custom properties, it may make sense to implement this. + Err(()) + }, + _ => Err(()), + } + } +} + +/// <https://drafts.csswg.org/web-animations-1/#animating-visibility> +impl Animate for Visibility { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + match procedure { + Procedure::Interpolate { .. } => { + let (this_weight, other_weight) = procedure.weights(); + match (*self, *other) { + (Visibility::Visible, _) => { + Ok(if this_weight > 0.0 { *self } else { *other }) + }, + (_, Visibility::Visible) => { + Ok(if other_weight > 0.0 { *other } else { *self }) + }, + _ => Err(()), + } + }, + _ => Err(()), + } + } +} + +impl ComputeSquaredDistance for Visibility { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. })) + } +} + +impl ToAnimatedZero for Visibility { + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + Err(()) + } +} + +/// <https://drafts.csswg.org/css-contain-3/#content-visibility-animation> +impl Animate for ContentVisibility { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + match procedure { + Procedure::Interpolate { .. } => { + let (this_weight, other_weight) = procedure.weights(); + match (*self, *other) { + (ContentVisibility::Hidden, _) => { + Ok(if other_weight > 0.0 { *other } else { *self }) + }, + (_, ContentVisibility::Hidden) => { + Ok(if this_weight > 0.0 { *self } else { *other }) + }, + _ => Err(()), + } + }, + _ => Err(()), + } + } +} + +impl ComputeSquaredDistance for ContentVisibility { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. })) + } +} + +impl ToAnimatedZero for ContentVisibility { + #[inline] + fn to_animated_zero(&self) -> Result<Self, ()> { + Err(()) + } +} + +/// <https://drafts.csswg.org/css-transitions/#animtype-rect> +impl Animate for ClipRect { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + use crate::values::computed::LengthOrAuto; + let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| { + let result = this.animate(other, procedure)?; + if let Procedure::Interpolate { .. } = procedure { + return Ok(result); + } + if result.is_auto() { + // FIXME(emilio): Why? A couple SMIL tests fail without this, + // but it seems extremely fishy. + return Err(()); + } + Ok(result) + }; + + Ok(ClipRect { + top: animate_component(&self.top, &other.top)?, + right: animate_component(&self.right, &other.right)?, + bottom: animate_component(&self.bottom, &other.bottom)?, + left: animate_component(&self.left, &other.left)?, + }) + } +} + +<% + FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale', + 'HueRotate', 'Invert', 'Opacity', 'Saturate', + 'Sepia' ] +%> + +/// <https://drafts.fxtf.org/filters/#animation-of-filters> +impl Animate for AnimatedFilter { + fn animate( + &self, + other: &Self, + procedure: Procedure, + ) -> Result<Self, ()> { + use crate::values::animated::animate_multiplicative_factor; + match (self, other) { + % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: + (&Filter::${func}(ref this), &Filter::${func}(ref other)) => { + Ok(Filter::${func}(this.animate(other, procedure)?)) + }, + % endfor + % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']: + (&Filter::${func}(this), &Filter::${func}(other)) => { + Ok(Filter::${func}(animate_multiplicative_factor(this, other, procedure)?)) + }, + % endfor + % if engine == "gecko": + (&Filter::DropShadow(ref this), &Filter::DropShadow(ref other)) => { + Ok(Filter::DropShadow(this.animate(other, procedure)?)) + }, + % endif + _ => Err(()), + } + } +} + +/// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation> +impl ToAnimatedZero for AnimatedFilter { + fn to_animated_zero(&self) -> Result<Self, ()> { + match *self { + % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: + Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)), + % endfor + % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']: + Filter::${func}(_) => Ok(Filter::${func}(1.)), + % endfor + % if engine == "gecko": + Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)), + % endif + _ => Err(()), + } + } +} + +/// An iterator over all the properties that transition on a given style. +pub struct TransitionPropertyIterator<'a> { + style: &'a ComputedValues, + index_range: core::ops::Range<usize>, + longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>, +} + +impl<'a> TransitionPropertyIterator<'a> { + /// Create a `TransitionPropertyIterator` for the given style. + pub fn from_style(style: &'a ComputedValues) -> Self { + Self { + style, + index_range: 0..style.get_ui().transition_property_count(), + longhand_iterator: None, + } + } +} + +/// A single iteration of the TransitionPropertyIterator. +pub struct TransitionPropertyIteration { + /// The id of the longhand for this property. + pub longhand_id: LonghandId, + + /// The index of this property in the list of transition properties for this + /// iterator's style. + pub index: usize, +} + +impl<'a> Iterator for TransitionPropertyIterator<'a> { + type Item = TransitionPropertyIteration; + + fn next(&mut self) -> Option<Self::Item> { + use crate::values::computed::TransitionProperty; + loop { + if let Some(ref mut longhand_iterator) = self.longhand_iterator { + if let Some(longhand_id) = longhand_iterator.next() { + return Some(TransitionPropertyIteration { + longhand_id, + index: self.index_range.start - 1, + }); + } + self.longhand_iterator = None; + } + + let index = self.index_range.next()?; + match self.style.get_ui().transition_property_at(index) { + TransitionProperty::NonCustom(id) => { + match id.longhand_or_shorthand() { + Ok(longhand_id) => { + return Some(TransitionPropertyIteration { + longhand_id, + index, + }); + }, + Err(shorthand_id) => { + // In the other cases, we set up our state so that we are ready to + // compute the next value of the iterator and then loop (equivalent + // to calling self.next()). + self.longhand_iterator = Some(shorthand_id.longhands()); + }, + } + } + TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {} + } + } + } +} diff --git a/servo/components/style/properties/longhands/background.mako.rs b/servo/components/style/properties/longhands/background.mako.rs new file mode 100644 index 0000000000..48270f748e --- /dev/null +++ b/servo/components/style/properties/longhands/background.mako.rs @@ -0,0 +1,126 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("Background", inherited=False) %> + +${helpers.predefined_type( + "background-color", + "Color", + "computed::Color::TRANSPARENT_BLACK", + engines="gecko servo-2013 servo-2020", + initial_specified_value="SpecifiedValue::transparent()", + spec="https://drafts.csswg.org/css-backgrounds/#background-color", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + allow_quirks="Yes", + flags="CAN_ANIMATE_ON_COMPOSITOR", + affects="paint", +)} + +${helpers.predefined_type( + "background-image", + "Image", + engines="gecko servo-2013 servo-2020", + initial_value="computed::Image::None", + initial_specified_value="specified::Image::None", + spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", + vector="True", + animation_value_type="discrete", + ignored_when_colors_disabled="True", + affects="paint", +)} + +% for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]: + ${helpers.predefined_type( + "background-position-" + axis, + "position::" + direction + "Position", + "computed::LengthPercentage::zero_percent()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="SpecifiedValue::initial_specified_value()", + spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis, + animation_value_type="ComputedValue", + vector=True, + vector_animation_type="repeatable_list", + affects="paint", + )} +% endfor + +${helpers.predefined_type( + "background-repeat", + "BackgroundRepeat", + "computed::BackgroundRepeat::repeat()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::BackgroundRepeat::repeat()", + animation_value_type="discrete", + vector=True, + spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat", + affects="paint", +)} + +${helpers.single_keyword( + "background-attachment", + "scroll" + (" fixed" if engine in ["gecko", "servo-2013"] else "") + (" local" if engine == "gecko" else ""), + engines="gecko servo-2013 servo-2020", + vector=True, + gecko_enum_prefix="StyleImageLayerAttachment", + spec="https://drafts.csswg.org/css-backgrounds/#the-background-attachment", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.single_keyword( + "background-clip", + "border-box padding-box content-box", + engines="gecko servo-2013 servo-2020", + extra_gecko_values="text", + vector=True, extra_prefixes="webkit", + gecko_enum_prefix="StyleGeometryBox", + gecko_inexhaustive=True, + spec="https://drafts.csswg.org/css-backgrounds/#the-background-clip", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.single_keyword( + "background-origin", + "padding-box border-box content-box", + engines="gecko servo-2013 servo-2020", + vector=True, extra_prefixes="webkit", + gecko_enum_prefix="StyleGeometryBox", + gecko_inexhaustive=True, + spec="https://drafts.csswg.org/css-backgrounds/#the-background-origin", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "background-size", + "BackgroundSize", + engines="gecko servo-2013 servo-2020", + initial_value="computed::BackgroundSize::auto()", + initial_specified_value="specified::BackgroundSize::auto()", + spec="https://drafts.csswg.org/css-backgrounds/#the-background-size", + vector=True, + vector_animation_type="repeatable_list", + animation_value_type="BackgroundSizeList", + extra_prefixes="webkit", + affects="paint", +)} + +// https://drafts.fxtf.org/compositing/#background-blend-mode +${helpers.single_keyword( + "background-blend-mode", + """normal multiply screen overlay darken lighten color-dodge + color-burn hard-light soft-light difference exclusion hue + saturation color luminosity""", + gecko_enum_prefix="StyleBlend", + vector=True, + engines="gecko", + animation_value_type="discrete", + gecko_inexhaustive=True, + spec="https://drafts.fxtf.org/compositing/#background-blend-mode", + affects="paint", +)} diff --git a/servo/components/style/properties/longhands/border.mako.rs b/servo/components/style/properties/longhands/border.mako.rs new file mode 100644 index 0000000000..4d0676f678 --- /dev/null +++ b/servo/components/style/properties/longhands/border.mako.rs @@ -0,0 +1,170 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import Keyword, Method, ALL_CORNERS, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %> + +<% data.new_style_struct("Border", inherited=False, + additional_methods=[Method("border_" + side + "_has_nonzero_width", + "bool") for side in ["top", "right", "bottom", "left"]]) %> +<% + def maybe_logical_spec(side, kind): + if side[1]: # if it is logical + return "https://drafts.csswg.org/css-logical-props/#propdef-border-%s-%s" % (side[0], kind) + else: + return "https://drafts.csswg.org/css-backgrounds/#border-%s-%s" % (side[0], kind) +%> +% for side in ALL_SIDES: + <% + side_name = side[0] + is_logical = side[1] + %> + ${helpers.predefined_type( + "border-%s-color" % side_name, "Color", + "computed_value::T::currentcolor()", + engines="gecko servo-2013 servo-2020", + aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-color"), + spec=maybe_logical_spec(side, "color"), + animation_value_type="AnimatedColor", + logical=is_logical, + logical_group="border-color", + allow_quirks="No" if is_logical else "Yes", + ignored_when_colors_disabled=True, + affects="paint", + )} + + ${helpers.predefined_type( + "border-%s-style" % side_name, "BorderStyle", + "specified::BorderStyle::None", + engines="gecko servo-2013 servo-2020", + aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-style"), + spec=maybe_logical_spec(side, "style"), + animation_value_type="discrete" if not is_logical else "none", + logical=is_logical, + logical_group="border-style", + affects="layout", + )} + + ${helpers.predefined_type( + "border-%s-width" % side_name, + "BorderSideWidth", + "app_units::Au::from_px(3)", + engines="gecko servo-2013 servo-2020", + aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-width"), + spec=maybe_logical_spec(side, "width"), + animation_value_type="NonNegativeLength", + logical=is_logical, + logical_group="border-width", + allow_quirks="No" if is_logical else "Yes", + servo_restyle_damage="reflow rebuild_and_reflow_inline", + affects="layout", + )} +% endfor + +% for corner in ALL_CORNERS: + <% + corner_name = corner[0] + is_logical = corner[1] + if is_logical: + prefixes = None + else: + prefixes = "webkit" + %> + ${helpers.predefined_type( + "border-%s-radius" % corner_name, + "BorderCornerRadius", + "computed::BorderCornerRadius::zero()", + "parse", + engines="gecko servo-2013 servo-2020", + extra_prefixes=prefixes, + spec=maybe_logical_spec(corner, "radius"), + boxed=True, + animation_value_type="BorderCornerRadius", + logical_group="border-radius", + logical=is_logical, + affects="paint", + )} +% endfor + +${helpers.single_keyword( + "box-decoration-break", + "slice clone", + engines="gecko", + gecko_enum_prefix="StyleBoxDecorationBreak", + spec="https://drafts.csswg.org/css-break/#propdef-box-decoration-break", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.single_keyword( + "-moz-float-edge", + "content-box margin-box", + engines="gecko", + gecko_ffi_name="mFloatEdge", + gecko_enum_prefix="StyleFloatEdge", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-float-edge)", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "border-image-source", + "Image", + engines="gecko servo-2013 servo-2020", + initial_value="computed::Image::None", + initial_specified_value="specified::Image::None", + spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", + vector=False, + animation_value_type="discrete", + boxed=engine == "servo-2013", + ignored_when_colors_disabled=True, + affects="paint", +)} + +${helpers.predefined_type( + "border-image-outset", + "NonNegativeLengthOrNumberRect", + engines="gecko servo-2013 servo-2020", + initial_value="generics::rect::Rect::all(computed::NonNegativeLengthOrNumber::zero())", + initial_specified_value="generics::rect::Rect::all(specified::NonNegativeLengthOrNumber::zero())", + spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset", + animation_value_type="NonNegativeLengthOrNumberRect", + boxed=True, + affects="paint", +)} + +${helpers.predefined_type( + "border-image-repeat", + "BorderImageRepeat", + "computed::BorderImageRepeat::stretch()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::BorderImageRepeat::stretch()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat", + affects="paint", +)} + +${helpers.predefined_type( + "border-image-width", + "BorderImageWidth", + engines="gecko servo-2013 servo-2020", + initial_value="computed::BorderImageWidth::all(computed::BorderImageSideWidth::one())", + initial_specified_value="specified::BorderImageWidth::all(specified::BorderImageSideWidth::one())", + spec="https://drafts.csswg.org/css-backgrounds/#border-image-width", + animation_value_type="BorderImageWidth", + boxed=True, + affects="paint", +)} + +${helpers.predefined_type( + "border-image-slice", + "BorderImageSlice", + engines="gecko servo-2013 servo-2020", + initial_value="computed::BorderImageSlice::hundred_percent()", + initial_specified_value="specified::BorderImageSlice::hundred_percent()", + spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice", + animation_value_type="BorderImageSlice", + boxed=True, + affects="paint", +)} diff --git a/servo/components/style/properties/longhands/box.mako.rs b/servo/components/style/properties/longhands/box.mako.rs new file mode 100644 index 0000000000..017bef38ea --- /dev/null +++ b/servo/components/style/properties/longhands/box.mako.rs @@ -0,0 +1,644 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%> + +<% data.new_style_struct("Box", + inherited=False, + gecko_name="Display") %> + +${helpers.predefined_type( + "display", + "Display", + "computed::Display::inline()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Display::inline()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-display/#propdef-display", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.single_keyword( + "-moz-top-layer", + "none top", + engines="gecko", + gecko_enum_prefix="StyleTopLayer", + gecko_ffi_name="mTopLayer", + animation_value_type="none", + enabled_in="ua", + spec="Internal (not web-exposed)", + affects="layout", +)} + +// An internal-only property for elements in a top layer +// https://fullscreen.spec.whatwg.org/#top-layer +${helpers.single_keyword( + "-servo-top-layer", + "none top", + engines="servo-2013 servo-2020", + animation_value_type="none", + enabled_in="ua", + spec="Internal (not web-exposed)", + affects="layout", +)} + +<%helpers:single_keyword + name="position" + values="static absolute relative fixed ${'sticky' if engine in ['gecko', 'servo-2013'] else ''}" + engines="gecko servo-2013 servo-2020" + animation_value_type="discrete" + gecko_enum_prefix="StylePositionProperty" + spec="https://drafts.csswg.org/css-position/#position-property" + servo_restyle_damage="rebuild_and_reflow" + affects="layout" +> +impl computed_value::T { + pub fn is_absolutely_positioned(self) -> bool { + matches!(self, Self::Absolute | Self::Fixed) + } + pub fn is_relative(self) -> bool { + self == Self::Relative + } +} +</%helpers:single_keyword> + +${helpers.predefined_type( + "float", + "Float", + "computed::Float::None", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + initial_specified_value="specified::Float::None", + spec="https://drafts.csswg.org/css-box/#propdef-float", + animation_value_type="discrete", + servo_restyle_damage="rebuild_and_reflow", + gecko_ffi_name="mFloat", + affects="layout", +)} + +${helpers.predefined_type( + "clear", + "Clear", + "computed::Clear::None", + engines="gecko servo-2013", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css2/#propdef-clear", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "vertical-align", + "VerticalAlign", + "computed::VerticalAlign::baseline()", + engines="gecko servo-2013", + animation_value_type="ComputedValue", + spec="https://www.w3.org/TR/CSS2/visudet.html#propdef-vertical-align", + servo_restyle_damage = "reflow", + affects="layout", +)} + +${helpers.predefined_type( + "baseline-source", + "BaselineSource", + "computed::BaselineSource::Auto", + engines="gecko servo-2013", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-inline-3/#baseline-source", + servo_restyle_damage = "reflow", + affects="layout", +)} + +// CSS 2.1, Section 11 - Visual effects + +${helpers.single_keyword( + "-servo-overflow-clip-box", + "padding-box content-box", + engines="servo-2013", + animation_value_type="none", + enabled_in="ua", + spec="Internal, not web-exposed, \ + may be standardized in the future (https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)", + affects="layout", +)} + +% for direction in ["inline", "block"]: + ${helpers.predefined_type( + "overflow-clip-box-" + direction, + "OverflowClipBox", + "computed::OverflowClipBox::PaddingBox", + engines="gecko", + enabled_in="ua", + gecko_pref="layout.css.overflow-clip-box.enabled", + animation_value_type="discrete", + spec="Internal, may be standardized in the future: \ + https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box", + affects="layout", + )} +% endfor + +% for (axis, logical) in ALL_AXES: + <% full_name = "overflow-{}".format(axis) %> + ${helpers.predefined_type( + full_name, + "Overflow", + "computed::Overflow::Visible", + engines="gecko servo-2013 servo-2020", + logical_group="overflow", + logical=logical, + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-overflow-3/#propdef-{}".format(full_name), + servo_restyle_damage = "reflow", + affects="layout", + )} +% endfor + +${helpers.predefined_type( + "overflow-anchor", + "OverflowAnchor", + "computed::OverflowAnchor::Auto", + engines="gecko", + initial_specified_value="specified::OverflowAnchor::Auto", + gecko_pref="layout.css.scroll-anchoring.enabled", + spec="https://drafts.csswg.org/css-scroll-anchoring/#exclusion-api", + animation_value_type="discrete", + affects="", +)} + +<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %> + +${helpers.predefined_type( + "transform", + "Transform", + "generics::transform::Transform::none()", + engines="gecko servo-2013 servo-2020", + extra_prefixes=transform_extra_prefixes, + animation_value_type="ComputedValue", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.csswg.org/css-transforms/#propdef-transform", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +${helpers.predefined_type( + "rotate", + "Rotate", + "generics::transform::Rotate::None", + engines="gecko servo-2013", + animation_value_type="ComputedValue", + boxed=True, + flags="CAN_ANIMATE_ON_COMPOSITOR", + gecko_pref="layout.css.individual-transform.enabled", + spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", + servo_restyle_damage = "reflow_out_of_flow", + affects="overflow", +)} + +${helpers.predefined_type( + "scale", + "Scale", + "generics::transform::Scale::None", + engines="gecko servo-2013", + animation_value_type="ComputedValue", + boxed=True, + flags="CAN_ANIMATE_ON_COMPOSITOR", + gecko_pref="layout.css.individual-transform.enabled", + spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", + servo_restyle_damage = "reflow_out_of_flow", + affects="overflow", +)} + +${helpers.predefined_type( + "translate", + "Translate", + "generics::transform::Translate::None", + engines="gecko servo-2013", + animation_value_type="ComputedValue", + boxed=True, + flags="CAN_ANIMATE_ON_COMPOSITOR", + gecko_pref="layout.css.individual-transform.enabled", + spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +// Motion Path Module Level 1 +${helpers.predefined_type( + "offset-path", + "OffsetPath", + "computed::OffsetPath::none()", + engines="gecko", + animation_value_type="motion::OffsetPath", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.fxtf.org/motion-1/#offset-path-property", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +// Motion Path Module Level 1 +${helpers.predefined_type( + "offset-distance", + "LengthPercentage", + "computed::LengthPercentage::zero()", + engines="gecko", + animation_value_type="ComputedValue", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.fxtf.org/motion-1/#offset-distance-property", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +// Motion Path Module Level 1 +${helpers.predefined_type( + "offset-rotate", + "OffsetRotate", + "computed::OffsetRotate::auto()", + engines="gecko", + animation_value_type="ComputedValue", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.fxtf.org/motion-1/#offset-rotate-property", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +// Motion Path Module Level 1 +${helpers.predefined_type( + "offset-anchor", + "PositionOrAuto", + "computed::PositionOrAuto::auto()", + engines="gecko", + animation_value_type="ComputedValue", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.fxtf.org/motion-1/#offset-anchor-property", + servo_restyle_damage="reflow_out_of_flow", + boxed=True, + affects="overflow", +)} + +// Motion Path Module Level 1 +${helpers.predefined_type( + "offset-position", + "OffsetPosition", + "computed::OffsetPosition::normal()", + engines="gecko", + animation_value_type="ComputedValue", + gecko_pref="layout.css.motion-path-offset-position.enabled", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.fxtf.org/motion-1/#offset-position-property", + servo_restyle_damage="reflow_out_of_flow", + boxed=True, + affects="overflow", +)} + +// CSSOM View Module +// https://www.w3.org/TR/cssom-view-1/ +${helpers.single_keyword( + "scroll-behavior", + "auto smooth", + engines="gecko", + spec="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior", + animation_value_type="discrete", + gecko_enum_prefix="StyleScrollBehavior", + affects="", +)} + +${helpers.predefined_type( + "scroll-snap-align", + "ScrollSnapAlign", + "computed::ScrollSnapAlign::none()", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-align", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "scroll-snap-type", + "ScrollSnapType", + "computed::ScrollSnapType::none()", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "scroll-snap-stop", + "ScrollSnapStop", + "computed::ScrollSnapStop::Normal", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop", + animation_value_type="discrete", + affects="paint", +)} + +% for (axis, logical) in ALL_AXES: + ${helpers.predefined_type( + "overscroll-behavior-" + axis, + "OverscrollBehavior", + "computed::OverscrollBehavior::Auto", + engines="gecko", + logical_group="overscroll-behavior", + logical=logical, + gecko_pref="layout.css.overscroll-behavior.enabled", + spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", + animation_value_type="discrete", + affects="paint", + )} +% endfor + +// Compositing and Blending Level 1 +// http://www.w3.org/TR/compositing-1/ +${helpers.single_keyword( + "isolation", + "auto isolate", + engines="gecko", + spec="https://drafts.fxtf.org/compositing/#isolation", + gecko_enum_prefix="StyleIsolation", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "break-after", + "BreakBetween", + "computed::BreakBetween::Auto", + engines="gecko", + spec="https://drafts.csswg.org/css-break/#propdef-break-after", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "break-before", + "BreakBetween", + "computed::BreakBetween::Auto", + engines="gecko", + spec="https://drafts.csswg.org/css-break/#propdef-break-before", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "break-inside", + "BreakWithin", + "computed::BreakWithin::Auto", + engines="gecko", + spec="https://drafts.csswg.org/css-break/#propdef-break-inside", + animation_value_type="discrete", + affects="layout", +)} + +// CSS Basic User Interface Module Level 3 +// http://dev.w3.org/csswg/css-ui +${helpers.predefined_type( + "resize", + "Resize", + "computed::Resize::None", + engines="gecko", + animation_value_type="discrete", + gecko_ffi_name="mResize", + spec="https://drafts.csswg.org/css-ui/#propdef-resize", + affects="layout", +)} + +${helpers.predefined_type( + "perspective", + "Perspective", + "computed::Perspective::none()", + engines="gecko servo-2013 servo-2020", + gecko_ffi_name="mChildPerspective", + spec="https://drafts.csswg.org/css-transforms/#perspective", + extra_prefixes=transform_extra_prefixes, + animation_value_type="AnimatedPerspective", + servo_restyle_damage = "reflow_out_of_flow", + affects="overflow", +)} + +${helpers.predefined_type( + "perspective-origin", + "Position", + "computed::position::Position::center()", + engines="gecko servo-2013 servo-2020", + boxed=True, + extra_prefixes=transform_extra_prefixes, + spec="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property", + animation_value_type="ComputedValue", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +${helpers.single_keyword( + "backface-visibility", + "visible hidden", + engines="gecko servo-2013 servo-2020", + gecko_enum_prefix="StyleBackfaceVisibility", + spec="https://drafts.csswg.org/css-transforms/#backface-visibility-property", + extra_prefixes=transform_extra_prefixes, + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "transform-box", + "TransformBox", + "computed::TransformBox::ViewBox", + engines="gecko", + spec="https://drafts.csswg.org/css-transforms/#transform-box", + animation_value_type="discrete", + affects="overflow", +)} + +${helpers.predefined_type( + "transform-style", + "TransformStyle", + "computed::TransformStyle::Flat", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-transforms-2/#transform-style-property", + extra_prefixes=transform_extra_prefixes, + animation_value_type="discrete", + servo_restyle_damage = "reflow_out_of_flow", + affects="overflow", +)} + +${helpers.predefined_type( + "transform-origin", + "TransformOrigin", + "computed::TransformOrigin::initial_value()", + engines="gecko servo-2013 servo-2020", + animation_value_type="ComputedValue", + extra_prefixes=transform_extra_prefixes, + gecko_ffi_name="mTransformOrigin", + boxed=True, + spec="https://drafts.csswg.org/css-transforms/#transform-origin-property", + servo_restyle_damage="reflow_out_of_flow", + affects="overflow", +)} + +${helpers.predefined_type( + "contain", + "Contain", + "specified::Contain::empty()", + engines="gecko", + animation_value_type="none", + spec="https://drafts.csswg.org/css-contain/#contain-property", + affects="layout", +)} + +${helpers.predefined_type( + "content-visibility", + "ContentVisibility", + "computed::ContentVisibility::Visible", + engines="gecko", + spec="https://drafts.csswg.org/css-contain/#content-visibility", + gecko_pref="layout.css.content-visibility.enabled", + animation_value_type="ComputedValue", + affects="layout", +)} + +${helpers.predefined_type( + "container-type", + "ContainerType", + "computed::ContainerType::Normal", + engines="gecko", + animation_value_type="none", + enabled_in="ua", + gecko_pref="layout.css.container-queries.enabled", + spec="https://drafts.csswg.org/css-contain-3/#container-type", + affects="layout", +)} + +${helpers.predefined_type( + "container-name", + "ContainerName", + "computed::ContainerName::none()", + engines="gecko", + animation_value_type="none", + enabled_in="ua", + gecko_pref="layout.css.container-queries.enabled", + spec="https://drafts.csswg.org/css-contain-3/#container-name", + affects="", +)} + +${helpers.predefined_type( + "appearance", + "Appearance", + "computed::Appearance::None", + engines="gecko", + aliases="-moz-appearance -webkit-appearance", + spec="https://drafts.csswg.org/css-ui-4/#propdef-appearance", + animation_value_type="discrete", + gecko_ffi_name="mAppearance", + affects="paint", +)} + +// The inherent widget type of an element, selected by specifying +// `appearance: auto`. +${helpers.predefined_type( + "-moz-default-appearance", + "Appearance", + "computed::Appearance::None", + engines="gecko", + animation_value_type="none", + spec="Internal (not web-exposed)", + enabled_in="chrome", + gecko_ffi_name="mDefaultAppearance", + affects="paint", +)} + +${helpers.single_keyword( + "-moz-orient", + "inline block horizontal vertical", + engines="gecko", + gecko_ffi_name="mOrient", + gecko_enum_prefix="StyleOrient", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-orient)", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "will-change", + "WillChange", + "computed::WillChange::auto()", + engines="gecko", + animation_value_type="none", + spec="https://drafts.csswg.org/css-will-change/#will-change", + affects="layout", +)} + +// The spec issue for the parse_method: https://github.com/w3c/csswg-drafts/issues/4102. +${helpers.predefined_type( + "shape-image-threshold", + "Opacity", + "0.0", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-shapes/#shape-image-threshold-property", + affects="layout", +)} + +${helpers.predefined_type( + "shape-margin", + "NonNegativeLengthPercentage", + "computed::NonNegativeLengthPercentage::zero()", + engines="gecko", + animation_value_type="NonNegativeLengthPercentage", + spec="https://drafts.csswg.org/css-shapes/#shape-margin-property", + affects="layout", +)} + +${helpers.predefined_type( + "shape-outside", + "basic_shape::ShapeOutside", + "generics::basic_shape::ShapeOutside::None", + engines="gecko", + animation_value_type="basic_shape::ShapeOutside", + spec="https://drafts.csswg.org/css-shapes/#shape-outside-property", + affects="layout", +)} + +${helpers.predefined_type( + "touch-action", + "TouchAction", + "computed::TouchAction::auto()", + engines="gecko", + animation_value_type="discrete", + spec="https://compat.spec.whatwg.org/#touch-action", + affects="paint", +)} + +${helpers.predefined_type( + "-webkit-line-clamp", + "LineClamp", + "computed::LineClamp::none()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-overflow-3/#line-clamp", + affects="layout", +)} + +${helpers.predefined_type( + "scrollbar-gutter", + "ScrollbarGutter", + "computed::ScrollbarGutter::AUTO", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property", + affects="layout", +)} + +${helpers.predefined_type( + "zoom", + "Zoom", + "computed::box_::Zoom::ONE", + engines="gecko", + animation_value_type="Number", + spec="Non-standard (https://github.com/atanassov/css-zoom/ is the closest)", + gecko_pref="layout.css.zoom.enabled", + affects="layout", + enabled_in="chrome", +)} diff --git a/servo/components/style/properties/longhands/column.mako.rs b/servo/components/style/properties/longhands/column.mako.rs new file mode 100644 index 0000000000..38c32938c6 --- /dev/null +++ b/servo/components/style/properties/longhands/column.mako.rs @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("Column", inherited=False) %> + +${helpers.predefined_type( + "column-width", + "length::NonNegativeLengthOrAuto", + "computed::length::NonNegativeLengthOrAuto::auto()", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + initial_specified_value="specified::length::NonNegativeLengthOrAuto::auto()", + animation_value_type="NonNegativeLengthOrAuto", + servo_2013_pref="layout.columns.enabled", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-width", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "column-count", + "ColumnCount", + "computed::ColumnCount::auto()", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + initial_specified_value="specified::ColumnCount::auto()", + servo_2013_pref="layout.columns.enabled", + animation_value_type="AnimatedColumnCount", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-count", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.single_keyword( + "column-fill", + "balance auto", + engines="gecko", + animation_value_type="discrete", + gecko_enum_prefix="StyleColumnFill", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill", + affects="layout", +)} + +${helpers.predefined_type( + "column-rule-width", + "BorderSideWidth", + "app_units::Au::from_px(3)", + engines="gecko", + initial_specified_value="specified::BorderSideWidth::medium()", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width", + animation_value_type="NonNegativeLength", + affects="layout", +)} + +// https://drafts.csswg.org/css-multicol-1/#crc +${helpers.predefined_type( + "column-rule-color", + "Color", + "computed_value::T::currentcolor()", + engines="gecko", + initial_specified_value="specified::Color::currentcolor()", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color", + affects="paint", +)} + +${helpers.single_keyword( + "column-span", + "none all", + engines="gecko", + animation_value_type="discrete", + gecko_enum_prefix="StyleColumnSpan", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-span", + affects="layout", +)} + +${helpers.predefined_type( + "column-rule-style", + "BorderStyle", + "computed::BorderStyle::None", + engines="gecko", + initial_specified_value="specified::BorderStyle::None", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-style", + affects="paint", +)} diff --git a/servo/components/style/properties/longhands/counters.mako.rs b/servo/components/style/properties/longhands/counters.mako.rs new file mode 100644 index 0000000000..6c844c3567 --- /dev/null +++ b/servo/components/style/properties/longhands/counters.mako.rs @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %> + +${helpers.predefined_type( + "content", + "Content", + "computed::Content::normal()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Content::normal()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-content/#propdef-content", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "counter-increment", + "CounterIncrement", + engines="gecko servo-2013", + initial_value="Default::default()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "counter-reset", + "CounterReset", + engines="gecko servo-2013", + initial_value="Default::default()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "counter-set", + "CounterSet", + engines="gecko", + initial_value="Default::default()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-set", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/effects.mako.rs b/servo/components/style/properties/longhands/effects.mako.rs new file mode 100644 index 0000000000..b301aab5dd --- /dev/null +++ b/servo/components/style/properties/longhands/effects.mako.rs @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +// Box-shadow, etc. +<% data.new_style_struct("Effects", inherited=False) %> + +${helpers.predefined_type( + "opacity", + "Opacity", + "1.0", + engines="gecko servo-2013 servo-2020", + animation_value_type="ComputedValue", + flags="CAN_ANIMATE_ON_COMPOSITOR", + spec="https://drafts.csswg.org/css-color/#transparency", + servo_restyle_damage = "reflow_out_of_flow", + affects="paint", +)} + +${helpers.predefined_type( + "box-shadow", + "BoxShadow", + None, + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + vector=True, + simple_vector_bindings=True, + animation_value_type="AnimatedBoxShadowList", + vector_animation_type="with_zero", + extra_prefixes="webkit", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-backgrounds/#box-shadow", + affects="overflow", +)} + +${helpers.predefined_type( + "clip", + "ClipRectOrAuto", + "computed::ClipRectOrAuto::auto()", + engines="gecko servo-2013 servo-2020", + animation_value_type="ComputedValue", + boxed=True, + allow_quirks="Yes", + spec="https://drafts.fxtf.org/css-masking/#clip-property", + affects="overflow", +)} + +${helpers.predefined_type( + "filter", + "Filter", + None, + engines="gecko servo-2013 servo-2020", + vector=True, + simple_vector_bindings=True, + gecko_ffi_name="mFilters", + separator="Space", + animation_value_type="AnimatedFilterList", + vector_animation_type="with_zero", + extra_prefixes="webkit", + spec="https://drafts.fxtf.org/filters/#propdef-filter", + affects="overflow", +)} + +${helpers.predefined_type( + "backdrop-filter", + "Filter", + None, + engines="gecko", + vector=True, + simple_vector_bindings=True, + gecko_ffi_name="mBackdropFilters", + separator="Space", + animation_value_type="AnimatedFilterList", + vector_animation_type="with_zero", + gecko_pref="layout.css.backdrop-filter.enabled", + spec="https://drafts.fxtf.org/filter-effects-2/#propdef-backdrop-filter", + affects="overflow", +)} + +${helpers.single_keyword( + "mix-blend-mode", + """normal multiply screen overlay darken lighten color-dodge + color-burn hard-light soft-light difference exclusion hue + saturation color luminosity plus-lighter""", + engines="gecko servo-2013 servo-2020", + gecko_enum_prefix="StyleBlend", + animation_value_type="discrete", + spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode", + affects="paint", +)} diff --git a/servo/components/style/properties/longhands/font.mako.rs b/servo/components/style/properties/longhands/font.mako.rs new file mode 100644 index 0000000000..f188af5b1f --- /dev/null +++ b/servo/components/style/properties/longhands/font.mako.rs @@ -0,0 +1,505 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import Method, to_camel_case, to_rust_ident, to_camel_case_lower, SYSTEM_FONT_LONGHANDS %> + +<% data.new_style_struct("Font", inherited=True) %> + +${helpers.predefined_type( + "font-family", + "FontFamily", + engines="gecko servo-2013 servo-2020", + initial_value="computed::FontFamily::serif()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-family", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "font-style", + "FontStyle", + engines="gecko servo-2013 servo-2020", + initial_value="computed::FontStyle::normal()", + initial_specified_value="specified::FontStyle::normal()", + animation_value_type="FontStyle", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-style", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +<% font_variant_caps_custom_consts= { "small-caps": "SMALLCAPS", + "all-small-caps": "ALLSMALL", + "petite-caps": "PETITECAPS", + "all-petite-caps": "ALLPETITE", + "titling-caps": "TITLING" } %> + +${helpers.single_keyword( + "font-variant-caps", + "normal small-caps", + engines="gecko servo-2013 servo-2020", + extra_gecko_values="all-small-caps petite-caps all-petite-caps unicase titling-caps", + gecko_constant_prefix="NS_FONT_VARIANT_CAPS", + gecko_ffi_name="mFont.variantCaps", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps", + custom_consts=font_variant_caps_custom_consts, + animation_value_type="discrete", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "font-weight", + "FontWeight", + engines="gecko servo-2013 servo-2020", + initial_value="computed::FontWeight::normal()", + initial_specified_value="specified::FontWeight::normal()", + animation_value_type="Number", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-weight", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "font-size", + "FontSize", + engines="gecko servo-2013 servo-2020", + initial_value="computed::FontSize::medium()", + initial_specified_value="specified::FontSize::medium()", + animation_value_type="NonNegativeLength", + allow_quirks="Yes", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-size", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "font-size-adjust", + "FontSizeAdjust", + engines="gecko", + initial_value="computed::FontSizeAdjust::None", + initial_specified_value="specified::FontSizeAdjust::None", + animation_value_type="FontSizeAdjust", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-size-adjust", + affects="layout", +)} + +${helpers.predefined_type( + "font-synthesis-weight", + "FontSynthesis", + engines="gecko", + initial_value="computed::FontSynthesis::Auto", + initial_specified_value="specified::FontSynthesis::Auto", + gecko_ffi_name="mFont.synthesisWeight", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-weight", + affects="layout", +)} + +${helpers.predefined_type( + "font-synthesis-style", + "FontSynthesis", + engines="gecko", + initial_value="computed::FontSynthesis::Auto", + initial_specified_value="specified::FontSynthesis::Auto", + gecko_ffi_name="mFont.synthesisStyle", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-style", + affects="layout", +)} + +${helpers.predefined_type( + "font-synthesis-small-caps", + "FontSynthesis", + engines="gecko", + initial_value="computed::FontSynthesis::Auto", + initial_specified_value="specified::FontSynthesis::Auto", + gecko_ffi_name="mFont.synthesisSmallCaps", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-small-caps", + affects="layout", +)} + +${helpers.predefined_type( + "font-synthesis-position", + "FontSynthesis", + engines="gecko", + initial_value="computed::FontSynthesis::Auto", + initial_specified_value="specified::FontSynthesis::Auto", + gecko_ffi_name="mFont.synthesisPosition", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-position", + affects="layout", +)} + +${helpers.predefined_type( + "font-stretch", + "FontStretch", + engines="gecko servo-2013 servo-2020", + initial_value="computed::FontStretch::hundred()", + initial_specified_value="specified::FontStretch::normal()", + animation_value_type="Percentage", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-stretch", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.single_keyword( + "font-kerning", + "auto none normal", + engines="gecko", + gecko_ffi_name="mFont.kerning", + gecko_constant_prefix="NS_FONT_KERNING", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-kerning", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "font-variant-alternates", + "FontVariantAlternates", + engines="gecko", + initial_value="computed::FontVariantAlternates::default()", + initial_specified_value="specified::FontVariantAlternates::default()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-alternates", + affects="layout", +)} + +${helpers.predefined_type( + "font-variant-east-asian", + "FontVariantEastAsian", + engines="gecko", + initial_value="computed::FontVariantEastAsian::empty()", + initial_specified_value="specified::FontVariantEastAsian::empty()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian", + affects="layout", +)} + +${helpers.single_keyword( + "font-variant-emoji", + "normal text emoji unicode", + engines="gecko", + gecko_pref="layout.css.font-variant-emoji.enabled", + has_effect_on_gecko_scrollbars=False, + gecko_enum_prefix="StyleFontVariantEmoji", + gecko_ffi_name="mFont.variantEmoji", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-emoji", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "font-variant-ligatures", + "FontVariantLigatures", + engines="gecko", + initial_value="computed::FontVariantLigatures::empty()", + initial_specified_value="specified::FontVariantLigatures::empty()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures", + affects="layout", +)} + +${helpers.predefined_type( + "font-variant-numeric", + "FontVariantNumeric", + engines="gecko", + initial_value="computed::FontVariantNumeric::empty()", + initial_specified_value="specified::FontVariantNumeric::empty()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric", + affects="layout", +)} + +${helpers.single_keyword( + "font-variant-position", + "normal sub super", + engines="gecko", + gecko_ffi_name="mFont.variantPosition", + gecko_constant_prefix="NS_FONT_VARIANT_POSITION", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-position", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "font-feature-settings", + "FontFeatureSettings", + engines="gecko", + initial_value="computed::FontFeatureSettings::normal()", + initial_specified_value="specified::FontFeatureSettings::normal()", + extra_prefixes="moz:layout.css.prefixes.font-features", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings", + affects="layout", +)} + +${helpers.predefined_type( + "font-variation-settings", + "FontVariationSettings", + engines="gecko", + gecko_pref="layout.css.font-variations.enabled", + has_effect_on_gecko_scrollbars=False, + initial_value="computed::FontVariationSettings::normal()", + initial_specified_value="specified::FontVariationSettings::normal()", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-fonts-4/#propdef-font-variation-settings", + affects="layout", +)} + +${helpers.predefined_type( + "font-language-override", + "FontLanguageOverride", + engines="gecko", + initial_value="computed::FontLanguageOverride::normal()", + initial_specified_value="specified::FontLanguageOverride::normal()", + animation_value_type="discrete", + extra_prefixes="moz:layout.css.prefixes.font-features", + spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override", + affects="layout", +)} + +${helpers.single_keyword( + "font-optical-sizing", + "auto none", + engines="gecko", + gecko_pref="layout.css.font-variations.enabled", + has_effect_on_gecko_scrollbars=False, + gecko_ffi_name="mFont.opticalSizing", + gecko_constant_prefix="NS_FONT_OPTICAL_SIZING", + animation_value_type="discrete", + spec="https://www.w3.org/TR/css-fonts-4/#font-optical-sizing-def", + affects="layout", +)} + +${helpers.predefined_type( + "font-palette", + "FontPalette", + engines="gecko", + initial_value="computed::FontPalette::normal()", + initial_specified_value="specified::FontPalette::normal()", + animation_value_type="discrete", + gecko_pref="layout.css.font-palette.enabled", + has_effect_on_gecko_scrollbars=False, + spec="https://drafts.csswg.org/css-fonts/#font-palette-prop", + affects="layout", +)} + +${helpers.predefined_type( + "-x-lang", + "XLang", + engines="gecko", + initial_value="computed::XLang::get_initial_value()", + animation_value_type="none", + enabled_in="", + has_effect_on_gecko_scrollbars=False, + spec="Internal (not web-exposed)", + affects="layout", +)} + +${helpers.predefined_type( + "math-depth", + "MathDepth", + "0", + engines="gecko", + gecko_pref="layout.css.math-depth.enabled", + has_effect_on_gecko_scrollbars=False, + animation_value_type="none", + enabled_in="ua", + spec="https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property", + affects="", +)} + +${helpers.single_keyword( + "math-style", + "normal compact", + engines="gecko", + gecko_enum_prefix="StyleMathStyle", + gecko_pref="layout.css.math-style.enabled", + spec="https://mathml-refresh.github.io/mathml-core/#the-math-style-property", + has_effect_on_gecko_scrollbars=False, + animation_value_type="none", + enabled_in="ua", + needs_conversion=True, + affects="layout", +)} + +${helpers.single_keyword( + "-moz-math-variant", + """none normal bold italic bold-italic script bold-script + fraktur double-struck bold-fraktur sans-serif + bold-sans-serif sans-serif-italic sans-serif-bold-italic + monospace initial tailed looped stretched""", + engines="gecko", + gecko_enum_prefix="StyleMathVariant", + gecko_ffi_name="mMathVariant", + spec="Internal (not web-exposed)", + animation_value_type="none", + enabled_in="", + has_effect_on_gecko_scrollbars=False, + needs_conversion=True, + affects="layout", +)} + +${helpers.predefined_type( + "-x-text-scale", + "XTextScale", + "computed::XTextScale::All", + engines="gecko", + animation_value_type="none", + enabled_in="", + has_effect_on_gecko_scrollbars=False, + spec="Internal (not web-exposed)", + affects="layout", +)} + +${helpers.predefined_type( + "line-height", + "LineHeight", + "computed::LineHeight::normal()", + engines="gecko servo-2013 servo-2020", + animation_value_type="LineHeight", + spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height", + servo_restyle_damage="reflow", + affects="layout", +)} + +% if engine == "gecko": +pub mod system_font { + //! We deal with system fonts here + //! + //! System fonts can only be set as a group via the font shorthand. + //! They resolve at compute time (not parse time -- this lets the + //! browser respond to changes to the OS font settings). + //! + //! While Gecko handles these as a separate property and keyword + //! values on each property indicating that the font should be picked + //! from the -x-system-font property, we avoid this. Instead, + //! each font longhand has a special SystemFont variant which contains + //! the specified system font. When the cascade function (in helpers) + //! detects that a value has a system font, it will resolve it, and + //! cache it on the ComputedValues. After this, it can be just fetched + //! whenever a font longhand on the same element needs the system font. + //! + //! When a longhand property is holding a SystemFont, it's serialized + //! to an empty string as if its value comes from a shorthand with + //! variable reference. We may want to improve this behavior at some + //! point. See also https://github.com/w3c/csswg-drafts/issues/1586. + + use crate::properties::longhands; + use std::hash::{Hash, Hasher}; + use crate::values::computed::{ToComputedValue, Context}; + use crate::values::specified::font::SystemFont; + // ComputedValues are compared at times + // so we need these impls. We don't want to + // add Eq to Number (which contains a float) + // so instead we have an eq impl which skips the + // cached values + impl PartialEq for ComputedSystemFont { + fn eq(&self, other: &Self) -> bool { + self.system_font == other.system_font + } + } + impl Eq for ComputedSystemFont {} + + impl Hash for ComputedSystemFont { + fn hash<H: Hasher>(&self, hasher: &mut H) { + self.system_font.hash(hasher) + } + } + + impl ToComputedValue for SystemFont { + type ComputedValue = ComputedSystemFont; + + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + use crate::gecko_bindings::bindings; + use crate::gecko_bindings::structs::nsFont; + use crate::values::computed::font::FontSize; + use crate::values::specified::font::KeywordInfo; + use crate::values::generics::NonNegative; + use std::mem; + + let mut system = mem::MaybeUninit::<nsFont>::uninit(); + let system = unsafe { + bindings::Gecko_nsFont_InitSystem( + system.as_mut_ptr(), + *self, + &**cx.style().get_font(), + cx.device().document() + ); + &mut *system.as_mut_ptr() + }; + let size = NonNegative(cx.maybe_zoom_text(system.size.0)); + let ret = ComputedSystemFont { + font_family: system.family.clone(), + font_size: FontSize { + computed_size: size, + used_size: size, + keyword_info: KeywordInfo::none() + }, + font_weight: system.weight, + font_stretch: system.stretch, + font_style: system.style, + system_font: *self, + }; + unsafe { bindings::Gecko_nsFont_Destroy(system); } + ret + } + + fn from_computed_value(_: &ComputedSystemFont) -> Self { + unreachable!() + } + } + + #[inline] + /// Compute and cache a system font + /// + /// Must be called before attempting to compute a system font + /// specified value + pub fn resolve_system_font(system: SystemFont, context: &mut Context) { + // Checking if context.cached_system_font.is_none() isn't enough, + // if animating from one system font to another the cached system font + // may change + if Some(system) != context.cached_system_font.as_ref().map(|x| x.system_font) { + let computed = system.to_computed_value(context); + context.cached_system_font = Some(computed); + } + } + + #[derive(Clone, Debug)] + pub struct ComputedSystemFont { + % for name in SYSTEM_FONT_LONGHANDS: + pub ${name}: longhands::${name}::computed_value::T, + % endfor + pub system_font: SystemFont, + } + +} +% endif + +${helpers.single_keyword( + "-moz-osx-font-smoothing", + "auto grayscale", + engines="gecko", + gecko_constant_prefix="NS_FONT_SMOOTHING", + gecko_ffi_name="mFont.smoothing", + gecko_pref="layout.css.osx-font-smoothing.enabled", + has_effect_on_gecko_scrollbars=False, + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth)", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "-moz-min-font-size-ratio", + "Percentage", + "computed::Percentage::hundred()", + engines="gecko", + animation_value_type="none", + enabled_in="ua", + spec="Nonstandard (Internal-only)", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/inherited_box.mako.rs b/servo/components/style/properties/longhands/inherited_box.mako.rs new file mode 100644 index 0000000000..7fd94d1a1f --- /dev/null +++ b/servo/components/style/properties/longhands/inherited_box.mako.rs @@ -0,0 +1,105 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %> + +// TODO: collapse. Well, do tables first. +${helpers.single_keyword( + "visibility", + "visible hidden collapse", + engines="gecko servo-2013 servo-2020", + gecko_ffi_name="mVisible", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-box/#propdef-visibility", + gecko_enum_prefix="StyleVisibility", + affects="paint", +)} + +// CSS Writing Modes Level 3 +// https://drafts.csswg.org/css-writing-modes-3 +${helpers.single_keyword( + "writing-mode", + "horizontal-tb vertical-rl vertical-lr", + engines="gecko servo-2013 servo-2020", + extra_gecko_values="sideways-rl sideways-lr", + gecko_aliases="lr=horizontal-tb lr-tb=horizontal-tb \ + rl=horizontal-tb rl-tb=horizontal-tb \ + tb=vertical-rl tb-rl=vertical-rl", + servo_2013_pref="layout.writing-mode.enabled", + servo_2020_pref="layout.writing-mode.enabled", + animation_value_type="none", + spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode", + gecko_enum_prefix="StyleWritingModeProperty", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.single_keyword( + "direction", + "ltr rtl", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + animation_value_type="none", + spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction", + gecko_enum_prefix="StyleDirection", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.single_keyword( + "-moz-box-collapse", + "flex legacy", + engines="gecko", + gecko_enum_prefix="StyleMozBoxCollapse", + animation_value_type="none", + enabled_in="chrome", + spec="None (internal)", + affects="layout", +)} + +${helpers.single_keyword( + "text-orientation", + "mixed upright sideways", + engines="gecko", + gecko_aliases="sideways-right=sideways", + gecko_enum_prefix="StyleTextOrientation", + animation_value_type="none", + spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation", + affects="layout", +)} + +${helpers.predefined_type( + "print-color-adjust", + "PrintColorAdjust", + "computed::PrintColorAdjust::Economy", + engines="gecko", + aliases="color-adjust", + spec="https://drafts.csswg.org/css-color-adjust/#print-color-adjust", + animation_value_type="discrete", + affects="paint", +)} + +// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` +// And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337) +${helpers.predefined_type( + "image-rendering", + "ImageRendering", + "computed::ImageRendering::Auto", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.single_keyword( + "image-orientation", + "from-image none", + engines="gecko", + gecko_enum_prefix="StyleImageOrientation", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-images/#propdef-image-orientation", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/inherited_svg.mako.rs b/servo/components/style/properties/longhands/inherited_svg.mako.rs new file mode 100644 index 0000000000..90443f962a --- /dev/null +++ b/servo/components/style/properties/longhands/inherited_svg.mako.rs @@ -0,0 +1,239 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +// SVG 2 +// https://svgwg.org/svg2-draft/ +<% data.new_style_struct("InheritedSVG", inherited=True, gecko_name="SVG") %> + +// Section 10 - Text + +${helpers.single_keyword( + "dominant-baseline", + """auto ideographic alphabetic hanging mathematical central middle + text-after-edge text-before-edge""", + engines="gecko", + animation_value_type="discrete", + spec="https://www.w3.org/TR/css-inline-3/#propdef-dominant-baseline", + gecko_enum_prefix="StyleDominantBaseline", + affects="layout", +)} + +${helpers.single_keyword( + "text-anchor", + "start middle end", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/text.html#TextAnchorProperty", + gecko_enum_prefix="StyleTextAnchor", + affects="layout", +)} + +// Section 11 - Painting: Filling, Stroking and Marker Symbols +${helpers.single_keyword( + "color-interpolation", + "srgb auto linearrgb", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty", + gecko_enum_prefix="StyleColorInterpolation", + affects="paint", +)} + +${helpers.single_keyword( + "color-interpolation-filters", + "linearrgb auto srgb", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#ColorInterpolationFiltersProperty", + gecko_enum_prefix="StyleColorInterpolation", + affects="paint", +)} + +${helpers.predefined_type( + "fill", + "SVGPaint", + "crate::values::computed::SVGPaint::BLACK", + engines="gecko", + animation_value_type="IntermediateSVGPaint", + boxed=True, + spec="https://svgwg.org/svg2-draft/painting.html#SpecifyingFillPaint", + affects="paint", +)} + +${helpers.predefined_type( + "fill-opacity", + "SVGOpacity", + "Default::default()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/painting.html#FillOpacity", + affects="paint", +)} + +${helpers.predefined_type( + "fill-rule", + "FillRule", + "Default::default()", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#FillRuleProperty", + affects="paint", +)} + +${helpers.single_keyword( + "shape-rendering", + "auto optimizespeed crispedges geometricprecision", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty", + gecko_enum_prefix = "StyleShapeRendering", + affects="paint", +)} + +${helpers.predefined_type( + "stroke", + "SVGPaint", + "Default::default()", + engines="gecko", + animation_value_type="IntermediateSVGPaint", + boxed=True, + spec="https://svgwg.org/svg2-draft/painting.html#SpecifyingStrokePaint", + affects="paint", +)} + +${helpers.predefined_type( + "stroke-width", + "SVGWidth", + "computed::SVGWidth::one()", + engines="gecko", + animation_value_type="crate::values::computed::SVGWidth", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeWidth", + affects="layout", +)} + +${helpers.single_keyword( + "stroke-linecap", + "butt round square", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeLinecapProperty", + gecko_enum_prefix = "StyleStrokeLinecap", + affects="layout", +)} + +${helpers.single_keyword( + "stroke-linejoin", + "miter round bevel", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeLinejoinProperty", + gecko_enum_prefix = "StyleStrokeLinejoin", + affects="layout", +)} + +${helpers.predefined_type( + "stroke-miterlimit", + "NonNegativeNumber", + "From::from(4.0)", + engines="gecko", + animation_value_type="crate::values::computed::NonNegativeNumber", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeMiterlimitProperty", + affects="layout", +)} + +${helpers.predefined_type( + "stroke-opacity", + "SVGOpacity", + "Default::default()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeOpacity", + affects="paint", +)} + +${helpers.predefined_type( + "stroke-dasharray", + "SVGStrokeDashArray", + "Default::default()", + engines="gecko", + animation_value_type="crate::values::computed::SVGStrokeDashArray", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeDashing", + affects="paint", +)} + +${helpers.predefined_type( + "stroke-dashoffset", + "SVGLength", + "computed::SVGLength::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/painting.html#StrokeDashing", + affects="paint", +)} + +// Section 14 - Clipping, Masking and Compositing +${helpers.predefined_type( + "clip-rule", + "FillRule", + "Default::default()", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/masking.html#ClipRuleProperty", + affects="paint", +)} + +${helpers.predefined_type( + "marker-start", + "url::UrlOrNone", + "computed::url::UrlOrNone::none()", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#VertexMarkerProperties", + affects="layout", +)} + +${helpers.predefined_type( + "marker-mid", + "url::UrlOrNone", + "computed::url::UrlOrNone::none()", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#VertexMarkerProperties", + affects="layout", +)} + +${helpers.predefined_type( + "marker-end", + "url::UrlOrNone", + "computed::url::UrlOrNone::none()", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#VertexMarkerProperties", + affects="layout", +)} + +${helpers.predefined_type( + "paint-order", + "SVGPaintOrder", + "computed::SVGPaintOrder::normal()", + engines="gecko", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#PaintOrder", + affects="paint", +)} + +${helpers.predefined_type( + "-moz-context-properties", + "MozContextProperties", + "computed::MozContextProperties::default()", + engines="gecko", + enabled_in="chrome", + gecko_pref="svg.context-properties.content.enabled", + has_effect_on_gecko_scrollbars=False, + animation_value_type="none", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)", + affects="paint", +)} diff --git a/servo/components/style/properties/longhands/inherited_table.mako.rs b/servo/components/style/properties/longhands/inherited_table.mako.rs new file mode 100644 index 0000000000..7eb42a6eb2 --- /dev/null +++ b/servo/components/style/properties/longhands/inherited_table.mako.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("InheritedTable", inherited=True, gecko_name="TableBorder") %> + +${helpers.single_keyword( + "border-collapse", + "separate collapse", + engines="gecko servo-2013", + gecko_enum_prefix="StyleBorderCollapse", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-tables/#propdef-border-collapse", + servo_restyle_damage = "reflow", + affects="layout", +)} + +${helpers.single_keyword( + "empty-cells", + "show hide", + engines="gecko servo-2013", + gecko_enum_prefix="StyleEmptyCells", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-tables/#propdef-empty-cells", + servo_restyle_damage="rebuild_and_reflow", + affects="paint", +)} + +${helpers.predefined_type( + "caption-side", + "table::CaptionSide", + "computed::table::CaptionSide::Top", + engines="gecko servo-2013", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-tables/#propdef-caption-side", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "border-spacing", + "BorderSpacing", + "computed::BorderSpacing::zero()", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + animation_value_type="BorderSpacing", + boxed=True, + spec="https://drafts.csswg.org/css-tables/#propdef-border-spacing", + servo_restyle_damage="reflow", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/inherited_text.mako.rs b/servo/components/style/properties/longhands/inherited_text.mako.rs new file mode 100644 index 0000000000..544ba99bf7 --- /dev/null +++ b/servo/components/style/properties/longhands/inherited_text.mako.rs @@ -0,0 +1,414 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import Keyword %> +<% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %> + +${helpers.predefined_type( + "color", + "ColorPropertyValue", + "crate::color::AbsoluteColor::BLACK", + engines="gecko servo-2013 servo-2020", + animation_value_type="AbsoluteColor", + ignored_when_colors_disabled="True", + spec="https://drafts.csswg.org/css-color/#color", + affects="paint", +)} + +// CSS Text Module Level 3 + +${helpers.predefined_type( + "text-transform", + "TextTransform", + "computed::TextTransform::none()", + engines="gecko servo-2013", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-text-transform", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.single_keyword( + "hyphens", + "manual none auto", + engines="gecko", + gecko_enum_prefix="StyleHyphens", + animation_value_type="discrete", + extra_prefixes="moz", + spec="https://drafts.csswg.org/css-text/#propdef-hyphens", + affects="layout", +)} + +// TODO: Support <percentage> +${helpers.single_keyword( + "-moz-text-size-adjust", + "auto none", + engines="gecko", + gecko_enum_prefix="StyleTextSizeAdjust", + gecko_ffi_name="mTextSizeAdjust", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-size-adjust/#adjustment-control", + aliases="-webkit-text-size-adjust", + affects="layout", +)} + +${helpers.predefined_type( + "text-indent", + "TextIndent", + "computed::TextIndent::zero()", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-text/#propdef-text-indent", + servo_restyle_damage = "reflow", + affects="layout", +)} + +// Also known as "word-wrap" (which is more popular because of IE), but this is +// the preferred name per CSS-TEXT 6.2. +${helpers.predefined_type( + "overflow-wrap", + "OverflowWrap", + "computed::OverflowWrap::Normal", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-overflow-wrap", + aliases="word-wrap", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "word-break", + "WordBreak", + "computed::WordBreak::Normal", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-word-break", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "text-justify", + "TextJustify", + "computed::TextJustify::Auto", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-text-justify", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "text-align-last", + "TextAlignLast", + "computed::text::TextAlignLast::Auto", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-text-align-last", + affects="layout", +)} + +// TODO make this a shorthand and implement text-align-last/text-align-all +${helpers.predefined_type( + "text-align", + "TextAlign", + "computed::TextAlign::Start", + engines="gecko servo-2013 servo-2020", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-text-align", + servo_restyle_damage = "reflow", + affects="layout", +)} + +${helpers.predefined_type( + "letter-spacing", + "LetterSpacing", + "computed::LetterSpacing::normal()", + engines="gecko servo-2013 servo-2020", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "word-spacing", + "WordSpacing", + "computed::WordSpacing::zero()", + engines="gecko servo-2013 servo-2020", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-text/#propdef-word-spacing", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +// TODO: `white-space-collapse: discard` not yet supported +${helpers.single_keyword( + name="white-space-collapse", + values="collapse preserve preserve-breaks preserve-spaces break-spaces", + engines="gecko", + gecko_enum_prefix="StyleWhiteSpaceCollapse", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-4/#propdef-white-space-collapse", + affects="layout", +)} + +${helpers.predefined_type( + "text-shadow", + "SimpleShadow", + None, + engines="gecko servo-2013", + vector=True, + vector_animation_type="with_zero", + animation_value_type="AnimatedTextShadowList", + ignored_when_colors_disabled=True, + simple_vector_bindings=True, + spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property", + affects="overflow", +)} + +${helpers.predefined_type( + "text-emphasis-style", + "TextEmphasisStyle", + "computed::TextEmphasisStyle::None", + engines="gecko", + initial_specified_value="SpecifiedValue::None", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style", + affects="overflow", +)} + +${helpers.predefined_type( + "text-emphasis-position", + "TextEmphasisPosition", + "computed::TextEmphasisPosition::OVER", + engines="gecko", + initial_specified_value="specified::TextEmphasisPosition::OVER", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-position", + affects="layout", +)} + +${helpers.predefined_type( + "text-emphasis-color", + "Color", + "computed_value::T::currentcolor()", + engines="gecko", + initial_specified_value="specified::Color::currentcolor()", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color", + affects="paint", +)} + +${helpers.predefined_type( + "tab-size", + "NonNegativeLengthOrNumber", + "generics::length::LengthOrNumber::Number(From::from(8.0))", + engines="gecko", + animation_value_type="LengthOrNumber", + spec="https://drafts.csswg.org/css-text-3/#tab-size-property", + aliases="-moz-tab-size", + affects="layout", +)} + +${helpers.predefined_type( + "line-break", + "LineBreak", + "computed::LineBreak::Auto", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-3/#line-break-property", + affects="layout", +)} + +// CSS Compatibility +// https://compat.spec.whatwg.org +${helpers.predefined_type( + "-webkit-text-fill-color", + "Color", + "computed_value::T::currentcolor()", + engines="gecko", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color", + affects="paint", +)} + +${helpers.predefined_type( + "-webkit-text-stroke-color", + "Color", + "computed_value::T::currentcolor()", + initial_specified_value="specified::Color::currentcolor()", + engines="gecko", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color", + affects="paint", +)} + +${helpers.predefined_type( + "-webkit-text-stroke-width", + "LineWidth", + "app_units::Au(0)", + engines="gecko", + initial_specified_value="specified::LineWidth::zero()", + spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width", + animation_value_type="discrete", + affects="overflow", +)} + +// CSS Ruby Layout Module Level 1 +// https://drafts.csswg.org/css-ruby/ +${helpers.single_keyword( + "ruby-align", + "space-around start center space-between", + engines="gecko", + animation_value_type="discrete", + gecko_enum_prefix="StyleRubyAlign", + spec="https://drafts.csswg.org/css-ruby/#ruby-align-property", + affects="layout", +)} + +${helpers.predefined_type( + "ruby-position", + "RubyPosition", + "computed::RubyPosition::AlternateOver", + engines="gecko", + spec="https://drafts.csswg.org/css-ruby/#ruby-position-property", + animation_value_type="discrete", + affects="layout", +)} + +// CSS Writing Modes Module Level 3 +// https://drafts.csswg.org/css-writing-modes-3/ + +${helpers.single_keyword( + "text-combine-upright", + "none all", + engines="gecko", + gecko_enum_prefix="StyleTextCombineUpright", + animation_value_type="none", + spec="https://drafts.csswg.org/css-writing-modes-3/#text-combine-upright", + affects="layout", +)} + +// SVG 2: Section 13 - Painting: Filling, Stroking and Marker Symbols +${helpers.single_keyword( + "text-rendering", + "auto optimizespeed optimizelegibility geometricprecision", + engines="gecko servo-2013 servo-2020", + gecko_enum_prefix="StyleTextRendering", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "-moz-control-character-visibility", + "text::MozControlCharacterVisibility", + "Default::default()", + engines="gecko", + enabled_in="chrome", + gecko_pref="layout.css.moz-control-character-visibility.enabled", + has_effect_on_gecko_scrollbars=False, + animation_value_type="none", + spec="Nonstandard", + affects="layout", +)} + +// text underline offset +${helpers.predefined_type( + "text-underline-offset", + "LengthPercentageOrAuto", + "computed::LengthPercentageOrAuto::auto()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-text-decor-4/#underline-offset", + affects="overflow", +)} + +// text underline position +${helpers.predefined_type( + "text-underline-position", + "TextUnderlinePosition", + "computed::TextUnderlinePosition::AUTO", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-decor-3/#text-underline-position-property", + affects="overflow", +)} + +// text decoration skip ink +${helpers.predefined_type( + "text-decoration-skip-ink", + "TextDecorationSkipInk", + "computed::TextDecorationSkipInk::Auto", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property", + affects="overflow", +)} + +// hyphenation character +${helpers.predefined_type( + "hyphenate-character", + "HyphenateCharacter", + "computed::HyphenateCharacter::Auto", + engines="gecko", + animation_value_type="discrete", + spec="https://www.w3.org/TR/css-text-4/#hyphenate-character", + affects="layout", +)} + +${helpers.predefined_type( + "forced-color-adjust", + "ForcedColorAdjust", + "computed::ForcedColorAdjust::Auto", + engines="gecko", + gecko_pref="layout.css.forced-color-adjust.enabled", + has_effect_on_gecko_scrollbars=False, + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-color-adjust-1/#forced-color-adjust-prop", + affects="paint", +)} + +${helpers.single_keyword( + "-webkit-text-security", + "none circle disc square", + engines="gecko", + gecko_enum_prefix="StyleTextSecurity", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#MISSING", + affects="layout", +)} + +${helpers.single_keyword( + "text-wrap-mode", + "wrap nowrap", + engines="gecko", + gecko_enum_prefix="StyleTextWrapMode", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-4/#propdef-text-wrap-mode", + affects="layout", +)} + +${helpers.single_keyword( + "text-wrap-style", + "auto stable balance", + engines="gecko", + gecko_pref="layout.css.text-wrap-balance.enabled", + has_effect_on_gecko_scrollbars=False, + gecko_enum_prefix="StyleTextWrapStyle", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-4/#text-wrap-style", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/inherited_ui.mako.rs b/servo/components/style/properties/longhands/inherited_ui.mako.rs new file mode 100644 index 0000000000..6cdf721336 --- /dev/null +++ b/servo/components/style/properties/longhands/inherited_ui.mako.rs @@ -0,0 +1,135 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %> + +${helpers.predefined_type( + "cursor", + "Cursor", + "computed::Cursor::auto()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Cursor::auto()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-ui/#cursor", + affects="paint", +)} + +// NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact) +// is nonstandard, slated for CSS4-UI. +// TODO(pcwalton): SVG-only values. +${helpers.single_keyword( + "pointer-events", + "auto none", + engines="gecko servo-2013 servo-2020", + animation_value_type="discrete", + extra_gecko_values="visiblepainted visiblefill visiblestroke visible painted fill stroke all", + spec="https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty", + gecko_enum_prefix="StylePointerEvents", + affects="paint", +)} + +${helpers.single_keyword( + "-moz-inert", + "none inert", + engines="gecko", + gecko_ffi_name="mInert", + gecko_enum_prefix="StyleInert", + animation_value_type="discrete", + enabled_in="ua", + spec="Nonstandard (https://html.spec.whatwg.org/multipage/#inert-subtrees)", + affects="paint", +)} + +${helpers.single_keyword( + "-moz-user-input", + "auto none", + engines="gecko", + gecko_ffi_name="mUserInput", + gecko_enum_prefix="StyleUserInput", + animation_value_type="discrete", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-input)", + affects="", +)} + +${helpers.single_keyword( + "-moz-user-modify", + "read-only read-write write-only", + engines="gecko", + gecko_ffi_name="mUserModify", + gecko_enum_prefix="StyleUserModify", + needs_conversion=True, + animation_value_type="discrete", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-modify)", + affects="", +)} + +${helpers.single_keyword( + "-moz-user-focus", + "normal none ignore", + engines="gecko", + gecko_ffi_name="mUserFocus", + gecko_enum_prefix="StyleUserFocus", + animation_value_type="discrete", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)", + enabled_in="chrome", + affects="", +)} + +${helpers.predefined_type( + "caret-color", + "color::CaretColor", + "generics::color::CaretColor::auto()", + engines="gecko", + spec="https://drafts.csswg.org/css-ui/#caret-color", + animation_value_type="CaretColor", + ignored_when_colors_disabled=True, + affects="paint", +)} + +${helpers.predefined_type( + "accent-color", + "ColorOrAuto", + "generics::color::ColorOrAuto::Auto", + engines="gecko", + spec="https://drafts.csswg.org/css-ui-4/#widget-accent", + animation_value_type="ColorOrAuto", + ignored_when_colors_disabled=True, + affects="paint", +)} + +${helpers.predefined_type( + "color-scheme", + "ColorScheme", + "specified::color::ColorScheme::normal()", + engines="gecko", + spec="https://drafts.csswg.org/css-color-adjust/#color-scheme-prop", + animation_value_type="discrete", + ignored_when_colors_disabled=True, + affects="paint", +)} + +${helpers.predefined_type( + "scrollbar-color", + "ui::ScrollbarColor", + "Default::default()", + engines="gecko", + spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-color", + animation_value_type="ScrollbarColor", + boxed=True, + ignored_when_colors_disabled=True, + affects="paint", +)} + +${helpers.predefined_type( + "-moz-theme", + "ui::MozTheme", + "specified::ui::MozTheme::Auto", + engines="gecko", + enabled_in="chrome", + animation_value_type="discrete", + spec="Internal", + affects="paint", +)} diff --git a/servo/components/style/properties/longhands/list.mako.rs b/servo/components/style/properties/longhands/list.mako.rs new file mode 100644 index 0000000000..619724bd32 --- /dev/null +++ b/servo/components/style/properties/longhands/list.mako.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("List", inherited=True) %> + +${helpers.single_keyword( + "list-style-position", + "outside inside", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + gecko_enum_prefix="StyleListStylePosition", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-lists/#propdef-list-style-position", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +// TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1: +// +// decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman, +// upper-roman +// +// [1]: http://dev.w3.org/csswg/css-counter-styles/ +% if engine in ["servo-2013", "servo-2020"]: + ${helpers.single_keyword( + "list-style-type", + "disc none circle square disclosure-open disclosure-closed", + extra_servo_2013_values=""" + decimal lower-alpha upper-alpha arabic-indic bengali cambodian cjk-decimal devanagari + gujarati gurmukhi kannada khmer lao malayalam mongolian myanmar oriya persian telugu + thai tibetan cjk-earthly-branch cjk-heavenly-stem lower-greek hiragana hiragana-iroha + katakana katakana-iroha + """, + engines="servo-2013 servo-2020", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", + )} +% endif +% if engine == "gecko": + ${helpers.predefined_type( + "list-style-type", + "ListStyleType", + "computed::ListStyleType::disc()", + engines="gecko", + initial_specified_value="specified::ListStyleType::disc()", + animation_value_type="discrete", + boxed=True, + spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", + )} +% endif + +${helpers.predefined_type( + "list-style-image", + "Image", + engines="gecko servo-2013 servo-2020", + initial_value="computed::Image::None", + initial_specified_value="specified::Image::None", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "quotes", + "Quotes", + "computed::Quotes::get_initial_value()", + engines="gecko servo-2013", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-content/#propdef-quotes", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/margin.mako.rs b/servo/components/style/properties/longhands/margin.mako.rs new file mode 100644 index 0000000000..b5a87f9683 --- /dev/null +++ b/servo/components/style/properties/longhands/margin.mako.rs @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import ALL_SIDES, DEFAULT_RULES_AND_PAGE, maybe_moz_logical_alias %> +<% data.new_style_struct("Margin", inherited=False) %> + +% for side in ALL_SIDES: + <% + spec = "https://drafts.csswg.org/css-box/#propdef-margin-%s" % side[0] + if side[1]: + spec = "https://drafts.csswg.org/css-logical-props/#propdef-margin-%s" % side[1] + %> + ${helpers.predefined_type( + "margin-%s" % side[0], + "LengthPercentageOrAuto", + "computed::LengthPercentageOrAuto::zero()", + engines="gecko servo-2013 servo-2020", + aliases=maybe_moz_logical_alias(engine, side, "-moz-margin-%s"), + allow_quirks="No" if side[1] else "Yes", + animation_value_type="ComputedValue", + logical=side[1], + logical_group="margin", + spec=spec, + rule_types_allowed=DEFAULT_RULES_AND_PAGE, + servo_restyle_damage="reflow", + affects="layout", + )} +% endfor + +${helpers.predefined_type( + "overflow-clip-margin", + "Length", + "computed::Length::zero()", + parse_method="parse_non_negative", + engines="gecko", + spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin", + animation_value_type="ComputedValue", + affects="overflow", +)} + +% for side in ALL_SIDES: + ${helpers.predefined_type( + "scroll-margin-%s" % side[0], + "Length", + "computed::Length::zero()", + engines="gecko", + logical=side[1], + logical_group="scroll-margin", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin-%s" % side[0], + animation_value_type="ComputedValue", + affects="", + )} +% endfor diff --git a/servo/components/style/properties/longhands/outline.mako.rs b/servo/components/style/properties/longhands/outline.mako.rs new file mode 100644 index 0000000000..8e7f956bf5 --- /dev/null +++ b/servo/components/style/properties/longhands/outline.mako.rs @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import Method %> + +<% data.new_style_struct("Outline", + inherited=False, + additional_methods=[Method("outline_has_nonzero_width", "bool")]) %> + +// TODO(pcwalton): `invert` +${helpers.predefined_type( + "outline-color", + "Color", + "computed_value::T::currentcolor()", + engines="gecko servo-2013", + initial_specified_value="specified::Color::currentcolor()", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-ui/#propdef-outline-color", + affects="paint", +)} + +${helpers.predefined_type( + "outline-style", + "OutlineStyle", + "computed::OutlineStyle::none()", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + initial_specified_value="specified::OutlineStyle::none()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-ui/#propdef-outline-style", + affects="overflow", +)} + +${helpers.predefined_type( + "outline-width", + "BorderSideWidth", + "app_units::Au::from_px(3)", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.2020.unimplemented", + initial_specified_value="specified::BorderSideWidth::medium()", + animation_value_type="NonNegativeLength", + spec="https://drafts.csswg.org/css-ui/#propdef-outline-width", + affects="overflow", +)} + +${helpers.predefined_type( + "outline-offset", + "Length", + "crate::values::computed::Length::new(0.)", + engines="gecko servo-2013", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-ui/#propdef-outline-offset", + affects="overflow", +)} diff --git a/servo/components/style/properties/longhands/padding.mako.rs b/servo/components/style/properties/longhands/padding.mako.rs new file mode 100644 index 0000000000..a165e2cd34 --- /dev/null +++ b/servo/components/style/properties/longhands/padding.mako.rs @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import ALL_SIDES, maybe_moz_logical_alias %> +<% data.new_style_struct("Padding", inherited=False) %> + +% for side in ALL_SIDES: + <% + spec = "https://drafts.csswg.org/css-box/#propdef-padding-%s" % side[0] + if side[1]: + spec = "https://drafts.csswg.org/css-logical-props/#propdef-padding-%s" % side[1] + %> + ${helpers.predefined_type( + "padding-%s" % side[0], + "NonNegativeLengthPercentage", + "computed::NonNegativeLengthPercentage::zero()", + engines="gecko servo-2013 servo-2020", + aliases=maybe_moz_logical_alias(engine, side, "-moz-padding-%s"), + animation_value_type="NonNegativeLengthPercentage", + logical=side[1], + logical_group="padding", + spec=spec, + allow_quirks="No" if side[1] else "Yes", + servo_restyle_damage="reflow rebuild_and_reflow_inline", + affects="layout", + )} +% endfor + +% for side in ALL_SIDES: + ${helpers.predefined_type( + "scroll-padding-%s" % side[0], + "NonNegativeLengthPercentageOrAuto", + "computed::NonNegativeLengthPercentageOrAuto::auto()", + engines="gecko", + logical=side[1], + logical_group="scroll-padding", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-%s" % side[0], + animation_value_type="NonNegativeLengthPercentageOrAuto", + affects="", + )} +% endfor diff --git a/servo/components/style/properties/longhands/page.mako.rs b/servo/components/style/properties/longhands/page.mako.rs new file mode 100644 index 0000000000..86cd284e18 --- /dev/null +++ b/servo/components/style/properties/longhands/page.mako.rs @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import PAGE_RULE %> + +<% data.new_style_struct("Page", inherited=False) %> + +${helpers.predefined_type( + "size", + "PageSize", + "computed::PageSize::auto()", + engines="gecko", + initial_specified_value="specified::PageSize::auto()", + spec="https://drafts.csswg.org/css-page-3/#page-size-prop", + boxed=True, + animation_value_type="none", + rule_types_allowed=PAGE_RULE, + affects="layout", +)} + +${helpers.predefined_type( + "page", + "PageName", + "computed::PageName::auto()", + engines="gecko", + spec="https://drafts.csswg.org/css-page-3/#using-named-pages", + animation_value_type="discrete", + affects="layout", +)} + +${helpers.predefined_type( + "page-orientation", + "PageOrientation", + "computed::PageOrientation::Upright", + engines="gecko", + gecko_pref="layout.css.page-orientation.enabled", + initial_specified_value="specified::PageOrientation::Upright", + spec="https://drafts.csswg.org/css-page-3/#page-orientation-prop", + animation_value_type="none", + rule_types_allowed=PAGE_RULE, + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/position.mako.rs b/servo/components/style/properties/longhands/position.mako.rs new file mode 100644 index 0000000000..fb68baa6b4 --- /dev/null +++ b/servo/components/style/properties/longhands/position.mako.rs @@ -0,0 +1,485 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%! from data import to_rust_ident %> +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import ALL_SIZES, PHYSICAL_SIDES, LOGICAL_SIDES %> + +<% data.new_style_struct("Position", inherited=False) %> + +// "top" / "left" / "bottom" / "right" +% for side in PHYSICAL_SIDES: + ${helpers.predefined_type( + side, + "LengthPercentageOrAuto", + "computed::LengthPercentageOrAuto::auto()", + engines="gecko servo-2013 servo-2020", + spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side, + animation_value_type="ComputedValue", + allow_quirks="Yes", + servo_restyle_damage="reflow_out_of_flow", + logical_group="inset", + affects="layout", + )} +% endfor +// inset-* logical properties, map to "top" / "left" / "bottom" / "right" +% for side in LOGICAL_SIDES: + ${helpers.predefined_type( + "inset-%s" % side, + "LengthPercentageOrAuto", + "computed::LengthPercentageOrAuto::auto()", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-logical-props/#propdef-inset-%s" % side, + animation_value_type="ComputedValue", + logical=True, + logical_group="inset", + affects="layout", + )} +% endfor + +${helpers.predefined_type( + "z-index", + "ZIndex", + "computed::ZIndex::auto()", + engines="gecko servo-2013 servo-2020", + spec="https://www.w3.org/TR/CSS2/visuren.html#z-index", + animation_value_type="ComputedValue", + affects="paint", +)} + +// CSS Flexible Box Layout Module Level 1 +// http://www.w3.org/TR/css3-flexbox/ + +// Flex container properties +${helpers.single_keyword( + "flex-direction", + "row row-reverse column column-reverse", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property", + extra_prefixes="webkit", + animation_value_type="discrete", + servo_restyle_damage = "reflow", + gecko_enum_prefix = "StyleFlexDirection", + affects="layout", +)} + +${helpers.single_keyword( + "flex-wrap", + "nowrap wrap wrap-reverse", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property", + extra_prefixes="webkit", + animation_value_type="discrete", + servo_restyle_damage = "reflow", + gecko_enum_prefix = "StyleFlexWrap", + affects="layout", +)} + +% if engine == "servo-2013": + // FIXME: Update Servo to support the same Syntax as Gecko. + ${helpers.single_keyword( + "justify-content", + "flex-start stretch flex-end center space-between space-around", + engines="servo-2013", + extra_prefixes="webkit", + spec="https://drafts.csswg.org/css-align/#propdef-justify-content", + animation_value_type="discrete", + servo_restyle_damage = "reflow", + affects="layout", + )} +% endif +% if engine == "gecko": + ${helpers.predefined_type( + "justify-content", + "JustifyContent", + "specified::JustifyContent(specified::ContentDistribution::normal())", + engines="gecko", + spec="https://drafts.csswg.org/css-align/#propdef-justify-content", + extra_prefixes="webkit", + animation_value_type="discrete", + servo_restyle_damage="reflow", + affects="layout", + )} + + ${helpers.predefined_type( + "justify-tracks", + "JustifyTracks", + "specified::JustifyTracks::default()", + engines="gecko", + gecko_pref="layout.css.grid-template-masonry-value.enabled", + animation_value_type="discrete", + servo_restyle_damage="reflow", + spec="https://github.com/w3c/csswg-drafts/issues/4650", + affects="layout", + )} +% endif + +% if engine in ["servo-2013", "servo-2020"]: + // FIXME: Update Servo to support the same Syntax as Gecko. + ${helpers.single_keyword( + "align-content", + "stretch flex-start flex-end center space-between space-around", + engines="servo-2013", + extra_prefixes="webkit", + spec="https://drafts.csswg.org/css-align/#propdef-align-content", + animation_value_type="discrete", + servo_restyle_damage="reflow", + affects="layout", + )} + + ${helpers.single_keyword( + "align-items", + "stretch flex-start flex-end center baseline", + engines="servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + extra_prefixes="webkit", + spec="https://drafts.csswg.org/css-flexbox/#align-items-property", + animation_value_type="discrete", + servo_restyle_damage="reflow", + affects="layout", + )} +% endif +% if engine == "gecko": + ${helpers.predefined_type( + "align-content", + "AlignContent", + "specified::AlignContent(specified::ContentDistribution::normal())", + engines="gecko", + spec="https://drafts.csswg.org/css-align/#propdef-align-content", + extra_prefixes="webkit", + animation_value_type="discrete", + servo_restyle_damage="reflow", + affects="layout", + )} + + ${helpers.predefined_type( + "align-tracks", + "AlignTracks", + "specified::AlignTracks::default()", + engines="gecko", + gecko_pref="layout.css.grid-template-masonry-value.enabled", + animation_value_type="discrete", + servo_restyle_damage="reflow", + spec="https://github.com/w3c/csswg-drafts/issues/4650", + affects="layout", + )} + + ${helpers.predefined_type( + "align-items", + "AlignItems", + "specified::AlignItems::normal()", + engines="gecko", + spec="https://drafts.csswg.org/css-align/#propdef-align-items", + extra_prefixes="webkit", + animation_value_type="discrete", + servo_restyle_damage="reflow", + affects="layout", + )} + + ${helpers.predefined_type( + "justify-items", + "JustifyItems", + "computed::JustifyItems::legacy()", + engines="gecko", + spec="https://drafts.csswg.org/css-align/#propdef-justify-items", + animation_value_type="discrete", + affects="layout", + )} +% endif + +// Flex item properties +${helpers.predefined_type( + "flex-grow", + "NonNegativeNumber", + "From::from(0.0)", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property", + extra_prefixes="webkit", + animation_value_type="NonNegativeNumber", + servo_restyle_damage="reflow", + affects="layout", +)} + +${helpers.predefined_type( + "flex-shrink", + "NonNegativeNumber", + "From::from(1.0)", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property", + extra_prefixes="webkit", + animation_value_type="NonNegativeNumber", + servo_restyle_damage = "reflow", + affects="layout", +)} + +// https://drafts.csswg.org/css-align/#align-self-property +% if engine in ["servo-2013", "servo-2020"]: + // FIXME: Update Servo to support the same syntax as Gecko. + ${helpers.single_keyword( + "align-self", + "auto stretch flex-start flex-end center baseline", + engines="servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + extra_prefixes="webkit", + spec="https://drafts.csswg.org/css-flexbox/#propdef-align-self", + animation_value_type="discrete", + servo_restyle_damage = "reflow", + affects="layout", + )} +% endif +% if engine == "gecko": + ${helpers.predefined_type( + "align-self", + "AlignSelf", + "specified::AlignSelf(specified::SelfAlignment::auto())", + engines="gecko", + spec="https://drafts.csswg.org/css-align/#align-self-property", + extra_prefixes="webkit", + animation_value_type="discrete", + affects="layout", + )} + + ${helpers.predefined_type( + "justify-self", + "JustifySelf", + "specified::JustifySelf(specified::SelfAlignment::auto())", + engines="gecko", + spec="https://drafts.csswg.org/css-align/#justify-self-property", + animation_value_type="discrete", + affects="layout", + )} +% endif + +// https://drafts.csswg.org/css-flexbox/#propdef-order +${helpers.predefined_type( + "order", + "Integer", + "0", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + extra_prefixes="webkit", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-flexbox/#order-property", + servo_restyle_damage="reflow", + affects="layout", +)} + +${helpers.predefined_type( + "flex-basis", + "FlexBasis", + "computed::FlexBasis::auto()", + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property", + extra_prefixes="webkit", + animation_value_type="FlexBasis", + servo_restyle_damage="reflow", + boxed=True, + affects="layout", +)} + +% for (size, logical) in ALL_SIZES: + <% + spec = "https://drafts.csswg.org/css-box/#propdef-%s" + if logical: + spec = "https://drafts.csswg.org/css-logical-props/#propdef-%s" + %> + // width, height, block-size, inline-size + ${helpers.predefined_type( + size, + "Size", + "computed::Size::auto()", + engines="gecko servo-2013 servo-2020", + logical=logical, + logical_group="size", + allow_quirks="No" if logical else "Yes", + spec=spec % size, + animation_value_type="Size", + servo_restyle_damage="reflow", + affects="layout", + )} + // min-width, min-height, min-block-size, min-inline-size + ${helpers.predefined_type( + "min-%s" % size, + "Size", + "computed::Size::auto()", + engines="gecko servo-2013 servo-2020", + logical=logical, + logical_group="min-size", + allow_quirks="No" if logical else "Yes", + spec=spec % size, + animation_value_type="Size", + servo_restyle_damage="reflow", + affects="layout", + )} + ${helpers.predefined_type( + "max-%s" % size, + "MaxSize", + "computed::MaxSize::none()", + engines="gecko servo-2013 servo-2020", + logical=logical, + logical_group="max-size", + allow_quirks="No" if logical else "Yes", + spec=spec % size, + animation_value_type="MaxSize", + servo_restyle_damage="reflow", + affects="layout", + )} +% endfor + +${helpers.single_keyword( + "box-sizing", + "content-box border-box", + engines="gecko servo-2013 servo-2020", + extra_prefixes="moz:layout.css.prefixes.box-sizing webkit", + spec="https://drafts.csswg.org/css-ui/#propdef-box-sizing", + gecko_enum_prefix="StyleBoxSizing", + custom_consts={ "content-box": "Content", "border-box": "Border" }, + animation_value_type="discrete", + servo_restyle_damage = "reflow", + affects="layout", +)} + +${helpers.single_keyword( + "object-fit", + "fill contain cover none scale-down", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-images/#propdef-object-fit", + gecko_enum_prefix = "StyleObjectFit", + affects="layout", +)} + +${helpers.predefined_type( + "object-position", + "Position", + "computed::Position::center()", + engines="gecko", + boxed=True, + spec="https://drafts.csswg.org/css-images-3/#the-object-position", + animation_value_type="ComputedValue", + affects="layout", +)} + +% for kind in ["row", "column"]: + % for range in ["start", "end"]: + ${helpers.predefined_type( + "grid-%s-%s" % (kind, range), + "GridLine", + "Default::default()", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-%s" % (kind, range), + affects="layout", + )} + % endfor + + ${helpers.predefined_type( + "grid-auto-%ss" % kind, + "ImplicitGridTracks", + "Default::default()", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-%ss" % kind, + affects="layout", + )} + + ${helpers.predefined_type( + "grid-template-%ss" % kind, + "GridTemplateComponent", + "specified::GenericGridTemplateComponent::None", + engines="gecko", + spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-%ss" % kind, + animation_value_type="ComputedValue", + affects="layout", + )} + +% endfor + +${helpers.predefined_type( + "masonry-auto-flow", + "MasonryAutoFlow", + "computed::MasonryAutoFlow::initial()", + engines="gecko", + gecko_pref="layout.css.grid-template-masonry-value.enabled", + animation_value_type="discrete", + spec="https://github.com/w3c/csswg-drafts/issues/4650", + affects="layout", +)} + +${helpers.predefined_type( + "grid-auto-flow", + "GridAutoFlow", + "computed::GridAutoFlow::ROW", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow", + affects="layout", +)} + +${helpers.predefined_type( + "grid-template-areas", + "GridTemplateAreas", + "computed::GridTemplateAreas::none()", + engines="gecko", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas", + affects="layout", +)} + +${helpers.predefined_type( + "column-gap", + "length::NonNegativeLengthPercentageOrNormal", + "computed::length::NonNegativeLengthPercentageOrNormal::normal()", + engines="gecko servo-2013", + aliases="grid-column-gap" if engine == "gecko" else "", + servo_2013_pref="layout.columns.enabled", + spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap", + animation_value_type="NonNegativeLengthPercentageOrNormal", + servo_restyle_damage="reflow", + affects="layout", +)} + +// no need for -moz- prefixed alias for this property +${helpers.predefined_type( + "row-gap", + "length::NonNegativeLengthPercentageOrNormal", + "computed::length::NonNegativeLengthPercentageOrNormal::normal()", + engines="gecko", + aliases="grid-row-gap", + spec="https://drafts.csswg.org/css-align-3/#propdef-row-gap", + animation_value_type="NonNegativeLengthPercentageOrNormal", + servo_restyle_damage="reflow", + affects="layout", +)} + +${helpers.predefined_type( + "aspect-ratio", + "AspectRatio", + "computed::AspectRatio::auto()", + engines="gecko servo-2013", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-sizing-4/#aspect-ratio", + servo_restyle_damage="reflow", + affects="layout", +)} + +% for (size, logical) in ALL_SIZES: + ${helpers.predefined_type( + "contain-intrinsic-" + size, + "ContainIntrinsicSize", + "computed::ContainIntrinsicSize::None", + engines="gecko", + logical_group="contain-intrinsic-size", + logical=logical, + gecko_pref="layout.css.contain-intrinsic-size.enabled", + spec="https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override", + animation_value_type="NonNegativeLength", + affects="layout", + )} +% endfor diff --git a/servo/components/style/properties/longhands/svg.mako.rs b/servo/components/style/properties/longhands/svg.mako.rs new file mode 100644 index 0000000000..10788d4802 --- /dev/null +++ b/servo/components/style/properties/longhands/svg.mako.rs @@ -0,0 +1,282 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("SVG", inherited=False, gecko_name="SVGReset") %> + +${helpers.single_keyword( + "vector-effect", + "none non-scaling-stroke", + engines="gecko", + gecko_enum_prefix="StyleVectorEffect", + animation_value_type="discrete", + spec="https://svgwg.org/svg2-draft/coords.html#VectorEffects", + affects="layout", +)} + +// Section 14 - Gradients and Patterns + +${helpers.predefined_type( + "stop-color", + "Color", + "computed::Color::BLACK", + engines="gecko", + animation_value_type="AnimatedRGBA", + spec="https://svgwg.org/svg2-draft/pservers.html#StopColorProperties", + affects="paint", +)} + +${helpers.predefined_type( + "stop-opacity", + "Opacity", + "1.0", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/pservers.html#StopOpacityProperty", + affects="paint", +)} + +// Filter Effects Module + +${helpers.predefined_type( + "flood-color", + "Color", + "computed::Color::BLACK", + engines="gecko", + animation_value_type="AnimatedColor", + spec="https://drafts.fxtf.org/filter-effects-1/#FloodColorProperty", + affects="paint", +)} + +${helpers.predefined_type( + "flood-opacity", + "Opacity", + "1.0", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://drafts.fxtf.org/filter-effects-1/#FloodOpacityProperty", + affects="paint", +)} + +${helpers.predefined_type( + "lighting-color", + "Color", + "computed::Color::WHITE", + engines="gecko", + animation_value_type="AnimatedColor", + spec="https://drafts.fxtf.org/filter-effects-1#LightingColorProperty", + affects="paint", +)} + +// CSS Masking Module Level 1 +// https://drafts.fxtf.org/css-masking-1 +${helpers.single_keyword( + "mask-type", + "luminance alpha", + engines="gecko", + gecko_enum_prefix="StyleMaskType", + animation_value_type="discrete", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-type", + affects="paint", +)} + +${helpers.predefined_type( + "clip-path", + "basic_shape::ClipPath", + "generics::basic_shape::ClipPath::None", + engines="gecko", + extra_prefixes="webkit", + animation_value_type="basic_shape::ClipPath", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-clip-path", + affects="paint", +)} + +${helpers.single_keyword( + "mask-mode", + "match-source alpha luminance", + engines="gecko", + gecko_enum_prefix="StyleMaskMode", + vector=True, + animation_value_type="discrete", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode", + affects="paint", +)} + +${helpers.predefined_type( + "mask-repeat", + "BackgroundRepeat", + "computed::BackgroundRepeat::repeat()", + engines="gecko", + initial_specified_value="specified::BackgroundRepeat::repeat()", + extra_prefixes="webkit", + animation_value_type="discrete", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat", + vector=True, + affects="paint", +)} + +% for (axis, direction) in [("x", "Horizontal"), ("y", "Vertical")]: + ${helpers.predefined_type( + "mask-position-" + axis, + "position::" + direction + "Position", + "computed::LengthPercentage::zero_percent()", + engines="gecko", + extra_prefixes="webkit", + initial_specified_value="specified::PositionComponent::Center", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-position", + animation_value_type="ComputedValue", + vector_animation_type="repeatable_list", + vector=True, + affects="paint", + )} +% endfor + +${helpers.single_keyword( + "mask-clip", + "border-box content-box padding-box", + engines="gecko", + extra_gecko_values="fill-box stroke-box view-box no-clip", + vector=True, + extra_prefixes="webkit", + gecko_enum_prefix="StyleGeometryBox", + gecko_inexhaustive=True, + animation_value_type="discrete", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip", + affects="paint", +)} + +${helpers.single_keyword( + "mask-origin", + "border-box content-box padding-box", + engines="gecko", + extra_gecko_values="fill-box stroke-box view-box", + vector=True, + extra_prefixes="webkit", + gecko_enum_prefix="StyleGeometryBox", + gecko_inexhaustive=True, + animation_value_type="discrete", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin", + affects="paint", +)} + +${helpers.predefined_type( + "mask-size", + "background::BackgroundSize", + "computed::BackgroundSize::auto()", + engines="gecko", + initial_specified_value="specified::BackgroundSize::auto()", + extra_prefixes="webkit", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-size", + animation_value_type="MaskSizeList", + vector=True, + vector_animation_type="repeatable_list", + affects="paint", +)} + +${helpers.single_keyword( + "mask-composite", + "add subtract intersect exclude", + engines="gecko", + gecko_enum_prefix="StyleMaskComposite", + vector=True, + extra_prefixes="webkit", + animation_value_type="discrete", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite", + affects="paint", +)} + +${helpers.predefined_type( + "mask-image", + "Image", + engines="gecko", + initial_value="computed::Image::None", + initial_specified_value="specified::Image::None", + parse_method="parse_with_cors_anonymous", + spec="https://drafts.fxtf.org/css-masking-1/#propdef-mask-image", + vector=True, + extra_prefixes="webkit", + animation_value_type="discrete", + affects="paint", +)} + +${helpers.predefined_type( + "x", + "LengthPercentage", + "computed::LengthPercentage::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/geometry.html#X", + affects="layout", +)} + +${helpers.predefined_type( + "y", + "LengthPercentage", + "computed::LengthPercentage::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/geometry.html#Y", + affects="layout", +)} + +${helpers.predefined_type( + "cx", + "LengthPercentage", + "computed::LengthPercentage::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/geometry.html#CX", + affects="layout", +)} + +${helpers.predefined_type( + "cy", + "LengthPercentage", + "computed::LengthPercentage::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/geometry.html#CY", + affects="layout", +)} + +${helpers.predefined_type( + "rx", + "NonNegativeLengthPercentageOrAuto", + "computed::NonNegativeLengthPercentageOrAuto::auto()", + engines="gecko", + animation_value_type="LengthPercentageOrAuto", + spec="https://svgwg.org/svg2-draft/geometry.html#RX", + affects="layout", +)} + +${helpers.predefined_type( + "ry", + "NonNegativeLengthPercentageOrAuto", + "computed::NonNegativeLengthPercentageOrAuto::auto()", + engines="gecko", + animation_value_type="LengthPercentageOrAuto", + spec="https://svgwg.org/svg2-draft/geometry.html#RY", + affects="layout", +)} + +${helpers.predefined_type( + "r", + "NonNegativeLengthPercentage", + "computed::NonNegativeLengthPercentage::zero()", + engines="gecko", + animation_value_type="LengthPercentage", + spec="https://svgwg.org/svg2-draft/geometry.html#R", + affects="layout", +)} + +${helpers.predefined_type( + "d", + "DProperty", + "specified::DProperty::none()", + engines="gecko", + animation_value_type="ComputedValue", + spec="https://svgwg.org/svg2-draft/paths.html#TheDProperty", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/table.mako.rs b/servo/components/style/properties/longhands/table.mako.rs new file mode 100644 index 0000000000..3a756636ad --- /dev/null +++ b/servo/components/style/properties/longhands/table.mako.rs @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<% data.new_style_struct("Table", inherited=False) %> + +${helpers.single_keyword( + "table-layout", + "auto fixed", + engines="gecko servo-2013", + gecko_ffi_name="mLayoutStrategy", + animation_value_type="discrete", + gecko_enum_prefix="StyleTableLayout", + spec="https://drafts.csswg.org/css-tables/#propdef-table-layout", + servo_restyle_damage="reflow", + affects="layout", +)} + +${helpers.predefined_type( + "-x-span", + "Integer", + "1", + engines="gecko", + spec="Internal-only (for `<col span>` pres attr)", + animation_value_type="none", + enabled_in="", + affects="layout", +)} diff --git a/servo/components/style/properties/longhands/text.mako.rs b/servo/components/style/properties/longhands/text.mako.rs new file mode 100644 index 0000000000..0ee8ba3168 --- /dev/null +++ b/servo/components/style/properties/longhands/text.mako.rs @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import Method %> + +<% data.new_style_struct("Text", inherited=False, gecko_name="TextReset") %> + +${helpers.predefined_type( + "text-overflow", + "TextOverflow", + "computed::TextOverflow::get_initial_value()", + engines="gecko servo-2013", + animation_value_type="discrete", + boxed=True, + spec="https://drafts.csswg.org/css-ui/#propdef-text-overflow", + servo_restyle_damage="rebuild_and_reflow", + affects="paint", +)} + +${helpers.single_keyword( + "unicode-bidi", + "normal embed isolate bidi-override isolate-override plaintext", + engines="gecko servo-2013", + gecko_enum_prefix="StyleUnicodeBidi", + animation_value_type="none", + spec="https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi", + servo_restyle_damage="rebuild_and_reflow", + affects="layout", +)} + +${helpers.predefined_type( + "text-decoration-line", + "TextDecorationLine", + "specified::TextDecorationLine::none()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::TextDecorationLine::none()", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-line", + servo_restyle_damage="rebuild_and_reflow", + affects="overflow", +)} + +${helpers.single_keyword( + "text-decoration-style", + "solid double dotted dashed wavy -moz-none", + engines="gecko servo-2020", + gecko_enum_prefix="StyleTextDecorationStyle", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style", + affects="overflow", +)} + +${helpers.predefined_type( + "text-decoration-color", + "Color", + "computed_value::T::currentcolor()", + engines="gecko servo-2020", + initial_specified_value="specified::Color::currentcolor()", + animation_value_type="AnimatedColor", + ignored_when_colors_disabled=True, + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color", + affects="paint", +)} + +${helpers.predefined_type( + "initial-letter", + "InitialLetter", + "computed::InitialLetter::normal()", + engines="gecko", + initial_specified_value="specified::InitialLetter::normal()", + animation_value_type="discrete", + gecko_pref="layout.css.initial-letter.enabled", + spec="https://drafts.csswg.org/css-inline/#sizing-drop-initials", + affects="layout", +)} + +${helpers.predefined_type( + "text-decoration-thickness", + "TextDecorationLength", + "generics::text::GenericTextDecorationLength::Auto", + engines="gecko", + initial_specified_value="generics::text::GenericTextDecorationLength::Auto", + animation_value_type="ComputedValue", + spec="https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property", + affects="overflow", +)} diff --git a/servo/components/style/properties/longhands/ui.mako.rs b/servo/components/style/properties/longhands/ui.mako.rs new file mode 100644 index 0000000000..1150816ac0 --- /dev/null +++ b/servo/components/style/properties/longhands/ui.mako.rs @@ -0,0 +1,422 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %> + +// CSS Basic User Interface Module Level 1 +// https://drafts.csswg.org/css-ui-3/ +<% data.new_style_struct("UI", inherited=False, gecko_name="UIReset") %> + +// TODO spec says that UAs should not support this +// we should probably remove from gecko (https://bugzilla.mozilla.org/show_bug.cgi?id=1328331) +${helpers.single_keyword( + "ime-mode", + "auto normal active disabled inactive", + engines="gecko", + gecko_enum_prefix="StyleImeMode", + gecko_ffi_name="mIMEMode", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-ui/#input-method-editor", + affects="", +)} + +${helpers.single_keyword( + "scrollbar-width", + "auto thin none", + engines="gecko", + gecko_enum_prefix="StyleScrollbarWidth", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-width", + affects="layout", +)} + +${helpers.predefined_type( + "user-select", + "UserSelect", + "computed::UserSelect::Auto", + engines="gecko", + extra_prefixes="moz webkit", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-ui-4/#propdef-user-select", + affects="", +)} + +// TODO(emilio): This probably should be hidden from content. +${helpers.single_keyword( + "-moz-window-dragging", + "default drag no-drag", + engines="gecko", + gecko_ffi_name="mWindowDragging", + gecko_enum_prefix="StyleWindowDragging", + animation_value_type="discrete", + spec="None (Nonstandard Firefox-only property)", + affects="paint", +)} + +// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this? But +// that requires making -moz-window-input-region-margin work there... +${helpers.single_keyword( + "-moz-window-shadow", + "auto none", + engines="gecko", + gecko_ffi_name="mWindowShadow", + gecko_enum_prefix="StyleWindowShadow", + animation_value_type="discrete", + enabled_in="chrome", + spec="None (Nonstandard internal property)", + affects="overflow", +)} + +${helpers.predefined_type( + "-moz-window-opacity", + "Opacity", + "1.0", + engines="gecko", + gecko_ffi_name="mWindowOpacity", + animation_value_type="ComputedValue", + spec="None (Nonstandard internal property)", + enabled_in="chrome", + affects="paint", +)} + +${helpers.predefined_type( + "-moz-window-transform", + "Transform", + "generics::transform::Transform::none()", + engines="gecko", + animation_value_type="ComputedValue", + spec="None (Nonstandard internal property)", + enabled_in="chrome", + affects="overflow", +)} + +${helpers.predefined_type( + "-moz-window-transform-origin", + "TransformOrigin", + "computed::TransformOrigin::initial_value()", + engines="gecko", + animation_value_type="ComputedValue", + gecko_ffi_name="mWindowTransformOrigin", + boxed=True, + spec="None (Nonstandard internal property)", + enabled_in="chrome", + affects="overflow", +)} + +${helpers.predefined_type( + "-moz-window-input-region-margin", + "Length", + "computed::Length::zero()", + engines="gecko", + animation_value_type="ComputedValue", + spec="None (Nonstandard internal property)", + enabled_in="chrome", + affects="", +)} + +// Hack to allow chrome to hide stuff only visually (without hiding it from a11y). +${helpers.predefined_type( + "-moz-subtree-hidden-only-visually", + "BoolInteger", + "computed::BoolInteger::zero()", + engines="gecko", + animation_value_type="discrete", + spec="None (Nonstandard internal property)", + enabled_in="chrome", + affects="paint", +)} + +// TODO(emilio): Probably also should be hidden from content. +${helpers.predefined_type( + "-moz-force-broken-image-icon", + "BoolInteger", + "computed::BoolInteger::zero()", + engines="gecko", + animation_value_type="discrete", + spec="None (Nonstandard Firefox-only property)", + affects="layout", +)} + +<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %> + +${helpers.predefined_type( + "transition-duration", + "Time", + "computed::Time::zero()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Time::zero()", + parse_method="parse_non_negative", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", + affects="", +)} + +${helpers.predefined_type( + "transition-timing-function", + "TimingFunction", + "computed::TimingFunction::ease()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::TimingFunction::ease()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function", + affects="", +)} + +${helpers.predefined_type( + "transition-property", + "TransitionProperty", + "computed::TransitionProperty::all()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::TransitionProperty::all()", + vector=True, + none_value="computed::TransitionProperty::none()", + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property", + affects="", +)} + +${helpers.predefined_type( + "transition-delay", + "Time", + "computed::Time::zero()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Time::zero()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=transition_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay", + affects="", +)} + +<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %> + +${helpers.predefined_type( + "animation-name", + "AnimationName", + "computed::AnimationName::none()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::AnimationName::none()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-name", + affects="", +)} + +${helpers.predefined_type( + "animation-duration", + "Time", + "computed::Time::zero()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Time::zero()", + parse_method="parse_non_negative", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", + affects="", +)} + +// animation-timing-function is the exception to the rule for allowed_in_keyframe_block: +// https://drafts.csswg.org/css-animations/#keyframes +${helpers.predefined_type( + "animation-timing-function", + "TimingFunction", + "computed::TimingFunction::ease()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::TimingFunction::ease()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function", + affects="", +)} + +${helpers.predefined_type( + "animation-iteration-count", + "AnimationIterationCount", + "computed::AnimationIterationCount::one()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::AnimationIterationCount::one()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count", + affects="", +)} + +${helpers.predefined_type( + "animation-direction", + "AnimationDirection", + "computed::AnimationDirection::Normal", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::AnimationDirection::Normal", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "animation-play-state", + "AnimationPlayState", + "computed::AnimationPlayState::Running", + engines="gecko servo-2013 servo-2020", + initial_specified_value="computed::AnimationPlayState::Running", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "animation-fill-mode", + "AnimationFillMode", + "computed::AnimationFillMode::None", + engines="gecko servo-2013 servo-2020", + initial_specified_value="computed::AnimationFillMode::None", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "animation-composition", + "AnimationComposition", + "computed::AnimationComposition::Replace", + engines="gecko", + initial_specified_value="computed::AnimationComposition::Replace", + vector=True, + need_index=True, + animation_value_type="none", + gecko_pref="layout.css.animation-composition.enabled", + spec="https://drafts.csswg.org/css-animations-2/#animation-composition", + affects="", +)} + +${helpers.predefined_type( + "animation-delay", + "Time", + "computed::Time::zero()", + engines="gecko servo-2013 servo-2020", + initial_specified_value="specified::Time::zero()", + vector=True, + need_index=True, + animation_value_type="none", + extra_prefixes=animation_extra_prefixes, + spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "animation-timeline", + "AnimationTimeline", + "computed::AnimationTimeline::auto()", + engines="gecko", + initial_specified_value="specified::AnimationTimeline::auto()", + vector=True, + need_index=True, + animation_value_type="none", + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "scroll-timeline-name", + "ScrollTimelineName", + "computed::ScrollTimelineName::none()", + vector=True, + need_index=True, + engines="gecko", + animation_value_type="none", + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-name", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "scroll-timeline-axis", + "ScrollAxis", + "computed::ScrollAxis::default()", + vector=True, + need_index=True, + engines="gecko", + animation_value_type="none", + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "view-timeline-name", + "ScrollTimelineName", + "computed::ScrollTimelineName::none()", + vector=True, + need_index=True, + engines="gecko", + animation_value_type="none", + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-name", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "view-timeline-axis", + "ScrollAxis", + "computed::ScrollAxis::default()", + vector=True, + need_index=True, + engines="gecko", + animation_value_type="none", + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} + +${helpers.predefined_type( + "view-timeline-inset", + "ViewTimelineInset", + "computed::ViewTimelineInset::default()", + vector=True, + need_index=True, + engines="gecko", + animation_value_type="none", + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, + affects="", +)} diff --git a/servo/components/style/properties/longhands/xul.mako.rs b/servo/components/style/properties/longhands/xul.mako.rs new file mode 100644 index 0000000000..8974ac30dc --- /dev/null +++ b/servo/components/style/properties/longhands/xul.mako.rs @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import Method %> + +// Non-standard properties that Gecko uses for XUL elements. +<% data.new_style_struct("XUL", inherited=False) %> + +${helpers.single_keyword( + "-moz-box-align", + "stretch start center baseline end", + engines="gecko", + gecko_ffi_name="mBoxAlign", + gecko_enum_prefix="StyleBoxAlign", + animation_value_type="discrete", + aliases="-webkit-box-align", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-align)", + affects="layout", +)} + +${helpers.single_keyword( + "-moz-box-direction", + "normal reverse", + engines="gecko", + gecko_ffi_name="mBoxDirection", + gecko_enum_prefix="StyleBoxDirection", + animation_value_type="discrete", + aliases="-webkit-box-direction", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-direction)", + affects="layout", +)} + +${helpers.predefined_type( + "-moz-box-flex", + "NonNegativeNumber", + "From::from(0.)", + engines="gecko", + gecko_ffi_name="mBoxFlex", + animation_value_type="NonNegativeNumber", + aliases="-webkit-box-flex", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-flex)", + affects="layout", +)} + +${helpers.single_keyword( + "-moz-box-orient", + "horizontal vertical", + engines="gecko", + gecko_ffi_name="mBoxOrient", + gecko_aliases="inline-axis=horizontal block-axis=vertical", + gecko_enum_prefix="StyleBoxOrient", + animation_value_type="discrete", + aliases="-webkit-box-orient", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-orient)", + affects="layout", +)} + +${helpers.single_keyword( + "-moz-box-pack", + "start center end justify", + engines="gecko", + gecko_ffi_name="mBoxPack", + gecko_enum_prefix="StyleBoxPack", + animation_value_type="discrete", + aliases="-webkit-box-pack", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-pack)", + affects="layout", +)} + +// NOTE(heycam): Odd that the initial value is 1 yet 0 is a valid value. There +// are uses of `-moz-box-ordinal-group: 0` in the tree, too. +${helpers.predefined_type( + "-moz-box-ordinal-group", + "Integer", + "1", + engines="gecko", + parse_method="parse_non_negative", + aliases="-webkit-box-ordinal-group", + gecko_ffi_name="mBoxOrdinal", + animation_value_type="discrete", + spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-box-ordinal-group)", + affects="layout", +)} diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs new file mode 100644 index 0000000000..7adb6d4ae6 --- /dev/null +++ b/servo/components/style/properties/mod.rs @@ -0,0 +1,1531 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! Supported CSS properties and the cascade. + +pub mod cascade; +pub mod declaration_block; + +pub use self::cascade::*; +pub use self::declaration_block::*; +pub use self::generated::*; +/// The CSS properties supported by the style system. +/// Generated from the properties.mako.rs template by build.rs +#[macro_use] +#[allow(unsafe_code)] +#[deny(missing_docs)] +pub mod generated { + include!(concat!(env!("OUT_DIR"), "/properties.rs")); + + #[cfg(feature = "gecko")] + #[allow(unsafe_code, missing_docs)] + pub mod gecko { + include!(concat!(env!("OUT_DIR"), "/gecko_properties.rs")); + } +} + +use crate::custom_properties::{self, ComputedCustomProperties}; +#[cfg(feature = "gecko")] +use crate::gecko_bindings::structs::{nsCSSPropertyID, AnimatedPropertyID, RefPtr}; +use crate::logical_geometry::WritingMode; +use crate::parser::ParserContext; +use crate::str::CssString; +use crate::stylesheets::Origin; +use crate::stylist::Stylist; +use crate::values::{computed, serialize_atom_name}; +use arrayvec::{ArrayVec, Drain as ArrayVecDrain}; +use cssparser::{Parser, ParserInput}; +use fxhash::FxHashMap; +use servo_arc::Arc; +use std::{ + borrow::Cow, + fmt::{self, Write}, + mem, +}; +use style_traits::{ + CssWriter, KeywordsCollectFn, ParseError, ParsingMode, SpecifiedValueInfo, ToCss, +}; + +bitflags! { + /// A set of flags for properties. + #[derive(Clone, Copy)] + pub struct PropertyFlags: u16 { + /// This longhand property applies to ::first-letter. + const APPLIES_TO_FIRST_LETTER = 1 << 1; + /// This longhand property applies to ::first-line. + const APPLIES_TO_FIRST_LINE = 1 << 2; + /// This longhand property applies to ::placeholder. + const APPLIES_TO_PLACEHOLDER = 1 << 3; + /// This longhand property applies to ::cue. + const APPLIES_TO_CUE = 1 << 4; + /// This longhand property applies to ::marker. + const APPLIES_TO_MARKER = 1 << 5; + /// This property is a legacy shorthand. + /// + /// https://drafts.csswg.org/css-cascade/#legacy-shorthand + const IS_LEGACY_SHORTHAND = 1 << 6; + + /* The following flags are currently not used in Rust code, they + * only need to be listed in corresponding properties so that + * they can be checked in the C++ side via ServoCSSPropList.h. */ + + /// This property can be animated on the compositor. + const CAN_ANIMATE_ON_COMPOSITOR = 0; + /// This shorthand property is accessible from getComputedStyle. + const SHORTHAND_IN_GETCS = 0; + /// See data.py's documentation about the affects_flags. + const AFFECTS_LAYOUT = 0; + #[allow(missing_docs)] + const AFFECTS_OVERFLOW = 0; + #[allow(missing_docs)] + const AFFECTS_PAINT = 0; + } +} + +/// An enum to represent a CSS Wide keyword. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum CSSWideKeyword { + /// The `initial` keyword. + Initial, + /// The `inherit` keyword. + Inherit, + /// The `unset` keyword. + Unset, + /// The `revert` keyword. + Revert, + /// The `revert-layer` keyword. + RevertLayer, +} + +impl CSSWideKeyword { + /// Returns the string representation of the keyword. + pub fn to_str(&self) -> &'static str { + match *self { + CSSWideKeyword::Initial => "initial", + CSSWideKeyword::Inherit => "inherit", + CSSWideKeyword::Unset => "unset", + CSSWideKeyword::Revert => "revert", + CSSWideKeyword::RevertLayer => "revert-layer", + } + } +} + +impl CSSWideKeyword { + /// Parses a CSS wide keyword from a CSS identifier. + pub fn from_ident(ident: &str) -> Result<Self, ()> { + Ok(match_ignore_ascii_case! { ident, + "initial" => CSSWideKeyword::Initial, + "inherit" => CSSWideKeyword::Inherit, + "unset" => CSSWideKeyword::Unset, + "revert" => CSSWideKeyword::Revert, + "revert-layer" => CSSWideKeyword::RevertLayer, + _ => return Err(()), + }) + } + + /// Parses a CSS wide keyword completely. + pub fn parse(input: &mut Parser) -> Result<Self, ()> { + let keyword = { + let ident = input.expect_ident().map_err(|_| ())?; + Self::from_ident(ident)? + }; + input.expect_exhausted().map_err(|_| ())?; + Ok(keyword) + } +} + +/// A declaration using a CSS-wide keyword. +#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] +pub struct WideKeywordDeclaration { + #[css(skip)] + id: LonghandId, + /// The CSS-wide keyword. + pub keyword: CSSWideKeyword, +} + +/// An unparsed declaration that contains `var()` functions. +#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] +pub struct VariableDeclaration { + /// The id of the property this declaration represents. + #[css(skip)] + id: LonghandId, + /// The unparsed value of the variable. + #[ignore_malloc_size_of = "Arc"] + pub value: Arc<UnparsedValue>, +} + +/// A custom property declaration value is either an unparsed value or a CSS +/// wide-keyword. +#[derive(Clone, PartialEq, ToCss, ToShmem)] +pub enum CustomDeclarationValue { + /// A value. + Value(Arc<custom_properties::SpecifiedValue>), + /// A wide keyword. + CSSWideKeyword(CSSWideKeyword), +} + +/// A custom property declaration with the property name and the declared value. +#[derive(Clone, PartialEq, ToCss, ToShmem, MallocSizeOf)] +pub struct CustomDeclaration { + /// The name of the custom property. + #[css(skip)] + pub name: custom_properties::Name, + /// The value of the custom property. + #[ignore_malloc_size_of = "Arc"] + pub value: CustomDeclarationValue, +} + +impl fmt::Debug for PropertyDeclaration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.id().to_css(&mut CssWriter::new(f))?; + f.write_str(": ")?; + + // Because PropertyDeclaration::to_css requires CssStringWriter, we can't write + // it directly to f, and need to allocate an intermediate string. This is + // fine for debug-only code. + let mut s = CssString::new(); + self.to_css(&mut s)?; + write!(f, "{}", s) + } +} + +/// A longhand or shorthand property. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, ToComputedValue, ToResolvedValue, ToShmem, MallocSizeOf)] +#[repr(C)] +pub struct NonCustomPropertyId(u16); + +impl ToCss for NonCustomPropertyId { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str(self.name()) + } +} + +impl NonCustomPropertyId { + /// Returns the underlying index, used for use counter. + pub fn bit(self) -> usize { + self.0 as usize + } + + /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + // unsafe: guaranteed by static_assert_nscsspropertyid. + unsafe { mem::transmute(self.0 as i32) } + } + + /// Convert an `nsCSSPropertyID` into a `NonCustomPropertyId`. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Option<Self> { + let prop = prop as i32; + if prop < 0 || prop >= property_counts::NON_CUSTOM as i32 { + return None; + } + // guaranteed by static_assert_nscsspropertyid above. + Some(NonCustomPropertyId(prop as u16)) + } + + /// Resolves the alias of a given property if needed. + pub fn unaliased(self) -> Self { + let Some(alias_id) = self.as_alias() else { + return self; + }; + alias_id.aliased_property() + } + + /// Turns this `NonCustomPropertyId` into a `PropertyId`. + #[inline] + pub fn to_property_id(self) -> PropertyId { + PropertyId::NonCustom(self) + } + + /// Returns a longhand id, if this property is one. + #[inline] + pub fn as_longhand(self) -> Option<LonghandId> { + if self.0 < property_counts::LONGHANDS as u16 { + return Some(unsafe { mem::transmute(self.0 as u16) }); + } + None + } + + /// Returns a shorthand id, if this property is one. + #[inline] + pub fn as_shorthand(self) -> Option<ShorthandId> { + if self.0 >= property_counts::LONGHANDS as u16 && + self.0 < property_counts::LONGHANDS_AND_SHORTHANDS as u16 + { + return Some(unsafe { mem::transmute(self.0 - (property_counts::LONGHANDS as u16)) }); + } + None + } + + /// Returns an alias id, if this property is one. + #[inline] + pub fn as_alias(self) -> Option<AliasId> { + debug_assert!((self.0 as usize) < property_counts::NON_CUSTOM); + if self.0 >= property_counts::LONGHANDS_AND_SHORTHANDS as u16 { + return Some(unsafe { + mem::transmute(self.0 - (property_counts::LONGHANDS_AND_SHORTHANDS as u16)) + }); + } + None + } + + /// Returns either a longhand or a shorthand, resolving aliases. + #[inline] + pub fn longhand_or_shorthand(self) -> Result<LonghandId, ShorthandId> { + let id = self.unaliased(); + match id.as_longhand() { + Some(lh) => Ok(lh), + None => Err(id.as_shorthand().unwrap()), + } + } + + /// Converts a longhand id into a non-custom property id. + #[inline] + pub const fn from_longhand(id: LonghandId) -> Self { + Self(id as u16) + } + + /// Converts a shorthand id into a non-custom property id. + #[inline] + pub const fn from_shorthand(id: ShorthandId) -> Self { + Self((id as u16) + (property_counts::LONGHANDS as u16)) + } + + /// Converts an alias id into a non-custom property id. + #[inline] + pub const fn from_alias(id: AliasId) -> Self { + Self((id as u16) + (property_counts::LONGHANDS_AND_SHORTHANDS as u16)) + } +} + +impl From<LonghandId> for NonCustomPropertyId { + #[inline] + fn from(id: LonghandId) -> Self { + Self::from_longhand(id) + } +} + +impl From<ShorthandId> for NonCustomPropertyId { + #[inline] + fn from(id: ShorthandId) -> Self { + Self::from_shorthand(id) + } +} + +impl From<AliasId> for NonCustomPropertyId { + #[inline] + fn from(id: AliasId) -> Self { + Self::from_alias(id) + } +} + +/// Representation of a CSS property, that is, either a longhand, a shorthand, or a custom +/// property. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum PropertyId { + /// An alias for a shorthand property. + NonCustom(NonCustomPropertyId), + /// A custom property. + Custom(custom_properties::Name), +} + +impl ToCss for PropertyId { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + PropertyId::NonCustom(id) => dest.write_str(id.name()), + PropertyId::Custom(ref name) => { + dest.write_str("--")?; + serialize_atom_name(name, dest) + }, + } + } +} + +impl PropertyId { + /// Return the longhand id that this property id represents. + #[inline] + pub fn longhand_id(&self) -> Option<LonghandId> { + self.non_custom_non_alias_id()?.as_longhand() + } + + /// Returns true if this property is one of the animatable properties. + pub fn is_animatable(&self) -> bool { + match self { + Self::NonCustom(id) => id.is_animatable(), + Self::Custom(..) => true, + } + } + + /// Returns a given property from the given name, _regardless of whether it is enabled or + /// not_, or Err(()) for unknown properties. + /// + /// Do not use for non-testing purposes. + pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> { + Self::parse_unchecked(name, None) + } + + /// Parses a property name, and returns an error if it's unknown or isn't enabled for all + /// content. + #[inline] + pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> { + let id = Self::parse_unchecked(name, None)?; + + if !id.enabled_for_all_content() { + return Err(()); + } + + Ok(id) + } + + /// Parses a property name, and returns an error if it's unknown or isn't allowed in this + /// context. + #[inline] + pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> { + let id = Self::parse_unchecked(name, context.use_counters)?; + if !id.allowed_in(context) { + return Err(()); + } + Ok(id) + } + + /// Parses a property name, and returns an error if it's unknown or isn't allowed in this + /// context, ignoring the rule_type checks. + /// + /// This is useful for parsing stuff from CSS values, for example. + #[inline] + pub fn parse_ignoring_rule_type(name: &str, context: &ParserContext) -> Result<Self, ()> { + let id = Self::parse_unchecked(name, None)?; + if !id.allowed_in_ignoring_rule_type(context) { + return Err(()); + } + Ok(id) + } + + /// Returns a property id from Gecko's nsCSSPropertyID. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Option<Self> { + Some(NonCustomPropertyId::from_nscsspropertyid(id)?.to_property_id()) + } + + /// Returns a property id from Gecko's AnimatedPropertyID. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_gecko_animated_property_id(property: &AnimatedPropertyID) -> Option<Self> { + Some( + if property.mID == nsCSSPropertyID::eCSSPropertyExtra_variable { + debug_assert!(!property.mCustomName.mRawPtr.is_null()); + Self::Custom(unsafe { crate::Atom::from_raw(property.mCustomName.mRawPtr) }) + } else { + Self::NonCustom(NonCustomPropertyId::from_nscsspropertyid(property.mID)?) + }, + ) + } + + /// Returns true if the property is a shorthand or shorthand alias. + #[inline] + pub fn is_shorthand(&self) -> bool { + self.as_shorthand().is_ok() + } + + /// Given this property id, get it either as a shorthand or as a + /// `PropertyDeclarationId`. + pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId> { + match *self { + Self::NonCustom(id) => match id.longhand_or_shorthand() { + Ok(lh) => Err(PropertyDeclarationId::Longhand(lh)), + Err(sh) => Ok(sh), + }, + Self::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)), + } + } + + /// Returns the `NonCustomPropertyId` corresponding to this property id. + pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> { + match *self { + Self::Custom(_) => None, + Self::NonCustom(id) => Some(id), + } + } + + /// Returns non-alias NonCustomPropertyId corresponding to this + /// property id. + fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> { + self.non_custom_id().map(NonCustomPropertyId::unaliased) + } + + /// Whether the property is enabled for all content regardless of the + /// stylesheet it was declared on (that is, in practice only checks prefs). + #[inline] + pub fn enabled_for_all_content(&self) -> bool { + let id = match self.non_custom_id() { + // Custom properties are allowed everywhere + None => return true, + Some(id) => id, + }; + + id.enabled_for_all_content() + } + + /// Converts this PropertyId in nsCSSPropertyID, resolving aliases to the + /// resolved property, and returning eCSSPropertyExtra_variable for custom + /// properties. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid_resolving_aliases(&self) -> nsCSSPropertyID { + match self.non_custom_non_alias_id() { + Some(id) => id.to_nscsspropertyid(), + None => nsCSSPropertyID::eCSSPropertyExtra_variable, + } + } + + fn allowed_in(&self, context: &ParserContext) -> bool { + let id = match self.non_custom_id() { + // Custom properties are allowed everywhere + None => return true, + Some(id) => id, + }; + id.allowed_in(context) + } + + #[inline] + fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool { + let id = match self.non_custom_id() { + // Custom properties are allowed everywhere + None => return true, + Some(id) => id, + }; + id.allowed_in_ignoring_rule_type(context) + } + + /// Whether the property supports the given CSS type. + /// `ty` should a bitflags of constants in style_traits::CssType. + pub fn supports_type(&self, ty: u8) -> bool { + let id = self.non_custom_non_alias_id(); + id.map_or(0, |id| id.supported_types()) & ty != 0 + } + + /// Collect supported starting word of values of this property. + /// + /// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more + /// details. + pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { + if let Some(id) = self.non_custom_non_alias_id() { + id.collect_property_completion_keywords(f); + } + CSSWideKeyword::collect_completion_keywords(f); + } +} + +impl ToCss for LonghandId { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str(self.name()) + } +} + +impl fmt::Debug for LonghandId { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(self.name()) + } +} + +impl LonghandId { + /// Get the name of this longhand property. + #[inline] + pub fn name(&self) -> &'static str { + NonCustomPropertyId::from(*self).name() + } + + /// Returns whether the longhand property is inherited by default. + #[inline] + pub fn inherited(self) -> bool { + !LonghandIdSet::reset().contains(self) + } + + /// Returns true if the property is one that is ignored when document + /// colors are disabled. + #[inline] + pub fn ignored_when_document_colors_disabled(self) -> bool { + LonghandIdSet::ignored_when_colors_disabled().contains(self) + } + + /// Returns whether this longhand is `non_custom` or is a longhand of it. + pub fn is_or_is_longhand_of(self, non_custom: NonCustomPropertyId) -> bool { + match non_custom.longhand_or_shorthand() { + Ok(lh) => self == lh, + Err(sh) => self.is_longhand_of(sh), + } + } + + /// Returns whether this longhand is a longhand of `shorthand`. + pub fn is_longhand_of(self, shorthand: ShorthandId) -> bool { + self.shorthands().any(|s| s == shorthand) + } + + /// Returns whether this property is animatable. + #[inline] + pub fn is_animatable(self) -> bool { + NonCustomPropertyId::from(self).is_animatable() + } + + /// Returns whether this property is animatable in a discrete way. + #[inline] + pub fn is_discrete_animatable(self) -> bool { + LonghandIdSet::discrete_animatable().contains(self) + } + + /// Converts from a LonghandId to an adequate nsCSSPropertyID. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + NonCustomPropertyId::from(self).to_nscsspropertyid() + } + + #[cfg(feature = "gecko")] + /// Returns a longhand id from Gecko's nsCSSPropertyID. + pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Option<Self> { + NonCustomPropertyId::from_nscsspropertyid(id)? + .unaliased() + .as_longhand() + } + + /// Return whether this property is logical. + #[inline] + pub fn is_logical(self) -> bool { + LonghandIdSet::logical().contains(self) + } +} + +impl ToCss for ShorthandId { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str(self.name()) + } +} + +impl ShorthandId { + /// Get the name for this shorthand property. + #[inline] + pub fn name(&self) -> &'static str { + NonCustomPropertyId::from(*self).name() + } + + /// Converts from a ShorthandId to an adequate nsCSSPropertyID. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + NonCustomPropertyId::from(self).to_nscsspropertyid() + } + + /// Converts from a nsCSSPropertyID to a ShorthandId. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Option<Self> { + NonCustomPropertyId::from_nscsspropertyid(id)? + .unaliased() + .as_shorthand() + } + + /// Finds and returns an appendable value for the given declarations. + /// + /// Returns the optional appendable value. + pub fn get_shorthand_appendable_value<'a, 'b: 'a>( + self, + declarations: &'a [&'b PropertyDeclaration], + ) -> Option<AppendableValue<'a, 'b>> { + let first_declaration = declarations.get(0)?; + let rest = || declarations.iter().skip(1); + + // https://drafts.csswg.org/css-variables/#variables-in-shorthands + if let Some(css) = first_declaration.with_variables_from_shorthand(self) { + if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) { + return Some(AppendableValue::Css(css)); + } + return None; + } + + // Check whether they are all the same CSS-wide keyword. + if let Some(keyword) = first_declaration.get_css_wide_keyword() { + if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) { + return Some(AppendableValue::Css(keyword.to_str())); + } + return None; + } + + if self == ShorthandId::All { + // 'all' only supports variables and CSS wide keywords. + return None; + } + + // Check whether all declarations can be serialized as part of shorthand. + if declarations + .iter() + .all(|d| d.may_serialize_as_part_of_shorthand()) + { + return Some(AppendableValue::DeclarationsForShorthand( + self, + declarations, + )); + } + + None + } + + /// Returns whether this property is a legacy shorthand. + #[inline] + pub fn is_legacy_shorthand(self) -> bool { + self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND) + } +} + +impl PropertyDeclaration { + fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> { + match *self { + PropertyDeclaration::WithVariables(ref declaration) => { + let s = declaration.value.from_shorthand?; + if s != shorthand { + return None; + } + Some(&*declaration.value.variable_value.css) + }, + _ => None, + } + } + + /// Returns a CSS-wide keyword declaration for a given property. + #[inline] + pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self { + Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword }) + } + + /// Returns a CSS-wide keyword if the declaration's value is one. + #[inline] + pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> { + match *self { + PropertyDeclaration::CSSWideKeyword(ref declaration) => Some(declaration.keyword), + _ => None, + } + } + + /// Returns whether the declaration may be serialized as part of a shorthand. + /// + /// This method returns false if this declaration contains variable or has a + /// CSS-wide keyword value, since these values cannot be serialized as part + /// of a shorthand. + /// + /// Caller should check `with_variables_from_shorthand()` and whether all + /// needed declarations has the same CSS-wide keyword first. + /// + /// Note that, serialization of a shorthand may still fail because of other + /// property-specific requirement even when this method returns true for all + /// the longhand declarations. + pub fn may_serialize_as_part_of_shorthand(&self) -> bool { + match *self { + PropertyDeclaration::CSSWideKeyword(..) | PropertyDeclaration::WithVariables(..) => { + false + }, + PropertyDeclaration::Custom(..) => { + unreachable!("Serializing a custom property as part of shorthand?") + }, + _ => true, + } + } + + /// Returns true if this property declaration is for one of the animatable properties. + pub fn is_animatable(&self) -> bool { + self.id().is_animatable() + } + + /// Returns true if this property is a custom property, false + /// otherwise. + pub fn is_custom(&self) -> bool { + matches!(*self, PropertyDeclaration::Custom(..)) + } + + /// The `context` parameter controls this: + /// + /// <https://drafts.csswg.org/css-animations/#keyframes> + /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property + /// > except those defined in this specification, + /// > but does accept the `animation-play-state` property and interprets it specially. + /// + /// This will not actually parse Importance values, and will always set things + /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser, + /// we only set them here so that we don't have to reallocate + pub fn parse_into<'i, 't>( + declarations: &mut SourcePropertyDeclaration, + id: PropertyId, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + assert!(declarations.is_empty()); + debug_assert!(id.allowed_in(context), "{:?}", id); + input.skip_whitespace(); + + let start = input.state(); + let non_custom_id = match id { + PropertyId::Custom(property_name) => { + let value = match input.try_parse(CSSWideKeyword::parse) { + Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword), + Err(()) => CustomDeclarationValue::Value(Arc::new( + custom_properties::VariableValue::parse(input, &context.url_data)?, + )), + }; + declarations.push(PropertyDeclaration::Custom(CustomDeclaration { + name: property_name, + value, + })); + return Ok(()); + }, + PropertyId::NonCustom(id) => id, + }; + match non_custom_id.longhand_or_shorthand() { + Ok(longhand_id) => { + let declaration = input + .try_parse(CSSWideKeyword::parse) + .map(|keyword| PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) + .or_else(|()| { + input.look_for_var_or_env_functions(); + input.parse_entirely(|input| longhand_id.parse_value(context, input)) + }) + .or_else(|err| { + while let Ok(_) = input.next() {} // Look for var() after the error. + if !input.seen_var_or_env_functions() { + return Err(err); + } + input.reset(&start); + let variable_value = + custom_properties::VariableValue::parse(input, &context.url_data)?; + Ok(PropertyDeclaration::WithVariables(VariableDeclaration { + id: longhand_id, + value: Arc::new(UnparsedValue { + variable_value, + from_shorthand: None, + }), + })) + })?; + declarations.push(declaration) + }, + Err(shorthand_id) => { + if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { + if shorthand_id == ShorthandId::All { + declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword) + } else { + for longhand in shorthand_id.longhands() { + declarations + .push(PropertyDeclaration::css_wide_keyword(longhand, keyword)); + } + } + } else { + input.look_for_var_or_env_functions(); + // Not using parse_entirely here: each + // ${shorthand.ident}::parse_into function needs to do so + // *before* pushing to `declarations`. + shorthand_id + .parse_into(declarations, context, input) + .or_else(|err| { + while let Ok(_) = input.next() {} // Look for var() after the error. + if !input.seen_var_or_env_functions() { + return Err(err); + } + + input.reset(&start); + let variable_value = + custom_properties::VariableValue::parse(input, &context.url_data)?; + let unparsed = Arc::new(UnparsedValue { + variable_value, + from_shorthand: Some(shorthand_id), + }); + if shorthand_id == ShorthandId::All { + declarations.all_shorthand = AllShorthand::WithVariables(unparsed) + } else { + for id in shorthand_id.longhands() { + declarations.push(PropertyDeclaration::WithVariables( + VariableDeclaration { + id, + value: unparsed.clone(), + }, + )) + } + } + Ok(()) + })?; + } + }, + } + if let Some(use_counters) = context.use_counters { + use_counters.non_custom_properties.record(non_custom_id); + } + Ok(()) + } +} + +/// A PropertyDeclarationId without references, for use as a hash map key. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum OwnedPropertyDeclarationId { + /// A longhand. + Longhand(LonghandId), + /// A custom property declaration. + Custom(custom_properties::Name), +} + +impl OwnedPropertyDeclarationId { + /// Return whether this property is logical. + #[inline] + pub fn is_logical(&self) -> bool { + self.as_borrowed().is_logical() + } + + /// Returns the corresponding PropertyDeclarationId. + #[inline] + pub fn as_borrowed(&self) -> PropertyDeclarationId { + match self { + Self::Longhand(id) => PropertyDeclarationId::Longhand(*id), + Self::Custom(name) => PropertyDeclarationId::Custom(name), + } + } + + /// Convert an `AnimatedPropertyID` into an `OwnedPropertyDeclarationId`. + #[cfg(feature = "gecko")] + #[inline] + pub fn from_gecko_animated_property_id(property: &AnimatedPropertyID) -> Option<Self> { + Some( + match PropertyId::from_gecko_animated_property_id(property)? { + PropertyId::Custom(name) => Self::Custom(name), + PropertyId::NonCustom(id) => Self::Longhand(id.as_longhand()?), + }, + ) + } +} + +/// An identifier for a given property declaration, which can be either a +/// longhand or a custom property. +#[derive(Clone, Copy, Debug, PartialEq, MallocSizeOf)] +pub enum PropertyDeclarationId<'a> { + /// A longhand. + Longhand(LonghandId), + /// A custom property declaration. + Custom(&'a custom_properties::Name), +} + +impl<'a> ToCss for PropertyDeclarationId<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()), + PropertyDeclarationId::Custom(name) => { + dest.write_str("--")?; + serialize_atom_name(name, dest) + }, + } + } +} + +impl<'a> PropertyDeclarationId<'a> { + /// Returns PropertyFlags for given property. + #[inline(always)] + pub fn flags(&self) -> PropertyFlags { + match self { + Self::Longhand(id) => id.flags(), + Self::Custom(_) => PropertyFlags::empty(), + } + } + + /// Convert to an OwnedPropertyDeclarationId. + pub fn to_owned(&self) -> OwnedPropertyDeclarationId { + match self { + PropertyDeclarationId::Longhand(id) => OwnedPropertyDeclarationId::Longhand(*id), + PropertyDeclarationId::Custom(name) => { + OwnedPropertyDeclarationId::Custom((*name).clone()) + }, + } + } + + /// Whether a given declaration id is either the same as `other`, or a + /// longhand of it. + pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool { + match *self { + PropertyDeclarationId::Longhand(id) => match *other { + PropertyId::NonCustom(non_custom_id) => id.is_or_is_longhand_of(non_custom_id), + PropertyId::Custom(_) => false, + }, + PropertyDeclarationId::Custom(name) => { + matches!(*other, PropertyId::Custom(ref other_name) if name == other_name) + }, + } + } + + /// Whether a given declaration id is a longhand belonging to this + /// shorthand. + pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool { + match *self { + PropertyDeclarationId::Longhand(ref id) => id.is_longhand_of(shorthand), + _ => false, + } + } + + /// Returns the name of the property without CSS escaping. + pub fn name(&self) -> Cow<'static, str> { + match *self { + PropertyDeclarationId::Longhand(id) => id.name().into(), + PropertyDeclarationId::Custom(name) => { + let mut s = String::new(); + write!(&mut s, "--{}", name).unwrap(); + s.into() + }, + } + } + + /// Returns longhand id if it is, None otherwise. + #[inline] + pub fn as_longhand(&self) -> Option<LonghandId> { + match *self { + PropertyDeclarationId::Longhand(id) => Some(id), + _ => None, + } + } + + /// Return whether this property is logical. + #[inline] + pub fn is_logical(&self) -> bool { + match self { + PropertyDeclarationId::Longhand(id) => id.is_logical(), + PropertyDeclarationId::Custom(_) => false, + } + } + + /// If this is a logical property, return the corresponding physical one in + /// the given writing mode. + /// + /// Otherwise, return unchanged. + #[inline] + pub fn to_physical(&self, wm: WritingMode) -> Self { + match self { + Self::Longhand(id) => Self::Longhand(id.to_physical(wm)), + Self::Custom(_) => self.clone(), + } + } + + /// Returns whether this property is animatable. + #[inline] + pub fn is_animatable(&self) -> bool { + match self { + Self::Longhand(id) => id.is_animatable(), + Self::Custom(_) => true, + } + } + + /// Returns whether this property is animatable in a discrete way. + #[inline] + pub fn is_discrete_animatable(&self) -> bool { + match self { + Self::Longhand(longhand) => longhand.is_discrete_animatable(), + // TODO(bug 1846516): Refine this? + Self::Custom(_) => true, + } + } + + /// Converts from a to an adequate nsCSSPropertyID, returning + /// eCSSPropertyExtra_variable for custom properties. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { + match self { + PropertyDeclarationId::Longhand(id) => id.to_nscsspropertyid(), + PropertyDeclarationId::Custom(_) => nsCSSPropertyID::eCSSPropertyExtra_variable, + } + } + + /// Convert a `PropertyDeclarationId` into an `AnimatedPropertyID` + /// Note that the rust AnimatedPropertyID doesn't implement Drop, so owned controls whether the + /// custom name should be addrefed or not. + /// + /// FIXME(emilio, bug 1870107): This is a bit error-prone. We should consider using cbindgen to + /// generate the property id representation or so. + #[cfg(feature = "gecko")] + #[inline] + pub fn to_gecko_animated_property_id(&self, owned: bool) -> AnimatedPropertyID { + match self { + Self::Longhand(id) => AnimatedPropertyID { + mID: id.to_nscsspropertyid(), + mCustomName: RefPtr::null(), + }, + Self::Custom(name) => { + let mut property_id = AnimatedPropertyID { + mID: nsCSSPropertyID::eCSSPropertyExtra_variable, + mCustomName: RefPtr::null(), + }; + property_id.mCustomName.mRawPtr = if owned { + (*name).clone().into_addrefed() + } else { + name.as_ptr() + }; + property_id + }, + } + } +} + +/// A set of all properties. +#[derive(Clone, PartialEq, Default)] +pub struct NonCustomPropertyIdSet { + storage: [u32; ((property_counts::NON_CUSTOM as usize) - 1 + 32) / 32], +} + +impl NonCustomPropertyIdSet { + /// Creates an empty `NonCustomPropertyIdSet`. + pub fn new() -> Self { + Self { + storage: Default::default(), + } + } + + /// Insert a non-custom-property in the set. + #[inline] + pub fn insert(&mut self, id: NonCustomPropertyId) { + let bit = id.0 as usize; + self.storage[bit / 32] |= 1 << (bit % 32); + } + + /// Return whether the given property is in the set + #[inline] + pub fn contains(&self, id: NonCustomPropertyId) -> bool { + let bit = id.0 as usize; + (self.storage[bit / 32] & (1 << (bit % 32))) != 0 + } +} + +/// A set of longhand properties +#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] +pub struct LonghandIdSet { + storage: [u32; ((property_counts::LONGHANDS as usize) - 1 + 32) / 32], +} + +to_shmem::impl_trivial_to_shmem!(LonghandIdSet); + +impl LonghandIdSet { + /// Return an empty LonghandIdSet. + #[inline] + pub fn new() -> Self { + Self { + storage: Default::default(), + } + } + + /// Iterate over the current longhand id set. + pub fn iter(&self) -> LonghandIdSetIterator { + LonghandIdSetIterator { + longhands: self, + cur: 0, + } + } + + /// Returns whether this set contains at least every longhand that `other` + /// also contains. + pub fn contains_all(&self, other: &Self) -> bool { + for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { + if (*self_cell & *other_cell) != *other_cell { + return false; + } + } + true + } + + /// Returns whether this set contains any longhand that `other` also contains. + pub fn contains_any(&self, other: &Self) -> bool { + for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) { + if (*self_cell & *other_cell) != 0 { + return true; + } + } + false + } + + /// Remove all the given properties from the set. + #[inline] + pub fn remove_all(&mut self, other: &Self) { + for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) { + *self_cell &= !*other_cell; + } + } + + /// Return whether the given property is in the set + #[inline] + pub fn contains(&self, id: LonghandId) -> bool { + let bit = id as usize; + (self.storage[bit / 32] & (1 << (bit % 32))) != 0 + } + + /// Return whether this set contains any reset longhand. + #[inline] + pub fn contains_any_reset(&self) -> bool { + self.contains_any(Self::reset()) + } + + /// Add the given property to the set + #[inline] + pub fn insert(&mut self, id: LonghandId) { + let bit = id as usize; + self.storage[bit / 32] |= 1 << (bit % 32); + } + + /// Remove the given property from the set + #[inline] + pub fn remove(&mut self, id: LonghandId) { + let bit = id as usize; + self.storage[bit / 32] &= !(1 << (bit % 32)); + } + + /// Clear all bits + #[inline] + pub fn clear(&mut self) { + for cell in &mut self.storage { + *cell = 0 + } + } + + /// Returns whether the set is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.storage.iter().all(|c| *c == 0) + } +} + +/// An iterator over a set of longhand ids. +pub struct LonghandIdSetIterator<'a> { + longhands: &'a LonghandIdSet, + cur: usize, +} + +impl<'a> Iterator for LonghandIdSetIterator<'a> { + type Item = LonghandId; + + fn next(&mut self) -> Option<Self::Item> { + loop { + if self.cur >= property_counts::LONGHANDS { + return None; + } + + let id: LonghandId = unsafe { mem::transmute(self.cur as u16) }; + self.cur += 1; + + if self.longhands.contains(id) { + return Some(id); + } + } + } +} + +/// An ArrayVec of subproperties, contains space for the longest shorthand except all. +pub type SubpropertiesVec<T> = ArrayVec<T, { property_counts::MAX_SHORTHAND_EXPANDED }>; + +/// A stack-allocated vector of `PropertyDeclaration` +/// large enough to parse one CSS `key: value` declaration. +/// (Shorthands expand to multiple `PropertyDeclaration`s.) +#[derive(Default)] +pub struct SourcePropertyDeclaration { + /// The storage for the actual declarations (except for all). + pub declarations: SubpropertiesVec<PropertyDeclaration>, + /// Stored separately to keep SubpropertiesVec smaller. + pub all_shorthand: AllShorthand, +} + +// This is huge, but we allocate it on the stack and then never move it, +// we only pass `&mut SourcePropertyDeclaration` references around. +size_of_test!(SourcePropertyDeclaration, 632); + +impl SourcePropertyDeclaration { + /// Create one with a single PropertyDeclaration. + #[inline] + pub fn with_one(decl: PropertyDeclaration) -> Self { + let mut result = Self::default(); + result.declarations.push(decl); + result + } + + /// Similar to Vec::drain: leaves this empty when the return value is dropped. + pub fn drain(&mut self) -> SourcePropertyDeclarationDrain { + SourcePropertyDeclarationDrain { + declarations: self.declarations.drain(..), + all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet), + } + } + + /// Reset to initial state + pub fn clear(&mut self) { + self.declarations.clear(); + self.all_shorthand = AllShorthand::NotSet; + } + + /// Whether we're empty. + pub fn is_empty(&self) -> bool { + self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet) + } + + /// Push a single declaration. + pub fn push(&mut self, declaration: PropertyDeclaration) { + let _result = self.declarations.try_push(declaration); + debug_assert!(_result.is_ok()); + } +} + +/// Return type of SourcePropertyDeclaration::drain +pub struct SourcePropertyDeclarationDrain<'a> { + /// A drain over the non-all declarations. + pub declarations: + ArrayVecDrain<'a, PropertyDeclaration, { property_counts::MAX_SHORTHAND_EXPANDED }>, + /// The all shorthand that was set. + pub all_shorthand: AllShorthand, +} + +/// An unparsed property value that contains `var()` functions. +#[derive(Debug, Eq, PartialEq, ToShmem)] +pub struct UnparsedValue { + /// The variable value, references and so on. + pub(super) variable_value: custom_properties::VariableValue, + /// The shorthand this came from. + from_shorthand: Option<ShorthandId>, +} + +impl ToCss for UnparsedValue { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + // https://drafts.csswg.org/css-variables/#variables-in-shorthands + if self.from_shorthand.is_none() { + self.variable_value.to_css(dest)?; + } + Ok(()) + } +} + +/// A simple cache for properties that come from a shorthand and have variable +/// references. +/// +/// This cache works because of the fact that you can't have competing values +/// for a given longhand coming from the same shorthand (but note that this is +/// why the shorthand needs to be part of the cache key). +pub type ShorthandsWithPropertyReferencesCache = + FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>; + +impl UnparsedValue { + fn substitute_variables<'cache>( + &self, + longhand_id: LonghandId, + custom_properties: &ComputedCustomProperties, + stylist: &Stylist, + computed_context: &computed::Context, + shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, + ) -> Cow<'cache, PropertyDeclaration> { + let invalid_at_computed_value_time = || { + let keyword = if longhand_id.inherited() { + CSSWideKeyword::Inherit + } else { + CSSWideKeyword::Initial + }; + Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) + }; + + if computed_context + .builder + .invalid_non_custom_properties + .contains(longhand_id) + { + return invalid_at_computed_value_time(); + } + + if let Some(shorthand_id) = self.from_shorthand { + let key = (shorthand_id, longhand_id); + if shorthand_cache.contains_key(&key) { + // FIXME: This double lookup should be avoidable, but rustc + // doesn't like that, see: + // + // https://github.com/rust-lang/rust/issues/82146 + return Cow::Borrowed(&shorthand_cache[&key]); + } + } + + let css = match custom_properties::substitute( + &self.variable_value, + custom_properties, + stylist, + computed_context, + ) { + Ok(css) => css, + Err(..) => return invalid_at_computed_value_time(), + }; + + // As of this writing, only the base URL is used for property + // values. + // + // NOTE(emilio): we intentionally pase `None` as the rule type here. + // If something starts depending on it, it's probably a bug, since + // it'd change how values are parsed depending on whether we're in a + // @keyframes rule or not, for example... So think twice about + // whether you want to do this! + // + // FIXME(emilio): ParsingMode is slightly fishy... + let context = ParserContext::new( + Origin::Author, + &self.variable_value.url_data, + None, + ParsingMode::DEFAULT, + computed_context.quirks_mode, + /* namespaces = */ Default::default(), + None, + None, + ); + + let mut input = ParserInput::new(&css); + let mut input = Parser::new(&mut input); + input.skip_whitespace(); + + if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { + return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)); + } + + let shorthand = match self.from_shorthand { + None => { + return match input.parse_entirely(|input| longhand_id.parse_value(&context, input)) + { + Ok(decl) => Cow::Owned(decl), + Err(..) => invalid_at_computed_value_time(), + } + }, + Some(shorthand) => shorthand, + }; + + let mut decls = SourcePropertyDeclaration::default(); + // parse_into takes care of doing `parse_entirely` for us. + if shorthand + .parse_into(&mut decls, &context, &mut input) + .is_err() + { + return invalid_at_computed_value_time(); + } + + for declaration in decls.declarations.drain(..) { + let longhand = declaration.id().as_longhand().unwrap(); + if longhand.is_logical() { + let writing_mode = computed_context.builder.writing_mode; + shorthand_cache.insert( + (shorthand, longhand.to_physical(writing_mode)), + declaration.clone(), + ); + } + shorthand_cache.insert((shorthand, longhand), declaration); + } + + let key = (shorthand, longhand_id); + match shorthand_cache.get(&key) { + Some(decl) => Cow::Borrowed(decl), + None => { + // FIXME: We should always have the key here but it seems + // sometimes we don't, see bug 1696409. + #[cfg(feature = "gecko")] + { + if crate::gecko_bindings::structs::GECKO_IS_NIGHTLY { + panic!("Expected {:?} to be in the cache but it was not!", key); + } + } + invalid_at_computed_value_time() + }, + } + } +} +/// A parsed all-shorthand value. +pub enum AllShorthand { + /// Not present. + NotSet, + /// A CSS-wide keyword. + CSSWideKeyword(CSSWideKeyword), + /// An all shorthand with var() references that we can't resolve right now. + WithVariables(Arc<UnparsedValue>), +} + +impl Default for AllShorthand { + fn default() -> Self { + Self::NotSet + } +} + +impl AllShorthand { + /// Iterates property declarations from the given all shorthand value. + #[inline] + pub fn declarations(&self) -> AllShorthandDeclarationIterator { + AllShorthandDeclarationIterator { + all_shorthand: self, + longhands: ShorthandId::All.longhands(), + } + } +} + +/// An iterator over the all shorthand's shorthand declarations. +pub struct AllShorthandDeclarationIterator<'a> { + all_shorthand: &'a AllShorthand, + longhands: NonCustomPropertyIterator<LonghandId>, +} + +impl<'a> Iterator for AllShorthandDeclarationIterator<'a> { + type Item = PropertyDeclaration; + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + match *self.all_shorthand { + AllShorthand::NotSet => None, + AllShorthand::CSSWideKeyword(ref keyword) => Some( + PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword), + ), + AllShorthand::WithVariables(ref unparsed) => { + Some(PropertyDeclaration::WithVariables(VariableDeclaration { + id: self.longhands.next()?, + value: unparsed.clone(), + })) + }, + } + } +} + +/// An iterator over all the property ids that are enabled for a given +/// shorthand, if that shorthand is enabled for all content too. +pub struct NonCustomPropertyIterator<Item: 'static> { + filter: bool, + iter: std::slice::Iter<'static, Item>, +} + +impl<Item> Iterator for NonCustomPropertyIterator<Item> +where + Item: 'static + Copy + Into<NonCustomPropertyId>, +{ + type Item = Item; + + fn next(&mut self) -> Option<Self::Item> { + loop { + let id = *self.iter.next()?; + if !self.filter || id.into().enabled_for_all_content() { + return Some(id); + } + } + } +} diff --git a/servo/components/style/properties/properties.html.mako b/servo/components/style/properties/properties.html.mako new file mode 100644 index 0000000000..5c51593517 --- /dev/null +++ b/servo/components/style/properties/properties.html.mako @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Supported CSS properties in Servo</title> + <link rel="stylesheet" type="text/css" href="../normalize.css"> + <link rel="stylesheet" type="text/css" href="../rustdoc.css"> + <link rel="stylesheet" type="text/css" href="../light.css"> +</head> +<body class="rustdoc"> + <section id='main' class="content mod"> + <h1 class='fqn'><span class='in-band'>CSS properties currently supported in Servo</span></h1> + % for kind, props in sorted(properties.items()): + <h2>${kind.capitalize()}</h2> + <table> + <tr> + <th>Name</th> + <th>Pref</th> + </tr> + % for name, data in sorted(props.items()): + <tr> + <td><code>${name}</code></td> + <td><code>${data['pref'] or ''}</code></td> + </tr> + % endfor + </table> + % endfor + </section> +</body> +</html> diff --git a/servo/components/style/properties/properties.mako.rs b/servo/components/style/properties/properties.mako.rs new file mode 100644 index 0000000000..b08314d7d5 --- /dev/null +++ b/servo/components/style/properties/properties.mako.rs @@ -0,0 +1,2958 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// This file is a Mako template: http://www.makotemplates.org/ + +// Please note that valid Rust syntax may be mangled by the Mako parser. +// For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code +// can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>. + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +use app_units::Au; +use servo_arc::{Arc, UniqueArc}; +use std::{ops, ptr}; +use std::{fmt, mem}; + +#[cfg(feature = "servo")] use euclid::SideOffsets2D; +#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{self, nsCSSPropertyID}; +#[cfg(feature = "servo")] use crate::logical_geometry::LogicalMargin; +#[cfg(feature = "servo")] use crate::computed_values; +use crate::logical_geometry::WritingMode; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use crate::computed_value_flags::*; +use cssparser::Parser; +use crate::media_queries::Device; +use crate::parser::ParserContext; +use crate::selector_parser::PseudoElement; +use crate::stylist::Stylist; +#[cfg(feature = "servo")] use servo_config::prefs; +use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; +use crate::stylesheets::{CssRuleType, CssRuleTypes, Origin}; +use crate::logical_geometry::{LogicalAxis, LogicalCorner, LogicalSide}; +use crate::use_counters::UseCounters; +use crate::rule_tree::StrongRuleNode; +use crate::str::CssStringWriter; +use crate::values::{ + computed, + resolved, + specified::{font::SystemFont, length::LineHeightBase}, +}; +use std::cell::Cell; +use super::{ + PropertyDeclarationId, PropertyId, NonCustomPropertyId, + NonCustomPropertyIdSet, PropertyFlags, SourcePropertyDeclaration, + LonghandIdSet, VariableDeclaration, CustomDeclaration, + WideKeywordDeclaration, NonCustomPropertyIterator, +}; + +<%! + from collections import defaultdict + from data import Method, PropertyRestrictions, Keyword, to_rust_ident, \ + to_camel_case, RULE_VALUES, SYSTEM_FONT_LONGHANDS, PRIORITARY_PROPERTIES + import os.path +%> + +/// Conversion with fewer impls than From/Into +pub trait MaybeBoxed<Out> { + /// Convert + fn maybe_boxed(self) -> Out; +} + +impl<T> MaybeBoxed<T> for T { + #[inline] + fn maybe_boxed(self) -> T { self } +} + +impl<T> MaybeBoxed<Box<T>> for T { + #[inline] + fn maybe_boxed(self) -> Box<T> { Box::new(self) } +} + +macro_rules! expanded { + ( $( $name: ident: $value: expr ),+ ) => { + expanded!( $( $name: $value, )+ ) + }; + ( $( $name: ident: $value: expr, )+ ) => { + Longhands { + $( + $name: MaybeBoxed::maybe_boxed($value), + )+ + } + } +} + +/// A module with all the code for longhand properties. +#[allow(missing_docs)] +pub mod longhands { + % for style_struct in data.style_structs: + include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); + % endfor +} + +macro_rules! unwrap_or_initial { + ($prop: ident) => (unwrap_or_initial!($prop, $prop)); + ($prop: ident, $expr: expr) => + ($expr.unwrap_or_else(|| $prop::get_initial_specified_value())); +} + +/// A module with code for all the shorthand css properties, and a few +/// serialization helpers. +#[allow(missing_docs)] +pub mod shorthands { + use cssparser::Parser; + use crate::parser::{Parse, ParserContext}; + use style_traits::{ParseError, StyleParseErrorKind}; + use crate::values::specified; + + % for style_struct in data.style_structs: + include!("${repr(os.path.join(OUT_DIR, 'shorthands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); + % endfor + + // We didn't define the 'all' shorthand using the regular helpers:shorthand + // mechanism, since it causes some very large types to be generated. + // + // Also, make sure logical properties appear before its physical + // counter-parts, in order to prevent bugs like: + // + // https://bugzilla.mozilla.org/show_bug.cgi?id=1410028 + // + // FIXME(emilio): Adopt the resolution from: + // + // https://github.com/w3c/csswg-drafts/issues/1898 + // + // when there is one, whatever that is. + <% + logical_longhands = [] + other_longhands = [] + + for p in data.longhands: + if p.name in ['direction', 'unicode-bidi']: + continue; + if not p.enabled_in_content() and not p.experimental(engine): + continue; + if "Style" not in p.rule_types_allowed_names(): + continue; + if p.logical: + logical_longhands.append(p.name) + else: + other_longhands.append(p.name) + + data.declare_shorthand( + "all", + logical_longhands + other_longhands, + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand" + ) + ALL_SHORTHAND_LEN = len(logical_longhands) + len(other_longhands); + %> +} + +<% + from itertools import groupby + + # After this code, `data.longhands` is sorted in the following order: + # - first all keyword variants and all variants known to be Copy, + # - second all the other variants, such as all variants with the same field + # have consecutive discriminants. + # The variable `variants` contain the same entries as `data.longhands` in + # the same order, but must exist separately to the data source, because + # we then need to add three additional variants `WideKeywordDeclaration`, + # `VariableDeclaration` and `CustomDeclaration`. + + variants = [] + for property in data.longhands: + variants.append({ + "name": property.camel_case, + "type": property.specified_type(), + "doc": "`" + property.name + "`", + "copy": property.specified_is_copy(), + }) + + groups = {} + keyfunc = lambda x: x["type"] + sortkeys = {} + for ty, group in groupby(sorted(variants, key=keyfunc), keyfunc): + group = list(group) + groups[ty] = group + for v in group: + if len(group) == 1: + sortkeys[v["name"]] = (not v["copy"], 1, v["name"], "") + else: + sortkeys[v["name"]] = (not v["copy"], len(group), ty, v["name"]) + variants.sort(key=lambda x: sortkeys[x["name"]]) + + # It is extremely important to sort the `data.longhands` array here so + # that it is in the same order as `variants`, for `LonghandId` and + # `PropertyDeclarationId` to coincide. + data.longhands.sort(key=lambda x: sortkeys[x.camel_case]) +%> + +// WARNING: It is *really* important for the variants of `LonghandId` +// and `PropertyDeclaration` to be defined in the exact same order, +// with the exception of `CSSWideKeyword`, `WithVariables` and `Custom`, +// which don't exist in `LonghandId`. + +<% + extra_variants = [ + { + "name": "CSSWideKeyword", + "type": "WideKeywordDeclaration", + "doc": "A CSS-wide keyword.", + "copy": False, + }, + { + "name": "WithVariables", + "type": "VariableDeclaration", + "doc": "An unparsed declaration.", + "copy": False, + }, + { + "name": "Custom", + "type": "CustomDeclaration", + "doc": "A custom property declaration.", + "copy": False, + }, + ] + for v in extra_variants: + variants.append(v) + groups[v["type"]] = [v] +%> + +/// Servo's representation for a property declaration. +#[derive(ToShmem)] +#[repr(u16)] +pub enum PropertyDeclaration { + % for variant in variants: + /// ${variant["doc"]} + ${variant["name"]}(${variant["type"]}), + % endfor +} + +// There's one of these for each parsed declaration so it better be small. +size_of_test!(PropertyDeclaration, 32); + +#[repr(C)] +struct PropertyDeclarationVariantRepr<T> { + tag: u16, + value: T +} + +impl Clone for PropertyDeclaration { + #[inline] + fn clone(&self) -> Self { + use self::PropertyDeclaration::*; + + <% + [copy, others] = [list(g) for _, g in groupby(variants, key=lambda x: not x["copy"])] + %> + + let self_tag = unsafe { + (*(self as *const _ as *const PropertyDeclarationVariantRepr<()>)).tag + }; + if self_tag <= LonghandId::${copy[-1]["name"]} as u16 { + #[derive(Clone, Copy)] + #[repr(u16)] + enum CopyVariants { + % for v in copy: + _${v["name"]}(${v["type"]}), + % endfor + } + + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut CopyVariants, + *(self as *const _ as *const CopyVariants), + ); + return out.assume_init(); + } + } + + // This function ensures that all properties not handled above + // do not have a specified value implements Copy. If you hit + // compile error here, you may want to add the type name into + // Longhand.specified_is_copy in data.py. + fn _static_assert_others_are_not_copy() { + struct Helper<T>(T); + trait AssertCopy { fn assert() {} } + trait AssertNotCopy { fn assert() {} } + impl<T: Copy> AssertCopy for Helper<T> {} + % for ty in sorted(set(x["type"] for x in others)): + impl AssertNotCopy for Helper<${ty}> {} + Helper::<${ty}>::assert(); + % endfor + } + + match *self { + ${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => { + unsafe { debug_unreachable!() } + } + % for ty, vs in groupby(others, key=lambda x: x["type"]): + <% + vs = list(vs) + %> + % if len(vs) == 1: + ${vs[0]["name"]}(ref value) => { + ${vs[0]["name"]}(value.clone()) + } + % else: + ${" |\n".join("{}(ref value)".format(v["name"]) for v in vs)} => { + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${ty}>, + PropertyDeclarationVariantRepr { + tag: *(self as *const _ as *const u16), + value: value.clone(), + }, + ); + out.assume_init() + } + } + % endif + % endfor + } + } +} + +impl PartialEq for PropertyDeclaration { + #[inline] + fn eq(&self, other: &Self) -> bool { + use self::PropertyDeclaration::*; + + unsafe { + let this_repr = + &*(self as *const _ as *const PropertyDeclarationVariantRepr<()>); + let other_repr = + &*(other as *const _ as *const PropertyDeclarationVariantRepr<()>); + if this_repr.tag != other_repr.tag { + return false; + } + match *self { + % for ty, vs in groupby(variants, key=lambda x: x["type"]): + ${" |\n".join("{}(ref this)".format(v["name"]) for v in vs)} => { + let other_repr = + &*(other as *const _ as *const PropertyDeclarationVariantRepr<${ty}>); + *this == other_repr.value + } + % endfor + } + } + } +} + +impl MallocSizeOf for PropertyDeclaration { + #[inline] + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + use self::PropertyDeclaration::*; + + match *self { + % for ty, vs in groupby(variants, key=lambda x: x["type"]): + ${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => { + value.size_of(ops) + } + % endfor + } + } +} + + +impl PropertyDeclaration { + /// Returns the given value for this declaration as a particular type. + /// It's the caller's responsibility to guarantee that the longhand id has the right specified + /// value representation. + pub(crate) unsafe fn unchecked_value_as<T>(&self) -> &T { + &(*(self as *const _ as *const PropertyDeclarationVariantRepr<T>)).value + } + + /// Dumps the property declaration before crashing. + #[cold] + #[cfg(debug_assertions)] + pub(crate) fn debug_crash(&self, reason: &str) { + panic!("{}: {:?}", reason, self); + } + #[cfg(not(debug_assertions))] + #[inline(always)] + pub(crate) fn debug_crash(&self, _reason: &str) {} + + /// Returns whether this is a variant of the Longhand(Value) type, rather + /// than one of the special variants in extra_variants. + fn is_longhand_value(&self) -> bool { + match *self { + % for v in extra_variants: + PropertyDeclaration::${v["name"]}(..) => false, + % endfor + _ => true, + } + } + + /// Like the method on ToCss, but without the type parameter to avoid + /// accidentally monomorphizing this large function multiple times for + /// different writers. + pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { + use self::PropertyDeclaration::*; + + let mut dest = CssWriter::new(dest); + match *self { + % for ty, vs in groupby(variants, key=lambda x: x["type"]): + ${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => { + value.to_css(&mut dest) + } + % endfor + } + } + + /// Returns the color value of a given property, for high-contrast-mode tweaks. + pub(super) fn color_value(&self) -> Option<<&crate::values::specified::Color> { + ${static_longhand_id_set("COLOR_PROPERTIES", lambda p: p.predefined_type == "Color")} + <% + # sanity check + assert data.longhands_by_name["background-color"].predefined_type == "Color" + + color_specified_type = data.longhands_by_name["background-color"].specified_type() + %> + let id = self.id().as_longhand()?; + if !COLOR_PROPERTIES.contains(id) || !self.is_longhand_value() { + return None; + } + let repr = self as *const _ as *const PropertyDeclarationVariantRepr<${color_specified_type}>; + Some(unsafe { &(*repr).value }) + } +} + +/// A module with all the code related to animated properties. +/// +/// This needs to be "included" by mako at least after all longhand modules, +/// given they populate the global data. +pub mod animated_properties { + <%include file="/helpers/animated_properties.mako.rs" /> +} + +/// A module to group various interesting property counts. +pub mod property_counts { + /// The number of (non-alias) longhand properties. + pub const LONGHANDS: usize = ${len(data.longhands)}; + /// The number of (non-alias) shorthand properties. + pub const SHORTHANDS: usize = ${len(data.shorthands)}; + /// The number of aliases. + pub const ALIASES: usize = ${len(data.all_aliases())}; + /// The number of counted unknown properties. + pub const COUNTED_UNKNOWN: usize = ${len(data.counted_unknown_properties)}; + /// The number of (non-alias) longhands and shorthands. + pub const LONGHANDS_AND_SHORTHANDS: usize = LONGHANDS + SHORTHANDS; + /// The number of non-custom properties. + pub const NON_CUSTOM: usize = LONGHANDS_AND_SHORTHANDS + ALIASES; + /// The number of prioritary properties that we have. + pub const PRIORITARY: usize = ${len(PRIORITARY_PROPERTIES)}; + /// The max number of longhands that a shorthand other than "all" expands to. + pub const MAX_SHORTHAND_EXPANDED: usize = + ${max(len(s.sub_properties) for s in data.shorthands_except_all())}; + /// The max amount of longhands that the `all` shorthand will ever contain. + pub const ALL_SHORTHAND_EXPANDED: usize = ${ALL_SHORTHAND_LEN}; + /// The number of animatable properties. + pub const ANIMATABLE: usize = ${sum(1 for prop in data.longhands if prop.animatable)}; +} + +% if engine == "gecko": +#[allow(dead_code)] +unsafe fn static_assert_nscsspropertyid() { + % for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()): + std::mem::transmute::<[u8; ${i}], [u8; ${property.nscsspropertyid()} as usize]>([0; ${i}]); // ${property.name} + % endfor +} +% endif + +impl NonCustomPropertyId { + /// Get the property name. + #[inline] + pub fn name(self) -> &'static str { + static MAP: [&'static str; property_counts::NON_CUSTOM] = [ + % for property in data.longhands + data.shorthands + data.all_aliases(): + "${property.name}", + % endfor + ]; + MAP[self.0 as usize] + } + + /// Returns whether this property is animatable. + #[inline] + pub fn is_animatable(self) -> bool { + ${static_non_custom_property_id_set("ANIMATABLE", lambda p: p.animatable)} + ANIMATABLE.contains(self) + } + + /// Whether this property is enabled for all content right now. + #[inline] + pub(super) fn enabled_for_all_content(self) -> bool { + ${static_non_custom_property_id_set( + "EXPERIMENTAL", + lambda p: p.experimental(engine) + )} + + ${static_non_custom_property_id_set( + "ALWAYS_ENABLED", + lambda p: (not p.experimental(engine)) and p.enabled_in_content() + )} + + let passes_pref_check = || { + % if engine == "gecko": + unsafe { structs::nsCSSProps_gPropertyEnabled[self.0 as usize] } + % else: + static PREF_NAME: [Option< &str>; ${ + len(data.longhands) + len(data.shorthands) + len(data.all_aliases()) + }] = [ + % for property in data.longhands + data.shorthands + data.all_aliases(): + <% + attrs = {"servo-2013": "servo_2013_pref", "servo-2020": "servo_2020_pref"} + pref = getattr(property, attrs[engine]) + %> + % if pref: + Some("${pref}"), + % else: + None, + % endif + % endfor + ]; + let pref = match PREF_NAME[self.0 as usize] { + None => return true, + Some(pref) => pref, + }; + + prefs::pref_map().get(pref).as_bool().unwrap_or(false) + % endif + }; + + if ALWAYS_ENABLED.contains(self) { + return true + } + + if EXPERIMENTAL.contains(self) && passes_pref_check() { + return true + } + + false + } + + /// Returns whether a given rule allows a given property. + #[inline] + pub fn allowed_in_rule(self, rule_types: CssRuleTypes) -> bool { + debug_assert!( + rule_types.contains(CssRuleType::Keyframe) || + rule_types.contains(CssRuleType::Page) || + rule_types.contains(CssRuleType::Style), + "Declarations are only expected inside a keyframe, page, or style rule." + ); + + static MAP: [u32; property_counts::NON_CUSTOM] = [ + % for property in data.longhands + data.shorthands + data.all_aliases(): + % for name in RULE_VALUES: + % if property.rule_types_allowed & RULE_VALUES[name] != 0: + CssRuleType::${name}.bit() | + % endif + % endfor + 0, + % endfor + ]; + MAP[self.0 as usize] & rule_types.bits() != 0 + } + + pub(super) fn allowed_in(self, context: &ParserContext) -> bool { + if !self.allowed_in_rule(context.rule_types()) { + return false; + } + + self.allowed_in_ignoring_rule_type(context) + } + + + pub(super) fn allowed_in_ignoring_rule_type(self, context: &ParserContext) -> bool { + // The semantics of these are kinda hard to reason about, what follows + // is a description of the different combinations that can happen with + // these three sets. + // + // Experimental properties are generally controlled by prefs, but an + // experimental property explicitly enabled in certain context (UA or + // chrome sheets) is always usable in the context regardless of the + // pref value. + // + // Non-experimental properties are either normal properties which are + // usable everywhere, or internal-only properties which are only usable + // in certain context they are explicitly enabled in. + if self.enabled_for_all_content() { + return true; + } + + ${static_non_custom_property_id_set( + "ENABLED_IN_UA_SHEETS", + lambda p: p.explicitly_enabled_in_ua_sheets() + )} + ${static_non_custom_property_id_set( + "ENABLED_IN_CHROME", + lambda p: p.explicitly_enabled_in_chrome() + )} + + if context.stylesheet_origin == Origin::UserAgent && + ENABLED_IN_UA_SHEETS.contains(self) + { + return true + } + + if context.chrome_rules_enabled() && ENABLED_IN_CHROME.contains(self) { + return true + } + + false + } + + /// The supported types of this property. The return value should be + /// style_traits::CssType when it can become a bitflags type. + pub(super) fn supported_types(&self) -> u8 { + const SUPPORTED_TYPES: [u8; ${len(data.longhands) + len(data.shorthands)}] = [ + % for prop in data.longhands: + <${prop.specified_type()} as SpecifiedValueInfo>::SUPPORTED_TYPES, + % endfor + % for prop in data.shorthands: + % if prop.name == "all": + 0, // 'all' accepts no value other than CSS-wide keywords + % else: + <shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>::SUPPORTED_TYPES, + % endif + % endfor + ]; + SUPPORTED_TYPES[self.0 as usize] + } + + /// See PropertyId::collect_property_completion_keywords. + pub(super) fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) { + fn do_nothing(_: KeywordsCollectFn) {} + const COLLECT_FUNCTIONS: [fn(KeywordsCollectFn); + ${len(data.longhands) + len(data.shorthands)}] = [ + % for prop in data.longhands: + <${prop.specified_type()} as SpecifiedValueInfo>::collect_completion_keywords, + % endfor + % for prop in data.shorthands: + % if prop.name == "all": + do_nothing, // 'all' accepts no value other than CSS-wide keywords + % else: + <shorthands::${prop.ident}::Longhands as SpecifiedValueInfo>:: + collect_completion_keywords, + % endif + % endfor + ]; + COLLECT_FUNCTIONS[self.0 as usize](f); + } +} + +<%def name="static_non_custom_property_id_set(name, is_member)"> +static ${name}: NonCustomPropertyIdSet = NonCustomPropertyIdSet { + <% + storage = [0] * int((len(data.longhands) + len(data.shorthands) + len(data.all_aliases()) - 1 + 32) / 32) + for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()): + if is_member(property): + storage[int(i / 32)] |= 1 << (i % 32) + %> + storage: [${", ".join("0x%x" % word for word in storage)}] +}; +</%def> + +<%def name="static_longhand_id_set(name, is_member)"> +static ${name}: LonghandIdSet = LonghandIdSet { + <% + storage = [0] * int((len(data.longhands) - 1 + 32) / 32) + for i, property in enumerate(data.longhands): + if is_member(property): + storage[int(i / 32)] |= 1 << (i % 32) + %> + storage: [${", ".join("0x%x" % word for word in storage)}] +}; +</%def> + +<% + logical_groups = defaultdict(list) + for prop in data.longhands: + if prop.logical_group: + logical_groups[prop.logical_group].append(prop) + + for group, props in logical_groups.items(): + logical_count = sum(1 for p in props if p.logical) + if logical_count * 2 != len(props): + raise RuntimeError("Logical group {} has ".format(group) + + "unbalanced logical / physical properties") + + FIRST_LINE_RESTRICTIONS = PropertyRestrictions.first_line(data) + FIRST_LETTER_RESTRICTIONS = PropertyRestrictions.first_letter(data) + MARKER_RESTRICTIONS = PropertyRestrictions.marker(data) + PLACEHOLDER_RESTRICTIONS = PropertyRestrictions.placeholder(data) + CUE_RESTRICTIONS = PropertyRestrictions.cue(data) + + def restriction_flags(property): + name = property.name + flags = [] + if name in FIRST_LINE_RESTRICTIONS: + flags.append("APPLIES_TO_FIRST_LINE") + if name in FIRST_LETTER_RESTRICTIONS: + flags.append("APPLIES_TO_FIRST_LETTER") + if name in PLACEHOLDER_RESTRICTIONS: + flags.append("APPLIES_TO_PLACEHOLDER") + if name in MARKER_RESTRICTIONS: + flags.append("APPLIES_TO_MARKER") + if name in CUE_RESTRICTIONS: + flags.append("APPLIES_TO_CUE") + return flags + +%> + +/// A group for properties which may override each other via logical resolution. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[repr(u8)] +pub enum LogicalGroupId { + % for i, group in enumerate(logical_groups.keys()): + /// ${group} + ${to_camel_case(group)} = ${i}, + % endfor +} + +impl LogicalGroupId { + /// Return the list of physical mapped properties for a given logical group. + fn physical_properties(self) -> &'static [LonghandId] { + static PROPS: [[LonghandId; 4]; ${len(logical_groups)}] = [ + % for group, props in logical_groups.items(): + [ + <% physical_props = [p for p in props if p.logical][0].all_physical_mapped_properties(data) %> + % for phys in physical_props: + LonghandId::${phys.camel_case}, + % endfor + % for i in range(len(physical_props), 4): + LonghandId::${physical_props[0].camel_case}, + % endfor + ], + % endfor + ]; + &PROPS[self as usize] + } +} + +/// A set of logical groups. +#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] +pub struct LogicalGroupSet { + storage: [u32; (${len(logical_groups)} - 1 + 32) / 32] +} + +impl LogicalGroupSet { + /// Creates an empty `NonCustomPropertyIdSet`. + pub fn new() -> Self { + Self { + storage: Default::default(), + } + } + + /// Return whether the given group is in the set + #[inline] + pub fn contains(&self, g: LogicalGroupId) -> bool { + let bit = g as usize; + (self.storage[bit / 32] & (1 << (bit % 32))) != 0 + } + + /// Insert a group the set. + #[inline] + pub fn insert(&mut self, g: LogicalGroupId) { + let bit = g as usize; + self.storage[bit / 32] |= 1 << (bit % 32); + } +} + + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub(crate) enum PrioritaryPropertyId { + % for p in data.longhands: + % if p.is_prioritary(): + ${p.camel_case}, + % endif + % endfor +} + +impl PrioritaryPropertyId { + #[inline] + pub fn to_longhand(self) -> LonghandId { + static PRIORITARY_TO_LONGHAND: [LonghandId; property_counts::PRIORITARY] = [ + % for p in data.longhands: + % if p.is_prioritary(): + LonghandId::${p.camel_case}, + % endif + % endfor + ]; + PRIORITARY_TO_LONGHAND[self as usize] + } + #[inline] + pub fn from_longhand(l: LonghandId) -> Option<Self> { + static LONGHAND_TO_PRIORITARY: [Option<PrioritaryPropertyId>; ${len(data.longhands)}] = [ + % for p in data.longhands: + % if p.is_prioritary(): + Some(PrioritaryPropertyId::${p.camel_case}), + % else: + None, + % endif + % endfor + ]; + LONGHAND_TO_PRIORITARY[l as usize] + } +} + +impl LonghandIdSet { + /// The set of non-inherited longhands. + #[inline] + pub(super) fn reset() -> &'static Self { + ${static_longhand_id_set("RESET", lambda p: not p.style_struct.inherited)} + &RESET + } + + #[inline] + pub(super) fn discrete_animatable() -> &'static Self { + ${static_longhand_id_set("DISCRETE_ANIMATABLE", lambda p: p.animation_value_type == "discrete")} + &DISCRETE_ANIMATABLE + } + + #[inline] + pub(super) fn logical() -> &'static Self { + ${static_longhand_id_set("LOGICAL", lambda p: p.logical)} + &LOGICAL + } + + /// Returns the set of longhands that are ignored when document colors are + /// disabled. + #[inline] + pub(super) fn ignored_when_colors_disabled() -> &'static Self { + ${static_longhand_id_set( + "IGNORED_WHEN_COLORS_DISABLED", + lambda p: p.ignored_when_colors_disabled + )} + &IGNORED_WHEN_COLORS_DISABLED + } + + /// Only a few properties are allowed to depend on the visited state of + /// links. When cascading visited styles, we can save time by only + /// processing these properties. + pub(super) fn visited_dependent() -> &'static Self { + ${static_longhand_id_set("VISITED_DEPENDENT", lambda p: p.is_visited_dependent())} + debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT)); + &VISITED_DEPENDENT + } + + #[inline] + pub(super) fn prioritary_properties() -> &'static Self { + ${static_longhand_id_set("PRIORITARY_PROPERTIES", lambda p: p.is_prioritary())} + &PRIORITARY_PROPERTIES + } + + #[inline] + pub(super) fn late_group_only_inherited() -> &'static Self { + ${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and not p.is_prioritary())} + &LATE_GROUP_ONLY_INHERITED + } + + #[inline] + pub(super) fn late_group() -> &'static Self { + ${static_longhand_id_set("LATE_GROUP", lambda p: not p.is_prioritary())} + &LATE_GROUP + } + + /// Returns the set of properties that are declared as having no effect on + /// Gecko <scrollbar> elements or their descendant scrollbar parts. + #[cfg(debug_assertions)] + #[cfg(feature = "gecko")] + #[inline] + pub fn has_no_effect_on_gecko_scrollbars() -> &'static Self { + // data.py asserts that has_no_effect_on_gecko_scrollbars is True or + // False for properties that are inherited and Gecko pref controlled, + // and is None for all other properties. + ${static_longhand_id_set( + "HAS_NO_EFFECT_ON_SCROLLBARS", + lambda p: p.has_effect_on_gecko_scrollbars is False + )} + &HAS_NO_EFFECT_ON_SCROLLBARS + } + + /// Returns the set of border properties for the purpose of disabling native + /// appearance. + #[inline] + pub fn border_background_properties() -> &'static Self { + ${static_longhand_id_set( + "BORDER_BACKGROUND_PROPERTIES", + lambda p: (p.logical_group and p.logical_group.startswith("border")) or \ + p.name in ["background-color", "background-image"] + )} + &BORDER_BACKGROUND_PROPERTIES + } +} + +/// An identifier for a given longhand property. +#[derive(Clone, Copy, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u16)] +pub enum LonghandId { + % for i, property in enumerate(data.longhands): + /// ${property.name} + ${property.camel_case} = ${i}, + % endfor +} + +enum LogicalMappingKind { + Side(LogicalSide), + Corner(LogicalCorner), + Axis(LogicalAxis), +} + +struct LogicalMappingData { + group: LogicalGroupId, + kind: LogicalMappingKind, +} + +impl LogicalMappingData { + fn to_physical(&self, wm: WritingMode) -> LonghandId { + let index = match self.kind { + LogicalMappingKind::Side(s) => s.to_physical(wm) as usize, + LogicalMappingKind::Corner(c) => c.to_physical(wm) as usize, + LogicalMappingKind::Axis(a) => a.to_physical(wm) as usize, + }; + self.group.physical_properties()[index] + } +} + +impl LonghandId { + /// Returns an iterator over all the shorthands that include this longhand. + pub fn shorthands(self) -> NonCustomPropertyIterator<ShorthandId> { + // first generate longhand to shorthands lookup map + // + // NOTE(emilio): This currently doesn't exclude the "all" shorthand. It + // could potentially do so, which would speed up serialization + // algorithms and what not, I guess. + <% + from functools import cmp_to_key + longhand_to_shorthand_map = {} + num_sub_properties = {} + for shorthand in data.shorthands: + num_sub_properties[shorthand.camel_case] = len(shorthand.sub_properties) + for sub_property in shorthand.sub_properties: + if sub_property.ident not in longhand_to_shorthand_map: + longhand_to_shorthand_map[sub_property.ident] = [] + + longhand_to_shorthand_map[sub_property.ident].append(shorthand.camel_case) + + def cmp(a, b): + return (a > b) - (a < b) + + def preferred_order(x, y): + # Since we want properties in order from most subproperties to least, + # reverse the arguments to cmp from the expected order. + result = cmp(num_sub_properties.get(y, 0), num_sub_properties.get(x, 0)) + if result: + return result + # Fall back to lexicographic comparison. + return cmp(x, y) + + # Sort the lists of shorthand properties according to preferred order: + # https://drafts.csswg.org/cssom/#concept-shorthands-preferred-order + for shorthand_list in longhand_to_shorthand_map.values(): + shorthand_list.sort(key=cmp_to_key(preferred_order)) + %> + + // based on lookup results for each longhand, create result arrays + static MAP: [&'static [ShorthandId]; property_counts::LONGHANDS] = [ + % for property in data.longhands: + &[ + % for shorthand in longhand_to_shorthand_map.get(property.ident, []): + ShorthandId::${shorthand}, + % endfor + ], + % endfor + ]; + + NonCustomPropertyIterator { + filter: NonCustomPropertyId::from(self).enabled_for_all_content(), + iter: MAP[self as usize].iter(), + } + } + + pub(super) fn parse_value<'i, 't>( + self, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>> { + type ParsePropertyFn = for<'i, 't> fn( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>>; + static PARSE_PROPERTY: [ParsePropertyFn; ${len(data.longhands)}] = [ + % for property in data.longhands: + longhands::${property.ident}::parse_declared, + % endfor + ]; + (PARSE_PROPERTY[self as usize])(context, input) + } + + /// Return the relevant data to map a particular logical property into physical. + fn logical_mapping_data(self) -> Option<<&'static LogicalMappingData> { + const LOGICAL_MAPPING_DATA: [Option<LogicalMappingData>; ${len(data.longhands)}] = [ + % for prop in data.longhands: + % if prop.logical: + Some(LogicalMappingData { + group: LogicalGroupId::${to_camel_case(prop.logical_group)}, + kind: ${prop.logical_mapping_kind(data)} + }), + % else: + None, + % endif + % endfor + ]; + LOGICAL_MAPPING_DATA[self as usize].as_ref() + } + + /// If this is a logical property, return the corresponding physical one in the given + /// writing mode. Otherwise, return unchanged. + #[inline] + pub fn to_physical(self, wm: WritingMode) -> Self { + let Some(data) = self.logical_mapping_data() else { return self }; + data.to_physical(wm) + } + + /// Return the logical group of this longhand property. + pub fn logical_group(self) -> Option<LogicalGroupId> { + const LOGICAL_GROUP_IDS: [Option<LogicalGroupId>; ${len(data.longhands)}] = [ + % for prop in data.longhands: + % if prop.logical_group: + Some(LogicalGroupId::${to_camel_case(prop.logical_group)}), + % else: + None, + % endif + % endfor + ]; + LOGICAL_GROUP_IDS[self as usize] + } + + /// Returns PropertyFlags for given longhand property. + #[inline(always)] + pub fn flags(self) -> PropertyFlags { + // TODO(emilio): This can be simplified further as Rust gains more + // constant expression support. + const FLAGS: [u16; ${len(data.longhands)}] = [ + % for property in data.longhands: + % for flag in property.flags + restriction_flags(property): + PropertyFlags::${flag}.bits() | + % endfor + 0, + % endfor + ]; + PropertyFlags::from_bits_retain(FLAGS[self as usize]) + } +} + +/// An identifier for a given shorthand property. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u16)] +pub enum ShorthandId { + % for i, property in enumerate(data.shorthands): + /// ${property.name} + ${property.camel_case} = ${i}, + % endfor +} + +impl ShorthandId { + /// Get the longhand ids that form this shorthand. + pub fn longhands(self) -> NonCustomPropertyIterator<LonghandId> { + static MAP: [&'static [LonghandId]; property_counts::SHORTHANDS] = [ + % for property in data.shorthands: + &[ + % for sub in property.sub_properties: + LonghandId::${sub.camel_case}, + % endfor + ], + % endfor + ]; + NonCustomPropertyIterator { + filter: NonCustomPropertyId::from(self).enabled_for_all_content(), + iter: MAP[self as usize].iter(), + } + } + + /// Try to serialize the given declarations as this shorthand. + /// + /// Returns an error if writing to the stream fails, or if the declarations + /// do not map to a shorthand. + pub fn longhands_to_css( + self, + declarations: &[&PropertyDeclaration], + dest: &mut CssStringWriter, + ) -> fmt::Result { + type LonghandsToCssFn = for<'a, 'b> fn(&'a [&'b PropertyDeclaration], &mut CssStringWriter) -> fmt::Result; + fn all_to_css(_: &[&PropertyDeclaration], _: &mut CssStringWriter) -> fmt::Result { + // No need to try to serialize the declarations as the 'all' + // shorthand, since it only accepts CSS-wide keywords (and variable + // references), which will be handled in + // get_shorthand_appendable_value. + Ok(()) + } + + static LONGHANDS_TO_CSS: [LonghandsToCssFn; ${len(data.shorthands)}] = [ + % for shorthand in data.shorthands: + % if shorthand.ident == "all": + all_to_css, + % else: + shorthands::${shorthand.ident}::to_css, + % endif + % endfor + ]; + + LONGHANDS_TO_CSS[self as usize](declarations, dest) + } + + /// Returns PropertyFlags for the given shorthand property. + #[inline] + pub fn flags(self) -> PropertyFlags { + const FLAGS: [u16; ${len(data.shorthands)}] = [ + % for property in data.shorthands: + % for flag in property.flags: + PropertyFlags::${flag}.bits() | + % endfor + 0, + % endfor + ]; + PropertyFlags::from_bits_retain(FLAGS[self as usize]) + } + + /// Returns the order in which this property appears relative to other + /// shorthands in idl-name-sorting order. + #[inline] + pub fn idl_name_sort_order(self) -> u32 { + <% + from data import to_idl_name + ordered = {} + sorted_shorthands = sorted(data.shorthands, key=lambda p: to_idl_name(p.ident)) + for order, shorthand in enumerate(sorted_shorthands): + ordered[shorthand.ident] = order + %> + static IDL_NAME_SORT_ORDER: [u32; ${len(data.shorthands)}] = [ + % for property in data.shorthands: + ${ordered[property.ident]}, + % endfor + ]; + IDL_NAME_SORT_ORDER[self as usize] + } + + pub(super) fn parse_into<'i, 't>( + self, + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + type ParseIntoFn = for<'i, 't> fn( + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>>; + + fn parse_all<'i, 't>( + _: &mut SourcePropertyDeclaration, + _: &ParserContext, + input: &mut Parser<'i, 't> + ) -> Result<(), ParseError<'i>> { + // 'all' accepts no value other than CSS-wide keywords + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + + static PARSE_INTO: [ParseIntoFn; ${len(data.shorthands)}] = [ + % for shorthand in data.shorthands: + % if shorthand.ident == "all": + parse_all, + % else: + shorthands::${shorthand.ident}::parse_into, + % endif + % endfor + ]; + + (PARSE_INTO[self as usize])(declarations, context, input) + } +} + +/// The counted unknown property list which is used for css use counters. +/// +/// FIXME: This should be just #[repr(u8)], but can't be because of ABI issues, +/// see https://bugs.llvm.org/show_bug.cgi?id=44228. +#[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, PartialEq)] +#[repr(u32)] +pub enum CountedUnknownProperty { + % for prop in data.counted_unknown_properties: + /// ${prop.name} + ${prop.camel_case}, + % endfor +} + +impl CountedUnknownProperty { + /// Parse the counted unknown property, for testing purposes only. + pub fn parse_for_testing(property_name: &str) -> Option<Self> { + ascii_case_insensitive_phf_map! { + unknown_ids -> CountedUnknownProperty = { + % for property in data.counted_unknown_properties: + "${property.name}" => CountedUnknownProperty::${property.camel_case}, + % endfor + } + } + unknown_ids::get(property_name).cloned() + } + + /// Returns the underlying index, used for use counter. + #[inline] + pub fn bit(self) -> usize { + self as usize + } +} + +impl PropertyId { + /// Returns a given property from the given name, _regardless of whether it + /// is enabled or not_, or Err(()) for unknown properties. + pub(super) fn parse_unchecked( + property_name: &str, + use_counters: Option< &UseCounters>, + ) -> Result<Self, ()> { + // A special id for css use counters. ShorthandAlias is not used in the Servo build. + // That's why we need to allow dead_code. + pub enum StaticId { + NonCustom(NonCustomPropertyId), + CountedUnknown(CountedUnknownProperty), + } + ascii_case_insensitive_phf_map! { + static_ids -> StaticId = { + % for i, property in enumerate(data.longhands + data.shorthands + data.all_aliases()): + "${property.name}" => StaticId::NonCustom(NonCustomPropertyId(${i})), + % endfor + % for property in data.counted_unknown_properties: + "${property.name}" => { + StaticId::CountedUnknown(CountedUnknownProperty::${property.camel_case}) + }, + % endfor + } + } + + if let Some(id) = static_ids::get(property_name) { + return Ok(match *id { + StaticId::NonCustom(id) => PropertyId::NonCustom(id), + StaticId::CountedUnknown(unknown_prop) => { + if let Some(counters) = use_counters { + counters.counted_unknown_properties.record(unknown_prop); + } + // Always return Err(()) because these aren't valid custom property names. + return Err(()); + } + }); + } + + let name = crate::custom_properties::parse_name(property_name)?; + Ok(PropertyId::Custom(crate::custom_properties::Name::from(name))) + } +} + +impl PropertyDeclaration { + /// Given a property declaration, return the property declaration id. + #[inline] + pub fn id(&self) -> PropertyDeclarationId { + match *self { + PropertyDeclaration::Custom(ref declaration) => { + return PropertyDeclarationId::Custom(&declaration.name) + } + PropertyDeclaration::CSSWideKeyword(ref declaration) => { + return PropertyDeclarationId::Longhand(declaration.id); + } + PropertyDeclaration::WithVariables(ref declaration) => { + return PropertyDeclarationId::Longhand(declaration.id); + } + _ => {} + } + // This is just fine because PropertyDeclaration and LonghandId + // have corresponding discriminants. + let id = unsafe { *(self as *const _ as *const LonghandId) }; + debug_assert_eq!(id, match *self { + % for property in data.longhands: + PropertyDeclaration::${property.camel_case}(..) => LonghandId::${property.camel_case}, + % endfor + _ => id, + }); + PropertyDeclarationId::Longhand(id) + } + + /// Given a declaration, convert it into a declaration for a corresponding + /// physical property. + #[inline] + pub fn to_physical(&self, wm: WritingMode) -> Self { + match *self { + PropertyDeclaration::WithVariables(VariableDeclaration { + id, + ref value, + }) => { + return PropertyDeclaration::WithVariables(VariableDeclaration { + id: id.to_physical(wm), + value: value.clone(), + }) + } + PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { + id, + keyword, + }) => { + return PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration { + id: id.to_physical(wm), + keyword, + }) + } + PropertyDeclaration::Custom(..) => return self.clone(), + % for prop in data.longhands: + PropertyDeclaration::${prop.camel_case}(..) => {}, + % endfor + } + + let mut ret = self.clone(); + + % for prop in data.longhands: + % for physical_property in prop.all_physical_mapped_properties(data): + % if physical_property.specified_type() != prop.specified_type(): + <% raise "Logical property %s should share specified value with physical property %s" % \ + (prop.name, physical_property.name) %> + % endif + % endfor + % endfor + + unsafe { + let longhand_id = *(&mut ret as *mut _ as *mut LonghandId); + + debug_assert_eq!( + PropertyDeclarationId::Longhand(longhand_id), + ret.id() + ); + + // This is just fine because PropertyDeclaration and LonghandId + // have corresponding discriminants. + *(&mut ret as *mut _ as *mut LonghandId) = longhand_id.to_physical(wm); + + debug_assert_eq!( + PropertyDeclarationId::Longhand(longhand_id.to_physical(wm)), + ret.id() + ); + } + + ret + } + + /// Returns whether or not the property is set by a system font + pub fn get_system(&self) -> Option<SystemFont> { + match *self { + % if engine == "gecko": + % for prop in SYSTEM_FONT_LONGHANDS: + PropertyDeclaration::${to_camel_case(prop)}(ref prop) => { + prop.get_system() + } + % endfor + % endif + _ => None, + } + } +} + +#[cfg(feature = "gecko")] +pub use super::gecko::style_structs; + +/// The module where all the style structs are defined. +#[cfg(feature = "servo")] +pub mod style_structs { + use fxhash::FxHasher; + use super::longhands; + use std::hash::{Hash, Hasher}; + use crate::logical_geometry::WritingMode; + use crate::media_queries::Device; + use crate::values::computed::NonNegativeLength; + + % for style_struct in data.active_style_structs(): + % if style_struct.name == "Font": + #[derive(Clone, Debug, MallocSizeOf)] + #[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] + % else: + #[derive(Clone, Debug, MallocSizeOf, PartialEq)] + % endif + /// The ${style_struct.name} style struct. + pub struct ${style_struct.name} { + % for longhand in style_struct.longhands: + % if not longhand.logical: + /// The ${longhand.name} computed value. + pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T, + % endif + % endfor + % if style_struct.name == "InheritedText": + /// The "used" text-decorations that apply to this box. + /// + /// FIXME(emilio): This is technically a box-tree concept, and + /// would be nice to move away from style. + pub text_decorations_in_effect: crate::values::computed::text::TextDecorationsInEffect, + % endif + % if style_struct.name == "Font": + /// The font hash, used for font caching. + pub hash: u64, + % endif + % if style_struct.name == "Box": + /// The display value specified by the CSS stylesheets (without any style adjustments), + /// which is needed for hypothetical layout boxes. + pub original_display: longhands::display::computed_value::T, + % endif + } + % if style_struct.name == "Font": + impl PartialEq for Font { + fn eq(&self, other: &Font) -> bool { + self.hash == other.hash + % for longhand in style_struct.longhands: + && self.${longhand.ident} == other.${longhand.ident} + % endfor + } + } + % endif + + impl ${style_struct.name} { + % for longhand in style_struct.longhands: + % if not longhand.logical: + % if longhand.ident == "display": + /// Set `display`. + /// + /// We need to keep track of the original display for hypothetical boxes, + /// so we need to special-case this. + #[allow(non_snake_case)] + #[inline] + pub fn set_display(&mut self, v: longhands::display::computed_value::T) { + self.display = v; + self.original_display = v; + } + % else: + /// Set ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T) { + self.${longhand.ident} = v; + } + % endif + % if longhand.ident == "display": + /// Set `display` from other struct. + /// + /// Same as `set_display` above. + /// Thus, we need to special-case this. + #[allow(non_snake_case)] + #[inline] + pub fn copy_display_from(&mut self, other: &Self) { + self.display = other.display.clone(); + self.original_display = other.display.clone(); + } + % else: + /// Set ${longhand.name} from other struct. + #[allow(non_snake_case)] + #[inline] + pub fn copy_${longhand.ident}_from(&mut self, other: &Self) { + self.${longhand.ident} = other.${longhand.ident}.clone(); + } + % endif + /// Reset ${longhand.name} from the initial struct. + #[allow(non_snake_case)] + #[inline] + pub fn reset_${longhand.ident}(&mut self, other: &Self) { + self.copy_${longhand.ident}_from(other) + } + + /// Get the computed value for ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn clone_${longhand.ident}(&self) -> longhands::${longhand.ident}::computed_value::T { + self.${longhand.ident}.clone() + } + % endif + % if longhand.need_index: + /// If this longhand is indexed, get the number of elements. + #[allow(non_snake_case)] + pub fn ${longhand.ident}_count(&self) -> usize { + self.${longhand.ident}.0.len() + } + + /// If this longhand is indexed, get the element at given + /// index. + #[allow(non_snake_case)] + pub fn ${longhand.ident}_at(&self, index: usize) + -> longhands::${longhand.ident}::computed_value::SingleComputedValue { + self.${longhand.ident}.0[index].clone() + } + % endif + % endfor + % if style_struct.name == "Border": + % for side in ["top", "right", "bottom", "left"]: + /// Whether the border-${side} property has nonzero width. + #[allow(non_snake_case)] + pub fn border_${side}_has_nonzero_width(&self) -> bool { + use crate::Zero; + !self.border_${side}_width.is_zero() + } + % endfor + % elif style_struct.name == "Font": + /// Computes a font hash in order to be able to cache fonts + /// effectively in GFX and layout. + pub fn compute_font_hash(&mut self) { + // Corresponds to the fields in + // `gfx::font_template::FontTemplateDescriptor`. + let mut hasher: FxHasher = Default::default(); + self.font_weight.hash(&mut hasher); + self.font_stretch.hash(&mut hasher); + self.font_style.hash(&mut hasher); + self.font_family.hash(&mut hasher); + self.hash = hasher.finish() + } + + /// (Servo does not handle MathML, so this just calls copy_font_size_from) + pub fn inherit_font_size_from(&mut self, parent: &Self, + _: Option<NonNegativeLength>, + _: &Device) { + self.copy_font_size_from(parent); + } + /// (Servo does not handle MathML, so this just calls set_font_size) + pub fn apply_font_size(&mut self, + v: longhands::font_size::computed_value::T, + _: &Self, + _: &Device) -> Option<NonNegativeLength> { + self.set_font_size(v); + None + } + /// (Servo does not handle MathML, so this does nothing) + pub fn apply_unconstrained_font_size(&mut self, _: NonNegativeLength) { + } + + % elif style_struct.name == "Outline": + /// Whether the outline-width property is non-zero. + #[inline] + pub fn outline_has_nonzero_width(&self) -> bool { + use crate::Zero; + !self.outline_width.is_zero() + } + % elif style_struct.name == "Box": + /// Sets the display property, but without touching original_display, + /// except when the adjustment comes from root or item display fixups. + pub fn set_adjusted_display( + &mut self, + dpy: longhands::display::computed_value::T, + is_item_or_root: bool + ) { + self.display = dpy; + if is_item_or_root { + self.original_display = dpy; + } + } + % endif + } + + % endfor +} + +% for style_struct in data.active_style_structs(): + impl style_structs::${style_struct.name} { + % for longhand in style_struct.longhands: + % if longhand.need_index: + /// Iterate over the values of ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn ${longhand.ident}_iter(&self) -> ${longhand.camel_case}Iter { + ${longhand.camel_case}Iter { + style_struct: self, + current: 0, + max: self.${longhand.ident}_count(), + } + } + + /// Get a value mod `index` for the property ${longhand.name}. + #[allow(non_snake_case)] + #[inline] + pub fn ${longhand.ident}_mod(&self, index: usize) + -> longhands::${longhand.ident}::computed_value::SingleComputedValue { + self.${longhand.ident}_at(index % self.${longhand.ident}_count()) + } + + /// Clone the computed value for the property. + #[allow(non_snake_case)] + #[inline] + #[cfg(feature = "gecko")] + pub fn clone_${longhand.ident}( + &self, + ) -> longhands::${longhand.ident}::computed_value::T { + longhands::${longhand.ident}::computed_value::List( + self.${longhand.ident}_iter().collect() + ) + } + % endif + % endfor + + % if style_struct.name == "UI": + /// Returns whether there is any animation specified with + /// animation-name other than `none`. + pub fn specifies_animations(&self) -> bool { + self.animation_name_iter().any(|name| !name.is_none()) + } + + /// Returns whether there are any transitions specified. + #[cfg(feature = "servo")] + pub fn specifies_transitions(&self) -> bool { + (0..self.transition_property_count()).any(|index| { + let combined_duration = + self.transition_duration_mod(index).seconds().max(0.) + + self.transition_delay_mod(index).seconds(); + combined_duration > 0. + }) + } + + /// Returns whether there is any named progress timeline specified with + /// scroll-timeline-name other than `none`. + pub fn specifies_scroll_timelines(&self) -> bool { + self.scroll_timeline_name_iter().any(|name| !name.is_none()) + } + + /// Returns whether there is any named progress timeline specified with + /// view-timeline-name other than `none`. + pub fn specifies_view_timelines(&self) -> bool { + self.view_timeline_name_iter().any(|name| !name.is_none()) + } + + /// Returns true if animation properties are equal between styles, but without + /// considering keyframe data and animation-timeline. + #[cfg(feature = "servo")] + pub fn animations_equals(&self, other: &Self) -> bool { + self.animation_name_iter().eq(other.animation_name_iter()) && + self.animation_delay_iter().eq(other.animation_delay_iter()) && + self.animation_direction_iter().eq(other.animation_direction_iter()) && + self.animation_duration_iter().eq(other.animation_duration_iter()) && + self.animation_fill_mode_iter().eq(other.animation_fill_mode_iter()) && + self.animation_iteration_count_iter().eq(other.animation_iteration_count_iter()) && + self.animation_play_state_iter().eq(other.animation_play_state_iter()) && + self.animation_timing_function_iter().eq(other.animation_timing_function_iter()) + } + + % elif style_struct.name == "Column": + /// Whether this is a multicol style. + #[cfg(feature = "servo")] + pub fn is_multicol(&self) -> bool { + !self.column_width.is_auto() || !self.column_count.is_auto() + } + % endif + } + + % for longhand in style_struct.longhands: + % if longhand.need_index: + /// An iterator over the values of the ${longhand.name} properties. + pub struct ${longhand.camel_case}Iter<'a> { + style_struct: &'a style_structs::${style_struct.name}, + current: usize, + max: usize, + } + + impl<'a> Iterator for ${longhand.camel_case}Iter<'a> { + type Item = longhands::${longhand.ident}::computed_value::SingleComputedValue; + + fn next(&mut self) -> Option<Self::Item> { + self.current += 1; + if self.current <= self.max { + Some(self.style_struct.${longhand.ident}_at(self.current - 1)) + } else { + None + } + } + } + % endif + % endfor +% endfor + + +#[cfg(feature = "gecko")] +pub use super::gecko::{ComputedValues, ComputedValuesInner}; + +#[cfg(feature = "servo")] +#[cfg_attr(feature = "servo", derive(Clone, Debug))] +/// Actual data of ComputedValues, to match up with Gecko +pub struct ComputedValuesInner { + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, + % endfor + custom_properties: crate::custom_properties::ComputedCustomProperties, + + /// The writing mode of this computed values struct. + pub writing_mode: WritingMode, + + /// The effective zoom value. + pub effective_zoom: Zoom, + + /// A set of flags we use to store misc information regarding this style. + pub flags: ComputedValueFlags, + + /// The rule node representing the ordered list of rules matched for this + /// node. Can be None for default values and text nodes. This is + /// essentially an optimization to avoid referencing the root rule node. + pub rules: Option<StrongRuleNode>, + + /// The element's computed values if visited, only computed if there's a + /// relevant link for this element. A element's "relevant link" is the + /// element being matched if it is a link or the nearest ancestor link. + visited_style: Option<Arc<ComputedValues>>, +} + +/// The struct that Servo uses to represent computed values. +/// +/// This struct contains an immutable atomically-reference-counted pointer to +/// every kind of style struct. +/// +/// When needed, the structs may be copied in order to get mutated. +#[cfg(feature = "servo")] +#[cfg_attr(feature = "servo", derive(Clone, Debug))] +pub struct ComputedValues { + /// The actual computed values + /// + /// In Gecko the outer ComputedValues is actually a ComputedStyle, whereas + /// ComputedValuesInner is the core set of computed values. + /// + /// We maintain this distinction in servo to reduce the amount of special + /// casing. + inner: ComputedValuesInner, + + /// The pseudo-element that we're using. + pseudo: Option<PseudoElement>, +} + +impl ComputedValues { + /// Returns the pseudo-element that this style represents. + #[cfg(feature = "servo")] + pub fn pseudo(&self) -> Option<<&PseudoElement> { + self.pseudo.as_ref() + } + + /// Returns true if this is the style for a pseudo-element. + #[cfg(feature = "servo")] + pub fn is_pseudo_style(&self) -> bool { + self.pseudo().is_some() + } + + /// Returns whether this style's display value is equal to contents. + pub fn is_display_contents(&self) -> bool { + self.clone_display().is_contents() + } + + /// Gets a reference to the rule node. Panic if no rule node exists. + pub fn rules(&self) -> &StrongRuleNode { + self.rules.as_ref().unwrap() + } + + /// Returns the visited rules, if applicable. + pub fn visited_rules(&self) -> Option<<&StrongRuleNode> { + self.visited_style().and_then(|s| s.rules.as_ref()) + } + + /// Gets a reference to the custom properties map (if one exists). + pub fn custom_properties(&self) -> &crate::custom_properties::ComputedCustomProperties { + &self.custom_properties + } + + /// Returns whether we have the same custom properties as another style. + pub fn custom_properties_equal(&self, other: &Self) -> bool { + self.custom_properties() == other.custom_properties() + } + +% for prop in data.longhands: +% if not prop.logical: + /// Gets the computed value of a given property. + #[inline(always)] + #[allow(non_snake_case)] + pub fn clone_${prop.ident}( + &self, + ) -> longhands::${prop.ident}::computed_value::T { + self.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}() + } +% endif +% endfor + + /// Writes the (resolved or computed) value of the given longhand as a string in `dest`. + /// + /// TODO(emilio): We should move all the special resolution from + /// nsComputedDOMStyle to ToResolvedValue instead. + pub fn computed_or_resolved_value( + &self, + property_id: LonghandId, + context: Option<<&resolved::Context>, + dest: &mut CssStringWriter, + ) -> fmt::Result { + use crate::values::resolved::ToResolvedValue; + let mut dest = CssWriter::new(dest); + let property_id = property_id.to_physical(self.writing_mode); + match property_id { + % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): + <% props = list(props) %> + ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { + let value = match property_id { + % for prop in props: + % if not prop.logical: + LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), + % endif + % endfor + _ => unsafe { debug_unreachable!() }, + }; + if let Some(c) = context { + value.to_resolved_value(c).to_css(&mut dest) + } else { + value.to_css(&mut dest) + } + } + % endfor + } + } + + /// Returns the given longhand's resolved value as a property declaration. + pub fn computed_or_resolved_declaration( + &self, + property_id: LonghandId, + context: Option<<&resolved::Context>, + ) -> PropertyDeclaration { + use crate::values::resolved::ToResolvedValue; + use crate::values::computed::ToComputedValue; + let physical_property_id = property_id.to_physical(self.writing_mode); + match physical_property_id { + % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): + <% props = list(props) %> + ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { + let mut computed_value = match physical_property_id { + % for prop in props: + % if not prop.logical: + LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), + % endif + % endfor + _ => unsafe { debug_unreachable!() }, + }; + if let Some(c) = context { + let resolved = computed_value.to_resolved_value(c); + computed_value = ToResolvedValue::from_resolved_value(resolved); + } + let specified = ToComputedValue::from_computed_value(&computed_value); + % if props[0].boxed: + let specified = Box::new(specified); + % endif + % if len(props) == 1: + PropertyDeclaration::${props[0].camel_case}(specified) + % else: + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified_type}>, + PropertyDeclarationVariantRepr { + tag: property_id as u16, + value: specified, + }, + ); + out.assume_init() + } + % endif + } + % endfor + } + } + + /// Resolves the currentColor keyword. + /// + /// Any color value from computed values (except for the 'color' property + /// itself) should go through this method. + /// + /// Usage example: + /// let top_color = + /// style.resolve_color(style.get_border().clone_border_top_color()); + #[inline] + pub fn resolve_color(&self, color: computed::Color) -> crate::color::AbsoluteColor { + let current_color = self.get_inherited_text().clone_color(); + color.resolve_to_absolute(¤t_color) + } + + /// Returns which longhand properties have different values in the two + /// ComputedValues. + #[cfg(feature = "gecko_debug")] + pub fn differing_properties(&self, other: &ComputedValues) -> LonghandIdSet { + let mut set = LonghandIdSet::new(); + % for prop in data.longhands: + % if not prop.logical: + if self.clone_${prop.ident}() != other.clone_${prop.ident}() { + set.insert(LonghandId::${prop.camel_case}); + } + % endif + % endfor + set + } + + /// Create a `TransitionPropertyIterator` for this styles transition properties. + pub fn transition_properties<'a>( + &'a self + ) -> animated_properties::TransitionPropertyIterator<'a> { + animated_properties::TransitionPropertyIterator::from_style(self) + } +} + +#[cfg(feature = "servo")] +impl ComputedValues { + /// Create a new refcounted `ComputedValues` + pub fn new( + pseudo: Option<<&PseudoElement>, + custom_properties: crate::custom_properties::ComputedCustomProperties, + writing_mode: WritingMode, + effective_zoom: computed::Zoom, + flags: ComputedValueFlags, + rules: Option<StrongRuleNode>, + visited_style: Option<Arc<ComputedValues>>, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, + % endfor + ) -> Arc<Self> { + Arc::new(Self { + inner: ComputedValuesInner { + custom_properties, + writing_mode, + rules, + visited_style, + effective_zoom, + flags, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}, + % endfor + }, + pseudo: pseudo.cloned(), + }) + } + + /// Get the initial computed values. + pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES } + + /// Serializes the computed value of this property as a string. + pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String { + match property { + PropertyDeclarationId::Longhand(id) => { + let mut s = String::new(); + self.get_longhand_property_value( + id, + &mut CssWriter::new(&mut s) + ).unwrap(); + s + } + PropertyDeclarationId::Custom(name) => { + // FIXME(bug 1869476): This should use a stylist to determine + // whether the name corresponds to an inherited custom property + // and then choose the inherited/non_inherited map accordingly. + let p = &self.custom_properties; + let value = p + .inherited + .as_ref() + .and_then(|map| map.get(name)) + .or_else(|| p.non_inherited.as_ref().and_then(|map| map.get(name))); + value.map_or(String::new(), |value| value.to_css_string()) + } + } + } +} + +#[cfg(feature = "servo")] +impl ops::Deref for ComputedValues { + type Target = ComputedValuesInner; + fn deref(&self) -> &ComputedValuesInner { + &self.inner + } +} + +#[cfg(feature = "servo")] +impl ops::DerefMut for ComputedValues { + fn deref_mut(&mut self) -> &mut ComputedValuesInner { + &mut self.inner + } +} + +#[cfg(feature = "servo")] +impl ComputedValuesInner { + /// Returns the visited style, if any. + pub fn visited_style(&self) -> Option<<&ComputedValues> { + self.visited_style.as_deref() + } + + % for style_struct in data.active_style_structs(): + /// Clone the ${style_struct.name} struct. + #[inline] + pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> { + self.${style_struct.ident}.clone() + } + + /// Get a immutable reference to the ${style_struct.name} struct. + #[inline] + pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + &self.${style_struct.ident} + } + + /// Get a mutable reference to the ${style_struct.name} struct. + #[inline] + pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { + Arc::make_mut(&mut self.${style_struct.ident}) + } + % endfor + + /// Gets a reference to the rule node. Panic if no rule node exists. + pub fn rules(&self) -> &StrongRuleNode { + self.rules.as_ref().unwrap() + } + + #[inline] + /// Returns whether the "content" property for the given style is completely + /// ineffective, and would yield an empty `::before` or `::after` + /// pseudo-element. + pub fn ineffective_content_property(&self) -> bool { + use crate::values::generics::counters::Content; + match self.get_counters().content { + Content::Normal | Content::None => true, + Content::Items(ref items) => items.is_empty(), + } + } + + /// Whether the current style or any of its ancestors is multicolumn. + #[inline] + pub fn can_be_fragmented(&self) -> bool { + self.flags.contains(ComputedValueFlags::CAN_BE_FRAGMENTED) + } + + /// Whether the current style is multicolumn. + #[inline] + pub fn is_multicol(&self) -> bool { + self.get_column().is_multicol() + } + + /// Get the logical computed inline size. + #[inline] + pub fn content_inline_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { + &position_style.height + } else { + &position_style.width + } + } + + /// Get the logical computed block size. + #[inline] + pub fn content_block_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.width } else { &position_style.height } + } + + /// Get the logical computed min inline size. + #[inline] + pub fn min_inline_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.min_height } else { &position_style.min_width } + } + + /// Get the logical computed min block size. + #[inline] + pub fn min_block_size(&self) -> &computed::Size { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.min_width } else { &position_style.min_height } + } + + /// Get the logical computed max inline size. + #[inline] + pub fn max_inline_size(&self) -> &computed::MaxSize { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.max_height } else { &position_style.max_width } + } + + /// Get the logical computed max block size. + #[inline] + pub fn max_block_size(&self) -> &computed::MaxSize { + let position_style = self.get_position(); + if self.writing_mode.is_vertical() { &position_style.max_width } else { &position_style.max_height } + } + + /// Get the logical computed padding for this writing mode. + #[inline] + pub fn logical_padding(&self) -> LogicalMargin<<&computed::LengthPercentage> { + let padding_style = self.get_padding(); + LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( + &padding_style.padding_top.0, + &padding_style.padding_right.0, + &padding_style.padding_bottom.0, + &padding_style.padding_left.0, + )) + } + + /// Get the logical border width + #[inline] + pub fn border_width_for_writing_mode(&self, writing_mode: WritingMode) -> LogicalMargin<Au> { + let border_style = self.get_border(); + LogicalMargin::from_physical(writing_mode, SideOffsets2D::new( + Au::from(border_style.border_top_width), + Au::from(border_style.border_right_width), + Au::from(border_style.border_bottom_width), + Au::from(border_style.border_left_width), + )) + } + + /// Gets the logical computed border widths for this style. + #[inline] + pub fn logical_border_width(&self) -> LogicalMargin<Au> { + self.border_width_for_writing_mode(self.writing_mode) + } + + /// Gets the logical computed margin from this style. + #[inline] + pub fn logical_margin(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { + let margin_style = self.get_margin(); + LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( + &margin_style.margin_top, + &margin_style.margin_right, + &margin_style.margin_bottom, + &margin_style.margin_left, + )) + } + + /// Gets the logical position from this style. + #[inline] + pub fn logical_position(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { + // FIXME(SimonSapin): should be the writing mode of the containing block, maybe? + let position_style = self.get_position(); + LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( + &position_style.top, + &position_style.right, + &position_style.bottom, + &position_style.left, + )) + } + + /// Return true if the effects force the transform style to be Flat + pub fn overrides_transform_style(&self) -> bool { + use crate::computed_values::mix_blend_mode::T as MixBlendMode; + + let effects = self.get_effects(); + // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. + effects.opacity < 1.0 || + !effects.filter.0.is_empty() || + !effects.clip.is_auto() || + effects.mix_blend_mode != MixBlendMode::Normal + } + + /// <https://drafts.csswg.org/css-transforms/#grouping-property-values> + pub fn get_used_transform_style(&self) -> computed_values::transform_style::T { + use crate::computed_values::transform_style::T as TransformStyle; + + let box_ = self.get_box(); + + if self.overrides_transform_style() { + TransformStyle::Flat + } else { + // Return the computed value if not overridden by the above exceptions + box_.transform_style + } + } + + /// Whether given this transform value, the compositor would require a + /// layer. + pub fn transform_requires_layer(&self) -> bool { + use crate::values::generics::transform::TransformOperation; + // Check if the transform matrix is 2D or 3D + for transform in &*self.get_box().transform.0 { + match *transform { + TransformOperation::Perspective(..) => { + return true; + } + TransformOperation::Matrix3D(m) => { + // See http://dev.w3.org/csswg/css-transforms/#2d-matrix + if m.m31 != 0.0 || m.m32 != 0.0 || + m.m13 != 0.0 || m.m23 != 0.0 || + m.m43 != 0.0 || m.m14 != 0.0 || + m.m24 != 0.0 || m.m34 != 0.0 || + m.m33 != 1.0 || m.m44 != 1.0 { + return true; + } + } + TransformOperation::Translate3D(_, _, z) | + TransformOperation::TranslateZ(z) => { + if z.px() != 0. { + return true; + } + } + _ => {} + } + } + + // Neither perspective nor transform present + false + } +} + +/// A reference to a style struct of the parent, or our own style struct. +pub enum StyleStructRef<'a, T: 'static> { + /// A borrowed struct from the parent, for example, for inheriting style. + Borrowed(&'a T), + /// An owned struct, that we've already mutated. + Owned(UniqueArc<T>), + /// Temporarily vacated, will panic if accessed + Vacated, +} + +impl<'a, T: 'a> StyleStructRef<'a, T> +where + T: Clone, +{ + /// Ensure a mutable reference of this value exists, either cloning the + /// borrowed value, or returning the owned one. + pub fn mutate(&mut self) -> &mut T { + if let StyleStructRef::Borrowed(v) = *self { + *self = StyleStructRef::Owned(UniqueArc::new(v.clone())); + } + + match *self { + StyleStructRef::Owned(ref mut v) => v, + StyleStructRef::Borrowed(..) => unreachable!(), + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } + + /// Whether this is pointer-equal to the struct we're going to copy the + /// value from. + /// + /// This is used to avoid allocations when people write stuff like `font: + /// inherit` or such `all: initial`. + #[inline] + pub fn ptr_eq(&self, struct_to_copy_from: &T) -> bool { + match *self { + StyleStructRef::Owned(..) => false, + StyleStructRef::Borrowed(s) => { + s as *const T == struct_to_copy_from as *const T + } + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } + + /// Extract a unique Arc from this struct, vacating it. + /// + /// The vacated state is a transient one, please put the Arc back + /// when done via `put()`. This function is to be used to separate + /// the struct being mutated from the computed context + pub fn take(&mut self) -> UniqueArc<T> { + use std::mem::replace; + let inner = replace(self, StyleStructRef::Vacated); + + match inner { + StyleStructRef::Owned(arc) => arc, + StyleStructRef::Borrowed(s) => UniqueArc::new(s.clone()), + StyleStructRef::Vacated => panic!("Accessed vacated style struct"), + } + } + + /// Replace vacated ref with an arc + pub fn put(&mut self, arc: UniqueArc<T>) { + debug_assert!(matches!(*self, StyleStructRef::Vacated)); + *self = StyleStructRef::Owned(arc); + } + + /// Get a mutable reference to the owned struct, or `None` if the struct + /// hasn't been mutated. + pub fn get_if_mutated(&mut self) -> Option<<&mut T> { + match *self { + StyleStructRef::Owned(ref mut v) => Some(v), + StyleStructRef::Borrowed(..) => None, + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } + + /// Returns an `Arc` to the internal struct, constructing one if + /// appropriate. + pub fn build(self) -> Arc<T> { + match self { + StyleStructRef::Owned(v) => v.shareable(), + // SAFETY: We know all style structs are arc-allocated. + StyleStructRef::Borrowed(v) => unsafe { Arc::from_raw_addrefed(v) }, + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } +} + +impl<'a, T: 'a> ops::Deref for StyleStructRef<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + match *self { + StyleStructRef::Owned(ref v) => &**v, + StyleStructRef::Borrowed(v) => v, + StyleStructRef::Vacated => panic!("Accessed vacated style struct") + } + } +} + +/// A type used to compute a struct with minimal overhead. +/// +/// This allows holding references to the parent/default computed values without +/// actually cloning them, until we either build the style, or mutate the +/// inherited value. +pub struct StyleBuilder<'a> { + /// The device we're using to compute style. + /// + /// This provides access to viewport unit ratios, etc. + pub device: &'a Device, + + /// The stylist we're using to compute style except for media queries. + /// device is used in media queries instead. + pub stylist: Option<<&'a Stylist>, + + /// The style we're inheriting from. + /// + /// This is effectively + /// `parent_style.unwrap_or(device.default_computed_values())`. + inherited_style: &'a ComputedValues, + + /// The style we're getting reset structs from. + reset_style: &'a ComputedValues, + + /// The rule node representing the ordered list of rules matched for this + /// node. + pub rules: Option<StrongRuleNode>, + + /// The computed custom properties. + pub custom_properties: crate::custom_properties::ComputedCustomProperties, + + /// Non-custom properties that are considered invalid at compute time + /// due to cyclic dependencies with custom properties. + /// e.g. `--foo: 1em; font-size: var(--foo)` where `--foo` is registered. + pub invalid_non_custom_properties: LonghandIdSet, + + /// The pseudo-element this style will represent. + pub pseudo: Option<<&'a PseudoElement>, + + /// Whether we have mutated any reset structs since the the last time + /// `clear_modified_reset` was called. This is used to tell whether the + /// `StyleAdjuster` did any work. + modified_reset: bool, + + /// Whether this is the style for the root element. + pub is_root_element: bool, + + /// The writing mode flags. + /// + /// TODO(emilio): Make private. + pub writing_mode: WritingMode, + + /// The effective zoom. + pub effective_zoom: computed::Zoom, + + /// Flags for the computed value. + pub flags: Cell<ComputedValueFlags>, + + /// The element's style if visited, only computed if there's a relevant link + /// for this element. A element's "relevant link" is the element being + /// matched if it is a link or the nearest ancestor link. + pub visited_style: Option<Arc<ComputedValues>>, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: StyleStructRef<'a, style_structs::${style_struct.name}>, + % endfor +} + +impl<'a> StyleBuilder<'a> { + /// Trivially construct a `StyleBuilder`. + pub fn new( + device: &'a Device, + stylist: Option<<&'a Stylist>, + parent_style: Option<<&'a ComputedValues>, + pseudo: Option<<&'a PseudoElement>, + rules: Option<StrongRuleNode>, + is_root_element: bool, + ) -> Self { + let reset_style = device.default_computed_values(); + let inherited_style = parent_style.unwrap_or(reset_style); + + let flags = inherited_style.flags.inherited(); + StyleBuilder { + device, + stylist, + inherited_style, + reset_style, + pseudo, + rules, + modified_reset: false, + is_root_element, + custom_properties: crate::custom_properties::ComputedCustomProperties::default(), + invalid_non_custom_properties: LonghandIdSet::default(), + writing_mode: inherited_style.writing_mode, + effective_zoom: inherited_style.effective_zoom, + flags: Cell::new(flags), + visited_style: None, + % for style_struct in data.active_style_structs(): + % if style_struct.inherited: + ${style_struct.ident}: StyleStructRef::Borrowed(inherited_style.get_${style_struct.name_lower}()), + % else: + ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.get_${style_struct.name_lower}()), + % endif + % endfor + } + } + + /// NOTE(emilio): This is done so we can compute relative units with respect + /// to the parent style, but all the early properties / writing-mode / etc + /// are already set to the right ones on the kid. + /// + /// Do _not_ actually call this to construct a style, this should mostly be + /// used for animations. + pub fn for_animation( + device: &'a Device, + stylist: Option<<&'a Stylist>, + style_to_derive_from: &'a ComputedValues, + parent_style: Option<<&'a ComputedValues>, + ) -> Self { + let reset_style = device.default_computed_values(); + let inherited_style = parent_style.unwrap_or(reset_style); + StyleBuilder { + device, + stylist, + inherited_style, + reset_style, + pseudo: None, + modified_reset: false, + is_root_element: false, + rules: None, + custom_properties: style_to_derive_from.custom_properties().clone(), + invalid_non_custom_properties: LonghandIdSet::default(), + writing_mode: style_to_derive_from.writing_mode, + effective_zoom: style_to_derive_from.effective_zoom, + flags: Cell::new(style_to_derive_from.flags), + visited_style: None, + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: StyleStructRef::Borrowed( + style_to_derive_from.get_${style_struct.name_lower}() + ), + % endfor + } + } + + /// Copy the reset properties from `style`. + pub fn copy_reset_from(&mut self, style: &'a ComputedValues) { + % for style_struct in data.active_style_structs(): + % if not style_struct.inherited: + self.${style_struct.ident} = + StyleStructRef::Borrowed(style.get_${style_struct.name_lower}()); + % endif + % endfor + } + + % for property in data.longhands: + % if not property.logical: + % if not property.style_struct.inherited: + /// Inherit `${property.ident}` from our parent style. + #[allow(non_snake_case)] + pub fn inherit_${property.ident}(&mut self) { + let inherited_struct = + self.inherited_style.get_${property.style_struct.name_lower}(); + + self.modified_reset = true; + self.add_flags(ComputedValueFlags::INHERITS_RESET_STYLE); + + % if property.ident == "content": + self.add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); + % endif + + % if property.ident == "display": + self.add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); + % endif + + if self.${property.style_struct.ident}.ptr_eq(inherited_struct) { + return; + } + + self.${property.style_struct.ident}.mutate() + .copy_${property.ident}_from(inherited_struct); + } + % else: + /// Reset `${property.ident}` to the initial value. + #[allow(non_snake_case)] + pub fn reset_${property.ident}(&mut self) { + let reset_struct = + self.reset_style.get_${property.style_struct.name_lower}(); + + if self.${property.style_struct.ident}.ptr_eq(reset_struct) { + return; + } + + self.${property.style_struct.ident}.mutate() + .reset_${property.ident}(reset_struct); + } + % endif + + % if not property.is_vector or property.simple_vector_bindings or engine in ["servo-2013", "servo-2020"]: + /// Set the `${property.ident}` to the computed value `value`. + #[allow(non_snake_case)] + pub fn set_${property.ident}( + &mut self, + value: longhands::${property.ident}::computed_value::T + ) { + % if not property.style_struct.inherited: + self.modified_reset = true; + % endif + + self.${property.style_struct.ident}.mutate() + .set_${property.ident}( + value, + % if property.logical: + self.writing_mode, + % endif + ); + } + % endif + % endif + % endfor + <% del property %> + + /// Inherits style from the parent element, accounting for the default + /// computed values that need to be provided as well. + pub fn for_inheritance( + device: &'a Device, + stylist: Option<<&'a Stylist>, + parent: Option<<&'a ComputedValues>, + pseudo: Option<<&'a PseudoElement>, + ) -> Self { + // Rebuild the visited style from the parent, ensuring that it will also + // not have rules. This matches the unvisited style that will be + // produced by this builder. This assumes that the caller doesn't need + // to adjust or process visited style, so we can just build visited + // style here for simplicity. + let visited_style = parent.and_then(|parent| { + parent.visited_style().map(|style| { + Self::for_inheritance( + device, + stylist, + Some(style), + pseudo, + ).build() + }) + }); + let custom_properties = if let Some(p) = parent { p.custom_properties().clone() } else { crate::custom_properties::ComputedCustomProperties::default() }; + let mut ret = Self::new( + device, + stylist, + parent, + pseudo, + /* rules = */ None, + /* is_root_element = */ false, + ); + ret.custom_properties = custom_properties; + ret.visited_style = visited_style; + ret + } + + /// Returns whether we have a visited style. + pub fn has_visited_style(&self) -> bool { + self.visited_style.is_some() + } + + /// Returns whether we're a pseudo-elements style. + pub fn is_pseudo_element(&self) -> bool { + self.pseudo.map_or(false, |p| !p.is_anon_box()) + } + + /// Returns the style we're getting reset properties from. + pub fn default_style(&self) -> &'a ComputedValues { + self.reset_style + } + + % for style_struct in data.active_style_structs(): + /// Gets an immutable view of the current `${style_struct.name}` style. + pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + &self.${style_struct.ident} + } + + /// Gets a mutable view of the current `${style_struct.name}` style. + pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { + % if not style_struct.inherited: + self.modified_reset = true; + % endif + self.${style_struct.ident}.mutate() + } + + /// Gets a mutable view of the current `${style_struct.name}` style. + pub fn take_${style_struct.name_lower}(&mut self) -> UniqueArc<style_structs::${style_struct.name}> { + % if not style_struct.inherited: + self.modified_reset = true; + % endif + self.${style_struct.ident}.take() + } + + /// Gets a mutable view of the current `${style_struct.name}` style. + pub fn put_${style_struct.name_lower}(&mut self, s: UniqueArc<style_structs::${style_struct.name}>) { + self.${style_struct.ident}.put(s) + } + + /// Gets a mutable view of the current `${style_struct.name}` style, + /// only if it's been mutated before. + pub fn get_${style_struct.name_lower}_if_mutated(&mut self) + -> Option<<&mut style_structs::${style_struct.name}> { + self.${style_struct.ident}.get_if_mutated() + } + + /// Reset the current `${style_struct.name}` style to its default value. + pub fn reset_${style_struct.name_lower}_struct(&mut self) { + self.${style_struct.ident} = + StyleStructRef::Borrowed(self.reset_style.get_${style_struct.name_lower}()); + } + % endfor + <% del style_struct %> + + /// Returns whether this computed style represents a floated object. + pub fn is_floating(&self) -> bool { + self.get_box().clone_float().is_floating() + } + + /// Returns whether this computed style represents an absolutely-positioned + /// object. + pub fn is_absolutely_positioned(&self) -> bool { + self.get_box().clone_position().is_absolutely_positioned() + } + + /// Whether this style has a top-layer style. + #[cfg(feature = "servo")] + pub fn in_top_layer(&self) -> bool { + matches!(self.get_box().clone__servo_top_layer(), + longhands::_servo_top_layer::computed_value::T::Top) + } + + /// Whether this style has a top-layer style. + #[cfg(feature = "gecko")] + pub fn in_top_layer(&self) -> bool { + matches!(self.get_box().clone__moz_top_layer(), + longhands::_moz_top_layer::computed_value::T::Top) + } + + /// Clears the "have any reset structs been modified" flag. + pub fn clear_modified_reset(&mut self) { + self.modified_reset = false; + } + + /// Returns whether we have mutated any reset structs since the the last + /// time `clear_modified_reset` was called. + pub fn modified_reset(&self) -> bool { + self.modified_reset + } + + /// Return the current flags. + #[inline] + pub fn flags(&self) -> ComputedValueFlags { + self.flags.get() + } + + /// Add a flag to the current builder. + #[inline] + pub fn add_flags(&self, flag: ComputedValueFlags) { + let flags = self.flags() | flag; + self.flags.set(flags); + } + + /// Removes a flag to the current builder. + #[inline] + pub fn remove_flags(&self, flag: ComputedValueFlags) { + let flags = self.flags() & !flag; + self.flags.set(flags); + } + + /// Turns this `StyleBuilder` into a proper `ComputedValues` instance. + pub fn build(self) -> Arc<ComputedValues> { + ComputedValues::new( + self.pseudo, + self.custom_properties, + self.writing_mode, + self.effective_zoom, + self.flags.get(), + self.rules, + self.visited_style, + % for style_struct in data.active_style_structs(): + self.${style_struct.ident}.build(), + % endfor + ) + } + + /// Get the custom properties map if necessary. + pub fn custom_properties(&self) -> &crate::custom_properties::ComputedCustomProperties { + &self.custom_properties + } + + + /// Get the inherited custom properties map. + pub fn inherited_custom_properties(&self) -> &crate::custom_properties::ComputedCustomProperties { + &self.inherited_style.custom_properties + } + + /// Access to various information about our inherited styles. We don't + /// expose an inherited ComputedValues directly, because in the + /// ::first-line case some of the inherited information needs to come from + /// one ComputedValues instance and some from a different one. + + /// Inherited writing-mode. + pub fn inherited_writing_mode(&self) -> &WritingMode { + &self.inherited_style.writing_mode + } + + /// The effective zoom value that we should multiply absolute lengths by. + pub fn effective_zoom(&self) -> computed::Zoom { + self.effective_zoom + } + + /// The zoom specified on this element. + pub fn specified_zoom(&self) -> computed::Zoom { + self.get_box().clone_zoom() + } + + /// Inherited zoom. + pub fn inherited_effective_zoom(&self) -> computed::Zoom { + self.inherited_style.effective_zoom + } + + /// The computed value flags of our parent. + #[inline] + pub fn get_parent_flags(&self) -> ComputedValueFlags { + self.inherited_style.flags + } + + /// Calculate the line height, given the currently resolved line-height and font. + pub fn calc_line_height( + &self, + device: &Device, + line_height_base: LineHeightBase, + writing_mode: WritingMode, + ) -> computed::NonNegativeLength { + use crate::computed_value_flags::ComputedValueFlags; + let (font, flag) = match line_height_base { + LineHeightBase::CurrentStyle => ( + self.get_font(), + ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS, + ), + LineHeightBase::InheritedStyle => ( + self.get_parent_font(), + ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS, + ), + }; + let line_height = font.clone_line_height(); + if matches!(line_height, computed::LineHeight::Normal) { + self.add_flags(flag); + } + device.calc_line_height(&font, writing_mode, None) + } + + /// And access to inherited style structs. + % for style_struct in data.active_style_structs(): + /// Gets our inherited `${style_struct.name}`. We don't name these + /// accessors `inherited_${style_struct.name_lower}` because we already + /// have things like "box" vs "inherited_box" as struct names. Do the + /// next-best thing and call them `parent_${style_struct.name_lower}` + /// instead. + pub fn get_parent_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { + self.inherited_style.get_${style_struct.name_lower}() + } + % endfor +} + +#[cfg(feature = "servo")] +pub use self::lazy_static_module::INITIAL_SERVO_VALUES; + +// Use a module to work around #[cfg] on lazy_static! not being applied to every generated item. +#[cfg(feature = "servo")] +#[allow(missing_docs)] +mod lazy_static_module { + use crate::logical_geometry::WritingMode; + use crate::computed_value_flags::ComputedValueFlags; + use servo_arc::Arc; + use super::{ComputedValues, ComputedValuesInner, longhands, style_structs}; + + lazy_static! { + /// The initial values for all style structs as defined by the specification. + pub static ref INITIAL_SERVO_VALUES: ComputedValues = ComputedValues { + inner: ComputedValuesInner { + % for style_struct in data.active_style_structs(): + ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} { + % for longhand in style_struct.longhands: + % if not longhand.logical: + ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(), + % endif + % endfor + % if style_struct.name == "InheritedText": + text_decorations_in_effect: + crate::values::computed::text::TextDecorationsInEffect::default(), + % endif + % if style_struct.name == "Font": + hash: 0, + % endif + % if style_struct.name == "Box": + original_display: longhands::display::get_initial_value(), + % endif + }), + % endfor + custom_properties, + writing_mode: WritingMode::empty(), + rules: None, + visited_style: None, + flags: ComputedValueFlags::empty(), + }, + pseudo: None, + }; + } +} + +/// A per-longhand function that performs the CSS cascade for that longhand. +pub type CascadePropertyFn = + unsafe extern "Rust" fn( + declaration: &PropertyDeclaration, + context: &mut computed::Context, + ); + +/// A per-longhand array of functions to perform the CSS cascade on each of +/// them, effectively doing virtual dispatch. +pub static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [ + % for property in data.longhands: + longhands::${property.ident}::cascade_property, + % endfor +]; + +/// See StyleAdjuster::adjust_for_border_width. +pub fn adjust_border_width(style: &mut StyleBuilder) { + % for side in ["top", "right", "bottom", "left"]: + // Like calling to_computed_value, which wouldn't type check. + if style.get_border().clone_border_${side}_style().none_or_hidden() && + style.get_border().border_${side}_has_nonzero_width() { + style.set_border_${side}_width(Au(0)); + } + % endfor +} + +/// An identifier for a given alias property. +#[derive(Clone, Copy, Eq, PartialEq, MallocSizeOf)] +#[repr(u16)] +pub enum AliasId { + % for i, property in enumerate(data.all_aliases()): + /// ${property.name} + ${property.camel_case} = ${i}, + % endfor +} + +impl fmt::Debug for AliasId { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let name = NonCustomPropertyId::from(*self).name(); + formatter.write_str(name) + } +} + +impl AliasId { + /// Returns the property we're aliasing, as a longhand or a shorthand. + #[inline] + pub fn aliased_property(self) -> NonCustomPropertyId { + static MAP: [NonCustomPropertyId; ${len(data.all_aliases())}] = [ + % for alias in data.all_aliases(): + % if alias.original.type() == "longhand": + NonCustomPropertyId::from_longhand(LonghandId::${alias.original.camel_case}), + % else: + <% assert alias.original.type() == "shorthand" %> + NonCustomPropertyId::from_shorthand(ShorthandId::${alias.original.camel_case}), + % endif + % endfor + ]; + MAP[self as usize] + } +} + +/// Call the given macro with tokens like this for each longhand and shorthand properties +/// that is enabled in content: +/// +/// ``` +/// [CamelCaseName, SetCamelCaseName, PropertyId::Longhand(LonghandId::CamelCaseName)], +/// ``` +/// +/// NOTE(emilio): Callers are responsible to deal with prefs. +#[macro_export] +macro_rules! css_properties_accessors { + ($macro_name: ident) => { + $macro_name! { + % for kind, props in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]: + % for property in props: + % if property.enabled_in_content(): + % for prop in [property] + property.aliases: + % if '-' in prop.name: + [${prop.ident.capitalize()}, Set${prop.ident.capitalize()}, + PropertyId::${kind}(${kind}Id::${property.camel_case})], + % endif + [${prop.camel_case}, Set${prop.camel_case}, + PropertyId::${kind}(${kind}Id::${property.camel_case})], + % endfor + % endif + % endfor + % endfor + } + } +} + +/// Call the given macro with tokens like this for each longhand properties: +/// +/// ``` +/// { snake_case_ident } +/// ``` +#[macro_export] +macro_rules! longhand_properties_idents { + ($macro_name: ident) => { + $macro_name! { + % for property in data.longhands: + { ${property.ident} } + % endfor + } + } +} + +// Large pages generate tens of thousands of ComputedValues. +size_of_test!(ComputedValues, 240); +// FFI relies on this. +size_of_test!(Option<Arc<ComputedValues>>, 8); + +// There are two reasons for this test to fail: +// +// * Your changes made a specified value type for a given property go +// over the threshold. In that case, you should try to shrink it again +// or, if not possible, mark the property as boxed in the property +// definition. +// +// * Your changes made a specified value type smaller, so that it no +// longer needs to be boxed. In this case you just need to remove +// boxed=True from the property definition. Nice job! +#[cfg(target_pointer_width = "64")] +#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952 +const BOX_THRESHOLD: usize = 24; +% for longhand in data.longhands: +#[cfg(target_pointer_width = "64")] +% if longhand.boxed: +const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() > BOX_THRESHOLD); +% else: +const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() <= BOX_THRESHOLD); +% endif +% endfor + +% if engine in ["servo-2013", "servo-2020"]: +% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]: + macro_rules! restyle_damage_${effect_name} { + ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ]) => ({ + if + % for style_struct in data.active_style_structs(): + % for longhand in style_struct.longhands: + % if effect_name in longhand.servo_restyle_damage.split() and not longhand.logical: + $old.get_${style_struct.name_lower}().${longhand.ident} != + $new.get_${style_struct.name_lower}().${longhand.ident} || + % endif + % endfor + % endfor + + false { + $damage.insert($($effect)|*); + true + } else { + false + } + }) + } +% endfor +% endif diff --git a/servo/components/style/properties/shorthands/background.mako.rs b/servo/components/style/properties/shorthands/background.mako.rs new file mode 100644 index 0000000000..08838233f6 --- /dev/null +++ b/servo/components/style/properties/shorthands/background.mako.rs @@ -0,0 +1,289 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +// TODO: other background-* properties +<%helpers:shorthand name="background" + engines="gecko servo-2013 servo-2020" + sub_properties="background-color background-position-x background-position-y background-repeat + background-attachment background-image background-size background-origin + background-clip" + spec="https://drafts.csswg.org/css-backgrounds/#the-background"> + use crate::properties::longhands::{background_position_x, background_position_y, background_repeat}; + use crate::properties::longhands::{background_attachment, background_color, background_image, background_size, background_origin}; + use crate::properties::longhands::background_clip; + use crate::properties::longhands::background_clip::single_value::computed_value::T as Clip; + use crate::properties::longhands::background_origin::single_value::computed_value::T as Origin; + use crate::values::specified::{AllowQuirks, Color, Position, PositionComponent}; + use crate::parser::Parse; + + // FIXME(emilio): Should be the same type! + impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue { + fn from(origin: background_origin::single_value::SpecifiedValue) -> + background_clip::single_value::SpecifiedValue { + match origin { + background_origin::single_value::SpecifiedValue::ContentBox => + background_clip::single_value::SpecifiedValue::ContentBox, + background_origin::single_value::SpecifiedValue::PaddingBox => + background_clip::single_value::SpecifiedValue::PaddingBox, + background_origin::single_value::SpecifiedValue::BorderBox => + background_clip::single_value::SpecifiedValue::BorderBox, + } + } + } + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut background_color = None; + + % for name in "image position_x position_y repeat size attachment origin clip".split(): + // Vec grows from 0 to 4 by default on first push(). So allocate with + // capacity 1, so in the common case of only one item we don't way + // overallocate, then shrink. Note that we always push at least one + // item if parsing succeeds. + let mut background_${name} = Vec::with_capacity(1); + % endfor + input.parse_comma_separated(|input| { + // background-color can only be in the last element, so if it + // is parsed anywhere before, the value is invalid. + if background_color.is_some() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + % for name in "image position repeat size attachment origin clip".split(): + let mut ${name} = None; + % endfor + loop { + if background_color.is_none() { + if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) { + background_color = Some(value); + continue + } + } + if position.is_none() { + if let Ok(value) = input.try_parse(|input| { + Position::parse_three_value_quirky(context, input, AllowQuirks::No) + }) { + position = Some(value); + + // Parse background size, if applicable. + size = input.try_parse(|input| { + input.expect_delim('/')?; + background_size::single_value::parse(context, input) + }).ok(); + + continue + } + } + % for name in "image repeat attachment origin clip".split(): + if ${name}.is_none() { + if let Ok(value) = input.try_parse(|input| background_${name}::single_value + ::parse(context, input)) { + ${name} = Some(value); + continue + } + } + % endfor + break + } + if clip.is_none() { + if let Some(origin) = origin { + clip = Some(background_clip::single_value::SpecifiedValue::from(origin)); + } + } + let mut any = false; + % for name in "image position repeat size attachment origin clip".split(): + any = any || ${name}.is_some(); + % endfor + any = any || background_color.is_some(); + if any { + if let Some(position) = position { + background_position_x.push(position.horizontal); + background_position_y.push(position.vertical); + } else { + background_position_x.push(PositionComponent::zero()); + background_position_y.push(PositionComponent::zero()); + } + % for name in "image repeat size attachment origin clip".split(): + if let Some(bg_${name}) = ${name} { + background_${name}.push(bg_${name}); + } else { + background_${name}.push(background_${name}::single_value + ::get_initial_specified_value()); + } + % endfor + Ok(()) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + })?; + + Ok(expanded! { + background_color: background_color.unwrap_or(Color::transparent()), + % for name in "image position_x position_y repeat size attachment origin clip".split(): + background_${name}: background_${name}::SpecifiedValue(background_${name}.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let len = self.background_image.0.len(); + // There should be at least one declared value + if len == 0 { + return Ok(()); + } + + // If a value list length is differs then we don't do a shorthand serialization. + // The exceptions to this is color which appears once only and is serialized + // with the last item. + % for name in "image position_x position_y size repeat origin clip attachment".split(): + if len != self.background_${name}.0.len() { + return Ok(()); + } + % endfor + + for i in 0..len { + % for name in "image position_x position_y repeat size attachment origin clip".split(): + let ${name} = &self.background_${name}.0[i]; + % endfor + + if i != 0 { + dest.write_str(", ")?; + } + + let mut wrote_value = false; + + if i == len - 1 { + if *self.background_color != background_color::get_initial_specified_value() { + self.background_color.to_css(dest)?; + wrote_value = true; + } + } + + if *image != background_image::single_value::get_initial_specified_value() { + if wrote_value { + dest.write_char(' ')?; + } + image.to_css(dest)?; + wrote_value = true; + } + + // Size is only valid after a position so when there is a + // non-initial size we must also serialize position + if *position_x != PositionComponent::zero() || + *position_y != PositionComponent::zero() || + *size != background_size::single_value::get_initial_specified_value() + { + if wrote_value { + dest.write_char(' ')?; + } + + Position { + horizontal: position_x.clone(), + vertical: position_y.clone() + }.to_css(dest)?; + + wrote_value = true; + + if *size != background_size::single_value::get_initial_specified_value() { + dest.write_str(" / ")?; + size.to_css(dest)?; + } + } + + % for name in "repeat attachment".split(): + if *${name} != background_${name}::single_value::get_initial_specified_value() { + if wrote_value { + dest.write_char(' ')?; + } + ${name}.to_css(dest)?; + wrote_value = true; + } + % endfor + + if *origin != Origin::PaddingBox || *clip != Clip::BorderBox { + if wrote_value { + dest.write_char(' ')?; + } + origin.to_css(dest)?; + if *clip != From::from(*origin) { + dest.write_char(' ')?; + clip.to_css(dest)?; + } + + wrote_value = true; + } + + if !wrote_value { + image.to_css(dest)?; + } + } + + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="background-position" + engines="gecko servo-2013 servo-2020" + flags="SHORTHAND_IN_GETCS" + sub_properties="background-position-x background-position-y" + spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position"> + use crate::properties::longhands::{background_position_x, background_position_y}; + use crate::values::specified::AllowQuirks; + use crate::values::specified::position::Position; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + // Vec grows from 0 to 4 by default on first push(). So allocate with + // capacity 1, so in the common case of only one item we don't way + // overallocate, then shrink. Note that we always push at least one + // item if parsing succeeds. + let mut position_x = Vec::with_capacity(1); + let mut position_y = Vec::with_capacity(1); + let mut any = false; + + input.parse_comma_separated(|input| { + let value = Position::parse_three_value_quirky(context, input, AllowQuirks::Yes)?; + position_x.push(value.horizontal); + position_y.push(value.vertical); + any = true; + Ok(()) + })?; + if !any { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + Ok(expanded! { + background_position_x: background_position_x::SpecifiedValue(position_x.into()), + background_position_y: background_position_y::SpecifiedValue(position_y.into()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let len = self.background_position_x.0.len(); + if len == 0 || len != self.background_position_y.0.len() { + return Ok(()); + } + for i in 0..len { + Position { + horizontal: self.background_position_x.0[i].clone(), + vertical: self.background_position_y.0[i].clone() + }.to_css(dest)?; + + if i < len - 1 { + dest.write_str(", ")?; + } + } + Ok(()) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/border.mako.rs b/servo/components/style/properties/shorthands/border.mako.rs new file mode 100644 index 0000000000..c6a87f3197 --- /dev/null +++ b/servo/components/style/properties/shorthands/border.mako.rs @@ -0,0 +1,491 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import to_rust_ident, ALL_SIDES, PHYSICAL_SIDES, maybe_moz_logical_alias %> + +${helpers.four_sides_shorthand( + "border-color", + "border-%s-color", + "specified::Color::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-backgrounds/#border-color", + allow_quirks="Yes", +)} + +${helpers.four_sides_shorthand( + "border-style", + "border-%s-style", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-backgrounds/#border-style", +)} + +<%helpers:shorthand + name="border-width" + engines="gecko servo-2013 servo-2020" + sub_properties="${ + ' '.join('border-%s-width' % side + for side in PHYSICAL_SIDES)}" + spec="https://drafts.csswg.org/css-backgrounds/#border-width"> + use crate::values::generics::rect::Rect; + use crate::values::specified::{AllowQuirks, BorderSideWidth}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let rect = Rect::parse_with(context, input, |_, i| { + BorderSideWidth::parse_quirky(context, i, AllowQuirks::Yes) + })?; + Ok(expanded! { + border_top_width: rect.0, + border_right_width: rect.1, + border_bottom_width: rect.2, + border_left_width: rect.3, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + % for side in PHYSICAL_SIDES: + let ${side} = &self.border_${side}_width; + % endfor + Rect::new(top, right, bottom, left).to_css(dest) + } + } +</%helpers:shorthand> + + +pub fn parse_border<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, +) -> Result<(specified::Color, specified::BorderStyle, specified::BorderSideWidth), ParseError<'i>> { + use crate::values::specified::{Color, BorderStyle, BorderSideWidth}; + let _unused = context; + let mut color = None; + let mut style = None; + let mut width = None; + let mut any = false; + loop { + if width.is_none() { + if let Ok(value) = input.try_parse(|i| BorderSideWidth::parse(context, i)) { + width = Some(value); + any = true; + } + } + if style.is_none() { + if let Ok(value) = input.try_parse(BorderStyle::parse) { + style = Some(value); + any = true; + continue + } + } + if color.is_none() { + if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) { + color = Some(value); + any = true; + continue + } + } + break + } + if !any { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + Ok((color.unwrap_or(Color::CurrentColor), style.unwrap_or(BorderStyle::None), width.unwrap_or(BorderSideWidth::medium()))) +} + +% for side, logical in ALL_SIDES: + <% + spec = "https://drafts.csswg.org/css-backgrounds/#border-%s" % side + if logical: + spec = "https://drafts.csswg.org/css-logical-props/#propdef-border-%s" % side + %> + <%helpers:shorthand + name="border-${side}" + engines="gecko servo-2013 servo-2020" + sub_properties="${' '.join( + 'border-%s-%s' % (side, prop) + for prop in ['width', 'style', 'color'] + )}" + aliases="${maybe_moz_logical_alias(engine, (side, logical), '-moz-border-%s')}" + spec="${spec}"> + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let (color, style, width) = super::parse_border(context, input)?; + Ok(expanded! { + border_${to_rust_ident(side)}_color: color, + border_${to_rust_ident(side)}_style: style, + border_${to_rust_ident(side)}_width: width + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + crate::values::specified::border::serialize_directional_border( + dest, + self.border_${to_rust_ident(side)}_width, + self.border_${to_rust_ident(side)}_style, + self.border_${to_rust_ident(side)}_color + ) + } + } + + </%helpers:shorthand> +% endfor + +<%helpers:shorthand name="border" + engines="gecko servo-2013 servo-2020" + sub_properties="${' '.join('border-%s-%s' % (side, prop) + for side in PHYSICAL_SIDES for prop in ['width', 'style', 'color'] + )} + ${' '.join('border-image-%s' % name + for name in ['outset', 'repeat', 'slice', 'source', 'width'])}" + derive_value_info="False" + spec="https://drafts.csswg.org/css-backgrounds/#border"> + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice}; + use crate::properties::longhands::{border_image_source, border_image_width}; + + let (color, style, width) = super::parse_border(context, input)?; + Ok(expanded! { + % for side in PHYSICAL_SIDES: + border_${side}_color: color.clone(), + border_${side}_style: style, + border_${side}_width: width.clone(), + % endfor + + // The ‘border’ shorthand resets ‘border-image’ to its initial value. + // See https://drafts.csswg.org/css-backgrounds-3/#the-border-shorthands + % for name in "outset repeat slice source width".split(): + border_image_${name}: border_image_${name}::get_initial_specified_value(), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use crate::properties::longhands; + + // If any of the border-image longhands differ from their initial specified values we should not + // invoke serialize_directional_border(), so there is no point in continuing on to compute all_equal. + % for name in "outset repeat slice source width".split(): + if *self.border_image_${name} != longhands::border_image_${name}::get_initial_specified_value() { + return Ok(()); + } + % endfor + + let all_equal = { + % for side in PHYSICAL_SIDES: + let border_${side}_width = self.border_${side}_width; + let border_${side}_style = self.border_${side}_style; + let border_${side}_color = self.border_${side}_color; + % endfor + + border_top_width == border_right_width && + border_right_width == border_bottom_width && + border_bottom_width == border_left_width && + + border_top_style == border_right_style && + border_right_style == border_bottom_style && + border_bottom_style == border_left_style && + + border_top_color == border_right_color && + border_right_color == border_bottom_color && + border_bottom_color == border_left_color + }; + + // If all longhands are all present, then all sides should be the same, + // so we can just one set of color/style/width + if !all_equal { + return Ok(()) + } + crate::values::specified::border::serialize_directional_border( + dest, + self.border_${side}_width, + self.border_${side}_style, + self.border_${side}_color + ) + } + } + + // Just use the same as border-left. The border shorthand can't accept + // any value that the sub-shorthand couldn't. + <% + border_left = "<crate::properties::shorthands::border_left::Longhands as SpecifiedValueInfo>" + %> + impl SpecifiedValueInfo for Longhands { + const SUPPORTED_TYPES: u8 = ${border_left}::SUPPORTED_TYPES; + fn collect_completion_keywords(f: KeywordsCollectFn) { + ${border_left}::collect_completion_keywords(f); + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="border-radius" + engines="gecko servo-2013 servo-2020" + sub_properties="${' '.join( + 'border-%s-radius' % (corner) + for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left'] + )}" + extra_prefixes="webkit" + spec="https://drafts.csswg.org/css-backgrounds/#border-radius" +> + use crate::values::generics::rect::Rect; + use crate::values::generics::border::BorderCornerRadius; + use crate::values::specified::border::BorderRadius; + use crate::parser::Parse; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let radii = BorderRadius::parse(context, input)?; + Ok(expanded! { + border_top_left_radius: radii.top_left, + border_top_right_radius: radii.top_right, + border_bottom_right_radius: radii.bottom_right, + border_bottom_left_radius: radii.bottom_left, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let LonghandsToSerialize { + border_top_left_radius: &BorderCornerRadius(ref tl), + border_top_right_radius: &BorderCornerRadius(ref tr), + border_bottom_right_radius: &BorderCornerRadius(ref br), + border_bottom_left_radius: &BorderCornerRadius(ref bl), + } = *self; + + + let widths = Rect::new(tl.width(), tr.width(), br.width(), bl.width()); + let heights = Rect::new(tl.height(), tr.height(), br.height(), bl.height()); + + BorderRadius::serialize_rects(widths, heights, dest) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="border-image" + engines="gecko servo-2013" + sub_properties="border-image-outset + border-image-repeat border-image-slice border-image-source border-image-width" + extra_prefixes="moz:layout.css.prefixes.border-image webkit" + spec="https://drafts.csswg.org/css-backgrounds-3/#border-image" +> + use crate::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice}; + use crate::properties::longhands::{border_image_source, border_image_width}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + % for name in "outset repeat slice source width".split(): + let mut ${name} = border_image_${name}::get_initial_specified_value(); + % endfor + let mut any = false; + let mut parsed_slice = false; + let mut parsed_source = false; + let mut parsed_repeat = false; + loop { + if !parsed_slice { + if let Ok(value) = input.try_parse(|input| border_image_slice::parse(context, input)) { + parsed_slice = true; + any = true; + slice = value; + // Parse border image width and outset, if applicable. + let maybe_width_outset: Result<_, ParseError> = input.try_parse(|input| { + input.expect_delim('/')?; + + // Parse border image width, if applicable. + let w = input.try_parse(|input| border_image_width::parse(context, input)).ok(); + + // Parse border image outset if applicable. + let o = input.try_parse(|input| { + input.expect_delim('/')?; + border_image_outset::parse(context, input) + }).ok(); + if w.is_none() && o.is_none() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + Ok((w, o)) + }); + if let Ok((w, o)) = maybe_width_outset { + if let Some(w) = w { + width = w; + } + if let Some(o) = o { + outset = o; + } + } + continue; + } + } + % for name in "source repeat".split(): + if !parsed_${name} { + if let Ok(value) = input.try_parse(|input| border_image_${name}::parse(context, input)) { + ${name} = value; + parsed_${name} = true; + any = true; + continue + } + } + % endfor + break + } + if !any { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + Ok(expanded! { + % for name in "outset repeat slice source width".split(): + border_image_${name}: ${name}, + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let mut has_any = false; + % for name in "source slice outset width repeat".split(): + let has_${name} = *self.border_image_${name} != border_image_${name}::get_initial_specified_value(); + has_any = has_any || has_${name}; + % endfor + if has_source || !has_any { + self.border_image_source.to_css(dest)?; + if !has_any { + return Ok(()); + } + } + let needs_slice = has_slice || has_width || has_outset; + if needs_slice { + if has_source { + dest.write_char(' ')?; + } + self.border_image_slice.to_css(dest)?; + if has_width || has_outset { + dest.write_str(" /")?; + if has_width { + dest.write_char(' ')?; + self.border_image_width.to_css(dest)?; + } + if has_outset { + dest.write_str(" / ")?; + self.border_image_outset.to_css(dest)?; + } + } + } + if has_repeat { + if has_source || needs_slice { + dest.write_char(' ')?; + } + self.border_image_repeat.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +% for axis in ["block", "inline"]: + % for prop in ["width", "style", "color"]: + <% + spec = "https://drafts.csswg.org/css-logical/#propdef-border-%s-%s" % (axis, prop) + %> + <%helpers:shorthand + engines="gecko servo-2013 servo-2020" + name="border-${axis}-${prop}" + sub_properties="${' '.join( + 'border-%s-%s-%s' % (axis, side, prop) + for side in ['start', 'end'] + )}" + spec="${spec}"> + + use crate::properties::longhands::border_${axis}_start_${prop}; + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let start_value = border_${axis}_start_${prop}::parse(context, input)?; + let end_value = + input.try_parse(|input| border_${axis}_start_${prop}::parse(context, input)) + .unwrap_or_else(|_| start_value.clone()); + + Ok(expanded! { + border_${axis}_start_${prop}: start_value, + border_${axis}_end_${prop}: end_value, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.border_${axis}_start_${prop}.to_css(dest)?; + + if self.border_${axis}_end_${prop} != self.border_${axis}_start_${prop} { + dest.write_char(' ')?; + self.border_${axis}_end_${prop}.to_css(dest)?; + } + + Ok(()) + } + } + </%helpers:shorthand> + % endfor +% endfor + +% for axis in ["block", "inline"]: + <% + spec = "https://drafts.csswg.org/css-logical/#propdef-border-%s" % (axis) + %> + <%helpers:shorthand + name="border-${axis}" + engines="gecko servo-2013 servo-2020" + sub_properties="${' '.join( + 'border-%s-%s-width' % (axis, side) + for side in ['start', 'end'] + )} ${' '.join( + 'border-%s-%s-style' % (axis, side) + for side in ['start', 'end'] + )} ${' '.join( + 'border-%s-%s-color' % (axis, side) + for side in ['start', 'end'] + )}" + spec="${spec}"> + + use crate::properties::shorthands::border_${axis}_start; + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let start_value = border_${axis}_start::parse_value(context, input)?; + Ok(expanded! { + border_${axis}_start_width: start_value.border_${axis}_start_width.clone(), + border_${axis}_end_width: start_value.border_${axis}_start_width, + border_${axis}_start_style: start_value.border_${axis}_start_style.clone(), + border_${axis}_end_style: start_value.border_${axis}_start_style, + border_${axis}_start_color: start_value.border_${axis}_start_color.clone(), + border_${axis}_end_color: start_value.border_${axis}_start_color, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + crate::values::specified::border::serialize_directional_border( + dest, + self.border_${axis}_start_width, + self.border_${axis}_start_style, + self.border_${axis}_start_color + ) + } + } + </%helpers:shorthand> +% endfor diff --git a/servo/components/style/properties/shorthands/box.mako.rs b/servo/components/style/properties/shorthands/box.mako.rs new file mode 100644 index 0000000000..f644687dc0 --- /dev/null +++ b/servo/components/style/properties/shorthands/box.mako.rs @@ -0,0 +1,253 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +${helpers.two_properties_shorthand( + "overflow", + "overflow-x", + "overflow-y", + engines="gecko servo-2013 servo-2020", + flags="SHORTHAND_IN_GETCS", + spec="https://drafts.csswg.org/css-overflow/#propdef-overflow", +)} + +${helpers.two_properties_shorthand( + "overflow-clip-box", + "overflow-clip-box-block", + "overflow-clip-box-inline", + engines="gecko", + enabled_in="ua", + gecko_pref="layout.css.overflow-clip-box.enabled", + spec="Internal, may be standardized in the future " + "(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)", +)} + +${helpers.two_properties_shorthand( + "overscroll-behavior", + "overscroll-behavior-x", + "overscroll-behavior-y", + engines="gecko", + gecko_pref="layout.css.overscroll-behavior.enabled", + spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", +)} + +<%helpers:shorthand + engines="gecko" + name="container" + sub_properties="container-name container-type" + gecko_pref="layout.css.container-queries.enabled" + enabled_in="ua" + spec="https://drafts.csswg.org/css-contain-3/#container-shorthand" +> + use crate::values::specified::box_::{ContainerName, ContainerType}; + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::parser::Parse; + // See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't + // match the spec. + let container_name = ContainerName::parse(context, input)?; + let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() { + ContainerType::parse(input)? + } else { + ContainerType::Normal + }; + Ok(expanded! { + container_name: container_name, + container_type: container_type, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.container_name.to_css(dest)?; + if !self.container_type.is_normal() { + dest.write_str(" / ")?; + self.container_type.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + engines="gecko" + name="page-break-before" + flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" + sub_properties="break-before" + spec="https://drafts.csswg.org/css-break-3/#page-break-properties" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::values::specified::box_::BreakBetween; + Ok(expanded! { + break_before: BreakBetween::parse_legacy(context, input)?, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.break_before.to_css_legacy(dest) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + engines="gecko" + name="page-break-after" + flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" + sub_properties="break-after" + spec="https://drafts.csswg.org/css-break-3/#page-break-properties" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::values::specified::box_::BreakBetween; + Ok(expanded! { + break_after: BreakBetween::parse_legacy(context, input)?, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.break_after.to_css_legacy(dest) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + engines="gecko" + name="page-break-inside" + flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" + sub_properties="break-inside" + spec="https://drafts.csswg.org/css-break-3/#page-break-properties" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::values::specified::box_::BreakWithin; + Ok(expanded! { + break_inside: BreakWithin::parse_legacy(context, input)?, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.break_inside.to_css_legacy(dest) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="offset" + engines="gecko" + sub_properties="offset-path offset-distance offset-rotate offset-anchor + offset-position" + spec="https://drafts.fxtf.org/motion-1/#offset-shorthand"> + use crate::parser::Parse; + use crate::values::specified::motion::{OffsetPath, OffsetPosition, OffsetRotate}; + use crate::values::specified::{LengthPercentage, PositionOrAuto}; + use crate::Zero; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let offset_position = + if static_prefs::pref!("layout.css.motion-path-offset-position.enabled") { + input.try_parse(|i| OffsetPosition::parse(context, i)).ok() + } else { + None + }; + + let offset_path = input.try_parse(|i| OffsetPath::parse(context, i)).ok(); + + // Must have one of [offset-position, offset-path]. + // FIXME: The syntax is out-of-date after the update of the spec. + // https://github.com/w3c/fxtf-drafts/issues/515 + if offset_position.is_none() && offset_path.is_none() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + let mut offset_distance = None; + let mut offset_rotate = None; + // offset-distance and offset-rotate are grouped with offset-path. + if offset_path.is_some() { + loop { + if offset_distance.is_none() { + if let Ok(value) = input.try_parse(|i| LengthPercentage::parse(context, i)) { + offset_distance = Some(value); + } + } + + if offset_rotate.is_none() { + if let Ok(value) = input.try_parse(|i| OffsetRotate::parse(context, i)) { + offset_rotate = Some(value); + continue; + } + } + break; + } + } + + let offset_anchor = input.try_parse(|i| { + i.expect_delim('/')?; + PositionOrAuto::parse(context, i) + }).ok(); + + Ok(expanded! { + offset_position: offset_position.unwrap_or(OffsetPosition::normal()), + offset_path: offset_path.unwrap_or(OffsetPath::none()), + offset_distance: offset_distance.unwrap_or(LengthPercentage::zero()), + offset_rotate: offset_rotate.unwrap_or(OffsetRotate::auto()), + offset_anchor: offset_anchor.unwrap_or(PositionOrAuto::auto()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + if let Some(offset_position) = self.offset_position { + // The basic concept is: we must serialize offset-position or offset-path group. + // offset-path group means "offset-path offset-distance offset-rotate". + let must_serialize_path = *self.offset_path != OffsetPath::None + || (!self.offset_distance.is_zero() || !self.offset_rotate.is_auto()); + let position_is_default = matches!(offset_position, OffsetPosition::Normal); + if !position_is_default || !must_serialize_path { + offset_position.to_css(dest)?; + } + + if must_serialize_path { + if !position_is_default { + dest.write_char(' ')?; + } + self.offset_path.to_css(dest)?; + } + } else { + // If the pref is off, we always show offset-path. + self.offset_path.to_css(dest)?; + } + + if !self.offset_distance.is_zero() { + dest.write_char(' ')?; + self.offset_distance.to_css(dest)?; + } + + if !self.offset_rotate.is_auto() { + dest.write_char(' ')?; + self.offset_rotate.to_css(dest)?; + } + + if *self.offset_anchor != PositionOrAuto::auto() { + dest.write_str(" / ")?; + self.offset_anchor.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/column.mako.rs b/servo/components/style/properties/shorthands/column.mako.rs new file mode 100644 index 0000000000..3740775a7e --- /dev/null +++ b/servo/components/style/properties/shorthands/column.mako.rs @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="columns" + engines="gecko servo-2013" + sub_properties="column-width column-count" + servo_2013_pref="layout.columns.enabled", + spec="https://drafts.csswg.org/css-multicol/#propdef-columns"> + use crate::properties::longhands::{column_count, column_width}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut column_count = None; + let mut column_width = None; + let mut autos = 0; + + loop { + if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { + // Leave the options to None, 'auto' is the initial value. + autos += 1; + continue + } + + if column_count.is_none() { + if let Ok(value) = input.try_parse(|input| column_count::parse(context, input)) { + column_count = Some(value); + continue + } + } + + if column_width.is_none() { + if let Ok(value) = input.try_parse(|input| column_width::parse(context, input)) { + column_width = Some(value); + continue + } + } + + break + } + + let values = autos + column_count.iter().len() + column_width.iter().len(); + if values == 0 || values > 2 { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } else { + Ok(expanded! { + column_count: unwrap_or_initial!(column_count), + column_width: unwrap_or_initial!(column_width), + }) + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + if self.column_width.is_auto() { + return self.column_count.to_css(dest) + } + self.column_width.to_css(dest)?; + if !self.column_count.is_auto() { + dest.write_char(' ')?; + self.column_count.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="column-rule" + engines="gecko" + sub_properties="column-rule-width column-rule-style column-rule-color" + derive_serialize="True" + spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule" +> + use crate::properties::longhands::{column_rule_width, column_rule_style}; + use crate::properties::longhands::column_rule_color; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + % for name in "width style color".split(): + let mut column_rule_${name} = None; + % endfor + let mut any = false; + + loop { + % for name in "width style color".split(): + if column_rule_${name}.is_none() { + if let Ok(value) = input.try_parse(|input| + column_rule_${name}::parse(context, input)) { + column_rule_${name} = Some(value); + any = true; + continue + } + } + % endfor + + break + } + if any { + Ok(expanded! { + column_rule_width: unwrap_or_initial!(column_rule_width), + column_rule_style: unwrap_or_initial!(column_rule_style), + column_rule_color: unwrap_or_initial!(column_rule_color), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/font.mako.rs b/servo/components/style/properties/shorthands/font.mako.rs new file mode 100644 index 0000000000..17dcf9d926 --- /dev/null +++ b/servo/components/style/properties/shorthands/font.mako.rs @@ -0,0 +1,542 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import SYSTEM_FONT_LONGHANDS %> + +<%helpers:shorthand + name="font" + engines="gecko servo-2013 servo-2020" + sub_properties=" + font-style + font-variant-caps + font-weight + font-stretch + font-size + line-height + font-family + ${'font-size-adjust' if engine == 'gecko' else ''} + ${'font-kerning' if engine == 'gecko' else ''} + ${'font-optical-sizing' if engine == 'gecko' else ''} + ${'font-variant-alternates' if engine == 'gecko' else ''} + ${'font-variant-east-asian' if engine == 'gecko' else ''} + ${'font-variant-emoji' if engine == 'gecko' else ''} + ${'font-variant-ligatures' if engine == 'gecko' else ''} + ${'font-variant-numeric' if engine == 'gecko' else ''} + ${'font-variant-position' if engine == 'gecko' else ''} + ${'font-language-override' if engine == 'gecko' else ''} + ${'font-feature-settings' if engine == 'gecko' else ''} + ${'font-variation-settings' if engine == 'gecko' else ''} + " + derive_value_info="False" + spec="https://drafts.csswg.org/css-fonts-3/#propdef-font" +> + use crate::computed_values::font_variant_caps::T::SmallCaps; + use crate::parser::Parse; + use crate::properties::longhands::{font_family, font_style, font_size, font_weight, font_stretch}; + use crate::properties::longhands::font_variant_caps; + use crate::values::specified::font::LineHeight; + use crate::values::specified::{FontSize, FontWeight}; + use crate::values::specified::font::{FontStretch, FontStretchKeyword}; + #[cfg(feature = "gecko")] + use crate::values::specified::font::SystemFont; + + <% + gecko_sub_properties = "kerning language_override size_adjust \ + variant_alternates variant_east_asian \ + variant_emoji variant_ligatures \ + variant_numeric variant_position \ + feature_settings variation_settings \ + optical_sizing".split() + %> + % if engine == "gecko": + % for prop in gecko_sub_properties: + use crate::properties::longhands::font_${prop}; + % endfor + % endif + use self::font_family::SpecifiedValue as FontFamily; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut nb_normals = 0; + let mut style = None; + let mut variant_caps = None; + let mut weight = None; + let mut stretch = None; + let size; + % if engine == "gecko": + if let Ok(sys) = input.try_parse(|i| SystemFont::parse(context, i)) { + return Ok(expanded! { + % for name in SYSTEM_FONT_LONGHANDS: + ${name}: ${name}::SpecifiedValue::system_font(sys), + % endfor + line_height: LineHeight::normal(), + % for name in gecko_sub_properties + ["variant_caps"]: + font_${name}: font_${name}::get_initial_specified_value(), + % endfor + }) + } + % endif + loop { + // Special-case 'normal' because it is valid in each of + // font-style, font-weight, font-variant and font-stretch. + // Leaves the values to None, 'normal' is the initial value for each of them. + if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { + nb_normals += 1; + continue; + } + if style.is_none() { + if let Ok(value) = input.try_parse(|input| font_style::parse(context, input)) { + style = Some(value); + continue + } + } + if weight.is_none() { + if let Ok(value) = input.try_parse(|input| font_weight::parse(context, input)) { + weight = Some(value); + continue + } + } + if variant_caps.is_none() { + // The only variant-caps value allowed is small-caps (from CSS2); the added values + // defined by CSS Fonts 3 and later are not accepted. + // https://www.w3.org/TR/css-fonts-4/#font-prop + if input.try_parse(|input| input.expect_ident_matching("small-caps")).is_ok() { + variant_caps = Some(SmallCaps); + continue + } + } + if stretch.is_none() { + if let Ok(value) = input.try_parse(FontStretchKeyword::parse) { + stretch = Some(FontStretch::Keyword(value)); + continue + } + } + size = Some(FontSize::parse(context, input)?); + break + } + + let size = match size { + Some(s) => s, + None => { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + }; + + let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() { + Some(LineHeight::parse(context, input)?) + } else { + None + }; + + #[inline] + fn count<T>(opt: &Option<T>) -> u8 { + if opt.is_some() { 1 } else { 0 } + } + + if (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + + let family = FontFamily::parse(context, input)?; + Ok(expanded! { + % for name in "style weight stretch variant_caps".split(): + font_${name}: unwrap_or_initial!(font_${name}, ${name}), + % endfor + font_size: size, + line_height: line_height.unwrap_or(LineHeight::normal()), + font_family: family, + % if engine == "gecko": + % for name in gecko_sub_properties: + font_${name}: font_${name}::get_initial_specified_value(), + % endfor + % endif + }) + } + + % if engine == "gecko": + enum CheckSystemResult { + AllSystem(SystemFont), + SomeSystem, + None + } + % endif + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + % if engine == "gecko": + match self.check_system() { + CheckSystemResult::AllSystem(sys) => return sys.to_css(dest), + CheckSystemResult::SomeSystem => return Ok(()), + CheckSystemResult::None => {} + } + % endif + + % if engine == "gecko": + if let Some(v) = self.font_optical_sizing { + if v != &font_optical_sizing::get_initial_specified_value() { + return Ok(()); + } + } + if let Some(v) = self.font_variation_settings { + if v != &font_variation_settings::get_initial_specified_value() { + return Ok(()); + } + } + if let Some(v) = self.font_variant_emoji { + if v != &font_variant_emoji::get_initial_specified_value() { + return Ok(()); + } + } + + % for name in gecko_sub_properties: + % if name != "optical_sizing" and name != "variation_settings" and name != "variant_emoji": + if self.font_${name} != &font_${name}::get_initial_specified_value() { + return Ok(()); + } + % endif + % endfor + % endif + + // Only font-stretch keywords are allowed as part as the font + // shorthand. + let font_stretch = match *self.font_stretch { + FontStretch::Keyword(kw) => kw, + FontStretch::Stretch(percentage) => { + match FontStretchKeyword::from_percentage(percentage.0.get()) { + Some(kw) => kw, + None => return Ok(()), + } + } + FontStretch::System(..) => return Ok(()), + }; + + // The only variant-caps value allowed in the shorthand is small-caps (from CSS2); + // the added values defined by CSS Fonts 3 and later are not supported. + // https://www.w3.org/TR/css-fonts-4/#font-prop + if self.font_variant_caps != &font_variant_caps::get_initial_specified_value() && + *self.font_variant_caps != SmallCaps { + return Ok(()); + } + + % for name in "style variant_caps".split(): + if self.font_${name} != &font_${name}::get_initial_specified_value() { + self.font_${name}.to_css(dest)?; + dest.write_char(' ')?; + } + % endfor + + // The initial specified font-weight value of 'normal' computes as a number (400), + // not to the keyword, so we need to check for that as well in order to properly + // serialize the computed style. + if self.font_weight != &FontWeight::normal() && + self.font_weight != &FontWeight::from_gecko_keyword(400) { + self.font_weight.to_css(dest)?; + dest.write_char(' ')?; + } + + if font_stretch != FontStretchKeyword::Normal { + font_stretch.to_css(dest)?; + dest.write_char(' ')?; + } + + self.font_size.to_css(dest)?; + + if *self.line_height != LineHeight::normal() { + dest.write_str(" / ")?; + self.line_height.to_css(dest)?; + } + + dest.write_char(' ')?; + self.font_family.to_css(dest)?; + + Ok(()) + } + } + + impl<'a> LonghandsToSerialize<'a> { + % if engine == "gecko": + /// Check if some or all members are system fonts + fn check_system(&self) -> CheckSystemResult { + let mut sys = None; + let mut all = true; + + % for prop in SYSTEM_FONT_LONGHANDS: + % if prop == "font_optical_sizing" or prop == "font_variation_settings": + if let Some(value) = self.${prop} { + % else: + { + let value = self.${prop}; + % endif + match value.get_system() { + Some(s) => { + debug_assert!(sys.is_none() || s == sys.unwrap()); + sys = Some(s); + } + None => { + all = false; + } + } + } + % endfor + if self.line_height != &LineHeight::normal() { + all = false + } + if all { + CheckSystemResult::AllSystem(sys.unwrap()) + } else if sys.is_some() { + CheckSystemResult::SomeSystem + } else { + CheckSystemResult::None + } + } + % endif + } + + <% + subprops_for_value_info = ["font_style", "font_weight", "font_stretch", + "font_variant_caps", "font_size", "font_family"] + subprops_for_value_info = [ + "<longhands::{}::SpecifiedValue as SpecifiedValueInfo>".format(p) + for p in subprops_for_value_info + ] + %> + impl SpecifiedValueInfo for Longhands { + const SUPPORTED_TYPES: u8 = 0 + % for p in subprops_for_value_info: + | ${p}::SUPPORTED_TYPES + % endfor + ; + + fn collect_completion_keywords(f: KeywordsCollectFn) { + % for p in subprops_for_value_info: + ${p}::collect_completion_keywords(f); + % endfor + <SystemFont as SpecifiedValueInfo>::collect_completion_keywords(f); + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="font-variant" + engines="gecko servo-2013" + flags="SHORTHAND_IN_GETCS" + sub_properties="font-variant-caps + ${'font-variant-alternates' if engine == 'gecko' else ''} + ${'font-variant-east-asian' if engine == 'gecko' else ''} + ${'font-variant-emoji' if engine == 'gecko' else ''} + ${'font-variant-ligatures' if engine == 'gecko' else ''} + ${'font-variant-numeric' if engine == 'gecko' else ''} + ${'font-variant-position' if engine == 'gecko' else ''}" + spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> +% if engine == 'gecko': + <% sub_properties = "ligatures caps alternates numeric east_asian position emoji".split() %> +% else: + <% sub_properties = ["caps"] %> +% endif + +% for prop in sub_properties: + use crate::properties::longhands::font_variant_${prop}; +% endfor + #[allow(unused_imports)] + use crate::values::specified::FontVariantLigatures; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + % for prop in sub_properties: + let mut ${prop} = None; + % endfor + + if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { + // Leave the values to None, 'normal' is the initial value for all the sub properties. + } else if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + // The 'none' value sets 'font-variant-ligatures' to 'none' and resets all other sub properties + // to their initial value. + % if engine == "gecko": + ligatures = Some(FontVariantLigatures::NONE); + % endif + } else { + let mut has_custom_value: bool = false; + loop { + if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() || + input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + % for prop in sub_properties: + if ${prop}.is_none() { + if let Ok(value) = input.try_parse(|i| font_variant_${prop}::parse(context, i)) { + has_custom_value = true; + ${prop} = Some(value); + continue + } + } + % endfor + + break + } + + if !has_custom_value { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + Ok(expanded! { + % for prop in sub_properties: + font_variant_${prop}: unwrap_or_initial!(font_variant_${prop}, ${prop}), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + #[allow(unused_assignments)] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + + let has_none_ligatures = + % if engine == "gecko": + self.font_variant_ligatures == &FontVariantLigatures::NONE; + % else: + false; + % endif + + const TOTAL_SUBPROPS: usize = ${len(sub_properties)}; + let mut nb_normals = 0; + % for prop in sub_properties: + % if prop == "emoji": + if let Some(value) = self.font_variant_${prop} { + % else: + { + let value = self.font_variant_${prop}; + % endif + if value == &font_variant_${prop}::get_initial_specified_value() { + nb_normals += 1; + } + } + % if prop == "emoji": + else { + // The property was disabled, so we count it as 'normal' for the purpose + // of deciding how the shorthand can be serialized. + nb_normals += 1; + } + % endif + % endfor + + + if nb_normals > 0 && nb_normals == TOTAL_SUBPROPS { + dest.write_str("normal")?; + } else if has_none_ligatures { + if nb_normals == TOTAL_SUBPROPS - 1 { + // Serialize to 'none' if 'font-variant-ligatures' is set to 'none' and all other + // font feature properties are reset to their initial value. + dest.write_str("none")?; + } else { + return Ok(()) + } + } else { + let mut has_any = false; + % for prop in sub_properties: + % if prop == "emoji": + if let Some(value) = self.font_variant_${prop} { + % else: + { + let value = self.font_variant_${prop}; + % endif + if value != &font_variant_${prop}::get_initial_specified_value() { + if has_any { + dest.write_char(' ')?; + } + has_any = true; + value.to_css(dest)?; + } + } + % endfor + } + + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="font-synthesis" + engines="gecko" + flags="SHORTHAND_IN_GETCS" + sub_properties="font-synthesis-weight font-synthesis-style font-synthesis-small-caps font-synthesis-position" + derive_value_info="False" + spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> + <% sub_properties = ["weight", "style", "small_caps", "position"] %> + + use crate::values::specified::FontSynthesis; + + pub fn parse_value<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + % for prop in sub_properties: + let mut ${prop} = FontSynthesis::None; + % endfor + + if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + // Leave all the individual values as None + } else { + let mut has_custom_value = false; + while !input.is_exhausted() { + try_match_ident_ignore_ascii_case! { input, + % for prop in sub_properties: + "${prop.replace('_', '-')}" if ${prop} == FontSynthesis::None => { + has_custom_value = true; + ${prop} = FontSynthesis::Auto; + continue; + }, + % endfor + } + } + if !has_custom_value { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } + + Ok(expanded! { + % for prop in sub_properties: + font_synthesis_${prop}: ${prop}, + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let mut has_any = false; + + % for prop in sub_properties: + if self.font_synthesis_${prop} == &FontSynthesis::Auto { + if has_any { + dest.write_char(' ')?; + } + has_any = true; + dest.write_str("${prop.replace('_', '-')}")?; + } + % endfor + + if !has_any { + dest.write_str("none")?; + } + + Ok(()) + } + } + + // The shorthand takes the sub-property names of the longhands, and not the + // 'auto' keyword like they do, so we can't automatically derive this. + impl SpecifiedValueInfo for Longhands { + fn collect_completion_keywords(f: KeywordsCollectFn) { + f(&[ + "none", + % for prop in sub_properties: + "${prop.replace('_', '-')}", + % endfor + ]); + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/inherited_svg.mako.rs b/servo/components/style/properties/shorthands/inherited_svg.mako.rs new file mode 100644 index 0000000000..f29e78a69f --- /dev/null +++ b/servo/components/style/properties/shorthands/inherited_svg.mako.rs @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand + name="marker" + engines="gecko" + sub_properties="marker-start marker-end marker-mid" + spec="https://svgwg.org/svg2-draft/painting.html#MarkerShorthand" +> + use crate::values::specified::url::UrlOrNone; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::parser::Parse; + let url = UrlOrNone::parse(context, input)?; + + Ok(expanded! { + marker_start: url.clone(), + marker_mid: url.clone(), + marker_end: url, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + if self.marker_start == self.marker_mid && self.marker_mid == self.marker_end { + self.marker_start.to_css(dest) + } else { + Ok(()) + } + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/inherited_text.mako.rs b/servo/components/style/properties/shorthands/inherited_text.mako.rs new file mode 100644 index 0000000000..d470553e42 --- /dev/null +++ b/servo/components/style/properties/shorthands/inherited_text.mako.rs @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand + name="text-emphasis" + engines="gecko" + sub_properties="text-emphasis-style text-emphasis-color" + derive_serialize="True" + spec="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-property" +> + use crate::properties::longhands::{text_emphasis_color, text_emphasis_style}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut color = None; + let mut style = None; + + loop { + if color.is_none() { + if let Ok(value) = input.try_parse(|input| text_emphasis_color::parse(context, input)) { + color = Some(value); + continue + } + } + if style.is_none() { + if let Ok(value) = input.try_parse(|input| text_emphasis_style::parse(context, input)) { + style = Some(value); + continue + } + } + break + } + if color.is_some() || style.is_some() { + Ok(expanded! { + text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color), + text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="text-wrap" + engines="gecko" + sub_properties="text-wrap-mode text-wrap-style" + spec="https://www.w3.org/TR/css-text-4/#text-wrap" +> + use crate::properties::longhands::{text_wrap_mode, text_wrap_style}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut mode = None; + let mut style = None; + + loop { + if mode.is_none() { + if let Ok(value) = input.try_parse(|input| text_wrap_mode::parse(context, input)) { + mode = Some(value); + continue + } + } + if style.is_none() { + if let Ok(value) = input.try_parse(|input| text_wrap_style::parse(context, input)) { + style = Some(value); + continue + } + } + break + } + if mode.is_some() || style.is_some() { + Ok(expanded! { + text_wrap_mode: unwrap_or_initial!(text_wrap_mode, mode), + text_wrap_style: unwrap_or_initial!(text_wrap_style, style), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use text_wrap_mode::computed_value::T as Mode; + use text_wrap_style::computed_value::T as Style; + + if matches!(self.text_wrap_style, None | Some(&Style::Auto)) { + return self.text_wrap_mode.to_css(dest); + } + + if *self.text_wrap_mode != Mode::Wrap { + self.text_wrap_mode.to_css(dest)?; + dest.write_char(' ')?; + } + + self.text_wrap_style.to_css(dest) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="white-space" + engines="gecko" + sub_properties="text-wrap-mode white-space-collapse" + spec="https://www.w3.org/TR/css-text-4/#white-space-property" +> + use crate::properties::longhands::{text_wrap_mode, white_space_collapse}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + use white_space_collapse::computed_value::T as Collapse; + use text_wrap_mode::computed_value::T as Wrap; + + fn parse_special_shorthands<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Longhands, ParseError<'i>> { + let (mode, collapse) = try_match_ident_ignore_ascii_case! { input, + "normal" => (Wrap::Wrap, Collapse::Collapse), + "pre" => (Wrap::Nowrap, Collapse::Preserve), + "pre-wrap" => (Wrap::Wrap, Collapse::Preserve), + "pre-line" => (Wrap::Wrap, Collapse::PreserveBreaks), + // TODO: deprecate/remove -moz-pre-space; the white-space-collapse: preserve-spaces value + // should serve this purpose? + "-moz-pre-space" => (Wrap::Wrap, Collapse::PreserveSpaces), + }; + Ok(expanded! { + text_wrap_mode: mode, + white_space_collapse: collapse, + }) + } + + if let Ok(result) = input.try_parse(parse_special_shorthands) { + return Ok(result); + } + + let mut wrap = None; + let mut collapse = None; + + loop { + if wrap.is_none() { + if let Ok(value) = input.try_parse(|input| text_wrap_mode::parse(context, input)) { + wrap = Some(value); + continue + } + } + if collapse.is_none() { + if let Ok(value) = input.try_parse(|input| white_space_collapse::parse(context, input)) { + collapse = Some(value); + continue + } + } + break + } + + if wrap.is_some() || collapse.is_some() { + Ok(expanded! { + text_wrap_mode: unwrap_or_initial!(text_wrap_mode, wrap), + white_space_collapse: unwrap_or_initial!(white_space_collapse, collapse), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use white_space_collapse::computed_value::T as Collapse; + use text_wrap_mode::computed_value::T as Wrap; + + match *self.text_wrap_mode { + Wrap::Wrap => { + match *self.white_space_collapse { + Collapse::Collapse => return dest.write_str("normal"), + Collapse::Preserve => return dest.write_str("pre-wrap"), + Collapse::PreserveBreaks => return dest.write_str("pre-line"), + Collapse::PreserveSpaces => return dest.write_str("-moz-pre-space"), + _ => (), + } + }, + Wrap::Nowrap => { + if let Collapse::Preserve = *self.white_space_collapse { + return dest.write_str("pre"); + } + }, + } + + let mut has_value = false; + if *self.white_space_collapse != Collapse::Collapse { + self.white_space_collapse.to_css(dest)?; + has_value = true; + } + + if *self.text_wrap_mode != Wrap::Wrap { + if has_value { + dest.write_char(' ')?; + } + self.text_wrap_mode.to_css(dest)?; + } + + Ok(()) + } + } +</%helpers:shorthand> + +// CSS Compatibility +// https://compat.spec.whatwg.org/ +<%helpers:shorthand name="-webkit-text-stroke" + engines="gecko" + sub_properties="-webkit-text-stroke-width + -webkit-text-stroke-color" + derive_serialize="True" + spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke"> + use crate::properties::longhands::{_webkit_text_stroke_color, _webkit_text_stroke_width}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut color = None; + let mut width = None; + loop { + if color.is_none() { + if let Ok(value) = input.try_parse(|input| _webkit_text_stroke_color::parse(context, input)) { + color = Some(value); + continue + } + } + + if width.is_none() { + if let Ok(value) = input.try_parse(|input| _webkit_text_stroke_width::parse(context, input)) { + width = Some(value); + continue + } + } + break + } + + if color.is_some() || width.is_some() { + Ok(expanded! { + _webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color), + _webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/list.mako.rs b/servo/components/style/properties/shorthands/list.mako.rs new file mode 100644 index 0000000000..2e234e3d8f --- /dev/null +++ b/servo/components/style/properties/shorthands/list.mako.rs @@ -0,0 +1,137 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="list-style" + engines="gecko servo-2013 servo-2020" + sub_properties="list-style-position list-style-image list-style-type" + spec="https://drafts.csswg.org/css-lists/#propdef-list-style"> + use crate::properties::longhands::{list_style_image, list_style_position, list_style_type}; + use crate::values::specified::Image; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + // `none` is ambiguous until we've finished parsing the shorthands, so we count the number + // of times we see it. + let mut nones = 0u8; + let (mut image, mut position, mut list_style_type, mut any) = (None, None, None, false); + loop { + if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + nones = nones + 1; + if nones > 2 { + return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("none".into()))) + } + any = true; + continue + } + + if image.is_none() { + if let Ok(value) = input.try_parse(|input| list_style_image::parse(context, input)) { + image = Some(value); + any = true; + continue + } + } + + if position.is_none() { + if let Ok(value) = input.try_parse(|input| list_style_position::parse(context, input)) { + position = Some(value); + any = true; + continue + } + } + + // list-style-type must be checked the last, because it accepts + // arbitrary identifier for custom counter style, and thus may + // affect values of list-style-position. + if list_style_type.is_none() { + if let Ok(value) = input.try_parse(|input| list_style_type::parse(context, input)) { + list_style_type = Some(value); + any = true; + continue + } + } + break + } + + let position = unwrap_or_initial!(list_style_position, position); + + // If there are two `none`s, then we can't have a type or image; if there is one `none`, + // then we can't have both a type *and* an image; if there is no `none` then we're fine as + // long as we parsed something. + use self::list_style_type::SpecifiedValue as ListStyleType; + match (any, nones, list_style_type, image) { + (true, 2, None, None) => { + Ok(expanded! { + list_style_position: position, + list_style_image: Image::None, + list_style_type: ListStyleType::None, + }) + } + (true, 1, None, Some(image)) => { + Ok(expanded! { + list_style_position: position, + list_style_image: image, + list_style_type: ListStyleType::None, + }) + } + (true, 1, Some(list_style_type), None) => { + Ok(expanded! { + list_style_position: position, + list_style_image: Image::None, + list_style_type: list_style_type, + }) + } + (true, 1, None, None) => { + Ok(expanded! { + list_style_position: position, + list_style_image: Image::None, + list_style_type: ListStyleType::None, + }) + } + (true, 0, list_style_type, image) => { + Ok(expanded! { + list_style_position: position, + list_style_image: unwrap_or_initial!(list_style_image, image), + list_style_type: unwrap_or_initial!(list_style_type), + }) + } + _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use longhands::list_style_position::SpecifiedValue as ListStylePosition; + use longhands::list_style_type::SpecifiedValue as ListStyleType; + use longhands::list_style_image::SpecifiedValue as ListStyleImage; + let mut have_one_non_initial_value = false; + if self.list_style_position != &ListStylePosition::Outside { + self.list_style_position.to_css(dest)?; + have_one_non_initial_value = true; + } + if self.list_style_image != &ListStyleImage::None { + if have_one_non_initial_value { + dest.write_char(' ')?; + } + self.list_style_image.to_css(dest)?; + have_one_non_initial_value = true; + } + if self.list_style_type != &ListStyleType::disc() { + if have_one_non_initial_value { + dest.write_char(' ')?; + } + self.list_style_type.to_css(dest)?; + have_one_non_initial_value = true; + } + if !have_one_non_initial_value { + self.list_style_position.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/margin.mako.rs b/servo/components/style/properties/shorthands/margin.mako.rs new file mode 100644 index 0000000000..6b5bf7e467 --- /dev/null +++ b/servo/components/style/properties/shorthands/margin.mako.rs @@ -0,0 +1,60 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import DEFAULT_RULES_AND_PAGE %> + +${helpers.four_sides_shorthand( + "margin", + "margin-%s", + "specified::LengthPercentageOrAuto::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-box/#propdef-margin", + rule_types_allowed=DEFAULT_RULES_AND_PAGE, + allow_quirks="Yes", +)} + +${helpers.two_properties_shorthand( + "margin-block", + "margin-block-start", + "margin-block-end", + "specified::LengthPercentageOrAuto::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-logical/#propdef-margin-block" +)} + +${helpers.two_properties_shorthand( + "margin-inline", + "margin-inline-start", + "margin-inline-end", + "specified::LengthPercentageOrAuto::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-logical/#propdef-margin-inline" +)} + +${helpers.four_sides_shorthand( + "scroll-margin", + "scroll-margin-%s", + "specified::Length::parse", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin", +)} + +${helpers.two_properties_shorthand( + "scroll-margin-block", + "scroll-margin-block-start", + "scroll-margin-block-end", + "specified::Length::parse", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin-block", +)} + +${helpers.two_properties_shorthand( + "scroll-margin-inline", + "scroll-margin-inline-start", + "scroll-margin-inline-end", + "specified::Length::parse", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin-inline", +)} diff --git a/servo/components/style/properties/shorthands/outline.mako.rs b/servo/components/style/properties/shorthands/outline.mako.rs new file mode 100644 index 0000000000..6ee8ed22c9 --- /dev/null +++ b/servo/components/style/properties/shorthands/outline.mako.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="outline" + engines="gecko servo-2013" + sub_properties="outline-color outline-style outline-width" + spec="https://drafts.csswg.org/css-ui/#propdef-outline"> + use crate::properties::longhands::{outline_color, outline_width, outline_style}; + use crate::values::specified; + use crate::parser::Parse; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let _unused = context; + let mut color = None; + let mut style = None; + let mut width = None; + let mut any = false; + loop { + if color.is_none() { + if let Ok(value) = input.try_parse(|i| specified::Color::parse(context, i)) { + color = Some(value); + any = true; + continue + } + } + if style.is_none() { + if let Ok(value) = input.try_parse(|input| outline_style::parse(context, input)) { + style = Some(value); + any = true; + continue + } + } + if width.is_none() { + if let Ok(value) = input.try_parse(|input| outline_width::parse(context, input)) { + width = Some(value); + any = true; + continue + } + } + break + } + if any { + Ok(expanded! { + outline_color: unwrap_or_initial!(outline_color, color), + outline_style: unwrap_or_initial!(outline_style, style), + outline_width: unwrap_or_initial!(outline_width, width), + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let mut wrote_value = false; + + % for name in "color style width".split(): + if *self.outline_${name} != outline_${name}::get_initial_specified_value() { + if wrote_value { + dest.write_char(' ')?; + } + self.outline_${name}.to_css(dest)?; + wrote_value = true; + } + % endfor + + if !wrote_value { + self.outline_style.to_css(dest)?; + } + + Ok(()) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/padding.mako.rs b/servo/components/style/properties/shorthands/padding.mako.rs new file mode 100644 index 0000000000..11ddfed3b1 --- /dev/null +++ b/servo/components/style/properties/shorthands/padding.mako.rs @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +${helpers.four_sides_shorthand( + "padding", + "padding-%s", + "specified::NonNegativeLengthPercentage::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-box-3/#propdef-padding", + allow_quirks="Yes", +)} + +${helpers.two_properties_shorthand( + "padding-block", + "padding-block-start", + "padding-block-end", + "specified::NonNegativeLengthPercentage::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-logical/#propdef-padding-block" +)} + +${helpers.two_properties_shorthand( + "padding-inline", + "padding-inline-start", + "padding-inline-end", + "specified::NonNegativeLengthPercentage::parse", + engines="gecko servo-2013 servo-2020", + spec="https://drafts.csswg.org/css-logical/#propdef-padding-inline" +)} + +${helpers.four_sides_shorthand( + "scroll-padding", + "scroll-padding-%s", + "specified::NonNegativeLengthPercentageOrAuto::parse", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding" +)} + +${helpers.two_properties_shorthand( + "scroll-padding-block", + "scroll-padding-block-start", + "scroll-padding-block-end", + "specified::NonNegativeLengthPercentageOrAuto::parse", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-block" +)} + +${helpers.two_properties_shorthand( + "scroll-padding-inline", + "scroll-padding-inline-start", + "scroll-padding-inline-end", + "specified::NonNegativeLengthPercentageOrAuto::parse", + engines="gecko", + spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-inline" +)} diff --git a/servo/components/style/properties/shorthands/position.mako.rs b/servo/components/style/properties/shorthands/position.mako.rs new file mode 100644 index 0000000000..ed7df5e27a --- /dev/null +++ b/servo/components/style/properties/shorthands/position.mako.rs @@ -0,0 +1,891 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="flex-flow" + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + sub_properties="flex-direction flex-wrap" + extra_prefixes="webkit" + spec="https://drafts.csswg.org/css-flexbox/#flex-flow-property"> + use crate::properties::longhands::{flex_direction, flex_wrap}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut direction = None; + let mut wrap = None; + loop { + if direction.is_none() { + if let Ok(value) = input.try_parse(|input| flex_direction::parse(context, input)) { + direction = Some(value); + continue + } + } + if wrap.is_none() { + if let Ok(value) = input.try_parse(|input| flex_wrap::parse(context, input)) { + wrap = Some(value); + continue + } + } + break + } + + if direction.is_none() && wrap.is_none() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + Ok(expanded! { + flex_direction: unwrap_or_initial!(flex_direction, direction), + flex_wrap: unwrap_or_initial!(flex_wrap, wrap), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + if *self.flex_direction == flex_direction::get_initial_specified_value() && + *self.flex_wrap != flex_wrap::get_initial_specified_value() { + return self.flex_wrap.to_css(dest) + } + self.flex_direction.to_css(dest)?; + if *self.flex_wrap != flex_wrap::get_initial_specified_value() { + dest.write_char(' ')?; + self.flex_wrap.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="flex" + engines="gecko servo-2013 servo-2020", + servo_2020_pref="layout.flexbox.enabled", + sub_properties="flex-grow flex-shrink flex-basis" + extra_prefixes="webkit" + derive_serialize="True" + spec="https://drafts.csswg.org/css-flexbox/#flex-property"> + use crate::parser::Parse; + use crate::values::specified::NonNegativeNumber; + use crate::properties::longhands::flex_basis::SpecifiedValue as FlexBasis; + + fn parse_flexibility<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(NonNegativeNumber, Option<NonNegativeNumber>),ParseError<'i>> { + let grow = NonNegativeNumber::parse(context, input)?; + let shrink = input.try_parse(|i| NonNegativeNumber::parse(context, i)).ok(); + Ok((grow, shrink)) + } + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut grow = None; + let mut shrink = None; + let mut basis = None; + + if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(expanded! { + flex_grow: NonNegativeNumber::new(0.0), + flex_shrink: NonNegativeNumber::new(0.0), + flex_basis: FlexBasis::auto(), + }) + } + loop { + if grow.is_none() { + if let Ok((flex_grow, flex_shrink)) = input.try_parse(|i| parse_flexibility(context, i)) { + grow = Some(flex_grow); + shrink = flex_shrink; + continue + } + } + if basis.is_none() { + if let Ok(value) = input.try_parse(|input| FlexBasis::parse(context, input)) { + basis = Some(value); + continue + } + } + break + } + + if grow.is_none() && basis.is_none() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + Ok(expanded! { + flex_grow: grow.unwrap_or(NonNegativeNumber::new(1.0)), + flex_shrink: shrink.unwrap_or(NonNegativeNumber::new(1.0)), + // Per spec, this should be SpecifiedValue::zero(), but all + // browsers currently agree on using `0%`. This is a spec + // change which hasn't been adopted by browsers: + // https://github.com/w3c/csswg-drafts/commit/2c446befdf0f686217905bdd7c92409f6bd3921b + flex_basis: basis.unwrap_or(FlexBasis::zero_percent()), + }) + } +</%helpers:shorthand> + +<%helpers:shorthand + name="gap" + engines="gecko" + aliases="grid-gap" + sub_properties="row-gap column-gap" + spec="https://drafts.csswg.org/css-align-3/#gap-shorthand" +> + use crate::properties::longhands::{row_gap, column_gap}; + + pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) + -> Result<Longhands, ParseError<'i>> { + let r_gap = row_gap::parse(context, input)?; + let c_gap = input.try_parse(|input| column_gap::parse(context, input)).unwrap_or(r_gap.clone()); + + Ok(expanded! { + row_gap: r_gap, + column_gap: c_gap, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + if self.row_gap == self.column_gap { + self.row_gap.to_css(dest) + } else { + self.row_gap.to_css(dest)?; + dest.write_char(' ')?; + self.column_gap.to_css(dest) + } + } + } + +</%helpers:shorthand> + +% for kind in ["row", "column"]: +<%helpers:shorthand + name="grid-${kind}" + sub_properties="grid-${kind}-start grid-${kind}-end" + engines="gecko", + spec="https://drafts.csswg.org/css-grid/#propdef-grid-${kind}" +> + use crate::values::specified::GridLine; + use crate::parser::Parse; + use crate::Zero; + + // NOTE: Since both the shorthands have the same code, we should (re-)use code from one to implement + // the other. This might not be a big deal for now, but we should consider looking into this in the future + // to limit the amount of code generated. + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let start = input.try_parse(|i| GridLine::parse(context, i))?; + let end = if input.try_parse(|i| i.expect_delim('/')).is_ok() { + GridLine::parse(context, input)? + } else { + let mut line = GridLine::auto(); + if start.line_num.is_zero() && !start.is_span { + line.ident = start.ident.clone(); // ident from start value should be taken + } + + line + }; + + Ok(expanded! { + grid_${kind}_start: start, + grid_${kind}_end: end, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + // Return the shortest possible serialization of the `grid-${kind}-[start/end]` values. + // This function exploits the opportunities to omit the end value per this spec text: + // + // https://drafts.csswg.org/css-grid/#propdef-grid-column + // "When the second value is omitted, if the first value is a <custom-ident>, + // the grid-row-end/grid-column-end longhand is also set to that <custom-ident>; + // otherwise, it is set to auto." + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.grid_${kind}_start.to_css(dest)?; + if self.grid_${kind}_start.can_omit(self.grid_${kind}_end) { + return Ok(()); // the end value is redundant + } + dest.write_str(" / ")?; + self.grid_${kind}_end.to_css(dest) + } + } +</%helpers:shorthand> +% endfor + +<%helpers:shorthand + name="grid-area" + engines="gecko" + sub_properties="grid-row-start grid-row-end grid-column-start grid-column-end" + spec="https://drafts.csswg.org/css-grid/#propdef-grid-area" +> + use crate::values::specified::GridLine; + use crate::parser::Parse; + use crate::Zero; + + // The code is the same as `grid-{row,column}` except that this can have four values at most. + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + fn line_with_ident_from(other: &GridLine) -> GridLine { + let mut this = GridLine::auto(); + if other.line_num.is_zero() && !other.is_span { + this.ident = other.ident.clone(); + } + + this + } + + let row_start = input.try_parse(|i| GridLine::parse(context, i))?; + let (column_start, row_end, column_end) = if input.try_parse(|i| i.expect_delim('/')).is_ok() { + let column_start = GridLine::parse(context, input)?; + let (row_end, column_end) = if input.try_parse(|i| i.expect_delim('/')).is_ok() { + let row_end = GridLine::parse(context, input)?; + let column_end = if input.try_parse(|i| i.expect_delim('/')).is_ok() { + GridLine::parse(context, input)? + } else { // grid-column-end has not been given + line_with_ident_from(&column_start) + }; + + (row_end, column_end) + } else { // grid-row-start and grid-column-start has been given + let row_end = line_with_ident_from(&row_start); + let column_end = line_with_ident_from(&column_start); + (row_end, column_end) + }; + + (column_start, row_end, column_end) + } else { // only grid-row-start is given + let line = line_with_ident_from(&row_start); + (line.clone(), line.clone(), line) + }; + + Ok(expanded! { + grid_row_start: row_start, + grid_row_end: row_end, + grid_column_start: column_start, + grid_column_end: column_end, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + // Return the shortest possible serialization of the `grid-[column/row]-[start/end]` values. + // This function exploits the opportunities to omit trailing values per this spec text: + // + // https://drafts.csswg.org/css-grid/#propdef-grid-area + // "If four <grid-line> values are specified, grid-row-start is set to the first value, + // grid-column-start is set to the second value, grid-row-end is set to the third value, + // and grid-column-end is set to the fourth value. + // + // When grid-column-end is omitted, if grid-column-start is a <custom-ident>, + // grid-column-end is set to that <custom-ident>; otherwise, it is set to auto. + // + // When grid-row-end is omitted, if grid-row-start is a <custom-ident>, grid-row-end is + // set to that <custom-ident>; otherwise, it is set to auto. + // + // When grid-column-start is omitted, if grid-row-start is a <custom-ident>, all four + // longhands are set to that value. Otherwise, it is set to auto." + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.grid_row_start.to_css(dest)?; + let mut trailing_values = 3; + if self.grid_column_start.can_omit(self.grid_column_end) { + trailing_values -= 1; + if self.grid_row_start.can_omit(self.grid_row_end) { + trailing_values -= 1; + if self.grid_row_start.can_omit(self.grid_column_start) { + trailing_values -= 1; + } + } + } + let values = [&self.grid_column_start, &self.grid_row_end, &self.grid_column_end]; + for value in values.iter().take(trailing_values) { + dest.write_str(" / ")?; + value.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="grid-template" + engines="gecko" + sub_properties="grid-template-rows grid-template-columns grid-template-areas" + spec="https://drafts.csswg.org/css-grid/#propdef-grid-template" +> + use crate::parser::Parse; + use servo_arc::Arc; + use crate::values::generics::grid::{TrackSize, TrackList}; + use crate::values::generics::grid::{TrackListValue, concat_serialize_idents}; + use crate::values::specified::{GridTemplateComponent, GenericGridTemplateComponent}; + use crate::values::specified::grid::parse_line_names; + use crate::values::specified::position::{GridTemplateAreas, TemplateAreasParser, TemplateAreasArc}; + + /// Parsing for `<grid-template>` shorthand (also used by `grid` shorthand). + pub fn parse_grid_template<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(GridTemplateComponent, GridTemplateComponent, GridTemplateAreas), ParseError<'i>> { + // Other shorthand sub properties also parse the `none` keyword and this shorthand + // should know after this keyword there is nothing to parse. Otherwise it gets + // confused and rejects the sub properties that contains `none`. + <% keywords = { + "none": "GenericGridTemplateComponent::None", + } + %> + % for keyword, rust_type in keywords.items(): + if let Ok(x) = input.try_parse(|i| { + if i.try_parse(|i| i.expect_ident_matching("${keyword}")).is_ok() { + if !i.is_exhausted() { + return Err(()); + } + return Ok((${rust_type}, ${rust_type}, GridTemplateAreas::None)); + } + Err(()) + }) { + return Ok(x); + } + % endfor + + let first_line_names = input.try_parse(parse_line_names).unwrap_or_default(); + let mut areas_parser = TemplateAreasParser::default(); + if areas_parser.try_parse_string(input).is_ok() { + let mut values = vec![]; + let mut line_names = vec![]; + line_names.push(first_line_names); + loop { + let size = input.try_parse(|i| TrackSize::parse(context, i)).unwrap_or_default(); + values.push(TrackListValue::TrackSize(size)); + let mut names = input.try_parse(parse_line_names).unwrap_or_default(); + let more_names = input.try_parse(parse_line_names); + + match areas_parser.try_parse_string(input) { + Ok(()) => { + if let Ok(v) = more_names { + // We got `[names] [more_names] "string"` - merge the two name lists. + let mut names_vec = names.into_vec(); + names_vec.extend(v.into_iter()); + names = names_vec.into(); + } + line_names.push(names); + }, + Err(e) => { + if more_names.is_ok() { + // We've parsed `"string" [names] [more_names]` but then failed to parse another `"string"`. + // The grammar doesn't allow two trailing `<line-names>` so this is an invalid value. + return Err(e); + } + // only the named area determines whether we should bail out + line_names.push(names); + break + }, + }; + } + + if line_names.len() == values.len() { + // should be one longer than track sizes + line_names.push(Default::default()); + } + + let template_areas = areas_parser.finish() + .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?; + let template_rows = TrackList { + values: values.into(), + line_names: line_names.into(), + auto_repeat_index: std::usize::MAX, + }; + + let template_cols = if input.try_parse(|i| i.expect_delim('/')).is_ok() { + let value = GridTemplateComponent::parse_without_none(context, input)?; + if let GenericGridTemplateComponent::TrackList(ref list) = value { + if !list.is_explicit() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + value + } else { + GridTemplateComponent::default() + }; + + Ok(( + GenericGridTemplateComponent::TrackList(Box::new(template_rows)), + template_cols, + GridTemplateAreas::Areas(TemplateAreasArc(Arc::new(template_areas))) + )) + } else { + let mut template_rows = GridTemplateComponent::parse(context, input)?; + if let GenericGridTemplateComponent::TrackList(ref mut list) = template_rows { + // Fist line names are parsed already and it shouldn't be parsed again. + // If line names are not empty, that means given property value is not acceptable + if list.line_names[0].is_empty() { + list.line_names[0] = first_line_names; // won't panic + } else { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + } + + input.expect_delim('/')?; + Ok((template_rows, GridTemplateComponent::parse(context, input)?, GridTemplateAreas::None)) + } + } + + #[inline] + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let (rows, columns, areas) = parse_grid_template(context, input)?; + Ok(expanded! { + grid_template_rows: rows, + grid_template_columns: columns, + grid_template_areas: areas, + }) + } + + /// Serialization for `<grid-template>` shorthand (also used by `grid` shorthand). + pub fn serialize_grid_template<W>( + template_rows: &GridTemplateComponent, + template_columns: &GridTemplateComponent, + template_areas: &GridTemplateAreas, + dest: &mut CssWriter<W>, + ) -> fmt::Result + where + W: Write { + match *template_areas { + GridTemplateAreas::None => { + if template_rows.is_initial() && template_columns.is_initial() { + return GridTemplateComponent::default().to_css(dest); + } + template_rows.to_css(dest)?; + dest.write_str(" / ")?; + template_columns.to_css(dest) + }, + GridTemplateAreas::Areas(ref areas) => { + // The length of template-area and template-rows values should be equal. + if areas.0.strings.len() != template_rows.track_list_len() { + return Ok(()); + } + + let track_list = match *template_rows { + GenericGridTemplateComponent::TrackList(ref list) => { + // We should fail if there is a `repeat` function. + // `grid` and `grid-template` shorthands doesn't accept + // that. Only longhand accepts. + if !list.is_explicit() { + return Ok(()); + } + list + }, + // Others template components shouldn't exist with normal shorthand values. + // But if we need to serialize a group of longhand sub-properties for + // the shorthand, we should be able to return empty string instead of crashing. + _ => return Ok(()), + }; + + // We need to check some values that longhand accepts but shorthands don't. + match *template_columns { + // We should fail if there is a `repeat` function. `grid` and + // `grid-template` shorthands doesn't accept that. Only longhand accepts that. + GenericGridTemplateComponent::TrackList(ref list) => { + if !list.is_explicit() { + return Ok(()); + } + }, + // Also the shorthands don't accept subgrids unlike longhand. + // We should fail without an error here. + GenericGridTemplateComponent::Subgrid(_) => { + return Ok(()); + }, + _ => {}, + } + + let mut names_iter = track_list.line_names.iter(); + for (((i, string), names), value) in areas.0.strings.iter().enumerate() + .zip(&mut names_iter) + .zip(track_list.values.iter()) { + if i > 0 { + dest.write_char(' ')?; + } + + if !names.is_empty() { + concat_serialize_idents("[", "] ", names, " ", dest)?; + } + + string.to_css(dest)?; + + // If the track size is the initial value then it's redundant here. + if !value.is_initial() { + dest.write_char(' ')?; + value.to_css(dest)?; + } + } + + if let Some(names) = names_iter.next() { + concat_serialize_idents(" [", "]", names, " ", dest)?; + } + + if let GenericGridTemplateComponent::TrackList(ref list) = *template_columns { + dest.write_str(" / ")?; + list.to_css(dest)?; + } + + Ok(()) + }, + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + #[inline] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + serialize_grid_template( + self.grid_template_rows, + self.grid_template_columns, + self.grid_template_areas, + dest + ) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="grid" + engines="gecko" + sub_properties="grid-template-rows grid-template-columns grid-template-areas + grid-auto-rows grid-auto-columns grid-auto-flow" + spec="https://drafts.csswg.org/css-grid/#propdef-grid" +> + use crate::parser::Parse; + use crate::properties::longhands::{grid_auto_columns, grid_auto_rows, grid_auto_flow}; + use crate::values::generics::grid::GridTemplateComponent; + use crate::values::specified::{GenericGridTemplateComponent, ImplicitGridTracks}; + use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas}; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let mut temp_rows = GridTemplateComponent::default(); + let mut temp_cols = GridTemplateComponent::default(); + let mut temp_areas = GridTemplateAreas::None; + let mut auto_rows = ImplicitGridTracks::default(); + let mut auto_cols = ImplicitGridTracks::default(); + let mut flow = grid_auto_flow::get_initial_value(); + + fn parse_auto_flow<'i, 't>( + input: &mut Parser<'i, 't>, + is_row: bool, + ) -> Result<GridAutoFlow, ParseError<'i>> { + let mut track = None; + let mut dense = GridAutoFlow::empty(); + + for _ in 0..2 { + if input.try_parse(|i| i.expect_ident_matching("auto-flow")).is_ok() { + track = if is_row { + Some(GridAutoFlow::ROW) + } else { + Some(GridAutoFlow::COLUMN) + }; + } else if input.try_parse(|i| i.expect_ident_matching("dense")).is_ok() { + dense = GridAutoFlow::DENSE + } else { + break + } + } + + if track.is_some() { + Ok(track.unwrap() | dense) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + if let Ok((rows, cols, areas)) = input.try_parse(|i| super::grid_template::parse_grid_template(context, i)) { + temp_rows = rows; + temp_cols = cols; + temp_areas = areas; + } else if let Ok(rows) = input.try_parse(|i| GridTemplateComponent::parse(context, i)) { + temp_rows = rows; + input.expect_delim('/')?; + flow = parse_auto_flow(input, false)?; + auto_cols = input.try_parse(|i| grid_auto_columns::parse(context, i)).unwrap_or_default(); + } else { + flow = parse_auto_flow(input, true)?; + auto_rows = input.try_parse(|i| grid_auto_rows::parse(context, i)).unwrap_or_default(); + input.expect_delim('/')?; + temp_cols = GridTemplateComponent::parse(context, input)?; + } + + Ok(expanded! { + grid_template_rows: temp_rows, + grid_template_columns: temp_cols, + grid_template_areas: temp_areas, + grid_auto_rows: auto_rows, + grid_auto_columns: auto_cols, + grid_auto_flow: flow, + }) + } + + impl<'a> LonghandsToSerialize<'a> { + /// Returns true if other sub properties except template-{rows,columns} are initial. + fn is_grid_template(&self) -> bool { + self.grid_auto_rows.is_initial() && + self.grid_auto_columns.is_initial() && + *self.grid_auto_flow == grid_auto_flow::get_initial_value() + } + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + if self.is_grid_template() { + return super::grid_template::serialize_grid_template( + self.grid_template_rows, + self.grid_template_columns, + self.grid_template_areas, + dest + ); + } + + if *self.grid_template_areas != GridTemplateAreas::None { + // No other syntax can set the template areas, so fail to + // serialize. + return Ok(()); + } + + if self.grid_auto_flow.contains(GridAutoFlow::COLUMN) { + // It should fail to serialize if other branch of the if condition's values are set. + if !self.grid_auto_rows.is_initial() || + !self.grid_template_columns.is_initial() { + return Ok(()); + } + + // It should fail to serialize if template-rows value is not Explicit. + if let GenericGridTemplateComponent::TrackList(ref list) = *self.grid_template_rows { + if !list.is_explicit() { + return Ok(()); + } + } + + self.grid_template_rows.to_css(dest)?; + dest.write_str(" / auto-flow")?; + if self.grid_auto_flow.contains(GridAutoFlow::DENSE) { + dest.write_str(" dense")?; + } + + if !self.grid_auto_columns.is_initial() { + dest.write_char(' ')?; + self.grid_auto_columns.to_css(dest)?; + } + + return Ok(()); + } + + // It should fail to serialize if other branch of the if condition's values are set. + if !self.grid_auto_columns.is_initial() || + !self.grid_template_rows.is_initial() { + return Ok(()); + } + + // It should fail to serialize if template-column value is not Explicit. + if let GenericGridTemplateComponent::TrackList(ref list) = *self.grid_template_columns { + if !list.is_explicit() { + return Ok(()); + } + } + + dest.write_str("auto-flow")?; + if self.grid_auto_flow.contains(GridAutoFlow::DENSE) { + dest.write_str(" dense")?; + } + + if !self.grid_auto_rows.is_initial() { + dest.write_char(' ')?; + self.grid_auto_rows.to_css(dest)?; + } + + dest.write_str(" / ")?; + self.grid_template_columns.to_css(dest)?; + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="place-content" + engines="gecko" + sub_properties="align-content justify-content" + spec="https://drafts.csswg.org/css-align/#propdef-place-content" +> + use crate::values::specified::align::{AlignContent, JustifyContent, ContentDistribution, AxisDirection}; + + pub fn parse_value<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let align_content = + ContentDistribution::parse(input, AxisDirection::Block)?; + + let justify_content = input.try_parse(|input| { + ContentDistribution::parse(input, AxisDirection::Inline) + }); + + let justify_content = match justify_content { + Ok(v) => v, + Err(..) => { + // https://drafts.csswg.org/css-align-3/#place-content: + // + // The second value is assigned to justify-content; if + // omitted, it is copied from the first value, unless that + // value is a <baseline-position> in which case it is + // defaulted to start. + // + if !align_content.is_baseline_position() { + align_content + } else { + ContentDistribution::start() + } + } + }; + + Ok(expanded! { + align_content: AlignContent(align_content), + justify_content: JustifyContent(justify_content), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.align_content.to_css(dest)?; + if self.align_content.0 != self.justify_content.0 { + dest.write_char(' ')?; + self.justify_content.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="place-self" + engines="gecko" + sub_properties="align-self justify-self" + spec="https://drafts.csswg.org/css-align/#place-self-property" +> + use crate::values::specified::align::{AlignSelf, JustifySelf, SelfAlignment, AxisDirection}; + + pub fn parse_value<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let align = SelfAlignment::parse(input, AxisDirection::Block)?; + let justify = input.try_parse(|input| SelfAlignment::parse(input, AxisDirection::Inline)); + + let justify = match justify { + Ok(v) => v, + Err(..) => { + debug_assert!(align.is_valid_on_both_axes()); + align + } + }; + + Ok(expanded! { + align_self: AlignSelf(align), + justify_self: JustifySelf(justify), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.align_self.to_css(dest)?; + if self.align_self.0 != self.justify_self.0 { + dest.write_char(' ')?; + self.justify_self.to_css(dest)?; + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="place-items" + engines="gecko" + sub_properties="align-items justify-items" + spec="https://drafts.csswg.org/css-align/#place-items-property" +> + use crate::values::specified::align::{AlignItems, JustifyItems}; + use crate::parser::Parse; + + impl From<AlignItems> for JustifyItems { + fn from(align: AlignItems) -> JustifyItems { + JustifyItems(align.0) + } + } + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let align = AlignItems::parse(context, input)?; + let justify = + input.try_parse(|input| JustifyItems::parse(context, input)) + .unwrap_or_else(|_| JustifyItems::from(align)); + + Ok(expanded! { + align_items: align, + justify_items: justify, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.align_items.to_css(dest)?; + if self.align_items.0 != self.justify_items.0 { + dest.write_char(' ')?; + self.justify_items.to_css(dest)?; + } + + Ok(()) + } + } +</%helpers:shorthand> + +// See https://github.com/w3c/csswg-drafts/issues/3525 for the quirks stuff. +${helpers.four_sides_shorthand( + "inset", + "%s", + "specified::LengthPercentageOrAuto::parse", + engines="gecko servo-2013", + spec="https://drafts.csswg.org/css-logical/#propdef-inset", + allow_quirks="No", +)} + +${helpers.two_properties_shorthand( + "inset-block", + "inset-block-start", + "inset-block-end", + "specified::LengthPercentageOrAuto::parse", + engines="gecko servo-2013", + spec="https://drafts.csswg.org/css-logical/#propdef-inset-block" +)} + +${helpers.two_properties_shorthand( + "inset-inline", + "inset-inline-start", + "inset-inline-end", + "specified::LengthPercentageOrAuto::parse", + engines="gecko servo-2013", + spec="https://drafts.csswg.org/css-logical/#propdef-inset-inline" +)} + +${helpers.two_properties_shorthand( + "contain-intrinsic-size", + "contain-intrinsic-width", + "contain-intrinsic-height", + engines="gecko", + gecko_pref="layout.css.contain-intrinsic-size.enabled", + spec="https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override", +)} diff --git a/servo/components/style/properties/shorthands/svg.mako.rs b/servo/components/style/properties/shorthands/svg.mako.rs new file mode 100644 index 0000000000..cf34b116ee --- /dev/null +++ b/servo/components/style/properties/shorthands/svg.mako.rs @@ -0,0 +1,287 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="mask" engines="gecko" extra_prefixes="webkit" + flags="SHORTHAND_IN_GETCS" + sub_properties="mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x + mask-position-y mask-size mask-image" + spec="https://drafts.fxtf.org/css-masking/#propdef-mask"> + use crate::properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position_x, + mask_position_y}; + use crate::properties::longhands::{mask_size, mask_image}; + use crate::values::specified::{Position, PositionComponent}; + use crate::parser::Parse; + + // FIXME(emilio): These two mask types should be the same! + impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue { + fn from(origin: mask_origin::single_value::SpecifiedValue) -> mask_clip::single_value::SpecifiedValue { + match origin { + mask_origin::single_value::SpecifiedValue::ContentBox => + mask_clip::single_value::SpecifiedValue::ContentBox, + mask_origin::single_value::SpecifiedValue::PaddingBox => + mask_clip::single_value::SpecifiedValue::PaddingBox , + mask_origin::single_value::SpecifiedValue::BorderBox => + mask_clip::single_value::SpecifiedValue::BorderBox, + % if engine == "gecko": + mask_origin::single_value::SpecifiedValue::FillBox => + mask_clip::single_value::SpecifiedValue::FillBox , + mask_origin::single_value::SpecifiedValue::StrokeBox => + mask_clip::single_value::SpecifiedValue::StrokeBox, + mask_origin::single_value::SpecifiedValue::ViewBox=> + mask_clip::single_value::SpecifiedValue::ViewBox, + % endif + } + } + } + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + % for name in "image mode position_x position_y size repeat origin clip composite".split(): + // Vec grows from 0 to 4 by default on first push(). So allocate with + // capacity 1, so in the common case of only one item we don't way + // overallocate, then shrink. Note that we always push at least one + // item if parsing succeeds. + let mut mask_${name} = Vec::with_capacity(1); + % endfor + + input.parse_comma_separated(|input| { + % for name in "image mode position size repeat origin clip composite".split(): + let mut ${name} = None; + % endfor + loop { + if image.is_none() { + if let Ok(value) = input.try_parse(|input| mask_image::single_value + ::parse(context, input)) { + image = Some(value); + continue + } + } + if position.is_none() { + if let Ok(value) = input.try_parse(|input| Position::parse(context, input)) { + position = Some(value); + + // Parse mask size, if applicable. + size = input.try_parse(|input| { + input.expect_delim('/')?; + mask_size::single_value::parse(context, input) + }).ok(); + + continue + } + } + % for name in "repeat origin clip composite mode".split(): + if ${name}.is_none() { + if let Ok(value) = input.try_parse(|input| mask_${name}::single_value + ::parse(context, input)) { + ${name} = Some(value); + continue + } + } + % endfor + break + } + if clip.is_none() { + if let Some(origin) = origin { + clip = Some(mask_clip::single_value::SpecifiedValue::from(origin)); + } + } + let mut any = false; + % for name in "image mode position size repeat origin clip composite".split(): + any = any || ${name}.is_some(); + % endfor + if any { + if let Some(position) = position { + mask_position_x.push(position.horizontal); + mask_position_y.push(position.vertical); + } else { + mask_position_x.push(PositionComponent::zero()); + mask_position_y.push(PositionComponent::zero()); + } + % for name in "image mode size repeat origin clip composite".split(): + if let Some(m_${name}) = ${name} { + mask_${name}.push(m_${name}); + } else { + mask_${name}.push(mask_${name}::single_value + ::get_initial_specified_value()); + } + % endfor + Ok(()) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + })?; + + Ok(expanded! { + % for name in "image mode position_x position_y size repeat origin clip composite".split(): + mask_${name}: mask_${name}::SpecifiedValue(mask_${name}.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use crate::properties::longhands::mask_origin::single_value::computed_value::T as Origin; + use crate::properties::longhands::mask_clip::single_value::computed_value::T as Clip; + use style_traits::values::SequenceWriter; + + let len = self.mask_image.0.len(); + if len == 0 { + return Ok(()); + } + % for name in "mode position_x position_y size repeat origin clip composite".split(): + if self.mask_${name}.0.len() != len { + return Ok(()); + } + % endfor + + // For each <mask-layer>, we serialize it according to the following order: + // <mask-layer> = + // <mask-reference> || + // <position> [ / <bg-size> ]? || + // <repeat-style> || + // <geometry-box> || + // [ <geometry-box> | no-clip ] || + // <compositing-operator> || + // <masking-mode> + // https://drafts.fxtf.org/css-masking-1/#the-mask + for i in 0..len { + if i > 0 { + dest.write_str(", ")?; + } + + % for name in "image mode position_x position_y size repeat origin clip composite".split(): + let ${name} = &self.mask_${name}.0[i]; + % endfor + + let mut has_other = false; + % for name in "image mode size repeat composite".split(): + let has_${name} = + *${name} != mask_${name}::single_value::get_initial_specified_value(); + has_other |= has_${name}; + % endfor + let has_position = *position_x != PositionComponent::zero() + || *position_y != PositionComponent::zero(); + let has_origin = *origin != Origin::BorderBox; + let has_clip = *clip != Clip::BorderBox; + + // If all are initial values, we serialize mask-image. + if !has_other && !has_position && !has_origin && !has_clip { + return image.to_css(dest); + } + + let mut writer = SequenceWriter::new(dest, " "); + // <mask-reference> + if has_image { + writer.item(image)?; + } + + // <position> [ / <bg-size> ]? + if has_position || has_size { + writer.item(&Position { + horizontal: position_x.clone(), + vertical: position_y.clone() + })?; + + if has_size { + writer.raw_item("/")?; + writer.item(size)?; + } + } + + // <repeat-style> + if has_repeat { + writer.item(repeat)?; + } + + // <geometry-box> + if has_origin { + writer.item(origin)?; + } + + // [ <geometry-box> | no-clip ] + if has_clip && *clip != From::from(*origin) { + writer.item(clip)?; + } + + // <compositing-operator> + if has_composite { + writer.item(composite)?; + } + + // <masking-mode> + if has_mode { + writer.item(mode)?; + } + } + + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="mask-position" engines="gecko" extra_prefixes="webkit" + flags="SHORTHAND_IN_GETCS" + sub_properties="mask-position-x mask-position-y" + spec="https://drafts.csswg.org/css-masks-4/#the-mask-position"> + use crate::properties::longhands::{mask_position_x,mask_position_y}; + use crate::values::specified::position::Position; + use crate::parser::Parse; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + // Vec grows from 0 to 4 by default on first push(). So allocate with + // capacity 1, so in the common case of only one item we don't way + // overallocate, then shrink. Note that we always push at least one + // item if parsing succeeds. + let mut position_x = Vec::with_capacity(1); + let mut position_y = Vec::with_capacity(1); + let mut any = false; + + input.parse_comma_separated(|input| { + let value = Position::parse(context, input)?; + position_x.push(value.horizontal); + position_y.push(value.vertical); + any = true; + Ok(()) + })?; + + if !any { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + + Ok(expanded! { + mask_position_x: mask_position_x::SpecifiedValue(position_x.into()), + mask_position_y: mask_position_y::SpecifiedValue(position_y.into()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let len = self.mask_position_x.0.len(); + if len == 0 || self.mask_position_y.0.len() != len { + return Ok(()); + } + + for i in 0..len { + Position { + horizontal: self.mask_position_x.0[i].clone(), + vertical: self.mask_position_y.0[i].clone() + }.to_css(dest)?; + + if i < len - 1 { + dest.write_str(", ")?; + } + } + + Ok(()) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/text.mako.rs b/servo/components/style/properties/shorthands/text.mako.rs new file mode 100644 index 0000000000..5b071be2c4 --- /dev/null +++ b/servo/components/style/properties/shorthands/text.mako.rs @@ -0,0 +1,120 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +<%helpers:shorthand name="text-decoration" + engines="gecko servo-2013 servo-2020" + flags="SHORTHAND_IN_GETCS" + sub_properties="text-decoration-line + ${' text-decoration-style text-decoration-color text-decoration-thickness' if engine == 'gecko' else ''}" + spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration"> + % if engine == "gecko": + use crate::values::specified; + use crate::properties::longhands::{text_decoration_style, text_decoration_color, text_decoration_thickness}; + % endif + use crate::properties::longhands::text_decoration_line; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + % if engine == "gecko": + let (mut line, mut style, mut color, mut thickness, mut any) = (None, None, None, None, false); + % else: + let (mut line, mut any) = (None, false); + % endif + + loop { + macro_rules! parse_component { + ($value:ident, $module:ident) => ( + if $value.is_none() { + if let Ok(value) = input.try_parse(|input| $module::parse(context, input)) { + $value = Some(value); + any = true; + continue; + } + } + ) + } + + parse_component!(line, text_decoration_line); + + % if engine == "gecko": + parse_component!(style, text_decoration_style); + parse_component!(color, text_decoration_color); + parse_component!(thickness, text_decoration_thickness); + % endif + + break; + } + + if !any { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + Ok(expanded! { + text_decoration_line: unwrap_or_initial!(text_decoration_line, line), + + % if engine == "gecko": + text_decoration_style: unwrap_or_initial!(text_decoration_style, style), + text_decoration_color: unwrap_or_initial!(text_decoration_color, color), + text_decoration_thickness: unwrap_or_initial!(text_decoration_thickness, thickness), + % endif + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + #[allow(unused)] + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use crate::values::specified::TextDecorationLine; + + let (is_solid_style, is_current_color, is_auto_thickness) = + ( + % if engine == "gecko": + *self.text_decoration_style == text_decoration_style::SpecifiedValue::Solid, + *self.text_decoration_color == specified::Color::CurrentColor, + self.text_decoration_thickness.is_auto() + % else: + true, true, true + % endif + ); + + let mut has_value = false; + let is_none = *self.text_decoration_line == TextDecorationLine::none(); + if (is_solid_style && is_current_color && is_auto_thickness) || !is_none { + self.text_decoration_line.to_css(dest)?; + has_value = true; + } + + if !is_auto_thickness { + if has_value { + dest.write_char(' ')?; + } + self.text_decoration_thickness.to_css(dest)?; + has_value = true; + } + + % if engine == "gecko": + if !is_solid_style { + if has_value { + dest.write_char(' ')?; + } + self.text_decoration_style.to_css(dest)?; + has_value = true; + } + + if !is_current_color { + if has_value { + dest.write_char(' ')?; + } + self.text_decoration_color.to_css(dest)?; + has_value = true; + } + % endif + + Ok(()) + } + } +</%helpers:shorthand> diff --git a/servo/components/style/properties/shorthands/ui.mako.rs b/servo/components/style/properties/shorthands/ui.mako.rs new file mode 100644 index 0000000000..1fdb5965fc --- /dev/null +++ b/servo/components/style/properties/shorthands/ui.mako.rs @@ -0,0 +1,444 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> + +macro_rules! try_parse_one { + ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { + if $var.is_none() { + if let Ok(value) = $input.try_parse(|i| { + $prop_module::single_value::parse($context, i) + }) { + $var = Some(value); + continue; + } + } + }; +} + +<%helpers:shorthand name="transition" + engines="gecko servo-2013 servo-2020" + extra_prefixes="moz:layout.css.prefixes.transitions webkit" + sub_properties="transition-property transition-duration + transition-timing-function + transition-delay" + spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> + use crate::parser::Parse; + % for prop in "delay duration property timing_function".split(): + use crate::properties::longhands::transition_${prop}; + % endfor + use crate::values::specified::TransitionProperty; + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + struct SingleTransition { + % for prop in "property duration timing_function delay".split(): + transition_${prop}: transition_${prop}::SingleSpecifiedValue, + % endfor + } + + fn parse_one_transition<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + first: bool, + ) -> Result<SingleTransition,ParseError<'i>> { + % for prop in "property duration timing_function delay".split(): + let mut ${prop} = None; + % endfor + + let mut parsed = 0; + loop { + parsed += 1; + + try_parse_one!(context, input, duration, transition_duration); + try_parse_one!(context, input, timing_function, transition_timing_function); + try_parse_one!(context, input, delay, transition_delay); + // Must check 'transition-property' after 'transition-timing-function' since + // 'transition-property' accepts any keyword. + if property.is_none() { + if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) { + property = Some(value); + continue; + } + + // 'none' is not a valid value for <single-transition-property>, + // so it's only acceptable as the first item. + if first && input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + property = Some(TransitionProperty::none()); + continue; + } + } + + parsed -= 1; + break + } + + if parsed != 0 { + Ok(SingleTransition { + % for prop in "property duration timing_function delay".split(): + transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value + ::get_initial_specified_value), + % endfor + }) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } + + % for prop in "property duration timing_function delay".split(): + let mut ${prop}s = Vec::new(); + % endfor + + let mut first = true; + let mut has_transition_property_none = false; + let results = input.parse_comma_separated(|i| { + if has_transition_property_none { + // If you specify transition-property: none, multiple items are invalid. + return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + let transition = parse_one_transition(context, i, first)?; + first = false; + has_transition_property_none = transition.transition_property.is_none(); + Ok(transition) + })?; + for result in results { + % for prop in "property duration timing_function delay".split(): + ${prop}s.push(result.transition_${prop}); + % endfor + } + + Ok(expanded! { + % for prop in "property duration timing_function delay".split(): + transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + use crate::Zero; + use style_traits::values::SequenceWriter; + + let property_len = self.transition_property.0.len(); + + // There are two cases that we can do shorthand serialization: + // * when all value lists have the same length, or + // * when transition-property is none, and other value lists have exactly one item. + if property_len == 0 { + % for name in "duration delay timing_function".split(): + if self.transition_${name}.0.len() != 1 { + return Ok(()); + } + % endfor + } else { + % for name in "duration delay timing_function".split(): + if self.transition_${name}.0.len() != property_len { + return Ok(()); + } + % endfor + } + + // Representative length. + let len = self.transition_duration.0.len(); + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + let has_duration = !self.transition_duration.0[i].is_zero(); + let has_timing = !self.transition_timing_function.0[i].is_ease(); + let has_delay = !self.transition_delay.0[i].is_zero(); + let has_any = has_duration || has_timing || has_delay; + + let mut writer = SequenceWriter::new(dest, " "); + + if property_len == 0 { + writer.raw_item("none")?; + } else if !self.transition_property.0[i].is_all() || !has_any { + writer.item(&self.transition_property.0[i])?; + } + + // In order to avoid ambiguity, we have to serialize duration if we have delay. + if has_duration || has_delay { + writer.item(&self.transition_duration.0[i])?; + } + + if has_timing { + writer.item(&self.transition_timing_function.0[i])?; + } + + if has_delay { + writer.item(&self.transition_delay.0[i])?; + } + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand name="animation" + engines="gecko servo-2013 servo-2020" + extra_prefixes="moz:layout.css.prefixes.animations webkit" + sub_properties="animation-name animation-duration + animation-timing-function animation-delay + animation-iteration-count animation-direction + animation-fill-mode animation-play-state animation-timeline" + rule_types_allowed="Style" + spec="https://drafts.csswg.org/css-animations/#propdef-animation"> + <% + props = "name timeline duration timing_function delay iteration_count \ + direction fill_mode play_state".split() + %> + % for prop in props: + use crate::properties::longhands::animation_${prop}; + % endfor + + pub fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + struct SingleAnimation { + % for prop in props: + animation_${prop}: animation_${prop}::SingleSpecifiedValue, + % endfor + } + + fn parse_one_animation<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SingleAnimation, ParseError<'i>> { + % for prop in props: + let mut ${prop} = None; + % endfor + + let mut parsed = 0; + // NB: Name must be the last one here so that keywords valid for other + // longhands are not interpreted as names. + // + // Also, duration must be before delay, see + // https://drafts.csswg.org/css-animations/#typedef-single-animation + loop { + parsed += 1; + try_parse_one!(context, input, duration, animation_duration); + try_parse_one!(context, input, timing_function, animation_timing_function); + try_parse_one!(context, input, delay, animation_delay); + try_parse_one!(context, input, iteration_count, animation_iteration_count); + try_parse_one!(context, input, direction, animation_direction); + try_parse_one!(context, input, fill_mode, animation_fill_mode); + try_parse_one!(context, input, play_state, animation_play_state); + try_parse_one!(context, input, name, animation_name); + if static_prefs::pref!("layout.css.scroll-driven-animations.enabled") { + try_parse_one!(context, input, timeline, animation_timeline); + } + + parsed -= 1; + break + } + + // If nothing is parsed, this is an invalid entry. + if parsed == 0 { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } else { + Ok(SingleAnimation { + % for prop in props: + animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value + ::get_initial_specified_value), + % endfor + }) + } + } + + % for prop in props: + let mut ${prop}s = vec![]; + % endfor + + let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?; + for result in results.into_iter() { + % for prop in props: + ${prop}s.push(result.animation_${prop}); + % endfor + } + + Ok(expanded! { + % for prop in props: + animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()), + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let len = self.animation_name.0.len(); + // There should be at least one declared value + if len == 0 { + return Ok(()); + } + + // If any value list length is differs then we don't do a shorthand serialization + // either. + % for name in props[2:]: + if len != self.animation_${name}.0.len() { + return Ok(()) + } + % endfor + + // If the preference of animation-timeline is disabled, `self.animation_timeline` is + // None. + if self.animation_timeline.map_or(false, |v| len != v.0.len()) { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + % for name in props[2:]: + self.animation_${name}.0[i].to_css(dest)?; + dest.write_char(' ')?; + % endfor + + self.animation_name.0[i].to_css(dest)?; + + // Based on the spec, the default values of other properties must be output in at + // least the cases necessary to distinguish an animation-name. The serialization + // order of animation-timeline is always later than animation-name, so it's fine + // to not serialize it if it is the default value. It's still possible to + // distinguish them (because we always serialize animation-name). + // https://drafts.csswg.org/css-animations-1/#animation + // https://drafts.csswg.org/css-animations-2/#typedef-single-animation + // + // Note: it's also fine to always serialize this. However, it seems Blink + // doesn't serialize default animation-timeline now, so we follow the same rule. + if let Some(ref timeline) = self.animation_timeline { + if !timeline.0[i].is_auto() { + dest.write_char(' ')?; + timeline.0[i].to_css(dest)?; + } + } + } + Ok(()) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + engines="gecko" + name="scroll-timeline" + sub_properties="scroll-timeline-name scroll-timeline-axis" + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-shorthand" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::properties::longhands::{scroll_timeline_axis, scroll_timeline_name}; + + let mut names = Vec::with_capacity(1); + let mut axes = Vec::with_capacity(1); + input.parse_comma_separated(|input| { + let name = scroll_timeline_name::single_value::parse(context, input)?; + let axis = input.try_parse(|i| scroll_timeline_axis::single_value::parse(context, i)); + + names.push(name); + axes.push(axis.unwrap_or_default()); + + Ok(()) + })?; + + Ok(expanded! { + scroll_timeline_name: scroll_timeline_name::SpecifiedValue(names.into()), + scroll_timeline_axis: scroll_timeline_axis::SpecifiedValue(axes.into()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + // If any value list length is differs then we don't do a shorthand serialization + // either. + let len = self.scroll_timeline_name.0.len(); + if len != self.scroll_timeline_axis.0.len() { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + self.scroll_timeline_name.0[i].to_css(dest)?; + + if self.scroll_timeline_axis.0[i] != Default::default() { + dest.write_char(' ')?; + self.scroll_timeline_axis.0[i].to_css(dest)?; + } + + } + Ok(()) + } + } +</%helpers:shorthand> + +// Note: view-timeline shorthand doesn't take view-timeline-inset into account. +<%helpers:shorthand + engines="gecko" + name="view-timeline" + sub_properties="view-timeline-name view-timeline-axis" + gecko_pref="layout.css.scroll-driven-animations.enabled", + spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-shorthand" +> + pub fn parse_value<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::properties::longhands::{view_timeline_axis, view_timeline_name}; + + let mut names = Vec::with_capacity(1); + let mut axes = Vec::with_capacity(1); + input.parse_comma_separated(|input| { + let name = view_timeline_name::single_value::parse(context, input)?; + let axis = input.try_parse(|i| view_timeline_axis::single_value::parse(context, i)); + + names.push(name); + axes.push(axis.unwrap_or_default()); + + Ok(()) + })?; + + Ok(expanded! { + view_timeline_name: view_timeline_name::SpecifiedValue(names.into()), + view_timeline_axis: view_timeline_axis::SpecifiedValue(axes.into()), + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + // If any value list length is differs then we don't do a shorthand serialization + // either. + let len = self.view_timeline_name.0.len(); + if len != self.view_timeline_axis.0.len() { + return Ok(()); + } + + for i in 0..len { + if i != 0 { + dest.write_str(", ")?; + } + + self.view_timeline_name.0[i].to_css(dest)?; + + if self.view_timeline_axis.0[i] != Default::default() { + dest.write_char(' ')?; + self.view_timeline_axis.0[i].to_css(dest)?; + } + + } + Ok(()) + } + } +</%helpers:shorthand> |