mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
4074 lines
146 KiB
Rust
4074 lines
146 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" />
|
||
|
||
#[cfg(feature = "servo")]
|
||
use app_units::Au;
|
||
use arrayvec::{ArrayVec, Drain as ArrayVecDrain};
|
||
use servo_arc::{Arc, UniqueArc};
|
||
use std::borrow::Cow;
|
||
use std::{ops, ptr};
|
||
use std::fmt::{self, Write};
|
||
use std::mem;
|
||
|
||
use cssparser::{Parser, RGBA, TokenSerializationType};
|
||
use cssparser::ParserInput;
|
||
#[cfg(feature = "servo")] use euclid::SideOffsets2D;
|
||
use crate::context::QuirksMode;
|
||
#[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 crate::media_queries::Device;
|
||
use crate::parser::ParserContext;
|
||
use crate::properties::longhands::system_font::SystemFont;
|
||
use crate::selector_parser::PseudoElement;
|
||
use selectors::parser::SelectorParseErrorKind;
|
||
#[cfg(feature = "servo")] use servo_config::prefs;
|
||
use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode};
|
||
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
|
||
use to_shmem::impl_trivial_to_shmem;
|
||
use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
|
||
use crate::use_counters::UseCounters;
|
||
use crate::values::generics::text::LineHeight;
|
||
use crate::values::{computed, resolved};
|
||
use crate::values::computed::NonNegativeLength;
|
||
use crate::values::serialize_atom_name;
|
||
use crate::rule_tree::StrongRuleNode;
|
||
use crate::Zero;
|
||
use crate::str::{CssString, CssStringBorrow, CssStringWriter};
|
||
use std::cell::Cell;
|
||
|
||
pub use self::declaration_block::*;
|
||
pub use self::cascade::*;
|
||
|
||
<%!
|
||
from collections import defaultdict
|
||
from data import Method, PropertyRestrictions, Keyword, to_rust_ident, to_camel_case, SYSTEM_FONT_LONGHANDS
|
||
import os.path
|
||
%>
|
||
|
||
#[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
|
||
pub mod declaration_block;
|
||
#[path="${repr(os.path.join(os.path.dirname(__file__), 'cascade.rs'))[1:-1]}"]
|
||
pub mod cascade;
|
||
|
||
/// Conversion with fewer impls than From/Into
|
||
pub trait MaybeBoxed<Out> {
|
||
/// Convert
|
||
fn maybe_boxed(self) -> Out;
|
||
}
|
||
|
||
impl<T> MaybeBoxed<T> for T {
|
||
#[inline]
|
||
fn maybe_boxed(self) -> T { self }
|
||
}
|
||
|
||
impl<T> MaybeBoxed<Box<T>> for T {
|
||
#[inline]
|
||
fn maybe_boxed(self) -> Box<T> { Box::new(self) }
|
||
}
|
||
|
||
macro_rules! expanded {
|
||
( $( $name: ident: $value: expr ),+ ) => {
|
||
expanded!( $( $name: $value, )+ )
|
||
};
|
||
( $( $name: ident: $value: expr, )+ ) => {
|
||
Longhands {
|
||
$(
|
||
$name: MaybeBoxed::maybe_boxed($value),
|
||
)+
|
||
}
|
||
}
|
||
}
|
||
|
||
/// A module with all the code for longhand properties.
|
||
#[allow(missing_docs)]
|
||
pub mod longhands {
|
||
% for style_struct in data.style_structs:
|
||
include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}");
|
||
% endfor
|
||
pub const ANIMATABLE_PROPERTY_COUNT: usize = ${sum(1 for prop in data.longhands if prop.animatable)};
|
||
}
|
||
|
||
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;
|
||
|
||
use style_traits::{CssWriter, ToCss};
|
||
use crate::values::specified::{BorderStyle, Color};
|
||
use std::fmt::{self, Write};
|
||
|
||
fn serialize_directional_border<W, I,>(
|
||
dest: &mut CssWriter<W>,
|
||
width: &I,
|
||
style: &BorderStyle,
|
||
color: &Color,
|
||
) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
I: ToCss,
|
||
{
|
||
width.to_css(dest)?;
|
||
// FIXME(emilio): Should we really serialize the border style if it's
|
||
// `solid`?
|
||
dest.write_str(" ")?;
|
||
style.to_css(dest)?;
|
||
if *color != Color::CurrentColor {
|
||
dest.write_str(" ")?;
|
||
color.to_css(dest)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
% for style_struct in data.style_structs:
|
||
include!("${repr(os.path.join(OUT_DIR, 'shorthands/{}.rs'.format(style_struct.name_lower)))[1:-1]}");
|
||
% endfor
|
||
|
||
// We didn't define the 'all' shorthand using the regular helpers:shorthand
|
||
// mechanism, since it causes some very large types to be generated.
|
||
//
|
||
// Also, make sure logical properties appear before its physical
|
||
// counter-parts, in order to prevent bugs like:
|
||
//
|
||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1410028
|
||
//
|
||
// FIXME(emilio): Adopt the resolution from:
|
||
//
|
||
// https://github.com/w3c/csswg-drafts/issues/1898
|
||
//
|
||
// when there is one, whatever that is.
|
||
<%
|
||
logical_longhands = []
|
||
other_longhands = []
|
||
|
||
for p in data.longhands:
|
||
if p.name in ['direction', 'unicode-bidi']:
|
||
continue;
|
||
if not p.enabled_in_content() and not p.experimental(engine):
|
||
continue;
|
||
if p.logical:
|
||
logical_longhands.append(p.name)
|
||
else:
|
||
other_longhands.append(p.name)
|
||
|
||
data.declare_shorthand(
|
||
"all",
|
||
logical_longhands + other_longhands,
|
||
engines="gecko servo-2013 servo-2020",
|
||
spec="https://drafts.csswg.org/css-cascade-3/#all-shorthand"
|
||
)
|
||
%>
|
||
|
||
/// The max amount of longhands that the `all` shorthand will ever contain.
|
||
pub const ALL_SHORTHAND_MAX_LEN: usize = ${len(logical_longhands + 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 = [
|
||
{
|
||
"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.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
|
||
}
|
||
|
||
#[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 {
|
||
/// 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
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 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 longhand or shorthand property.
|
||
#[derive(Clone, Copy, Debug)]
|
||
pub struct NonCustomPropertyId(usize);
|
||
|
||
/// The length of all the non-custom properties.
|
||
pub const NON_CUSTOM_PROPERTY_ID_COUNT: usize =
|
||
${len(data.longhands) + len(data.shorthands) + len(data.all_aliases())};
|
||
|
||
/// The length of all counted unknown properties.
|
||
pub const COUNTED_UNKNOWN_PROPERTY_COUNT: usize = ${len(data.counted_unknown_properties)};
|
||
|
||
% 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 {
|
||
/// Returns the underlying index, used for use counter.
|
||
pub fn bit(self) -> usize {
|
||
self.0
|
||
}
|
||
|
||
#[cfg(feature = "gecko")]
|
||
#[inline]
|
||
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
||
// unsafe: guaranteed by static_assert_nscsspropertyid above.
|
||
unsafe { std::mem::transmute(self.0 as i32) }
|
||
}
|
||
|
||
/// Convert an `nsCSSPropertyID` into a `NonCustomPropertyId`.
|
||
#[cfg(feature = "gecko")]
|
||
#[inline]
|
||
pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result<Self, ()> {
|
||
let prop = prop as i32;
|
||
if prop < 0 {
|
||
return Err(());
|
||
}
|
||
if prop >= NON_CUSTOM_PROPERTY_ID_COUNT as i32 {
|
||
return Err(());
|
||
}
|
||
// unsafe: guaranteed by static_assert_nscsspropertyid above.
|
||
Ok(unsafe { std::mem::transmute(prop as usize) })
|
||
}
|
||
|
||
/// Get the property name.
|
||
#[inline]
|
||
pub fn name(self) -> &'static str {
|
||
static MAP: [&'static str; NON_CUSTOM_PROPERTY_ID_COUNT] = [
|
||
% for property in data.longhands + data.shorthands + data.all_aliases():
|
||
"${property.name}",
|
||
% endfor
|
||
];
|
||
MAP[self.0]
|
||
}
|
||
|
||
/// Returns whether this property is transitionable.
|
||
#[inline]
|
||
pub fn is_transitionable(self) -> bool {
|
||
${static_non_custom_property_id_set("TRANSITIONABLE", lambda p: p.transitionable)}
|
||
TRANSITIONABLE.contains(self)
|
||
}
|
||
|
||
/// 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)
|
||
}
|
||
|
||
#[inline]
|
||
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] }
|
||
% else:
|
||
static PREF_NAME: [Option< &str>; ${
|
||
len(data.longhands) + len(data.shorthands) + len(data.all_aliases())
|
||
}] = [
|
||
% for property in data.longhands + data.shorthands + data.all_aliases():
|
||
<%
|
||
attrs = {"servo-2013": "servo_2013_pref", "servo-2020": "servo_2020_pref"}
|
||
pref = getattr(property, attrs[engine])
|
||
%>
|
||
% if pref:
|
||
Some("${pref}"),
|
||
% else:
|
||
None,
|
||
% endif
|
||
% endfor
|
||
];
|
||
let pref = match PREF_NAME[self.0] {
|
||
None => return true,
|
||
Some(pref) => pref,
|
||
};
|
||
|
||
prefs::pref_map().get(pref).as_bool().unwrap_or(false)
|
||
% endif
|
||
};
|
||
|
||
if ALWAYS_ENABLED.contains(self) {
|
||
return true
|
||
}
|
||
|
||
if EXPERIMENTAL.contains(self) && passes_pref_check() {
|
||
return true
|
||
}
|
||
|
||
false
|
||
}
|
||
|
||
fn allowed_in(self, context: &ParserContext) -> bool {
|
||
debug_assert!(
|
||
matches!(
|
||
context.rule_type(),
|
||
CssRuleType::Keyframe | CssRuleType::Page | CssRuleType::Style
|
||
),
|
||
"Declarations are only expected inside a keyframe, page, or style rule."
|
||
);
|
||
|
||
${static_non_custom_property_id_set(
|
||
"DISALLOWED_IN_KEYFRAME_BLOCK",
|
||
lambda p: not p.allowed_in_keyframe_block
|
||
)}
|
||
${static_non_custom_property_id_set(
|
||
"DISALLOWED_IN_PAGE_RULE",
|
||
lambda p: not p.allowed_in_page_rule
|
||
)}
|
||
match context.rule_type() {
|
||
CssRuleType::Keyframe if DISALLOWED_IN_KEYFRAME_BLOCK.contains(self) => {
|
||
return false;
|
||
}
|
||
CssRuleType::Page if DISALLOWED_IN_PAGE_RULE.contains(self) => {
|
||
return false;
|
||
}
|
||
_ => {}
|
||
}
|
||
|
||
self.allowed_in_ignoring_rule_type(context)
|
||
}
|
||
|
||
|
||
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.
|
||
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]
|
||
}
|
||
|
||
/// See PropertyId::collect_property_completion_keywords.
|
||
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](f);
|
||
}
|
||
|
||
/// Turns this `NonCustomPropertyId` into a `PropertyId`.
|
||
#[inline]
|
||
pub fn to_property_id(self) -> PropertyId {
|
||
use std::mem::transmute;
|
||
if self.0 < ${len(data.longhands)} {
|
||
return unsafe {
|
||
PropertyId::Longhand(transmute(self.0 as u16))
|
||
}
|
||
}
|
||
if self.0 < ${len(data.longhands) + len(data.shorthands)} {
|
||
return unsafe {
|
||
PropertyId::Shorthand(transmute((self.0 - ${len(data.longhands)}) as u16))
|
||
}
|
||
}
|
||
assert!(self.0 < NON_CUSTOM_PROPERTY_ID_COUNT);
|
||
let alias_id: AliasId = unsafe {
|
||
transmute((self.0 - ${len(data.longhands) + len(data.shorthands)}) as u16)
|
||
};
|
||
|
||
match alias_id.aliased_property() {
|
||
AliasedPropertyId::Longhand(longhand) => PropertyId::LonghandAlias(longhand, alias_id),
|
||
AliasedPropertyId::Shorthand(shorthand) => PropertyId::ShorthandAlias(shorthand, alias_id),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<LonghandId> for NonCustomPropertyId {
|
||
#[inline]
|
||
fn from(id: LonghandId) -> Self {
|
||
NonCustomPropertyId(id as usize)
|
||
}
|
||
}
|
||
|
||
impl From<ShorthandId> for NonCustomPropertyId {
|
||
#[inline]
|
||
fn from(id: ShorthandId) -> Self {
|
||
NonCustomPropertyId((id as usize) + ${len(data.longhands)})
|
||
}
|
||
}
|
||
|
||
impl From<AliasId> for NonCustomPropertyId {
|
||
#[inline]
|
||
fn from(id: AliasId) -> Self {
|
||
NonCustomPropertyId(id as usize + ${len(data.longhands) + len(data.shorthands)})
|
||
}
|
||
}
|
||
|
||
/// A set of all properties
|
||
#[derive(Clone, PartialEq)]
|
||
pub struct NonCustomPropertyIdSet {
|
||
storage: [u32; (NON_CUSTOM_PROPERTY_ID_COUNT - 1 + 32) / 32]
|
||
}
|
||
|
||
impl NonCustomPropertyIdSet {
|
||
/// Creates an empty `NonCustomPropertyIdSet`.
|
||
pub fn new() -> Self {
|
||
Self {
|
||
storage: Default::default(),
|
||
}
|
||
}
|
||
|
||
/// Insert a non-custom-property in the set.
|
||
#[inline]
|
||
pub fn insert(&mut self, id: NonCustomPropertyId) {
|
||
let bit = id.0;
|
||
self.storage[bit / 32] |= 1 << (bit % 32);
|
||
}
|
||
|
||
/// Return whether the given property is in the set
|
||
#[inline]
|
||
pub fn contains(&self, id: NonCustomPropertyId) -> bool {
|
||
let bit = id.0;
|
||
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
|
||
}
|
||
}
|
||
|
||
<%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, Eq, Hash, PartialEq)]
|
||
pub enum LogicalGroup {
|
||
% for group in sorted(logical_groups.keys()):
|
||
/// ${group}
|
||
${to_camel_case(group)},
|
||
% endfor
|
||
}
|
||
|
||
|
||
/// A set of longhand properties
|
||
#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
|
||
pub struct LonghandIdSet {
|
||
storage: [u32; (${len(data.longhands)} - 1 + 32) / 32]
|
||
}
|
||
|
||
impl_trivial_to_shmem!(LonghandIdSet);
|
||
|
||
/// An iterator over a set of longhand ids.
|
||
pub struct LonghandIdSetIterator<'a> {
|
||
longhands: &'a LonghandIdSet,
|
||
cur: usize,
|
||
}
|
||
|
||
impl<'a> Iterator for LonghandIdSetIterator<'a> {
|
||
type Item = LonghandId;
|
||
|
||
fn next(&mut self) -> Option<Self::Item> {
|
||
use std::mem;
|
||
|
||
loop {
|
||
if self.cur >= ${len(data.longhands)} {
|
||
return None;
|
||
}
|
||
|
||
let id: LonghandId = unsafe { mem::transmute(self.cur as u16) };
|
||
self.cur += 1;
|
||
|
||
if self.longhands.contains(id) {
|
||
return Some(id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl LonghandIdSet {
|
||
#[inline]
|
||
fn reset() -> &'static Self {
|
||
${static_longhand_id_set("RESET", lambda p: not p.style_struct.inherited)}
|
||
&RESET
|
||
}
|
||
|
||
#[inline]
|
||
fn animatable() -> &'static Self {
|
||
${static_longhand_id_set("ANIMATABLE", lambda p: p.animatable)}
|
||
&ANIMATABLE
|
||
}
|
||
|
||
#[inline]
|
||
fn discrete_animatable() -> &'static Self {
|
||
${static_longhand_id_set("DISCRETE_ANIMATABLE", lambda p: p.animation_value_type == "discrete")}
|
||
&DISCRETE_ANIMATABLE
|
||
}
|
||
|
||
#[inline]
|
||
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 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
|
||
}
|
||
|
||
/// 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 padding properties for the purpose of disabling
|
||
/// native appearance.
|
||
#[inline]
|
||
pub fn padding_properties() -> &'static Self {
|
||
<% assert "padding" in logical_groups %>
|
||
${static_longhand_id_set(
|
||
"PADDING_PROPERTIES",
|
||
lambda p: p.logical_group == "padding"
|
||
)}
|
||
&PADDING_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.name in ["background-color", "background-image"]
|
||
)}
|
||
&BORDER_BACKGROUND_PROPERTIES
|
||
}
|
||
|
||
/// Iterate over the current longhand id set.
|
||
pub fn iter(&self) -> LonghandIdSetIterator {
|
||
LonghandIdSetIterator { longhands: self, cur: 0, }
|
||
}
|
||
|
||
/// Returns whether this set contains at least every longhand that `other`
|
||
/// also contains.
|
||
pub fn contains_all(&self, other: &Self) -> bool {
|
||
for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
|
||
if (*self_cell & *other_cell) != *other_cell {
|
||
return false;
|
||
}
|
||
}
|
||
true
|
||
}
|
||
|
||
/// Returns whether this set contains any longhand that `other` also contains.
|
||
pub fn contains_any(&self, other: &Self) -> bool {
|
||
for (self_cell, other_cell) in self.storage.iter().zip(other.storage.iter()) {
|
||
if (*self_cell & *other_cell) != 0 {
|
||
return true;
|
||
}
|
||
}
|
||
false
|
||
}
|
||
|
||
/// Remove all the given properties from the set.
|
||
#[inline]
|
||
pub fn remove_all(&mut self, other: &Self) {
|
||
for (self_cell, other_cell) in self.storage.iter_mut().zip(other.storage.iter()) {
|
||
*self_cell &= !*other_cell;
|
||
}
|
||
}
|
||
|
||
/// Create an empty set
|
||
#[inline]
|
||
pub fn new() -> LonghandIdSet {
|
||
LonghandIdSet { storage: [0; (${len(data.longhands)} - 1 + 32) / 32] }
|
||
}
|
||
|
||
/// Return whether the given property is in the set
|
||
#[inline]
|
||
pub fn contains(&self, id: LonghandId) -> bool {
|
||
let bit = id as usize;
|
||
(self.storage[bit / 32] & (1 << (bit % 32))) != 0
|
||
}
|
||
|
||
/// Return whether this set contains any reset longhand.
|
||
#[inline]
|
||
pub fn contains_any_reset(&self) -> bool {
|
||
self.contains_any(Self::reset())
|
||
}
|
||
|
||
/// Add the given property to the set
|
||
#[inline]
|
||
pub fn insert(&mut self, id: LonghandId) {
|
||
let bit = id as usize;
|
||
self.storage[bit / 32] |= 1 << (bit % 32);
|
||
}
|
||
|
||
/// Remove the given property from the set
|
||
#[inline]
|
||
pub fn remove(&mut self, id: LonghandId) {
|
||
let bit = id as usize;
|
||
self.storage[bit / 32] &= !(1 << (bit % 32));
|
||
}
|
||
|
||
/// Clear all bits
|
||
#[inline]
|
||
pub fn clear(&mut self) {
|
||
for cell in &mut self.storage {
|
||
*cell = 0
|
||
}
|
||
}
|
||
|
||
/// Returns whether the set is empty.
|
||
#[inline]
|
||
pub fn is_empty(&self) -> bool {
|
||
self.storage.iter().all(|c| *c == 0)
|
||
}
|
||
}
|
||
|
||
/// An enum to represent a CSS Wide keyword.
|
||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo,
|
||
ToCss, ToShmem)]
|
||
pub enum CSSWideKeyword {
|
||
/// The `initial` keyword.
|
||
Initial,
|
||
/// The `inherit` keyword.
|
||
Inherit,
|
||
/// The `unset` keyword.
|
||
Unset,
|
||
/// The `revert` keyword.
|
||
Revert,
|
||
}
|
||
|
||
impl CSSWideKeyword {
|
||
fn to_str(&self) -> &'static str {
|
||
match *self {
|
||
CSSWideKeyword::Initial => "initial",
|
||
CSSWideKeyword::Inherit => "inherit",
|
||
CSSWideKeyword::Unset => "unset",
|
||
CSSWideKeyword::Revert => "revert",
|
||
}
|
||
}
|
||
}
|
||
|
||
impl CSSWideKeyword {
|
||
fn parse(input: &mut Parser) -> Result<Self, ()> {
|
||
let keyword = {
|
||
let ident = input.expect_ident().map_err(|_| ())?;
|
||
match_ignore_ascii_case! { ident,
|
||
// If modifying this set of keyword, also update values::CustomIdent::from_ident
|
||
"initial" => CSSWideKeyword::Initial,
|
||
"inherit" => CSSWideKeyword::Inherit,
|
||
"unset" => CSSWideKeyword::Unset,
|
||
"revert" => CSSWideKeyword::Revert,
|
||
_ => return Err(()),
|
||
}
|
||
};
|
||
input.expect_exhausted().map_err(|_| ())?;
|
||
Ok(keyword)
|
||
}
|
||
}
|
||
|
||
bitflags! {
|
||
/// A set of flags for properties.
|
||
pub struct PropertyFlags: u16 {
|
||
/// This property requires a stacking context.
|
||
const CREATES_STACKING_CONTEXT = 1 << 0;
|
||
/// This property has values that can establish a containing block for
|
||
/// fixed positioned and absolutely positioned elements.
|
||
const FIXPOS_CB = 1 << 1;
|
||
/// This property has values that can establish a containing block for
|
||
/// absolutely positioned elements.
|
||
const ABSPOS_CB = 1 << 2;
|
||
/// This longhand property applies to ::first-letter.
|
||
const APPLIES_TO_FIRST_LETTER = 1 << 3;
|
||
/// This longhand property applies to ::first-line.
|
||
const APPLIES_TO_FIRST_LINE = 1 << 4;
|
||
/// This longhand property applies to ::placeholder.
|
||
const APPLIES_TO_PLACEHOLDER = 1 << 5;
|
||
/// This longhand property applies to ::cue.
|
||
const APPLIES_TO_CUE = 1 << 6;
|
||
/// This longhand property applies to ::marker.
|
||
const APPLIES_TO_MARKER = 1 << 7;
|
||
/// This property is a legacy shorthand.
|
||
///
|
||
/// https://drafts.csswg.org/css-cascade/#legacy-shorthand
|
||
const IS_LEGACY_SHORTHAND = 1 << 8;
|
||
|
||
/* The following flags are currently not used in Rust code, they
|
||
* only need to be listed in corresponding properties so that
|
||
* they can be checked in the C++ side via ServoCSSPropList.h. */
|
||
/// This property can be animated on the compositor.
|
||
const CAN_ANIMATE_ON_COMPOSITOR = 0;
|
||
/// This shorthand property is accessible from getComputedStyle.
|
||
const SHORTHAND_IN_GETCS = 0;
|
||
}
|
||
}
|
||
|
||
/// 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
|
||
}
|
||
|
||
impl ToCss for LonghandId {
|
||
#[inline]
|
||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
{
|
||
dest.write_str(self.name())
|
||
}
|
||
}
|
||
|
||
impl fmt::Debug for LonghandId {
|
||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||
formatter.write_str(self.name())
|
||
}
|
||
}
|
||
|
||
impl LonghandId {
|
||
/// Get the name of this longhand property.
|
||
#[inline]
|
||
pub fn name(&self) -> &'static str {
|
||
NonCustomPropertyId::from(*self).name()
|
||
}
|
||
|
||
/// Returns whether the longhand property is inherited by default.
|
||
#[inline]
|
||
pub fn inherited(self) -> bool {
|
||
!LonghandIdSet::reset().contains(self)
|
||
}
|
||
|
||
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
|
||
% for property in data.longhands:
|
||
static ${property.ident.upper()}: &'static [ShorthandId] = &[
|
||
% for shorthand in longhand_to_shorthand_map.get(property.ident, []):
|
||
ShorthandId::${shorthand},
|
||
% endfor
|
||
];
|
||
% endfor
|
||
|
||
NonCustomPropertyIterator {
|
||
filter: NonCustomPropertyId::from(*self).enabled_for_all_content(),
|
||
iter: match *self {
|
||
% for property in data.longhands:
|
||
LonghandId::${property.camel_case} => ${property.ident.upper()},
|
||
% endfor
|
||
}.iter(),
|
||
}
|
||
}
|
||
|
||
// TODO(emilio): Should we use a function table like CASCADE_PROPERTY does
|
||
// to avoid blowing up code-size here?
|
||
fn parse_value<'i, 't>(
|
||
&self,
|
||
context: &ParserContext,
|
||
input: &mut Parser<'i, 't>,
|
||
) -> Result<PropertyDeclaration, ParseError<'i>> {
|
||
match *self {
|
||
% for property in data.longhands:
|
||
LonghandId::${property.camel_case} => {
|
||
longhands::${property.ident}::parse_declared(context, input)
|
||
}
|
||
% endfor
|
||
}
|
||
}
|
||
|
||
/// Returns whether this property is animatable.
|
||
#[inline]
|
||
pub fn is_animatable(self) -> bool {
|
||
LonghandIdSet::animatable().contains(self)
|
||
}
|
||
|
||
/// Returns whether this property is animatable in a discrete way.
|
||
#[inline]
|
||
pub fn is_discrete_animatable(self) -> bool {
|
||
LonghandIdSet::discrete_animatable().contains(self)
|
||
}
|
||
|
||
/// Converts from a LonghandId to an adequate nsCSSPropertyID.
|
||
#[cfg(feature = "gecko")]
|
||
#[inline]
|
||
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
||
NonCustomPropertyId::from(self).to_nscsspropertyid()
|
||
}
|
||
|
||
#[cfg(feature = "gecko")]
|
||
#[allow(non_upper_case_globals)]
|
||
/// Returns a longhand id from Gecko's nsCSSPropertyID.
|
||
pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
|
||
match PropertyId::from_nscsspropertyid(id) {
|
||
Ok(PropertyId::Longhand(id)) |
|
||
Ok(PropertyId::LonghandAlias(id, _)) => Ok(id),
|
||
_ => Err(()),
|
||
}
|
||
}
|
||
|
||
/// Return whether this property is logical.
|
||
#[inline]
|
||
pub fn is_logical(self) -> bool {
|
||
LonghandIdSet::logical().contains(self)
|
||
}
|
||
|
||
/// If this is a logical property, return the corresponding physical one in
|
||
/// the given writing mode.
|
||
///
|
||
/// Otherwise, return unchanged.
|
||
#[inline]
|
||
pub fn to_physical(&self, wm: WritingMode) -> Self {
|
||
match *self {
|
||
% for property in data.longhands:
|
||
% if property.logical:
|
||
<% logical_group = property.logical_group %>
|
||
LonghandId::${property.camel_case} => {
|
||
<%helpers:logical_setter_helper name="${property.name}">
|
||
<%def name="inner(physical_ident)">
|
||
<%
|
||
physical_name = physical_ident.replace("_", "-")
|
||
physical_property = data.longhands_by_name[physical_name]
|
||
assert logical_group == physical_property.logical_group
|
||
%>
|
||
LonghandId::${to_camel_case(physical_ident)}
|
||
</%def>
|
||
</%helpers:logical_setter_helper>
|
||
}
|
||
% endif
|
||
% endfor
|
||
_ => *self
|
||
}
|
||
}
|
||
|
||
/// Return the logical group of this longhand property.
|
||
pub fn logical_group(&self) -> Option<LogicalGroup> {
|
||
const LOGICAL_GROUPS: [Option<LogicalGroup>; ${len(data.longhands)}] = [
|
||
% for prop in data.longhands:
|
||
% if prop.logical_group:
|
||
Some(LogicalGroup::${to_camel_case(prop.logical_group)}),
|
||
% else:
|
||
None,
|
||
% endif
|
||
% endfor
|
||
];
|
||
LOGICAL_GROUPS[*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_truncate(FLAGS[self as usize])
|
||
}
|
||
|
||
/// 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.
|
||
fn is_visited_dependent(&self) -> bool {
|
||
matches!(*self,
|
||
% if engine == "gecko":
|
||
LonghandId::ColumnRuleColor |
|
||
LonghandId::TextEmphasisColor |
|
||
LonghandId::WebkitTextFillColor |
|
||
LonghandId::WebkitTextStrokeColor |
|
||
LonghandId::TextDecorationColor |
|
||
LonghandId::Fill |
|
||
LonghandId::Stroke |
|
||
LonghandId::CaretColor |
|
||
% endif
|
||
LonghandId::BackgroundColor |
|
||
LonghandId::BorderTopColor |
|
||
LonghandId::BorderRightColor |
|
||
LonghandId::BorderBottomColor |
|
||
LonghandId::BorderLeftColor |
|
||
% if engine in ["gecko", "servo-2013"]:
|
||
LonghandId::OutlineColor |
|
||
% endif
|
||
LonghandId::Color
|
||
)
|
||
}
|
||
|
||
/// Returns true if the property is one that is ignored when document
|
||
/// colors are disabled.
|
||
#[inline]
|
||
fn ignored_when_document_colors_disabled(self) -> bool {
|
||
LonghandIdSet::ignored_when_colors_disabled().contains(self)
|
||
}
|
||
|
||
/// The computed value of some properties depends on the (sometimes
|
||
/// computed) value of *other* properties.
|
||
///
|
||
/// So we classify properties into "early" and "other", such that the only
|
||
/// dependencies can be from "other" to "early".
|
||
///
|
||
/// Unfortunately, it’s not easy to check that this classification is
|
||
/// correct.
|
||
fn is_early_property(&self) -> bool {
|
||
matches!(*self,
|
||
% if engine == "gecko":
|
||
|
||
// Needed to properly compute the writing mode, to resolve logical
|
||
// properties, and similar stuff. In this block instead of along
|
||
// `WritingMode` and `Direction` just for convenience, since it's
|
||
// Gecko-only (for now at least).
|
||
//
|
||
// see WritingMode::new.
|
||
LonghandId::TextOrientation |
|
||
|
||
// Needed to properly compute the zoomed font-size.
|
||
//
|
||
// FIXME(emilio): This could probably just be a cascade flag like
|
||
// IN_SVG_SUBTREE or such, and we could nuke this property.
|
||
LonghandId::XTextZoom |
|
||
|
||
// Needed to do font-size computation in a language-dependent way.
|
||
LonghandId::XLang |
|
||
// Needed for ruby to respect language-dependent min-font-size
|
||
// preferences properly, see bug 1165538.
|
||
LonghandId::MozMinFontSizeRatio |
|
||
|
||
// Needed to do font-size for MathML. :(
|
||
LonghandId::MozScriptLevel |
|
||
% endif
|
||
|
||
// Needed to compute the first available font, in order to
|
||
// compute font-relative units correctly.
|
||
LonghandId::FontSize |
|
||
LonghandId::FontWeight |
|
||
LonghandId::FontStretch |
|
||
LonghandId::FontStyle |
|
||
LonghandId::FontFamily |
|
||
|
||
// Needed to properly compute the writing mode, to resolve logical
|
||
// properties, and similar stuff.
|
||
LonghandId::WritingMode |
|
||
LonghandId::Direction
|
||
)
|
||
}
|
||
}
|
||
|
||
/// An iterator over all the property ids that are enabled for a given
|
||
/// shorthand, if that shorthand is enabled for all content too.
|
||
pub struct NonCustomPropertyIterator<Item: 'static> {
|
||
filter: bool,
|
||
iter: std::slice::Iter<'static, Item>,
|
||
}
|
||
|
||
impl<Item> Iterator for NonCustomPropertyIterator<Item>
|
||
where
|
||
Item: 'static + Copy + Into<NonCustomPropertyId>,
|
||
{
|
||
type Item = Item;
|
||
|
||
fn next(&mut self) -> Option<Self::Item> {
|
||
loop {
|
||
let id = *self.iter.next()?;
|
||
if !self.filter || id.into().enabled_for_all_content() {
|
||
return Some(id)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 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 ToCss for ShorthandId {
|
||
#[inline]
|
||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
{
|
||
dest.write_str(self.name())
|
||
}
|
||
}
|
||
|
||
impl ShorthandId {
|
||
/// Get the name for this shorthand property.
|
||
#[inline]
|
||
pub fn name(&self) -> &'static str {
|
||
NonCustomPropertyId::from(*self).name()
|
||
}
|
||
|
||
/// Converts from a ShorthandId to an adequate nsCSSPropertyID.
|
||
#[cfg(feature = "gecko")]
|
||
#[inline]
|
||
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
|
||
NonCustomPropertyId::from(self).to_nscsspropertyid()
|
||
}
|
||
|
||
/// Converts from a nsCSSPropertyID to a ShorthandId.
|
||
#[cfg(feature = "gecko")]
|
||
#[inline]
|
||
pub fn from_nscsspropertyid(prop: nsCSSPropertyID) -> Result<Self, ()> {
|
||
PropertyId::from_nscsspropertyid(prop)?.as_shorthand().map_err(|_| ())
|
||
}
|
||
|
||
/// Get the longhand ids that form this shorthand.
|
||
pub fn longhands(&self) -> NonCustomPropertyIterator<LonghandId> {
|
||
% for property in data.shorthands:
|
||
static ${property.ident.upper()}: &'static [LonghandId] = &[
|
||
% for sub in property.sub_properties:
|
||
LonghandId::${sub.camel_case},
|
||
% endfor
|
||
];
|
||
% endfor
|
||
NonCustomPropertyIterator {
|
||
filter: NonCustomPropertyId::from(*self).enabled_for_all_content(),
|
||
iter: match *self {
|
||
% for property in data.shorthands:
|
||
ShorthandId::${property.camel_case} => ${property.ident.upper()},
|
||
% endfor
|
||
}.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<'a, W, I>(
|
||
&self,
|
||
declarations: I,
|
||
dest: &mut CssWriter<W>,
|
||
) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
I: Iterator<Item=&'a PropertyDeclaration>,
|
||
{
|
||
match *self {
|
||
ShorthandId::All => {
|
||
// 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.
|
||
Err(fmt::Error)
|
||
}
|
||
% for property in data.shorthands_except_all():
|
||
ShorthandId::${property.camel_case} => {
|
||
match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) {
|
||
Ok(longhands) => longhands.to_css(dest),
|
||
Err(_) => Err(fmt::Error)
|
||
}
|
||
},
|
||
% endfor
|
||
}
|
||
}
|
||
|
||
/// Finds and returns an appendable value for the given declarations.
|
||
///
|
||
/// Returns the optional appendable value.
|
||
pub fn get_shorthand_appendable_value<'a, I>(
|
||
self,
|
||
declarations: I,
|
||
) -> Option<AppendableValue<'a, I::IntoIter>>
|
||
where
|
||
I: IntoIterator<Item=&'a PropertyDeclaration>,
|
||
I::IntoIter: Clone,
|
||
{
|
||
let declarations = declarations.into_iter();
|
||
|
||
// Only cloning iterators (a few pointers each) not declarations.
|
||
let mut declarations2 = declarations.clone();
|
||
let mut declarations3 = declarations.clone();
|
||
|
||
let first_declaration = declarations2.next()?;
|
||
|
||
// https://drafts.csswg.org/css-variables/#variables-in-shorthands
|
||
if let Some(css) = first_declaration.with_variables_from_shorthand(self) {
|
||
if declarations2.all(|d| d.with_variables_from_shorthand(self) == Some(css)) {
|
||
return Some(AppendableValue::Css {
|
||
css: CssStringBorrow::from(css),
|
||
with_variables: true,
|
||
});
|
||
}
|
||
return None;
|
||
}
|
||
|
||
// Check whether they are all the same CSS-wide keyword.
|
||
if let Some(keyword) = first_declaration.get_css_wide_keyword() {
|
||
if declarations2.all(|d| d.get_css_wide_keyword() == Some(keyword)) {
|
||
return Some(AppendableValue::Css {
|
||
css: CssStringBorrow::from(keyword.to_str()),
|
||
with_variables: false,
|
||
});
|
||
}
|
||
return None;
|
||
}
|
||
|
||
// Check whether all declarations can be serialized as part of shorthand.
|
||
if declarations3.all(|d| d.may_serialize_as_part_of_shorthand()) {
|
||
return Some(AppendableValue::DeclarationsForShorthand(self, declarations));
|
||
}
|
||
|
||
None
|
||
}
|
||
|
||
/// 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_truncate(FLAGS[self as usize])
|
||
}
|
||
|
||
/// Returns whether this property is a legacy shorthand.
|
||
#[inline]
|
||
pub fn is_legacy_shorthand(self) -> bool {
|
||
self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND)
|
||
}
|
||
|
||
/// 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]
|
||
}
|
||
|
||
fn parse_into<'i, 't>(
|
||
&self,
|
||
declarations: &mut SourcePropertyDeclaration,
|
||
context: &ParserContext,
|
||
input: &mut Parser<'i, 't>,
|
||
) -> Result<(), ParseError<'i>> {
|
||
match *self {
|
||
% for shorthand in data.shorthands_except_all():
|
||
ShorthandId::${shorthand.camel_case} => {
|
||
shorthands::${shorthand.ident}::parse_into(declarations, context, input)
|
||
}
|
||
% endfor
|
||
// 'all' accepts no value other than CSS-wide keywords
|
||
ShorthandId::All => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
|
||
}
|
||
}
|
||
}
|
||
|
||
/// An unparsed property value that contains `var()` functions.
|
||
#[derive(Debug, Eq, PartialEq, ToShmem)]
|
||
pub struct UnparsedValue {
|
||
/// The css serialization for this value.
|
||
css: String,
|
||
/// The first token type for this serialization.
|
||
first_token_type: TokenSerializationType,
|
||
/// The url data for resolving url values.
|
||
url_data: UrlExtraData,
|
||
/// The shorthand this came from.
|
||
from_shorthand: Option<ShorthandId>,
|
||
}
|
||
|
||
impl ToCss for UnparsedValue {
|
||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
{
|
||
// https://drafts.csswg.org/css-variables/#variables-in-shorthands
|
||
if self.from_shorthand.is_none() {
|
||
dest.write_str(&*self.css)?;
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl UnparsedValue {
|
||
fn substitute_variables(
|
||
&self,
|
||
longhand_id: LonghandId,
|
||
custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>,
|
||
quirks_mode: QuirksMode,
|
||
device: &Device,
|
||
) -> PropertyDeclaration {
|
||
let invalid_at_computed_value_time = || {
|
||
let keyword = if longhand_id.inherited() {
|
||
CSSWideKeyword::Inherit
|
||
} else {
|
||
CSSWideKeyword::Initial
|
||
};
|
||
PropertyDeclaration::css_wide_keyword(longhand_id, keyword)
|
||
};
|
||
|
||
let css = match crate::custom_properties::substitute(
|
||
&self.css,
|
||
self.first_token_type,
|
||
custom_properties,
|
||
device,
|
||
) {
|
||
Ok(css) => css,
|
||
Err(..) => return invalid_at_computed_value_time(),
|
||
};
|
||
|
||
// As of this writing, only the base URL is used for property
|
||
// values.
|
||
//
|
||
// NOTE(emilio): we intentionally pase `None` as the rule type here.
|
||
// If something starts depending on it, it's probably a bug, since
|
||
// it'd change how values are parsed depending on whether we're in a
|
||
// @keyframes rule or not, for example... So think twice about
|
||
// whether you want to do this!
|
||
//
|
||
// FIXME(emilio): ParsingMode is slightly fishy...
|
||
let context = ParserContext::new(
|
||
Origin::Author,
|
||
&self.url_data,
|
||
None,
|
||
ParsingMode::DEFAULT,
|
||
quirks_mode,
|
||
None,
|
||
None,
|
||
);
|
||
|
||
let mut input = ParserInput::new(&css);
|
||
let mut input = Parser::new(&mut input);
|
||
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
|
||
if let Ok(keyword) = input.try(CSSWideKeyword::parse) {
|
||
return PropertyDeclaration::css_wide_keyword(longhand_id, keyword);
|
||
}
|
||
|
||
let declaration = input.parse_entirely(|input| {
|
||
match self.from_shorthand {
|
||
None => longhand_id.parse_value(&context, input),
|
||
Some(ShorthandId::All) => {
|
||
// No need to parse the 'all' shorthand as anything other
|
||
// than a CSS-wide keyword, after variable substitution.
|
||
Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("all".into())))
|
||
}
|
||
% for shorthand in data.shorthands_except_all():
|
||
Some(ShorthandId::${shorthand.camel_case}) => {
|
||
shorthands::${shorthand.ident}::parse_value(&context, input)
|
||
.map(|longhands| {
|
||
match longhand_id {
|
||
<% seen = set() %>
|
||
% for property in shorthand.sub_properties:
|
||
// When animating logical properties, we end up
|
||
// physicalizing the value during the animation, but
|
||
// the value still comes from the logical shorthand.
|
||
//
|
||
// So we need to handle the physical properties too.
|
||
% for prop in [property] + property.all_physical_mapped_properties(data):
|
||
% if prop.camel_case not in seen:
|
||
LonghandId::${prop.camel_case} => {
|
||
PropertyDeclaration::${prop.camel_case}(
|
||
longhands.${property.ident}
|
||
)
|
||
}
|
||
<% seen.add(prop.camel_case) %>
|
||
% endif
|
||
% endfor
|
||
% endfor
|
||
<% del seen %>
|
||
_ => unreachable!()
|
||
}
|
||
})
|
||
}
|
||
% endfor
|
||
}
|
||
});
|
||
|
||
match declaration {
|
||
Ok(decl) => decl,
|
||
Err(..) => invalid_at_computed_value_time(),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// An identifier for a given property declaration, which can be either a
|
||
/// longhand or a custom property.
|
||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
|
||
pub enum PropertyDeclarationId<'a> {
|
||
/// A longhand.
|
||
Longhand(LonghandId),
|
||
/// A custom property declaration.
|
||
Custom(&'a crate::custom_properties::Name),
|
||
}
|
||
|
||
impl<'a> ToCss for PropertyDeclarationId<'a> {
|
||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
{
|
||
match *self {
|
||
PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()),
|
||
PropertyDeclarationId::Custom(ref name) => {
|
||
dest.write_str("--")?;
|
||
serialize_atom_name(name, dest)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl<'a> PropertyDeclarationId<'a> {
|
||
/// Whether a given declaration id is either the same as `other`, or a
|
||
/// longhand of it.
|
||
pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool {
|
||
match *self {
|
||
PropertyDeclarationId::Longhand(id) => {
|
||
match *other {
|
||
PropertyId::Longhand(other_id) |
|
||
PropertyId::LonghandAlias(other_id, _) => id == other_id,
|
||
PropertyId::Shorthand(shorthand) |
|
||
PropertyId::ShorthandAlias(shorthand, _) => self.is_longhand_of(shorthand),
|
||
PropertyId::Custom(_) => false,
|
||
}
|
||
}
|
||
PropertyDeclarationId::Custom(name) => {
|
||
matches!(*other, PropertyId::Custom(ref other_name) if name == other_name)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Whether a given declaration id is a longhand belonging to this
|
||
/// shorthand.
|
||
pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool {
|
||
match *self {
|
||
PropertyDeclarationId::Longhand(ref id) => id.shorthands().any(|s| s == shorthand),
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
/// Returns the name of the property without CSS escaping.
|
||
pub fn name(&self) -> Cow<'static, str> {
|
||
match *self {
|
||
PropertyDeclarationId::Longhand(id) => id.name().into(),
|
||
PropertyDeclarationId::Custom(name) => {
|
||
let mut s = String::new();
|
||
write!(&mut s, "--{}", name).unwrap();
|
||
s.into()
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Returns longhand id if it is, None otherwise.
|
||
#[inline]
|
||
pub fn as_longhand(&self) -> Option<LonghandId> {
|
||
match *self {
|
||
PropertyDeclarationId::Longhand(id) => Some(id),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Servo's representation of a CSS property, that is, either a longhand, a
|
||
/// shorthand, or a custom property.
|
||
#[derive(Clone, Eq, PartialEq)]
|
||
pub enum PropertyId {
|
||
/// A longhand property.
|
||
Longhand(LonghandId),
|
||
/// A shorthand property.
|
||
Shorthand(ShorthandId),
|
||
/// An alias for a longhand property.
|
||
LonghandAlias(LonghandId, AliasId),
|
||
/// An alias for a shorthand property.
|
||
ShorthandAlias(ShorthandId, AliasId),
|
||
/// A custom property.
|
||
Custom(crate::custom_properties::Name),
|
||
}
|
||
|
||
impl fmt::Debug for PropertyId {
|
||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||
self.to_css(&mut CssWriter::new(formatter))
|
||
}
|
||
}
|
||
|
||
impl ToCss for PropertyId {
|
||
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
{
|
||
match *self {
|
||
PropertyId::Longhand(id) => dest.write_str(id.name()),
|
||
PropertyId::Shorthand(id) => dest.write_str(id.name()),
|
||
PropertyId::LonghandAlias(id, _) => dest.write_str(id.name()),
|
||
PropertyId::ShorthandAlias(id, _) => dest.write_str(id.name()),
|
||
PropertyId::Custom(ref name) => {
|
||
dest.write_str("--")?;
|
||
serialize_atom_name(name, dest)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 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_id -> CountedUnknownProperty = {
|
||
% for property in data.counted_unknown_properties:
|
||
"${property.name}" => CountedUnknownProperty::${property.camel_case},
|
||
% endfor
|
||
}
|
||
}
|
||
unknown_id(property_name).cloned()
|
||
}
|
||
|
||
/// Returns the underlying index, used for use counter.
|
||
#[inline]
|
||
pub fn bit(self) -> usize {
|
||
self as usize
|
||
}
|
||
}
|
||
|
||
impl PropertyId {
|
||
/// Return the longhand id that this property id represents.
|
||
#[inline]
|
||
pub fn longhand_id(&self) -> Option<LonghandId> {
|
||
Some(match *self {
|
||
PropertyId::Longhand(id) => id,
|
||
PropertyId::LonghandAlias(id, _) => id,
|
||
_ => return None,
|
||
})
|
||
}
|
||
|
||
/// Returns a given property from the given name, _regardless of whether it
|
||
/// is enabled or not_, or Err(()) for unknown properties.
|
||
///
|
||
/// Do not use for non-testing purposes.
|
||
pub fn parse_unchecked_for_testing(name: &str) -> Result<Self, ()> {
|
||
Self::parse_unchecked(name, None)
|
||
}
|
||
|
||
/// Returns a given property from the given name, _regardless of whether it
|
||
/// is enabled or not_, or Err(()) for unknown properties.
|
||
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.
|
||
#[allow(dead_code)]
|
||
pub enum StaticId {
|
||
Longhand(LonghandId),
|
||
Shorthand(ShorthandId),
|
||
LonghandAlias(LonghandId, AliasId),
|
||
ShorthandAlias(ShorthandId, AliasId),
|
||
CountedUnknown(CountedUnknownProperty),
|
||
}
|
||
ascii_case_insensitive_phf_map! {
|
||
static_id -> StaticId = {
|
||
% for (kind, properties) in [("Longhand", data.longhands), ("Shorthand", data.shorthands)]:
|
||
% for property in properties:
|
||
"${property.name}" => StaticId::${kind}(${kind}Id::${property.camel_case}),
|
||
% for alias in property.alias:
|
||
"${alias.name}" => {
|
||
StaticId::${kind}Alias(
|
||
${kind}Id::${property.camel_case},
|
||
AliasId::${alias.camel_case},
|
||
)
|
||
},
|
||
% endfor
|
||
% endfor
|
||
% endfor
|
||
% for property in data.counted_unknown_properties:
|
||
"${property.name}" => {
|
||
StaticId::CountedUnknown(CountedUnknownProperty::${property.camel_case})
|
||
},
|
||
% endfor
|
||
}
|
||
}
|
||
|
||
if let Some(id) = static_id(property_name) {
|
||
return Ok(match *id {
|
||
StaticId::Longhand(id) => PropertyId::Longhand(id),
|
||
StaticId::Shorthand(id) => {
|
||
#[cfg(feature = "gecko")]
|
||
{
|
||
// We want to count `zoom` even if disabled.
|
||
if matches!(id, ShorthandId::Zoom) {
|
||
if let Some(counters) = use_counters {
|
||
counters.non_custom_properties.record(id.into());
|
||
}
|
||
}
|
||
}
|
||
|
||
PropertyId::Shorthand(id)
|
||
},
|
||
StaticId::LonghandAlias(id, alias) => PropertyId::LonghandAlias(id, alias),
|
||
StaticId::ShorthandAlias(id, alias) => PropertyId::ShorthandAlias(id, alias),
|
||
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)))
|
||
}
|
||
|
||
/// Parses a property name, and returns an error if it's unknown or isn't
|
||
/// enabled for all content.
|
||
#[inline]
|
||
pub fn parse_enabled_for_all_content(name: &str) -> Result<Self, ()> {
|
||
let id = Self::parse_unchecked(name, None)?;
|
||
|
||
if !id.enabled_for_all_content() {
|
||
return Err(());
|
||
}
|
||
|
||
Ok(id)
|
||
}
|
||
|
||
|
||
/// Parses a property name, and returns an error if it's unknown or isn't
|
||
/// allowed in this context.
|
||
#[inline]
|
||
pub fn parse(name: &str, context: &ParserContext) -> Result<Self, ()> {
|
||
let id = Self::parse_unchecked(name, context.use_counters)?;
|
||
|
||
if !id.allowed_in(context) {
|
||
return Err(());
|
||
}
|
||
|
||
Ok(id)
|
||
}
|
||
|
||
/// Parses a property name, and returns an error if it's unknown or isn't
|
||
/// allowed in this context, ignoring the rule_type checks.
|
||
///
|
||
/// This is useful for parsing stuff from CSS values, for example.
|
||
#[inline]
|
||
pub fn parse_ignoring_rule_type(
|
||
name: &str,
|
||
context: &ParserContext,
|
||
) -> Result<Self, ()> {
|
||
let id = Self::parse_unchecked(name, None)?;
|
||
|
||
if !id.allowed_in_ignoring_rule_type(context) {
|
||
return Err(());
|
||
}
|
||
|
||
Ok(id)
|
||
}
|
||
|
||
/// Returns a property id from Gecko's nsCSSPropertyID.
|
||
#[cfg(feature = "gecko")]
|
||
#[allow(non_upper_case_globals)]
|
||
#[inline]
|
||
pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
|
||
Ok(NonCustomPropertyId::from_nscsspropertyid(id)?.to_property_id())
|
||
}
|
||
|
||
/// Returns true if the property is a shorthand or shorthand alias.
|
||
#[inline]
|
||
pub fn is_shorthand(&self) -> bool {
|
||
self.as_shorthand().is_ok()
|
||
}
|
||
|
||
/// Given this property id, get it either as a shorthand or as a
|
||
/// `PropertyDeclarationId`.
|
||
pub fn as_shorthand(&self) -> Result<ShorthandId, PropertyDeclarationId> {
|
||
match *self {
|
||
PropertyId::ShorthandAlias(id, _) |
|
||
PropertyId::Shorthand(id) => Ok(id),
|
||
PropertyId::LonghandAlias(id, _) |
|
||
PropertyId::Longhand(id) => Err(PropertyDeclarationId::Longhand(id)),
|
||
PropertyId::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)),
|
||
}
|
||
}
|
||
|
||
/// Returns the `NonCustomPropertyId` corresponding to this property id.
|
||
pub fn non_custom_id(&self) -> Option<NonCustomPropertyId> {
|
||
Some(match *self {
|
||
PropertyId::Custom(_) => return None,
|
||
PropertyId::Shorthand(shorthand_id) => shorthand_id.into(),
|
||
PropertyId::Longhand(longhand_id) => longhand_id.into(),
|
||
PropertyId::ShorthandAlias(_, alias_id) => alias_id.into(),
|
||
PropertyId::LonghandAlias(_, alias_id) => alias_id.into(),
|
||
})
|
||
}
|
||
|
||
/// Returns non-alias NonCustomPropertyId corresponding to this
|
||
/// property id.
|
||
fn non_custom_non_alias_id(&self) -> Option<NonCustomPropertyId> {
|
||
Some(match *self {
|
||
PropertyId::Custom(_) => return None,
|
||
PropertyId::Shorthand(id) => id.into(),
|
||
PropertyId::Longhand(id) => id.into(),
|
||
PropertyId::ShorthandAlias(id, _) => id.into(),
|
||
PropertyId::LonghandAlias(id, _) => id.into(),
|
||
})
|
||
}
|
||
|
||
/// Whether the property is enabled for all content regardless of the
|
||
/// stylesheet it was declared on (that is, in practice only checks prefs).
|
||
#[inline]
|
||
pub fn enabled_for_all_content(&self) -> bool {
|
||
let id = match self.non_custom_id() {
|
||
// Custom properties are allowed everywhere
|
||
None => return true,
|
||
Some(id) => id,
|
||
};
|
||
|
||
id.enabled_for_all_content()
|
||
}
|
||
|
||
/// Converts this PropertyId in nsCSSPropertyID, resolving aliases to the
|
||
/// resolved property, and returning eCSSPropertyExtra_variable for custom
|
||
/// properties.
|
||
#[cfg(feature = "gecko")]
|
||
#[inline]
|
||
pub fn to_nscsspropertyid_resolving_aliases(&self) -> nsCSSPropertyID {
|
||
match self.non_custom_non_alias_id() {
|
||
Some(id) => id.to_nscsspropertyid(),
|
||
None => nsCSSPropertyID::eCSSPropertyExtra_variable,
|
||
}
|
||
}
|
||
|
||
fn allowed_in(&self, context: &ParserContext) -> bool {
|
||
let id = match self.non_custom_id() {
|
||
// Custom properties are allowed everywhere
|
||
None => return true,
|
||
Some(id) => id,
|
||
};
|
||
id.allowed_in(context)
|
||
}
|
||
|
||
#[inline]
|
||
fn allowed_in_ignoring_rule_type(&self, context: &ParserContext) -> bool {
|
||
let id = match self.non_custom_id() {
|
||
// Custom properties are allowed everywhere
|
||
None => return true,
|
||
Some(id) => id,
|
||
};
|
||
id.allowed_in_ignoring_rule_type(context)
|
||
}
|
||
|
||
/// Whether the property supports the given CSS type.
|
||
/// `ty` should a bitflags of constants in style_traits::CssType.
|
||
pub fn supports_type(&self, ty: u8) -> bool {
|
||
let id = self.non_custom_non_alias_id();
|
||
id.map_or(0, |id| id.supported_types()) & ty != 0
|
||
}
|
||
|
||
/// Collect supported starting word of values of this property.
|
||
///
|
||
/// See style_traits::SpecifiedValueInfo::collect_completion_keywords for more
|
||
/// details.
|
||
pub fn collect_property_completion_keywords(&self, f: KeywordsCollectFn) {
|
||
if let Some(id) = self.non_custom_non_alias_id() {
|
||
id.collect_property_completion_keywords(f);
|
||
}
|
||
CSSWideKeyword::collect_completion_keywords(f);
|
||
}
|
||
}
|
||
|
||
/// A declaration using a CSS-wide keyword.
|
||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||
#[derive(Clone, PartialEq, ToCss, ToShmem)]
|
||
pub struct WideKeywordDeclaration {
|
||
#[css(skip)]
|
||
id: LonghandId,
|
||
keyword: CSSWideKeyword,
|
||
}
|
||
|
||
/// An unparsed declaration that contains `var()` functions.
|
||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||
#[derive(Clone, PartialEq, ToCss, ToShmem)]
|
||
pub struct VariableDeclaration {
|
||
#[css(skip)]
|
||
id: LonghandId,
|
||
#[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
|
||
value: Arc<UnparsedValue>,
|
||
}
|
||
|
||
/// A custom property declaration value is either an unparsed value or a CSS
|
||
/// wide-keyword.
|
||
#[derive(Clone, PartialEq, ToCss, ToShmem)]
|
||
pub enum CustomDeclarationValue {
|
||
/// A value.
|
||
Value(Arc<crate::custom_properties::SpecifiedValue>),
|
||
/// A wide keyword.
|
||
CSSWideKeyword(CSSWideKeyword),
|
||
}
|
||
|
||
/// A custom property declaration with the property name and the declared value.
|
||
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
|
||
#[derive(Clone, PartialEq, ToCss, ToShmem)]
|
||
pub struct CustomDeclaration {
|
||
/// The name of the custom property.
|
||
#[css(skip)]
|
||
pub name: crate::custom_properties::Name,
|
||
/// The value of the custom property.
|
||
#[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
|
||
pub value: CustomDeclarationValue,
|
||
}
|
||
|
||
impl fmt::Debug for PropertyDeclaration {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
self.id().to_css(&mut CssWriter::new(f))?;
|
||
f.write_str(": ")?;
|
||
|
||
// Because PropertyDeclaration::to_css requires CssStringWriter, we can't write
|
||
// it directly to f, and need to allocate an intermediate string. This is
|
||
// fine for debug-only code.
|
||
let mut s = CssString::new();
|
||
self.to_css(&mut s)?;
|
||
write!(f, "{}", s)
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option< &str> {
|
||
match *self {
|
||
PropertyDeclaration::WithVariables(ref declaration) => {
|
||
let s = declaration.value.from_shorthand?;
|
||
if s != shorthand {
|
||
return None;
|
||
}
|
||
Some(&*declaration.value.css)
|
||
},
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Returns a CSS-wide keyword declaration for a given property.
|
||
#[inline]
|
||
pub fn css_wide_keyword(id: LonghandId, keyword: CSSWideKeyword) -> Self {
|
||
Self::CSSWideKeyword(WideKeywordDeclaration { id, keyword })
|
||
}
|
||
|
||
/// Returns a CSS-wide keyword if the declaration's value is one.
|
||
#[inline]
|
||
pub fn get_css_wide_keyword(&self) -> Option<CSSWideKeyword> {
|
||
match *self {
|
||
PropertyDeclaration::CSSWideKeyword(ref declaration) => {
|
||
Some(declaration.keyword)
|
||
},
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// Returns whether 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,
|
||
}
|
||
}
|
||
|
||
/// Is it the default value of line-height?
|
||
pub fn is_default_line_height(&self) -> bool {
|
||
match *self {
|
||
PropertyDeclaration::LineHeight(LineHeight::Normal) => true,
|
||
_ => false
|
||
}
|
||
}
|
||
|
||
/// Returns whether the declaration may be serialized as part of a shorthand.
|
||
///
|
||
/// This method returns false if this declaration contains variable or has a
|
||
/// CSS-wide keyword value, since these values cannot be serialized as part
|
||
/// of a shorthand.
|
||
///
|
||
/// Caller should check `with_variables_from_shorthand()` and whether all
|
||
/// needed declarations has the same CSS-wide keyword first.
|
||
///
|
||
/// Note that, serialization of a shorthand may still fail because of other
|
||
/// property-specific requirement even when this method returns true for all
|
||
/// the longhand declarations.
|
||
pub fn may_serialize_as_part_of_shorthand(&self) -> bool {
|
||
match *self {
|
||
PropertyDeclaration::CSSWideKeyword(..) |
|
||
PropertyDeclaration::WithVariables(..) => false,
|
||
PropertyDeclaration::Custom(..) =>
|
||
unreachable!("Serializing a custom property as part of shorthand?"),
|
||
_ => true,
|
||
}
|
||
}
|
||
|
||
/// Return whether the value is stored as it was in the CSS source,
|
||
/// preserving whitespace (as opposed to being parsed into a more abstract
|
||
/// data structure).
|
||
///
|
||
/// This is the case of custom properties and values that contain
|
||
/// unsubstituted variables.
|
||
pub fn value_is_unparsed(&self) -> bool {
|
||
match *self {
|
||
PropertyDeclaration::WithVariables(..) => true,
|
||
PropertyDeclaration::Custom(ref declaration) => {
|
||
matches!(declaration.value, CustomDeclarationValue::Value(..))
|
||
}
|
||
_ => false,
|
||
}
|
||
}
|
||
|
||
/// Returns true if this property declaration is for one of the animatable
|
||
/// properties.
|
||
pub fn is_animatable(&self) -> bool {
|
||
match self.id() {
|
||
PropertyDeclarationId::Longhand(id) => id.is_animatable(),
|
||
PropertyDeclarationId::Custom(..) => false,
|
||
}
|
||
}
|
||
|
||
/// Returns true if this property is a custom property, false
|
||
/// otherwise.
|
||
pub fn is_custom(&self) -> bool {
|
||
matches!(*self, PropertyDeclaration::Custom(..))
|
||
}
|
||
|
||
/// The `context` parameter controls this:
|
||
///
|
||
/// <https://drafts.csswg.org/css-animations/#keyframes>
|
||
/// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
|
||
/// > except those defined in this specification,
|
||
/// > but does accept the `animation-play-state` property and interprets it specially.
|
||
///
|
||
/// This will not actually parse Importance values, and will always set things
|
||
/// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
|
||
/// we only set them here so that we don't have to reallocate
|
||
pub fn parse_into<'i, 't>(
|
||
declarations: &mut SourcePropertyDeclaration,
|
||
id: PropertyId,
|
||
context: &ParserContext,
|
||
input: &mut Parser<'i, 't>,
|
||
) -> Result<(), ParseError<'i>> {
|
||
assert!(declarations.is_empty());
|
||
debug_assert!(id.allowed_in(context), "{:?}", id);
|
||
|
||
let non_custom_id = id.non_custom_id();
|
||
let start = input.state();
|
||
match id {
|
||
PropertyId::Custom(property_name) => {
|
||
// FIXME: fully implement https://github.com/w3c/csswg-drafts/issues/774
|
||
// before adding skip_whitespace here.
|
||
// This probably affects some test results.
|
||
let value = match input.try(CSSWideKeyword::parse) {
|
||
Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
|
||
Err(()) => CustomDeclarationValue::Value(
|
||
crate::custom_properties::SpecifiedValue::parse(input)?
|
||
),
|
||
};
|
||
declarations.push(PropertyDeclaration::Custom(CustomDeclaration {
|
||
name: property_name,
|
||
value,
|
||
}));
|
||
return Ok(());
|
||
}
|
||
PropertyId::LonghandAlias(id, _) |
|
||
PropertyId::Longhand(id) => {
|
||
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
|
||
input.try(CSSWideKeyword::parse).map(|keyword| {
|
||
PropertyDeclaration::css_wide_keyword(id, keyword)
|
||
}).or_else(|()| {
|
||
input.look_for_var_or_env_functions();
|
||
input.parse_entirely(|input| id.parse_value(context, input))
|
||
.or_else(|err| {
|
||
while let Ok(_) = input.next() {} // Look for var() after the error.
|
||
if !input.seen_var_or_env_functions() {
|
||
return Err(err);
|
||
}
|
||
input.reset(&start);
|
||
let (first_token_type, css) =
|
||
crate::custom_properties::parse_non_custom_with_var(input)?;
|
||
Ok(PropertyDeclaration::WithVariables(VariableDeclaration {
|
||
id,
|
||
value: Arc::new(UnparsedValue {
|
||
css: css.into_owned(),
|
||
first_token_type: first_token_type,
|
||
url_data: context.url_data.clone(),
|
||
from_shorthand: None,
|
||
}),
|
||
}))
|
||
})
|
||
}).map(|declaration| {
|
||
declarations.push(declaration)
|
||
})?;
|
||
}
|
||
PropertyId::ShorthandAlias(id, _) |
|
||
PropertyId::Shorthand(id) => {
|
||
input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less.
|
||
if let Ok(keyword) = input.try(CSSWideKeyword::parse) {
|
||
if id == ShorthandId::All {
|
||
declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword)
|
||
} else {
|
||
for longhand in id.longhands() {
|
||
declarations.push(PropertyDeclaration::css_wide_keyword(longhand, keyword));
|
||
}
|
||
}
|
||
} else {
|
||
input.look_for_var_or_env_functions();
|
||
// Not using parse_entirely here: each
|
||
// ${shorthand.ident}::parse_into function needs to do so
|
||
// *before* pushing to `declarations`.
|
||
id.parse_into(declarations, context, input).or_else(|err| {
|
||
while let Ok(_) = input.next() {} // Look for var() after the error.
|
||
if !input.seen_var_or_env_functions() {
|
||
return Err(err);
|
||
}
|
||
|
||
input.reset(&start);
|
||
let (first_token_type, css) =
|
||
crate::custom_properties::parse_non_custom_with_var(input)?;
|
||
let unparsed = Arc::new(UnparsedValue {
|
||
css: css.into_owned(),
|
||
first_token_type: first_token_type,
|
||
url_data: context.url_data.clone(),
|
||
from_shorthand: Some(id),
|
||
});
|
||
if id == ShorthandId::All {
|
||
declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
|
||
} else {
|
||
for id in id.longhands() {
|
||
declarations.push(
|
||
PropertyDeclaration::WithVariables(VariableDeclaration {
|
||
id,
|
||
value: unparsed.clone(),
|
||
})
|
||
)
|
||
}
|
||
}
|
||
Ok(())
|
||
})?;
|
||
}
|
||
}
|
||
}
|
||
debug_assert!(non_custom_id.is_some(), "Custom properties should've returned earlier");
|
||
if let Some(use_counters) = context.use_counters {
|
||
use_counters.non_custom_properties.record(non_custom_id.unwrap());
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
type SubpropertiesArray<T> =
|
||
[T; ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
|
||
if data.shorthands_except_all() else 0}];
|
||
|
||
type SubpropertiesVec<T> = ArrayVec<SubpropertiesArray<T>>;
|
||
|
||
/// A stack-allocated vector of `PropertyDeclaration`
|
||
/// large enough to parse one CSS `key: value` declaration.
|
||
/// (Shorthands expand to multiple `PropertyDeclaration`s.)
|
||
pub struct SourcePropertyDeclaration {
|
||
declarations: SubpropertiesVec<PropertyDeclaration>,
|
||
|
||
/// Stored separately to keep SubpropertiesVec smaller.
|
||
all_shorthand: AllShorthand,
|
||
}
|
||
|
||
impl SourcePropertyDeclaration {
|
||
/// Create one. It’s big, try not to move it around.
|
||
#[inline]
|
||
pub fn new() -> Self {
|
||
SourcePropertyDeclaration {
|
||
declarations: ::arrayvec::ArrayVec::new(),
|
||
all_shorthand: AllShorthand::NotSet,
|
||
}
|
||
}
|
||
|
||
/// Create one with a single PropertyDeclaration.
|
||
#[inline]
|
||
pub fn with_one(decl: PropertyDeclaration) -> Self {
|
||
let mut result = Self::new();
|
||
result.declarations.push(decl);
|
||
result
|
||
}
|
||
|
||
/// Similar to Vec::drain: leaves this empty when the return value is dropped.
|
||
pub fn drain(&mut self) -> SourcePropertyDeclarationDrain {
|
||
SourcePropertyDeclarationDrain {
|
||
declarations: self.declarations.drain(..),
|
||
all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet),
|
||
}
|
||
}
|
||
|
||
/// Reset to initial state
|
||
pub fn clear(&mut self) {
|
||
self.declarations.clear();
|
||
self.all_shorthand = AllShorthand::NotSet;
|
||
}
|
||
|
||
fn is_empty(&self) -> bool {
|
||
self.declarations.is_empty() && matches!(self.all_shorthand, AllShorthand::NotSet)
|
||
}
|
||
|
||
fn push(&mut self, declaration: PropertyDeclaration) {
|
||
let _result = self.declarations.try_push(declaration);
|
||
debug_assert!(_result.is_ok());
|
||
}
|
||
}
|
||
|
||
/// Return type of SourcePropertyDeclaration::drain
|
||
pub struct SourcePropertyDeclarationDrain<'a> {
|
||
declarations: ArrayVecDrain<'a, SubpropertiesArray<PropertyDeclaration>>,
|
||
all_shorthand: AllShorthand,
|
||
}
|
||
|
||
enum AllShorthand {
|
||
NotSet,
|
||
CSSWideKeyword(CSSWideKeyword),
|
||
WithVariables(Arc<UnparsedValue>)
|
||
}
|
||
|
||
impl AllShorthand {
|
||
/// Iterates property declarations from the given all shorthand value.
|
||
#[inline]
|
||
fn declarations(&self) -> AllShorthandDeclarationIterator {
|
||
AllShorthandDeclarationIterator {
|
||
all_shorthand: self,
|
||
longhands: ShorthandId::All.longhands(),
|
||
}
|
||
}
|
||
}
|
||
|
||
struct AllShorthandDeclarationIterator<'a> {
|
||
all_shorthand: &'a AllShorthand,
|
||
longhands: NonCustomPropertyIterator<LonghandId>,
|
||
}
|
||
|
||
impl<'a> Iterator for AllShorthandDeclarationIterator<'a> {
|
||
type Item = PropertyDeclaration;
|
||
|
||
#[inline]
|
||
fn next(&mut self) -> Option<Self::Item> {
|
||
match *self.all_shorthand {
|
||
AllShorthand::NotSet => None,
|
||
AllShorthand::CSSWideKeyword(ref keyword) => {
|
||
Some(PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword))
|
||
}
|
||
AllShorthand::WithVariables(ref unparsed) => {
|
||
Some(PropertyDeclaration::WithVariables(
|
||
VariableDeclaration {
|
||
id: self.longhands.next()?,
|
||
value: unparsed.clone()
|
||
}
|
||
))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "gecko")]
|
||
pub use crate::gecko_properties::style_structs;
|
||
|
||
/// The module where all the style structs are defined.
|
||
#[cfg(feature = "servo")]
|
||
pub mod style_structs {
|
||
use fxhash::FxHasher;
|
||
use super::longhands;
|
||
use std::hash::{Hash, Hasher};
|
||
use crate::logical_geometry::WritingMode;
|
||
use crate::media_queries::Device;
|
||
use crate::values::computed::NonNegativeLength;
|
||
|
||
% for style_struct in data.active_style_structs():
|
||
% if style_struct.name == "Font":
|
||
#[derive(Clone, Debug, MallocSizeOf, Serialize, Deserialize)]
|
||
% else:
|
||
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
|
||
% endif
|
||
/// The ${style_struct.name} style struct.
|
||
pub struct ${style_struct.name} {
|
||
% for longhand in style_struct.longhands:
|
||
% if not longhand.logical:
|
||
/// The ${longhand.name} computed value.
|
||
pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T,
|
||
% endif
|
||
% endfor
|
||
% if style_struct.name == "InheritedText":
|
||
/// The "used" text-decorations that apply to this box.
|
||
///
|
||
/// FIXME(emilio): This is technically a box-tree concept, and
|
||
/// would be nice to move away from style.
|
||
pub text_decorations_in_effect: crate::values::computed::text::TextDecorationsInEffect,
|
||
% endif
|
||
% if style_struct.name == "Font":
|
||
/// The font hash, used for font caching.
|
||
pub hash: u64,
|
||
% endif
|
||
% if style_struct.name == "Box":
|
||
/// The display value specified by the CSS stylesheets (without any style adjustments),
|
||
/// which is needed for hypothetical layout boxes.
|
||
pub original_display: longhands::display::computed_value::T,
|
||
% endif
|
||
}
|
||
% if style_struct.name == "Font":
|
||
impl PartialEq for Font {
|
||
fn eq(&self, other: &Font) -> bool {
|
||
self.hash == other.hash
|
||
% for longhand in style_struct.longhands:
|
||
&& self.${longhand.ident} == other.${longhand.ident}
|
||
% endfor
|
||
}
|
||
}
|
||
% endif
|
||
|
||
impl ${style_struct.name} {
|
||
% for longhand in style_struct.longhands:
|
||
% if longhand.logical:
|
||
${helpers.logical_setter(name=longhand.name)}
|
||
% else:
|
||
% if longhand.ident == "display":
|
||
/// Set `display`.
|
||
///
|
||
/// We need to keep track of the original display for hypothetical boxes,
|
||
/// so we need to special-case this.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn set_display(&mut self, v: longhands::display::computed_value::T) {
|
||
self.display = v;
|
||
self.original_display = v;
|
||
}
|
||
% else:
|
||
/// Set ${longhand.name}.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T) {
|
||
self.${longhand.ident} = v;
|
||
}
|
||
% endif
|
||
% if longhand.ident == "display":
|
||
/// Set `display` from other struct.
|
||
///
|
||
/// Same as `set_display` above.
|
||
/// Thus, we need to special-case this.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn copy_display_from(&mut self, other: &Self) {
|
||
self.display = other.display.clone();
|
||
self.original_display = other.display.clone();
|
||
}
|
||
% else:
|
||
/// Set ${longhand.name} from other struct.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn copy_${longhand.ident}_from(&mut self, other: &Self) {
|
||
self.${longhand.ident} = other.${longhand.ident}.clone();
|
||
}
|
||
% endif
|
||
/// Reset ${longhand.name} from the initial struct.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn reset_${longhand.ident}(&mut self, other: &Self) {
|
||
self.copy_${longhand.ident}_from(other)
|
||
}
|
||
|
||
/// Get the computed value for ${longhand.name}.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn clone_${longhand.ident}(&self) -> longhands::${longhand.ident}::computed_value::T {
|
||
self.${longhand.ident}.clone()
|
||
}
|
||
% endif
|
||
% if longhand.need_index:
|
||
/// If this longhand is indexed, get the number of elements.
|
||
#[allow(non_snake_case)]
|
||
pub fn ${longhand.ident}_count(&self) -> usize {
|
||
self.${longhand.ident}.0.len()
|
||
}
|
||
|
||
/// If this longhand is indexed, get the element at given
|
||
/// index.
|
||
#[allow(non_snake_case)]
|
||
pub fn ${longhand.ident}_at(&self, index: usize)
|
||
-> longhands::${longhand.ident}::computed_value::SingleComputedValue {
|
||
self.${longhand.ident}.0[index].clone()
|
||
}
|
||
% endif
|
||
% endfor
|
||
% if style_struct.name == "Border":
|
||
% for side in ["top", "right", "bottom", "left"]:
|
||
/// Whether the border-${side} property has nonzero width.
|
||
#[allow(non_snake_case)]
|
||
pub fn border_${side}_has_nonzero_width(&self) -> bool {
|
||
use crate::Zero;
|
||
!self.border_${side}_width.is_zero()
|
||
}
|
||
% endfor
|
||
% elif style_struct.name == "Font":
|
||
/// Computes a font hash in order to be able to cache fonts
|
||
/// effectively in GFX and layout.
|
||
pub fn compute_font_hash(&mut self) {
|
||
// Corresponds to the fields in
|
||
// `gfx::font_template::FontTemplateDescriptor`.
|
||
let mut hasher: FxHasher = Default::default();
|
||
self.font_weight.hash(&mut hasher);
|
||
self.font_stretch.hash(&mut hasher);
|
||
self.font_style.hash(&mut hasher);
|
||
self.font_family.hash(&mut hasher);
|
||
self.hash = hasher.finish()
|
||
}
|
||
|
||
/// (Servo does not handle MathML, so this just calls copy_font_size_from)
|
||
pub fn inherit_font_size_from(&mut self, parent: &Self,
|
||
_: Option<NonNegativeLength>,
|
||
_: &Device) {
|
||
self.copy_font_size_from(parent);
|
||
}
|
||
/// (Servo does not handle MathML, so this just calls set_font_size)
|
||
pub fn apply_font_size(&mut self,
|
||
v: longhands::font_size::computed_value::T,
|
||
_: &Self,
|
||
_: &Device) -> Option<NonNegativeLength> {
|
||
self.set_font_size(v);
|
||
None
|
||
}
|
||
/// (Servo does not handle MathML, so this does nothing)
|
||
pub fn apply_unconstrained_font_size(&mut self, _: NonNegativeLength) {
|
||
}
|
||
|
||
% elif style_struct.name == "Outline":
|
||
/// Whether the outline-width property is non-zero.
|
||
#[inline]
|
||
pub fn outline_has_nonzero_width(&self) -> bool {
|
||
use crate::Zero;
|
||
!self.outline_width.is_zero()
|
||
}
|
||
% elif style_struct.name == "Box":
|
||
/// Sets the display property, but without touching original_display,
|
||
/// except when the adjustment comes from root or item display fixups.
|
||
pub fn set_adjusted_display(
|
||
&mut self,
|
||
dpy: longhands::display::computed_value::T,
|
||
is_item_or_root: bool
|
||
) {
|
||
self.display = dpy;
|
||
if is_item_or_root {
|
||
self.original_display = dpy;
|
||
}
|
||
}
|
||
% endif
|
||
}
|
||
|
||
% endfor
|
||
}
|
||
|
||
% for style_struct in data.active_style_structs():
|
||
impl style_structs::${style_struct.name} {
|
||
% for longhand in style_struct.longhands:
|
||
% if longhand.need_index:
|
||
/// Iterate over the values of ${longhand.name}.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn ${longhand.ident}_iter(&self) -> ${longhand.camel_case}Iter {
|
||
${longhand.camel_case}Iter {
|
||
style_struct: self,
|
||
current: 0,
|
||
max: self.${longhand.ident}_count(),
|
||
}
|
||
}
|
||
|
||
/// Get a value mod `index` for the property ${longhand.name}.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
pub fn ${longhand.ident}_mod(&self, index: usize)
|
||
-> longhands::${longhand.ident}::computed_value::SingleComputedValue {
|
||
self.${longhand.ident}_at(index % self.${longhand.ident}_count())
|
||
}
|
||
|
||
/// Clone the computed value for the property.
|
||
#[allow(non_snake_case)]
|
||
#[inline]
|
||
#[cfg(feature = "gecko")]
|
||
pub fn clone_${longhand.ident}(
|
||
&self,
|
||
) -> longhands::${longhand.ident}::computed_value::T {
|
||
longhands::${longhand.ident}::computed_value::List(
|
||
self.${longhand.ident}_iter().collect()
|
||
)
|
||
}
|
||
% endif
|
||
% endfor
|
||
|
||
% if style_struct.name == "Box":
|
||
/// 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.0.is_some())
|
||
}
|
||
|
||
/// Returns whether there are any transitions specified.
|
||
#[cfg(feature = "servo")]
|
||
pub fn specifies_transitions(&self) -> bool {
|
||
// TODO(mrobinson): This should check the combined duration and not just
|
||
// the duration.
|
||
self.transition_duration_iter()
|
||
.take(self.transition_property_count())
|
||
.any(|t| t.seconds() > 0.)
|
||
}
|
||
|
||
/// Returns true if animation properties are equal between styles, but without
|
||
/// considering keyframe data.
|
||
#[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 crate::gecko_properties::{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: Option<Arc<crate::custom_properties::CustomPropertiesMap>>,
|
||
/// The writing mode of this computed values struct.
|
||
pub writing_mode: WritingMode,
|
||
|
||
/// 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.get_box().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 style, if any.
|
||
pub fn visited_style(&self) -> Option<<&ComputedValues> {
|
||
self.visited_style.as_ref().map(|s| &**s)
|
||
}
|
||
|
||
/// Returns the visited rules, if applicable.
|
||
pub fn visited_rules(&self) -> Option<<&StrongRuleNode> {
|
||
self.visited_style.as_ref().and_then(|s| s.rules.as_ref())
|
||
}
|
||
|
||
/// Gets a reference to the custom properties map (if one exists).
|
||
pub fn custom_properties(&self) -> Option<<&Arc<crate::custom_properties::CustomPropertiesMap>> {
|
||
self.custom_properties.as_ref()
|
||
}
|
||
|
||
% for prop in data.longhands:
|
||
/// 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("_")}()
|
||
% if prop.logical:
|
||
.clone_${prop.ident}(self.writing_mode)
|
||
% else:
|
||
.clone_${prop.ident}()
|
||
% endif
|
||
}
|
||
% endfor
|
||
|
||
/// Writes the value of the given longhand as a string in `dest`.
|
||
///
|
||
/// Note that the value will usually be the computed value, except for
|
||
/// colors, where it's resolved.
|
||
pub fn get_longhand_property_value<W>(
|
||
&self,
|
||
property_id: LonghandId,
|
||
dest: &mut CssWriter<W>
|
||
) -> fmt::Result
|
||
where
|
||
W: Write,
|
||
{
|
||
use crate::values::resolved::ToResolvedValue;
|
||
|
||
let context = resolved::Context {
|
||
style: self,
|
||
};
|
||
|
||
// TODO(emilio): Is it worth to merge branches here just like
|
||
// PropertyDeclaration::to_css does?
|
||
match property_id {
|
||
% for prop in data.longhands:
|
||
LonghandId::${prop.camel_case} => {
|
||
let value = self.clone_${prop.ident}();
|
||
value.to_resolved_value(&context).to_css(dest)
|
||
}
|
||
% 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) -> RGBA {
|
||
color.to_rgba(self.get_inherited_text().clone_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 self.clone_${prop.ident}() != other.clone_${prop.ident}() {
|
||
set.insert(LonghandId::${prop.camel_case});
|
||
}
|
||
% 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: Option<Arc<crate::custom_properties::CustomPropertiesMap>>,
|
||
writing_mode: WritingMode,
|
||
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,
|
||
flags,
|
||
% for style_struct in data.active_style_structs():
|
||
${style_struct.ident},
|
||
% endfor
|
||
},
|
||
pseudo: pseudo.cloned(),
|
||
})
|
||
}
|
||
|
||
/// Get the initial computed values.
|
||
pub fn initial_values() -> &'static Self { &*INITIAL_SERVO_VALUES }
|
||
|
||
/// Serializes the computed value of this property as a string.
|
||
pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String {
|
||
match property {
|
||
PropertyDeclarationId::Longhand(id) => {
|
||
let mut s = String::new();
|
||
self.get_longhand_property_value(
|
||
id,
|
||
&mut CssWriter::new(&mut s)
|
||
).unwrap();
|
||
s
|
||
}
|
||
PropertyDeclarationId::Custom(name) => {
|
||
self.custom_properties
|
||
.as_ref()
|
||
.and_then(|map| map.get(name))
|
||
.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 {
|
||
% 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}
|
||
}
|
||
|
||
/// Gets an immutable reference to the refcounted value that wraps
|
||
/// `${style_struct.name}`.
|
||
pub fn ${style_struct.name_lower}_arc(&self) -> &Arc<style_structs::${style_struct.name}> {
|
||
&self.${style_struct.ident}
|
||
}
|
||
|
||
/// Get a mutable reference to the ${style_struct.name} struct.
|
||
#[inline]
|
||
pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} {
|
||
Arc::make_mut(&mut self.${style_struct.ident})
|
||
}
|
||
% endfor
|
||
|
||
/// Gets a reference to the rule node. Panic if no rule node exists.
|
||
pub fn rules(&self) -> &StrongRuleNode {
|
||
self.rules.as_ref().unwrap()
|
||
}
|
||
|
||
#[inline]
|
||
/// Returns whether the "content" property for the given style is completely
|
||
/// ineffective, and would yield an empty `::before` or `::after`
|
||
/// pseudo-element.
|
||
pub fn ineffective_content_property(&self) -> bool {
|
||
use crate::values::generics::counters::Content;
|
||
match self.get_counters().content {
|
||
Content::Normal | Content::None => true,
|
||
Content::Items(ref items) => items.is_empty(),
|
||
}
|
||
}
|
||
|
||
/// Whether the current style or any of its ancestors is multicolumn.
|
||
#[inline]
|
||
pub fn can_be_fragmented(&self) -> bool {
|
||
self.flags.contains(ComputedValueFlags::CAN_BE_FRAGMENTED)
|
||
}
|
||
|
||
/// Whether the current style is multicolumn.
|
||
#[inline]
|
||
pub fn is_multicol(&self) -> bool {
|
||
self.get_column().is_multicol()
|
||
}
|
||
|
||
/// Get the logical computed inline size.
|
||
#[inline]
|
||
pub fn content_inline_size(&self) -> &computed::Size {
|
||
let position_style = self.get_position();
|
||
if self.writing_mode.is_vertical() {
|
||
&position_style.height
|
||
} else {
|
||
&position_style.width
|
||
}
|
||
}
|
||
|
||
/// Get the logical computed block size.
|
||
#[inline]
|
||
pub fn content_block_size(&self) -> &computed::Size {
|
||
let position_style = self.get_position();
|
||
if self.writing_mode.is_vertical() { &position_style.width } else { &position_style.height }
|
||
}
|
||
|
||
/// Get the logical computed min inline size.
|
||
#[inline]
|
||
pub fn min_inline_size(&self) -> &computed::Size {
|
||
let position_style = self.get_position();
|
||
if self.writing_mode.is_vertical() { &position_style.min_height } else { &position_style.min_width }
|
||
}
|
||
|
||
/// Get the logical computed min block size.
|
||
#[inline]
|
||
pub fn min_block_size(&self) -> &computed::Size {
|
||
let position_style = self.get_position();
|
||
if self.writing_mode.is_vertical() { &position_style.min_width } else { &position_style.min_height }
|
||
}
|
||
|
||
/// Get the logical computed max inline size.
|
||
#[inline]
|
||
pub fn max_inline_size(&self) -> &computed::MaxSize {
|
||
let position_style = self.get_position();
|
||
if self.writing_mode.is_vertical() { &position_style.max_height } else { &position_style.max_width }
|
||
}
|
||
|
||
/// Get the logical computed max block size.
|
||
#[inline]
|
||
pub fn max_block_size(&self) -> &computed::MaxSize {
|
||
let position_style = self.get_position();
|
||
if self.writing_mode.is_vertical() { &position_style.max_width } else { &position_style.max_height }
|
||
}
|
||
|
||
/// Get the logical computed padding for this writing mode.
|
||
#[inline]
|
||
pub fn logical_padding(&self) -> LogicalMargin<<&computed::LengthPercentage> {
|
||
let padding_style = self.get_padding();
|
||
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
||
&padding_style.padding_top.0,
|
||
&padding_style.padding_right.0,
|
||
&padding_style.padding_bottom.0,
|
||
&padding_style.padding_left.0,
|
||
))
|
||
}
|
||
|
||
/// Get the logical border width
|
||
#[inline]
|
||
pub fn border_width_for_writing_mode(&self, writing_mode: WritingMode) -> LogicalMargin<Au> {
|
||
let border_style = self.get_border();
|
||
LogicalMargin::from_physical(writing_mode, SideOffsets2D::new(
|
||
Au::from(border_style.border_top_width),
|
||
Au::from(border_style.border_right_width),
|
||
Au::from(border_style.border_bottom_width),
|
||
Au::from(border_style.border_left_width),
|
||
))
|
||
}
|
||
|
||
/// Gets the logical computed border widths for this style.
|
||
#[inline]
|
||
pub fn logical_border_width(&self) -> LogicalMargin<Au> {
|
||
self.border_width_for_writing_mode(self.writing_mode)
|
||
}
|
||
|
||
/// Gets the logical computed margin from this style.
|
||
#[inline]
|
||
pub fn logical_margin(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> {
|
||
let margin_style = self.get_margin();
|
||
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
||
&margin_style.margin_top,
|
||
&margin_style.margin_right,
|
||
&margin_style.margin_bottom,
|
||
&margin_style.margin_left,
|
||
))
|
||
}
|
||
|
||
/// Gets the logical position from this style.
|
||
#[inline]
|
||
pub fn logical_position(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> {
|
||
// FIXME(SimonSapin): should be the writing mode of the containing block, maybe?
|
||
let position_style = self.get_position();
|
||
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
||
&position_style.top,
|
||
&position_style.right,
|
||
&position_style.bottom,
|
||
&position_style.left,
|
||
))
|
||
}
|
||
|
||
/// Return true if the effects force the transform style to be Flat
|
||
pub fn overrides_transform_style(&self) -> bool {
|
||
use crate::computed_values::mix_blend_mode::T as MixBlendMode;
|
||
|
||
let effects = self.get_effects();
|
||
// TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported.
|
||
effects.opacity < 1.0 ||
|
||
!effects.filter.0.is_empty() ||
|
||
!effects.clip.is_auto() ||
|
||
effects.mix_blend_mode != MixBlendMode::Normal
|
||
}
|
||
|
||
/// <https://drafts.csswg.org/css-transforms/#grouping-property-values>
|
||
pub fn get_used_transform_style(&self) -> computed_values::transform_style::T {
|
||
use crate::computed_values::transform_style::T as TransformStyle;
|
||
|
||
let box_ = self.get_box();
|
||
|
||
if self.overrides_transform_style() {
|
||
TransformStyle::Flat
|
||
} else {
|
||
// Return the computed value if not overridden by the above exceptions
|
||
box_.transform_style
|
||
}
|
||
}
|
||
|
||
/// Whether given this transform value, the compositor would require a
|
||
/// layer.
|
||
pub fn transform_requires_layer(&self) -> bool {
|
||
use crate::values::generics::transform::TransformOperation;
|
||
// Check if the transform matrix is 2D or 3D
|
||
for transform in &*self.get_box().transform.0 {
|
||
match *transform {
|
||
TransformOperation::Perspective(..) => {
|
||
return true;
|
||
}
|
||
TransformOperation::Matrix3D(m) => {
|
||
// See http://dev.w3.org/csswg/css-transforms/#2d-matrix
|
||
if m.m31 != 0.0 || m.m32 != 0.0 ||
|
||
m.m13 != 0.0 || m.m23 != 0.0 ||
|
||
m.m43 != 0.0 || m.m14 != 0.0 ||
|
||
m.m24 != 0.0 || m.m34 != 0.0 ||
|
||
m.m33 != 1.0 || m.m44 != 1.0 {
|
||
return true;
|
||
}
|
||
}
|
||
TransformOperation::Translate3D(_, _, z) |
|
||
TransformOperation::TranslateZ(z) => {
|
||
if z.px() != 0. {
|
||
return true;
|
||
}
|
||
}
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
// Neither perspective nor transform present
|
||
false
|
||
}
|
||
}
|
||
|
||
% if engine == "gecko":
|
||
pub use crate::servo_arc::RawOffsetArc as BuilderArc;
|
||
/// Clone an arc, returning a regular arc
|
||
fn clone_arc<T: 'static>(x: &BuilderArc<T>) -> Arc<T> {
|
||
Arc::from_raw_offset(x.clone())
|
||
}
|
||
% else:
|
||
pub use crate::servo_arc::Arc as BuilderArc;
|
||
/// Clone an arc, returning a regular arc
|
||
fn clone_arc<T: 'static>(x: &BuilderArc<T>) -> Arc<T> {
|
||
x.clone()
|
||
}
|
||
% endif
|
||
|
||
/// 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 BuilderArc<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(arc) => {
|
||
&**arc 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(arc) => UniqueArc::new((**arc).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(),
|
||
StyleStructRef::Borrowed(v) => clone_arc(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 style we're inheriting from.
|
||
///
|
||
/// This is effectively
|
||
/// `parent_style.unwrap_or(device.default_computed_values())`.
|
||
inherited_style: &'a ComputedValues,
|
||
|
||
/// The style we're inheriting from for properties that don't inherit from
|
||
/// ::first-line. This is the same as inherited_style, unless
|
||
/// inherited_style is a ::first-line style.
|
||
inherited_style_ignoring_first_line: &'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>,
|
||
|
||
custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>,
|
||
|
||
/// 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,
|
||
|
||
/// 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.
|
||
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`.
|
||
fn new(
|
||
device: &'a Device,
|
||
parent_style: Option<<&'a ComputedValues>,
|
||
parent_style_ignoring_first_line: Option<<&'a ComputedValues>,
|
||
pseudo: Option<<&'a PseudoElement>,
|
||
rules: Option<StrongRuleNode>,
|
||
custom_properties: Option<Arc<crate::custom_properties::CustomPropertiesMap>>,
|
||
is_root_element: bool,
|
||
) -> Self {
|
||
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
|
||
#[cfg(feature = "gecko")]
|
||
debug_assert!(parent_style.is_none() ||
|
||
std::ptr::eq(parent_style.unwrap(),
|
||
parent_style_ignoring_first_line.unwrap()) ||
|
||
parent_style.unwrap().is_first_line_style());
|
||
let reset_style = device.default_computed_values();
|
||
let inherited_style = parent_style.unwrap_or(reset_style);
|
||
let inherited_style_ignoring_first_line = parent_style_ignoring_first_line.unwrap_or(reset_style);
|
||
|
||
let flags = inherited_style.flags.inherited();
|
||
|
||
StyleBuilder {
|
||
device,
|
||
inherited_style,
|
||
inherited_style_ignoring_first_line,
|
||
reset_style,
|
||
pseudo,
|
||
rules,
|
||
modified_reset: false,
|
||
is_root_element,
|
||
custom_properties,
|
||
writing_mode: inherited_style.writing_mode,
|
||
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.${style_struct.name_lower}_arc()),
|
||
% else:
|
||
${style_struct.ident}: StyleStructRef::Borrowed(reset_style.${style_struct.name_lower}_arc()),
|
||
% 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,
|
||
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);
|
||
#[cfg(feature = "gecko")]
|
||
debug_assert!(parent_style.is_none() ||
|
||
!parent_style.unwrap().is_first_line_style());
|
||
StyleBuilder {
|
||
device,
|
||
inherited_style,
|
||
// None of our callers pass in ::first-line parent styles.
|
||
inherited_style_ignoring_first_line: inherited_style,
|
||
reset_style,
|
||
pseudo: None,
|
||
modified_reset: false,
|
||
is_root_element: false,
|
||
rules: None,
|
||
custom_properties: style_to_derive_from.custom_properties().cloned(),
|
||
writing_mode: style_to_derive_from.writing_mode,
|
||
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.${style_struct.name_lower}_arc()
|
||
),
|
||
% 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.${style_struct.name_lower}_arc());
|
||
% endif
|
||
% endfor
|
||
}
|
||
|
||
% for property in data.longhands:
|
||
% 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_ignoring_first_line
|
||
.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::INHERITS_CONTENT);
|
||
% endif
|
||
|
||
% if property.ident == "display":
|
||
self.add_flags(ComputedValueFlags::INHERITS_DISPLAY);
|
||
% endif
|
||
|
||
if self.${property.style_struct.ident}.ptr_eq(inherited_struct) {
|
||
return;
|
||
}
|
||
|
||
self.${property.style_struct.ident}.mutate()
|
||
.copy_${property.ident}_from(
|
||
inherited_struct,
|
||
% if property.logical:
|
||
self.writing_mode,
|
||
% endif
|
||
);
|
||
}
|
||
% 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,
|
||
% if property.logical:
|
||
self.writing_mode,
|
||
% endif
|
||
);
|
||
}
|
||
% endif
|
||
|
||
% if not property.is_vector or property.simple_vector_bindings or engine in ["servo-2013", "servo-2020"]:
|
||
/// Set the `${property.ident}` to the computed value `value`.
|
||
#[allow(non_snake_case)]
|
||
pub fn set_${property.ident}(
|
||
&mut self,
|
||
value: longhands::${property.ident}::computed_value::T
|
||
) {
|
||
% if not property.style_struct.inherited:
|
||
self.modified_reset = true;
|
||
% endif
|
||
|
||
self.${property.style_struct.ident}.mutate()
|
||
.set_${property.ident}(
|
||
value,
|
||
% if property.logical:
|
||
self.writing_mode,
|
||
% endif
|
||
);
|
||
}
|
||
% endif
|
||
% 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,
|
||
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,
|
||
Some(style),
|
||
pseudo,
|
||
).build()
|
||
})
|
||
});
|
||
let mut ret = Self::new(
|
||
device,
|
||
parent,
|
||
parent,
|
||
pseudo,
|
||
/* rules = */ None,
|
||
parent.and_then(|p| p.custom_properties().cloned()),
|
||
/* is_root_element = */ false,
|
||
);
|
||
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.${style_struct.name_lower}_arc());
|
||
}
|
||
% endfor
|
||
<% del style_struct %>
|
||
|
||
/// Returns whether this computed style represents a floated object.
|
||
pub fn is_floating(&self) -> bool {
|
||
self.get_box().clone_float().is_floating()
|
||
}
|
||
|
||
/// Returns whether this computed style represents an absolutely-positioned
|
||
/// object.
|
||
pub fn is_absolutely_positioned(&self) -> bool {
|
||
self.get_box().clone_position().is_absolutely_positioned()
|
||
}
|
||
|
||
/// Whether this style has a top-layer style.
|
||
#[cfg(feature = "servo")]
|
||
pub fn in_top_layer(&self) -> bool {
|
||
matches!(self.get_box().clone__servo_top_layer(),
|
||
longhands::_servo_top_layer::computed_value::T::Top)
|
||
}
|
||
|
||
/// Whether this style has a top-layer style.
|
||
#[cfg(feature = "gecko")]
|
||
pub fn in_top_layer(&self) -> bool {
|
||
matches!(self.get_box().clone__moz_top_layer(),
|
||
longhands::_moz_top_layer::computed_value::T::Top)
|
||
}
|
||
|
||
/// Clears the "have any reset structs been modified" flag.
|
||
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.
|
||
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.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.
|
||
///
|
||
/// Cloning the Arc here is fine because it only happens in the case where
|
||
/// we have custom properties, and those are both rare and expensive.
|
||
fn custom_properties(&self) -> Option<<&Arc<crate::custom_properties::CustomPropertiesMap>> {
|
||
self.custom_properties.as_ref()
|
||
}
|
||
|
||
/// 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 computed value flags of our parent.
|
||
#[inline]
|
||
pub fn get_parent_flags(&self) -> ComputedValueFlags {
|
||
self.inherited_style.flags
|
||
}
|
||
|
||
/// 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} {
|
||
% if style_struct.inherited:
|
||
self.inherited_style.get_${style_struct.name_lower}()
|
||
% else:
|
||
self.inherited_style_ignoring_first_line.get_${style_struct.name_lower}()
|
||
% endif
|
||
}
|
||
% endfor
|
||
}
|
||
|
||
#[cfg(feature = "servo")]
|
||
pub use self::lazy_static_module::INITIAL_SERVO_VALUES;
|
||
|
||
// Use a module to work around #[cfg] on lazy_static! not being applied to every generated item.
|
||
#[cfg(feature = "servo")]
|
||
#[allow(missing_docs)]
|
||
mod lazy_static_module {
|
||
use crate::logical_geometry::WritingMode;
|
||
use crate::computed_value_flags::ComputedValueFlags;
|
||
use servo_arc::Arc;
|
||
use super::{ComputedValues, ComputedValuesInner, longhands, style_structs};
|
||
|
||
lazy_static! {
|
||
/// The initial values for all style structs as defined by the specification.
|
||
pub static ref INITIAL_SERVO_VALUES: ComputedValues = ComputedValues {
|
||
inner: ComputedValuesInner {
|
||
% for style_struct in data.active_style_structs():
|
||
${style_struct.ident}: Arc::new(style_structs::${style_struct.name} {
|
||
% for longhand in style_struct.longhands:
|
||
% if not longhand.logical:
|
||
${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
|
||
% endif
|
||
% endfor
|
||
% if style_struct.name == "InheritedText":
|
||
text_decorations_in_effect:
|
||
crate::values::computed::text::TextDecorationsInEffect::default(),
|
||
% endif
|
||
% if style_struct.name == "Font":
|
||
hash: 0,
|
||
% endif
|
||
% if style_struct.name == "Box":
|
||
original_display: longhands::display::get_initial_value(),
|
||
% endif
|
||
}),
|
||
% endfor
|
||
custom_properties: None,
|
||
writing_mode: WritingMode::empty(),
|
||
rules: None,
|
||
visited_style: None,
|
||
flags: ComputedValueFlags::empty(),
|
||
},
|
||
pseudo: None,
|
||
};
|
||
}
|
||
}
|
||
|
||
/// A per-longhand function that performs the CSS cascade for that longhand.
|
||
pub type CascadePropertyFn =
|
||
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(NonNegativeLength::zero());
|
||
}
|
||
% 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
|
||
}
|
||
|
||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||
enum AliasedPropertyId {
|
||
#[allow(dead_code)] // Servo doesn't have aliased shorthands.
|
||
Shorthand(ShorthandId),
|
||
Longhand(LonghandId),
|
||
}
|
||
|
||
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]
|
||
fn aliased_property(self) -> AliasedPropertyId {
|
||
static MAP: [AliasedPropertyId; ${len(data.all_aliases())}] = [
|
||
% for alias in data.all_aliases():
|
||
% if alias.original.type() == "longhand":
|
||
AliasedPropertyId::Longhand(LonghandId::${alias.original.camel_case}),
|
||
% else:
|
||
<% assert alias.original.type() == "shorthand" %>
|
||
AliasedPropertyId::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.alias:
|
||
% if '-' in prop.name:
|
||
[${prop.ident.capitalize()}, Set${prop.ident.capitalize()},
|
||
PropertyId::${kind}(${kind}Id::${property.camel_case})],
|
||
% endif
|
||
[${prop.camel_case}, Set${prop.camel_case},
|
||
PropertyId::${kind}(${kind}Id::${property.camel_case})],
|
||
% endfor
|
||
% endif
|
||
% endfor
|
||
% endfor
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Call the given macro with tokens like this for each longhand properties:
|
||
///
|
||
/// ```
|
||
/// { snake_case_ident, true }
|
||
/// ```
|
||
///
|
||
/// … where the boolean indicates whether the property value type
|
||
/// is wrapped in a `Box<_>` in the corresponding `PropertyDeclaration` variant.
|
||
#[macro_export]
|
||
macro_rules! longhand_properties_idents {
|
||
($macro_name: ident) => {
|
||
$macro_name! {
|
||
% for property in data.longhands:
|
||
{ ${property.ident}, ${"true" if property.boxed else "false"} }
|
||
% endfor
|
||
}
|
||
}
|
||
}
|
||
|
||
% if engine in ["servo-2013", "servo-2020"]:
|
||
% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]:
|
||
macro_rules! restyle_damage_${effect_name} {
|
||
($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ]) => ({
|
||
if
|
||
% for style_struct in data.active_style_structs():
|
||
% for longhand in style_struct.longhands:
|
||
% if effect_name in longhand.servo_restyle_damage.split() and not longhand.logical:
|
||
$old.get_${style_struct.name_lower}().${longhand.ident} !=
|
||
$new.get_${style_struct.name_lower}().${longhand.ident} ||
|
||
% endif
|
||
% endfor
|
||
% endfor
|
||
|
||
false {
|
||
$damage.insert($($effect)|*);
|
||
true
|
||
} else {
|
||
false
|
||
}
|
||
})
|
||
}
|
||
% endfor
|
||
% endif
|