3008 lines
110 KiB
Rust
3008 lines
110 KiB
Rust
/* 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;
|
|
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, color::ColorSchemeFlags},
|
|
};
|
|
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:
|
|
<% data.current_style_struct = style_struct %>
|
|
<%include file="/longhands/${style_struct.name_lower}.mako.rs" />
|
|
% endfor
|
|
}
|
|
|
|
|
|
% if engine == "gecko":
|
|
#[allow(unsafe_code, missing_docs)]
|
|
pub mod gecko {
|
|
<%include file="/gecko.mako.rs" />
|
|
}
|
|
% endif
|
|
|
|
|
|
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 file="/shorthands/${style_struct.name_lower}.mako.rs" />
|
|
% 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",
|
|
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.
|
|
<% longhand_property_names = set(list(map(lambda p: p.name, data.longhands))) %>
|
|
<% enabled_prioritary_properties = PRIORITARY_PROPERTIES.intersection(longhand_property_names) %>
|
|
pub const PRIORITARY: usize = ${len(enabled_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():
|
|
<%
|
|
pref = getattr(property, "servo_pref")
|
|
%>
|
|
% if pref:
|
|
Some("${pref}"),
|
|
% else:
|
|
None,
|
|
% endif
|
|
% endfor
|
|
];
|
|
let pref = match PREF_NAME[self.0 as usize] {
|
|
None => return true,
|
|
Some(pref) => pref,
|
|
};
|
|
|
|
style_config::get_bool(pref)
|
|
% 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) ||
|
|
rule_types.contains(CssRuleType::PositionTry),
|
|
"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_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 margin properties, for the purposes of <h1> use counters / warnings.
|
|
#[inline]
|
|
pub fn margin_properties() -> &'static Self {
|
|
${static_longhand_id_set(
|
|
"MARGIN_PROPERTIES",
|
|
lambda p: p.logical_group == "margin"
|
|
)}
|
|
&MARGIN_PROPERTIES
|
|
}
|
|
|
|
/// 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 in data.shorthands_by_name["border"].sub_properties or \
|
|
p in data.shorthands_by_name["background"].sub_properties and \
|
|
p.name not in ["background-blend-mode", "background-repeat"]
|
|
)}
|
|
&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::values::specified::color::ColorSchemeFlags;
|
|
|
|
% 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 == "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) {
|
|
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()
|
|
}
|
|
/// Create a new Font with the initial values of all members.
|
|
pub fn initial_values() -> Self {
|
|
Self {
|
|
% for longhand in style_struct.longhands:
|
|
% if not longhand.logical:
|
|
${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
|
|
% endif
|
|
% endfor
|
|
hash: 0,
|
|
}
|
|
}
|
|
% elif style_struct.name == "InheritedUI":
|
|
/// Returns the ColorSchemeFlags corresponding to the value of `color-scheme`.
|
|
#[inline]
|
|
pub fn color_scheme_bits(&self) -> ColorSchemeFlags {
|
|
self.color_scheme.bits
|
|
}
|
|
% 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 animation-timeline is initial value. We need this information to
|
|
/// resolve animation-duration.
|
|
#[cfg(feature = "servo")]
|
|
pub fn has_initial_animation_timeline(&self) -> bool {
|
|
self.animation_timeline_count() == 1 && self.animation_timeline_at(0).is_auto()
|
|
}
|
|
|
|
/// Returns whether there is any named progress timeline specified with
|
|
/// scroll-timeline-name other than `none`.
|
|
#[cfg(feature = "gecko")]
|
|
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`.
|
|
#[cfg(feature = "gecko")]
|
|
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: computed::Zoom,
|
|
|
|
/// A set of flags we use to store misc information regarding this style.
|
|
pub flags: ComputedValueFlags,
|
|
|
|
/// The rule node representing the ordered list of rules matched for this
|
|
/// node. Can be None for default values and text nodes. This is
|
|
/// essentially an optimization to avoid referencing the root rule node.
|
|
pub rules: Option<StrongRuleNode>,
|
|
|
|
/// The element's computed values if visited, only computed if there's a
|
|
/// relevant link for this element. A element's "relevant link" is the
|
|
/// element being matched if it is a link or the nearest ancestor link.
|
|
visited_style: Option<Arc<ComputedValues>>,
|
|
}
|
|
|
|
/// The struct that Servo uses to represent computed values.
|
|
///
|
|
/// This struct contains an immutable atomically-reference-counted pointer to
|
|
/// every kind of style struct.
|
|
///
|
|
/// When needed, the structs may be copied in order to get mutated.
|
|
#[cfg(feature = "servo")]
|
|
#[cfg_attr(feature = "servo", derive(Clone, Debug))]
|
|
pub struct ComputedValues {
|
|
/// The actual computed values
|
|
///
|
|
/// In Gecko the outer ComputedValues is actually a ComputedStyle, whereas
|
|
/// ComputedValuesInner is the core set of computed values.
|
|
///
|
|
/// We maintain this distinction in servo to reduce the amount of special
|
|
/// casing.
|
|
inner: ComputedValuesInner,
|
|
|
|
/// The pseudo-element that we're using.
|
|
pseudo: Option<PseudoElement>,
|
|
}
|
|
|
|
impl ComputedValues {
|
|
/// Returns the pseudo-element that this style represents.
|
|
#[cfg(feature = "servo")]
|
|
pub fn pseudo(&self) -> Option<<&PseudoElement> {
|
|
self.pseudo.as_ref()
|
|
}
|
|
|
|
/// Returns true if this is the style for a pseudo-element.
|
|
#[cfg(feature = "servo")]
|
|
pub fn is_pseudo_style(&self) -> bool {
|
|
self.pseudo().is_some()
|
|
}
|
|
|
|
/// Returns whether this style's display value is equal to contents.
|
|
pub fn is_display_contents(&self) -> bool {
|
|
self.clone_display().is_contents()
|
|
}
|
|
|
|
/// Gets a reference to the rule node. Panic if no rule node exists.
|
|
pub fn rules(&self) -> &StrongRuleNode {
|
|
self.rules.as_ref().unwrap()
|
|
}
|
|
|
|
/// Returns the visited rules, if applicable.
|
|
pub fn visited_rules(&self) -> Option<<&StrongRuleNode> {
|
|
self.visited_style().and_then(|s| s.rules.as_ref())
|
|
}
|
|
|
|
/// Gets a reference to the custom properties map (if one exists).
|
|
pub fn custom_properties(&self) -> &crate::custom_properties::ComputedCustomProperties {
|
|
&self.custom_properties
|
|
}
|
|
|
|
/// Returns whether we have the same custom properties as another style.
|
|
pub fn custom_properties_equal(&self, other: &Self) -> bool {
|
|
self.custom_properties() == other.custom_properties()
|
|
}
|
|
|
|
% for prop in data.longhands:
|
|
% if not prop.logical:
|
|
/// Gets the computed value of a given property.
|
|
#[inline(always)]
|
|
#[allow(non_snake_case)]
|
|
pub fn clone_${prop.ident}(
|
|
&self,
|
|
) -> longhands::${prop.ident}::computed_value::T {
|
|
self.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}()
|
|
}
|
|
% endif
|
|
% endfor
|
|
|
|
/// Writes the (resolved or computed) value of the given longhand as a string in `dest`.
|
|
///
|
|
/// TODO(emilio): We should move all the special resolution from
|
|
/// nsComputedDOMStyle to ToResolvedValue instead.
|
|
pub fn computed_or_resolved_value(
|
|
&self,
|
|
property_id: LonghandId,
|
|
context: Option<<&resolved::Context>,
|
|
dest: &mut CssStringWriter,
|
|
) -> fmt::Result {
|
|
use crate::values::resolved::ToResolvedValue;
|
|
let mut dest = CssWriter::new(dest);
|
|
let property_id = property_id.to_physical(self.writing_mode);
|
|
match property_id {
|
|
% for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()):
|
|
<% props = list(props) %>
|
|
${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => {
|
|
let value = match property_id {
|
|
% for prop in props:
|
|
% if not prop.logical:
|
|
LonghandId::${prop.camel_case} => self.clone_${prop.ident}(),
|
|
% endif
|
|
% endfor
|
|
_ => unsafe { debug_unreachable!() },
|
|
};
|
|
if let Some(c) = context {
|
|
value.to_resolved_value(c).to_css(&mut dest)
|
|
} else {
|
|
value.to_css(&mut dest)
|
|
}
|
|
}
|
|
% endfor
|
|
}
|
|
}
|
|
|
|
/// Returns the given longhand's resolved value as a property declaration.
|
|
pub fn computed_or_resolved_declaration(
|
|
&self,
|
|
property_id: LonghandId,
|
|
context: Option<<&resolved::Context>,
|
|
) -> PropertyDeclaration {
|
|
use crate::values::resolved::ToResolvedValue;
|
|
use crate::values::computed::ToComputedValue;
|
|
let physical_property_id = property_id.to_physical(self.writing_mode);
|
|
match physical_property_id {
|
|
% for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()):
|
|
<% props = list(props) %>
|
|
${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => {
|
|
let mut computed_value = match physical_property_id {
|
|
% for prop in props:
|
|
% if not prop.logical:
|
|
LonghandId::${prop.camel_case} => self.clone_${prop.ident}(),
|
|
% endif
|
|
% endfor
|
|
_ => unsafe { debug_unreachable!() },
|
|
};
|
|
if let Some(c) = context {
|
|
let resolved = computed_value.to_resolved_value(c);
|
|
computed_value = ToResolvedValue::from_resolved_value(resolved);
|
|
}
|
|
let specified = ToComputedValue::from_computed_value(&computed_value);
|
|
% if props[0].boxed:
|
|
let specified = Box::new(specified);
|
|
% endif
|
|
% if len(props) == 1:
|
|
PropertyDeclaration::${props[0].camel_case}(specified)
|
|
% else:
|
|
unsafe {
|
|
let mut out = mem::MaybeUninit::uninit();
|
|
ptr::write(
|
|
out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified_type}>,
|
|
PropertyDeclarationVariantRepr {
|
|
tag: property_id as u16,
|
|
value: specified,
|
|
},
|
|
);
|
|
out.assume_init()
|
|
}
|
|
% endif
|
|
}
|
|
% endfor
|
|
}
|
|
}
|
|
|
|
/// Resolves the currentColor keyword.
|
|
///
|
|
/// Any color value from computed values (except for the 'color' property
|
|
/// itself) should go through this method.
|
|
///
|
|
/// Usage example:
|
|
/// let top_color =
|
|
/// style.resolve_color(&style.get_border().clone_border_top_color());
|
|
#[inline]
|
|
pub fn resolve_color(&self, color: &computed::Color) -> crate::color::AbsoluteColor {
|
|
let current_color = self.get_inherited_text().clone_color();
|
|
color.resolve_to_absolute(¤t_color)
|
|
}
|
|
|
|
/// Returns which longhand properties have different values in the two
|
|
/// ComputedValues.
|
|
#[cfg(feature = "gecko_debug")]
|
|
pub fn differing_properties(&self, other: &ComputedValues) -> LonghandIdSet {
|
|
let mut set = LonghandIdSet::new();
|
|
% for prop in data.longhands:
|
|
% if not prop.logical:
|
|
if self.clone_${prop.ident}() != other.clone_${prop.ident}() {
|
|
set.insert(LonghandId::${prop.camel_case});
|
|
}
|
|
% endif
|
|
% endfor
|
|
set
|
|
}
|
|
|
|
/// Create a `TransitionPropertyIterator` for this styles transition properties.
|
|
pub fn transition_properties<'a>(
|
|
&'a self
|
|
) -> animated_properties::TransitionPropertyIterator<'a> {
|
|
animated_properties::TransitionPropertyIterator::from_style(self)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "servo")]
|
|
impl ComputedValues {
|
|
/// Create a new refcounted `ComputedValues`
|
|
pub fn new(
|
|
pseudo: Option<<&PseudoElement>,
|
|
custom_properties: crate::custom_properties::ComputedCustomProperties,
|
|
writing_mode: WritingMode,
|
|
effective_zoom: computed::Zoom,
|
|
flags: ComputedValueFlags,
|
|
rules: Option<StrongRuleNode>,
|
|
visited_style: Option<Arc<ComputedValues>>,
|
|
% for style_struct in data.active_style_structs():
|
|
${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
|
|
% endfor
|
|
) -> Arc<Self> {
|
|
Arc::new(Self {
|
|
inner: ComputedValuesInner {
|
|
custom_properties,
|
|
writing_mode,
|
|
rules,
|
|
visited_style,
|
|
effective_zoom,
|
|
flags,
|
|
% for style_struct in data.active_style_structs():
|
|
${style_struct.ident},
|
|
% endfor
|
|
},
|
|
pseudo: pseudo.cloned(),
|
|
})
|
|
}
|
|
|
|
/// Get the initial computed values.
|
|
pub fn initial_values_with_font_override(default_font: super::style_structs::Font) -> Arc<Self> {
|
|
use crate::computed_value_flags::ComputedValueFlags;
|
|
use servo_arc::Arc;
|
|
use super::{ComputedValues, ComputedValuesInner, longhands, style_structs};
|
|
|
|
Arc::new(ComputedValues {
|
|
inner: ComputedValuesInner {
|
|
% for style_struct in data.active_style_structs():
|
|
% if style_struct.name == "Font":
|
|
font: Arc::new(default_font),
|
|
<% continue %>
|
|
% endif %
|
|
|
|
${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 == "Box":
|
|
original_display: longhands::display::get_initial_value(),
|
|
% endif
|
|
}),
|
|
% endfor
|
|
custom_properties: crate::custom_properties::ComputedCustomProperties::default(),
|
|
writing_mode: WritingMode::empty(),
|
|
rules: None,
|
|
visited_style: None,
|
|
effective_zoom: crate::values::computed::Zoom::ONE,
|
|
flags: ComputedValueFlags::empty(),
|
|
},
|
|
pseudo: 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 Servo (and that must be the case since ComputedValues'
|
|
// member is private).
|
|
unsafe { Arc::from_raw_addrefed(self) }
|
|
}
|
|
|
|
/// 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 context = resolved::Context {
|
|
style: self,
|
|
};
|
|
let mut s = String::new();
|
|
self.computed_or_resolved_value(
|
|
id,
|
|
Some(&context),
|
|
&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
|
|
.get(name)
|
|
.or_else(|| p.non_inherited.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.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::Margin> {
|
|
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::Inset> {
|
|
// 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 color-scheme bits. Needed because they may otherwise be different between visited and
|
|
/// unvisited colors.
|
|
pub color_scheme: ColorSchemeFlags,
|
|
|
|
/// 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();
|
|
Self {
|
|
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,
|
|
color_scheme: inherited_style.get_inherited_ui().color_scheme_bits(),
|
|
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);
|
|
Self {
|
|
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,
|
|
color_scheme: style_to_derive_from.get_inherited_ui().color_scheme_bits(),
|
|
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 == "servo":
|
|
/// 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::Auto)
|
|
}
|
|
|
|
/// 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()
|
|
}
|
|
|
|
/// The zoom we need to apply for this element, without including ancestor effective zooms.
|
|
pub fn resolved_specified_zoom(&self) -> computed::Zoom {
|
|
let zoom = self.specified_zoom();
|
|
if zoom.is_document() {
|
|
// If our inherited effective zoom has derived to zero, there's not much we can do.
|
|
// This value is not exposed to content anyways (it's used for scrollbars and to avoid
|
|
// zoom affecting canvas).
|
|
self.inherited_effective_zoom().inverted().unwrap_or(computed::Zoom::ONE)
|
|
} else {
|
|
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);
|
|
}
|
|
let lh = device.calc_line_height(&font, writing_mode, None);
|
|
if line_height_base == LineHeightBase::InheritedStyle {
|
|
// Apply our own zoom if our style source is the parent style.
|
|
computed::NonNegativeLength::new(self.resolved_specified_zoom().zoom(lh.px()))
|
|
} else {
|
|
lh
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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::NonCustom(${kind}Id::${property.camel_case}.into())],
|
|
% endif
|
|
[${prop.camel_case}, Set${prop.camel_case},
|
|
PropertyId::NonCustom(${kind}Id::${property.camel_case}.into())],
|
|
% 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.
|
|
#[cfg(feature = "gecko")]
|
|
size_of_test!(ComputedValues, 240);
|
|
#[cfg(feature = "servo")]
|
|
size_of_test!(ComputedValues, 208);
|
|
|
|
// 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 == "servo":
|
|
% 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),* ]) => ({
|
|
restyle_damage_${effect_name}!($old, $new, $damage, [$($effect),*], false)
|
|
});
|
|
($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ], $extra: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
|
|
|
|
$extra || false {
|
|
$damage.insert($($effect)|*);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
}
|
|
% endfor
|
|
% endif
|