summaryrefslogtreecommitdiffstats
path: root/servo/components/style/properties
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/properties')
-rw-r--r--servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whlbin0 -> 75521 bytes
-rw-r--r--servo/components/style/properties/build.py176
-rw-r--r--servo/components/style/properties/cascade.rs1381
-rw-r--r--servo/components/style/properties/computed_value_flags.rs194
-rw-r--r--servo/components/style/properties/counted_unknown_properties.py110
-rw-r--r--servo/components/style/properties/data.py1083
-rw-r--r--servo/components/style/properties/declaration_block.rs1642
-rw-r--r--servo/components/style/properties/gecko.mako.rs1806
-rw-r--r--servo/components/style/properties/helpers.mako.rs909
-rw-r--r--servo/components/style/properties/helpers/animated_properties.mako.rs785
-rw-r--r--servo/components/style/properties/longhands/background.mako.rs126
-rw-r--r--servo/components/style/properties/longhands/border.mako.rs170
-rw-r--r--servo/components/style/properties/longhands/box.mako.rs644
-rw-r--r--servo/components/style/properties/longhands/column.mako.rs90
-rw-r--r--servo/components/style/properties/longhands/counters.mako.rs52
-rw-r--r--servo/components/style/properties/longhands/effects.mako.rs92
-rw-r--r--servo/components/style/properties/longhands/font.mako.rs505
-rw-r--r--servo/components/style/properties/longhands/inherited_box.mako.rs105
-rw-r--r--servo/components/style/properties/longhands/inherited_svg.mako.rs239
-rw-r--r--servo/components/style/properties/longhands/inherited_table.mako.rs53
-rw-r--r--servo/components/style/properties/longhands/inherited_text.mako.rs414
-rw-r--r--servo/components/style/properties/longhands/inherited_ui.mako.rs135
-rw-r--r--servo/components/style/properties/longhands/list.mako.rs80
-rw-r--r--servo/components/style/properties/longhands/margin.mako.rs55
-rw-r--r--servo/components/style/properties/longhands/outline.mako.rs57
-rw-r--r--servo/components/style/properties/longhands/padding.mako.rs43
-rw-r--r--servo/components/style/properties/longhands/page.mako.rs44
-rw-r--r--servo/components/style/properties/longhands/position.mako.rs485
-rw-r--r--servo/components/style/properties/longhands/svg.mako.rs282
-rw-r--r--servo/components/style/properties/longhands/table.mako.rs30
-rw-r--r--servo/components/style/properties/longhands/text.mako.rs88
-rw-r--r--servo/components/style/properties/longhands/ui.mako.rs422
-rw-r--r--servo/components/style/properties/longhands/xul.mako.rs85
-rw-r--r--servo/components/style/properties/mod.rs1531
-rw-r--r--servo/components/style/properties/properties.html.mako31
-rw-r--r--servo/components/style/properties/properties.mako.rs2958
-rw-r--r--servo/components/style/properties/shorthands/background.mako.rs289
-rw-r--r--servo/components/style/properties/shorthands/border.mako.rs491
-rw-r--r--servo/components/style/properties/shorthands/box.mako.rs253
-rw-r--r--servo/components/style/properties/shorthands/column.mako.rs115
-rw-r--r--servo/components/style/properties/shorthands/font.mako.rs542
-rw-r--r--servo/components/style/properties/shorthands/inherited_svg.mako.rs38
-rw-r--r--servo/components/style/properties/shorthands/inherited_text.mako.rs254
-rw-r--r--servo/components/style/properties/shorthands/list.mako.rs137
-rw-r--r--servo/components/style/properties/shorthands/margin.mako.rs60
-rw-r--r--servo/components/style/properties/shorthands/outline.mako.rs80
-rw-r--r--servo/components/style/properties/shorthands/padding.mako.rs58
-rw-r--r--servo/components/style/properties/shorthands/position.mako.rs891
-rw-r--r--servo/components/style/properties/shorthands/svg.mako.rs287
-rw-r--r--servo/components/style/properties/shorthands/text.mako.rs120
-rw-r--r--servo/components/style/properties/shorthands/ui.mako.rs444
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
new file mode 100644
index 0000000000..9593025a47
--- /dev/null
+++ b/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl
Binary files differ
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(&current_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 &current_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(&current_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>