diff options
author | Delan Azabani <dazabani@igalia.com> | 2024-02-27 23:39:06 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-27 15:39:06 +0000 |
commit | faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4 (patch) | |
tree | 4725e1446680d036797b1fc258733ae6b2c9f354 | |
parent | b07505417e629bbb081be9683630f2d7a5f50544 (diff) | |
download | servo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.tar.gz servo-faf754dfa655f0b9a28f62bc47a78fbf78ebcaf4.zip |
Move Stylo to its own repo (#31350)
* Remove packages that were moved to external repo
* Add workspace dependencies pointing to 2023-06-14 branch
* Fix servo-tidy.toml errors
* Update commit to include #31346
* Update commit to include servo/stylo#2
* Move css-properties.json lookup to target/doc/stylo
* Remove dependency on vendored mako in favour of pypi dependency
This also removes etc/ci/generate_workflow.py, which has been unused
since at least 9e71bd6a7010d6e5723831696ae0ebe26b47682f.
* Add temporary code to debug Windows test failures
* Fix failures on Windows due to custom target dir
* Update commit to include servo/stylo#3
* Fix license in tests/unit/style/build.rs
* Document how to build with local Stylo in Cargo.toml
400 files changed, 112 insertions, 123600 deletions
diff --git a/Cargo.lock b/Cargo.lock index 71adf0ab738..f336e1928eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ [[package]] name = "derive_common" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "darling", "proc-macro2", @@ -3459,6 +3460,7 @@ dependencies = [ [[package]] name = "malloc_size_of" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "accountable-refcell", "app_units", @@ -3701,12 +3703,6 @@ dependencies = [ ] [[package]] -name = "mozbuild" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903970ae2f248d7275214cf8f387f8ba0c4ea7e3d87a320e85493db60ce28616" - -[[package]] name = "mozjs" version = "0.14.1" source = "git+https://github.com/servo/mozjs#d1f1519b8a9a57d97e3f623ea119e86c5dcbf524" @@ -5076,6 +5072,7 @@ dependencies = [ [[package]] name = "selectors" version = "0.24.0" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "bitflags 1.3.2", "cssparser", @@ -5363,6 +5360,7 @@ dependencies = [ [[package]] name = "servo_arc" version = "0.2.0" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "nodrop", "serde", @@ -5372,6 +5370,7 @@ dependencies = [ [[package]] name = "servo_atoms" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "string_cache", "string_cache_codegen", @@ -5576,6 +5575,7 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "size_of_test" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "static_assertions", ] @@ -5701,6 +5701,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "static_prefs" version = "0.1.0" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" [[package]] name = "str-buf" @@ -5743,11 +5744,11 @@ dependencies = [ [[package]] name = "style" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "app_units", "arrayvec", "atomic_refcell", - "bindgen", "bitflags 1.3.2", "byteorder", "cssparser", @@ -5764,7 +5765,6 @@ dependencies = [ "malloc_size_of", "malloc_size_of_derive", "mime", - "mozbuild", "new_debug_unreachable", "num-derive", "num-integer", @@ -5774,7 +5774,6 @@ dependencies = [ "parking_lot", "precomputed-hash", "rayon", - "regex", "selectors", "serde", "servo_arc", @@ -5791,7 +5790,6 @@ dependencies = [ "time 0.1.45", "to_shmem", "to_shmem_derive", - "toml 0.5.11", "uluru", "unicode-bidi", "unicode-segmentation", @@ -5803,6 +5801,7 @@ dependencies = [ [[package]] name = "style_config" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "lazy_static", ] @@ -5810,6 +5809,7 @@ dependencies = [ [[package]] name = "style_derive" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "darling", "derive_common", @@ -5840,6 +5840,7 @@ dependencies = [ [[package]] name = "style_traits" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "app_units", "bitflags 1.3.2", @@ -6182,6 +6183,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "to_shmem" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "cssparser", "servo_arc", @@ -6194,6 +6196,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.0.1" +source = "git+https://github.com/servo/stylo.git?branch=2023-06-14#9d04df286684bbe1b05858ed1b337b006784bf36" dependencies = [ "darling", "derive_common", diff --git a/Cargo.toml b/Cargo.toml index 00b606a941d..95f2f9622a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ layout_traits = { path = "components/shared/layout" } lazy_static = "1.4" libc = "0.2" log = "0.4" +malloc_size_of = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14", features = ["servo"] } malloc_size_of_derive = "0.1" mime = "0.3.13" mime_guess = "2.0.3" @@ -87,22 +88,28 @@ rustls = { version = "0.21.10", features = ["dangerous_configuration"] } rustls-pemfile = "1.0.4" script_layout_interface = { path = "components/shared/script_layout" } script_traits = { path = "components/shared/script" } +selectors = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } serde = "1.0.197" serde_bytes = "0.11" serde_json = "1.0" +servo_arc = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } +servo_atoms = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } +size_of_test = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } smallbitvec = "2.3.0" smallvec = "1.13" sparkle = "0.1.26" string_cache = "0.8" string_cache_codegen = "0.5" -style_config = { path = "components/style_config" } -style_traits = { path = "components/style_traits", features = ["servo"] } +style = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14", features = ["servo"] } +style_config = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } +style_traits = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14", features = ["servo"] } # NOTE: the sm-angle feature only enables ANGLE on Windows, not other platforms! surfman = { version = "0.9", features = ["chains", "sm-angle", "sm-angle-default"] } syn = { version = "2", default-features = false, features = ["clone-impls", "derive", "parsing"] } synstructure = "0.13" thin-vec = "0.2.13" time = "0.1.41" +to_shmem = { git = "https://github.com/servo/stylo.git", branch = "2023-06-14" } tokio = "1" tokio-rustls = "0.24" tungstenite = "0.20" @@ -140,7 +147,22 @@ debug-assertions = false # # <crate> = { path = "/path/to/local/checkout" } # -# Or for a git dependency: +# Or for Stylo: +# +# [patch."https://github.com/servo/stylo.git"] +# derive_common = { path = "../stylo/derive_common" } +# malloc_size_of = { path = "../stylo/malloc_size_of" } +# selectors = { path = "../stylo/selectors" } +# servo_arc = { path = "../stylo/servo_arc" } +# servo_atoms = { path = "../stylo/atoms" } +# size_of_test = { path = "../stylo/size_of_test" } +# static_prefs = { path = "../stylo/style_static_prefs" } +# style_config = { path = "../stylo/style_config" } +# style_derive = { path = "../stylo/style_derive" } +# style = { path = "../stylo/style" } +# style_traits = { path = "../stylo/style_traits" } +# +# Or for another git dependency: # # [patch."https://github.com/servo/<repository>"] # <crate> = { path = "/path/to/local/checkout" } diff --git a/components/atoms/Cargo.toml b/components/atoms/Cargo.toml deleted file mode 100644 index d48dbf0a350..00000000000 --- a/components/atoms/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "servo_atoms" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -publish = false -build = "build.rs" - -[lib] -path = "lib.rs" - -[dependencies] -string_cache = { workspace = true } - -[build-dependencies] -string_cache_codegen = { workspace = true } diff --git a/components/atoms/build.rs b/components/atoms/build.rs deleted file mode 100644 index 6bd2de37034..00000000000 --- a/components/atoms/build.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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/. */ - -use std::env; -use std::fs::File; -use std::io::{BufRead, BufReader}; -use std::path::Path; - -fn main() { - let static_atoms = - Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("static_atoms.txt"); - let static_atoms = BufReader::new(File::open(&static_atoms).unwrap()); - let mut atom_type = string_cache_codegen::AtomType::new("Atom", "atom!"); - - macro_rules! predefined { - ($($name: expr,)+) => { - { - $( - atom_type.atom($name); - )+ - } - } - } - include!("../style/counter_style/predefined.rs"); - - atom_type - .atoms(static_atoms.lines().map(Result::unwrap)) - .write_to_file(&Path::new(&env::var_os("OUT_DIR").unwrap()).join("atom.rs")) - .unwrap(); -} diff --git a/components/atoms/lib.rs b/components/atoms/lib.rs deleted file mode 100644 index 03560a40c0b..00000000000 --- a/components/atoms/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* 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/. */ - -include!(concat!(env!("OUT_DIR"), "/atom.rs")); diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt deleted file mode 100644 index 5d8f7754270..00000000000 --- a/components/atoms/static_atoms.txt +++ /dev/null @@ -1,174 +0,0 @@ --moz-content-preferred-color-scheme --moz-device-pixel-ratio --moz-gtk-csd-close-button-position --moz-gtk-csd-maximize-button-position --moz-gtk-csd-menu-radius --moz-gtk-csd-minimize-button-position --moz-gtk-csd-titlebar-radius --moz-gtk-menu-radius -DOMContentLoaded -abort -activate -addtrack -animationcancel -animationend -animationiteration -animationstart -aspect-ratio -beforeunload -block-size -button -canplay -canplaythrough -center -change -characteristicvaluechanged -checkbox -click -close -closing -color -complete -compositionend -compositionstart -compositionupdate -controllerchange -cursive -dark -datachannel -date -datetime-local -dir -device-pixel-ratio -durationchange -email -emptied -end -ended -error -fantasy -fetch -file -fill -fill-opacity -formdata -fullscreenchange -fullscreenerror -gattserverdisconnected -hashchange -height -hidden -icecandidate -iceconnectionstatechange -icegatheringstatechange -image -inline-size -input -inputsourceschange -invalid -keydown -keypress -kind -left -light -ltr -load -loadeddata -loadedmetadata -loadend -loadstart -message -message -messageerror -monospace -month -mousedown -mousemove -mouseover -mouseup -negotiationneeded -none -normal -number -onchange -open -orientation -pagehide -pageshow -password -pause -play -playing -popstate -postershown -print -progress -radio -range -ratechange -readystatechange -referrer -reftest-wait -rejectionhandled -removetrack -reset -resize -resolution -resourcetimingbufferfull -right -rtl -sans-serif -safe-area-inset-top -safe-area-inset-bottom -safe-area-inset-left -safe-area-inset-right -scan -screen -scroll-position -scrollbar-inline-size -search -seeked -seeking -select -selectend -selectionchange -selectstart -serif -sessionavailable -signalingstatechange -squeeze -squeezeend -squeezestart -srclang -statechange -stroke -stroke-opacity -storage -submit -suspend -system-ui -tel -text -time -timeupdate -toggle -track -transitioncancel -transitionend -transitionrun -transitionstart -uncapturederror -unhandledrejection -unload -url -visibilitychange -volumechange -waiting -webglcontextcreationerror -webkitAnimationEnd -webkitAnimationIteration -webkitAnimationStart -webkitTransitionEnd -webkitTransitionRun -week -width diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 692d2efdfce..ce420ae0c01 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -33,9 +33,9 @@ num-traits = { workspace = true } pathfinder_geometry = "0.5" pixels = { path = "../pixels" } raqote = "0.8.2" -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } sparkle = { workspace = true } -style = { path = "../style" } +style = { workspace = true } style_traits = { workspace = true } surfman = { workspace = true } time = { workspace = true, optional = true } diff --git a/components/config/Cargo.toml b/components/config/Cargo.toml index 8befa01cb54..ea92a1d99f8 100644 --- a/components/config/Cargo.toml +++ b/components/config/Cargo.toml @@ -22,7 +22,7 @@ serde_json = { workspace = true } servo_config_plugins = { path = "../config_plugins" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } -style_config = { path = "../style_config" } +style_config = { workspace = true } url = { workspace = true } [target.'cfg(not(target_os = "android"))'.dependencies] diff --git a/components/derive_common/Cargo.toml b/components/derive_common/Cargo.toml deleted file mode 100644 index 5677069ad56..00000000000 --- a/components/derive_common/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "derive_common" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -[lib] -path = "lib.rs" - -[dependencies] -darling = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -syn = { workspace = true } -synstructure = { workspace = true } diff --git a/components/derive_common/cg.rs b/components/derive_common/cg.rs deleted file mode 100644 index 73301af2ff2..00000000000 --- a/components/derive_common/cg.rs +++ /dev/null @@ -1,396 +0,0 @@ -/* 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/. */ - -use darling::{FromDeriveInput, FromField, FromVariant}; -use proc_macro2::{Span, TokenStream}; -use quote::TokenStreamExt; -use syn::{self, AngleBracketedGenericArguments, AssocType, DeriveInput, Field}; -use syn::{GenericArgument, GenericParam, Ident, Path}; -use syn::{PathArguments, PathSegment, QSelf, Type, TypeArray, TypeGroup}; -use syn::{TypeParam, TypeParen, TypePath, TypeSlice, TypeTuple}; -use syn::{Variant, WherePredicate}; -use synstructure::{self, BindStyle, BindingInfo, VariantAst, VariantInfo}; - -/// Given an input type which has some where clauses already, like: -/// -/// struct InputType<T> -/// where -/// T: Zero, -/// { -/// ... -/// } -/// -/// Add the necessary `where` clauses so that the output type of a trait -/// fulfils them. -/// -/// For example: -/// -/// ```ignore -/// <T as ToComputedValue>::ComputedValue: Zero, -/// ``` -/// -/// This needs to run before adding other bounds to the type parameters. -pub fn propagate_clauses_to_output_type( - where_clause: &mut Option<syn::WhereClause>, - generics: &syn::Generics, - trait_path: &Path, - trait_output: &Ident, -) { - let where_clause = match *where_clause { - Some(ref mut clause) => clause, - None => return, - }; - let mut extra_bounds = vec![]; - for pred in &where_clause.predicates { - let ty = match *pred { - syn::WherePredicate::Type(ref ty) => ty, - ref predicate => panic!("Unhanded complex where predicate: {:?}", predicate), - }; - - let path = match ty.bounded_ty { - syn::Type::Path(ref p) => &p.path, - ref ty => panic!("Unhanded complex where type: {:?}", ty), - }; - - assert!( - ty.lifetimes.is_none(), - "Unhanded complex lifetime bound: {:?}", - ty, - ); - - let ident = match path_to_ident(path) { - Some(i) => i, - None => panic!("Unhanded complex where type path: {:?}", path), - }; - - if generics.type_params().any(|param| param.ident == *ident) { - extra_bounds.push(ty.clone()); - } - } - - for bound in extra_bounds { - let ty = bound.bounded_ty; - let bounds = bound.bounds; - where_clause - .predicates - .push(parse_quote!(<#ty as #trait_path>::#trait_output: #bounds)) - } -} - -pub fn add_predicate(where_clause: &mut Option<syn::WhereClause>, pred: WherePredicate) { - where_clause - .get_or_insert(parse_quote!(where)) - .predicates - .push(pred); -} - -pub fn fmap_match<F>(input: &DeriveInput, bind_style: BindStyle, f: F) -> TokenStream -where - F: FnMut(&BindingInfo) -> TokenStream, -{ - fmap2_match(input, bind_style, f, |_| None) -} - -pub fn fmap2_match<F, G>( - input: &DeriveInput, - bind_style: BindStyle, - mut f: F, - mut g: G, -) -> TokenStream -where - F: FnMut(&BindingInfo) -> TokenStream, - G: FnMut(&BindingInfo) -> Option<TokenStream>, -{ - let mut s = synstructure::Structure::new(input); - s.variants_mut().iter_mut().for_each(|v| { - v.bind_with(|_| bind_style); - }); - s.each_variant(|variant| { - let (mapped, mapped_fields) = value(variant, "mapped"); - let fields_pairs = variant.bindings().iter().zip(mapped_fields.iter()); - let mut computations = quote!(); - computations.append_all(fields_pairs.map(|(field, mapped_field)| { - let expr = f(field); - quote! { let #mapped_field = #expr; } - })); - computations.append_all( - mapped_fields - .iter() - .map(|mapped_field| match g(mapped_field) { - Some(expr) => quote! { let #mapped_field = #expr; }, - None => quote!(), - }), - ); - computations.append_all(mapped); - Some(computations) - }) -} - -pub fn fmap_trait_output(input: &DeriveInput, trait_path: &Path, trait_output: &Ident) -> Path { - let segment = PathSegment { - ident: input.ident.clone(), - arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args: input - .generics - .params - .iter() - .map(|arg| match arg { - &GenericParam::Lifetime(ref data) => { - GenericArgument::Lifetime(data.lifetime.clone()) - }, - &GenericParam::Type(ref data) => { - let ident = &data.ident; - GenericArgument::Type(parse_quote!(<#ident as #trait_path>::#trait_output)) - }, - &GenericParam::Const(ref inner) => { - let ident = &inner.ident; - GenericArgument::Const(parse_quote!(#ident)) - }, - }) - .collect(), - colon2_token: Default::default(), - gt_token: Default::default(), - lt_token: Default::default(), - }), - }; - segment.into() -} - -pub fn map_type_params<F>(ty: &Type, params: &[&TypeParam], self_type: &Path, f: &mut F) -> Type -where - F: FnMut(&Ident) -> Type, -{ - match *ty { - Type::Slice(ref inner) => Type::from(TypeSlice { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }), - Type::Array(ref inner) => { - //ref ty, ref expr) => { - Type::from(TypeArray { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }) - }, - ref ty @ Type::Never(_) => ty.clone(), - Type::Tuple(ref inner) => Type::from(TypeTuple { - elems: inner - .elems - .iter() - .map(|ty| map_type_params(&ty, params, self_type, f)) - .collect(), - ..inner.clone() - }), - Type::Path(TypePath { - qself: None, - ref path, - }) => { - if let Some(ident) = path_to_ident(path) { - if params.iter().any(|ref param| ¶m.ident == ident) { - return f(ident); - } - if ident == "Self" { - return Type::from(TypePath { - qself: None, - path: self_type.clone(), - }); - } - } - Type::from(TypePath { - qself: None, - path: map_type_params_in_path(path, params, self_type, f), - }) - }, - Type::Path(TypePath { - ref qself, - ref path, - }) => Type::from(TypePath { - qself: qself.as_ref().map(|qself| QSelf { - ty: Box::new(map_type_params(&qself.ty, params, self_type, f)), - position: qself.position, - ..qself.clone() - }), - path: map_type_params_in_path(path, params, self_type, f), - }), - Type::Paren(ref inner) => Type::from(TypeParen { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }), - Type::Group(ref inner) => Type::from(TypeGroup { - elem: Box::new(map_type_params(&inner.elem, params, self_type, f)), - ..inner.clone() - }), - ref ty => panic!("type {:?} cannot be mapped yet", ty), - } -} - -fn map_type_params_in_path<F>( - path: &Path, - params: &[&TypeParam], - self_type: &Path, - f: &mut F, -) -> Path -where - F: FnMut(&Ident) -> Type, -{ - Path { - leading_colon: path.leading_colon, - segments: path - .segments - .iter() - .map(|segment| PathSegment { - ident: segment.ident.clone(), - arguments: match segment.arguments { - PathArguments::AngleBracketed(ref data) => { - PathArguments::AngleBracketed(AngleBracketedGenericArguments { - args: data - .args - .iter() - .map(|arg| match arg { - ty @ &GenericArgument::Lifetime(_) => ty.clone(), - &GenericArgument::Type(ref data) => GenericArgument::Type( - map_type_params(data, params, self_type, f), - ), - &GenericArgument::AssocType(ref data) => { - GenericArgument::AssocType(AssocType { - ty: map_type_params(&data.ty, params, self_type, f), - ..data.clone() - }) - }, - ref arg => panic!("arguments {:?} cannot be mapped yet", arg), - }) - .collect(), - ..data.clone() - }) - }, - ref arg @ PathArguments::None => arg.clone(), - ref parameters => panic!("parameters {:?} cannot be mapped yet", parameters), - }, - }) - .collect(), - } -} - -fn path_to_ident(path: &Path) -> Option<&Ident> { - match *path { - Path { - leading_colon: None, - ref segments, - } if segments.len() == 1 => { - if segments[0].arguments.is_empty() { - Some(&segments[0].ident) - } else { - None - } - }, - _ => None, - } -} - -pub fn parse_field_attrs<A>(field: &Field) -> A -where - A: FromField, -{ - match A::from_field(field) { - Ok(attrs) => attrs, - Err(e) => panic!("failed to parse field attributes: {}", e), - } -} - -pub fn parse_input_attrs<A>(input: &DeriveInput) -> A -where - A: FromDeriveInput, -{ - match A::from_derive_input(input) { - Ok(attrs) => attrs, - Err(e) => panic!("failed to parse input attributes: {}", e), - } -} - -pub fn parse_variant_attrs_from_ast<A>(variant: &VariantAst) -> A -where - A: FromVariant, -{ - let v = Variant { - ident: variant.ident.clone(), - attrs: variant.attrs.to_vec(), - fields: variant.fields.clone(), - discriminant: variant.discriminant.clone(), - }; - parse_variant_attrs(&v) -} - -pub fn parse_variant_attrs<A>(variant: &Variant) -> A -where - A: FromVariant, -{ - match A::from_variant(variant) { - Ok(attrs) => attrs, - Err(e) => panic!("failed to parse variant attributes: {}", e), - } -} - -pub fn ref_pattern<'a>( - variant: &'a VariantInfo, - prefix: &str, -) -> (TokenStream, Vec<BindingInfo<'a>>) { - let mut v = variant.clone(); - v.bind_with(|_| BindStyle::Ref); - v.bindings_mut().iter_mut().for_each(|b| { - b.binding = Ident::new(&format!("{}_{}", b.binding, prefix), Span::call_site()) - }); - (v.pat(), v.bindings().to_vec()) -} - -pub fn value<'a>(variant: &'a VariantInfo, prefix: &str) -> (TokenStream, Vec<BindingInfo<'a>>) { - let mut v = variant.clone(); - v.bindings_mut().iter_mut().for_each(|b| { - b.binding = Ident::new(&format!("{}_{}", b.binding, prefix), Span::call_site()) - }); - v.bind_with(|_| BindStyle::Move); - (v.pat(), v.bindings().to_vec()) -} - -/// Transforms "FooBar" to "foo-bar". -/// -/// If the first Camel segment is "Moz", "Webkit", or "Servo", the result string -/// is prepended with "-". -pub fn to_css_identifier(mut camel_case: &str) -> String { - camel_case = camel_case.trim_end_matches('_'); - let mut first = true; - let mut result = String::with_capacity(camel_case.len()); - while let Some(segment) = split_camel_segment(&mut camel_case) { - if first { - match segment { - "Moz" | "Webkit" | "Servo" => first = false, - _ => {}, - } - } - if !first { - result.push('-'); - } - first = false; - result.push_str(&segment.to_lowercase()); - } - result -} - -/// Transforms foo-bar to FOO_BAR. -pub fn to_scream_case(css_case: &str) -> String { - css_case.to_uppercase().replace('-', "_") -} - -/// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar". -fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> { - let index = match camel_case.chars().next() { - None => return None, - Some(ch) => ch.len_utf8(), - }; - let end_position = camel_case[index..] - .find(char::is_uppercase) - .map_or(camel_case.len(), |pos| index + pos); - let result = &camel_case[..end_position]; - *camel_case = &camel_case[end_position..]; - Some(result) -} diff --git a/components/derive_common/lib.rs b/components/derive_common/lib.rs deleted file mode 100644 index 14415351449..00000000000 --- a/components/derive_common/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -/* 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/. */ - -extern crate darling; -extern crate proc_macro2; -#[macro_use] -extern crate quote; -#[macro_use] -extern crate syn; -extern crate synstructure; - -pub mod cg; diff --git a/components/derive_common/rustfmt.toml b/components/derive_common/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/derive_common/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/geometry/Cargo.toml b/components/geometry/Cargo.toml index 87a870c0aa5..a816b6f5278 100644 --- a/components/geometry/Cargo.toml +++ b/components/geometry/Cargo.toml @@ -13,6 +13,6 @@ path = "lib.rs" [dependencies] app_units = { workspace = true } euclid = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } webrender_api = { workspace = true } diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml index 9538ce11960..114e727d041 100644 --- a/components/gfx/Cargo.toml +++ b/components/gfx/Cargo.toml @@ -25,16 +25,16 @@ ipc-channel = { workspace = true } lazy_static = { workspace = true } libc = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } net_traits = { workspace = true } range = { path = "../range" } serde = { workspace = true } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_url = { path = "../url" } smallvec = { workspace = true, features = ["union"] } surfman = { workspace = true } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } ucd = "0.1.1" unicode-bidi = { workspace = true, features = ["with_serde"] } unicode-script = { workspace = true } diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index 2f4040c9a48..6df0d11c36f 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -26,7 +26,7 @@ html5ever = { workspace = true } ipc-channel = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } msg = { workspace = true } net_traits = { workspace = true } parking_lot = { workspace = true } @@ -37,14 +37,14 @@ script_layout_interface = { workspace = true } script_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } -size_of_test = { path = "../size_of_test" } +size_of_test = { workspace = true } smallvec = { workspace = true, features = ["union"] } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } unicode-bidi = { workspace = true, features = ["with_serde"] } unicode-script = { workspace = true } diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index e5b647955b8..78ea0bf49a4 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -36,10 +36,10 @@ script_layout_interface = { workspace = true } script_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } unicode-script = { workspace = true } unicode-segmentation = { workspace = true } diff --git a/components/layout_thread/Cargo.toml b/components/layout_thread/Cargo.toml index 4da4f2d785a..97fc689dd2d 100644 --- a/components/layout_thread/Cargo.toml +++ b/components/layout_thread/Cargo.toml @@ -24,7 +24,7 @@ ipc-channel = { workspace = true } layout = { path = "../layout", package = "layout_2013" } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } metrics = { path = "../metrics" } msg = { workspace = true } net_traits = { workspace = true } @@ -36,11 +36,11 @@ script_layout_interface = { workspace = true } script_traits = { workspace = true } serde_json = { workspace = true } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } -style = { path = "../style" } +style = { workspace = true } style_traits = { workspace = true } time = { workspace = true } url = { workspace = true } diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml index 2a1e510630c..c5010281ae8 100644 --- a/components/layout_thread_2020/Cargo.toml +++ b/components/layout_thread_2020/Cargo.toml @@ -23,7 +23,7 @@ ipc-channel = { workspace = true } layout = { path = "../layout_2020", package = "layout_2020" } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } metrics = { path = "../metrics" } msg = { workspace = true } net_traits = { workspace = true } @@ -33,11 +33,11 @@ script = { path = "../script" } script_layout_interface = { workspace = true } script_traits = { workspace = true } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } -style = { path = "../style" } +style = { workspace = true } style_traits = { workspace = true } url = { workspace = true } webrender_api = { workspace = true } diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml deleted file mode 100644 index 01204a98e41..00000000000 --- a/components/malloc_size_of/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "malloc_size_of" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MIT OR Apache-2.0" -publish = false - -[lib] -path = "lib.rs" - -[features] -servo = [ - "accountable-refcell", - "content-security-policy", - "crossbeam-channel", - "http", - "keyboard-types", - "serde", - "serde_bytes", - "string_cache", - "time", - "url", - "uuid", - "webrender_api", - "xml5ever", -] - -[dependencies] -accountable-refcell = { workspace = true, optional = true } -app_units = { workspace = true } -content-security-policy = { workspace = true, optional = true } -crossbeam-channel = { workspace = true, optional = true } -cssparser = { workspace = true } -euclid = { workspace = true } -http = { workspace = true, optional = true } -indexmap = { workspace = true } -keyboard-types = { workspace = true, optional = true } -selectors = { path = "../selectors" } -serde = { workspace = true, optional = true } -serde_bytes = { workspace = true, optional = true } -servo_arc = { path = "../servo_arc" } -smallbitvec = { workspace = true } -smallvec = { workspace = true } -string_cache = { workspace = true, optional = true } -thin-vec = { workspace = true } -time = { workspace = true, optional = true } -tokio = { workspace = true, features = ["sync"] } -url = { workspace = true, optional = true } -uuid = { workspace = true, optional = true } -void = "1.0.2" -webrender_api = { workspace = true, optional = true } -xml5ever = { workspace = true, optional = true } diff --git a/components/malloc_size_of/LICENSE-APACHE b/components/malloc_size_of/LICENSE-APACHE deleted file mode 100644 index 16fe87b06e8..00000000000 --- a/components/malloc_size_of/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/components/malloc_size_of/LICENSE-MIT b/components/malloc_size_of/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/components/malloc_size_of/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs deleted file mode 100644 index dd0ca38e17c..00000000000 --- a/components/malloc_size_of/lib.rs +++ /dev/null @@ -1,978 +0,0 @@ -// Copyright 2016-2017 The Servo Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! A crate for measuring the heap usage of data structures in a way that -//! integrates with Firefox's memory reporting, particularly the use of -//! mozjemalloc and DMD. In particular, it has the following features. -//! - It isn't bound to a particular heap allocator. -//! - It provides traits for both "shallow" and "deep" measurement, which gives -//! flexibility in the cases where the traits can't be used. -//! - It allows for measuring blocks even when only an interior pointer can be -//! obtained for heap allocations, e.g. `HashSet` and `HashMap`. (This relies -//! on the heap allocator having suitable support, which mozjemalloc has.) -//! - It allows handling of types like `Rc` and `Arc` by providing traits that -//! are different to the ones for non-graph structures. -//! -//! Suggested uses are as follows. -//! - When possible, use the `MallocSizeOf` trait. (Deriving support is -//! provided by the `malloc_size_of_derive` crate.) -//! - If you need an additional synchronization argument, provide a function -//! that is like the standard trait method, but with the extra argument. -//! - If you need multiple measurements for a type, provide a function named -//! `add_size_of` that takes a mutable reference to a struct that contains -//! the multiple measurement fields. -//! - When deep measurement (via `MallocSizeOf`) cannot be implemented for a -//! type, shallow measurement (via `MallocShallowSizeOf`) in combination with -//! iteration can be a useful substitute. -//! - `Rc` and `Arc` are always tricky, which is why `MallocSizeOf` is not (and -//! should not be) implemented for them. -//! - If an `Rc` or `Arc` is known to be a "primary" reference and can always -//! be measured, it should be measured via the `MallocUnconditionalSizeOf` -//! trait. -//! - If an `Rc` or `Arc` should be measured only if it hasn't been seen -//! before, it should be measured via the `MallocConditionalSizeOf` trait. -//! - Using universal function call syntax is a good idea when measuring boxed -//! fields in structs, because it makes it clear that the Box is being -//! measured as well as the thing it points to. E.g. -//! `<Box<_> as MallocSizeOf>::size_of(field, ops)`. -//! -//! Note: WebRender has a reduced fork of this crate, so that we can avoid -//! publishing this crate on crates.io. - -#[cfg(feature = "servo")] -extern crate accountable_refcell; -extern crate app_units; -#[cfg(feature = "servo")] -extern crate content_security_policy; -#[cfg(feature = "servo")] -extern crate crossbeam_channel; -extern crate cssparser; -extern crate euclid; -#[cfg(feature = "servo")] -extern crate http; -#[cfg(feature = "servo")] -extern crate keyboard_types; -extern crate selectors; -#[cfg(feature = "servo")] -extern crate serde; -#[cfg(feature = "servo")] -extern crate serde_bytes; -extern crate servo_arc; -extern crate smallbitvec; -extern crate smallvec; -#[cfg(feature = "servo")] -extern crate string_cache; -#[cfg(feature = "servo")] -extern crate time; -#[cfg(feature = "url")] -extern crate url; -#[cfg(feature = "servo")] -extern crate uuid; -extern crate void; -#[cfg(feature = "webrender_api")] -extern crate webrender_api; -#[cfg(feature = "servo")] -extern crate xml5ever; - -#[cfg(feature = "servo")] -use content_security_policy as csp; -#[cfg(feature = "servo")] -use serde_bytes::ByteBuf; -use std::hash::{BuildHasher, Hash}; -use std::mem::size_of; -use std::ops::Range; -use std::ops::{Deref, DerefMut}; -use std::os::raw::c_void; -#[cfg(feature = "servo")] -use uuid::Uuid; -use void::Void; - -/// A C function that takes a pointer to a heap allocation and returns its size. -type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize; - -/// A closure implementing a stateful predicate on pointers. -type VoidPtrToBoolFnMut = dyn FnMut(*const c_void) -> bool; - -/// Operations used when measuring heap usage of data structures. -pub struct MallocSizeOfOps { - /// A function that returns the size of a heap allocation. - size_of_op: VoidPtrToSizeFn, - - /// Like `size_of_op`, but can take an interior pointer. Optional because - /// not all allocators support this operation. If it's not provided, some - /// memory measurements will actually be computed estimates rather than - /// real and accurate measurements. - enclosing_size_of_op: Option<VoidPtrToSizeFn>, - - /// Check if a pointer has been seen before, and remember it for next time. - /// Useful when measuring `Rc`s and `Arc`s. Optional, because many places - /// don't need it. - have_seen_ptr_op: Option<Box<VoidPtrToBoolFnMut>>, -} - -impl MallocSizeOfOps { - pub fn new( - size_of: VoidPtrToSizeFn, - malloc_enclosing_size_of: Option<VoidPtrToSizeFn>, - have_seen_ptr: Option<Box<VoidPtrToBoolFnMut>>, - ) -> Self { - MallocSizeOfOps { - size_of_op: size_of, - enclosing_size_of_op: malloc_enclosing_size_of, - have_seen_ptr_op: have_seen_ptr, - } - } - - /// Check if an allocation is empty. This relies on knowledge of how Rust - /// handles empty allocations, which may change in the future. - fn is_empty<T: ?Sized>(ptr: *const T) -> bool { - // The correct condition is this: - // `ptr as usize <= ::std::mem::align_of::<T>()` - // But we can't call align_of() on a ?Sized T. So we approximate it - // with the following. 256 is large enough that it should always be - // larger than the required alignment, but small enough that it is - // always in the first page of memory and therefore not a legitimate - // address. - return ptr as *const usize as usize <= 256; - } - - /// Call `size_of_op` on `ptr`, first checking that the allocation isn't - /// empty, because some types (such as `Vec`) utilize empty allocations. - pub unsafe fn malloc_size_of<T: ?Sized>(&self, ptr: *const T) -> usize { - if MallocSizeOfOps::is_empty(ptr) { - 0 - } else { - (self.size_of_op)(ptr as *const c_void) - } - } - - /// Is an `enclosing_size_of_op` available? - pub fn has_malloc_enclosing_size_of(&self) -> bool { - self.enclosing_size_of_op.is_some() - } - - /// Call `enclosing_size_of_op`, which must be available, on `ptr`, which - /// must not be empty. - pub unsafe fn malloc_enclosing_size_of<T>(&self, ptr: *const T) -> usize { - assert!(!MallocSizeOfOps::is_empty(ptr)); - (self.enclosing_size_of_op.unwrap())(ptr as *const c_void) - } - - /// Call `have_seen_ptr_op` on `ptr`. - pub fn have_seen_ptr<T>(&mut self, ptr: *const T) -> bool { - let have_seen_ptr_op = self - .have_seen_ptr_op - .as_mut() - .expect("missing have_seen_ptr_op"); - have_seen_ptr_op(ptr as *const c_void) - } -} - -/// Trait for measuring the "deep" heap usage of a data structure. This is the -/// most commonly-used of the traits. -pub trait MallocSizeOf { - /// Measure the heap usage of all descendant heap-allocated structures, but - /// not the space taken up by the value itself. - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// Trait for measuring the "shallow" heap usage of a container. -pub trait MallocShallowSizeOf { - /// Measure the heap usage of immediate heap-allocated descendant - /// structures, but not the space taken up by the value itself. Anything - /// beyond the immediate descendants must be measured separately, using - /// iteration. - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// Like `MallocSizeOf`, but with a different name so it cannot be used -/// accidentally with derive(MallocSizeOf). For use with types like `Rc` and -/// `Arc` when appropriate (e.g. when measuring a "primary" reference). -pub trait MallocUnconditionalSizeOf { - /// Measure the heap usage of all heap-allocated descendant structures, but - /// not the space taken up by the value itself. - fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// `MallocUnconditionalSizeOf` combined with `MallocShallowSizeOf`. -pub trait MallocUnconditionalShallowSizeOf { - /// `unconditional_size_of` combined with `shallow_size_of`. - fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// Like `MallocSizeOf`, but only measures if the value hasn't already been -/// measured. For use with types like `Rc` and `Arc` when appropriate (e.g. -/// when there is no "primary" reference). -pub trait MallocConditionalSizeOf { - /// Measure the heap usage of all heap-allocated descendant structures, but - /// not the space taken up by the value itself, and only if that heap usage - /// hasn't already been measured. - fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -/// `MallocConditionalSizeOf` combined with `MallocShallowSizeOf`. -pub trait MallocConditionalShallowSizeOf { - /// `conditional_size_of` combined with `shallow_size_of`. - fn conditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize; -} - -impl MallocSizeOf for String { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } -} - -impl<'a, T: ?Sized> MallocSizeOf for &'a T { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - // Zero makes sense for a non-owning reference. - 0 - } -} - -impl<T: ?Sized> MallocShallowSizeOf for Box<T> { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(&**self) } - } -} - -impl<T: MallocSizeOf + ?Sized> MallocSizeOf for Box<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.shallow_size_of(ops) + (**self).size_of(ops) - } -} - -impl MallocSizeOf for () { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -impl<T1, T2> MallocSizeOf for (T1, T2) -where - T1: MallocSizeOf, - T2: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) - } -} - -impl<T1, T2, T3> MallocSizeOf for (T1, T2, T3) -where - T1: MallocSizeOf, - T2: MallocSizeOf, - T3: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) - } -} - -impl<T1, T2, T3, T4> MallocSizeOf for (T1, T2, T3, T4) -where - T1: MallocSizeOf, - T2: MallocSizeOf, - T3: MallocSizeOf, - T4: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) + self.1.size_of(ops) + self.2.size_of(ops) + self.3.size_of(ops) - } -} - -impl<T: MallocSizeOf> MallocSizeOf for Option<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if let Some(val) = self.as_ref() { - val.size_of(ops) - } else { - 0 - } - } -} - -impl<T: MallocSizeOf, E: MallocSizeOf> MallocSizeOf for Result<T, E> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - Ok(ref x) => x.size_of(ops), - Err(ref e) => e.size_of(ops), - } - } -} - -impl<T: MallocSizeOf + Copy> MallocSizeOf for std::cell::Cell<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.get().size_of(ops) - } -} - -impl<T: MallocSizeOf> MallocSizeOf for std::cell::RefCell<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.borrow().size_of(ops) - } -} - -impl<'a, B: ?Sized + ToOwned> MallocSizeOf for std::borrow::Cow<'a, B> -where - B::Owned: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - std::borrow::Cow::Borrowed(_) => 0, - std::borrow::Cow::Owned(ref b) => b.size_of(ops), - } - } -} - -impl<T: MallocSizeOf> MallocSizeOf for [T] { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -#[cfg(feature = "servo")] -impl MallocShallowSizeOf for ByteBuf { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for ByteBuf { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl<T> MallocShallowSizeOf for Vec<T> { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } -} - -impl<T: MallocSizeOf> MallocSizeOf for Vec<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl<T> MallocShallowSizeOf for std::collections::VecDeque<T> { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.has_malloc_enclosing_size_of() { - if let Some(front) = self.front() { - // The front element is an interior pointer. - unsafe { ops.malloc_enclosing_size_of(&*front) } - } else { - // This assumes that no memory is allocated when the VecDeque is empty. - 0 - } - } else { - // An estimate. - self.capacity() * size_of::<T>() - } - } -} - -impl<T: MallocSizeOf> MallocSizeOf for std::collections::VecDeque<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl<A: smallvec::Array> MallocShallowSizeOf for smallvec::SmallVec<A> { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if self.spilled() { - unsafe { ops.malloc_size_of(self.as_ptr()) } - } else { - 0 - } - } -} - -impl<A> MallocSizeOf for smallvec::SmallVec<A> -where - A: smallvec::Array, - A::Item: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -impl<T> MallocShallowSizeOf for thin_vec::ThinVec<T> { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if self.capacity() == 0 { - // If it's the singleton we might not be a heap pointer. - return 0; - } - - assert_eq!( - std::mem::size_of::<Self>(), - std::mem::size_of::<*const ()>() - ); - unsafe { ops.malloc_size_of(*(self as *const Self as *const *const ())) } - } -} - -impl<T: MallocSizeOf> MallocSizeOf for thin_vec::ThinVec<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for elem in self.iter() { - n += elem.size_of(ops); - } - n - } -} - -macro_rules! malloc_size_of_hash_set { - ($ty:ty) => { - impl<T, S> MallocShallowSizeOf for $ty - where - T: Eq + Hash, - S: BuildHasher, - { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.has_malloc_enclosing_size_of() { - // The first value from the iterator gives us an interior pointer. - // `ops.malloc_enclosing_size_of()` then gives us the storage size. - // This assumes that the `HashSet`'s contents (values and hashes) - // are all stored in a single contiguous heap allocation. - self.iter() - .next() - .map_or(0, |t| unsafe { ops.malloc_enclosing_size_of(t) }) - } else { - // An estimate. - self.capacity() * (size_of::<T>() + size_of::<usize>()) - } - } - } - - impl<T, S> MallocSizeOf for $ty - where - T: Eq + Hash + MallocSizeOf, - S: BuildHasher, - { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for t in self.iter() { - n += t.size_of(ops); - } - n - } - } - }; -} - -malloc_size_of_hash_set!(std::collections::HashSet<T, S>); -malloc_size_of_hash_set!(indexmap::IndexSet<T, S>); - -macro_rules! malloc_size_of_hash_map { - ($ty:ty) => { - impl<K, V, S> MallocShallowSizeOf for $ty - where - K: Eq + Hash, - S: BuildHasher, - { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - // See the implementation for std::collections::HashSet for details. - if ops.has_malloc_enclosing_size_of() { - self.values() - .next() - .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) }) - } else { - self.capacity() * (size_of::<V>() + size_of::<K>() + size_of::<usize>()) - } - } - } - - impl<K, V, S> MallocSizeOf for $ty - where - K: Eq + Hash + MallocSizeOf, - V: MallocSizeOf, - S: BuildHasher, - { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for (k, v) in self.iter() { - n += k.size_of(ops); - n += v.size_of(ops); - } - n - } - } - }; -} - -malloc_size_of_hash_map!(std::collections::HashMap<K, V, S>); -malloc_size_of_hash_map!(indexmap::IndexMap<K, V, S>); - -impl<K, V> MallocShallowSizeOf for std::collections::BTreeMap<K, V> -where - K: Eq + Hash, -{ - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.has_malloc_enclosing_size_of() { - self.values() - .next() - .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) }) - } else { - self.len() * (size_of::<V>() + size_of::<K>() + size_of::<usize>()) - } - } -} - -impl<K, V> MallocSizeOf for std::collections::BTreeMap<K, V> -where - K: Eq + Hash + MallocSizeOf, - V: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.shallow_size_of(ops); - for (k, v) in self.iter() { - n += k.size_of(ops); - n += v.size_of(ops); - } - n - } -} - -// PhantomData is always 0. -impl<T> MallocSizeOf for std::marker::PhantomData<T> { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -// XXX: we don't want MallocSizeOf to be defined for Rc and Arc. If negative -// trait bounds are ever allowed, this code should be uncommented. -// (We do have a compile-fail test for this: -// rc_arc_must_not_derive_malloc_size_of.rs) -//impl<T> !MallocSizeOf for Arc<T> { } -//impl<T> !MallocShallowSizeOf for Arc<T> { } - -impl<T> MallocUnconditionalShallowSizeOf for servo_arc::Arc<T> { - fn unconditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.heap_ptr()) } - } -} - -impl<T: MallocSizeOf> MallocUnconditionalSizeOf for servo_arc::Arc<T> { - fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.unconditional_shallow_size_of(ops) + (**self).size_of(ops) - } -} - -impl<T> MallocConditionalShallowSizeOf for servo_arc::Arc<T> { - fn conditional_shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.have_seen_ptr(self.heap_ptr()) { - 0 - } else { - self.unconditional_shallow_size_of(ops) - } - } -} - -impl<T: MallocSizeOf> MallocConditionalSizeOf for servo_arc::Arc<T> { - fn conditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if ops.have_seen_ptr(self.heap_ptr()) { - 0 - } else { - self.unconditional_size_of(ops) - } - } -} - -/// If a mutex is stored directly as a member of a data type that is being measured, -/// it is the unique owner of its contents and deserves to be measured. -/// -/// If a mutex is stored inside of an Arc value as a member of a data type that is being measured, -/// the Arc will not be automatically measured so there is no risk of overcounting the mutex's -/// contents. -impl<T: MallocSizeOf> MallocSizeOf for std::sync::Mutex<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - (*self.lock().unwrap()).size_of(ops) - } -} - -impl MallocSizeOf for smallbitvec::SmallBitVec { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - if let Some(ptr) = self.heap_ptr() { - unsafe { ops.malloc_size_of(ptr) } - } else { - 0 - } - } -} - -impl<T: MallocSizeOf, Unit> MallocSizeOf for euclid::Length<T, Unit> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - -impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for euclid::Scale<T, Src, Dst> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - -impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Point2D<T, U> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.x.size_of(ops) + self.y.size_of(ops) - } -} - -impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Rect<T, U> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.origin.size_of(ops) + self.size.size_of(ops) - } -} - -impl<T: MallocSizeOf, U> MallocSizeOf for euclid::SideOffsets2D<T, U> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.top.size_of(ops) + - self.right.size_of(ops) + - self.bottom.size_of(ops) + - self.left.size_of(ops) - } -} - -impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Size2D<T, U> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.width.size_of(ops) + self.height.size_of(ops) - } -} - -impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for euclid::Transform2D<T, Src, Dst> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.m11.size_of(ops) + - self.m12.size_of(ops) + - self.m21.size_of(ops) + - self.m22.size_of(ops) + - self.m31.size_of(ops) + - self.m32.size_of(ops) - } -} - -impl<T: MallocSizeOf, Src, Dst> MallocSizeOf for euclid::Transform3D<T, Src, Dst> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.m11.size_of(ops) + - self.m12.size_of(ops) + - self.m13.size_of(ops) + - self.m14.size_of(ops) + - self.m21.size_of(ops) + - self.m22.size_of(ops) + - self.m23.size_of(ops) + - self.m24.size_of(ops) + - self.m31.size_of(ops) + - self.m32.size_of(ops) + - self.m33.size_of(ops) + - self.m34.size_of(ops) + - self.m41.size_of(ops) + - self.m42.size_of(ops) + - self.m43.size_of(ops) + - self.m44.size_of(ops) - } -} - -impl<T: MallocSizeOf, U> MallocSizeOf for euclid::Vector2D<T, U> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.x.size_of(ops) + self.y.size_of(ops) - } -} - -impl MallocSizeOf for selectors::parser::AncestorHashes { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let selectors::parser::AncestorHashes { ref packed_hashes } = *self; - packed_hashes.size_of(ops) - } -} - -impl<Impl: selectors::parser::SelectorImpl> MallocSizeOf for selectors::parser::Selector<Impl> -where - Impl::NonTSPseudoClass: MallocSizeOf, - Impl::PseudoElement: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - - // It's OK to measure this ThinArc directly because it's the - // "primary" reference. (The secondary references are on the - // Stylist.) - n += unsafe { ops.malloc_size_of(self.thin_arc_heap_ptr()) }; - for component in self.iter_raw_match_order() { - n += component.size_of(ops); - } - - n - } -} - -impl<Impl: selectors::parser::SelectorImpl> MallocSizeOf for selectors::parser::Component<Impl> -where - Impl::NonTSPseudoClass: MallocSizeOf, - Impl::PseudoElement: MallocSizeOf, -{ - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - use selectors::parser::Component; - - match self { - Component::AttributeOther(ref attr_selector) => attr_selector.size_of(ops), - Component::Negation(ref components) => components.size_of(ops), - Component::NonTSPseudoClass(ref pseudo) => (*pseudo).size_of(ops), - Component::Slotted(ref selector) | Component::Host(Some(ref selector)) => { - selector.size_of(ops) - }, - Component::Is(ref list) | Component::Where(ref list) => list.size_of(ops), - Component::Has(ref relative_selectors) => relative_selectors.size_of(ops), - Component::NthOf(ref nth_of_data) => nth_of_data.size_of(ops), - Component::PseudoElement(ref pseudo) => (*pseudo).size_of(ops), - Component::Combinator(..) | - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::DefaultNamespace(..) | - Component::Namespace(..) | - Component::ExplicitUniversalType | - Component::LocalName(..) | - Component::ID(..) | - Component::Part(..) | - Component::Class(..) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::Root | - Component::Empty | - Component::Scope | - Component::ParentSelector | - Component::Nth(..) | - Component::Host(None) | - Component::RelativeSelectorAnchor => 0, - } - } -} - -impl<Impl: selectors::parser::SelectorImpl> MallocSizeOf - for selectors::attr::AttrSelectorWithOptionalNamespace<Impl> -{ - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -impl MallocSizeOf for Void { - #[inline] - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - void::unreachable(*self) - } -} - -#[cfg(feature = "servo")] -impl<Static: string_cache::StaticAtomSet> MallocSizeOf for string_cache::Atom<Static> { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -/// For use on types where size_of() returns 0. -#[macro_export] -macro_rules! malloc_size_of_is_0( - ($($ty:ty),+) => ( - $( - impl $crate::MallocSizeOf for $ty { - #[inline(always)] - fn size_of(&self, _: &mut $crate::MallocSizeOfOps) -> usize { - 0 - } - } - )+ - ); - ($($ty:ident<$($gen:ident),+>),+) => ( - $( - impl<$($gen: $crate::MallocSizeOf),+> $crate::MallocSizeOf for $ty<$($gen),+> { - #[inline(always)] - fn size_of(&self, _: &mut $crate::MallocSizeOfOps) -> usize { - 0 - } - } - )+ - ); -); - -malloc_size_of_is_0!(bool, char, str); -malloc_size_of_is_0!(u8, u16, u32, u64, u128, usize); -malloc_size_of_is_0!(i8, i16, i32, i64, i128, isize); -malloc_size_of_is_0!(f32, f64); -malloc_size_of_is_0!(std::num::NonZeroU64); - -malloc_size_of_is_0!(std::sync::atomic::AtomicBool); -malloc_size_of_is_0!(std::sync::atomic::AtomicIsize); -malloc_size_of_is_0!(std::sync::atomic::AtomicUsize); - -malloc_size_of_is_0!(Range<u8>, Range<u16>, Range<u32>, Range<u64>, Range<usize>); -malloc_size_of_is_0!(Range<i8>, Range<i16>, Range<i32>, Range<i64>, Range<isize>); -malloc_size_of_is_0!(Range<f32>, Range<f64>); - -malloc_size_of_is_0!(app_units::Au); - -malloc_size_of_is_0!(cssparser::RGBA, cssparser::TokenSerializationType); - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(csp::Destination); - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(Uuid); - -#[cfg(feature = "url")] -impl MallocSizeOf for url::Host { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - url::Host::Domain(ref s) => s.size_of(ops), - _ => 0, - } - } -} -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::BorderRadius); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::BorderStyle); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::BoxShadowClipMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ColorF); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ComplexClipRegion); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ExtendMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::FilterOp); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ExternalScrollId); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::FontInstanceKey); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::GradientStop); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::GlyphInstance); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::NinePatchBorder); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ImageKey); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::ImageRendering); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::LineStyle); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::MixBlendMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::NormalBorder); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::RepeatMode); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::StickyOffsetBounds); -#[cfg(feature = "webrender_api")] -malloc_size_of_is_0!(webrender_api::TransformStyle); - -#[cfg(feature = "servo")] -impl MallocSizeOf for keyboard_types::Key { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match self { - keyboard_types::Key::Character(ref s) => s.size_of(ops), - _ => 0, - } - } -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(keyboard_types::Modifiers); - -#[cfg(feature = "servo")] -impl MallocSizeOf for xml5ever::QualName { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.prefix.size_of(ops) + self.ns.size_of(ops) + self.local.size_of(ops) - } -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(time::Duration); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(time::Tm); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(std::time::Duration); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(std::time::SystemTime); -#[cfg(feature = "servo")] -malloc_size_of_is_0!(std::time::Instant); - -// Placeholder for unique case where internals of Sender cannot be measured. -// malloc size of is 0 macro complains about type supplied! -#[cfg(feature = "servo")] -impl<T> MallocSizeOf for crossbeam_channel::Sender<T> { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -#[cfg(feature = "servo")] -impl<T> MallocSizeOf for tokio::sync::mpsc::UnboundedSender<T> { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -#[cfg(feature = "servo")] -impl MallocSizeOf for http::StatusCode { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - 0 - } -} - -/// Measurable that defers to inner value and used to verify MallocSizeOf implementation in a -/// struct. -#[derive(Clone)] -pub struct Measurable<T: MallocSizeOf>(pub T); - -impl<T: MallocSizeOf> Deref for Measurable<T> { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl<T: MallocSizeOf> DerefMut for Measurable<T> { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -#[cfg(feature = "servo")] -impl<T: MallocSizeOf> MallocSizeOf for accountable_refcell::RefCell<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.borrow().size_of(ops) - } -} diff --git a/components/malloc_size_of/rustfmt.toml b/components/malloc_size_of/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/malloc_size_of/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/metrics/Cargo.toml b/components/metrics/Cargo.toml index 29579d9aacb..08701edf372 100644 --- a/components/metrics/Cargo.toml +++ b/components/metrics/Cargo.toml @@ -14,7 +14,7 @@ path = "lib.rs" gfx_traits = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } msg = { workspace = true } profile_traits = { workspace = true } diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 0d188ba4d13..01da269cd0b 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -38,7 +38,7 @@ ipc-channel = { workspace = true } lazy_static = { workspace = true } libflate = "0.1" log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } mime = { workspace = true } mime_guess = { workspace = true } @@ -53,7 +53,7 @@ rustls-pemfile = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } servo_config = { path = "../config" } servo_url = { path = "../url" } sha2 = "0.10" diff --git a/components/pixels/Cargo.toml b/components/pixels/Cargo.toml index 708b16163ba..f201ff7c0e4 100644 --- a/components/pixels/Cargo.toml +++ b/components/pixels/Cargo.toml @@ -12,6 +12,6 @@ path = "lib.rs" [dependencies] euclid = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } serde = { workspace = true, features = ["derive"] } diff --git a/components/range/Cargo.toml b/components/range/Cargo.toml index bcaf874786f..c674d5a799c 100644 --- a/components/range/Cargo.toml +++ b/components/range/Cargo.toml @@ -11,7 +11,7 @@ name = "range" path = "lib.rs" [dependencies] -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } num-traits = { workspace = true } serde = { workspace = true } diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index cc29865bd69..b9e9ff1249c 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -68,7 +68,7 @@ keyboard-types = { workspace = true } lazy_static = { workspace = true } libc = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } media = { path = "../media" } metrics = { path = "../metrics" } @@ -88,20 +88,20 @@ ref_filter_map = "1.0.1" regex = { workspace = true } script_layout_interface = { workspace = true } script_traits = { workspace = true } -selectors = { path = "../selectors" } +selectors = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_bytes = { workspace = true } servo-media = { git = "https://github.com/servo/media" } servo_allocator = { path = "../allocator" } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms" } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_rand = { path = "../rand" } servo_url = { path = "../url" } smallvec = { workspace = true, features = ["union"] } sparkle = { workspace = true } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } swapper = "0.1" tempfile = "3" diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml deleted file mode 100644 index da1d0e509d8..00000000000 --- a/components/selectors/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "selectors" -version = "0.24.0" -authors = ["The Servo Project Developers"] -documentation = "https://docs.rs/selectors/" -description = "CSS Selectors matching for Rust" -repository = "https://github.com/servo/servo" -readme = "README.md" -keywords = ["css", "selectors"] -license = "MPL-2.0" -build = "build.rs" - -[lib] -name = "selectors" -path = "lib.rs" - -[features] -bench = [] - -[dependencies] -bitflags = "1.0" -cssparser = { workspace = true } -derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign"] } -fxhash = "0.2" -log = "0.4" -new_debug_unreachable = "1" -phf = "0.10" -precomputed-hash = "0.1" -servo_arc = { version = "0.2", path = "../servo_arc" } -size_of_test = { path = "../size_of_test" } -smallvec = "1.0" -to_shmem = { version = "0.0.1", path = "../to_shmem" } -to_shmem_derive = { version = "0.0.1", path = "../to_shmem_derive" } - -[build-dependencies] -phf_codegen = "0.10" diff --git a/components/selectors/README.md b/components/selectors/README.md deleted file mode 100644 index dac4a7ff91a..00000000000 --- a/components/selectors/README.md +++ /dev/null @@ -1,25 +0,0 @@ -rust-selectors -============== - -* []( - https://travis-ci.com/servo/rust-selectors) -* [Documentation](https://docs.rs/selectors/) -* [crates.io](https://crates.io/crates/selectors) - -CSS Selectors library for Rust. -Includes parsing and serilization of selectors, -as well as matching against a generic tree of elements. -Pseudo-elements and most pseudo-classes are generic as well. - -**Warning:** breaking changes are made to this library fairly frequently -(13 times in 2016, for example). -However you can use this crate without updating it that often, -old versions stay available on crates.io and Cargo will only automatically update -to versions that are numbered as compatible. - -To see how to use this library with your own tree representation, -see [Kuchiki’s `src/select.rs`](https://github.com/kuchiki-rs/kuchiki/blob/master/src/select.rs). -(Note however that Kuchiki is not always up to date with the latest rust-selectors version, -so that code may need to be tweaked.) -If you don’t already have a tree data structure, -consider using [Kuchiki](https://github.com/kuchiki-rs/kuchiki) itself. diff --git a/components/selectors/attr.rs b/components/selectors/attr.rs deleted file mode 100644 index e3122ea701e..00000000000 --- a/components/selectors/attr.rs +++ /dev/null @@ -1,192 +0,0 @@ -/* 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/. */ - -use crate::parser::SelectorImpl; -use cssparser::ToCss; -use std::fmt; - -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> { - #[shmem(field_bound)] - pub namespace: Option<NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>>, - #[shmem(field_bound)] - pub local_name: Impl::LocalName, - pub local_name_lower: Impl::LocalName, - #[shmem(field_bound)] - pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>, -} - -impl<Impl: SelectorImpl> AttrSelectorWithOptionalNamespace<Impl> { - pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> { - self.namespace.as_ref().map(|ns| match ns { - NamespaceConstraint::Any => NamespaceConstraint::Any, - NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url), - }) - } -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -pub enum NamespaceConstraint<NamespaceUrl> { - Any, - - /// Empty string for no namespace - Specific(NamespaceUrl), -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -pub enum ParsedAttrSelectorOperation<AttrValue> { - Exists, - WithValue { - operator: AttrSelectorOperator, - case_sensitivity: ParsedCaseSensitivity, - value: AttrValue, - }, -} - -#[derive(Clone, Eq, PartialEq)] -pub enum AttrSelectorOperation<AttrValue> { - Exists, - WithValue { - operator: AttrSelectorOperator, - case_sensitivity: CaseSensitivity, - value: AttrValue, - }, -} - -impl<AttrValue> AttrSelectorOperation<AttrValue> { - pub fn eval_str(&self, element_attr_value: &str) -> bool - where - AttrValue: AsRef<str>, - { - match *self { - AttrSelectorOperation::Exists => true, - AttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref value, - } => operator.eval_str( - element_attr_value, - value.as_ref(), - case_sensitivity, - ), - } - } -} - -#[derive(Clone, Copy, Eq, PartialEq, ToShmem)] -pub enum AttrSelectorOperator { - Equal, - Includes, - DashMatch, - Prefix, - Substring, - Suffix, -} - -impl ToCss for AttrSelectorOperator { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - // https://drafts.csswg.org/cssom/#serializing-selectors - // See "attribute selector". - dest.write_str(match *self { - AttrSelectorOperator::Equal => "=", - AttrSelectorOperator::Includes => "~=", - AttrSelectorOperator::DashMatch => "|=", - AttrSelectorOperator::Prefix => "^=", - AttrSelectorOperator::Substring => "*=", - AttrSelectorOperator::Suffix => "$=", - }) - } -} - -impl AttrSelectorOperator { - pub fn eval_str( - self, - element_attr_value: &str, - attr_selector_value: &str, - case_sensitivity: CaseSensitivity, - ) -> bool { - let e = element_attr_value.as_bytes(); - let s = attr_selector_value.as_bytes(); - let case = case_sensitivity; - match self { - AttrSelectorOperator::Equal => case.eq(e, s), - AttrSelectorOperator::Prefix => { - !s.is_empty() && e.len() >= s.len() && case.eq(&e[..s.len()], s) - }, - AttrSelectorOperator::Suffix => { - !s.is_empty() && e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) - }, - AttrSelectorOperator::Substring => { - !s.is_empty() && case.contains(element_attr_value, attr_selector_value) - }, - AttrSelectorOperator::Includes => { - !s.is_empty() && - element_attr_value - .split(SELECTOR_WHITESPACE) - .any(|part| case.eq(part.as_bytes(), s)) - }, - AttrSelectorOperator::DashMatch => { - case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s)) - }, - } - } -} - -/// The definition of whitespace per CSS Selectors Level 3 § 4. -pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C']; - -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub enum ParsedCaseSensitivity { - /// 's' was specified. - ExplicitCaseSensitive, - /// 'i' was specified. - AsciiCaseInsensitive, - /// No flags were specified and HTML says this is a case-sensitive attribute. - CaseSensitive, - /// No flags were specified and HTML says this is a case-insensitive attribute. - AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CaseSensitivity { - CaseSensitive, - AsciiCaseInsensitive, -} - -impl CaseSensitivity { - pub fn eq(self, a: &[u8], b: &[u8]) -> bool { - match self { - CaseSensitivity::CaseSensitive => a == b, - CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), - } - } - - pub fn contains(self, haystack: &str, needle: &str) -> bool { - match self { - CaseSensitivity::CaseSensitive => haystack.contains(needle), - CaseSensitivity::AsciiCaseInsensitive => { - if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { - haystack.bytes().enumerate().any(|(i, byte)| { - if !byte.eq_ignore_ascii_case(&n_first_byte) { - return false; - } - let after_this_byte = &haystack.as_bytes()[i + 1..]; - match after_this_byte.get(..n_rest.len()) { - None => false, - Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest), - } - }) - } else { - // any_str.contains("") == true, - // though these cases should be handled with *NeverMatches and never go here. - true - } - }, - } - } -} diff --git a/components/selectors/bloom.rs b/components/selectors/bloom.rs deleted file mode 100644 index 98461d1ba24..00000000000 --- a/components/selectors/bloom.rs +++ /dev/null @@ -1,422 +0,0 @@ -/* 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/. */ - -//! Counting and non-counting Bloom filters tuned for use as ancestor filters -//! for selector matching. - -use std::fmt::{self, Debug}; - -// The top 8 bits of the 32-bit hash value are not used by the bloom filter. -// Consumers may rely on this to pack hashes more efficiently. -pub const BLOOM_HASH_MASK: u32 = 0x00ffffff; -const KEY_SIZE: usize = 12; - -const ARRAY_SIZE: usize = 1 << KEY_SIZE; -const KEY_MASK: u32 = (1 << KEY_SIZE) - 1; - -/// A counting Bloom filter with 8-bit counters. -pub type BloomFilter = CountingBloomFilter<BloomStorageU8>; - -/// A counting Bloom filter with parameterized storage to handle -/// counters of different sizes. For now we assume that having two hash -/// functions is enough, but we may revisit that decision later. -/// -/// The filter uses an array with 2**KeySize entries. -/// -/// Assuming a well-distributed hash function, a Bloom filter with -/// array size M containing N elements and -/// using k hash function has expected false positive rate exactly -/// -/// $ (1 - (1 - 1/M)^{kN})^k $ -/// -/// because each array slot has a -/// -/// $ (1 - 1/M)^{kN} $ -/// -/// chance of being 0, and the expected false positive rate is the -/// probability that all of the k hash functions will hit a nonzero -/// slot. -/// -/// For reasonable assumptions (M large, kN large, which should both -/// hold if we're worried about false positives) about M and kN this -/// becomes approximately -/// -/// $$ (1 - \exp(-kN/M))^k $$ -/// -/// For our special case of k == 2, that's $(1 - \exp(-2N/M))^2$, -/// or in other words -/// -/// $$ N/M = -0.5 * \ln(1 - \sqrt(r)) $$ -/// -/// where r is the false positive rate. This can be used to compute -/// the desired KeySize for a given load N and false positive rate r. -/// -/// If N/M is assumed small, then the false positive rate can -/// further be approximated as 4*N^2/M^2. So increasing KeySize by -/// 1, which doubles M, reduces the false positive rate by about a -/// factor of 4, and a false positive rate of 1% corresponds to -/// about M/N == 20. -/// -/// What this means in practice is that for a few hundred keys using a -/// KeySize of 12 gives false positive rates on the order of 0.25-4%. -/// -/// Similarly, using a KeySize of 10 would lead to a 4% false -/// positive rate for N == 100 and to quite bad false positive -/// rates for larger N. -#[derive(Clone, Default)] -pub struct CountingBloomFilter<S> -where - S: BloomStorage, -{ - storage: S, -} - -impl<S> CountingBloomFilter<S> -where - S: BloomStorage, -{ - /// Creates a new bloom filter. - #[inline] - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn clear(&mut self) { - self.storage = Default::default(); - } - - // Slow linear accessor to make sure the bloom filter is zeroed. This should - // never be used in release builds. - #[cfg(debug_assertions)] - pub fn is_zeroed(&self) -> bool { - self.storage.is_zeroed() - } - - #[cfg(not(debug_assertions))] - pub fn is_zeroed(&self) -> bool { - unreachable!() - } - - /// Inserts an item with a particular hash into the bloom filter. - #[inline] - pub fn insert_hash(&mut self, hash: u32) { - self.storage.adjust_first_slot(hash, true); - self.storage.adjust_second_slot(hash, true); - } - - /// Removes an item with a particular hash from the bloom filter. - #[inline] - pub fn remove_hash(&mut self, hash: u32) { - self.storage.adjust_first_slot(hash, false); - self.storage.adjust_second_slot(hash, false); - } - - /// Check whether the filter might contain an item with the given hash. - /// This can sometimes return true even if the item is not in the filter, - /// but will never return false for items that are actually in the filter. - #[inline] - pub fn might_contain_hash(&self, hash: u32) -> bool { - !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash) - } -} - -impl<S> Debug for CountingBloomFilter<S> -where - S: BloomStorage, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut slots_used = 0; - for i in 0..ARRAY_SIZE { - if !self.storage.slot_is_empty(i) { - slots_used += 1; - } - } - write!(f, "BloomFilter({}/{})", slots_used, ARRAY_SIZE) - } -} - -pub trait BloomStorage: Clone + Default { - fn slot_is_empty(&self, index: usize) -> bool; - fn adjust_slot(&mut self, index: usize, increment: bool); - fn is_zeroed(&self) -> bool; - - #[inline] - fn first_slot_is_empty(&self, hash: u32) -> bool { - self.slot_is_empty(Self::first_slot_index(hash)) - } - - #[inline] - fn second_slot_is_empty(&self, hash: u32) -> bool { - self.slot_is_empty(Self::second_slot_index(hash)) - } - - #[inline] - fn adjust_first_slot(&mut self, hash: u32, increment: bool) { - self.adjust_slot(Self::first_slot_index(hash), increment) - } - - #[inline] - fn adjust_second_slot(&mut self, hash: u32, increment: bool) { - self.adjust_slot(Self::second_slot_index(hash), increment) - } - - #[inline] - fn first_slot_index(hash: u32) -> usize { - hash1(hash) as usize - } - - #[inline] - fn second_slot_index(hash: u32) -> usize { - hash2(hash) as usize - } -} - -/// Storage class for a CountingBloomFilter that has 8-bit counters. -pub struct BloomStorageU8 { - counters: [u8; ARRAY_SIZE], -} - -impl BloomStorage for BloomStorageU8 { - #[inline] - fn adjust_slot(&mut self, index: usize, increment: bool) { - let slot = &mut self.counters[index]; - if *slot != 0xff { - // full - if increment { - *slot += 1; - } else { - *slot -= 1; - } - } - } - - #[inline] - fn slot_is_empty(&self, index: usize) -> bool { - self.counters[index] == 0 - } - - #[inline] - fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) - } -} - -impl Default for BloomStorageU8 { - fn default() -> Self { - BloomStorageU8 { - counters: [0; ARRAY_SIZE], - } - } -} - -impl Clone for BloomStorageU8 { - fn clone(&self) -> Self { - BloomStorageU8 { - counters: self.counters, - } - } -} - -/// Storage class for a CountingBloomFilter that has 1-bit counters. -pub struct BloomStorageBool { - counters: [u8; ARRAY_SIZE / 8], -} - -impl BloomStorage for BloomStorageBool { - #[inline] - fn adjust_slot(&mut self, index: usize, increment: bool) { - let bit = 1 << (index % 8); - let byte = &mut self.counters[index / 8]; - - // Since we have only one bit for storage, decrementing it - // should never do anything. Assert against an accidental - // decrementing of a bit that was never set. - assert!( - increment || (*byte & bit) != 0, - "should not decrement if slot is already false" - ); - - if increment { - *byte |= bit; - } - } - - #[inline] - fn slot_is_empty(&self, index: usize) -> bool { - let bit = 1 << (index % 8); - (self.counters[index / 8] & bit) == 0 - } - - #[inline] - fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) - } -} - -impl Default for BloomStorageBool { - fn default() -> Self { - BloomStorageBool { - counters: [0; ARRAY_SIZE / 8], - } - } -} - -impl Clone for BloomStorageBool { - fn clone(&self) -> Self { - BloomStorageBool { - counters: self.counters, - } - } -} - -#[inline] -fn hash1(hash: u32) -> u32 { - hash & KEY_MASK -} - -#[inline] -fn hash2(hash: u32) -> u32 { - (hash >> KEY_SIZE) & KEY_MASK -} - -#[test] -fn create_and_insert_some_stuff() { - use fxhash::FxHasher; - use std::hash::{Hash, Hasher}; - use std::mem::transmute; - - fn hash_as_str(i: usize) -> u32 { - let mut hasher = FxHasher::default(); - let s = i.to_string(); - s.hash(&mut hasher); - let hash: u64 = hasher.finish(); - (hash >> 32) as u32 ^ (hash as u32) - } - - let mut bf = BloomFilter::new(); - - // Statically assert that ARRAY_SIZE is a multiple of 8, which - // BloomStorageBool relies on. - unsafe { - transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]); - } - - for i in 0_usize..1000 { - bf.insert_hash(hash_as_str(i)); - } - - for i in 0_usize..1000 { - assert!(bf.might_contain_hash(hash_as_str(i))); - } - - let false_positives = (1001_usize..2000) - .filter(|i| bf.might_contain_hash(hash_as_str(*i))) - .count(); - - assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. - - for i in 0_usize..100 { - bf.remove_hash(hash_as_str(i)); - } - - for i in 100_usize..1000 { - assert!(bf.might_contain_hash(hash_as_str(i))); - } - - let false_positives = (0_usize..100) - .filter(|i| bf.might_contain_hash(hash_as_str(*i))) - .count(); - - assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. - - bf.clear(); - - for i in 0_usize..2000 { - assert!(!bf.might_contain_hash(hash_as_str(i))); - } -} - -#[cfg(feature = "bench")] -#[cfg(test)] -mod bench { - extern crate test; - use super::BloomFilter; - - #[derive(Default)] - struct HashGenerator(u32); - - impl HashGenerator { - fn next(&mut self) -> u32 { - // Each hash is split into two twelve-bit segments, which are used - // as an index into an array. We increment each by 64 so that we - // hit the next cache line, and then another 1 so that our wrapping - // behavior leads us to different entries each time. - // - // Trying to simulate cold caches is rather difficult with the cargo - // benchmarking setup, so it may all be moot depending on the number - // of iterations that end up being run. But we might as well. - self.0 += (65) + (65 << super::KEY_SIZE); - self.0 - } - } - - #[bench] - fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) { - b.iter(|| { - let mut gen1 = HashGenerator::default(); - let mut gen2 = HashGenerator::default(); - let mut bf = BloomFilter::new(); - for _ in 0_usize..1000 { - bf.insert_hash(gen1.next()); - } - for _ in 0_usize..100 { - bf.remove_hash(gen2.next()); - } - for _ in 100_usize..200 { - test::black_box(bf.might_contain_hash(gen2.next())); - } - }); - } - - #[bench] - fn might_contain_10(b: &mut test::Bencher) { - let bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - b.iter(|| { - for _ in 0..10 { - test::black_box(bf.might_contain_hash(gen.next())); - } - }); - } - - #[bench] - fn clear(b: &mut test::Bencher) { - let mut bf = Box::new(BloomFilter::new()); - b.iter(|| test::black_box(&mut bf).clear()); - } - - #[bench] - fn insert_10(b: &mut test::Bencher) { - let mut bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - b.iter(|| { - for _ in 0..10 { - test::black_box(bf.insert_hash(gen.next())); - } - }); - } - - #[bench] - fn remove_10(b: &mut test::Bencher) { - let mut bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - // Note: this will underflow, and that's ok. - b.iter(|| { - for _ in 0..10 { - bf.remove_hash(gen.next()) - } - }); - } -} diff --git a/components/selectors/build.rs b/components/selectors/build.rs deleted file mode 100644 index c5c3803991e..00000000000 --- a/components/selectors/build.rs +++ /dev/null @@ -1,77 +0,0 @@ -/* 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/. */ - -extern crate phf_codegen; - -use std::env; -use std::fs::File; -use std::io::{BufWriter, Write}; -use std::path::Path; - -fn main() { - let path = Path::new(&env::var_os("OUT_DIR").unwrap()) - .join("ascii_case_insensitive_html_attributes.rs"); - let mut file = BufWriter::new(File::create(&path).unwrap()); - - let mut set = phf_codegen::Set::new(); - for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() { - set.entry(name); - } - write!( - &mut file, - "{{ static SET: ::phf::Set<&'static str> = {}; &SET }}", - set.build(), - ) - .unwrap(); -} - -/// <https://html.spec.whatwg.org/multipage/#selectors> -static ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES: &str = r#" - accept - accept-charset - align - alink - axis - bgcolor - charset - checked - clear - codetype - color - compact - declare - defer - dir - direction - disabled - enctype - face - frame - hreflang - http-equiv - lang - language - link - media - method - multiple - nohref - noresize - noshade - nowrap - readonly - rel - rev - rules - scope - scrolling - selected - shape - target - text - type - valign - valuetype - vlink -"#; diff --git a/components/selectors/builder.rs b/components/selectors/builder.rs deleted file mode 100644 index 63a6323c532..00000000000 --- a/components/selectors/builder.rs +++ /dev/null @@ -1,398 +0,0 @@ -/* 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/. */ - -//! Helper module to build up a selector safely and efficiently. -//! -//! Our selector representation is designed to optimize matching, and has -//! several requirements: -//! * All simple selectors and combinators are stored inline in the same buffer -//! as Component instances. -//! * We store the top-level compound selectors from right to left, i.e. in -//! matching order. -//! * We store the simple selectors for each combinator from left to right, so -//! that we match the cheaper simple selectors first. -//! -//! Meeting all these constraints without extra memmove traffic during parsing -//! is non-trivial. This module encapsulates those details and presents an -//! easy-to-use API for the parser. - -use crate::parser::{Combinator, Component, RelativeSelector, Selector, SelectorImpl}; -use crate::sink::Push; -use servo_arc::{Arc, HeaderWithLength, ThinArc}; -use smallvec::{self, SmallVec}; -use std::cmp; -use std::iter; -use std::ptr; -use std::slice; - -/// Top-level SelectorBuilder struct. This should be stack-allocated by the -/// consumer and never moved (because it contains a lot of inline data that -/// would be slow to memmov). -/// -/// After instantation, callers may call the push_simple_selector() and -/// push_combinator() methods to append selector data as it is encountered -/// (from left to right). Once the process is complete, callers should invoke -/// build(), which transforms the contents of the SelectorBuilder into a heap- -/// allocated Selector and leaves the builder in a drained state. -#[derive(Debug)] -pub struct SelectorBuilder<Impl: SelectorImpl> { - /// The entire sequence of simple selectors, from left to right, without combinators. - /// - /// We make this large because the result of parsing a selector is fed into a new - /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, - /// Components are large enough that we don't have much cache locality benefit - /// from reserving stack space for fewer of them. - simple_selectors: SmallVec<[Component<Impl>; 32]>, - /// The combinators, and the length of the compound selector to their left. - combinators: SmallVec<[(Combinator, usize); 16]>, - /// The length of the current compount selector. - current_len: usize, -} - -impl<Impl: SelectorImpl> Default for SelectorBuilder<Impl> { - #[inline(always)] - fn default() -> Self { - SelectorBuilder { - simple_selectors: SmallVec::new(), - combinators: SmallVec::new(), - current_len: 0, - } - } -} - -impl<Impl: SelectorImpl> Push<Component<Impl>> for SelectorBuilder<Impl> { - fn push(&mut self, value: Component<Impl>) { - self.push_simple_selector(value); - } -} - -impl<Impl: SelectorImpl> SelectorBuilder<Impl> { - /// Pushes a simple selector onto the current compound selector. - #[inline(always)] - pub fn push_simple_selector(&mut self, ss: Component<Impl>) { - assert!(!ss.is_combinator()); - self.simple_selectors.push(ss); - self.current_len += 1; - } - - /// Completes the current compound selector and starts a new one, delimited - /// by the given combinator. - #[inline(always)] - pub fn push_combinator(&mut self, c: Combinator) { - self.combinators.push((c, self.current_len)); - self.current_len = 0; - } - - /// Returns true if combinators have ever been pushed to this builder. - #[inline(always)] - pub fn has_combinators(&self) -> bool { - !self.combinators.is_empty() - } - - /// Consumes the builder, producing a Selector. - #[inline(always)] - pub fn build(&mut self) -> ThinArc<SpecificityAndFlags, Component<Impl>> { - // Compute the specificity and flags. - let sf = specificity_and_flags(self.simple_selectors.iter()); - self.build_with_specificity_and_flags(sf) - } - - /// Builds with an explicit SpecificityAndFlags. This is separated from build() so - /// that unit tests can pass an explicit specificity. - #[inline(always)] - pub(crate) fn build_with_specificity_and_flags( - &mut self, - spec: SpecificityAndFlags, - ) -> ThinArc<SpecificityAndFlags, Component<Impl>> { - // First, compute the total number of Components we'll need to allocate - // space for. - let full_len = self.simple_selectors.len() + self.combinators.len(); - - // Create the header. - let header = HeaderWithLength::new(spec, full_len); - - // Create the Arc using an iterator that drains our buffers. - - // Use a raw pointer to be able to call set_len despite "borrowing" the slice. - // This is similar to SmallVec::drain, but we use a slice here because - // we’re gonna traverse it non-linearly. - let raw_simple_selectors: *const [Component<Impl>] = &*self.simple_selectors; - unsafe { - // Panic-safety: if SelectorBuilderIter is not iterated to the end, - // some simple selectors will safely leak. - self.simple_selectors.set_len(0) - } - let (rest, current) = split_from_end(unsafe { &*raw_simple_selectors }, self.current_len); - let iter = SelectorBuilderIter { - current_simple_selectors: current.iter(), - rest_of_simple_selectors: rest, - combinators: self.combinators.drain(..).rev(), - }; - - Arc::into_thin(Arc::from_header_and_iter(header, iter)) - } -} - -struct SelectorBuilderIter<'a, Impl: SelectorImpl> { - current_simple_selectors: slice::Iter<'a, Component<Impl>>, - rest_of_simple_selectors: &'a [Component<Impl>], - combinators: iter::Rev<smallvec::Drain<'a, [(Combinator, usize); 16]>>, -} - -impl<'a, Impl: SelectorImpl> ExactSizeIterator for SelectorBuilderIter<'a, Impl> { - fn len(&self) -> usize { - self.current_simple_selectors.len() + - self.rest_of_simple_selectors.len() + - self.combinators.len() - } -} - -impl<'a, Impl: SelectorImpl> Iterator for SelectorBuilderIter<'a, Impl> { - type Item = Component<Impl>; - #[inline(always)] - fn next(&mut self) -> Option<Self::Item> { - if let Some(simple_selector_ref) = self.current_simple_selectors.next() { - // Move a simple selector out of this slice iterator. - // This is safe because we’ve called SmallVec::set_len(0) above, - // so SmallVec::drop won’t drop this simple selector. - unsafe { Some(ptr::read(simple_selector_ref)) } - } else { - self.combinators.next().map(|(combinator, len)| { - let (rest, current) = split_from_end(self.rest_of_simple_selectors, len); - self.rest_of_simple_selectors = rest; - self.current_simple_selectors = current.iter(); - Component::Combinator(combinator) - }) - } - } - - fn size_hint(&self) -> (usize, Option<usize>) { - (self.len(), Some(self.len())) - } -} - -fn split_from_end<T>(s: &[T], at: usize) -> (&[T], &[T]) { - s.split_at(s.len() - at) -} - -bitflags! { - /// Flags that indicate at which point of parsing a selector are we. - #[derive(Default, ToShmem)] - pub (crate) struct SelectorFlags : u8 { - const HAS_PSEUDO = 1 << 0; - const HAS_SLOTTED = 1 << 1; - const HAS_PART = 1 << 2; - const HAS_PARENT = 1 << 3; - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub struct SpecificityAndFlags { - /// There are two free bits here, since we use ten bits for each specificity - /// kind (id, class, element). - pub(crate) specificity: u32, - /// There's padding after this field due to the size of the flags. - pub(crate) flags: SelectorFlags, -} - -impl SpecificityAndFlags { - #[inline] - pub fn specificity(&self) -> u32 { - self.specificity - } - - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PSEUDO) - } - - #[inline] - pub fn has_parent_selector(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PARENT) - } - - #[inline] - pub fn is_slotted(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_SLOTTED) - } - - #[inline] - pub fn is_part(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PART) - } -} - -const MAX_10BIT: u32 = (1u32 << 10) - 1; - -#[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)] -pub(crate) struct Specificity { - id_selectors: u32, - class_like_selectors: u32, - element_selectors: u32, -} - -impl From<u32> for Specificity { - #[inline] - fn from(value: u32) -> Specificity { - assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT); - Specificity { - id_selectors: value >> 20, - class_like_selectors: (value >> 10) & MAX_10BIT, - element_selectors: value & MAX_10BIT, - } - } -} - -impl From<Specificity> for u32 { - #[inline] - fn from(specificity: Specificity) -> u32 { - cmp::min(specificity.id_selectors, MAX_10BIT) << 20 | - cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10 | - cmp::min(specificity.element_selectors, MAX_10BIT) - } -} - -pub(crate) fn specificity_and_flags<Impl>(iter: slice::Iter<Component<Impl>>) -> SpecificityAndFlags -where - Impl: SelectorImpl, -{ - complex_selector_specificity_and_flags(iter).into() -} - -fn complex_selector_specificity_and_flags<Impl>( - iter: slice::Iter<Component<Impl>>, -) -> SpecificityAndFlags -where - Impl: SelectorImpl, -{ - fn component_specificity<Impl>( - simple_selector: &Component<Impl>, - specificity: &mut Specificity, - flags: &mut SelectorFlags, - ) where - Impl: SelectorImpl, - { - match *simple_selector { - Component::Combinator(..) => {}, - Component::ParentSelector => flags.insert(SelectorFlags::HAS_PARENT), - Component::Part(..) => { - flags.insert(SelectorFlags::HAS_PART); - specificity.element_selectors += 1 - }, - Component::PseudoElement(..) => { - flags.insert(SelectorFlags::HAS_PSEUDO); - specificity.element_selectors += 1 - }, - Component::LocalName(..) => specificity.element_selectors += 1, - Component::Slotted(ref selector) => { - flags.insert(SelectorFlags::HAS_SLOTTED); - specificity.element_selectors += 1; - // Note that due to the way ::slotted works we only compete with - // other ::slotted rules, so the above rule doesn't really - // matter, but we do it still for consistency with other - // pseudo-elements. - // - // See: https://github.com/w3c/csswg-drafts/issues/1915 - *specificity += Specificity::from(selector.specificity()); - if selector.has_parent_selector() { - flags.insert(SelectorFlags::HAS_PARENT); - } - }, - Component::Host(ref selector) => { - specificity.class_like_selectors += 1; - if let Some(ref selector) = *selector { - // See: https://github.com/w3c/csswg-drafts/issues/1915 - *specificity += Specificity::from(selector.specificity()); - if selector.has_parent_selector() { - flags.insert(SelectorFlags::HAS_PARENT); - } - } - }, - Component::ID(..) => { - specificity.id_selectors += 1; - }, - Component::Class(..) | - Component::AttributeInNoNamespace { .. } | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeOther(..) | - Component::Root | - Component::Empty | - Component::Scope | - Component::Nth(..) | - Component::NonTSPseudoClass(..) => { - specificity.class_like_selectors += 1; - }, - Component::NthOf(ref nth_of_data) => { - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of the :nth-last-child() pseudo-class, - // like the :nth-child() pseudo-class, combines the - // specificity of a regular pseudo-class with that of its - // selector argument S. - specificity.class_like_selectors += 1; - let sf = selector_list_specificity_and_flags(nth_of_data.selectors().iter()); - *specificity += Specificity::from(sf.specificity); - flags.insert(sf.flags); - }, - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of an :is(), :not(), or :has() pseudo-class - // is replaced by the specificity of the most specific complex - // selector in its selector list argument. - Component::Where(ref list) | - Component::Negation(ref list) | - Component::Is(ref list) => { - let sf = selector_list_specificity_and_flags(list.iter()); - if !matches!(*simple_selector, Component::Where(..)) { - *specificity += Specificity::from(sf.specificity); - } - flags.insert(sf.flags); - }, - Component::Has(ref relative_selectors) => { - let sf = relative_selector_list_specificity_and_flags(relative_selectors); - *specificity += Specificity::from(sf.specificity); - flags.insert(sf.flags); - }, - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::DefaultNamespace(..) | - Component::Namespace(..) | - Component::RelativeSelectorAnchor => { - // Does not affect specificity - }, - } - } - - let mut specificity = Default::default(); - let mut flags = Default::default(); - for simple_selector in iter { - component_specificity(&simple_selector, &mut specificity, &mut flags); - } - SpecificityAndFlags { - specificity: specificity.into(), - flags, - } -} - -/// Finds the maximum specificity of elements in the list and returns it. -pub(crate) fn selector_list_specificity_and_flags<'a, Impl: SelectorImpl>( - itr: impl Iterator<Item = &'a Selector<Impl>>, -) -> SpecificityAndFlags { - let mut specificity = 0; - let mut flags = SelectorFlags::empty(); - for selector in itr { - specificity = std::cmp::max(specificity, selector.specificity()); - if selector.has_parent_selector() { - flags.insert(SelectorFlags::HAS_PARENT); - } - } - SpecificityAndFlags { specificity, flags } -} - -pub(crate) fn relative_selector_list_specificity_and_flags<Impl: SelectorImpl>( - list: &[RelativeSelector<Impl>], -) -> SpecificityAndFlags { - selector_list_specificity_and_flags(list.iter().map(|rel| &rel.selector)) -} diff --git a/components/selectors/context.rs b/components/selectors/context.rs deleted file mode 100644 index d8c461660d3..00000000000 --- a/components/selectors/context.rs +++ /dev/null @@ -1,363 +0,0 @@ -/* 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/. */ - -use crate::attr::CaseSensitivity; -use crate::bloom::BloomFilter; -use crate::nth_index_cache::{NthIndexCache, NthIndexCacheInner}; -use crate::parser::{Selector, SelectorImpl}; -use crate::tree::{Element, OpaqueElement}; - -/// What kind of selector matching mode we should use. -/// -/// There are two modes of selector matching. The difference is only noticeable -/// in presence of pseudo-elements. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum MatchingMode { - /// Don't ignore any pseudo-element selectors. - Normal, - - /// Ignores any stateless pseudo-element selectors in the rightmost sequence - /// of simple selectors. - /// - /// This is useful, for example, to match against ::before when you aren't a - /// pseudo-element yourself. - /// - /// For example, in presence of `::before:hover`, it would never match, but - /// `::before` would be ignored as in "matching". - /// - /// It's required for all the selectors you match using this mode to have a - /// pseudo-element. - ForStatelessPseudoElement, -} - -/// The mode to use when matching unvisited and visited links. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum VisitedHandlingMode { - /// All links are matched as if they are unvisted. - AllLinksUnvisited, - /// All links are matched as if they are visited and unvisited (both :link - /// and :visited match). - /// - /// This is intended to be used from invalidation code, to be conservative - /// about whether we need to restyle a link. - AllLinksVisitedAndUnvisited, - /// A element's "relevant link" is the element being matched if it is a link - /// or the nearest ancestor link. The relevant link is matched as though it - /// is visited, and all other links are matched as if they are unvisited. - RelevantLinkVisited, -} - -impl VisitedHandlingMode { - #[inline] - pub fn matches_visited(&self) -> bool { - matches!( - *self, - VisitedHandlingMode::RelevantLinkVisited | - VisitedHandlingMode::AllLinksVisitedAndUnvisited - ) - } - - #[inline] - pub fn matches_unvisited(&self) -> bool { - matches!( - *self, - VisitedHandlingMode::AllLinksUnvisited | - VisitedHandlingMode::AllLinksVisitedAndUnvisited - ) - } -} - -/// Whether we need to set selector invalidation flags on elements for this -/// match request. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum NeedsSelectorFlags { - No, - Yes, -} - -/// Which quirks mode is this document in. -/// -/// See: https://quirks.spec.whatwg.org/ -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum QuirksMode { - /// Quirks mode. - Quirks, - /// Limited quirks mode. - LimitedQuirks, - /// No quirks mode. - NoQuirks, -} - -impl QuirksMode { - #[inline] - pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity { - match self { - QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, - QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, - } - } -} - -/// Whether or not this matching considered relative selector. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum RelativeSelectorMatchingState { - /// Was not considered for any relative selector. - None, - /// Relative selector was considered for a match, but the element to be - /// under matching would not anchor the relative selector. i.e. The - /// relative selector was not part of the first compound selector (in match - /// order). - Considered, - /// Same as above, but the relative selector was part of the first compound - /// selector (in match order). - ConsideredAnchor, -} - -/// Data associated with the matching process for a element. This context is -/// used across many selectors for an element, so it's not appropriate for -/// transient data that applies to only a single selector. -pub struct MatchingContext<'a, Impl> -where - Impl: SelectorImpl, -{ - /// Input with the matching mode we should use when matching selectors. - matching_mode: MatchingMode, - /// Input with the bloom filter used to fast-reject selectors. - pub bloom_filter: Option<&'a BloomFilter>, - /// A cache to speed up nth-index-like selectors. - pub nth_index_cache: &'a mut NthIndexCache, - /// The element which is going to match :scope pseudo-class. It can be - /// either one :scope element, or the scoping element. - /// - /// Note that, although in theory there can be multiple :scope elements, - /// in current specs, at most one is specified, and when there is one, - /// scoping element is not relevant anymore, so we use a single field for - /// them. - /// - /// When this is None, :scope will match the root element. - /// - /// See https://drafts.csswg.org/selectors-4/#scope-pseudo - pub scope_element: Option<OpaqueElement>, - - /// The current shadow host we're collecting :host rules for. - pub current_host: Option<OpaqueElement>, - - /// Controls how matching for links is handled. - visited_handling: VisitedHandlingMode, - - /// The current nesting level of selectors that we're matching. - nesting_level: usize, - - /// Whether we're inside a negation or not. - in_negation: bool, - - /// An optional hook function for checking whether a pseudo-element - /// should match when matching_mode is ForStatelessPseudoElement. - pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>, - - /// Extra implementation-dependent matching data. - pub extra_data: Impl::ExtraMatchingData<'a>, - - /// The current element we're anchoring on for evaluating the relative selector. - current_relative_selector_anchor: Option<OpaqueElement>, - pub considered_relative_selector: RelativeSelectorMatchingState, - - quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, - classes_and_ids_case_sensitivity: CaseSensitivity, - _impl: ::std::marker::PhantomData<Impl>, -} - -impl<'a, Impl> MatchingContext<'a, Impl> -where - Impl: SelectorImpl, -{ - /// Constructs a new `MatchingContext`. - pub fn new( - matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - nth_index_cache: &'a mut NthIndexCache, - quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, - ) -> Self { - Self::new_for_visited( - matching_mode, - bloom_filter, - nth_index_cache, - VisitedHandlingMode::AllLinksUnvisited, - quirks_mode, - needs_selector_flags, - ) - } - - /// Constructs a new `MatchingContext` for use in visited matching. - pub fn new_for_visited( - matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - nth_index_cache: &'a mut NthIndexCache, - visited_handling: VisitedHandlingMode, - quirks_mode: QuirksMode, - needs_selector_flags: NeedsSelectorFlags, - ) -> Self { - Self { - matching_mode, - bloom_filter, - visited_handling, - nth_index_cache, - quirks_mode, - classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), - needs_selector_flags, - scope_element: None, - current_host: None, - nesting_level: 0, - in_negation: false, - pseudo_element_matching_fn: None, - extra_data: Default::default(), - current_relative_selector_anchor: None, - considered_relative_selector: RelativeSelectorMatchingState::None, - _impl: ::std::marker::PhantomData, - } - } - - // Grab a reference to the appropriate cache. - #[inline] - pub fn nth_index_cache( - &mut self, - is_of_type: bool, - is_from_end: bool, - selectors: &[Selector<Impl>], - ) -> &mut NthIndexCacheInner { - self.nth_index_cache.get(is_of_type, is_from_end, selectors) - } - - /// Whether we're matching a nested selector. - #[inline] - pub fn is_nested(&self) -> bool { - self.nesting_level != 0 - } - - /// Whether we're matching inside a :not(..) selector. - #[inline] - pub fn in_negation(&self) -> bool { - self.in_negation - } - - /// The quirks mode of the document. - #[inline] - pub fn quirks_mode(&self) -> QuirksMode { - self.quirks_mode - } - - /// The matching-mode for this selector-matching operation. - #[inline] - pub fn matching_mode(&self) -> MatchingMode { - self.matching_mode - } - - /// Whether we need to set selector flags. - #[inline] - pub fn needs_selector_flags(&self) -> bool { - self.needs_selector_flags == NeedsSelectorFlags::Yes - } - - /// The case-sensitivity for class and ID selectors - #[inline] - pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { - self.classes_and_ids_case_sensitivity - } - - /// Runs F with a deeper nesting level. - #[inline] - pub fn nest<F, R>(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - self.nesting_level += 1; - let result = f(self); - self.nesting_level -= 1; - result - } - - /// Runs F with a deeper nesting level, and marking ourselves in a negation, - /// for a :not(..) selector, for example. - #[inline] - pub fn nest_for_negation<F, R>(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - let old_in_negation = self.in_negation; - self.in_negation = true; - let result = self.nest(f); - self.in_negation = old_in_negation; - result - } - - #[inline] - pub fn visited_handling(&self) -> VisitedHandlingMode { - self.visited_handling - } - - /// Runs F with a different VisitedHandlingMode. - #[inline] - pub fn with_visited_handling_mode<F, R>( - &mut self, - handling_mode: VisitedHandlingMode, - f: F, - ) -> R - where - F: FnOnce(&mut Self) -> R, - { - let original_handling_mode = self.visited_handling; - self.visited_handling = handling_mode; - let result = f(self); - self.visited_handling = original_handling_mode; - result - } - - /// Runs F with a given shadow host which is the root of the tree whose - /// rules we're matching. - #[inline] - pub fn with_shadow_host<F, E, R>(&mut self, host: Option<E>, f: F) -> R - where - E: Element, - F: FnOnce(&mut Self) -> R, - { - let original_host = self.current_host.take(); - self.current_host = host.map(|h| h.opaque()); - let result = f(self); - self.current_host = original_host; - result - } - - /// Returns the current shadow host whose shadow root we're matching rules - /// against. - #[inline] - pub fn shadow_host(&self) -> Option<OpaqueElement> { - self.current_host - } - - /// Runs F with a deeper nesting level, with the given element as the anchor, - /// for a :has(...) selector, for example. - #[inline] - pub fn nest_for_relative_selector<F, R>(&mut self, anchor: OpaqueElement, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - debug_assert!( - self.current_relative_selector_anchor.is_none(), - "Nesting should've been rejected at parse time" - ); - self.current_relative_selector_anchor = Some(anchor); - self.considered_relative_selector = RelativeSelectorMatchingState::Considered; - let result = self.nest(f); - self.current_relative_selector_anchor = None; - result - } - - /// Returns the current anchor element to evaluate the relative selector against. - #[inline] - pub fn relative_selector_anchor(&self) -> Option<OpaqueElement> { - self.current_relative_selector_anchor - } -} diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs deleted file mode 100644 index d71d3c84876..00000000000 --- a/components/selectors/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* 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/. */ - -// Make |cargo bench| work. -#![cfg_attr(feature = "bench", feature(test))] - -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate cssparser; -#[macro_use] -extern crate debug_unreachable; -#[macro_use] -extern crate derive_more; -extern crate fxhash; -#[macro_use] -extern crate log; -extern crate phf; -extern crate precomputed_hash; -extern crate servo_arc; -extern crate smallvec; -extern crate to_shmem; -#[macro_use] -extern crate to_shmem_derive; - -pub mod attr; -pub mod bloom; -mod builder; -pub mod context; -pub mod matching; -mod nth_index_cache; -pub mod parser; -pub mod sink; -mod tree; -pub mod visitor; - -pub use crate::nth_index_cache::NthIndexCache; -pub use crate::parser::{Parser, SelectorImpl, SelectorList}; -pub use crate::tree::{Element, OpaqueElement}; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs deleted file mode 100644 index 83bc878797a..00000000000 --- a/components/selectors/matching.rs +++ /dev/null @@ -1,1133 +0,0 @@ -/* 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/. */ - -use crate::attr::{ - AttrSelectorOperation, CaseSensitivity, NamespaceConstraint, ParsedAttrSelectorOperation, - ParsedCaseSensitivity, -}; -use crate::bloom::{BloomFilter, BLOOM_HASH_MASK}; -use crate::parser::{ - AncestorHashes, Combinator, Component, LocalName, NthSelectorData, RelativeSelectorMatchHint, -}; -use crate::parser::{ - NonTSPseudoClass, RelativeSelector, Selector, SelectorImpl, SelectorIter, SelectorList, -}; -use crate::tree::Element; -use bitflags::bitflags; -use smallvec::SmallVec; -use std::borrow::Borrow; -use std::iter; - -pub use crate::context::*; - -// The bloom filter for descendant CSS selectors will have a <1% false -// positive rate until it has this many selectors in it, then it will -// rapidly increase. -pub static RECOMMENDED_SELECTOR_BLOOM_FILTER_SIZE: usize = 4096; - -bitflags! { - /// Set of flags that are set on either the element or its parent (depending - /// on the flag) if the element could potentially match a selector. - pub struct ElementSelectorFlags: usize { - /// When a child is added or removed from the parent, all the children - /// must be restyled, because they may match :nth-last-child, - /// :last-of-type, :nth-last-of-type, or :only-of-type. - const HAS_SLOW_SELECTOR = 1 << 0; - - /// When a child is added or removed from the parent, any later - /// children must be restyled, because they may match :nth-child, - /// :first-of-type, or :nth-of-type. - const HAS_SLOW_SELECTOR_LATER_SIBLINGS = 1 << 1; - - /// When a DOM mutation occurs on a child that might be matched by - /// :nth-last-child(.. of <selector list>), earlier children must be - /// restyled, and HAS_SLOW_SELECTOR will be set (which normally - /// indicates that all children will be restyled). - /// - /// Similarly, when a DOM mutation occurs on a child that might be - /// matched by :nth-child(.. of <selector list>), later children must be - /// restyled, and HAS_SLOW_SELECTOR_LATER_SIBLINGS will be set. - const HAS_SLOW_SELECTOR_NTH_OF = 1 << 2; - - /// When a child is added or removed from the parent, the first and - /// last children must be restyled, because they may match :first-child, - /// :last-child, or :only-child. - const HAS_EDGE_CHILD_SELECTOR = 1 << 3; - - /// The element has an empty selector, so when a child is appended we - /// might need to restyle the parent completely. - const HAS_EMPTY_SELECTOR = 1 << 4; - } -} - -impl ElementSelectorFlags { - /// Returns the subset of flags that apply to the element. - pub fn for_self(self) -> ElementSelectorFlags { - self & ElementSelectorFlags::HAS_EMPTY_SELECTOR - } - - /// Returns the subset of flags that apply to the parent. - pub fn for_parent(self) -> ElementSelectorFlags { - self & (ElementSelectorFlags::HAS_SLOW_SELECTOR | - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS | - ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF | - ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) - } -} - -/// Holds per-compound-selector data. -struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> { - shared: &'a mut MatchingContext<'b, Impl>, - quirks_data: Option<(Rightmost, SelectorIter<'a, Impl>)>, -} - -#[inline(always)] -pub fn matches_selector_list<E>( - selector_list: &SelectorList<E::Impl>, - element: &E, - context: &mut MatchingContext<E::Impl>, -) -> bool -where - E: Element, -{ - // This is pretty much any(..) but manually inlined because the compiler - // refuses to do so from querySelector / querySelectorAll. - for selector in &selector_list.0 { - let matches = matches_selector(selector, 0, None, element, context); - if matches { - return true; - } - } - - false -} - -#[inline(always)] -fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { - // Check the first three hashes. Note that we can check for zero before - // masking off the high bits, since if any of the first three hashes is - // zero the fourth will be as well. We also take care to avoid the - // special-case complexity of the fourth hash until we actually reach it, - // because we usually don't. - // - // To be clear: this is all extremely hot. - for i in 0..3 { - let packed = hashes.packed_hashes[i]; - if packed == 0 { - // No more hashes left - unable to fast-reject. - return true; - } - - if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) { - // Hooray! We fast-rejected on this hash. - return false; - } - } - - // Now do the slighty-more-complex work of synthesizing the fourth hash, - // and check it against the filter if it exists. - let fourth = hashes.fourth_hash(); - fourth == 0 || bf.might_contain_hash(fourth) -} - -/// A result of selector matching, includes 3 failure types, -/// -/// NotMatchedAndRestartFromClosestLaterSibling -/// NotMatchedAndRestartFromClosestDescendant -/// NotMatchedGlobally -/// -/// When NotMatchedGlobally appears, stop selector matching completely since -/// the succeeding selectors never matches. -/// It is raised when -/// Child combinator cannot find the candidate element. -/// Descendant combinator cannot find the candidate element. -/// -/// When NotMatchedAndRestartFromClosestDescendant appears, the selector -/// matching does backtracking and restarts from the closest Descendant -/// combinator. -/// It is raised when -/// NextSibling combinator cannot find the candidate element. -/// LaterSibling combinator cannot find the candidate element. -/// Child combinator doesn't match on the found element. -/// -/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector -/// matching does backtracking and restarts from the closest LaterSibling -/// combinator. -/// It is raised when -/// NextSibling combinator doesn't match on the found element. -/// -/// For example, when the selector "d1 d2 a" is provided and we cannot *find* -/// an appropriate ancestor element for "d1", this selector matching raises -/// NotMatchedGlobally since even if "d2" is moved to more upper element, the -/// candidates for "d1" becomes less than before and d1 . -/// -/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is -/// provided and we cannot *find* an appropriate brother element for b1, -/// the selector matching raises NotMatchedAndRestartFromClosestDescendant. -/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1". -/// -/// The additional example is child and sibling. When the selector -/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on -/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling. -/// However since the selector "c1" raises -/// NotMatchedAndRestartFromClosestDescendant. So the selector -/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1". -#[derive(Clone, Copy, Eq, PartialEq)] -enum SelectorMatchingResult { - Matched, - NotMatchedAndRestartFromClosestLaterSibling, - NotMatchedAndRestartFromClosestDescendant, - NotMatchedGlobally, -} - -/// Matches a selector, fast-rejecting against a bloom filter. -/// -/// We accept an offset to allow consumers to represent and match against -/// partial selectors (indexed from the right). We use this API design, rather -/// than having the callers pass a SelectorIter, because creating a SelectorIter -/// requires dereferencing the selector to get the length, which adds an -/// unncessary cache miss for cases when we can fast-reject with AncestorHashes -/// (which the caller can store inline with the selector pointer). -#[inline(always)] -pub fn matches_selector<E>( - selector: &Selector<E::Impl>, - offset: usize, - hashes: Option<&AncestorHashes>, - element: &E, - context: &mut MatchingContext<E::Impl>, -) -> bool -where - E: Element, -{ - // Use the bloom filter to fast-reject. - if let Some(hashes) = hashes { - if let Some(filter) = context.bloom_filter { - if !may_match(hashes, filter) { - return false; - } - } - } - - matches_complex_selector(selector.iter_from(offset), element, context) -} - -/// Whether a compound selector matched, and whether it was the rightmost -/// selector inside the complex selector. -pub enum CompoundSelectorMatchingResult { - /// The selector was fully matched. - FullyMatched, - /// The compound selector matched, and the next combinator offset is - /// `next_combinator_offset`. - Matched { next_combinator_offset: usize }, - /// The selector didn't match. - NotMatched, -} - -/// Matches a compound selector belonging to `selector`, starting at offset -/// `from_offset`, matching left to right. -/// -/// Requires that `from_offset` points to a `Combinator`. -/// -/// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the -/// complex selector, but it happens to be the case we don't need it. -pub fn matches_compound_selector_from<E>( - selector: &Selector<E::Impl>, - mut from_offset: usize, - context: &mut MatchingContext<E::Impl>, - element: &E, -) -> CompoundSelectorMatchingResult -where - E: Element, -{ - if cfg!(debug_assertions) && from_offset != 0 { - selector.combinator_at_parse_order(from_offset - 1); // This asserts. - } - - let mut local_context = LocalMatchingContext { - shared: context, - quirks_data: None, - }; - - // Find the end of the selector or the next combinator, then match - // backwards, so that we match in the same order as - // matches_complex_selector, which is usually faster. - let start_offset = from_offset; - for component in selector.iter_raw_parse_order_from(from_offset) { - if matches!(*component, Component::Combinator(..)) { - debug_assert_ne!(from_offset, 0, "Selector started with a combinator?"); - break; - } - - from_offset += 1; - } - - debug_assert!(from_offset >= 1); - debug_assert!(from_offset <= selector.len()); - - let iter = selector.iter_from(selector.len() - from_offset); - debug_assert!( - iter.clone().next().is_some() || - (from_offset != selector.len() && - matches!( - selector.combinator_at_parse_order(from_offset), - Combinator::SlotAssignment | Combinator::PseudoElement - )), - "Got the math wrong: {:?} | {:?} | {} {}", - selector, - selector.iter_raw_match_order().as_slice(), - from_offset, - start_offset - ); - - for component in iter { - if !matches_simple_selector(component, element, &mut local_context) { - return CompoundSelectorMatchingResult::NotMatched; - } - } - - if from_offset != selector.len() { - return CompoundSelectorMatchingResult::Matched { - next_combinator_offset: from_offset, - }; - } - - CompoundSelectorMatchingResult::FullyMatched -} - -/// Matches a complex selector. -#[inline(always)] -pub fn matches_complex_selector<E>( - mut iter: SelectorIter<E::Impl>, - element: &E, - context: &mut MatchingContext<E::Impl>, -) -> bool -where - E: Element, -{ - // If this is the special pseudo-element mode, consume the ::pseudo-element - // before proceeding, since the caller has already handled that part. - if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() { - // Consume the pseudo. - match *iter.next().unwrap() { - Component::PseudoElement(ref pseudo) => { - if let Some(ref f) = context.pseudo_element_matching_fn { - if !f(pseudo) { - return false; - } - } - }, - ref other => { - debug_assert!( - false, - "Used MatchingMode::ForStatelessPseudoElement \ - in a non-pseudo selector {:?}", - other - ); - return false; - }, - } - - if !iter.matches_for_stateless_pseudo_element() { - return false; - } - - // Advance to the non-pseudo-element part of the selector. - let next_sequence = iter.next_sequence().unwrap(); - debug_assert_eq!(next_sequence, Combinator::PseudoElement); - } - - let result = matches_complex_selector_internal(iter, element, context, Rightmost::Yes); - - matches!(result, SelectorMatchingResult::Matched) -} - -/// Matches each selector of a list as a complex selector -#[inline(always)] -pub fn list_matches_complex_selector<E: Element>( - list: &[Selector<E::Impl>], - element: &E, - context: &mut MatchingContext<E::Impl>, -) -> bool { - for selector in list { - if matches_complex_selector(selector.iter(), element, context) { - return true; - } - } - false -} - -/// Matches a relative selector in a list of relative selectors. -fn matches_relative_selectors<E: Element>( - selectors: &[RelativeSelector<E::Impl>], - element: &E, - context: &mut MatchingContext<E::Impl>, -) -> bool { - // If we've considered anchoring `:has()` selector while trying to match this element, - // mark it as such, as it has implications on style sharing (See style sharing - // code for further information). - context.considered_relative_selector = RelativeSelectorMatchingState::ConsideredAnchor; - for RelativeSelector { - match_hint, - selector, - } in selectors.iter() - { - let (traverse_subtree, traverse_siblings, mut next_element) = match match_hint { - RelativeSelectorMatchHint::InChild => (false, true, element.first_element_child()), - RelativeSelectorMatchHint::InSubtree => (true, true, element.first_element_child()), - RelativeSelectorMatchHint::InSibling => (false, true, element.next_sibling_element()), - RelativeSelectorMatchHint::InSiblingSubtree => { - (true, true, element.next_sibling_element()) - }, - RelativeSelectorMatchHint::InNextSibling => { - (false, false, element.next_sibling_element()) - }, - RelativeSelectorMatchHint::InNextSiblingSubtree => { - (true, false, element.next_sibling_element()) - }, - }; - while let Some(el) = next_element { - // TODO(dshin): `:has()` matching can get expensive when determining style changes. - // We'll need caching/filtering here, which is tracked in bug 1822177. - if matches_complex_selector(selector.iter(), &el, context) { - return true; - } - if traverse_subtree && matches_relative_selector_subtree(selector, &el, context) { - return true; - } - if !traverse_siblings { - break; - } - next_element = el.next_sibling_element(); - } - } - - false -} - -fn matches_relative_selector_subtree<E: Element>( - selector: &Selector<E::Impl>, - element: &E, - context: &mut MatchingContext<E::Impl>, -) -> bool { - let mut current = element.first_element_child(); - - while let Some(el) = current { - if matches_complex_selector(selector.iter(), &el, context) { - return true; - } - - if matches_relative_selector_subtree(selector, &el, context) { - return true; - } - - current = el.next_sibling_element(); - } - - false -} - -/// Whether the :hover and :active quirk applies. -/// -/// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk -fn hover_and_active_quirk_applies<Impl: SelectorImpl>( - selector_iter: &SelectorIter<Impl>, - context: &MatchingContext<Impl>, - rightmost: Rightmost, -) -> bool { - if context.quirks_mode() != QuirksMode::Quirks { - return false; - } - - if context.is_nested() { - return false; - } - - // This compound selector had a pseudo-element to the right that we - // intentionally skipped. - if rightmost == Rightmost::Yes && - context.matching_mode() == MatchingMode::ForStatelessPseudoElement - { - return false; - } - - selector_iter.clone().all(|simple| match *simple { - Component::LocalName(_) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::AttributeOther(_) | - Component::ID(_) | - Component::Class(_) | - Component::PseudoElement(_) | - Component::Negation(_) | - Component::Empty | - Component::Nth(_) | - Component::NthOf(_) => false, - Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), - _ => true, - }) -} - -#[derive(Clone, Copy, PartialEq)] -enum Rightmost { - Yes, - No, -} - -#[inline(always)] -fn next_element_for_combinator<E>( - element: &E, - combinator: Combinator, - selector: &SelectorIter<E::Impl>, - context: &MatchingContext<E::Impl>, -) -> Option<E> -where - E: Element, -{ - match combinator { - Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(), - Combinator::Child | Combinator::Descendant => { - match element.parent_element() { - Some(e) => return Some(e), - None => {}, - } - - if !element.parent_node_is_shadow_root() { - return None; - } - - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // For the purpose of Selectors, a shadow host also appears in - // its shadow tree, with the contents of the shadow tree treated - // as its children. (In other words, the shadow host is treated as - // replacing the shadow root node.) - // - // and also: - // - // When considered within its own shadow trees, the shadow host is - // featureless. Only the :host, :host(), and :host-context() - // pseudo-classes are allowed to match it. - // - // Since we know that the parent is a shadow root, we necessarily - // are in a shadow tree of the host, and the next selector will only - // match if the selector is a featureless :host selector. - if !selector.clone().is_featureless_host_selector() { - return None; - } - - element.containing_shadow_host() - }, - Combinator::Part => element.containing_shadow_host(), - Combinator::SlotAssignment => { - debug_assert!(element - .assigned_slot() - .map_or(true, |s| s.is_html_slot_element())); - let scope = context.current_host?; - let mut current_slot = element.assigned_slot()?; - while current_slot.containing_shadow_host().unwrap().opaque() != scope { - current_slot = current_slot.assigned_slot()?; - } - Some(current_slot) - }, - Combinator::PseudoElement => element.pseudo_element_originating_element(), - } -} - -fn matches_complex_selector_internal<E>( - mut selector_iter: SelectorIter<E::Impl>, - element: &E, - context: &mut MatchingContext<E::Impl>, - rightmost: Rightmost, -) -> SelectorMatchingResult -where - E: Element, -{ - debug!( - "Matching complex selector {:?} for {:?}", - selector_iter, element - ); - - let matches_compound_selector = - matches_compound_selector(&mut selector_iter, element, context, rightmost); - - let combinator = selector_iter.next_sequence(); - if combinator.map_or(false, |c| c.is_sibling()) { - if context.needs_selector_flags() { - element.apply_selector_flags(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS); - } - } - - if !matches_compound_selector { - return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; - } - - let combinator = match combinator { - None => return SelectorMatchingResult::Matched, - Some(c) => c, - }; - - let candidate_not_found = match combinator { - Combinator::NextSibling | Combinator::LaterSibling => { - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant - }, - Combinator::Child | - Combinator::Descendant | - Combinator::SlotAssignment | - Combinator::Part | - Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally, - }; - - // Stop matching :visited as soon as we find a link, or a combinator for - // something that isn't an ancestor. - let mut visited_handling = if combinator.is_sibling() { - VisitedHandlingMode::AllLinksUnvisited - } else { - context.visited_handling() - }; - - let mut element = element.clone(); - loop { - if element.is_link() { - visited_handling = VisitedHandlingMode::AllLinksUnvisited; - } - - element = match next_element_for_combinator(&element, combinator, &selector_iter, &context) - { - None => return candidate_not_found, - Some(next_element) => next_element, - }; - - let result = context.with_visited_handling_mode(visited_handling, |context| { - matches_complex_selector_internal( - selector_iter.clone(), - &element, - context, - Rightmost::No, - ) - }); - - match (result, combinator) { - // Return the status immediately. - (SelectorMatchingResult::Matched, _) | - (SelectorMatchingResult::NotMatchedGlobally, _) | - (_, Combinator::NextSibling) => { - return result; - }, - - // Upgrade the failure status to - // NotMatchedAndRestartFromClosestDescendant. - (_, Combinator::PseudoElement) | (_, Combinator::Child) => { - return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant; - }, - - // If the failure status is - // NotMatchedAndRestartFromClosestDescendant and combinator is - // Combinator::LaterSibling, give up this Combinator::LaterSibling - // matching and restart from the closest descendant combinator. - ( - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, - Combinator::LaterSibling, - ) => { - return result; - }, - - // The Combinator::Descendant combinator and the status is - // NotMatchedAndRestartFromClosestLaterSibling or - // NotMatchedAndRestartFromClosestDescendant, or the - // Combinator::LaterSibling combinator and the status is - // NotMatchedAndRestartFromClosestDescendant, we can continue to - // matching on the next candidate element. - _ => {}, - } - } -} - -#[inline] -fn matches_local_name<E>(element: &E, local_name: &LocalName<E::Impl>) -> bool -where - E: Element, -{ - let name = select_name(element, &local_name.name, &local_name.lower_name).borrow(); - element.has_local_name(name) -} - -/// Determines whether the given element matches the given compound selector. -#[inline] -fn matches_compound_selector<E>( - selector_iter: &mut SelectorIter<E::Impl>, - element: &E, - context: &mut MatchingContext<E::Impl>, - rightmost: Rightmost, -) -> bool -where - E: Element, -{ - let quirks_data = if context.quirks_mode() == QuirksMode::Quirks { - Some((rightmost, selector_iter.clone())) - } else { - None - }; - - // Handle some common cases first. - // We may want to get rid of this at some point if we can make the - // generic case fast enough. - let mut selector = selector_iter.next(); - if let Some(&Component::LocalName(ref local_name)) = selector { - if !matches_local_name(element, local_name) { - return false; - } - selector = selector_iter.next(); - } - let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity(); - if let Some(&Component::ID(ref id)) = selector { - if !element.has_id(id, class_and_id_case_sensitivity) { - return false; - } - selector = selector_iter.next(); - } - while let Some(&Component::Class(ref class)) = selector { - if !element.has_class(class, class_and_id_case_sensitivity) { - return false; - } - selector = selector_iter.next(); - } - let selector = match selector { - Some(s) => s, - None => return true, - }; - - let mut local_context = LocalMatchingContext { - shared: context, - quirks_data, - }; - iter::once(selector) - .chain(selector_iter) - .all(|simple| matches_simple_selector(simple, element, &mut local_context)) -} - -/// Determines whether the given element matches the given single selector. -fn matches_simple_selector<E>( - selector: &Component<E::Impl>, - element: &E, - context: &mut LocalMatchingContext<E::Impl>, -) -> bool -where - E: Element, -{ - debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); - - match *selector { - Component::ID(ref id) => { - element.has_id(id, context.shared.classes_and_ids_case_sensitivity()) - }, - Component::Class(ref class) => { - element.has_class(class, context.shared.classes_and_ids_case_sensitivity()) - }, - Component::LocalName(ref local_name) => matches_local_name(element, local_name), - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } => element.has_attr_in_no_namespace(select_name(element, local_name, local_name_lower)), - Component::AttributeInNoNamespace { - ref local_name, - ref value, - operator, - case_sensitivity, - } => { - element.attr_matches( - &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<E::Impl>()), - local_name, - &AttrSelectorOperation::WithValue { - operator, - case_sensitivity: to_unconditional_case_sensitivity(case_sensitivity, element), - value, - }, - ) - }, - Component::AttributeOther(ref attr_sel) => { - let empty_string; - let namespace = match attr_sel.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::<E::Impl>(); - NamespaceConstraint::Specific(&empty_string) - }, - }; - element.attr_matches( - &namespace, - select_name(element, &attr_sel.local_name, &attr_sel.local_name_lower), - &match attr_sel.operation { - ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists, - ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref value, - } => AttrSelectorOperation::WithValue { - operator, - case_sensitivity: to_unconditional_case_sensitivity( - case_sensitivity, - element, - ), - value, - }, - }, - ) - }, - Component::Part(ref parts) => { - let mut hosts = SmallVec::<[E; 4]>::new(); - - let mut host = match element.containing_shadow_host() { - Some(h) => h, - None => return false, - }; - - let current_host = context.shared.current_host; - if current_host != Some(host.opaque()) { - loop { - let outer_host = host.containing_shadow_host(); - if outer_host.as_ref().map(|h| h.opaque()) == current_host { - break; - } - let outer_host = match outer_host { - Some(h) => h, - None => return false, - }; - // TODO(emilio): if worth it, we could early return if - // host doesn't have the exportparts attribute. - hosts.push(host); - host = outer_host; - } - } - - // Translate the part into the right scope. - parts.iter().all(|part| { - let mut part = part.clone(); - for host in hosts.iter().rev() { - part = match host.imported_part(&part) { - Some(p) => p, - None => return false, - }; - } - element.is_part(&part) - }) - }, - Component::Slotted(ref selector) => { - // <slots> are never flattened tree slottables. - !element.is_html_slot_element() && - context - .shared - .nest(|context| matches_complex_selector(selector.iter(), element, context)) - }, - Component::PseudoElement(ref pseudo) => { - element.match_pseudo_element(pseudo, context.shared) - }, - Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true, - Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { - element.has_namespace(&url.borrow()) - }, - Component::ExplicitNoNamespace => { - let ns = crate::parser::namespace_empty_string::<E::Impl>(); - element.has_namespace(&ns.borrow()) - }, - Component::NonTSPseudoClass(ref pc) => { - if let Some((ref rightmost, ref iter)) = context.quirks_data { - if pc.is_active_or_hover() && - !element.is_link() && - hover_and_active_quirk_applies(iter, context.shared, *rightmost) - { - return false; - } - } - element.match_non_ts_pseudo_class(pc, &mut context.shared) - }, - Component::Root => element.is_root(), - Component::Empty => { - if context.shared.needs_selector_flags() { - element.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); - } - element.is_empty() - }, - Component::Host(ref selector) => { - context - .shared - .shadow_host() - .map_or(false, |host| host == element.opaque()) && - selector.as_ref().map_or(true, |selector| { - context - .shared - .nest(|context| matches_complex_selector(selector.iter(), element, context)) - }) - }, - // These should only work at parse time, should be replaced with :is() at CascadeData build - // time. - Component::ParentSelector => false, - Component::Scope => match context.shared.scope_element { - Some(ref scope_element) => element.opaque() == *scope_element, - None => element.is_root(), - }, - Component::Nth(ref nth_data) => { - matches_generic_nth_child(element, context.shared, nth_data, &[]) - }, - Component::NthOf(ref nth_of_data) => context.shared.nest(|context| { - matches_generic_nth_child( - element, - context, - nth_of_data.nth_data(), - nth_of_data.selectors(), - ) - }), - Component::Is(ref list) | Component::Where(ref list) => context - .shared - .nest(|context| list_matches_complex_selector(list, element, context)), - Component::Negation(ref list) => context - .shared - .nest_for_negation(|context| !list_matches_complex_selector(list, element, context)), - Component::Has(ref relative_selectors) => context - .shared - .nest_for_relative_selector(element.opaque(), |context| { - matches_relative_selectors(relative_selectors, element, context) - }), - Component::Combinator(_) => unsafe { - debug_unreachable!("Shouldn't try to selector-match combinators") - }, - Component::RelativeSelectorAnchor => { - let anchor = context.shared.relative_selector_anchor(); - debug_assert!( - anchor.is_some(), - "Relative selector outside of relative selector matching?" - ); - anchor.map_or(false, |a| a == element.opaque()) - }, - } -} - -#[inline(always)] -fn select_name<'a, E: Element, T: PartialEq>( - element: &E, - local_name: &'a T, - local_name_lower: &'a T, -) -> &'a T { - if local_name == local_name_lower || element.is_html_element_in_html_document() { - local_name_lower - } else { - local_name - } -} - -#[inline(always)] -fn to_unconditional_case_sensitivity<'a, E: Element>( - parsed: ParsedCaseSensitivity, - element: &E, -) -> CaseSensitivity { - match parsed { - ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => { - CaseSensitivity::CaseSensitive - }, - ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive, - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => { - if element.is_html_element_in_html_document() { - CaseSensitivity::AsciiCaseInsensitive - } else { - CaseSensitivity::CaseSensitive - } - }, - } -} - -fn matches_generic_nth_child<E>( - element: &E, - context: &mut MatchingContext<E::Impl>, - nth_data: &NthSelectorData, - selectors: &[Selector<E::Impl>], -) -> bool -where - E: Element, -{ - if element.ignores_nth_child_selectors() { - return false; - } - - let NthSelectorData { ty, a, b, .. } = *nth_data; - let is_of_type = ty.is_of_type(); - if ty.is_only() { - debug_assert!( - selectors.is_empty(), - ":only-child and :only-of-type cannot have a selector list!" - ); - return matches_generic_nth_child( - element, - context, - &NthSelectorData::first(is_of_type), - selectors, - ) && matches_generic_nth_child( - element, - context, - &NthSelectorData::last(is_of_type), - selectors, - ); - } - - let is_from_end = ty.is_from_end(); - - // It's useful to know whether this can only select the first/last element - // child for optimization purposes, see the `HAS_EDGE_CHILD_SELECTOR` flag. - let is_edge_child_selector = a == 0 && b == 1 && !is_of_type && selectors.is_empty(); - - if context.needs_selector_flags() { - let mut flags = if is_edge_child_selector { - ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR - } else if is_from_end { - ElementSelectorFlags::HAS_SLOW_SELECTOR - } else { - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS - }; - if !selectors.is_empty() { - flags |= ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF; - } - element.apply_selector_flags(flags); - } - - if !selectors.is_empty() && !list_matches_complex_selector(selectors, element, context) { - return false; - } - - // :first/last-child are rather trivial to match, don't bother with the - // cache. - if is_edge_child_selector { - return if is_from_end { - element.next_sibling_element() - } else { - element.prev_sibling_element() - } - .is_none(); - } - - // Lookup or compute the index. - let index = if let Some(i) = context - .nth_index_cache(is_of_type, is_from_end, selectors) - .lookup(element.opaque()) - { - i - } else { - let i = nth_child_index( - element, - context, - selectors, - is_of_type, - is_from_end, - /* check_cache = */ true, - ); - context - .nth_index_cache(is_of_type, is_from_end, selectors) - .insert(element.opaque(), i); - i - }; - debug_assert_eq!( - index, - nth_child_index( - element, - context, - selectors, - is_of_type, - is_from_end, - /* check_cache = */ false - ), - "invalid cache" - ); - - // Is there a non-negative integer n such that An+B=index? - match index.checked_sub(b) { - None => false, - Some(an) => match an.checked_div(a) { - Some(n) => n >= 0 && a * n == an, - None /* a == 0 */ => an == 0, - }, - } -} - -#[inline] -fn nth_child_index<E>( - element: &E, - context: &mut MatchingContext<E::Impl>, - selectors: &[Selector<E::Impl>], - is_of_type: bool, - is_from_end: bool, - check_cache: bool, -) -> i32 -where - E: Element, -{ - // The traversal mostly processes siblings left to right. So when we walk - // siblings to the right when computing NthLast/NthLastOfType we're unlikely - // to get cache hits along the way. As such, we take the hit of walking the - // siblings to the left checking the cache in the is_from_end case (this - // matches what Gecko does). The indices-from-the-left is handled during the - // regular look further below. - if check_cache && - is_from_end && - !context - .nth_index_cache(is_of_type, is_from_end, selectors) - .is_empty() - { - let mut index: i32 = 1; - let mut curr = element.clone(); - while let Some(e) = curr.prev_sibling_element() { - curr = e; - let matches = if is_of_type { - element.is_same_type(&curr) - } else if !selectors.is_empty() { - list_matches_complex_selector(selectors, &curr, context) - } else { - true - }; - if !matches { - continue; - } - if let Some(i) = context - .nth_index_cache(is_of_type, is_from_end, selectors) - .lookup(curr.opaque()) - { - return i - index; - } - index += 1; - } - } - - let mut index: i32 = 1; - let mut curr = element.clone(); - let next = |e: E| { - if is_from_end { - e.next_sibling_element() - } else { - e.prev_sibling_element() - } - }; - while let Some(e) = next(curr) { - curr = e; - let matches = if is_of_type { - element.is_same_type(&curr) - } else if !selectors.is_empty() { - list_matches_complex_selector(selectors, &curr, context) - } else { - true - }; - if !matches { - continue; - } - // If we're computing indices from the left, check each element in the - // cache. We handle the indices-from-the-right case at the top of this - // function. - if !is_from_end && check_cache { - if let Some(i) = context - .nth_index_cache(is_of_type, is_from_end, selectors) - .lookup(curr.opaque()) - { - return i + index; - } - } - index += 1; - } - - index -} diff --git a/components/selectors/nth_index_cache.rs b/components/selectors/nth_index_cache.rs deleted file mode 100644 index b4b41578d02..00000000000 --- a/components/selectors/nth_index_cache.rs +++ /dev/null @@ -1,102 +0,0 @@ -/* 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/. */ - -use std::hash::Hash; - -use crate::{parser::Selector, tree::OpaqueElement, SelectorImpl}; -use fxhash::FxHashMap; - -/// A cache to speed up matching of nth-index-like selectors. -/// -/// See [1] for some discussion around the design tradeoffs. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1401855#c3 -#[derive(Default)] -pub struct NthIndexCache { - nth: NthIndexCacheInner, - nth_of_selectors: NthIndexOfSelectorsCaches, - nth_last: NthIndexCacheInner, - nth_last_of_selectors: NthIndexOfSelectorsCaches, - nth_of_type: NthIndexCacheInner, - nth_last_of_type: NthIndexCacheInner, -} - -impl NthIndexCache { - /// Gets the appropriate cache for the given parameters. - pub fn get<Impl: SelectorImpl>( - &mut self, - is_of_type: bool, - is_from_end: bool, - selectors: &[Selector<Impl>], - ) -> &mut NthIndexCacheInner { - if is_of_type { - return if is_from_end { - &mut self.nth_last_of_type - } else { - &mut self.nth_of_type - }; - } - if !selectors.is_empty() { - return if is_from_end { - self.nth_last_of_selectors.lookup(selectors) - } else { - self.nth_of_selectors.lookup(selectors) - }; - } - if is_from_end { - &mut self.nth_last - } else { - &mut self.nth - } - } -} - -#[derive(Hash, Eq, PartialEq)] -struct SelectorListCacheKey(usize); - -/// Use the selector list's pointer as the cache key -impl SelectorListCacheKey { - // :nth-child of selectors are reference-counted with `ThinArc`, so we know their pointers are stable. - fn new<Impl: SelectorImpl>(selectors: &[Selector<Impl>]) -> Self { - Self(selectors.as_ptr() as usize) - } -} - -/// Use a different map of cached indices per :nth-child's or :nth-last-child's selector list -#[derive(Default)] -pub struct NthIndexOfSelectorsCaches(FxHashMap<SelectorListCacheKey, NthIndexCacheInner>); - -/// Get or insert a map of cached incides for the selector list of this -/// particular :nth-child or :nth-last-child pseudoclass -impl NthIndexOfSelectorsCaches { - pub fn lookup<Impl: SelectorImpl>( - &mut self, - selectors: &[Selector<Impl>], - ) -> &mut NthIndexCacheInner { - self.0 - .entry(SelectorListCacheKey::new(selectors)) - .or_default() - } -} - -/// The concrete per-pseudo-class cache. -#[derive(Default)] -pub struct NthIndexCacheInner(FxHashMap<OpaqueElement, i32>); - -impl NthIndexCacheInner { - /// Does a lookup for a given element in the cache. - pub fn lookup(&mut self, el: OpaqueElement) -> Option<i32> { - self.0.get(&el).copied() - } - - /// Inserts an entry into the cache. - pub fn insert(&mut self, element: OpaqueElement, index: i32) { - self.0.insert(element, index); - } - - /// Returns whether the cache is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs deleted file mode 100644 index 2c44da8018e..00000000000 --- a/components/selectors/parser.rs +++ /dev/null @@ -1,4140 +0,0 @@ -/* 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/. */ - -use crate::attr::{AttrSelectorOperator, AttrSelectorWithOptionalNamespace}; -use crate::attr::{NamespaceConstraint, ParsedAttrSelectorOperation, ParsedCaseSensitivity}; -use crate::bloom::BLOOM_HASH_MASK; -use crate::builder::{ - relative_selector_list_specificity_and_flags, selector_list_specificity_and_flags, - SelectorBuilder, SelectorFlags, Specificity, SpecificityAndFlags, -}; -use crate::context::QuirksMode; -use crate::sink::Push; -use crate::visitor::SelectorListKind; -pub use crate::visitor::SelectorVisitor; -use bitflags::bitflags; -use cssparser::parse_nth; -use cssparser::{BasicParseError, BasicParseErrorKind, ParseError, ParseErrorKind}; -use cssparser::{CowRcStr, Delimiter, SourceLocation}; -use cssparser::{Parser as CssParser, ToCss, Token}; -use precomputed_hash::PrecomputedHash; -use servo_arc::{HeaderWithLength, ThinArc, UniqueArc}; -use smallvec::SmallVec; -use std::borrow::{Borrow, Cow}; -use std::fmt::{self, Debug}; -use std::iter::Rev; -use std::slice; - -/// A trait that represents a pseudo-element. -pub trait PseudoElement: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl; - - /// Whether the pseudo-element supports a given state selector to the right - /// of it. - fn accepts_state_pseudo_classes(&self) -> bool { - false - } - - /// Whether this pseudo-element is valid after a ::slotted(..) pseudo. - fn valid_after_slotted(&self) -> bool { - false - } -} - -/// A trait that represents a pseudo-class. -pub trait NonTSPseudoClass: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl; - - /// Whether this pseudo-class is :active or :hover. - fn is_active_or_hover(&self) -> bool; - - /// Whether this pseudo-class belongs to: - /// - /// https://drafts.csswg.org/selectors-4/#useraction-pseudos - fn is_user_action_state(&self) -> bool; - - fn visit<V>(&self, _visitor: &mut V) -> bool - where - V: SelectorVisitor<Impl = Self::Impl>, - { - true - } -} - -/// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a -/// Cow::Owned if `s` had to be converted into ASCII lowercase. -fn to_ascii_lowercase(s: &str) -> Cow<str> { - if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { - let mut string = s.to_owned(); - string[first_uppercase..].make_ascii_lowercase(); - string.into() - } else { - s.into() - } -} - -bitflags! { - /// Flags that indicate at which point of parsing a selector are we. - struct SelectorParsingState: u8 { - /// Whether we should avoid adding default namespaces to selectors that - /// aren't type or universal selectors. - const SKIP_DEFAULT_NAMESPACE = 1 << 0; - - /// Whether we've parsed a ::slotted() pseudo-element already. - /// - /// If so, then we can only parse a subset of pseudo-elements, and - /// whatever comes after them if so. - const AFTER_SLOTTED = 1 << 1; - /// Whether we've parsed a ::part() pseudo-element already. - /// - /// If so, then we can only parse a subset of pseudo-elements, and - /// whatever comes after them if so. - const AFTER_PART = 1 << 2; - /// Whether we've parsed a pseudo-element (as in, an - /// `Impl::PseudoElement` thus not accounting for `::slotted` or - /// `::part`) already. - /// - /// If so, then other pseudo-elements and most other selectors are - /// disallowed. - const AFTER_PSEUDO_ELEMENT = 1 << 3; - /// Whether we've parsed a non-stateful pseudo-element (again, as-in - /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are - /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set - /// as well. - const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; - - /// Whether we are after any of the pseudo-like things. - const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits; - - /// Whether we explicitly disallow combinators. - const DISALLOW_COMBINATORS = 1 << 5; - - /// Whether we explicitly disallow pseudo-element-like things. - const DISALLOW_PSEUDOS = 1 << 6; - - /// Whether we explicitly disallow relative selectors (i.e. `:has()`). - const DISALLOW_RELATIVE_SELECTOR = 1 << 7; - } -} - -impl SelectorParsingState { - #[inline] - fn allows_pseudos(self) -> bool { - // NOTE(emilio): We allow pseudos after ::part and such. - !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) - } - - #[inline] - fn allows_slotted(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) - } - - #[inline] - fn allows_part(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) - } - - // TODO(emilio): Maybe some of these should be allowed, but this gets us on - // the safe side for now, matching previous behavior. Gotta be careful with - // the ones like :-moz-any, which allow nested selectors but don't carry the - // state, and so on. - #[inline] - fn allows_custom_functional_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO) - } - - #[inline] - fn allows_non_functional_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) - } - - #[inline] - fn allows_tree_structural_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO) - } - - #[inline] - fn allows_combinators(self) -> bool { - !self.intersects(Self::DISALLOW_COMBINATORS) - } -} - -pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; - -#[derive(Clone, Debug, PartialEq)] -pub enum SelectorParseErrorKind<'i> { - NoQualifiedNameInAttributeSelector(Token<'i>), - EmptySelector, - DanglingCombinator, - NonCompoundSelector, - NonPseudoElementAfterSlotted, - InvalidPseudoElementAfterSlotted, - InvalidPseudoElementInsideWhere, - InvalidState, - UnexpectedTokenInAttributeSelector(Token<'i>), - PseudoElementExpectedColon(Token<'i>), - PseudoElementExpectedIdent(Token<'i>), - NoIdentForPseudo(Token<'i>), - UnsupportedPseudoClassOrElement(CowRcStr<'i>), - UnexpectedIdent(CowRcStr<'i>), - ExpectedNamespace(CowRcStr<'i>), - ExpectedBarInAttr(Token<'i>), - BadValueInAttr(Token<'i>), - InvalidQualNameInAttr(Token<'i>), - ExplicitNamespaceUnexpectedToken(Token<'i>), - ClassNeedsIdent(Token<'i>), -} - -macro_rules! with_all_bounds { - ( - [ $( $InSelector: tt )* ] - [ $( $CommonBounds: tt )* ] - [ $( $FromStr: tt )* ] - ) => { - /// This trait allows to define the parser implementation in regards - /// of pseudo-classes/elements - /// - /// NB: We need Clone so that we can derive(Clone) on struct with that - /// are parameterized on SelectorImpl. See - /// <https://github.com/rust-lang/rust/issues/26925> - pub trait SelectorImpl: Clone + Debug + Sized + 'static { - type ExtraMatchingData<'a>: Sized + Default; - type AttrValue: $($InSelector)*; - type Identifier: $($InSelector)*; - type LocalName: $($InSelector)* + Borrow<Self::BorrowedLocalName>; - type NamespaceUrl: $($CommonBounds)* + Default + Borrow<Self::BorrowedNamespaceUrl>; - type NamespacePrefix: $($InSelector)* + Default; - type BorrowedNamespaceUrl: ?Sized + Eq; - type BorrowedLocalName: ?Sized + Eq; - - /// non tree-structural pseudo-classes - /// (see: https://drafts.csswg.org/selectors/#structural-pseudos) - type NonTSPseudoClass: $($CommonBounds)* + NonTSPseudoClass<Impl = Self>; - - /// pseudo-elements - type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>; - - /// Whether attribute hashes should be collected for filtering - /// purposes. - fn should_collect_attr_hash(_name: &Self::LocalName) -> bool { - false - } - } - } -} - -macro_rules! with_bounds { - ( [ $( $CommonBounds: tt )* ] [ $( $FromStr: tt )* ]) => { - with_all_bounds! { - [$($CommonBounds)* + $($FromStr)* + ToCss] - [$($CommonBounds)*] - [$($FromStr)*] - } - } -} - -with_bounds! { - [Clone + Eq] - [for<'a> From<&'a str>] -} - -pub trait Parser<'i> { - type Impl: SelectorImpl; - type Error: 'i + From<SelectorParseErrorKind<'i>>; - - /// Whether to parse the `::slotted()` pseudo-element. - fn parse_slotted(&self) -> bool { - false - } - - /// Whether to parse the `::part()` pseudo-element. - fn parse_part(&self) -> bool { - false - } - - /// Whether to parse the selector list of nth-child() or nth-last-child(). - fn parse_nth_child_of(&self) -> bool { - false - } - - /// Whether to parse the `:where` pseudo-class. - fn parse_is_and_where(&self) -> bool { - false - } - - /// Whether to parse the :has pseudo-class. - fn parse_has(&self) -> bool { - false - } - - /// Whether to parse the '&' delimiter as a parent selector. - fn parse_parent_selector(&self) -> bool { - false - } - - /// Whether the given function name is an alias for the `:is()` function. - fn is_is_alias(&self, _name: &str) -> bool { - false - } - - /// Whether to parse the `:host` pseudo-class. - fn parse_host(&self) -> bool { - false - } - - /// Whether to allow forgiving selector-list parsing. - fn allow_forgiving_selectors(&self) -> bool { - true - } - - /// This function can return an "Err" pseudo-element in order to support CSS2.1 - /// pseudo-elements. - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<<Self::Impl as SelectorImpl>::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<<Self::Impl as SelectorImpl>::PseudoElement, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> { - None - } - - fn namespace_for_prefix( - &self, - _prefix: &<Self::Impl as SelectorImpl>::NamespacePrefix, - ) -> Option<<Self::Impl as SelectorImpl>::NamespaceUrl> { - None - } -} - -#[derive(Clone, Debug, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct SelectorList<Impl: SelectorImpl>( - #[shmem(field_bound)] pub SmallVec<[Selector<Impl>; 1]>, -); - -/// Whether or not we're using forgiving parsing mode -enum ForgivingParsing { - /// Discard the entire selector list upon encountering any invalid selector. - /// This is the default behavior for almost all of CSS. - No, - /// Ignore invalid selectors, potentially creating an empty selector list. - /// - /// This is the error recovery mode of :is() and :where() - Yes, -} - -/// Flag indicating if we're parsing relative selectors. -#[derive(Copy, Clone, PartialEq)] -enum ParseRelative { - /// Expect selectors to start with a combinator, assuming descendant combinator if not present. - Yes, - /// Treat as parse error if any selector begins with a combinator. - No, -} - -impl<Impl: SelectorImpl> SelectorList<Impl> { - /// Returns a selector list with a single `&` - pub fn ampersand() -> Self { - Self(smallvec::smallvec![Selector::ampersand()]) - } - - /// Parse a comma-separated list of Selectors. - /// <https://drafts.csswg.org/selectors/#grouping> - /// - /// Return the Selectors or Err if there is an invalid selector. - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result<Self, ParseError<'i, P::Error>> - where - P: Parser<'i, Impl = Impl>, - { - Self::parse_with_state( - parser, - input, - SelectorParsingState::empty(), - ForgivingParsing::No, - ParseRelative::No, - ) - } - - #[inline] - fn parse_with_state<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - recovery: ForgivingParsing, - parse_relative: ParseRelative, - ) -> Result<Self, ParseError<'i, P::Error>> - where - P: Parser<'i, Impl = Impl>, - { - let mut values = SmallVec::new(); - loop { - let selector = input.parse_until_before(Delimiter::Comma, |i| { - parse_selector(parser, i, state, parse_relative) - }); - - let was_ok = selector.is_ok(); - match selector { - Ok(selector) => values.push(selector), - Err(err) => match recovery { - ForgivingParsing::No => return Err(err), - ForgivingParsing::Yes => { - if !parser.allow_forgiving_selectors() { - return Err(err); - } - }, - }, - } - - loop { - match input.next() { - Err(_) => return Ok(SelectorList(values)), - Ok(&Token::Comma) => break, - Ok(_) => { - debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); - }, - } - } - } - } - - /// Creates a SelectorList from a Vec of selectors. Used in tests. - #[allow(dead_code)] - pub(crate) fn from_vec(v: Vec<Selector<Impl>>) -> Self { - SelectorList(SmallVec::from_vec(v)) - } -} - -/// Parses one compound selector suitable for nested stuff like :-moz-any, etc. -fn parse_inner_compound_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result<Selector<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - parse_selector( - parser, - input, - state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS, - ParseRelative::No, - ) -} - -/// Ancestor hashes for the bloom filter. We precompute these and store them -/// inline with selectors to optimize cache performance during matching. -/// This matters a lot. -/// -/// We use 4 hashes, which is copied from Gecko, who copied it from WebKit. -/// Note that increasing the number of hashes here will adversely affect the -/// cache hit when fast-rejecting long lists of Rules with inline hashes. -/// -/// Because the bloom filter only uses the bottom 24 bits of the hash, we pack -/// the fourth hash into the upper bits of the first three hashes in order to -/// shrink Rule (whose size matters a lot). This scheme minimizes the runtime -/// overhead of the packing for the first three hashes (we just need to mask -/// off the upper bits) at the expense of making the fourth somewhat more -/// complicated to assemble, because we often bail out before checking all the -/// hashes. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AncestorHashes { - pub packed_hashes: [u32; 3], -} - -fn collect_ancestor_hashes<Impl: SelectorImpl>( - iter: SelectorIter<Impl>, - quirks_mode: QuirksMode, - hashes: &mut [u32; 4], - len: &mut usize, -) -> bool -where - Impl::Identifier: PrecomputedHash, - Impl::LocalName: PrecomputedHash, - Impl::NamespaceUrl: PrecomputedHash, -{ - for component in AncestorIter::new(iter) { - let hash = match *component { - Component::LocalName(LocalName { - ref name, - ref lower_name, - }) => { - // Only insert the local-name into the filter if it's all - // lowercase. Otherwise we would need to test both hashes, and - // our data structures aren't really set up for that. - if name != lower_name { - continue; - } - name.precomputed_hash() - }, - Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => { - url.precomputed_hash() - }, - // In quirks mode, class and id selectors should match - // case-insensitively, so just avoid inserting them into the filter. - Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(), - Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => { - class.precomputed_hash() - }, - Component::AttributeInNoNamespace { ref local_name, .. } - if Impl::should_collect_attr_hash(local_name) => - { - // AttributeInNoNamespace is only used when local_name == - // local_name_lower. - local_name.precomputed_hash() - }, - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - .. - } => { - // Only insert the local-name into the filter if it's all - // lowercase. Otherwise we would need to test both hashes, and - // our data structures aren't really set up for that. - if local_name != local_name_lower || !Impl::should_collect_attr_hash(local_name) { - continue; - } - local_name.precomputed_hash() - }, - Component::AttributeOther(ref selector) => { - if selector.local_name != selector.local_name_lower || - !Impl::should_collect_attr_hash(&selector.local_name) - { - continue; - } - selector.local_name.precomputed_hash() - }, - Component::Is(ref list) | Component::Where(ref list) => { - // :where and :is OR their selectors, so we can't put any hash - // in the filter if there's more than one selector, as that'd - // exclude elements that may match one of the other selectors. - if list.len() == 1 && - !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) - { - return false; - } - continue; - }, - _ => continue, - }; - - hashes[*len] = hash & BLOOM_HASH_MASK; - *len += 1; - if *len == hashes.len() { - return false; - } - } - true -} - -impl AncestorHashes { - pub fn new<Impl: SelectorImpl>(selector: &Selector<Impl>, quirks_mode: QuirksMode) -> Self - where - Impl::Identifier: PrecomputedHash, - Impl::LocalName: PrecomputedHash, - Impl::NamespaceUrl: PrecomputedHash, - { - // Compute ancestor hashes for the bloom filter. - let mut hashes = [0u32; 4]; - let mut len = 0; - collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len); - debug_assert!(len <= 4); - - // Now, pack the fourth hash (if it exists) into the upper byte of each of - // the other three hashes. - if len == 4 { - let fourth = hashes[3]; - hashes[0] |= (fourth & 0x000000ff) << 24; - hashes[1] |= (fourth & 0x0000ff00) << 16; - hashes[2] |= (fourth & 0x00ff0000) << 8; - } - - AncestorHashes { - packed_hashes: [hashes[0], hashes[1], hashes[2]], - } - } - - /// Returns the fourth hash, reassembled from parts. - pub fn fourth_hash(&self) -> u32 { - ((self.packed_hashes[0] & 0xff000000) >> 24) | - ((self.packed_hashes[1] & 0xff000000) >> 16) | - ((self.packed_hashes[2] & 0xff000000) >> 8) - } -} - -#[inline] -pub fn namespace_empty_string<Impl: SelectorImpl>() -> Impl::NamespaceUrl { - // Rust type’s default, not default namespace - Impl::NamespaceUrl::default() -} - -/// A Selector stores a sequence of simple selectors and combinators. The -/// iterator classes allow callers to iterate at either the raw sequence level or -/// at the level of sequences of simple selectors separated by combinators. Most -/// callers want the higher-level iterator. -/// -/// We store compound selectors internally right-to-left (in matching order). -/// Additionally, we invert the order of top-level compound selectors so that -/// each one matches left-to-right. This is because matching namespace, local name, -/// id, and class are all relatively cheap, whereas matching pseudo-classes might -/// be expensive (depending on the pseudo-class). Since authors tend to put the -/// pseudo-classes on the right, it's faster to start matching on the left. -/// -/// This reordering doesn't change the semantics of selector matching, and we -/// handle it in to_css to make it invisible to serialization. -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct Selector<Impl: SelectorImpl>( - #[shmem(field_bound)] ThinArc<SpecificityAndFlags, Component<Impl>>, -); - -impl<Impl: SelectorImpl> Selector<Impl> { - /// See Arc::mark_as_intentionally_leaked - pub fn mark_as_intentionally_leaked(&self) { - self.0.with_arc(|a| a.mark_as_intentionally_leaked()) - } - - fn ampersand() -> Self { - Self(ThinArc::from_header_and_iter( - SpecificityAndFlags { - specificity: 0, - flags: SelectorFlags::HAS_PARENT, - }, - std::iter::once(Component::ParentSelector), - )) - } - - #[inline] - pub fn specificity(&self) -> u32 { - self.0.header.header.specificity() - } - - #[inline] - fn flags(&self) -> SelectorFlags { - self.0.header.header.flags - } - - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.0.header.header.has_pseudo_element() - } - - #[inline] - pub fn has_parent_selector(&self) -> bool { - self.0.header.header.has_parent_selector() - } - - #[inline] - pub fn is_slotted(&self) -> bool { - self.0.header.header.is_slotted() - } - - #[inline] - pub fn is_part(&self) -> bool { - self.0.header.header.is_part() - } - - #[inline] - pub fn parts(&self) -> Option<&[Impl::Identifier]> { - if !self.is_part() { - return None; - } - - let mut iter = self.iter(); - if self.has_pseudo_element() { - // Skip the pseudo-element. - for _ in &mut iter {} - - let combinator = iter.next_sequence()?; - debug_assert_eq!(combinator, Combinator::PseudoElement); - } - - for component in iter { - if let Component::Part(ref part) = *component { - return Some(part); - } - } - - debug_assert!(false, "is_part() lied somehow?"); - None - } - - #[inline] - pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { - if !self.has_pseudo_element() { - return None; - } - - for component in self.iter() { - if let Component::PseudoElement(ref pseudo) = *component { - return Some(pseudo); - } - } - - debug_assert!(false, "has_pseudo_element lied!"); - None - } - - /// Whether this selector (pseudo-element part excluded) matches every element. - /// - /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs - #[inline] - pub fn is_universal(&self) -> bool { - self.iter_raw_match_order().all(|c| { - matches!( - *c, - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::Combinator(Combinator::PseudoElement) | - Component::PseudoElement(..) - ) - }) - } - - /// Returns an iterator over this selector in matching order (right-to-left). - /// When a combinator is reached, the iterator will return None, and - /// next_sequence() may be called to continue to the next sequence. - #[inline] - pub fn iter(&self) -> SelectorIter<Impl> { - SelectorIter { - iter: self.iter_raw_match_order(), - next_combinator: None, - } - } - - /// Same as `iter()`, but skips `RelativeSelectorAnchor` and its associated combinator. - #[inline] - pub fn iter_skip_relative_selector_anchor(&self) -> SelectorIter<Impl> { - if cfg!(debug_assertions) { - let mut selector_iter = self.iter_raw_parse_order_from(0); - assert!( - matches!( - selector_iter.next().unwrap(), - Component::RelativeSelectorAnchor - ), - "Relative selector does not start with RelativeSelectorAnchor" - ); - assert!( - selector_iter.next().unwrap().is_combinator(), - "Relative combinator does not exist" - ); - } - - SelectorIter { - iter: self.0.slice[..self.len() - 2].iter(), - next_combinator: None, - } - } - - /// Whether this selector is a featureless :host selector, with no - /// combinators to the left, and optionally has a pseudo-element to the - /// right. - #[inline] - pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool { - let mut iter = self.iter(); - if !self.has_pseudo_element() { - return iter.is_featureless_host_selector(); - } - - // Skip the pseudo-element. - for _ in &mut iter {} - - match iter.next_sequence() { - None => return false, - Some(combinator) => { - debug_assert_eq!(combinator, Combinator::PseudoElement); - }, - } - - iter.is_featureless_host_selector() - } - - /// Returns an iterator over this selector in matching order (right-to-left), - /// skipping the rightmost |offset| Components. - #[inline] - pub fn iter_from(&self, offset: usize) -> SelectorIter<Impl> { - let iter = self.0.slice[offset..].iter(); - SelectorIter { - iter, - next_combinator: None, - } - } - - /// Returns the combinator at index `index` (zero-indexed from the right), - /// or panics if the component is not a combinator. - #[inline] - pub fn combinator_at_match_order(&self, index: usize) -> Combinator { - match self.0.slice[index] { - Component::Combinator(c) => c, - ref other => panic!( - "Not a combinator: {:?}, {:?}, index: {}", - other, self, index - ), - } - } - - /// Returns an iterator over the entire sequence of simple selectors and - /// combinators, in matching order (from right to left). - #[inline] - pub fn iter_raw_match_order(&self) -> slice::Iter<Component<Impl>> { - self.0.slice.iter() - } - - /// Returns the combinator at index `index` (zero-indexed from the left), - /// or panics if the component is not a combinator. - #[inline] - pub fn combinator_at_parse_order(&self, index: usize) -> Combinator { - match self.0.slice[self.len() - index - 1] { - Component::Combinator(c) => c, - ref other => panic!( - "Not a combinator: {:?}, {:?}, index: {}", - other, self, index - ), - } - } - - /// Returns an iterator over the sequence of simple selectors and - /// combinators, in parse order (from left to right), starting from - /// `offset`. - #[inline] - pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev<slice::Iter<Component<Impl>>> { - self.0.slice[..self.len() - offset].iter().rev() - } - - /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. - #[allow(dead_code)] - pub(crate) fn from_vec( - vec: Vec<Component<Impl>>, - specificity: u32, - flags: SelectorFlags, - ) -> Self { - let mut builder = SelectorBuilder::default(); - for component in vec.into_iter() { - if let Some(combinator) = component.as_combinator() { - builder.push_combinator(combinator); - } else { - builder.push_simple_selector(component); - } - } - let spec = SpecificityAndFlags { specificity, flags }; - Selector(builder.build_with_specificity_and_flags(spec)) - } - - pub fn replace_parent_selector(&self, parent: &[Selector<Impl>]) -> Self { - // FIXME(emilio): Shouldn't allow replacing if parent has a pseudo-element selector - // or what not. - let flags = self.flags() - SelectorFlags::HAS_PARENT; - let mut specificity = Specificity::from(self.specificity()); - let parent_specificity = - Specificity::from(selector_list_specificity_and_flags(parent.iter()).specificity()); - - // The specificity at this point will be wrong, we replace it by the correct one after the - // fact. - let specificity_and_flags = SpecificityAndFlags { - specificity: self.specificity(), - flags, - }; - - fn replace_parent_on_selector_list<Impl: SelectorImpl>( - orig: &[Selector<Impl>], - parent: &[Selector<Impl>], - specificity: &mut Specificity, - with_specificity: bool, - ) -> Vec<Selector<Impl>> { - let mut any = false; - - let result = orig - .iter() - .map(|s| { - if !s.has_parent_selector() { - return s.clone(); - } - any = true; - s.replace_parent_selector(parent) - }) - .collect(); - - if !any || !with_specificity { - return result; - } - - *specificity += Specificity::from( - selector_list_specificity_and_flags(result.iter()).specificity - - selector_list_specificity_and_flags(orig.iter()).specificity, - ); - result - } - - fn replace_parent_on_relative_selector_list<Impl: SelectorImpl>( - orig: &[RelativeSelector<Impl>], - parent: &[Selector<Impl>], - specificity: &mut Specificity, - ) -> Vec<RelativeSelector<Impl>> { - let mut any = false; - - let result = orig - .iter() - .map(|s| { - if !s.selector.has_parent_selector() { - return s.clone(); - } - any = true; - RelativeSelector { - match_hint: s.match_hint, - selector: s.selector.replace_parent_selector(parent), - } - }) - .collect(); - - if !any { - return result; - } - - *specificity += Specificity::from( - relative_selector_list_specificity_and_flags(&result).specificity - - relative_selector_list_specificity_and_flags(orig).specificity, - ); - result - } - - fn replace_parent_on_selector<Impl: SelectorImpl>( - orig: &Selector<Impl>, - parent: &[Selector<Impl>], - specificity: &mut Specificity, - ) -> Selector<Impl> { - if !orig.has_parent_selector() { - return orig.clone(); - } - let new_selector = orig.replace_parent_selector(parent); - *specificity += Specificity::from(new_selector.specificity() - orig.specificity()); - new_selector - } - - let mut items = if !self.has_parent_selector() { - // Implicit `&` plus descendant combinator. - let iter = self.iter_raw_match_order(); - let len = iter.len() + 2; - specificity += parent_specificity; - let iter = iter - .cloned() - .chain(std::iter::once(Component::Combinator( - Combinator::Descendant, - ))) - .chain(std::iter::once(Component::Is( - parent.to_vec().into_boxed_slice(), - ))); - let header = HeaderWithLength::new(specificity_and_flags, len); - UniqueArc::from_header_and_iter_with_size(header, iter, len) - } else { - let iter = self.iter_raw_match_order().map(|component| { - use self::Component::*; - match *component { - LocalName(..) | - ID(..) | - Class(..) | - AttributeInNoNamespaceExists { .. } | - AttributeInNoNamespace { .. } | - AttributeOther(..) | - ExplicitUniversalType | - ExplicitAnyNamespace | - ExplicitNoNamespace | - DefaultNamespace(..) | - Namespace(..) | - Root | - Empty | - Scope | - Nth(..) | - NonTSPseudoClass(..) | - PseudoElement(..) | - Combinator(..) | - Host(None) | - Part(..) | - RelativeSelectorAnchor => component.clone(), - ParentSelector => { - specificity += parent_specificity; - Is(parent.to_vec().into_boxed_slice()) - }, - Negation(ref selectors) => { - Negation( - replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ true, - ) - .into_boxed_slice(), - ) - }, - Is(ref selectors) => { - Is(replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ true, - ) - .into_boxed_slice()) - }, - Where(ref selectors) => { - Where( - replace_parent_on_selector_list( - selectors, - parent, - &mut specificity, - /* with_specificity = */ false, - ) - .into_boxed_slice(), - ) - }, - Has(ref selectors) => Has(replace_parent_on_relative_selector_list( - selectors, - parent, - &mut specificity, - ) - .into_boxed_slice()), - - Host(Some(ref selector)) => Host(Some(replace_parent_on_selector( - selector, - parent, - &mut specificity, - ))), - NthOf(ref data) => { - let selectors = replace_parent_on_selector_list( - data.selectors(), - parent, - &mut specificity, - /* with_specificity = */ true, - ); - NthOf(NthOfSelectorData::new( - data.nth_data(), - selectors.into_iter(), - )) - }, - Slotted(ref selector) => Slotted(replace_parent_on_selector( - selector, - parent, - &mut specificity, - )), - } - }); - let header = HeaderWithLength::new(specificity_and_flags, iter.len()); - UniqueArc::from_header_and_iter(header, iter) - }; - items.header_mut().specificity = specificity.into(); - Selector(items.shareable_thin()) - } - - /// Returns count of simple selectors and combinators in the Selector. - #[inline] - pub fn len(&self) -> usize { - self.0.slice.len() - } - - /// Returns the address on the heap of the ThinArc for memory reporting. - pub fn thin_arc_heap_ptr(&self) -> *const ::std::os::raw::c_void { - self.0.heap_ptr() - } - - /// Traverse selector components inside `self`. - /// - /// Implementations of this method should call `SelectorVisitor` methods - /// or other impls of `Visit` as appropriate based on the fields of `Self`. - /// - /// A return value of `false` indicates terminating the traversal. - /// It should be propagated with an early return. - /// On the contrary, `true` indicates that all fields of `self` have been traversed: - /// - /// ```rust,ignore - /// if !visitor.visit_simple_selector(&self.some_simple_selector) { - /// return false; - /// } - /// if !self.some_component.visit(visitor) { - /// return false; - /// } - /// true - /// ``` - pub fn visit<V>(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor<Impl = Impl>, - { - let mut current = self.iter(); - let mut combinator = None; - loop { - if !visitor.visit_complex_selector(combinator) { - return false; - } - - for selector in &mut current { - if !selector.visit(visitor) { - return false; - } - } - - combinator = current.next_sequence(); - if combinator.is_none() { - break; - } - } - - true - } -} - -#[derive(Clone)] -pub struct SelectorIter<'a, Impl: 'a + SelectorImpl> { - iter: slice::Iter<'a, Component<Impl>>, - next_combinator: Option<Combinator>, -} - -impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { - /// Prepares this iterator to point to the next sequence to the left, - /// returning the combinator if the sequence was found. - #[inline] - pub fn next_sequence(&mut self) -> Option<Combinator> { - self.next_combinator.take() - } - - /// Whether this selector is a featureless host selector, with no - /// combinators to the left. - #[inline] - pub(crate) fn is_featureless_host_selector(&mut self) -> bool { - self.selector_length() > 0 && - self.all(|component| component.is_host()) && - self.next_sequence().is_none() - } - - #[inline] - pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool { - let first = match self.next() { - Some(c) => c, - // Note that this is the common path that we keep inline: the - // pseudo-element not having anything to its right. - None => return true, - }; - self.matches_for_stateless_pseudo_element_internal(first) - } - - #[inline(never)] - fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<Impl>) -> bool { - if !first.matches_for_stateless_pseudo_element() { - return false; - } - for component in self { - // The only other parser-allowed Components in this sequence are - // state pseudo-classes, or one of the other things that can contain - // them. - if !component.matches_for_stateless_pseudo_element() { - return false; - } - } - true - } - - /// Returns remaining count of the simple selectors and combinators in the Selector. - #[inline] - pub fn selector_length(&self) -> usize { - self.iter.len() - } -} - -impl<'a, Impl: SelectorImpl> Iterator for SelectorIter<'a, Impl> { - type Item = &'a Component<Impl>; - - #[inline] - fn next(&mut self) -> Option<Self::Item> { - debug_assert!( - self.next_combinator.is_none(), - "You should call next_sequence!" - ); - match *self.iter.next()? { - Component::Combinator(c) => { - self.next_combinator = Some(c); - None - }, - ref x => Some(x), - } - } -} - -impl<'a, Impl: SelectorImpl> fmt::Debug for SelectorIter<'a, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let iter = self.iter.clone().rev(); - for component in iter { - component.to_css(f)? - } - Ok(()) - } -} - -/// An iterator over all combinators in a selector. Does not traverse selectors within psuedoclasses. -struct CombinatorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); -impl<'a, Impl: 'a + SelectorImpl> CombinatorIter<'a, Impl> { - fn new(inner: SelectorIter<'a, Impl>) -> Self { - let mut result = CombinatorIter(inner); - result.consume_non_combinators(); - result - } - - fn consume_non_combinators(&mut self) { - while self.0.next().is_some() {} - } -} - -impl<'a, Impl: SelectorImpl> Iterator for CombinatorIter<'a, Impl> { - type Item = Combinator; - fn next(&mut self) -> Option<Self::Item> { - let result = self.0.next_sequence(); - self.consume_non_combinators(); - result - } -} - -/// An iterator over all simple selectors belonging to ancestors. -struct AncestorIter<'a, Impl: 'a + SelectorImpl>(SelectorIter<'a, Impl>); -impl<'a, Impl: 'a + SelectorImpl> AncestorIter<'a, Impl> { - /// Creates an AncestorIter. The passed-in iterator is assumed to point to - /// the beginning of the child sequence, which will be skipped. - fn new(inner: SelectorIter<'a, Impl>) -> Self { - let mut result = AncestorIter(inner); - result.skip_until_ancestor(); - result - } - - /// Skips a sequence of simple selectors and all subsequent sequences until - /// a non-pseudo-element ancestor combinator is reached. - fn skip_until_ancestor(&mut self) { - loop { - while self.0.next().is_some() {} - // If this is ever changed to stop at the "pseudo-element" - // combinator, we will need to fix the way we compute hashes for - // revalidation selectors. - if self.0.next_sequence().map_or(true, |x| { - matches!(x, Combinator::Child | Combinator::Descendant) - }) { - break; - } - } - } -} - -impl<'a, Impl: SelectorImpl> Iterator for AncestorIter<'a, Impl> { - type Item = &'a Component<Impl>; - fn next(&mut self) -> Option<Self::Item> { - // Grab the next simple selector in the sequence if available. - let next = self.0.next(); - if next.is_some() { - return next; - } - - // See if there are more sequences. If so, skip any non-ancestor sequences. - if let Some(combinator) = self.0.next_sequence() { - if !matches!(combinator, Combinator::Child | Combinator::Descendant) { - self.skip_until_ancestor(); - } - } - - self.0.next() - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub enum Combinator { - Child, // > - Descendant, // space - NextSibling, // + - LaterSibling, // ~ - /// A dummy combinator we use to the left of pseudo-elements. - /// - /// It serializes as the empty string, and acts effectively as a child - /// combinator in most cases. If we ever actually start using a child - /// combinator for this, we will need to fix up the way hashes are computed - /// for revalidation selectors. - PseudoElement, - /// Another combinator used for ::slotted(), which represent the jump from - /// a node to its assigned slot. - SlotAssignment, - /// Another combinator used for `::part()`, which represents the jump from - /// the part to the containing shadow host. - Part, -} - -impl Combinator { - /// Returns true if this combinator is a child or descendant combinator. - #[inline] - pub fn is_ancestor(&self) -> bool { - matches!( - *self, - Combinator::Child | - Combinator::Descendant | - Combinator::PseudoElement | - Combinator::SlotAssignment - ) - } - - /// Returns true if this combinator is a pseudo-element combinator. - #[inline] - pub fn is_pseudo_element(&self) -> bool { - matches!(*self, Combinator::PseudoElement) - } - - /// Returns true if this combinator is a next- or later-sibling combinator. - #[inline] - pub fn is_sibling(&self) -> bool { - matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) - } -} - -/// An enum for the different types of :nth- pseudoclasses -#[derive(Copy, Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub enum NthType { - Child, - LastChild, - OnlyChild, - OfType, - LastOfType, - OnlyOfType, -} - -impl NthType { - pub fn is_only(self) -> bool { - self == Self::OnlyChild || self == Self::OnlyOfType - } - - pub fn is_of_type(self) -> bool { - self == Self::OfType || self == Self::LastOfType || self == Self::OnlyOfType - } - - pub fn is_from_end(self) -> bool { - self == Self::LastChild || self == Self::LastOfType - } -} - -/// The properties that comprise an :nth- pseudoclass as of Selectors 3 (e.g., -/// nth-child(An+B)). -/// https://www.w3.org/TR/selectors-3/#nth-child-pseudo -#[derive(Copy, Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct NthSelectorData { - pub ty: NthType, - pub is_function: bool, - pub a: i32, - pub b: i32, -} - -impl NthSelectorData { - /// Returns selector data for :only-{child,of-type} - #[inline] - pub const fn only(of_type: bool) -> Self { - Self { - ty: if of_type { - NthType::OnlyOfType - } else { - NthType::OnlyChild - }, - is_function: false, - a: 0, - b: 1, - } - } - - /// Returns selector data for :first-{child,of-type} - #[inline] - pub const fn first(of_type: bool) -> Self { - Self { - ty: if of_type { - NthType::OfType - } else { - NthType::Child - }, - is_function: false, - a: 0, - b: 1, - } - } - - /// Returns selector data for :last-{child,of-type} - #[inline] - pub const fn last(of_type: bool) -> Self { - Self { - ty: if of_type { - NthType::LastOfType - } else { - NthType::LastChild - }, - is_function: false, - a: 0, - b: 1, - } - } - - /// Writes the beginning of the selector. - #[inline] - fn write_start<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result { - dest.write_str(match self.ty { - NthType::Child if self.is_function => ":nth-child(", - NthType::Child => ":first-child", - NthType::LastChild if self.is_function => ":nth-last-child(", - NthType::LastChild => ":last-child", - NthType::OfType if self.is_function => ":nth-of-type(", - NthType::OfType => ":first-of-type", - NthType::LastOfType if self.is_function => ":nth-last-of-type(", - NthType::LastOfType => ":last-of-type", - NthType::OnlyChild => ":only-child", - NthType::OnlyOfType => ":only-of-type", - }) - } - - /// Serialize <an+b> (part of the CSS Syntax spec, but currently only used here). - /// <https://drafts.csswg.org/css-syntax-3/#serialize-an-anb-value> - #[inline] - fn write_affine<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result { - match (self.a, self.b) { - (0, 0) => dest.write_char('0'), - - (1, 0) => dest.write_char('n'), - (-1, 0) => dest.write_str("-n"), - (_, 0) => write!(dest, "{}n", self.a), - - (0, _) => write!(dest, "{}", self.b), - (1, _) => write!(dest, "n{:+}", self.b), - (-1, _) => write!(dest, "-n{:+}", self.b), - (_, _) => write!(dest, "{}n{:+}", self.a, self.b), - } - } -} - -/// The properties that comprise an :nth- pseudoclass as of Selectors 4 (e.g., -/// nth-child(An+B [of S]?)). -/// https://www.w3.org/TR/selectors-4/#nth-child-pseudo -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct NthOfSelectorData<Impl: SelectorImpl>( - #[shmem(field_bound)] ThinArc<NthSelectorData, Selector<Impl>>, -); - -impl<Impl: SelectorImpl> NthOfSelectorData<Impl> { - /// Returns selector data for :nth-{,last-}{child,of-type}(An+B [of S]) - #[inline] - pub fn new<I>(nth_data: &NthSelectorData, selectors: I) -> Self - where - I: Iterator<Item = Selector<Impl>> + ExactSizeIterator, - { - Self(ThinArc::from_header_and_iter(*nth_data, selectors)) - } - - /// Returns the An+B part of the selector - #[inline] - pub fn nth_data(&self) -> &NthSelectorData { - &self.0.header.header - } - - /// Returns the selector list part of the selector - #[inline] - pub fn selectors(&self) -> &[Selector<Impl>] { - &self.0.slice - } -} - -/// Flag indicating where a given relative selector's match would be contained. -#[derive(Clone, Copy, Eq, PartialEq, ToShmem)] -pub enum RelativeSelectorMatchHint { - /// Within this element's subtree. - InSubtree, - /// Within this element's direct children. - InChild, - /// This element's next sibling. - InNextSibling, - /// Within this element's next sibling's subtree. - InNextSiblingSubtree, - /// Within this element's subsequent siblings. - InSibling, - /// Across this element's subsequent siblings and their subtrees. - InSiblingSubtree, -} - -/// Storage for a relative selector. -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct RelativeSelector<Impl: SelectorImpl> { - /// Match space constraining hint. - pub match_hint: RelativeSelectorMatchHint, - /// The selector. Guaranteed to contain `RelativeSelectorAnchor` and the relative combinator in parse order. - #[shmem(field_bound)] - pub selector: Selector<Impl>, -} - -bitflags! { - /// Composition of combinators in a given selector, not traversing selectors of pseudoclasses. - struct CombinatorComposition: u8 { - const DESCENDANTS = 1 << 0; - const SIBLINGS = 1 << 1; - } -} - -impl CombinatorComposition { - fn for_relative_selector<Impl: SelectorImpl>(inner_selector: &Selector<Impl>) -> Self { - let mut result = CombinatorComposition::empty(); - for combinator in CombinatorIter::new(inner_selector.iter_skip_relative_selector_anchor()) { - match combinator { - Combinator::Descendant | Combinator::Child => { - result.insert(Self::DESCENDANTS); - }, - Combinator::NextSibling | Combinator::LaterSibling => { - result.insert(Self::SIBLINGS); - }, - Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => { - continue - }, - }; - if result.is_all() { - break; - } - } - return result; - } -} - -impl<Impl: SelectorImpl> RelativeSelector<Impl> { - fn from_selector_list(selector_list: SelectorList<Impl>) -> Box<[Self]> { - let vec: Vec<Self> = selector_list - .0 - .into_iter() - .map(|selector| { - // It's more efficient to keep track of all this during the parse time, but that seems like a lot of special - // case handling for what it's worth. - if cfg!(debug_assertions) { - let relative_selector_anchor = selector.iter_raw_parse_order_from(0).next(); - debug_assert!( - relative_selector_anchor.is_some(), - "Relative selector is empty" - ); - debug_assert!( - matches!( - relative_selector_anchor.unwrap(), - Component::RelativeSelectorAnchor - ), - "Relative selector anchor is missing" - ); - } - // Leave a hint for narrowing down the search space when we're matching. - let match_hint = match selector.combinator_at_parse_order(1) { - Combinator::Descendant => RelativeSelectorMatchHint::InSubtree, - Combinator::Child => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() || composition == CombinatorComposition::SIBLINGS - { - RelativeSelectorMatchHint::InChild - } else { - // Technically, for any composition that consists of child combinators only, - // the search space is depth-constrained, but it's probably not worth optimizing for. - RelativeSelectorMatchHint::InSubtree - } - }, - Combinator::NextSibling => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() { - RelativeSelectorMatchHint::InNextSibling - } else if composition == CombinatorComposition::SIBLINGS { - RelativeSelectorMatchHint::InSibling - } else if composition == CombinatorComposition::DESCENDANTS { - // Match won't cross multiple siblings. - RelativeSelectorMatchHint::InNextSiblingSubtree - } else { - RelativeSelectorMatchHint::InSiblingSubtree - } - }, - Combinator::LaterSibling => { - let composition = CombinatorComposition::for_relative_selector(&selector); - if composition.is_empty() || composition == CombinatorComposition::SIBLINGS - { - RelativeSelectorMatchHint::InSibling - } else { - // Even if the match may not cross multiple siblings, we have to look until - // we find a match anyway. - RelativeSelectorMatchHint::InSiblingSubtree - } - }, - Combinator::Part | Combinator::PseudoElement | Combinator::SlotAssignment => { - debug_assert!(false, "Unexpected relative combinator"); - RelativeSelectorMatchHint::InSubtree - }, - }; - RelativeSelector { - match_hint, - selector, - } - }) - .collect(); - vec.into_boxed_slice() - } -} - -/// A CSS simple selector or combinator. We store both in the same enum for -/// optimal packing and cache performance, see [1]. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub enum Component<Impl: SelectorImpl> { - LocalName(LocalName<Impl>), - - ID(#[shmem(field_bound)] Impl::Identifier), - Class(#[shmem(field_bound)] Impl::Identifier), - - AttributeInNoNamespaceExists { - #[shmem(field_bound)] - local_name: Impl::LocalName, - local_name_lower: Impl::LocalName, - }, - // Used only when local_name is already lowercase. - AttributeInNoNamespace { - local_name: Impl::LocalName, - operator: AttrSelectorOperator, - #[shmem(field_bound)] - value: Impl::AttrValue, - case_sensitivity: ParsedCaseSensitivity, - }, - // Use a Box in the less common cases with more data to keep size_of::<Component>() small. - AttributeOther(Box<AttrSelectorWithOptionalNamespace<Impl>>), - - ExplicitUniversalType, - ExplicitAnyNamespace, - - ExplicitNoNamespace, - DefaultNamespace(#[shmem(field_bound)] Impl::NamespaceUrl), - Namespace( - #[shmem(field_bound)] Impl::NamespacePrefix, - #[shmem(field_bound)] Impl::NamespaceUrl, - ), - - /// Pseudo-classes - Negation(Box<[Selector<Impl>]>), - Root, - Empty, - Scope, - ParentSelector, - Nth(NthSelectorData), - NthOf(NthOfSelectorData<Impl>), - NonTSPseudoClass(#[shmem(field_bound)] Impl::NonTSPseudoClass), - /// The ::slotted() pseudo-element: - /// - /// https://drafts.csswg.org/css-scoping/#slotted-pseudo - /// - /// The selector here is a compound selector, that is, no combinators. - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put ::slotted() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 - Slotted(Selector<Impl>), - /// The `::part` pseudo-element. - /// https://drafts.csswg.org/css-shadow-parts/#part - Part(#[shmem(field_bound)] Box<[Impl::Identifier]>), - /// The `:host` pseudo-class: - /// - /// https://drafts.csswg.org/css-scoping/#host-selector - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put :host() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 - Host(Option<Selector<Impl>>), - /// The `:where` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#zero-matches - /// - /// The inner argument is conceptually a SelectorList, but we move the - /// selectors to the heap to keep Component small. - Where(Box<[Selector<Impl>]>), - /// The `:is` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#matches-pseudo - /// - /// Same comment as above re. the argument. - Is(Box<[Selector<Impl>]>), - /// The `:has` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#has-pseudo - /// - /// Same comment as above re. the argument. - Has(Box<[RelativeSelector<Impl>]>), - /// An implementation-dependent pseudo-element selector. - PseudoElement(#[shmem(field_bound)] Impl::PseudoElement), - - Combinator(Combinator), - - /// Used only for relative selectors, which starts with a combinator - /// (With an implied descendant combinator if not specified). - /// - /// https://drafts.csswg.org/selectors-4/#typedef-relative-selector - RelativeSelectorAnchor, -} - -impl<Impl: SelectorImpl> Component<Impl> { - /// Returns true if this is a combinator. - #[inline] - pub fn is_combinator(&self) -> bool { - matches!(*self, Component::Combinator(_)) - } - - /// Returns true if this is a :host() selector. - #[inline] - pub fn is_host(&self) -> bool { - matches!(*self, Component::Host(..)) - } - - /// Returns the value as a combinator if applicable, None otherwise. - pub fn as_combinator(&self) -> Option<Combinator> { - match *self { - Component::Combinator(c) => Some(c), - _ => None, - } - } - - /// Whether this component is valid after a pseudo-element. Only intended - /// for sanity-checking. - pub fn maybe_allowed_after_pseudo_element(&self) -> bool { - match *self { - Component::NonTSPseudoClass(..) => true, - Component::Negation(ref selectors) | - Component::Is(ref selectors) | - Component::Where(ref selectors) => selectors.iter().all(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.maybe_allowed_after_pseudo_element()) - }), - _ => false, - } - } - - /// Whether a given selector should match for stateless pseudo-elements. - /// - /// This is a bit subtle: Only selectors that return true in - /// `maybe_allowed_after_pseudo_element` should end up here, and - /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after - /// all). - fn matches_for_stateless_pseudo_element(&self) -> bool { - debug_assert!( - self.maybe_allowed_after_pseudo_element(), - "Someone messed up pseudo-element parsing: {:?}", - *self - ); - match *self { - Component::Negation(ref selectors) => !selectors.iter().all(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.matches_for_stateless_pseudo_element()) - }), - Component::Is(ref selectors) | Component::Where(ref selectors) => { - selectors.iter().any(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.matches_for_stateless_pseudo_element()) - }) - }, - _ => false, - } - } - - pub fn visit<V>(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor<Impl = Impl>, - { - use self::Component::*; - if !visitor.visit_simple_selector(self) { - return false; - } - - match *self { - Slotted(ref selector) => { - if !selector.visit(visitor) { - return false; - } - }, - Host(Some(ref selector)) => { - if !selector.visit(visitor) { - return false; - } - }, - AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()), - local_name, - local_name_lower, - ) { - return false; - } - }, - AttributeInNoNamespace { ref local_name, .. } => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::<Impl>()), - local_name, - local_name, - ) { - return false; - } - }, - AttributeOther(ref attr_selector) => { - let empty_string; - let namespace = match attr_selector.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::<Impl>(); - NamespaceConstraint::Specific(&empty_string) - }, - }; - if !visitor.visit_attribute_selector( - &namespace, - &attr_selector.local_name, - &attr_selector.local_name_lower, - ) { - return false; - } - }, - - NonTSPseudoClass(ref pseudo_class) => { - if !pseudo_class.visit(visitor) { - return false; - } - }, - Negation(ref list) | Is(ref list) | Where(ref list) => { - let list_kind = SelectorListKind::from_component(self); - debug_assert!(!list_kind.is_empty()); - if !visitor.visit_selector_list(list_kind, &list) { - return false; - } - }, - NthOf(ref nth_of_data) => { - if !visitor.visit_selector_list(SelectorListKind::NTH_OF, nth_of_data.selectors()) { - return false; - } - }, - _ => {}, - } - - true - } -} - -#[derive(Clone, Eq, PartialEq, ToShmem)] -#[shmem(no_bounds)] -pub struct LocalName<Impl: SelectorImpl> { - #[shmem(field_bound)] - pub name: Impl::LocalName, - pub lower_name: Impl::LocalName, -} - -impl<Impl: SelectorImpl> Debug for Selector<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Selector(")?; - self.to_css(f)?; - write!( - f, - ", specificity = {:#x}, flags = {:?})", - self.specificity(), - self.flags() - ) - } -} - -impl<Impl: SelectorImpl> Debug for Component<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} -impl<Impl: SelectorImpl> Debug for AttrSelectorWithOptionalNamespace<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} -impl<Impl: SelectorImpl> Debug for LocalName<Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } -} - -fn serialize_selector_list<'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result -where - Impl: SelectorImpl, - I: Iterator<Item = &'a Selector<Impl>>, - W: fmt::Write, -{ - let mut first = true; - for selector in iter { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; - } - Ok(()) -} - -impl<Impl: SelectorImpl> ToCss for SelectorList<Impl> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_selector_list(self.0.iter(), dest) - } -} - -impl<Impl: SelectorImpl> ToCss for Selector<Impl> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - // Compound selectors invert the order of their contents, so we need to - // undo that during serialization. - // - // This two-iterator strategy involves walking over the selector twice. - // We could do something more clever, but selector serialization probably - // isn't hot enough to justify it, and the stringification likely - // dominates anyway. - // - // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), - // which we need for |split|. So we split by combinators on a match-order - // sequence and then reverse. - - let mut combinators = self - .iter_raw_match_order() - .rev() - .filter_map(|x| x.as_combinator()); - let compound_selectors = self - .iter_raw_match_order() - .as_slice() - .split(|x| x.is_combinator()) - .rev(); - - let mut combinators_exhausted = false; - for compound in compound_selectors { - debug_assert!(!combinators_exhausted); - - // https://drafts.csswg.org/cssom/#serializing-selectors - if compound.is_empty() { - continue; - } - if let Component::RelativeSelectorAnchor = compound.first().unwrap() { - debug_assert!( - compound.len() == 1, - "RelativeLeft should only be a simple selector" - ); - combinators.next().unwrap().to_css_relative(dest)?; - continue; - } - - // 1. If there is only one simple selector in the compound selectors - // which is a universal selector, append the result of - // serializing the universal selector to s. - // - // Check if `!compound.empty()` first--this can happen if we have - // something like `... > ::before`, because we store `>` and `::` - // both as combinators internally. - // - // If we are in this case, after we have serialized the universal - // selector, we skip Step 2 and continue with the algorithm. - let (can_elide_namespace, first_non_namespace) = match compound[0] { - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::Namespace(..) => (false, 1), - Component::DefaultNamespace(..) => (true, 1), - _ => (true, 0), - }; - let mut perform_step_2 = true; - let next_combinator = combinators.next(); - if first_non_namespace == compound.len() - 1 { - match (next_combinator, &compound[first_non_namespace]) { - // We have to be careful here, because if there is a - // pseudo element "combinator" there isn't really just - // the one simple selector. Technically this compound - // selector contains the pseudo element selector as well - // -- Combinator::PseudoElement, just like - // Combinator::SlotAssignment, don't exist in the - // spec. - (Some(Combinator::PseudoElement), _) | - (Some(Combinator::SlotAssignment), _) => (), - (_, &Component::ExplicitUniversalType) => { - // Iterate over everything so we serialize the namespace - // too. - for simple in compound.iter() { - simple.to_css(dest)?; - } - // Skip step 2, which is an "otherwise". - perform_step_2 = false; - }, - _ => (), - } - } - - // 2. Otherwise, for each simple selector in the compound selectors - // that is not a universal selector of which the namespace prefix - // maps to a namespace that is not the default namespace - // serialize the simple selector and append the result to s. - // - // See https://github.com/w3c/csswg-drafts/issues/1606, which is - // proposing to change this to match up with the behavior asserted - // in cssom/serialize-namespaced-type-selectors.html, which the - // following code tries to match. - if perform_step_2 { - for simple in compound.iter() { - if let Component::ExplicitUniversalType = *simple { - // Can't have a namespace followed by a pseudo-element - // selector followed by a universal selector in the same - // compound selector, so we don't have to worry about the - // real namespace being in a different `compound`. - if can_elide_namespace { - continue; - } - } - simple.to_css(dest)?; - } - } - - // 3. If this is not the last part of the chain of the selector - // append a single SPACE (U+0020), followed by the combinator - // ">", "+", "~", ">>", "||", as appropriate, followed by another - // single SPACE (U+0020) if the combinator was not whitespace, to - // s. - match next_combinator { - Some(c) => c.to_css(dest)?, - None => combinators_exhausted = true, - }; - - // 4. If this is the last part of the chain of the selector and - // there is a pseudo-element, append "::" followed by the name of - // the pseudo-element, to s. - // - // (we handle this above) - } - - Ok(()) - } -} - -impl Combinator { - fn to_css_internal<W>(&self, dest: &mut W, prefix_space: bool) -> fmt::Result - where - W: fmt::Write, - { - if matches!( - *self, - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment - ) { - return Ok(()); - } - if prefix_space { - dest.write_char(' ')?; - } - match *self { - Combinator::Child => dest.write_str("> "), - Combinator::Descendant => Ok(()), - Combinator::NextSibling => dest.write_str("+ "), - Combinator::LaterSibling => dest.write_str("~ "), - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => unsafe { - debug_unreachable!("Already handled") - }, - } - } - - fn to_css_relative<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.to_css_internal(dest, false) - } -} - -impl ToCss for Combinator { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.to_css_internal(dest, true) - } -} - -impl<Impl: SelectorImpl> ToCss for Component<Impl> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use self::Component::*; - - match *self { - Combinator(ref c) => c.to_css(dest), - Slotted(ref selector) => { - dest.write_str("::slotted(")?; - selector.to_css(dest)?; - dest.write_char(')') - }, - Part(ref part_names) => { - dest.write_str("::part(")?; - for (i, name) in part_names.iter().enumerate() { - if i != 0 { - dest.write_char(' ')?; - } - name.to_css(dest)?; - } - dest.write_char(')') - }, - PseudoElement(ref p) => p.to_css(dest), - ID(ref s) => { - dest.write_char('#')?; - s.to_css(dest) - }, - Class(ref s) => { - dest.write_char('.')?; - s.to_css(dest) - }, - LocalName(ref s) => s.to_css(dest), - ExplicitUniversalType => dest.write_char('*'), - - DefaultNamespace(_) => Ok(()), - ExplicitNoNamespace => dest.write_char('|'), - ExplicitAnyNamespace => dest.write_str("*|"), - Namespace(ref prefix, _) => { - prefix.to_css(dest)?; - dest.write_char('|') - }, - - AttributeInNoNamespaceExists { ref local_name, .. } => { - dest.write_char('[')?; - local_name.to_css(dest)?; - dest.write_char(']') - }, - AttributeInNoNamespace { - ref local_name, - operator, - ref value, - case_sensitivity, - .. - } => { - dest.write_char('[')?; - local_name.to_css(dest)?; - operator.to_css(dest)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - dest.write_char(']') - }, - AttributeOther(ref attr_selector) => attr_selector.to_css(dest), - - // Pseudo-classes - Root => dest.write_str(":root"), - Empty => dest.write_str(":empty"), - Scope => dest.write_str(":scope"), - ParentSelector => dest.write_char('&'), - Host(ref selector) => { - dest.write_str(":host")?; - if let Some(ref selector) = *selector { - dest.write_char('(')?; - selector.to_css(dest)?; - dest.write_char(')')?; - } - Ok(()) - }, - Nth(ref nth_data) => { - nth_data.write_start(dest)?; - if nth_data.is_function { - nth_data.write_affine(dest)?; - dest.write_char(')')?; - } - Ok(()) - }, - NthOf(ref nth_of_data) => { - let nth_data = nth_of_data.nth_data(); - nth_data.write_start(dest)?; - debug_assert!( - nth_data.is_function, - "A selector must be a function to hold An+B notation" - ); - nth_data.write_affine(dest)?; - debug_assert!( - matches!(nth_data.ty, NthType::Child | NthType::LastChild), - "Only :nth-child or :nth-last-child can be of a selector list" - ); - debug_assert!( - !nth_of_data.selectors().is_empty(), - "The selector list should not be empty" - ); - dest.write_str(" of ")?; - serialize_selector_list(nth_of_data.selectors().iter(), dest)?; - dest.write_char(')') - }, - Is(ref list) | Where(ref list) | Negation(ref list) => { - match *self { - Where(..) => dest.write_str(":where(")?, - Is(..) => dest.write_str(":is(")?, - Negation(..) => dest.write_str(":not(")?, - _ => unreachable!(), - } - serialize_selector_list(list.iter(), dest)?; - dest.write_str(")") - }, - Has(ref list) => { - dest.write_str(":has(")?; - let mut first = true; - for RelativeSelector { ref selector, .. } in list.iter() { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; - } - dest.write_str(")") - }, - NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), - RelativeSelectorAnchor => Ok(()), - } - } -} - -impl<Impl: SelectorImpl> ToCss for AttrSelectorWithOptionalNamespace<Impl> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - dest.write_char('[')?; - match self.namespace { - Some(NamespaceConstraint::Specific((ref prefix, _))) => { - prefix.to_css(dest)?; - dest.write_char('|')? - }, - Some(NamespaceConstraint::Any) => dest.write_str("*|")?, - None => {}, - } - self.local_name.to_css(dest)?; - match self.operation { - ParsedAttrSelectorOperation::Exists => {}, - ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref value, - } => { - operator.to_css(dest)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - }, - } - dest.write_char(']') - } -} - -impl<Impl: SelectorImpl> ToCss for LocalName<Impl> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.name.to_css(dest) - } -} - -/// Build up a Selector. -/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; -/// -/// `Err` means invalid selector. -fn parse_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - mut state: SelectorParsingState, - parse_relative: ParseRelative, -) -> Result<Selector<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let mut builder = SelectorBuilder::default(); - if parse_relative == ParseRelative::Yes { - builder.push_simple_selector(Component::RelativeSelectorAnchor); - // Do we see a combinator? If so, push that. Otherwise, push a descendant combinator. - builder - .push_combinator(parse_combinator::<P, Impl>(input).unwrap_or(Combinator::Descendant)); - } - 'outer_loop: loop { - // Parse a sequence of simple selectors. - let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?; - if empty { - return Err(input.new_custom_error(if builder.has_combinators() { - SelectorParseErrorKind::DanglingCombinator - } else { - SelectorParseErrorKind::EmptySelector - })); - } - - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - debug_assert!(state.intersects( - SelectorParsingState::AFTER_PSEUDO_ELEMENT | - SelectorParsingState::AFTER_SLOTTED | - SelectorParsingState::AFTER_PART - )); - break; - } - - let combinator = if let Ok(c) = parse_combinator::<P, Impl>(input) { - c - } else { - break 'outer_loop; - }; - - if !state.allows_combinators() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - builder.push_combinator(combinator); - } - return Ok(Selector(builder.build())); -} - -fn parse_combinator<'i, 't, P, Impl>(input: &mut CssParser<'i, 't>) -> Result<Combinator, ()> { - let mut any_whitespace = false; - loop { - let before_this_token = input.state(); - match input.next_including_whitespace() { - Err(_e) => return Err(()), - Ok(&Token::WhiteSpace(_)) => any_whitespace = true, - Ok(&Token::Delim('>')) => { - return Ok(Combinator::Child); - }, - Ok(&Token::Delim('+')) => { - return Ok(Combinator::NextSibling); - }, - Ok(&Token::Delim('~')) => { - return Ok(Combinator::LaterSibling); - }, - Ok(_) => { - input.reset(&before_this_token); - if any_whitespace { - return Ok(Combinator::Descendant); - } else { - return Err(()); - } - }, - } - } -} - -impl<Impl: SelectorImpl> Selector<Impl> { - /// Parse a selector, without any pseudo-element. - #[inline] - pub fn parse<'i, 't, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result<Self, ParseError<'i, P::Error>> - where - P: Parser<'i, Impl = Impl>, - { - parse_selector( - parser, - input, - SelectorParsingState::empty(), - ParseRelative::No, - ) - } -} - -/// * `Err(())`: Invalid selector, abort -/// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. -/// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) -fn parse_type_selector<'i, 't, P, Impl, S>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - sink: &mut S, -) -> Result<bool, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, - S: Push<Component<Impl>>, -{ - match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { - Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), - .. - }) | - Ok(OptionalQName::None(_)) => Ok(false), - Ok(OptionalQName::Some(namespace, local_name)) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - match namespace { - QNamePrefix::ImplicitAnyNamespace => {}, - QNamePrefix::ImplicitDefaultNamespace(url) => { - sink.push(Component::DefaultNamespace(url)) - }, - QNamePrefix::ExplicitNamespace(prefix, url) => { - sink.push(match parser.default_namespace() { - Some(ref default_url) if url == *default_url => { - Component::DefaultNamespace(url) - }, - _ => Component::Namespace(prefix, url), - }) - }, - QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), - QNamePrefix::ExplicitAnyNamespace => { - match parser.default_namespace() { - // Element type selectors that have no namespace - // component (no namespace separator) represent elements - // without regard to the element's namespace (equivalent - // to "*|") unless a default namespace has been declared - // for namespaced selectors (e.g. in CSS, in the style - // sheet). If a default namespace has been declared, - // such selectors will represent only elements in the - // default namespace. - // -- Selectors § 6.1.1 - // So we'll have this act the same as the - // QNamePrefix::ImplicitAnyNamespace case. - None => {}, - Some(_) => sink.push(Component::ExplicitAnyNamespace), - } - }, - QNamePrefix::ImplicitNoNamespace => { - unreachable!() // Not returned with in_attr_selector = false - }, - } - match local_name { - Some(name) => sink.push(Component::LocalName(LocalName { - lower_name: to_ascii_lowercase(&name).as_ref().into(), - name: name.as_ref().into(), - })), - None => sink.push(Component::ExplicitUniversalType), - } - Ok(true) - }, - Err(e) => Err(e), - } -} - -#[derive(Debug)] -enum SimpleSelectorParseResult<Impl: SelectorImpl> { - SimpleSelector(Component<Impl>), - PseudoElement(Impl::PseudoElement), - SlottedPseudo(Selector<Impl>), - PartPseudo(Box<[Impl::Identifier]>), -} - -#[derive(Debug)] -enum QNamePrefix<Impl: SelectorImpl> { - ImplicitNoNamespace, // `foo` in attr selectors - ImplicitAnyNamespace, // `foo` in type selectors, without a default ns - ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns - ExplicitNoNamespace, // `|foo` - ExplicitAnyNamespace, // `*|foo` - ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` -} - -enum OptionalQName<'i, Impl: SelectorImpl> { - Some(QNamePrefix<Impl>, Option<CowRcStr<'i>>), - None(Token<'i>), -} - -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, -/// but the token is still returned. -/// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector -fn parse_qualified_name<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - in_attr_selector: bool, -) -> Result<OptionalQName<'i, Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let default_namespace = |local_name| { - let namespace = match parser.default_namespace() { - Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), - None => QNamePrefix::ImplicitAnyNamespace, - }; - Ok(OptionalQName::Some(namespace, local_name)) - }; - - let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { - let location = input.current_source_location(); - match input.next_including_whitespace() { - Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), - Ok(&Token::Ident(ref local_name)) => { - Ok(OptionalQName::Some(namespace, Some(local_name.clone()))) - }, - Ok(t) if in_attr_selector => { - let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); - Err(location.new_custom_error(e)) - }, - Ok(t) => Err(location.new_custom_error( - SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()), - )), - Err(e) => Err(e.into()), - } - }; - - let start = input.state(); - match input.next_including_whitespace() { - Ok(Token::Ident(value)) => { - let value = value.clone(); - let after_ident = input.state(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - let prefix = value.as_ref().into(); - let result = parser.namespace_for_prefix(&prefix); - let url = result.ok_or( - after_ident - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), - )?; - explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) - }, - _ => { - input.reset(&after_ident); - if in_attr_selector { - Ok(OptionalQName::Some( - QNamePrefix::ImplicitNoNamespace, - Some(value), - )) - } else { - default_namespace(Some(value)) - } - }, - } - }, - Ok(Token::Delim('*')) => { - let after_star = input.state(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace) - }, - _ if !in_attr_selector => { - input.reset(&after_star); - default_namespace(None) - }, - result => { - let t = result?; - Err(after_star - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t.clone()))) - }, - } - }, - Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), - Ok(t) => { - let t = t.clone(); - input.reset(&start); - Ok(OptionalQName::None(t)) - }, - Err(e) => { - input.reset(&start); - Err(e.into()) - }, - } -} - -fn parse_attribute_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let namespace; - let local_name; - - input.skip_whitespace(); - - match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { - OptionalQName::None(t) => { - return Err(input.new_custom_error( - SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t), - )); - }, - OptionalQName::Some(_, None) => unreachable!(), - OptionalQName::Some(ns, Some(ln)) => { - local_name = ln; - namespace = match ns { - QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, - QNamePrefix::ExplicitNamespace(prefix, url) => { - Some(NamespaceConstraint::Specific((prefix, url))) - }, - QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), - QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { - unreachable!() // Not returned with in_attr_selector = true - }, - } - }, - } - - let location = input.current_source_location(); - let operator = match input.next() { - // [foo] - Err(_) => { - let local_name_lower = to_ascii_lowercase(&local_name).as_ref().into(); - let local_name = local_name.as_ref().into(); - if let Some(namespace) = namespace { - return Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace: Some(namespace), - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::Exists, - }, - ))); - } else { - return Ok(Component::AttributeInNoNamespaceExists { - local_name, - local_name_lower, - }); - } - }, - - // [foo=bar] - Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, - // [foo~=bar] - Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, - // [foo|=bar] - Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, - // [foo^=bar] - Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, - // [foo*=bar] - Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, - // [foo$=bar] - Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, - Ok(t) => { - return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone()), - )); - }, - }; - - let value = match input.expect_ident_or_string() { - Ok(t) => t.clone(), - Err(BasicParseError { - kind: BasicParseErrorKind::UnexpectedToken(t), - location, - }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), - Err(e) => return Err(e.into()), - }; - - let attribute_flags = parse_attribute_flags(input)?; - let value = value.as_ref().into(); - let local_name_lower; - let local_name_is_ascii_lowercase; - let case_sensitivity; - { - let local_name_lower_cow = to_ascii_lowercase(&local_name); - case_sensitivity = - attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); - local_name_lower = local_name_lower_cow.as_ref().into(); - local_name_is_ascii_lowercase = matches!(local_name_lower_cow, Cow::Borrowed(..)); - } - let local_name = local_name.as_ref().into(); - if namespace.is_some() || !local_name_is_ascii_lowercase { - Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace, - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - value, - }, - }, - ))) - } else { - Ok(Component::AttributeInNoNamespace { - local_name, - operator, - value, - case_sensitivity, - }) - } -} - -/// An attribute selector can have 's' or 'i' as flags, or no flags at all. -enum AttributeFlags { - // Matching should be case-sensitive ('s' flag). - CaseSensitive, - // Matching should be case-insensitive ('i' flag). - AsciiCaseInsensitive, - // No flags. Matching behavior depends on the name of the attribute. - CaseSensitivityDependsOnName, -} - -impl AttributeFlags { - fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity { - match self { - AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive, - AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive, - AttributeFlags::CaseSensitivityDependsOnName => { - if !have_namespace && - include!(concat!( - env!("OUT_DIR"), - "/ascii_case_insensitive_html_attributes.rs" - )) - .contains(local_name) - { - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument - } else { - ParsedCaseSensitivity::CaseSensitive - } - }, - } - } -} - -fn parse_attribute_flags<'i, 't>( - input: &mut CssParser<'i, 't>, -) -> Result<AttributeFlags, BasicParseError<'i>> { - let location = input.current_source_location(); - let token = match input.next() { - Ok(t) => t, - Err(..) => { - // Selectors spec says language-defined; HTML says it depends on the - // exact attribute name. - return Ok(AttributeFlags::CaseSensitivityDependsOnName); - }, - }; - - let ident = match *token { - Token::Ident(ref i) => i, - ref other => return Err(location.new_basic_unexpected_token_error(other.clone())), - }; - - Ok(match_ignore_ascii_case! { - ident, - "i" => AttributeFlags::AsciiCaseInsensitive, - "s" => AttributeFlags::CaseSensitive, - _ => return Err(location.new_basic_unexpected_token_error(token.clone())), - }) -} - -/// Level 3: Parse **one** simple_selector. (Though we might insert a second -/// implied "<defaultns>|*" type selector.) -fn parse_negation<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let list = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::No, - ParseRelative::No, - )?; - - Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) -} - -/// simple_selector_sequence -/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* -/// | [ HASH | class | attrib | pseudo | negation ]+ -/// -/// `Err(())` means invalid selector. -/// `Ok(true)` is an empty selector -fn parse_compound_selector<'i, 't, P, Impl>( - parser: &P, - state: &mut SelectorParsingState, - input: &mut CssParser<'i, 't>, - builder: &mut SelectorBuilder<Impl>, -) -> Result<bool, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - input.skip_whitespace(); - - let mut empty = true; - if parse_type_selector(parser, input, *state, builder)? { - empty = false; - } - - loop { - let result = match parse_one_simple_selector(parser, input, *state)? { - None => break, - Some(result) => result, - }; - - if empty { - if let Some(url) = parser.default_namespace() { - // If there was no explicit type selector, but there is a - // default namespace, there is an implicit "<defaultns>|*" type - // selector. Except for :host() or :not() / :is() / :where(), - // where we ignore it. - // - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // When considered within its own shadow trees, the shadow - // host is featureless. Only the :host, :host(), and - // :host-context() pseudo-classes are allowed to match it. - // - // https://drafts.csswg.org/selectors-4/#featureless: - // - // A featureless element does not match any selector at all, - // except those it is explicitly defined to match. If a - // given selector is allowed to match a featureless element, - // it must do so while ignoring the default namespace. - // - // https://drafts.csswg.org/selectors-4/#matches - // - // Default namespace declarations do not affect the compound - // selector representing the subject of any selector within - // a :is() pseudo-class, unless that compound selector - // contains an explicit universal selector or type selector. - // - // (Similar quotes for :where() / :not()) - // - let ignore_default_ns = state - .intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) || - matches!( - result, - SimpleSelectorParseResult::SimpleSelector(Component::Host(..)) - ); - if !ignore_default_ns { - builder.push_simple_selector(Component::DefaultNamespace(url)); - } - } - } - - empty = false; - - match result { - SimpleSelectorParseResult::SimpleSelector(s) => { - builder.push_simple_selector(s); - }, - SimpleSelectorParseResult::PartPseudo(part_names) => { - state.insert(SelectorParsingState::AFTER_PART); - builder.push_combinator(Combinator::Part); - builder.push_simple_selector(Component::Part(part_names)); - }, - SimpleSelectorParseResult::SlottedPseudo(selector) => { - state.insert(SelectorParsingState::AFTER_SLOTTED); - builder.push_combinator(Combinator::SlotAssignment); - builder.push_simple_selector(Component::Slotted(selector)); - }, - SimpleSelectorParseResult::PseudoElement(p) => { - state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); - if !p.accepts_state_pseudo_classes() { - state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); - } - builder.push_combinator(Combinator::PseudoElement); - builder.push_simple_selector(Component::PseudoElement(p)); - }, - } - } - Ok(empty) -} - -fn parse_is_where<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - debug_assert!(parser.parse_is_and_where()); - // https://drafts.csswg.org/selectors/#matches-pseudo: - // - // Pseudo-elements cannot be represented by the matches-any - // pseudo-class; they are not valid within :is(). - // - let inner = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::Yes, - ParseRelative::No, - )?; - Ok(component(inner.0.into_vec().into_boxed_slice())) -} - -fn parse_has<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - debug_assert!(parser.parse_has()); - if state.intersects(SelectorParsingState::DISALLOW_RELATIVE_SELECTOR) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - // Nested `:has()` is disallowed, mark it as such. - // Note: The spec defines ":has-allowed pseudo-element," but there's no - // pseudo-element defined as such at the moment. - // https://w3c.github.io/csswg-drafts/selectors-4/#has-allowed-pseudo-element - let inner = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS | - SelectorParsingState::DISALLOW_RELATIVE_SELECTOR, - ForgivingParsing::No, - ParseRelative::Yes, - )?; - Ok(Component::Has(RelativeSelector::from_selector_list(inner))) -} - -fn parse_functional_pseudo_class<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - name: CowRcStr<'i>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - match_ignore_ascii_case! { &name, - "nth-child" => return parse_nth_pseudo_class(parser, input, state, NthType::Child), - "nth-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::OfType), - "nth-last-child" => return parse_nth_pseudo_class(parser, input, state, NthType::LastChild), - "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, state, NthType::LastOfType), - "is" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Is), - "where" if parser.parse_is_and_where() => return parse_is_where(parser, input, state, Component::Where), - "has" if parser.parse_has() => return parse_has(parser, input, state), - "host" => { - if !state.allows_tree_structural_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); - }, - "not" => { - return parse_negation(parser, input, state) - }, - _ => {} - } - - if parser.parse_is_and_where() && parser.is_is_alias(&name) { - return parse_is_where(parser, input, state, Component::Is); - } - - if !state.allows_custom_functional_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) -} - -fn parse_nth_pseudo_class<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - ty: NthType, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - if !state.allows_tree_structural_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let (a, b) = parse_nth(input)?; - let nth_data = NthSelectorData { - ty, - is_function: true, - a, - b, - }; - if !parser.parse_nth_child_of() || ty.is_of_type() { - return Ok(Component::Nth(nth_data)); - } - - // Try to parse "of <selector-list>". - if input.try_parse(|i| i.expect_ident_matching("of")).is_err() { - return Ok(Component::Nth(nth_data)); - } - // Whitespace between "of" and the selector list is optional - // https://github.com/w3c/csswg-drafts/issues/8285 - let mut selectors = SelectorList::parse_with_state( - parser, - input, - state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS, - ForgivingParsing::No, - ParseRelative::No, - )?; - Ok(Component::NthOf(NthOfSelectorData::new( - &nth_data, - selectors.0.drain(..), - ))) -} - -/// Returns whether the name corresponds to a CSS2 pseudo-element that -/// can be specified with the single colon syntax (in addition to the -/// double-colon syntax, which can be used for all pseudo-elements). -fn is_css2_pseudo_element(name: &str) -> bool { - // ** Do not add to this list! ** - match_ignore_ascii_case! { name, - "before" | "after" | "first-line" | "first-letter" => true, - _ => false, - } -} - -/// Parse a simple selector other than a type selector. -/// -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. -/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element -fn parse_one_simple_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, -) -> Result<Option<SimpleSelectorParseResult<Impl>>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - let start = input.state(); - let token = match input.next_including_whitespace().map(|t| t.clone()) { - Ok(t) => t, - Err(..) => { - input.reset(&start); - return Ok(None); - }, - }; - - Ok(Some(match token { - Token::IDHash(id) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let id = Component::ID(id.as_ref().into()); - SimpleSelectorParseResult::SimpleSelector(id) - }, - Token::Delim(delim) if delim == '.' || (delim == '&' && parser.parse_parent_selector()) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let location = input.current_source_location(); - SimpleSelectorParseResult::SimpleSelector(if delim == '&' { - Component::ParentSelector - } else { - let class = match *input.next_including_whitespace()? { - Token::Ident(ref class) => class, - ref t => { - let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); - return Err(location.new_custom_error(e)); - }, - }; - Component::Class(class.as_ref().into()) - }) - }, - Token::SquareBracketBlock => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; - SimpleSelectorParseResult::SimpleSelector(attr) - }, - Token::Colon => { - let location = input.current_source_location(); - let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { - Token::Colon => (false, input.next_including_whitespace()?.clone()), - t => (true, t), - }; - let (name, is_functional) = match next_token { - Token::Ident(name) => (name, false), - Token::Function(name) => (name, true), - t => { - let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); - return Err(input.new_custom_error(e)); - }, - }; - let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); - if is_pseudo_element { - if !state.allows_pseudos() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let pseudo_element = if is_functional { - if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { - if !state.allows_part() { - return Err( - input.new_custom_error(SelectorParseErrorKind::InvalidState) - ); - } - let names = input.parse_nested_block(|input| { - let mut result = Vec::with_capacity(1); - result.push(input.expect_ident()?.as_ref().into()); - while !input.is_exhausted() { - result.push(input.expect_ident()?.as_ref().into()); - } - Ok(result.into_boxed_slice()) - })?; - return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); - } - if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { - if !state.allows_slotted() { - return Err( - input.new_custom_error(SelectorParseErrorKind::InvalidState) - ); - } - let selector = input.parse_nested_block(|input| { - parse_inner_compound_selector(parser, input, state) - })?; - return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); - } - input.parse_nested_block(|input| { - P::parse_functional_pseudo_element(parser, name, input) - })? - } else { - P::parse_pseudo_element(parser, location, name)? - }; - - if state.intersects(SelectorParsingState::AFTER_SLOTTED) && - !pseudo_element.valid_after_slotted() - { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - SimpleSelectorParseResult::PseudoElement(pseudo_element) - } else { - let pseudo_class = if is_functional { - input.parse_nested_block(|input| { - parse_functional_pseudo_class(parser, input, name, state) - })? - } else { - parse_simple_pseudo_class(parser, location, name, state)? - }; - SimpleSelectorParseResult::SimpleSelector(pseudo_class) - } - }, - _ => { - input.reset(&start); - return Ok(None); - }, - })) -} - -fn parse_simple_pseudo_class<'i, P, Impl>( - parser: &P, - location: SourceLocation, - name: CowRcStr<'i>, - state: SelectorParsingState, -) -> Result<Component<Impl>, ParseError<'i, P::Error>> -where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl, -{ - if !state.allows_non_functional_pseudo_classes() { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - if state.allows_tree_structural_pseudo_classes() { - match_ignore_ascii_case! { &name, - "first-child" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ false))), - "last-child" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ false))), - "only-child" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ false))), - "root" => return Ok(Component::Root), - "empty" => return Ok(Component::Empty), - "scope" => return Ok(Component::Scope), - "host" if P::parse_host(parser) => return Ok(Component::Host(None)), - "first-of-type" => return Ok(Component::Nth(NthSelectorData::first(/* of_type = */ true))), - "last-of-type" => return Ok(Component::Nth(NthSelectorData::last(/* of_type = */ true))), - "only-of-type" => return Ok(Component::Nth(NthSelectorData::only(/* of_type = */ true))), - _ => {}, - } - } - - let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; - if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) && - !pseudo_class.is_user_action_state() - { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - Ok(Component::NonTSPseudoClass(pseudo_class)) -} - -// NB: pub module in order to access the DummyParser -#[cfg(test)] -pub mod tests { - use super::*; - use crate::builder::SelectorFlags; - use crate::parser; - use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; - use std::collections::HashMap; - use std::fmt; - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoClass { - Hover, - Active, - Lang(String), - } - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoElement { - Before, - After, - Highlight(String), - } - - impl parser::PseudoElement for PseudoElement { - type Impl = DummySelectorImpl; - - fn accepts_state_pseudo_classes(&self) -> bool { - true - } - - fn valid_after_slotted(&self) -> bool { - true - } - } - - impl parser::NonTSPseudoClass for PseudoClass { - type Impl = DummySelectorImpl; - - #[inline] - fn is_active_or_hover(&self) -> bool { - matches!(*self, PseudoClass::Active | PseudoClass::Hover) - } - - #[inline] - fn is_user_action_state(&self) -> bool { - self.is_active_or_hover() - } - } - - impl ToCss for PseudoClass { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoClass::Hover => dest.write_str(":hover"), - PseudoClass::Active => dest.write_str(":active"), - PseudoClass::Lang(ref lang) => { - dest.write_str(":lang(")?; - serialize_identifier(lang, dest)?; - dest.write_char(')') - }, - } - } - } - - impl ToCss for PseudoElement { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoElement::Before => dest.write_str("::before"), - PseudoElement::After => dest.write_str("::after"), - PseudoElement::Highlight(ref name) => { - dest.write_str("::highlight(")?; - serialize_identifier(&name, dest)?; - dest.write_char(')') - }, - } - } - } - - #[derive(Clone, Debug, PartialEq)] - pub struct DummySelectorImpl; - - #[derive(Default)] - pub struct DummyParser { - default_ns: Option<DummyAtom>, - ns_prefixes: HashMap<DummyAtom, DummyAtom>, - } - - impl DummyParser { - fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { - DummyParser { - default_ns: Some(default_ns), - ns_prefixes: Default::default(), - } - } - } - - impl SelectorImpl for DummySelectorImpl { - type ExtraMatchingData<'a> = std::marker::PhantomData<&'a ()>; - type AttrValue = DummyAttrValue; - type Identifier = DummyAtom; - type LocalName = DummyAtom; - type NamespaceUrl = DummyAtom; - type NamespacePrefix = DummyAtom; - type BorrowedLocalName = DummyAtom; - type BorrowedNamespaceUrl = DummyAtom; - type NonTSPseudoClass = PseudoClass; - type PseudoElement = PseudoElement; - } - - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAttrValue(String); - - impl ToCss for DummyAttrValue { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use std::fmt::Write; - - write!(cssparser::CssStringWriter::new(dest), "{}", &self.0) - } - } - - impl<'a> From<&'a str> for DummyAttrValue { - fn from(string: &'a str) -> Self { - Self(string.into()) - } - } - - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAtom(String); - - impl ToCss for DummyAtom { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_identifier(&self.0, dest) - } - } - - impl From<String> for DummyAtom { - fn from(string: String) -> Self { - DummyAtom(string) - } - } - - impl<'a> From<&'a str> for DummyAtom { - fn from(string: &'a str) -> Self { - DummyAtom(string.into()) - } - } - - impl<'i> Parser<'i> for DummyParser { - type Impl = DummySelectorImpl; - type Error = SelectorParseErrorKind<'i>; - - fn parse_slotted(&self) -> bool { - true - } - - fn parse_nth_child_of(&self) -> bool { - true - } - - fn parse_is_and_where(&self) -> bool { - true - } - - fn parse_has(&self) -> bool { - true - } - - fn parse_parent_selector(&self) -> bool { - true - } - - fn parse_part(&self) -> bool { - true - } - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<PseudoClass, SelectorParseError<'i>> { - match_ignore_ascii_case! { &name, - "hover" => return Ok(PseudoClass::Hover), - "active" => return Ok(PseudoClass::Active), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result<PseudoClass, SelectorParseError<'i>> { - match_ignore_ascii_case! { &name, - "lang" => { - let lang = parser.expect_ident_or_string()?.as_ref().to_owned(); - return Ok(PseudoClass::Lang(lang)); - }, - _ => {} - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<PseudoElement, SelectorParseError<'i>> { - match_ignore_ascii_case! { &name, - "before" => return Ok(PseudoElement::Before), - "after" => return Ok(PseudoElement::After), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result<PseudoElement, SelectorParseError<'i>> { - match_ignore_ascii_case! {&name, - "highlight" => return Ok(PseudoElement::Highlight(parser.expect_ident()?.as_ref().to_owned())), - _ => {} - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<DummyAtom> { - self.default_ns.clone() - } - - fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option<DummyAtom> { - self.ns_prefixes.get(prefix).cloned() - } - } - - fn parse<'i>( - input: &'i str, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - parse_ns(input, &DummyParser::default()) - } - - fn parse_expected<'i, 'a>( - input: &'i str, - expected: Option<&'a str>, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - parse_ns_expected(input, &DummyParser::default(), expected) - } - - fn parse_ns<'i>( - input: &'i str, - parser: &DummyParser, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - parse_ns_expected(input, parser, None) - } - - fn parse_ns_expected<'i, 'a>( - input: &'i str, - parser: &DummyParser, - expected: Option<&'a str>, - ) -> Result<SelectorList<DummySelectorImpl>, SelectorParseError<'i>> { - let mut parser_input = ParserInput::new(input); - let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input)); - if let Ok(ref selectors) = result { - // We can't assume that the serialized parsed selector will equal - // the input; for example, if there is no default namespace, '*|foo' - // should serialize to 'foo'. - assert_eq!( - selectors.to_css_string(), - match expected { - Some(x) => x, - None => input, - } - ); - } - result - } - - fn specificity(a: u32, b: u32, c: u32) -> u32 { - a << 20 | b << 10 | c - } - - #[test] - fn test_empty() { - let mut input = ParserInput::new(":empty"); - let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(&mut input)); - assert!(list.is_ok()); - } - - const MATHML: &str = "http://www.w3.org/1998/Math/MathML"; - const SVG: &str = "http://www.w3.org/2000/svg"; - - #[test] - fn test_parsing() { - assert!(parse("").is_err()); - assert!(parse(":lang(4)").is_err()); - assert!(parse(":lang(en US)").is_err()); - assert_eq!( - parse("EeÉ"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::LocalName(LocalName { - name: DummyAtom::from("EeÉ"), - lower_name: DummyAtom::from("eeÉ"), - })], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("|e"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - // When the default namespace is not set, *| should be elided. - // https://github.com/servo/servo/pull/17537 - assert_eq!( - parse_expected("*|e", Some("e")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - })], - specificity(0, 0, 1), - Default::default(), - )])) - ); - // When the default namespace is set, *| should _not_ be elided (as foo - // is no longer equivalent to *|foo--the former is only for foo in the - // default namespace). - // https://github.com/servo/servo/issues/16020 - assert_eq!( - parse_ns( - "*|e", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) - ), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("*"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("|*"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_expected("*|*", Some("*")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns( - "*|*", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) - ), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse(".foo:lang(en-US)"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Class(DummyAtom::from("foo")), - Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), - ], - specificity(0, 2, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("#bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ID(DummyAtom::from("bar"))], - specificity(1, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("e.foo#bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - Component::Class(DummyAtom::from("foo")), - Component::ID(DummyAtom::from("bar")), - ], - specificity(1, 1, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("e.foo #bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - Component::Class(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ID(DummyAtom::from("bar")), - ], - specificity(1, 1, 1), - Default::default(), - )])) - ); - // Default namespace does not apply to attribute selectors - // https://github.com/mozilla/servo/pull/1652 - let mut parser = DummyParser::default(); - assert_eq!( - parse_ns("[Foo]", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }], - specificity(0, 1, 0), - Default::default(), - )])) - ); - assert!(parse_ns("svg|circle", &parser).is_err()); - parser - .ns_prefixes - .insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); - assert_eq!( - parse_ns("svg|circle", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("circle"), - lower_name: DummyAtom::from("circle"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("svg|*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // Default namespace does not apply to attribute selectors - // https://github.com/mozilla/servo/pull/1652 - // but it does apply to implicit type selectors - // https://github.com/servo/rust-selectors/pull/82 - parser.default_ns = Some(MATHML.into()); - assert_eq!( - parse_ns("[Foo]", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }, - ], - specificity(0, 1, 0), - Default::default(), - )])) - ); - // Default namespace does apply to type selectors - assert_eq!( - parse_ns("e", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("*|*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // Default namespace applies to universal and type selectors inside :not and :matches, - // but not otherwise. - assert_eq!( - parse_ns(":not(.cl)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![Component::Class(DummyAtom::from("cl"))], - specificity(0, 1, 0), - Default::default(), - )] - .into_boxed_slice() - ), - ], - specificity(0, 1, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice(), - ), - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(e)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - ),] - .into_boxed_slice() - ), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("[attr|=\"foo\"]"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::AttributeInNoNamespace { - local_name: DummyAtom::from("attr"), - operator: AttrSelectorOperator::DashMatch, - value: DummyAttrValue::from("foo"), - case_sensitivity: ParsedCaseSensitivity::CaseSensitive, - }], - specificity(0, 1, 0), - Default::default(), - )])) - ); - // https://github.com/mozilla/servo/issues/1723 - assert_eq!( - parse("::before"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - ], - specificity(0, 0, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("::before:hover"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - ], - specificity(0, 1, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("::before:hover:hover"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - Component::NonTSPseudoClass(PseudoClass::Hover), - ], - specificity(0, 2, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert!(parse("::before:hover:lang(foo)").is_err()); - assert!(parse("::before:hover .foo").is_err()); - assert!(parse("::before .foo").is_err()); - assert!(parse("::before ~ bar").is_err()); - assert!(parse("::before:active").is_ok()); - - // https://github.com/servo/servo/issues/15335 - assert!(parse(":: before").is_err()); - assert_eq!( - parse("div ::after"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("div"), - lower_name: DummyAtom::from("div"), - }), - Component::Combinator(Combinator::Descendant), - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::After), - ], - specificity(0, 0, 2), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("#d1 > .ok"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ID(DummyAtom::from("d1")), - Component::Combinator(Combinator::Child), - Component::Class(DummyAtom::from("ok")), - ], - (1 << 20) + (1 << 10) + (0 << 0), - Default::default(), - )])) - ); - parser.default_ns = None; - assert!(parse(":not(#provel.old)").is_ok()); - assert!(parse(":not(#provel > old)").is_ok()); - assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); - // https://github.com/servo/servo/issues/16017 - assert_eq!( - parse_ns(":not(*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice() - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(|*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice(), - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // *| should be elided if there is no default namespace. - // https://github.com/servo/servo/pull/17537 - assert_eq!( - parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default() - )] - .into_boxed_slice() - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - - assert!(parse("::highlight(foo)").is_ok()); - - assert!(parse("::slotted()").is_err()); - assert!(parse("::slotted(div)").is_ok()); - assert!(parse("::slotted(div).foo").is_err()); - assert!(parse("::slotted(div + bar)").is_err()); - assert!(parse("::slotted(div) + foo").is_err()); - - assert!(parse("::part()").is_err()); - assert!(parse("::part(42)").is_err()); - assert!(parse("::part(foo bar)").is_ok()); - assert!(parse("::part(foo):hover").is_ok()); - assert!(parse("::part(foo) + bar").is_err()); - - assert!(parse("div ::slotted(div)").is_ok()); - assert!(parse("div + slot::slotted(div)").is_ok()); - assert!(parse("div + slot::slotted(div.foo)").is_ok()); - assert!(parse("slot::slotted(div,foo)::first-line").is_err()); - assert!(parse("::slotted(div)::before").is_ok()); - assert!(parse("slot::slotted(div,foo)").is_err()); - - assert!(parse("foo:where()").is_ok()); - assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); - assert!(parse_expected("foo:where(::before)", Some("foo:where()")).is_ok()); - } - - #[test] - fn parent_selector() { - assert!(parse("foo &").is_ok()); - assert_eq!( - parse("#foo &.bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ID(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ParentSelector, - Component::Class(DummyAtom::from("bar")), - ], - (1 << 20) + (1 << 10) + (0 << 0), - SelectorFlags::HAS_PARENT - )])) - ); - - let parent = parse(".bar, div .baz").unwrap(); - let child = parse("#foo &.bar").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0)]), - parse("#foo :is(.bar, div .baz).bar").unwrap() - ); - - let has_child = parse("#foo:has(&.bar)").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![has_child.0[0].replace_parent_selector(&parent.0)]), - parse("#foo:has(:is(.bar, div .baz).bar)").unwrap() - ); - - let child = parse("#foo").unwrap(); - assert_eq!( - SelectorList::from_vec(vec![child.0[0].replace_parent_selector(&parent.0)]), - parse(":is(.bar, div .baz) #foo").unwrap() - ); - } - - #[test] - fn test_pseudo_iter() { - let selector = &parse("q::before").unwrap().0[0]; - assert!(!selector.is_universal()); - let mut iter = selector.iter(); - assert_eq!( - iter.next(), - Some(&Component::PseudoElement(PseudoElement::Before)) - ); - assert_eq!(iter.next(), None); - let combinator = iter.next_sequence(); - assert_eq!(combinator, Some(Combinator::PseudoElement)); - assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), None); - } - - #[test] - fn test_universal() { - let selector = &parse_ns( - "*|*::before", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")), - ) - .unwrap() - .0[0]; - assert!(selector.is_universal()); - } - - #[test] - fn test_empty_pseudo_iter() { - let selector = &parse("::before").unwrap().0[0]; - assert!(selector.is_universal()); - let mut iter = selector.iter(); - assert_eq!( - iter.next(), - Some(&Component::PseudoElement(PseudoElement::Before)) - ); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement)); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), None); - } - - struct TestVisitor { - seen: Vec<String>, - } - - impl SelectorVisitor for TestVisitor { - type Impl = DummySelectorImpl; - - fn visit_simple_selector(&mut self, s: &Component<DummySelectorImpl>) -> bool { - let mut dest = String::new(); - s.to_css(&mut dest).unwrap(); - self.seen.push(dest); - true - } - } - - #[test] - fn visitor() { - let mut test_visitor = TestVisitor { seen: vec![] }; - parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); - assert!(test_visitor.seen.contains(&":hover".into())); - - let mut test_visitor = TestVisitor { seen: vec![] }; - parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); - assert!(test_visitor.seen.contains(&":hover".into())); - } -} diff --git a/components/selectors/rustfmt.toml b/components/selectors/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/selectors/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/selectors/sink.rs b/components/selectors/sink.rs deleted file mode 100644 index dcdd7ff259d..00000000000 --- a/components/selectors/sink.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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/. */ - -//! Small helpers to abstract over different containers. -#![deny(missing_docs)] - -use smallvec::{Array, SmallVec}; - -/// A trait to abstract over a `push` method that may be implemented for -/// different kind of types. -/// -/// Used to abstract over `Array`, `SmallVec` and `Vec`, and also to implement a -/// type which `push` method does only tweak a byte when we only need to check -/// for the presence of something. -pub trait Push<T> { - /// Push a value into self. - fn push(&mut self, value: T); -} - -impl<T> Push<T> for Vec<T> { - fn push(&mut self, value: T) { - Vec::push(self, value); - } -} - -impl<A: Array> Push<A::Item> for SmallVec<A> { - fn push(&mut self, value: A::Item) { - SmallVec::push(self, value); - } -} diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs deleted file mode 100644 index 560b1e61d81..00000000000 --- a/components/selectors/tree.rs +++ /dev/null @@ -1,163 +0,0 @@ -/* 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/. */ - -//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency -//! between layout and style. - -use crate::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use crate::matching::{ElementSelectorFlags, MatchingContext}; -use crate::parser::SelectorImpl; -use std::fmt::Debug; -use std::ptr::NonNull; - -/// Opaque representation of an Element, for identity comparisons. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct OpaqueElement(NonNull<()>); - -unsafe impl Send for OpaqueElement {} - -impl OpaqueElement { - /// Creates a new OpaqueElement from an arbitrarily-typed pointer. - pub fn new<T>(ptr: &T) -> Self { - unsafe { - OpaqueElement(NonNull::new_unchecked( - ptr as *const T as *const () as *mut (), - )) - } - } -} - -pub trait Element: Sized + Clone + Debug { - type Impl: SelectorImpl; - - /// Converts self into an opaque representation. - fn opaque(&self) -> OpaqueElement; - - fn parent_element(&self) -> Option<Self>; - - /// Whether the parent node of this element is a shadow root. - fn parent_node_is_shadow_root(&self) -> bool; - - /// The host of the containing shadow root, if any. - fn containing_shadow_host(&self) -> Option<Self>; - - /// The parent of a given pseudo-element, after matching a pseudo-element - /// selector. - /// - /// This is guaranteed to be called in a pseudo-element. - fn pseudo_element_originating_element(&self) -> Option<Self> { - debug_assert!(self.is_pseudo_element()); - self.parent_element() - } - - /// Whether we're matching on a pseudo-element. - fn is_pseudo_element(&self) -> bool; - - /// Skips non-element nodes - fn prev_sibling_element(&self) -> Option<Self>; - - /// Skips non-element nodes - fn next_sibling_element(&self) -> Option<Self>; - - /// Skips non-element nodes - fn first_element_child(&self) -> Option<Self>; - - fn is_html_element_in_html_document(&self) -> bool; - - fn has_local_name(&self, local_name: &<Self::Impl as SelectorImpl>::BorrowedLocalName) -> bool; - - /// Empty string for no namespace - fn has_namespace(&self, ns: &<Self::Impl as SelectorImpl>::BorrowedNamespaceUrl) -> bool; - - /// Whether this element and the `other` element have the same local name and namespace. - fn is_same_type(&self, other: &Self) -> bool; - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>, - local_name: &<Self::Impl as SelectorImpl>::LocalName, - operation: &AttrSelectorOperation<&<Self::Impl as SelectorImpl>::AttrValue>, - ) -> bool; - - fn has_attr_in_no_namespace( - &self, - local_name: &<Self::Impl as SelectorImpl>::LocalName, - ) -> bool { - self.attr_matches( - &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::<Self::Impl>()), - local_name, - &AttrSelectorOperation::Exists, - ) - } - - fn match_non_ts_pseudo_class( - &self, - pc: &<Self::Impl as SelectorImpl>::NonTSPseudoClass, - context: &mut MatchingContext<Self::Impl>, - ) -> bool; - - fn match_pseudo_element( - &self, - pe: &<Self::Impl as SelectorImpl>::PseudoElement, - context: &mut MatchingContext<Self::Impl>, - ) -> bool; - - /// Sets selector flags on the elemnt itself or the parent, depending on the - /// flags, which indicate what kind of work may need to be performed when - /// DOM state changes. - fn apply_selector_flags(&self, flags: ElementSelectorFlags); - - /// Whether this element is a `link`. - fn is_link(&self) -> bool; - - /// Returns whether the element is an HTML <slot> element. - fn is_html_slot_element(&self) -> bool; - - /// Returns the assigned <slot> element this element is assigned to. - /// - /// Necessary for the `::slotted` pseudo-class. - fn assigned_slot(&self) -> Option<Self> { - None - } - - fn has_id( - &self, - id: &<Self::Impl as SelectorImpl>::Identifier, - case_sensitivity: CaseSensitivity, - ) -> bool; - - fn has_class( - &self, - name: &<Self::Impl as SelectorImpl>::Identifier, - case_sensitivity: CaseSensitivity, - ) -> bool; - - /// Returns the mapping from the `exportparts` attribute in the reverse - /// direction, that is, in an outer-tree -> inner-tree direction. - fn imported_part( - &self, - name: &<Self::Impl as SelectorImpl>::Identifier, - ) -> Option<<Self::Impl as SelectorImpl>::Identifier>; - - fn is_part(&self, name: &<Self::Impl as SelectorImpl>::Identifier) -> bool; - - /// Returns whether this element matches `:empty`. - /// - /// That is, whether it does not contain any child element or any non-zero-length text node. - /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo - fn is_empty(&self) -> bool; - - /// Returns whether this element matches `:root`, - /// i.e. whether it is the root element of a document. - /// - /// Note: this can be false even if `.parent_element()` is `None` - /// if the parent node is a `DocumentFragment`. - fn is_root(&self) -> bool; - - /// Returns whether this element should ignore matching nth child - /// selector. - fn ignores_nth_child_selectors(&self) -> bool { - false - } -} diff --git a/components/selectors/visitor.rs b/components/selectors/visitor.rs deleted file mode 100644 index 785c12813a6..00000000000 --- a/components/selectors/visitor.rs +++ /dev/null @@ -1,111 +0,0 @@ -/* 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/. */ - -//! Visitor traits for selectors. - -#![deny(missing_docs)] - -use crate::attr::NamespaceConstraint; -use crate::parser::{Combinator, Component, Selector, SelectorImpl}; - -/// A trait to visit selector properties. -/// -/// All the `visit_foo` methods return a boolean indicating whether the -/// traversal should continue or not. -pub trait SelectorVisitor: Sized { - /// The selector implementation this visitor wants to visit. - type Impl: SelectorImpl; - - /// Visit an attribute selector that may match (there are other selectors - /// that may never match, like those containing whitespace or the empty - /// string). - fn visit_attribute_selector( - &mut self, - _namespace: &NamespaceConstraint<&<Self::Impl as SelectorImpl>::NamespaceUrl>, - _local_name: &<Self::Impl as SelectorImpl>::LocalName, - _local_name_lower: &<Self::Impl as SelectorImpl>::LocalName, - ) -> bool { - true - } - - /// Visit a simple selector. - fn visit_simple_selector(&mut self, _: &Component<Self::Impl>) -> bool { - true - } - - /// Visit a nested selector list. The caller is responsible to call visit - /// into the internal selectors if / as needed. - /// - /// The default implementation does this. - fn visit_selector_list( - &mut self, - _list_kind: SelectorListKind, - list: &[Selector<Self::Impl>], - ) -> bool { - for nested in list { - if !nested.visit(self) { - return false; - } - } - true - } - - /// Visits a complex selector. - /// - /// Gets the combinator to the right of the selector, or `None` if the - /// selector is the rightmost one. - fn visit_complex_selector(&mut self, _combinator_to_right: Option<Combinator>) -> bool { - true - } -} - -bitflags! { - /// The kinds of components the visitor is visiting the selector list of, if any - #[derive(Default)] - pub struct SelectorListKind: u8 { - /// The visitor is inside :not(..) - const NEGATION = 1 << 0; - /// The visitor is inside :is(..) - const IS = 1 << 1; - /// The visitor is inside :where(..) - const WHERE = 1 << 2; - /// The visitor is inside :nth-child(.. of <selector list>) or - /// :nth-last-child(.. of <selector list>) - const NTH_OF = 1 << 3; - } -} - -impl SelectorListKind { - /// Construct a SelectorListKind for the corresponding component. - pub fn from_component<Impl: SelectorImpl>(component: &Component<Impl>) -> Self { - match component { - Component::Negation(_) => SelectorListKind::NEGATION, - Component::Is(_) => SelectorListKind::IS, - Component::Where(_) => SelectorListKind::WHERE, - Component::NthOf(_) => SelectorListKind::NTH_OF, - _ => SelectorListKind::empty(), - } - } - - /// Whether the visitor is inside :not(..) - pub fn in_negation(&self) -> bool { - self.intersects(SelectorListKind::NEGATION) - } - - /// Whether the visitor is inside :is(..) - pub fn in_is(&self) -> bool { - self.intersects(SelectorListKind::IS) - } - - /// Whether the visitor is inside :where(..) - pub fn in_where(&self) -> bool { - self.intersects(SelectorListKind::WHERE) - } - - /// Whether the visitor is inside :nth-child(.. of <selector list>) or - /// :nth-last-child(.. of <selector list>) - pub fn in_nth_of(&self) -> bool { - self.intersects(SelectorListKind::NTH_OF) - } -} diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 29ae33c0d5e..7fed4386d64 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -72,7 +72,7 @@ servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } sparkle = { workspace = true } -style = { path = "../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } surfman = { workspace = true } webdriver_server = { path = "../webdriver_server", optional = true } diff --git a/components/servo_arc/Cargo.toml b/components/servo_arc/Cargo.toml deleted file mode 100644 index c975ad55412..00000000000 --- a/components/servo_arc/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "servo_arc" -version = "0.2.0" -authors = ["The Servo Project Developers"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/servo/servo" -description = "A fork of std::sync::Arc with some extra functionality and without weak references" - -[lib] -name = "servo_arc" -path = "lib.rs" - -[features] -gecko_refcount_logging = [] -servo = ["serde"] - -[dependencies] -nodrop = { version = "0.1.8" } -serde = { workspace = true, optional = true } -stable_deref_trait = "1.0.0" diff --git a/components/servo_arc/LICENSE-APACHE b/components/servo_arc/LICENSE-APACHE deleted file mode 100644 index 16fe87b06e8..00000000000 --- a/components/servo_arc/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/components/servo_arc/LICENSE-MIT b/components/servo_arc/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/components/servo_arc/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/components/servo_arc/lib.rs b/components/servo_arc/lib.rs deleted file mode 100644 index cc71827283a..00000000000 --- a/components/servo_arc/lib.rs +++ /dev/null @@ -1,1370 +0,0 @@ -// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! Fork of Arc for Servo. This has the following advantages over std::sync::Arc: -//! -//! * We don't waste storage on the weak reference count. -//! * We don't do extra RMU operations to handle the possibility of weak references. -//! * We can experiment with arena allocation (todo). -//! * We can add methods to support our custom use cases [1]. -//! * We have support for dynamically-sized types (see from_header_and_iter). -//! * We have support for thin arcs to unsized types (see ThinArc). -//! * We have support for references to static data, which don't do any -//! refcounting. -//! -//! [1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1360883 - -// The semantics of `Arc` are already documented in the Rust docs, so we don't -// duplicate those here. -#![allow(missing_docs)] - -#[cfg(feature = "servo")] -extern crate serde; -extern crate stable_deref_trait; - -#[cfg(feature = "servo")] -use serde::{Deserialize, Serialize}; -use stable_deref_trait::{CloneStableDeref, StableDeref}; -use std::alloc::{self, Layout}; -use std::borrow; -use std::cmp::Ordering; -use std::convert::From; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::iter::{ExactSizeIterator, Iterator}; -use std::marker::PhantomData; -use std::mem::{self, align_of, size_of}; -use std::ops::{Deref, DerefMut}; -use std::os::raw::c_void; -use std::process; -use std::ptr; -use std::slice; -use std::sync::atomic; -use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; -use std::{isize, usize}; - -/// A soft limit on the amount of references that may be made to an `Arc`. -/// -/// Going above this limit will abort your program (although not -/// necessarily) at _exactly_ `MAX_REFCOUNT + 1` references. -const MAX_REFCOUNT: usize = (isize::MAX) as usize; - -/// Special refcount value that means the data is not reference counted, -/// and that the `Arc` is really acting as a read-only static reference. -const STATIC_REFCOUNT: usize = usize::MAX; - -/// An atomically reference counted shared pointer -/// -/// See the documentation for [`Arc`] in the standard library. Unlike the -/// standard library `Arc`, this `Arc` does not support weak reference counting. -/// -/// See the discussion in https://github.com/rust-lang/rust/pull/60594 for the -/// usage of PhantomData. -/// -/// [`Arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html -/// -/// cbindgen:derive-eq=false -/// cbindgen:derive-neq=false -#[repr(C)] -pub struct Arc<T: ?Sized> { - p: ptr::NonNull<ArcInner<T>>, - phantom: PhantomData<T>, -} - -/// An `Arc` that is known to be uniquely owned -/// -/// When `Arc`s are constructed, they are known to be -/// uniquely owned. In such a case it is safe to mutate -/// the contents of the `Arc`. Normally, one would just handle -/// this by mutating the data on the stack before allocating the -/// `Arc`, however it's possible the data is large or unsized -/// and you need to heap-allocate it earlier in such a way -/// that it can be freely converted into a regular `Arc` once you're -/// done. -/// -/// `UniqueArc` exists for this purpose, when constructed it performs -/// the same allocations necessary for an `Arc`, however it allows mutable access. -/// Once the mutation is finished, you can call `.shareable()` and get a regular `Arc` -/// out of it. -/// -/// Ignore the doctest below there's no way to skip building with refcount -/// logging during doc tests (see rust-lang/rust#45599). -/// -/// ```rust,ignore -/// # use servo_arc::UniqueArc; -/// let data = [1, 2, 3, 4, 5]; -/// let mut x = UniqueArc::new(data); -/// x[4] = 7; // mutate! -/// let y = x.shareable(); // y is an Arc<T> -/// ``` -pub struct UniqueArc<T: ?Sized>(Arc<T>); - -impl<T> UniqueArc<T> { - #[inline] - /// Construct a new UniqueArc - pub fn new(data: T) -> Self { - UniqueArc(Arc::new(data)) - } - - /// Construct an uninitialized arc - #[inline] - pub fn new_uninit() -> UniqueArc<mem::MaybeUninit<T>> { - unsafe { - let layout = Layout::new::<ArcInner<mem::MaybeUninit<T>>>(); - let ptr = alloc::alloc(layout); - let mut p = ptr::NonNull::new(ptr) - .unwrap_or_else(|| alloc::handle_alloc_error(layout)) - .cast::<ArcInner<mem::MaybeUninit<T>>>(); - ptr::write(&mut p.as_mut().count, atomic::AtomicUsize::new(1)); - - #[cfg(feature = "gecko_refcount_logging")] - { - NS_LogCtor(p.as_ptr() as *mut _, b"ServoArc\0".as_ptr() as *const _, 8) - } - - UniqueArc(Arc { - p, - phantom: PhantomData, - }) - } - } - - #[inline] - /// Convert to a shareable Arc<T> once we're done mutating it - pub fn shareable(self) -> Arc<T> { - self.0 - } -} - -impl<T> UniqueArc<mem::MaybeUninit<T>> { - /// Convert to an initialized Arc. - #[inline] - pub unsafe fn assume_init(this: Self) -> UniqueArc<T> { - UniqueArc(Arc { - p: mem::ManuallyDrop::new(this).0.p.cast(), - phantom: PhantomData, - }) - } -} - -impl<T> Deref for UniqueArc<T> { - type Target = T; - fn deref(&self) -> &T { - &*self.0 - } -} - -impl<T> DerefMut for UniqueArc<T> { - fn deref_mut(&mut self) -> &mut T { - // We know this to be uniquely owned - unsafe { &mut (*self.0.ptr()).data } - } -} - -unsafe impl<T: ?Sized + Sync + Send> Send for Arc<T> {} -unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {} - -/// The object allocated by an Arc<T> -#[repr(C)] -struct ArcInner<T: ?Sized> { - count: atomic::AtomicUsize, - data: T, -} - -unsafe impl<T: ?Sized + Sync + Send> Send for ArcInner<T> {} -unsafe impl<T: ?Sized + Sync + Send> Sync for ArcInner<T> {} - -/// Computes the offset of the data field within ArcInner. -fn data_offset<T>() -> usize { - let size = size_of::<ArcInner<()>>(); - let align = align_of::<T>(); - // https://github.com/rust-lang/rust/blob/1.36.0/src/libcore/alloc.rs#L187-L207 - size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1) -} - -impl<T> Arc<T> { - /// Construct an `Arc<T>` - #[inline] - pub fn new(data: T) -> Self { - let ptr = Box::into_raw(Box::new(ArcInner { - count: atomic::AtomicUsize::new(1), - data, - })); - - #[cfg(feature = "gecko_refcount_logging")] - unsafe { - // FIXME(emilio): Would be so amazing to have - // std::intrinsics::type_name() around, so that we could also report - // a real size. - NS_LogCtor(ptr as *mut _, b"ServoArc\0".as_ptr() as *const _, 8); - } - - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - } - - /// Construct an intentionally-leaked arc. - #[inline] - pub fn new_leaked(data: T) -> Self { - let arc = Self::new(data); - arc.mark_as_intentionally_leaked(); - arc - } - - /// Convert the Arc<T> to a raw pointer, suitable for use across FFI - /// - /// Note: This returns a pointer to the data T, which is offset in the allocation. - #[inline] - pub fn into_raw(this: Self) -> *const T { - let ptr = unsafe { &((*this.ptr()).data) as *const _ }; - mem::forget(this); - ptr - } - - /// Reconstruct the Arc<T> from a raw pointer obtained from into_raw() - /// - /// Note: This raw pointer will be offset in the allocation and must be preceded - /// by the atomic count. - #[inline] - pub unsafe fn from_raw(ptr: *const T) -> Self { - // To find the corresponding pointer to the `ArcInner` we need - // to subtract the offset of the `data` field from the pointer. - let ptr = (ptr as *const u8).sub(data_offset::<T>()); - Arc { - p: ptr::NonNull::new_unchecked(ptr as *mut ArcInner<T>), - phantom: PhantomData, - } - } - - /// Like from_raw, but returns an addrefed arc instead. - #[inline] - pub unsafe fn from_raw_addrefed(ptr: *const T) -> Self { - let arc = Self::from_raw(ptr); - mem::forget(arc.clone()); - arc - } - - /// Create a new static Arc<T> (one that won't reference count the object) - /// and place it in the allocation provided by the specified `alloc` - /// function. - /// - /// `alloc` must return a pointer into a static allocation suitable for - /// storing data with the `Layout` passed into it. The pointer returned by - /// `alloc` will not be freed. - #[inline] - pub unsafe fn new_static<F>(alloc: F, data: T) -> Arc<T> - where - F: FnOnce(Layout) -> *mut u8, - { - let ptr = alloc(Layout::new::<ArcInner<T>>()) as *mut ArcInner<T>; - - let x = ArcInner { - count: atomic::AtomicUsize::new(STATIC_REFCOUNT), - data, - }; - - ptr::write(ptr, x); - - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - - /// Produce a pointer to the data that can be converted back - /// to an Arc. This is basically an `&Arc<T>`, without the extra indirection. - /// It has the benefits of an `&T` but also knows about the underlying refcount - /// and can be converted into more `Arc<T>`s if necessary. - #[inline] - pub fn borrow_arc<'a>(&'a self) -> ArcBorrow<'a, T> { - ArcBorrow(&**self) - } - - /// Returns the address on the heap of the Arc itself -- not the T within it -- for memory - /// reporting. - /// - /// If this is a static reference, this returns null. - pub fn heap_ptr(&self) -> *const c_void { - if self.inner().count.load(Relaxed) == STATIC_REFCOUNT { - ptr::null() - } else { - self.p.as_ptr() as *const ArcInner<T> as *const c_void - } - } -} - -impl<T: ?Sized> Arc<T> { - #[inline] - fn inner(&self) -> &ArcInner<T> { - // This unsafety is ok because while this arc is alive we're guaranteed - // that the inner pointer is valid. Furthermore, we know that the - // `ArcInner` structure itself is `Sync` because the inner data is - // `Sync` as well, so we're ok loaning out an immutable pointer to these - // contents. - unsafe { &*self.ptr() } - } - - #[inline(always)] - fn record_drop(&self) { - #[cfg(feature = "gecko_refcount_logging")] - unsafe { - NS_LogDtor(self.ptr() as *mut _, b"ServoArc\0".as_ptr() as *const _, 8); - } - } - - /// Marks this `Arc` as intentionally leaked for the purposes of refcount - /// logging. - /// - /// It's a logic error to call this more than once, but it's not unsafe, as - /// it'd just report negative leaks. - #[inline(always)] - pub fn mark_as_intentionally_leaked(&self) { - self.record_drop(); - } - - // Non-inlined part of `drop`. Just invokes the destructor and calls the - // refcount logging machinery if enabled. - #[inline(never)] - unsafe fn drop_slow(&mut self) { - self.record_drop(); - let _ = Box::from_raw(self.ptr()); - } - - /// Test pointer equality between the two Arcs, i.e. they must be the _same_ - /// allocation - #[inline] - pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.ptr() == other.ptr() - } - - fn ptr(&self) -> *mut ArcInner<T> { - self.p.as_ptr() - } -} - -#[cfg(feature = "gecko_refcount_logging")] -extern "C" { - fn NS_LogCtor( - aPtr: *mut std::os::raw::c_void, - aTypeName: *const std::os::raw::c_char, - aSize: u32, - ); - fn NS_LogDtor( - aPtr: *mut std::os::raw::c_void, - aTypeName: *const std::os::raw::c_char, - aSize: u32, - ); -} - -impl<T: ?Sized> Clone for Arc<T> { - #[inline] - fn clone(&self) -> Self { - // NOTE(emilio): If you change anything here, make sure that the - // implementation in layout/style/ServoStyleConstsInlines.h matches! - // - // Using a relaxed ordering to check for STATIC_REFCOUNT is safe, since - // `count` never changes between STATIC_REFCOUNT and other values. - if self.inner().count.load(Relaxed) != STATIC_REFCOUNT { - // Using a relaxed ordering is alright here, as knowledge of the - // original reference prevents other threads from erroneously deleting - // the object. - // - // As explained in the [Boost documentation][1], Increasing the - // reference counter can always be done with memory_order_relaxed: New - // references to an object can only be formed from an existing - // reference, and passing an existing reference from one thread to - // another must already provide any required synchronization. - // - // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) - let old_size = self.inner().count.fetch_add(1, Relaxed); - - // However we need to guard against massive refcounts in case someone - // is `mem::forget`ing Arcs. If we don't do this the count can overflow - // and users will use-after free. We racily saturate to `isize::MAX` on - // the assumption that there aren't ~2 billion threads incrementing - // the reference count at once. This branch will never be taken in - // any realistic program. - // - // We abort because such a program is incredibly degenerate, and we - // don't care to support it. - if old_size > MAX_REFCOUNT { - process::abort(); - } - } - - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(self.ptr()), - phantom: PhantomData, - } - } - } -} - -impl<T: ?Sized> Deref for Arc<T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - &self.inner().data - } -} - -impl<T: Clone> Arc<T> { - /// Makes a mutable reference to the `Arc`, cloning if necessary - /// - /// This is functionally equivalent to [`Arc::make_mut`][mm] from the standard library. - /// - /// If this `Arc` is uniquely owned, `make_mut()` will provide a mutable - /// reference to the contents. If not, `make_mut()` will create a _new_ `Arc` - /// with a copy of the contents, update `this` to point to it, and provide - /// a mutable reference to its contents. - /// - /// This is useful for implementing copy-on-write schemes where you wish to - /// avoid copying things if your `Arc` is not shared. - /// - /// [mm]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.make_mut - #[inline] - pub fn make_mut(this: &mut Self) -> &mut T { - if !this.is_unique() { - // Another pointer exists; clone - *this = Arc::new((**this).clone()); - } - - unsafe { - // This unsafety is ok because we're guaranteed that the pointer - // returned is the *only* pointer that will ever be returned to T. Our - // reference count is guaranteed to be 1 at this point, and we required - // the Arc itself to be `mut`, so we're returning the only possible - // reference to the inner data. - &mut (*this.ptr()).data - } - } -} - -impl<T: ?Sized> Arc<T> { - /// Provides mutable access to the contents _if_ the `Arc` is uniquely owned. - #[inline] - pub fn get_mut(this: &mut Self) -> Option<&mut T> { - if this.is_unique() { - unsafe { - // See make_mut() for documentation of the threadsafety here. - Some(&mut (*this.ptr()).data) - } - } else { - None - } - } - - /// Whether or not the `Arc` is a static reference. - #[inline] - pub fn is_static(&self) -> bool { - // Using a relaxed ordering to check for STATIC_REFCOUNT is safe, since - // `count` never changes between STATIC_REFCOUNT and other values. - self.inner().count.load(Relaxed) == STATIC_REFCOUNT - } - - /// Whether or not the `Arc` is uniquely owned (is the refcount 1?) and not - /// a static reference. - #[inline] - pub fn is_unique(&self) -> bool { - // See the extensive discussion in [1] for why this needs to be Acquire. - // - // [1] https://github.com/servo/servo/issues/21186 - self.inner().count.load(Acquire) == 1 - } -} - -impl<T: ?Sized> Drop for Arc<T> { - #[inline] - fn drop(&mut self) { - // NOTE(emilio): If you change anything here, make sure that the - // implementation in layout/style/ServoStyleConstsInlines.h matches! - if self.is_static() { - return; - } - - // Because `fetch_sub` is already atomic, we do not need to synchronize - // with other threads unless we are going to delete the object. - if self.inner().count.fetch_sub(1, Release) != 1 { - return; - } - - // FIXME(bholley): Use the updated comment when [2] is merged. - // - // This load is needed to prevent reordering of use of the data and - // deletion of the data. Because it is marked `Release`, the decreasing - // of the reference count synchronizes with this `Acquire` load. This - // means that use of the data happens before decreasing the reference - // count, which happens before this load, which happens before the - // deletion of the data. - // - // As explained in the [Boost documentation][1], - // - // > It is important to enforce any possible access to the object in one - // > thread (through an existing reference) to *happen before* deleting - // > the object in a different thread. This is achieved by a "release" - // > operation after dropping a reference (any access to the object - // > through this reference must obviously happened before), and an - // > "acquire" operation before deleting the object. - // - // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) - // [2]: https://github.com/rust-lang/rust/pull/41714 - self.inner().count.load(Acquire); - - unsafe { - self.drop_slow(); - } - } -} - -impl<T: ?Sized + PartialEq> PartialEq for Arc<T> { - fn eq(&self, other: &Arc<T>) -> bool { - Self::ptr_eq(self, other) || *(*self) == *(*other) - } - - fn ne(&self, other: &Arc<T>) -> bool { - !Self::ptr_eq(self, other) && *(*self) != *(*other) - } -} - -impl<T: ?Sized + PartialOrd> PartialOrd for Arc<T> { - fn partial_cmp(&self, other: &Arc<T>) -> Option<Ordering> { - (**self).partial_cmp(&**other) - } - - fn lt(&self, other: &Arc<T>) -> bool { - *(*self) < *(*other) - } - - fn le(&self, other: &Arc<T>) -> bool { - *(*self) <= *(*other) - } - - fn gt(&self, other: &Arc<T>) -> bool { - *(*self) > *(*other) - } - - fn ge(&self, other: &Arc<T>) -> bool { - *(*self) >= *(*other) - } -} -impl<T: ?Sized + Ord> Ord for Arc<T> { - fn cmp(&self, other: &Arc<T>) -> Ordering { - (**self).cmp(&**other) - } -} -impl<T: ?Sized + Eq> Eq for Arc<T> {} - -impl<T: ?Sized + fmt::Display> fmt::Display for Arc<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} - -impl<T: ?Sized + fmt::Debug> fmt::Debug for Arc<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl<T: ?Sized> fmt::Pointer for Arc<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Pointer::fmt(&self.ptr(), f) - } -} - -impl<T: Default> Default for Arc<T> { - fn default() -> Arc<T> { - Arc::new(Default::default()) - } -} - -impl<T: ?Sized + Hash> Hash for Arc<T> { - fn hash<H: Hasher>(&self, state: &mut H) { - (**self).hash(state) - } -} - -impl<T> From<T> for Arc<T> { - #[inline] - fn from(t: T) -> Self { - Arc::new(t) - } -} - -impl<T: ?Sized> borrow::Borrow<T> for Arc<T> { - #[inline] - fn borrow(&self) -> &T { - &**self - } -} - -impl<T: ?Sized> AsRef<T> for Arc<T> { - #[inline] - fn as_ref(&self) -> &T { - &**self - } -} - -unsafe impl<T: ?Sized> StableDeref for Arc<T> {} -unsafe impl<T: ?Sized> CloneStableDeref for Arc<T> {} - -#[cfg(feature = "servo")] -impl<'de, T: Deserialize<'de>> Deserialize<'de> for Arc<T> { - fn deserialize<D>(deserializer: D) -> Result<Arc<T>, D::Error> - where - D: ::serde::de::Deserializer<'de>, - { - T::deserialize(deserializer).map(Arc::new) - } -} - -#[cfg(feature = "servo")] -impl<T: Serialize> Serialize for Arc<T> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: ::serde::ser::Serializer, - { - (**self).serialize(serializer) - } -} - -/// Structure to allow Arc-managing some fixed-sized data and a variably-sized -/// slice in a single allocation. -#[derive(Debug, Eq, PartialEq, PartialOrd)] -#[repr(C)] -pub struct HeaderSlice<H, T: ?Sized> { - /// The fixed-sized data. - pub header: H, - - /// The dynamically-sized data. - pub slice: T, -} - -#[inline(always)] -fn divide_rounding_up(dividend: usize, divisor: usize) -> usize { - (dividend + divisor - 1) / divisor -} - -impl<H, T> Arc<HeaderSlice<H, [T]>> { - /// Creates an Arc for a HeaderSlice using the given header struct and - /// iterator to generate the slice. - /// - /// `is_static` indicates whether to create a static Arc. - /// - /// `alloc` is used to get a pointer to the memory into which the - /// dynamically sized ArcInner<HeaderSlice<H, T>> value will be - /// written. If `is_static` is true, then `alloc` must return a - /// pointer into some static memory allocation. If it is false, - /// then `alloc` must return an allocation that can be dellocated - /// by calling Box::from_raw::<ArcInner<HeaderSlice<H, T>>> on it. - #[inline] - fn from_header_and_iter_alloc<F, I>( - alloc: F, - header: H, - mut items: I, - num_items: usize, - is_static: bool, - ) -> Self - where - F: FnOnce(Layout) -> *mut u8, - I: Iterator<Item = T>, - { - assert_ne!(size_of::<T>(), 0, "Need to think about ZST"); - - let inner_align = align_of::<ArcInner<HeaderSlice<H, [T; 0]>>>(); - debug_assert!(inner_align >= align_of::<T>()); - - // Compute the required size for the allocation. - let size = { - // Next, synthesize a totally garbage (but properly aligned) pointer - // to a sequence of T. - let fake_slice_ptr = inner_align as *const T; - - // Convert that sequence to a fat pointer. The address component of - // the fat pointer will be garbage, but the length will be correct. - let fake_slice = unsafe { slice::from_raw_parts(fake_slice_ptr, num_items) }; - - // Pretend the garbage address points to our allocation target (with - // a trailing sequence of T), rather than just a sequence of T. - let fake_ptr = fake_slice as *const [T] as *const ArcInner<HeaderSlice<H, [T]>>; - let fake_ref: &ArcInner<HeaderSlice<H, [T]>> = unsafe { &*fake_ptr }; - - // Use size_of_val, which will combine static information about the - // type with the length from the fat pointer. The garbage address - // will not be used. - mem::size_of_val(fake_ref) - }; - - let ptr: *mut ArcInner<HeaderSlice<H, [T]>>; - unsafe { - // Allocate the buffer. - let layout = if inner_align <= align_of::<usize>() { - Layout::from_size_align_unchecked(size, align_of::<usize>()) - } else if inner_align <= align_of::<u64>() { - // On 32-bit platforms <T> may have 8 byte alignment while usize - // has 4 byte aligment. Use u64 to avoid over-alignment. - // This branch will compile away in optimized builds. - Layout::from_size_align_unchecked(size, align_of::<u64>()) - } else { - panic!("Over-aligned type not handled"); - }; - - let buffer = alloc(layout); - - // Synthesize the fat pointer. We do this by claiming we have a direct - // pointer to a [T], and then changing the type of the borrow. The key - // point here is that the length portion of the fat pointer applies - // only to the number of elements in the dynamically-sized portion of - // the type, so the value will be the same whether it points to a [T] - // or something else with a [T] as its last member. - let fake_slice: &mut [T] = slice::from_raw_parts_mut(buffer as *mut T, num_items); - ptr = fake_slice as *mut [T] as *mut ArcInner<HeaderSlice<H, [T]>>; - - // Write the data. - // - // Note that any panics here (i.e. from the iterator) are safe, since - // we'll just leak the uninitialized memory. - let count = if is_static { - atomic::AtomicUsize::new(STATIC_REFCOUNT) - } else { - atomic::AtomicUsize::new(1) - }; - ptr::write(&mut ((*ptr).count), count); - ptr::write(&mut ((*ptr).data.header), header); - if num_items != 0 { - let mut current: *mut T = &mut (*ptr).data.slice[0]; - for _ in 0..num_items { - ptr::write( - current, - items - .next() - .expect("ExactSizeIterator over-reported length"), - ); - current = current.offset(1); - } - // We should have consumed the buffer exactly, maybe accounting - // for some padding from the alignment. - debug_assert!( - (buffer.add(size) as usize - current as *mut u8 as usize) < inner_align - ); - } - assert!( - items.next().is_none(), - "ExactSizeIterator under-reported length" - ); - } - #[cfg(feature = "gecko_refcount_logging")] - unsafe { - if !is_static { - // FIXME(emilio): Would be so amazing to have - // std::intrinsics::type_name() around. - NS_LogCtor(ptr as *mut _, b"ServoArc\0".as_ptr() as *const _, 8) - } - } - - // Return the fat Arc. - assert_eq!( - size_of::<Self>(), - size_of::<usize>() * 2, - "The Arc will be fat" - ); - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - } - - /// Creates an Arc for a HeaderSlice using the given header struct and iterator to generate the - /// slice. Panics if num_items doesn't match the number of items. - #[inline] - pub fn from_header_and_iter_with_size<I>(header: H, items: I, num_items: usize) -> Self - where - I: Iterator<Item = T>, - { - Arc::from_header_and_iter_alloc( - |layout| { - // align will only ever be align_of::<usize>() or align_of::<u64>() - let align = layout.align(); - unsafe { - if align == mem::align_of::<usize>() { - Self::allocate_buffer::<usize>(layout.size()) - } else { - assert_eq!(align, mem::align_of::<u64>()); - Self::allocate_buffer::<u64>(layout.size()) - } - } - }, - header, - items, - num_items, - /* is_static = */ false, - ) - } - - /// Creates an Arc for a HeaderSlice using the given header struct and - /// iterator to generate the slice. The resulting Arc will be fat. - #[inline] - pub fn from_header_and_iter<I>(header: H, items: I) -> Self - where - I: Iterator<Item = T> + ExactSizeIterator, - { - let len = items.len(); - Self::from_header_and_iter_with_size(header, items, len) - } - - #[inline] - unsafe fn allocate_buffer<W>(size: usize) -> *mut u8 { - // We use Vec because the underlying allocation machinery isn't - // available in stable Rust. To avoid alignment issues, we allocate - // words rather than bytes, rounding up to the nearest word size. - let words_to_allocate = divide_rounding_up(size, mem::size_of::<W>()); - let mut vec = Vec::<W>::with_capacity(words_to_allocate); - vec.set_len(words_to_allocate); - Box::into_raw(vec.into_boxed_slice()) as *mut W as *mut u8 - } -} - -/// Header data with an inline length. Consumers that use HeaderWithLength as the -/// Header type in HeaderSlice can take advantage of ThinArc. -#[derive(Debug, Eq, PartialEq, PartialOrd)] -#[repr(C)] -pub struct HeaderWithLength<H> { - /// The fixed-sized data. - pub header: H, - - /// The slice length. - length: usize, -} - -impl<H> HeaderWithLength<H> { - /// Creates a new HeaderWithLength. - pub fn new(header: H, length: usize) -> Self { - HeaderWithLength { header, length } - } -} - -type HeaderSliceWithLength<H, T> = HeaderSlice<HeaderWithLength<H>, T>; - -/// A "thin" `Arc` containing dynamically sized data -/// -/// This is functionally equivalent to Arc<(H, [T])> -/// -/// When you create an `Arc` containing a dynamically sized type -/// like `HeaderSlice<H, [T]>`, the `Arc` is represented on the stack -/// as a "fat pointer", where the length of the slice is stored -/// alongside the `Arc`'s pointer. In some situations you may wish to -/// have a thin pointer instead, perhaps for FFI compatibility -/// or space efficiency. -/// -/// Note that we use `[T; 0]` in order to have the right alignment for `T`. -/// -/// `ThinArc` solves this by storing the length in the allocation itself, -/// via `HeaderSliceWithLength`. -#[repr(C)] -pub struct ThinArc<H, T> { - ptr: ptr::NonNull<ArcInner<HeaderSliceWithLength<H, [T; 0]>>>, - phantom: PhantomData<(H, T)>, -} - -impl<H: fmt::Debug, T: fmt::Debug> fmt::Debug for ThinArc<H, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.deref(), f) - } -} - -unsafe impl<H: Sync + Send, T: Sync + Send> Send for ThinArc<H, T> {} -unsafe impl<H: Sync + Send, T: Sync + Send> Sync for ThinArc<H, T> {} - -// Synthesize a fat pointer from a thin pointer. -// -// See the comment around the analogous operation in from_header_and_iter. -fn thin_to_thick<H, T>( - thin: *mut ArcInner<HeaderSliceWithLength<H, [T; 0]>>, -) -> *mut ArcInner<HeaderSliceWithLength<H, [T]>> { - let len = unsafe { (*thin).data.header.length }; - let fake_slice: *mut [T] = unsafe { slice::from_raw_parts_mut(thin as *mut T, len) }; - - fake_slice as *mut ArcInner<HeaderSliceWithLength<H, [T]>> -} - -impl<H, T> ThinArc<H, T> { - /// Temporarily converts |self| into a bonafide Arc and exposes it to the - /// provided callback. The refcount is not modified. - #[inline] - pub fn with_arc<F, U>(&self, f: F) -> U - where - F: FnOnce(&Arc<HeaderSliceWithLength<H, [T]>>) -> U, - { - // Synthesize transient Arc, which never touches the refcount of the ArcInner. - let transient = unsafe { - mem::ManuallyDrop::new(Arc { - p: ptr::NonNull::new_unchecked(thin_to_thick(self.ptr.as_ptr())), - phantom: PhantomData, - }) - }; - - // Expose the transient Arc to the callback, which may clone it if it wants. - let result = f(&transient); - - // Forward the result. - result - } - - /// Creates a `ThinArc` for a HeaderSlice using the given header struct and - /// iterator to generate the slice. - pub fn from_header_and_iter<I>(header: H, items: I) -> Self - where - I: Iterator<Item = T> + ExactSizeIterator, - { - let header = HeaderWithLength::new(header, items.len()); - Arc::into_thin(Arc::from_header_and_iter(header, items)) - } - - /// Create a static `ThinArc` for a HeaderSlice using the given header - /// struct and iterator to generate the slice, placing it in the allocation - /// provided by the specified `alloc` function. - /// - /// `alloc` must return a pointer into a static allocation suitable for - /// storing data with the `Layout` passed into it. The pointer returned by - /// `alloc` will not be freed. - pub unsafe fn static_from_header_and_iter<F, I>(alloc: F, header: H, items: I) -> Self - where - F: FnOnce(Layout) -> *mut u8, - I: Iterator<Item = T> + ExactSizeIterator, - { - let len = items.len(); - let header = HeaderWithLength::new(header, len); - Arc::into_thin(Arc::from_header_and_iter_alloc( - alloc, header, items, len, /* is_static = */ true, - )) - } - - /// Returns the address on the heap of the ThinArc itself -- not the T - /// within it -- for memory reporting, and bindings. - #[inline] - pub fn ptr(&self) -> *const c_void { - self.ptr.as_ptr() as *const ArcInner<T> as *const c_void - } - - /// If this is a static ThinArc, this returns null. - #[inline] - pub fn heap_ptr(&self) -> *const c_void { - let is_static = - ThinArc::with_arc(self, |a| a.inner().count.load(Relaxed) == STATIC_REFCOUNT); - if is_static { - ptr::null() - } else { - self.ptr() - } - } -} - -impl<H, T> Deref for ThinArc<H, T> { - type Target = HeaderSliceWithLength<H, [T]>; - - #[inline] - fn deref(&self) -> &Self::Target { - unsafe { &(*thin_to_thick(self.ptr.as_ptr())).data } - } -} - -impl<H, T> Clone for ThinArc<H, T> { - #[inline] - fn clone(&self) -> Self { - ThinArc::with_arc(self, |a| Arc::into_thin(a.clone())) - } -} - -impl<H, T> Drop for ThinArc<H, T> { - #[inline] - fn drop(&mut self) { - let _ = Arc::from_thin(ThinArc { - ptr: self.ptr, - phantom: PhantomData, - }); - } -} - -impl<H, T> Arc<HeaderSliceWithLength<H, [T]>> { - /// Converts an `Arc` into a `ThinArc`. This consumes the `Arc`, so the refcount - /// is not modified. - #[inline] - pub fn into_thin(a: Self) -> ThinArc<H, T> { - assert_eq!( - a.header.length, - a.slice.len(), - "Length needs to be correct for ThinArc to work" - ); - let fat_ptr: *mut ArcInner<HeaderSliceWithLength<H, [T]>> = a.ptr(); - mem::forget(a); - let thin_ptr = fat_ptr as *mut [usize] as *mut usize; - ThinArc { - ptr: unsafe { - ptr::NonNull::new_unchecked( - thin_ptr as *mut ArcInner<HeaderSliceWithLength<H, [T; 0]>>, - ) - }, - phantom: PhantomData, - } - } - - /// Converts a `ThinArc` into an `Arc`. This consumes the `ThinArc`, so the refcount - /// is not modified. - #[inline] - pub fn from_thin(a: ThinArc<H, T>) -> Self { - let ptr = thin_to_thick(a.ptr.as_ptr()); - mem::forget(a); - unsafe { - Arc { - p: ptr::NonNull::new_unchecked(ptr), - phantom: PhantomData, - } - } - } -} - -impl<H, T> UniqueArc<HeaderSliceWithLength<H, [T]>> { - #[inline] - pub fn from_header_and_iter<I>(header: HeaderWithLength<H>, items: I) -> Self - where - I: Iterator<Item = T> + ExactSizeIterator, - { - Self(Arc::from_header_and_iter(header, items)) - } - - #[inline] - pub fn from_header_and_iter_with_size<I>( - header: HeaderWithLength<H>, - items: I, - num_items: usize, - ) -> Self - where - I: Iterator<Item = T>, - { - Self(Arc::from_header_and_iter_with_size( - header, items, num_items, - )) - } - - /// Returns a mutable reference to the header. - pub fn header_mut(&mut self) -> &mut H { - // We know this to be uniquely owned - unsafe { &mut (*self.0.ptr()).data.header.header } - } - - /// Returns a mutable reference to the slice. - pub fn data_mut(&mut self) -> &mut [T] { - // We know this to be uniquely owned - unsafe { &mut (*self.0.ptr()).data.slice } - } - - pub fn shareable_thin(self) -> ThinArc<H, T> { - Arc::into_thin(self.0) - } -} - -impl<H: PartialEq, T: PartialEq> PartialEq for ThinArc<H, T> { - #[inline] - fn eq(&self, other: &ThinArc<H, T>) -> bool { - ThinArc::with_arc(self, |a| ThinArc::with_arc(other, |b| *a == *b)) - } -} - -impl<H: Eq, T: Eq> Eq for ThinArc<H, T> {} - -/// A "borrowed `Arc`". This is a pointer to -/// a T that is known to have been allocated within an -/// `Arc`. -/// -/// This is equivalent in guarantees to `&Arc<T>`, however it is -/// a bit more flexible. To obtain an `&Arc<T>` you must have -/// an `Arc<T>` instance somewhere pinned down until we're done with it. -/// It's also a direct pointer to `T`, so using this involves less pointer-chasing -/// -/// However, C++ code may hand us refcounted things as pointers to T directly, -/// so we have to conjure up a temporary `Arc` on the stack each time. -/// -/// `ArcBorrow` lets us deal with borrows of known-refcounted objects -/// without needing to worry about where the `Arc<T>` is. -#[derive(Debug, Eq, PartialEq)] -pub struct ArcBorrow<'a, T: 'a>(&'a T); - -impl<'a, T> Copy for ArcBorrow<'a, T> {} -impl<'a, T> Clone for ArcBorrow<'a, T> { - #[inline] - fn clone(&self) -> Self { - *self - } -} - -impl<'a, T> ArcBorrow<'a, T> { - /// Clone this as an `Arc<T>`. This bumps the refcount. - #[inline] - pub fn clone_arc(&self) -> Arc<T> { - let arc = unsafe { Arc::from_raw(self.0) }; - // addref it! - mem::forget(arc.clone()); - arc - } - - /// For constructing from a reference known to be Arc-backed, - /// e.g. if we obtain such a reference over FFI - #[inline] - pub unsafe fn from_ref(r: &'a T) -> Self { - ArcBorrow(r) - } - - /// Compare two `ArcBorrow`s via pointer equality. Will only return - /// true if they come from the same allocation - pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.0 as *const T == other.0 as *const T - } - - /// Temporarily converts |self| into a bonafide Arc and exposes it to the - /// provided callback. The refcount is not modified. - #[inline] - pub fn with_arc<F, U>(&self, f: F) -> U - where - F: FnOnce(&Arc<T>) -> U, - T: 'static, - { - // Synthesize transient Arc, which never touches the refcount. - let transient = unsafe { mem::ManuallyDrop::new(Arc::from_raw(self.0)) }; - - // Expose the transient Arc to the callback, which may clone it if it wants. - let result = f(&transient); - - // Forward the result. - result - } - - /// Similar to deref, but uses the lifetime |a| rather than the lifetime of - /// self, which is incompatible with the signature of the Deref trait. - #[inline] - pub fn get(&self) -> &'a T { - self.0 - } -} - -impl<'a, T> Deref for ArcBorrow<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - self.0 - } -} - -/// A tagged union that can represent `Arc<A>` or `Arc<B>` while only consuming a -/// single word. The type is also `NonNull`, and thus can be stored in an Option -/// without increasing size. -/// -/// This is functionally equivalent to -/// `enum ArcUnion<A, B> { First(Arc<A>), Second(Arc<B>)` but only takes up -/// up a single word of stack space. -/// -/// This could probably be extended to support four types if necessary. -pub struct ArcUnion<A, B> { - p: ptr::NonNull<()>, - phantom_a: PhantomData<A>, - phantom_b: PhantomData<B>, -} - -unsafe impl<A: Sync + Send, B: Send + Sync> Send for ArcUnion<A, B> {} -unsafe impl<A: Sync + Send, B: Send + Sync> Sync for ArcUnion<A, B> {} - -impl<A: PartialEq, B: PartialEq> PartialEq for ArcUnion<A, B> { - fn eq(&self, other: &Self) -> bool { - use crate::ArcUnionBorrow::*; - match (self.borrow(), other.borrow()) { - (First(x), First(y)) => x == y, - (Second(x), Second(y)) => x == y, - (_, _) => false, - } - } -} - -/// This represents a borrow of an `ArcUnion`. -#[derive(Debug)] -pub enum ArcUnionBorrow<'a, A: 'a, B: 'a> { - First(ArcBorrow<'a, A>), - Second(ArcBorrow<'a, B>), -} - -impl<A, B> ArcUnion<A, B> { - unsafe fn new(ptr: *mut ()) -> Self { - ArcUnion { - p: ptr::NonNull::new_unchecked(ptr), - phantom_a: PhantomData, - phantom_b: PhantomData, - } - } - - /// Returns true if the two values are pointer-equal. - #[inline] - pub fn ptr_eq(this: &Self, other: &Self) -> bool { - this.p == other.p - } - - #[inline] - pub fn ptr(&self) -> ptr::NonNull<()> { - self.p - } - - /// Returns an enum representing a borrow of either A or B. - #[inline] - pub fn borrow(&self) -> ArcUnionBorrow<A, B> { - if self.is_first() { - let ptr = self.p.as_ptr() as *const A; - let borrow = unsafe { ArcBorrow::from_ref(&*ptr) }; - ArcUnionBorrow::First(borrow) - } else { - let ptr = ((self.p.as_ptr() as usize) & !0x1) as *const B; - let borrow = unsafe { ArcBorrow::from_ref(&*ptr) }; - ArcUnionBorrow::Second(borrow) - } - } - - /// Creates an `ArcUnion` from an instance of the first type. - pub fn from_first(other: Arc<A>) -> Self { - unsafe { Self::new(Arc::into_raw(other) as *mut _) } - } - - /// Creates an `ArcUnion` from an instance of the second type. - pub fn from_second(other: Arc<B>) -> Self { - unsafe { Self::new(((Arc::into_raw(other) as usize) | 0x1) as *mut _) } - } - - /// Returns true if this `ArcUnion` contains the first type. - pub fn is_first(&self) -> bool { - self.p.as_ptr() as usize & 0x1 == 0 - } - - /// Returns true if this `ArcUnion` contains the second type. - pub fn is_second(&self) -> bool { - !self.is_first() - } - - /// Returns a borrow of the first type if applicable, otherwise `None`. - pub fn as_first(&self) -> Option<ArcBorrow<A>> { - match self.borrow() { - ArcUnionBorrow::First(x) => Some(x), - ArcUnionBorrow::Second(_) => None, - } - } - - /// Returns a borrow of the second type if applicable, otherwise None. - pub fn as_second(&self) -> Option<ArcBorrow<B>> { - match self.borrow() { - ArcUnionBorrow::First(_) => None, - ArcUnionBorrow::Second(x) => Some(x), - } - } -} - -impl<A, B> Clone for ArcUnion<A, B> { - fn clone(&self) -> Self { - match self.borrow() { - ArcUnionBorrow::First(x) => ArcUnion::from_first(x.clone_arc()), - ArcUnionBorrow::Second(x) => ArcUnion::from_second(x.clone_arc()), - } - } -} - -impl<A, B> Drop for ArcUnion<A, B> { - fn drop(&mut self) { - match self.borrow() { - ArcUnionBorrow::First(x) => unsafe { - let _ = Arc::from_raw(&*x); - }, - ArcUnionBorrow::Second(x) => unsafe { - let _ = Arc::from_raw(&*x); - }, - } - } -} - -impl<A: fmt::Debug, B: fmt::Debug> fmt::Debug for ArcUnion<A, B> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.borrow(), f) - } -} - -#[cfg(test)] -mod tests { - use super::{Arc, HeaderWithLength, ThinArc}; - use std::clone::Clone; - use std::ops::Drop; - use std::sync::atomic; - use std::sync::atomic::Ordering::{Acquire, SeqCst}; - - #[derive(PartialEq)] - struct Canary(*mut atomic::AtomicUsize); - - impl Drop for Canary { - fn drop(&mut self) { - unsafe { - (*self.0).fetch_add(1, SeqCst); - } - } - } - - #[test] - fn empty_thin() { - let header = HeaderWithLength::new(100u32, 0); - let x = Arc::from_header_and_iter(header, std::iter::empty::<i32>()); - let y = Arc::into_thin(x.clone()); - assert_eq!(y.header.header, 100); - assert!(y.slice.is_empty()); - assert_eq!(x.header.header, 100); - assert!(x.slice.is_empty()); - } - - #[test] - fn thin_assert_padding() { - #[derive(Clone, Default)] - #[repr(C)] - struct Padded { - i: u16, - } - - // The header will have more alignment than `Padded` - let header = HeaderWithLength::new(0i32, 2); - let items = vec![Padded { i: 0xdead }, Padded { i: 0xbeef }]; - let a = ThinArc::from_header_and_iter(header, items.into_iter()); - assert_eq!(a.slice.len(), 2); - assert_eq!(a.slice[0].i, 0xdead); - assert_eq!(a.slice[1].i, 0xbeef); - } - - #[test] - fn slices_and_thin() { - let mut canary = atomic::AtomicUsize::new(0); - let c = Canary(&mut canary as *mut atomic::AtomicUsize); - let v = vec![5, 6]; - let header = HeaderWithLength::new(c, v.len()); - { - let x = Arc::into_thin(Arc::from_header_and_iter(header, v.into_iter())); - let y = ThinArc::with_arc(&x, |q| q.clone()); - let _ = y.clone(); - let _ = x == x; - Arc::from_thin(x.clone()); - } - assert_eq!(canary.load(Acquire), 1); - } -} diff --git a/components/servo_arc/rustfmt.toml b/components/servo_arc/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/servo_arc/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/shared/canvas/Cargo.toml b/components/shared/canvas/Cargo.toml index 6f3eca8c160..6fd52f4e3a4 100644 --- a/components/shared/canvas/Cargo.toml +++ b/components/shared/canvas/Cargo.toml @@ -20,14 +20,14 @@ cssparser = { workspace = true } euclid = { workspace = true } ipc-channel = { workspace = true } lazy_static = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } pixels = { path = "../../pixels" } serde = { workspace = true } serde_bytes = { workspace = true } servo_config = { path = "../../config" } sparkle = { workspace = true } -style = { path = "../../style" } +style = { workspace = true } time = { workspace = true, optional = true } webrender_api = { workspace = true } webxr-api = { git = "https://github.com/servo/webxr", features = ["ipc"] } diff --git a/components/shared/devtools/Cargo.toml b/components/shared/devtools/Cargo.toml index d158a3117ab..c3be3bcb01b 100644 --- a/components/shared/devtools/Cargo.toml +++ b/components/shared/devtools/Cargo.toml @@ -14,7 +14,7 @@ path = "lib.rs" bitflags = { workspace = true } http = { workspace = true } ipc-channel = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } msg = { workspace = true } serde = { workspace = true } diff --git a/components/shared/gfx/Cargo.toml b/components/shared/gfx/Cargo.toml index f94b5870c35..23bf75bfb07 100644 --- a/components/shared/gfx/Cargo.toml +++ b/components/shared/gfx/Cargo.toml @@ -11,7 +11,7 @@ name = "gfx_traits" path = "lib.rs" [dependencies] -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } range = { path = "../../range" } serde = { workspace = true } diff --git a/components/shared/msg/Cargo.toml b/components/shared/msg/Cargo.toml index d64880b5c6f..97abd671eef 100644 --- a/components/shared/msg/Cargo.toml +++ b/components/shared/msg/Cargo.toml @@ -15,9 +15,9 @@ doctest = false [dependencies] ipc-channel = { workspace = true } lazy_static = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -size_of_test = { path = "../../size_of_test" } +size_of_test = { workspace = true } webrender_api = { workspace = true } diff --git a/components/shared/net/Cargo.toml b/components/shared/net/Cargo.toml index dffc41eeb3a..50e9d4eb21d 100644 --- a/components/shared/net/Cargo.toml +++ b/components/shared/net/Cargo.toml @@ -24,7 +24,7 @@ image = { workspace = true } ipc-channel = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } mime = { workspace = true } msg = { workspace = true } @@ -33,7 +33,7 @@ percent-encoding = { workspace = true } pixels = { path = "../../pixels" } rustls = { workspace = true } serde = { workspace = true } -servo_arc = { path = "../../servo_arc" } +servo_arc = { workspace = true } servo_rand = { path = "../../rand" } servo_url = { path = "../../url" } url = { workspace = true } diff --git a/components/shared/script/Cargo.toml b/components/shared/script/Cargo.toml index 64ea7be7e66..5cc2b1f8554 100644 --- a/components/shared/script/Cargo.toml +++ b/components/shared/script/Cargo.toml @@ -26,7 +26,7 @@ ipc-channel = { workspace = true } keyboard-types = { workspace = true } libc = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } media = { path = "../../media" } msg = { workspace = true } @@ -34,7 +34,7 @@ net_traits = { workspace = true } pixels = { path = "../../pixels" } profile_traits = { workspace = true } serde = { workspace = true } -servo_atoms = { path = "../../atoms" } +servo_atoms = { workspace = true } servo_url = { path = "../../url" } smallvec = { workspace = true } style_traits = { workspace = true } diff --git a/components/shared/script_layout/Cargo.toml b/components/shared/script_layout/Cargo.toml index 40c92e96c8e..df526c9bec9 100644 --- a/components/shared/script_layout/Cargo.toml +++ b/components/shared/script_layout/Cargo.toml @@ -21,7 +21,7 @@ gfx_traits = { workspace = true } html5ever = { workspace = true } ipc-channel = { workspace = true } libc = { workspace = true } -malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } metrics = { path = "../../metrics" } msg = { workspace = true } @@ -29,10 +29,10 @@ net_traits = { workspace = true } profile_traits = { workspace = true } range = { path = "../../range" } script_traits = { workspace = true } -selectors = { path = "../../selectors" } -servo_arc = { path = "../../servo_arc" } -servo_atoms = { path = "../../atoms" } +selectors = { workspace = true } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } servo_url = { path = "../../url" } -style = { path = "../../style", features = ["servo"] } +style = { workspace = true } style_traits = { workspace = true } webrender_api = { workspace = true } diff --git a/components/size_of_test/Cargo.toml b/components/size_of_test/Cargo.toml deleted file mode 100644 index faea55c5c1c..00000000000 --- a/components/size_of_test/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "size_of_test" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -publish = false - -[lib] -path = "lib.rs" - -[dependencies] -static_assertions = "1.1" diff --git a/components/size_of_test/lib.rs b/components/size_of_test/lib.rs deleted file mode 100644 index 18e45175e8c..00000000000 --- a/components/size_of_test/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -/* 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/. */ - -pub use static_assertions::const_assert_eq; - -/// Asserts the size of a type at compile time. -#[macro_export] -macro_rules! size_of_test { - ($t: ty, $expected_size: expr) => { - #[cfg(target_pointer_width = "64")] - $crate::const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); - }; -} diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml deleted file mode 100644 index 6040b62116b..00000000000 --- a/components/style/Cargo.toml +++ /dev/null @@ -1,95 +0,0 @@ -[package] -name = "style" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -build = "build.rs" -edition = "2018" - -# https://github.com/rust-lang/cargo/issues/3544 -links = "servo_style_crate" - -[lib] -name = "style" -path = "lib.rs" -doctest = false - -[features] -gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "mozbuild"] -servo = [ - "serde", - "style_traits/servo", - "servo_atoms", - "html5ever", - "cssparser/serde", - "encoding_rs", - "malloc_size_of/servo", - "string_cache", - "to_shmem/servo", - "servo_arc/servo", - "url", -] -gecko_debug = [] -gecko_refcount_logging = [] - -[dependencies] -app_units = "0.7" -arrayvec = "0.7" -atomic_refcell = "0.1" -bitflags = "1.0" -byteorder = "1.0" -cssparser = { workspace = true } -derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "from"] } -encoding_rs = { version = "0.8", optional = true } -euclid = "0.22" -fxhash = "0.2" -html5ever = { version = "0.26", optional = true } -indexmap = "1.0" -itertools = "0.10" -itoa = "1.0" -lazy_static = "1" -log = { version = "0.4", features = ["std"] } -malloc_size_of = { path = "../malloc_size_of" } -malloc_size_of_derive = "0.1" -mime = "0.3.13" -new_debug_unreachable = "1.0" -num-derive = "0.3" -num-integer = "0.1" -num-traits = "0.2" -num_cpus = { version = "1.1.0" } -owning_ref = "0.4" -parking_lot = "0.12" -precomputed-hash = "0.1.1" -rayon = "1" -selectors = { path = "../selectors" } -serde = { version = "1.0", optional = true, features = ["derive"] } -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms", optional = true } -smallbitvec = "2.3.0" -smallvec = "1.0" -static_assertions = "1.1" -static_prefs = { path = "../style_static_prefs" } -string_cache = { version = "0.8", optional = true } -style_config = { path = "../style_config" } -style_derive = { path = "../style_derive" } -style_traits = { path = "../style_traits" } -time = "0.1" -thin-vec = { workspace = true } -to_shmem = { path = "../to_shmem" } -to_shmem_derive = { path = "../to_shmem_derive" } -uluru = "3.0" -unicode-bidi = "0.3" -unicode-segmentation = "1.0" -url = { workspace = true, optional = true } -void = "1.0.2" - -[build-dependencies] -bindgen = { version = "0.69", optional = true, default-features = false } -lazy_static = "1" -log = "0.4" -mozbuild = { version = "0.1", optional = true } -regex = { version = "1.1", optional = true } -toml = { version = "0.5", optional = true, default-features = false } -walkdir = "2.1.4" diff --git a/components/style/README.md b/components/style/README.md deleted file mode 100644 index bdbe36e44c4..00000000000 --- a/components/style/README.md +++ /dev/null @@ -1,6 +0,0 @@ -servo-style -=========== - -Style system for Servo, using [rust-cssparser](https://github.com/servo/rust-cssparser) for parsing. - - * [Documentation](https://github.com/servo/servo/blob/main/docs/components/style.md). diff --git a/components/style/animation.rs b/components/style/animation.rs deleted file mode 100644 index aafe7067dc9..00000000000 --- a/components/style/animation.rs +++ /dev/null @@ -1,1415 +0,0 @@ -/* 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/. */ - -//! CSS transitions and animations. - -// NOTE(emilio): This code isn't really executed in Gecko, but we don't want to -// compile it out so that people remember it exists. - -use crate::context::{CascadeInputs, SharedStyleContext}; -use crate::dom::{OpaqueNode, TDocument, TElement, TNode}; -use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; -use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; -use crate::properties::longhands::animation_fill_mode::computed_value::single_value::T as AnimationFillMode; -use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState; -use crate::properties::AnimationDeclarations; -use crate::properties::{ - ComputedValues, Importance, LonghandId, LonghandIdSet, PropertyDeclarationBlock, - PropertyDeclarationId, -}; -use crate::rule_tree::CascadeLevel; -use crate::selector_parser::PseudoElement; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::style_resolver::StyleResolverForElement; -use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; -use crate::stylesheets::layer_rule::LayerOrder; -use crate::values::animated::{Animate, Procedure}; -use crate::values::computed::{Time, TimingFunction}; -use crate::values::generics::easing::BeforeFlag; -use crate::Atom; -use fxhash::FxHashMap; -use parking_lot::RwLock; -use servo_arc::Arc; -use std::fmt; - -/// Represents an animation for a given property. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct PropertyAnimation { - /// The value we are animating from. - from: AnimationValue, - - /// The value we are animating to. - to: AnimationValue, - - /// The timing function of this `PropertyAnimation`. - timing_function: TimingFunction, - - /// The duration of this `PropertyAnimation` in seconds. - pub duration: f64, -} - -impl PropertyAnimation { - /// Returns the given property longhand id. - pub fn property_id(&self) -> LonghandId { - debug_assert_eq!(self.from.id(), self.to.id()); - self.from.id() - } - - fn from_longhand( - longhand: LonghandId, - timing_function: TimingFunction, - duration: Time, - old_style: &ComputedValues, - new_style: &ComputedValues, - ) -> Option<PropertyAnimation> { - // FIXME(emilio): Handle the case where old_style and new_style's writing mode differ. - let longhand = longhand.to_physical(new_style.writing_mode); - let from = AnimationValue::from_computed_values(longhand, old_style)?; - let to = AnimationValue::from_computed_values(longhand, new_style)?; - let duration = duration.seconds() as f64; - - if from == to || duration == 0.0 { - return None; - } - - Some(PropertyAnimation { - from, - to, - timing_function, - duration, - }) - } - - /// The output of the timing function given the progress ration of this animation. - fn timing_function_output(&self, progress: f64) -> f64 { - let epsilon = 1. / (200. * self.duration); - // FIXME: Need to set the before flag correctly. - // In order to get the before flag, we have to know the current animation phase - // and whether the iteration is reversed. For now, we skip this calculation - // by treating as if the flag is unset at all times. - // https://drafts.csswg.org/css-easing/#step-timing-function-algo - self.timing_function - .calculate_output(progress, BeforeFlag::Unset, epsilon) - } - - /// Update the given animation at a given point of progress. - fn calculate_value(&self, progress: f64) -> Result<AnimationValue, ()> { - let procedure = Procedure::Interpolate { - progress: self.timing_function_output(progress), - }; - self.from.animate(&self.to, procedure) - } -} - -/// This structure represents the state of an animation. -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub enum AnimationState { - /// The animation has been created, but is not running yet. This state - /// is also used when an animation is still in the first delay phase. - Pending, - /// This animation is currently running. - Running, - /// This animation is paused. The inner field is the percentage of progress - /// when it was paused, from 0 to 1. - Paused(f64), - /// This animation has finished. - Finished, - /// This animation has been canceled. - Canceled, -} - -impl AnimationState { - /// Whether or not this state requires its owning animation to be ticked. - fn needs_to_be_ticked(&self) -> bool { - *self == AnimationState::Running || *self == AnimationState::Pending - } -} - -/// This structure represents a keyframes animation current iteration state. -/// -/// If the iteration count is infinite, there's no other state, otherwise we -/// have to keep track the current iteration and the max iteration count. -#[derive(Clone, Debug, MallocSizeOf)] -pub enum KeyframesIterationState { - /// Infinite iterations with the current iteration count. - Infinite(f64), - /// Current and max iterations. - Finite(f64, f64), -} - -/// A temporary data structure used when calculating ComputedKeyframes for an -/// animation. This data structure is used to collapse information for steps -/// which may be spread across multiple keyframe declarations into a single -/// instance per `start_percentage`. -struct IntermediateComputedKeyframe { - declarations: PropertyDeclarationBlock, - timing_function: Option<TimingFunction>, - start_percentage: f32, -} - -impl IntermediateComputedKeyframe { - fn new(start_percentage: f32) -> Self { - IntermediateComputedKeyframe { - declarations: PropertyDeclarationBlock::new(), - timing_function: None, - start_percentage, - } - } - - /// Walk through all keyframe declarations and combine all declarations with the - /// same `start_percentage` into individual `IntermediateComputedKeyframe`s. - fn generate_for_keyframes( - animation: &KeyframesAnimation, - context: &SharedStyleContext, - base_style: &ComputedValues, - ) -> Vec<Self> { - let mut intermediate_steps: Vec<Self> = Vec::with_capacity(animation.steps.len()); - let mut current_step = IntermediateComputedKeyframe::new(0.); - for step in animation.steps.iter() { - let start_percentage = step.start_percentage.0; - if start_percentage != current_step.start_percentage { - let new_step = IntermediateComputedKeyframe::new(start_percentage); - intermediate_steps.push(std::mem::replace(&mut current_step, new_step)); - } - - current_step.update_from_step(step, context, base_style); - } - intermediate_steps.push(current_step); - - // We should always have a first and a last step, even if these are just - // generated by KeyframesStepValue::ComputedValues. - debug_assert!(intermediate_steps.first().unwrap().start_percentage == 0.); - debug_assert!(intermediate_steps.last().unwrap().start_percentage == 1.); - - intermediate_steps - } - - fn update_from_step( - &mut self, - step: &KeyframesStep, - context: &SharedStyleContext, - base_style: &ComputedValues, - ) { - // Each keyframe declaration may optionally specify a timing function, falling - // back to the one defined global for the animation. - let guard = &context.guards.author; - if let Some(timing_function) = step.get_animation_timing_function(&guard) { - self.timing_function = Some(timing_function.to_computed_value_without_context()); - } - - let block = match step.value { - KeyframesStepValue::ComputedValues => return, - KeyframesStepValue::Declarations { ref block } => block, - }; - - // Filter out !important, non-animatable properties, and the - // 'display' property (which is only animatable from SMIL). - let guard = block.read_with(&guard); - for declaration in guard.normal_declaration_iter() { - if let PropertyDeclarationId::Longhand(id) = declaration.id() { - if id == LonghandId::Display { - continue; - } - - if !id.is_animatable() { - continue; - } - } - - self.declarations.push( - declaration.to_physical(base_style.writing_mode), - Importance::Normal, - ); - } - } - - fn resolve_style<E>( - self, - element: E, - context: &SharedStyleContext, - base_style: &Arc<ComputedValues>, - resolver: &mut StyleResolverForElement<E>, - ) -> Arc<ComputedValues> - where - E: TElement, - { - if !self.declarations.any_normal() { - return base_style.clone(); - } - - let document = element.as_node().owner_doc(); - let locked_block = Arc::new(document.shared_lock().wrap(self.declarations)); - let mut important_rules_changed = false; - let rule_node = base_style.rules().clone(); - let new_node = context.stylist.rule_tree().update_rule_at_level( - CascadeLevel::Animations, - LayerOrder::root(), - Some(locked_block.borrow_arc()), - &rule_node, - &context.guards, - &mut important_rules_changed, - ); - - if new_node.is_none() { - return base_style.clone(); - } - - let inputs = CascadeInputs { - rules: new_node, - visited_rules: base_style.visited_rules().cloned(), - flags: base_style.flags.for_cascade_inputs(), - }; - resolver - .cascade_style_and_visited_with_default_parents(inputs) - .0 - } -} - -/// A single computed keyframe for a CSS Animation. -#[derive(Clone, MallocSizeOf)] -struct ComputedKeyframe { - /// The timing function to use for transitions between this step - /// and the next one. - timing_function: TimingFunction, - - /// The starting percentage (a number between 0 and 1) which represents - /// at what point in an animation iteration this step is. - start_percentage: f32, - - /// The animation values to transition to and from when processing this - /// keyframe animation step. - values: Vec<AnimationValue>, -} - -impl ComputedKeyframe { - fn generate_for_keyframes<E>( - element: E, - animation: &KeyframesAnimation, - context: &SharedStyleContext, - base_style: &Arc<ComputedValues>, - default_timing_function: TimingFunction, - resolver: &mut StyleResolverForElement<E>, - ) -> Vec<Self> - where - E: TElement, - { - let mut animating_properties = LonghandIdSet::new(); - for property in animation.properties_changed.iter() { - debug_assert!(property.is_animatable()); - animating_properties.insert(property.to_physical(base_style.writing_mode)); - } - - let animation_values_from_style: Vec<AnimationValue> = animating_properties - .iter() - .map(|property| { - AnimationValue::from_computed_values(property, &**base_style) - .expect("Unexpected non-animatable property.") - }) - .collect(); - - let intermediate_steps = - IntermediateComputedKeyframe::generate_for_keyframes(animation, context, base_style); - - let mut computed_steps: Vec<Self> = Vec::with_capacity(intermediate_steps.len()); - for (step_index, step) in intermediate_steps.into_iter().enumerate() { - let start_percentage = step.start_percentage; - let properties_changed_in_step = step.declarations.longhands().clone(); - let step_timing_function = step.timing_function.clone(); - let step_style = step.resolve_style(element, context, base_style, resolver); - let timing_function = - step_timing_function.unwrap_or_else(|| default_timing_function.clone()); - - let values = { - // If a value is not set in a property declaration we use the value from - // the style for the first and last keyframe. For intermediate ones, we - // use the value from the previous keyframe. - // - // TODO(mrobinson): According to the spec, we should use an interpolated - // value for properties missing from keyframe declarations. - let default_values = if start_percentage == 0. || start_percentage == 1.0 { - &animation_values_from_style - } else { - debug_assert!(step_index != 0); - &computed_steps[step_index - 1].values - }; - - // For each property that is animating, pull the value from the resolved - // style for this step if it's in one of the declarations. Otherwise, we - // use the default value from the set we calculated above. - animating_properties - .iter() - .zip(default_values.iter()) - .map(|(longhand, default_value)| { - if properties_changed_in_step.contains(longhand) { - AnimationValue::from_computed_values(longhand, &step_style) - .unwrap_or_else(|| default_value.clone()) - } else { - default_value.clone() - } - }) - .collect() - }; - - computed_steps.push(ComputedKeyframe { - timing_function, - start_percentage, - values, - }); - } - computed_steps - } -} - -/// A CSS Animation -#[derive(Clone, MallocSizeOf)] -pub struct Animation { - /// The name of this animation as defined by the style. - pub name: Atom, - - /// The properties that change in this animation. - properties_changed: LonghandIdSet, - - /// The computed style for each keyframe of this animation. - computed_steps: Vec<ComputedKeyframe>, - - /// The time this animation started at, which is the current value of the animation - /// timeline when this animation was created plus any animation delay. - pub started_at: f64, - - /// The duration of this animation. - pub duration: f64, - - /// The delay of the animation. - pub delay: f64, - - /// The `animation-fill-mode` property of this animation. - pub fill_mode: AnimationFillMode, - - /// The current iteration state for the animation. - pub iteration_state: KeyframesIterationState, - - /// Whether this animation is paused. - pub state: AnimationState, - - /// The declared animation direction of this animation. - pub direction: AnimationDirection, - - /// The current animation direction. This can only be `normal` or `reverse`. - pub current_direction: AnimationDirection, - - /// The original cascade style, needed to compute the generated keyframes of - /// the animation. - #[ignore_malloc_size_of = "ComputedValues"] - pub cascade_style: Arc<ComputedValues>, - - /// Whether or not this animation is new and or has already been tracked - /// by the script thread. - pub is_new: bool, -} - -impl Animation { - /// Whether or not this animation is cancelled by changes from a new style. - fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool { - let new_ui = new_style.get_ui(); - let index = new_ui - .animation_name_iter() - .position(|animation_name| Some(&self.name) == animation_name.as_atom()); - let index = match index { - Some(index) => index, - None => return true, - }; - - new_ui.animation_duration_mod(index).seconds() == 0. - } - - /// Given the current time, advances this animation to the next iteration, - /// updates times, and then toggles the direction if appropriate. Otherwise - /// does nothing. Returns true if this animation has iterated. - pub fn iterate_if_necessary(&mut self, time: f64) -> bool { - if !self.iteration_over(time) { - return false; - } - - // Only iterate animations that are currently running. - if self.state != AnimationState::Running { - return false; - } - - if self.on_last_iteration() { - return false; - } - - self.iterate(); - true - } - - fn iterate(&mut self) { - debug_assert!(!self.on_last_iteration()); - - if let KeyframesIterationState::Finite(ref mut current, max) = self.iteration_state { - *current = (*current + 1.).min(max); - } - - if let AnimationState::Paused(ref mut progress) = self.state { - debug_assert!(*progress > 1.); - *progress -= 1.; - } - - // Update the next iteration direction if applicable. - self.started_at += self.duration; - match self.direction { - AnimationDirection::Alternate | AnimationDirection::AlternateReverse => { - self.current_direction = match self.current_direction { - AnimationDirection::Normal => AnimationDirection::Reverse, - AnimationDirection::Reverse => AnimationDirection::Normal, - _ => unreachable!(), - }; - }, - _ => {}, - } - } - - /// A number (> 0 and <= 1) which represents the fraction of a full iteration - /// that the current iteration of the animation lasts. This will be less than 1 - /// if the current iteration is the fractional remainder of a non-integral - /// iteration count. - pub fn current_iteration_end_progress(&self) -> f64 { - match self.iteration_state { - KeyframesIterationState::Finite(current, max) => (max - current).min(1.), - KeyframesIterationState::Infinite(_) => 1., - } - } - - /// The duration of the current iteration of this animation which may be less - /// than the animation duration if it has a non-integral iteration count. - pub fn current_iteration_duration(&self) -> f64 { - self.current_iteration_end_progress() * self.duration - } - - /// Whether or not the current iteration is over. Note that this method assumes that - /// the animation is still running. - fn iteration_over(&self, time: f64) -> bool { - time > (self.started_at + self.current_iteration_duration()) - } - - /// Assuming this animation is running, whether or not it is on the last iteration. - fn on_last_iteration(&self) -> bool { - match self.iteration_state { - KeyframesIterationState::Finite(current, max) => current >= (max - 1.), - KeyframesIterationState::Infinite(_) => false, - } - } - - /// Whether or not this animation has finished at the provided time. This does - /// not take into account canceling i.e. when an animation or transition is - /// canceled due to changes in the style. - pub fn has_ended(&self, time: f64) -> bool { - if !self.on_last_iteration() { - return false; - } - - let progress = match self.state { - AnimationState::Finished => return true, - AnimationState::Paused(progress) => progress, - AnimationState::Running => (time - self.started_at) / self.duration, - AnimationState::Pending | AnimationState::Canceled => return false, - }; - - progress >= self.current_iteration_end_progress() - } - - /// Updates the appropiate state from other animation. - /// - /// This happens when an animation is re-submitted to layout, presumably - /// because of an state change. - /// - /// There are some bits of state we can't just replace, over all taking in - /// account times, so here's that logic. - pub fn update_from_other(&mut self, other: &Self, now: f64) { - use self::AnimationState::*; - - debug!( - "KeyframesAnimationState::update_from_other({:?}, {:?})", - self, other - ); - - // NB: We shall not touch the started_at field, since we don't want to - // restart the animation. - let old_started_at = self.started_at; - let old_duration = self.duration; - let old_direction = self.current_direction; - let old_state = self.state.clone(); - let old_iteration_state = self.iteration_state.clone(); - - *self = other.clone(); - - self.started_at = old_started_at; - self.current_direction = old_direction; - - // Don't update the iteration count, just the iteration limit. - // TODO: see how changing the limit affects rendering in other browsers. - // We might need to keep the iteration count even when it's infinite. - match (&mut self.iteration_state, old_iteration_state) { - ( - &mut KeyframesIterationState::Finite(ref mut iters, _), - KeyframesIterationState::Finite(old_iters, _), - ) => *iters = old_iters, - _ => {}, - } - - // Don't pause or restart animations that should remain finished. - // We call mem::replace because `has_ended(...)` looks at `Animation::state`. - let new_state = std::mem::replace(&mut self.state, Running); - if old_state == Finished && self.has_ended(now) { - self.state = Finished; - } else { - self.state = new_state; - } - - // If we're unpausing the animation, fake the start time so we seem to - // restore it. - // - // If the animation keeps paused, keep the old value. - // - // If we're pausing the animation, compute the progress value. - match (&mut self.state, &old_state) { - (&mut Pending, &Paused(progress)) => { - self.started_at = now - (self.duration * progress); - }, - (&mut Paused(ref mut new), &Paused(old)) => *new = old, - (&mut Paused(ref mut progress), &Running) => { - *progress = (now - old_started_at) / old_duration - }, - _ => {}, - } - - // Try to detect when we should skip straight to the running phase to - // avoid sending multiple animationstart events. - if self.state == Pending && self.started_at <= now && old_state != Pending { - self.state = Running; - } - } - - /// Fill in an `AnimationValueMap` with values calculated from this animation at - /// the given time value. - fn get_property_declaration_at_time(&self, now: f64, map: &mut AnimationValueMap) { - debug_assert!(!self.computed_steps.is_empty()); - - let total_progress = match self.state { - AnimationState::Running | AnimationState::Pending | AnimationState::Finished => { - (now - self.started_at) / self.duration - }, - AnimationState::Paused(progress) => progress, - AnimationState::Canceled => return, - }; - - if total_progress < 0. && - self.fill_mode != AnimationFillMode::Backwards && - self.fill_mode != AnimationFillMode::Both - { - return; - } - if self.has_ended(now) && - self.fill_mode != AnimationFillMode::Forwards && - self.fill_mode != AnimationFillMode::Both - { - return; - } - let total_progress = total_progress - .min(self.current_iteration_end_progress()) - .max(0.0); - - // Get the indices of the previous (from) keyframe and the next (to) keyframe. - let next_keyframe_index; - let prev_keyframe_index; - let num_steps = self.computed_steps.len(); - match self.current_direction { - AnimationDirection::Normal => { - next_keyframe_index = self - .computed_steps - .iter() - .position(|step| total_progress as f32 <= step.start_percentage); - prev_keyframe_index = next_keyframe_index - .and_then(|pos| if pos != 0 { Some(pos - 1) } else { None }) - .unwrap_or(0); - }, - AnimationDirection::Reverse => { - next_keyframe_index = self - .computed_steps - .iter() - .rev() - .position(|step| total_progress as f32 <= 1. - step.start_percentage) - .map(|pos| num_steps - pos - 1); - prev_keyframe_index = next_keyframe_index - .and_then(|pos| { - if pos != num_steps - 1 { - Some(pos + 1) - } else { - None - } - }) - .unwrap_or(num_steps - 1) - }, - _ => unreachable!(), - } - - debug!( - "Animation::get_property_declaration_at_time: keyframe from {:?} to {:?}", - prev_keyframe_index, next_keyframe_index - ); - - let prev_keyframe = &self.computed_steps[prev_keyframe_index]; - let next_keyframe = match next_keyframe_index { - Some(index) => &self.computed_steps[index], - None => return, - }; - - // If we only need to take into account one keyframe, then exit early - // in order to avoid doing more work. - let mut add_declarations_to_map = |keyframe: &ComputedKeyframe| { - for value in keyframe.values.iter() { - map.insert(value.id(), value.clone()); - } - }; - if total_progress <= 0.0 { - add_declarations_to_map(&prev_keyframe); - return; - } - if total_progress >= 1.0 { - add_declarations_to_map(&next_keyframe); - return; - } - - let percentage_between_keyframes = - (next_keyframe.start_percentage - prev_keyframe.start_percentage).abs() as f64; - let duration_between_keyframes = percentage_between_keyframes * self.duration; - let direction_aware_prev_keyframe_start_percentage = match self.current_direction { - AnimationDirection::Normal => prev_keyframe.start_percentage as f64, - AnimationDirection::Reverse => 1. - prev_keyframe.start_percentage as f64, - _ => unreachable!(), - }; - let progress_between_keyframes = (total_progress - - direction_aware_prev_keyframe_start_percentage) / - percentage_between_keyframes; - - for (from, to) in prev_keyframe.values.iter().zip(next_keyframe.values.iter()) { - let animation = PropertyAnimation { - from: from.clone(), - to: to.clone(), - timing_function: prev_keyframe.timing_function.clone(), - duration: duration_between_keyframes as f64, - }; - - if let Ok(value) = animation.calculate_value(progress_between_keyframes) { - map.insert(value.id(), value); - } - } - } -} - -impl fmt::Debug for Animation { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Animation") - .field("name", &self.name) - .field("started_at", &self.started_at) - .field("duration", &self.duration) - .field("delay", &self.delay) - .field("iteration_state", &self.iteration_state) - .field("state", &self.state) - .field("direction", &self.direction) - .field("current_direction", &self.current_direction) - .field("cascade_style", &()) - .finish() - } -} - -/// A CSS Transition -#[derive(Clone, Debug, MallocSizeOf)] -pub struct Transition { - /// The start time of this transition, which is the current value of the animation - /// timeline when this transition was created plus any animation delay. - pub start_time: f64, - - /// The delay used for this transition. - pub delay: f64, - - /// The internal style `PropertyAnimation` for this transition. - pub property_animation: PropertyAnimation, - - /// The state of this transition. - pub state: AnimationState, - - /// Whether or not this transition is new and or has already been tracked - /// by the script thread. - pub is_new: bool, - - /// If this `Transition` has been replaced by a new one this field is - /// used to help produce better reversed transitions. - pub reversing_adjusted_start_value: AnimationValue, - - /// If this `Transition` has been replaced by a new one this field is - /// used to help produce better reversed transitions. - pub reversing_shortening_factor: f64, -} - -impl Transition { - fn update_for_possibly_reversed_transition( - &mut self, - replaced_transition: &Transition, - delay: f64, - now: f64, - ) { - // If we reach here, we need to calculate a reversed transition according to - // https://drafts.csswg.org/css-transitions/#starting - // - // "...if the reversing-adjusted start value of the running transition - // is the same as the value of the property in the after-change style (see - // the section on reversing of transitions for why these case exists), - // implementations must cancel the running transition and start - // a new transition..." - if replaced_transition.reversing_adjusted_start_value != self.property_animation.to { - return; - } - - // "* reversing-adjusted start value is the end value of the running transition" - let replaced_animation = &replaced_transition.property_animation; - self.reversing_adjusted_start_value = replaced_animation.to.clone(); - - // "* reversing shortening factor is the absolute value, clamped to the - // range [0, 1], of the sum of: - // 1. the output of the timing function of the old transition at the - // time of the style change event, times the reversing shortening - // factor of the old transition - // 2. 1 minus the reversing shortening factor of the old transition." - let transition_progress = ((now - replaced_transition.start_time) / - (replaced_transition.property_animation.duration)) - .min(1.0) - .max(0.0); - let timing_function_output = replaced_animation.timing_function_output(transition_progress); - let old_reversing_shortening_factor = replaced_transition.reversing_shortening_factor; - self.reversing_shortening_factor = ((timing_function_output * - old_reversing_shortening_factor) + - (1.0 - old_reversing_shortening_factor)) - .abs() - .min(1.0) - .max(0.0); - - // "* start time is the time of the style change event plus: - // 1. if the matching transition delay is nonnegative, the matching - // transition delay, or. - // 2. if the matching transition delay is negative, the product of the new - // transition’s reversing shortening factor and the matching transition delay," - self.start_time = if delay >= 0. { - now + delay - } else { - now + (self.reversing_shortening_factor * delay) - }; - - // "* end time is the start time plus the product of the matching transition - // duration and the new transition’s reversing shortening factor," - self.property_animation.duration *= self.reversing_shortening_factor; - - // "* start value is the current value of the property in the running transition, - // * end value is the value of the property in the after-change style," - let procedure = Procedure::Interpolate { - progress: timing_function_output, - }; - match replaced_animation - .from - .animate(&replaced_animation.to, procedure) - { - Ok(new_start) => self.property_animation.from = new_start, - Err(..) => {}, - } - } - - /// Whether or not this animation has ended at the provided time. This does - /// not take into account canceling i.e. when an animation or transition is - /// canceled due to changes in the style. - pub fn has_ended(&self, time: f64) -> bool { - time >= self.start_time + (self.property_animation.duration) - } - - /// Update the given animation at a given point of progress. - pub fn calculate_value(&self, time: f64) -> Option<AnimationValue> { - let progress = (time - self.start_time) / (self.property_animation.duration); - if progress < 0.0 { - return None; - } - - self.property_animation - .calculate_value(progress.min(1.0)) - .ok() - } -} - -/// Holds the animation state for a particular element. -#[derive(Debug, Default, MallocSizeOf)] -pub struct ElementAnimationSet { - /// The animations for this element. - pub animations: Vec<Animation>, - - /// The transitions for this element. - pub transitions: Vec<Transition>, - - /// Whether or not this ElementAnimationSet has had animations or transitions - /// which have been added, removed, or had their state changed. - pub dirty: bool, -} - -impl ElementAnimationSet { - /// Cancel all animations in this `ElementAnimationSet`. This is typically called - /// when the element has been removed from the DOM. - pub fn cancel_all_animations(&mut self) { - self.dirty = !self.animations.is_empty(); - for animation in self.animations.iter_mut() { - animation.state = AnimationState::Canceled; - } - self.cancel_active_transitions(); - } - - fn cancel_active_transitions(&mut self) { - for transition in self.transitions.iter_mut() { - if transition.state != AnimationState::Finished { - self.dirty = true; - transition.state = AnimationState::Canceled; - } - } - } - - /// Apply all active animations. - pub fn apply_active_animations( - &self, - context: &SharedStyleContext, - style: &mut Arc<ComputedValues>, - ) { - let now = context.current_time_for_animations; - let mutable_style = Arc::make_mut(style); - if let Some(map) = self.get_value_map_for_active_animations(now) { - for value in map.values() { - value.set_in_style_for_servo(mutable_style); - } - } - - if let Some(map) = self.get_value_map_for_active_transitions(now) { - for value in map.values() { - value.set_in_style_for_servo(mutable_style); - } - } - } - - /// Clear all canceled animations and transitions from this `ElementAnimationSet`. - pub fn clear_canceled_animations(&mut self) { - self.animations - .retain(|animation| animation.state != AnimationState::Canceled); - self.transitions - .retain(|animation| animation.state != AnimationState::Canceled); - } - - /// Whether this `ElementAnimationSet` is empty, which means it doesn't - /// hold any animations in any state. - pub fn is_empty(&self) -> bool { - self.animations.is_empty() && self.transitions.is_empty() - } - - /// Whether or not this state needs animation ticks for its transitions - /// or animations. - pub fn needs_animation_ticks(&self) -> bool { - self.animations - .iter() - .any(|animation| animation.state.needs_to_be_ticked()) || - self.transitions - .iter() - .any(|transition| transition.state.needs_to_be_ticked()) - } - - /// The number of running animations and transitions for this `ElementAnimationSet`. - pub fn running_animation_and_transition_count(&self) -> usize { - self.animations - .iter() - .filter(|animation| animation.state.needs_to_be_ticked()) - .count() + - self.transitions - .iter() - .filter(|transition| transition.state.needs_to_be_ticked()) - .count() - } - - /// If this `ElementAnimationSet` has any any active animations. - pub fn has_active_animation(&self) -> bool { - self.animations - .iter() - .any(|animation| animation.state != AnimationState::Canceled) - } - - /// If this `ElementAnimationSet` has any any active transitions. - pub fn has_active_transition(&self) -> bool { - self.transitions - .iter() - .any(|transition| transition.state != AnimationState::Canceled) - } - - /// Update our animations given a new style, canceling or starting new animations - /// when appropriate. - pub fn update_animations_for_new_style<E>( - &mut self, - element: E, - context: &SharedStyleContext, - new_style: &Arc<ComputedValues>, - resolver: &mut StyleResolverForElement<E>, - ) where - E: TElement, - { - for animation in self.animations.iter_mut() { - if animation.is_cancelled_in_new_style(new_style) { - animation.state = AnimationState::Canceled; - } - } - - maybe_start_animations(element, &context, &new_style, self, resolver); - } - - /// Update our transitions given a new style, canceling or starting new animations - /// when appropriate. - pub fn update_transitions_for_new_style( - &mut self, - might_need_transitions_update: bool, - context: &SharedStyleContext, - old_style: Option<&Arc<ComputedValues>>, - after_change_style: &Arc<ComputedValues>, - ) { - // If this is the first style, we don't trigger any transitions and we assume - // there were no previously triggered transitions. - let mut before_change_style = match old_style { - Some(old_style) => Arc::clone(old_style), - None => return, - }; - - // If the style of this element is display:none, then cancel all active transitions. - if after_change_style.get_box().clone_display().is_none() { - self.cancel_active_transitions(); - return; - } - - if !might_need_transitions_update { - return; - } - - // We convert old values into `before-change-style` here. - if self.has_active_transition() || self.has_active_animation() { - self.apply_active_animations(context, &mut before_change_style); - } - - let transitioning_properties = start_transitions_if_applicable( - context, - &before_change_style, - after_change_style, - self, - ); - - // Cancel any non-finished transitions that have properties which no longer transition. - for transition in self.transitions.iter_mut() { - if transition.state == AnimationState::Finished { - continue; - } - if transitioning_properties.contains(transition.property_animation.property_id()) { - continue; - } - transition.state = AnimationState::Canceled; - self.dirty = true; - } - } - - fn start_transition_if_applicable( - &mut self, - context: &SharedStyleContext, - longhand_id: LonghandId, - index: usize, - old_style: &ComputedValues, - new_style: &Arc<ComputedValues>, - ) { - if !longhand_id.is_transitionable() { - return; - } - - let style = new_style.get_ui(); - let timing_function = style.transition_timing_function_mod(index); - let duration = style.transition_duration_mod(index); - let delay = style.transition_delay_mod(index).seconds() as f64; - let now = context.current_time_for_animations; - - // Only start a new transition if the style actually changes between - // the old style and the new style. - let property_animation = match PropertyAnimation::from_longhand( - longhand_id, - timing_function, - duration, - old_style, - new_style, - ) { - Some(property_animation) => property_animation, - None => return, - }; - - // Per [1], don't trigger a new transition if the end state for that - // transition is the same as that of a transition that's running or - // completed. We don't take into account any canceled animations. - // [1]: https://drafts.csswg.org/css-transitions/#starting - if self - .transitions - .iter() - .filter(|transition| transition.state != AnimationState::Canceled) - .any(|transition| transition.property_animation.to == property_animation.to) - { - return; - } - - // We are going to start a new transition, but we might have to update - // it if we are replacing a reversed transition. - let reversing_adjusted_start_value = property_animation.from.clone(); - let mut new_transition = Transition { - start_time: now + delay, - delay, - property_animation, - state: AnimationState::Pending, - is_new: true, - reversing_adjusted_start_value, - reversing_shortening_factor: 1.0, - }; - - if let Some(old_transition) = self - .transitions - .iter_mut() - .filter(|transition| transition.state == AnimationState::Running) - .find(|transition| transition.property_animation.property_id() == longhand_id) - { - // We always cancel any running transitions for the same property. - old_transition.state = AnimationState::Canceled; - new_transition.update_for_possibly_reversed_transition(old_transition, delay, now); - } - - self.transitions.push(new_transition); - self.dirty = true; - } - - /// Generate a `AnimationValueMap` for this `ElementAnimationSet`'s - /// active transitions at the given time value. - pub fn get_value_map_for_active_transitions(&self, now: f64) -> Option<AnimationValueMap> { - if !self.has_active_transition() { - return None; - } - - let mut map = - AnimationValueMap::with_capacity_and_hasher(self.transitions.len(), Default::default()); - for transition in &self.transitions { - if transition.state == AnimationState::Canceled { - continue; - } - let value = match transition.calculate_value(now) { - Some(value) => value, - None => continue, - }; - map.insert(value.id(), value); - } - - Some(map) - } - - /// Generate a `AnimationValueMap` for this `ElementAnimationSet`'s - /// active animations at the given time value. - pub fn get_value_map_for_active_animations(&self, now: f64) -> Option<AnimationValueMap> { - if !self.has_active_animation() { - return None; - } - - let mut map = Default::default(); - for animation in &self.animations { - animation.get_property_declaration_at_time(now, &mut map); - } - - Some(map) - } -} - -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -/// A key that is used to identify nodes in the `DocumentAnimationSet`. -pub struct AnimationSetKey { - /// The node for this `AnimationSetKey`. - pub node: OpaqueNode, - /// The pseudo element for this `AnimationSetKey`. If `None` this key will - /// refer to the main content for its node. - pub pseudo_element: Option<PseudoElement>, -} - -impl AnimationSetKey { - /// Create a new key given a node and optional pseudo element. - pub fn new(node: OpaqueNode, pseudo_element: Option<PseudoElement>) -> Self { - AnimationSetKey { - node, - pseudo_element, - } - } - - /// Create a new key for the main content of this node. - pub fn new_for_non_pseudo(node: OpaqueNode) -> Self { - AnimationSetKey { - node, - pseudo_element: None, - } - } - - /// Create a new key for given node and pseudo element. - pub fn new_for_pseudo(node: OpaqueNode, pseudo_element: PseudoElement) -> Self { - AnimationSetKey { - node, - pseudo_element: Some(pseudo_element), - } - } -} - -#[derive(Clone, Debug, Default, MallocSizeOf)] -/// A set of animations for a document. -pub struct DocumentAnimationSet { - /// The `ElementAnimationSet`s that this set contains. - #[ignore_malloc_size_of = "Arc is hard"] - pub sets: Arc<RwLock<FxHashMap<AnimationSetKey, ElementAnimationSet>>>, -} - -impl DocumentAnimationSet { - /// Return whether or not the provided node has active CSS animations. - pub fn has_active_animations(&self, key: &AnimationSetKey) -> bool { - self.sets - .read() - .get(key) - .map_or(false, |set| set.has_active_animation()) - } - - /// Return whether or not the provided node has active CSS transitions. - pub fn has_active_transitions(&self, key: &AnimationSetKey) -> bool { - self.sets - .read() - .get(key) - .map_or(false, |set| set.has_active_transition()) - } - - /// Return a locked PropertyDeclarationBlock with animation values for the given - /// key and time. - pub fn get_animation_declarations( - &self, - key: &AnimationSetKey, - time: f64, - shared_lock: &SharedRwLock, - ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> { - self.sets - .read() - .get(key) - .and_then(|set| set.get_value_map_for_active_animations(time)) - .map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }) - } - - /// Return a locked PropertyDeclarationBlock with transition values for the given - /// key and time. - pub fn get_transition_declarations( - &self, - key: &AnimationSetKey, - time: f64, - shared_lock: &SharedRwLock, - ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> { - self.sets - .read() - .get(key) - .and_then(|set| set.get_value_map_for_active_transitions(time)) - .map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }) - } - - /// Get all the animation declarations for the given key, returning an empty - /// `AnimationDeclarations` if there are no animations. - pub fn get_all_declarations( - &self, - key: &AnimationSetKey, - time: f64, - shared_lock: &SharedRwLock, - ) -> AnimationDeclarations { - let sets = self.sets.read(); - let set = match sets.get(key) { - Some(set) => set, - None => return Default::default(), - }; - - let animations = set.get_value_map_for_active_animations(time).map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }); - let transitions = set.get_value_map_for_active_transitions(time).map(|map| { - let block = PropertyDeclarationBlock::from_animation_value_map(&map); - Arc::new(shared_lock.wrap(block)) - }); - AnimationDeclarations { - animations, - transitions, - } - } - - /// Cancel all animations for set at the given key. - pub fn cancel_all_animations_for_key(&self, key: &AnimationSetKey) { - if let Some(set) = self.sets.write().get_mut(key) { - set.cancel_all_animations(); - } - } -} - -/// Kick off any new transitions for this node and return all of the properties that are -/// transitioning. This is at the end of calculating style for a single node. -pub fn start_transitions_if_applicable( - context: &SharedStyleContext, - old_style: &ComputedValues, - new_style: &Arc<ComputedValues>, - animation_state: &mut ElementAnimationSet, -) -> LonghandIdSet { - let mut properties_that_transition = LonghandIdSet::new(); - for transition in new_style.transition_properties() { - let physical_property = transition.longhand_id.to_physical(new_style.writing_mode); - if properties_that_transition.contains(physical_property) { - continue; - } - - properties_that_transition.insert(physical_property); - animation_state.start_transition_if_applicable( - context, - physical_property, - transition.index, - old_style, - new_style, - ); - } - - properties_that_transition -} - -/// Triggers animations for a given node looking at the animation property -/// values. -pub fn maybe_start_animations<E>( - element: E, - context: &SharedStyleContext, - new_style: &Arc<ComputedValues>, - animation_state: &mut ElementAnimationSet, - resolver: &mut StyleResolverForElement<E>, -) where - E: TElement, -{ - let style = new_style.get_ui(); - for (i, name) in style.animation_name_iter().enumerate() { - let name = match name.as_atom() { - Some(atom) => atom, - None => continue, - }; - - debug!("maybe_start_animations: name={}", name); - let duration = style.animation_duration_mod(i).seconds() as f64; - if duration == 0. { - continue; - } - - let keyframe_animation = match context.stylist.get_animation(name, element) { - Some(animation) => animation, - None => continue, - }; - - debug!("maybe_start_animations: animation {} found", name); - - // If this animation doesn't have any keyframe, we can just continue - // without submitting it to the compositor, since both the first and - // the second keyframes would be synthetised from the computed - // values. - if keyframe_animation.steps.is_empty() { - continue; - } - - // NB: This delay may be negative, meaning that the animation may be created - // in a state where we have advanced one or more iterations or even that the - // animation begins in a finished state. - let delay = style.animation_delay_mod(i).seconds(); - - let iteration_count = style.animation_iteration_count_mod(i); - let iteration_state = if iteration_count.0.is_infinite() { - KeyframesIterationState::Infinite(0.0) - } else { - KeyframesIterationState::Finite(0.0, iteration_count.0 as f64) - }; - - let animation_direction = style.animation_direction_mod(i); - - let initial_direction = match animation_direction { - AnimationDirection::Normal | AnimationDirection::Alternate => { - AnimationDirection::Normal - }, - AnimationDirection::Reverse | AnimationDirection::AlternateReverse => { - AnimationDirection::Reverse - }, - }; - - let now = context.current_time_for_animations; - let started_at = now + delay as f64; - let mut starting_progress = (now - started_at) / duration; - let state = match style.animation_play_state_mod(i) { - AnimationPlayState::Paused => AnimationState::Paused(starting_progress), - AnimationPlayState::Running => AnimationState::Pending, - }; - - let computed_steps = ComputedKeyframe::generate_for_keyframes( - element, - &keyframe_animation, - context, - new_style, - style.animation_timing_function_mod(i), - resolver, - ); - - let mut new_animation = Animation { - name: name.clone(), - properties_changed: keyframe_animation.properties_changed, - computed_steps, - started_at, - duration, - fill_mode: style.animation_fill_mode_mod(i), - delay: delay as f64, - iteration_state, - state, - direction: animation_direction, - current_direction: initial_direction, - cascade_style: new_style.clone(), - is_new: true, - }; - - // If we started with a negative delay, make sure we iterate the animation if - // the delay moves us past the first iteration. - while starting_progress > 1. && !new_animation.on_last_iteration() { - new_animation.iterate(); - starting_progress -= 1.; - } - - animation_state.dirty = true; - - // If the animation was already present in the list for the node, just update its state. - for existing_animation in animation_state.animations.iter_mut() { - if existing_animation.state == AnimationState::Canceled { - continue; - } - - if new_animation.name == existing_animation.name { - existing_animation - .update_from_other(&new_animation, context.current_time_for_animations); - return; - } - } - - animation_state.animations.push(new_animation); - } -} diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs deleted file mode 100644 index a0dbb60da8e..00000000000 --- a/components/style/applicable_declarations.rs +++ /dev/null @@ -1,210 +0,0 @@ -/* 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/. */ - -//! Applicable declarations management. - -use crate::properties::PropertyDeclarationBlock; -use crate::rule_tree::{CascadeLevel, StyleSource}; -use crate::shared_lock::Locked; -use crate::stylesheets::layer_rule::LayerOrder; -use servo_arc::Arc; -use smallvec::SmallVec; - -/// List of applicable declarations. This is a transient structure that shuttles -/// declarations between selector matching and inserting into the rule tree, and -/// therefore we want to avoid heap-allocation where possible. -/// -/// In measurements on wikipedia, we pretty much never have more than 8 applicable -/// declarations, so we could consider making this 8 entries instead of 16. -/// However, it may depend a lot on workload, and stack space is cheap. -pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>; - -/// Blink uses 18 bits to store source order, and does not check overflow [1]. -/// That's a limit that could be reached in realistic webpages, so we use -/// 24 bits and enforce defined behavior in the overflow case. -/// -/// Note that right now this restriction could be lifted if wanted (because we -/// no longer stash the cascade level in the remaining bits), but we keep it in -/// place in case we come up with a use-case for them, lacking reports of the -/// current limit being too small. -/// -/// [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/ -/// RuleSet.h?l=128&rcl=90140ab80b84d0f889abc253410f44ed54ae04f3 -const SOURCE_ORDER_BITS: usize = 24; -const SOURCE_ORDER_MAX: u32 = (1 << SOURCE_ORDER_BITS) - 1; -const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX; - -/// The cascade-level+layer order of this declaration. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub struct CascadePriority { - cascade_level: CascadeLevel, - layer_order: LayerOrder, -} - -const_assert_eq!( - std::mem::size_of::<CascadePriority>(), - std::mem::size_of::<u32>() -); - -impl PartialOrd for CascadePriority { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for CascadePriority { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.cascade_level.cmp(&other.cascade_level).then_with(|| { - let ordering = self.layer_order.cmp(&other.layer_order); - if ordering == std::cmp::Ordering::Equal { - return ordering; - } - // https://drafts.csswg.org/css-cascade-5/#cascade-layering - // - // Cascade layers (like declarations) are ordered by order - // of appearance. When comparing declarations that belong to - // different layers, then for normal rules the declaration - // whose cascade layer is last wins, and for important rules - // the declaration whose cascade layer is first wins. - // - // But the style attribute layer for some reason is special. - if self.cascade_level.is_important() && - !self.layer_order.is_style_attribute_layer() && - !other.layer_order.is_style_attribute_layer() - { - ordering.reverse() - } else { - ordering - } - }) - } -} - -impl CascadePriority { - /// Construct a new CascadePriority for a given (level, order) pair. - pub fn new(cascade_level: CascadeLevel, layer_order: LayerOrder) -> Self { - Self { - cascade_level, - layer_order, - } - } - - /// Returns the layer order. - #[inline] - pub fn layer_order(&self) -> LayerOrder { - self.layer_order - } - - /// Returns the cascade level. - #[inline] - pub fn cascade_level(&self) -> CascadeLevel { - self.cascade_level - } - - /// Whether this declaration should be allowed if `revert` or `revert-layer` - /// have been specified on a given origin. - /// - /// `self` is the priority at which the `revert` or `revert-layer` keyword - /// have been specified. - pub fn allows_when_reverted(&self, other: &Self, origin_revert: bool) -> bool { - if origin_revert { - other.cascade_level.origin() < self.cascade_level.origin() - } else { - other.unimportant() < self.unimportant() - } - } - - /// Convert this priority from "important" to "non-important", if needed. - pub fn unimportant(&self) -> Self { - Self::new(self.cascade_level().unimportant(), self.layer_order()) - } - - /// Convert this priority from "non-important" to "important", if needed. - pub fn important(&self) -> Self { - Self::new(self.cascade_level().important(), self.layer_order()) - } -} - -/// A property declaration together with its precedence among rules of equal -/// specificity so that we can sort them. -/// -/// This represents the declarations in a given declaration block for a given -/// importance. -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub struct ApplicableDeclarationBlock { - /// The style source, either a style rule, or a property declaration block. - #[ignore_malloc_size_of = "Arc"] - pub source: StyleSource, - /// The bits containing the source order, cascade level, and shadow cascade - /// order. - source_order: u32, - /// The specificity of the selector. - pub specificity: u32, - /// The cascade priority of the rule. - pub cascade_priority: CascadePriority, -} - -impl ApplicableDeclarationBlock { - /// Constructs an applicable declaration block from a given property - /// declaration block and importance. - #[inline] - pub fn from_declarations( - declarations: Arc<Locked<PropertyDeclarationBlock>>, - level: CascadeLevel, - layer_order: LayerOrder, - ) -> Self { - ApplicableDeclarationBlock { - source: StyleSource::from_declarations(declarations), - source_order: 0, - specificity: 0, - cascade_priority: CascadePriority::new(level, layer_order), - } - } - - /// Constructs an applicable declaration block from the given components. - #[inline] - pub fn new( - source: StyleSource, - source_order: u32, - level: CascadeLevel, - specificity: u32, - layer_order: LayerOrder, - ) -> Self { - ApplicableDeclarationBlock { - source, - source_order: source_order & SOURCE_ORDER_MASK, - specificity, - cascade_priority: CascadePriority::new(level, layer_order), - } - } - - /// Returns the source order of the block. - #[inline] - pub fn source_order(&self) -> u32 { - self.source_order - } - - /// Returns the cascade level of the block. - #[inline] - pub fn level(&self) -> CascadeLevel { - self.cascade_priority.cascade_level() - } - - /// Returns the cascade level of the block. - #[inline] - pub fn layer_order(&self) -> LayerOrder { - self.cascade_priority.layer_order() - } - - /// Convenience method to consume self and return the right thing for the - /// rule tree to iterate over. - #[inline] - pub fn for_rule_tree(self) -> (StyleSource, CascadePriority) { - (self.source, self.cascade_priority) - } -} - -// Size of this struct determines sorting and selector-matching performance. -size_of_test!(ApplicableDeclarationBlock, 24); diff --git a/components/style/attr.rs b/components/style/attr.rs deleted file mode 100644 index 7747921ffe9..00000000000 --- a/components/style/attr.rs +++ /dev/null @@ -1,601 +0,0 @@ -/* 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/. */ - -//! Parsed representations of [DOM attributes][attr]. -//! -//! [attr]: https://dom.spec.whatwg.org/#interface-attr - -use crate::properties::PropertyDeclarationBlock; -use crate::shared_lock::Locked; -use crate::str::str_join; -use crate::str::{read_exponent, read_fraction, HTML_SPACE_CHARACTERS}; -use crate::str::{read_numbers, split_commas, split_html_space_chars}; -use crate::values::specified::Length; -use crate::values::AtomString; -use crate::{Atom, LocalName, Namespace, Prefix}; -use app_units::Au; -use cssparser::{self, Color, RGBA}; -use euclid::num::Zero; -use num_traits::ToPrimitive; -use selectors::attr::AttrSelectorOperation; -use servo_arc::Arc; -use std::str::FromStr; - -// Duplicated from script::dom::values. -const UNSIGNED_LONG_MAX: u32 = 2147483647; - -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum LengthOrPercentageOrAuto { - Auto, - Percentage(f32), - Length(Au), -} - -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub enum AttrValue { - String(String), - TokenList(String, Vec<Atom>), - UInt(String, u32), - Int(String, i32), - Double(String, f64), - Atom(Atom), - Length(String, Option<Length>), - Color(String, Option<RGBA>), - Dimension(String, LengthOrPercentageOrAuto), - - /// Stores a URL, computed from the input string and a document's base URL. - /// - /// The URL is resolved at setting-time, so this kind of attribute value is - /// not actually suitable for most URL-reflecting IDL attributes. - ResolvedUrl( - String, - #[ignore_malloc_size_of = "Arc"] Option<Arc<url::Url>> - ), - - /// Note that this variant is only used transitively as a fast path to set - /// the property declaration block relevant to the style of an element when - /// set from the inline declaration of that element (that is, - /// `element.style`). - /// - /// This can, as of this writing, only correspond to the value of the - /// `style` element, and is set from its relevant CSSInlineStyleDeclaration, - /// and then converted to a string in Element::attribute_mutated. - /// - /// Note that we don't necessarily need to do that (we could just clone the - /// declaration block), but that avoids keeping a refcounted - /// declarationblock for longer than needed. - Declaration( - String, - #[ignore_malloc_size_of = "Arc"] Arc<Locked<PropertyDeclarationBlock>>, - ), -} - -/// Shared implementation to parse an integer according to -/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-integers> or -/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-negative-integers> -fn do_parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i64, ()> { - let mut input = input - .skip_while(|c| HTML_SPACE_CHARACTERS.iter().any(|s| s == c)) - .peekable(); - - let sign = match input.peek() { - None => return Err(()), - Some(&'-') => { - input.next(); - -1 - }, - Some(&'+') => { - input.next(); - 1 - }, - Some(_) => 1, - }; - - let (value, _) = read_numbers(input); - - value.and_then(|value| value.checked_mul(sign)).ok_or(()) -} - -/// Parse an integer according to -/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-integers>. -pub fn parse_integer<T: Iterator<Item = char>>(input: T) -> Result<i32, ()> { - do_parse_integer(input).and_then(|result| result.to_i32().ok_or(())) -} - -/// Parse an integer according to -/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-negative-integers> -pub fn parse_unsigned_integer<T: Iterator<Item = char>>(input: T) -> Result<u32, ()> { - do_parse_integer(input).and_then(|result| result.to_u32().ok_or(())) -} - -/// Parse a floating-point number according to -/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-floating-point-number-values> -pub fn parse_double(string: &str) -> Result<f64, ()> { - let trimmed = string.trim_matches(HTML_SPACE_CHARACTERS); - let mut input = trimmed.chars().peekable(); - - let (value, divisor, chars_skipped) = match input.peek() { - None => return Err(()), - Some(&'-') => { - input.next(); - (-1f64, -1f64, 1) - }, - Some(&'+') => { - input.next(); - (1f64, 1f64, 1) - }, - _ => (1f64, 1f64, 0), - }; - - let (value, value_digits) = if let Some(&'.') = input.peek() { - (0f64, 0) - } else { - let (read_val, read_digits) = read_numbers(input); - ( - value * read_val.and_then(|result| result.to_f64()).unwrap_or(1f64), - read_digits, - ) - }; - - let input = trimmed - .chars() - .skip(value_digits + chars_skipped) - .peekable(); - - let (mut value, fraction_digits) = read_fraction(input, divisor, value); - - let input = trimmed - .chars() - .skip(value_digits + chars_skipped + fraction_digits) - .peekable(); - - if let Some(exp) = read_exponent(input) { - value *= 10f64.powi(exp) - }; - - Ok(value) -} - -impl AttrValue { - pub fn from_serialized_tokenlist(tokens: String) -> AttrValue { - let atoms = - split_html_space_chars(&tokens) - .map(Atom::from) - .fold(vec![], |mut acc, atom| { - if !acc.contains(&atom) { - acc.push(atom) - } - acc - }); - AttrValue::TokenList(tokens, atoms) - } - - pub fn from_comma_separated_tokenlist(tokens: String) -> AttrValue { - let atoms = split_commas(&tokens) - .map(Atom::from) - .fold(vec![], |mut acc, atom| { - if !acc.contains(&atom) { - acc.push(atom) - } - acc - }); - AttrValue::TokenList(tokens, atoms) - } - - pub fn from_atomic_tokens(atoms: Vec<Atom>) -> AttrValue { - // TODO(ajeffrey): effecient conversion of Vec<Atom> to String - let tokens = String::from(str_join(&atoms, "\x20")); - AttrValue::TokenList(tokens, atoms) - } - - // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-unsigned-long - pub fn from_u32(string: String, default: u32) -> AttrValue { - let result = parse_unsigned_integer(string.chars()).unwrap_or(default); - let result = if result > UNSIGNED_LONG_MAX { - default - } else { - result - }; - AttrValue::UInt(string, result) - } - - pub fn from_i32(string: String, default: i32) -> AttrValue { - let result = parse_integer(string.chars()).unwrap_or(default); - AttrValue::Int(string, result) - } - - // https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes:idl-double - pub fn from_double(string: String, default: f64) -> AttrValue { - let result = parse_double(&string).unwrap_or(default); - - if result.is_normal() { - AttrValue::Double(string, result) - } else { - AttrValue::Double(string, default) - } - } - - // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers - pub fn from_limited_i32(string: String, default: i32) -> AttrValue { - let result = parse_integer(string.chars()).unwrap_or(default); - - if result < 0 { - AttrValue::Int(string, default) - } else { - AttrValue::Int(string, result) - } - } - - // https://html.spec.whatwg.org/multipage/#limited-to-only-non-negative-numbers-greater-than-zero - pub fn from_limited_u32(string: String, default: u32) -> AttrValue { - let result = parse_unsigned_integer(string.chars()).unwrap_or(default); - let result = if result == 0 || result > UNSIGNED_LONG_MAX { - default - } else { - result - }; - AttrValue::UInt(string, result) - } - - pub fn from_atomic(string: String) -> AttrValue { - let value = Atom::from(string); - AttrValue::Atom(value) - } - - pub fn from_resolved_url(base: &Arc<::url::Url>, url: String) -> AttrValue { - let joined = base.join(&url).ok().map(Arc::new); - AttrValue::ResolvedUrl(url, joined) - } - - pub fn from_legacy_color(string: String) -> AttrValue { - let parsed = parse_legacy_color(&string).ok(); - AttrValue::Color(string, parsed) - } - - pub fn from_dimension(string: String) -> AttrValue { - let parsed = parse_length(&string); - AttrValue::Dimension(string, parsed) - } - - pub fn from_nonzero_dimension(string: String) -> AttrValue { - let parsed = parse_nonzero_length(&string); - AttrValue::Dimension(string, parsed) - } - - /// Assumes the `AttrValue` is a `TokenList` and returns its tokens - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `TokenList` - pub fn as_tokens(&self) -> &[Atom] { - match *self { - AttrValue::TokenList(_, ref tokens) => tokens, - _ => panic!("Tokens not found"), - } - } - - /// Assumes the `AttrValue` is an `Atom` and returns its value - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not an `Atom` - pub fn as_atom(&self) -> &Atom { - match *self { - AttrValue::Atom(ref value) => value, - _ => panic!("Atom not found"), - } - } - - /// Assumes the `AttrValue` is a `Color` and returns its value - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `Color` - pub fn as_color(&self) -> Option<&RGBA> { - match *self { - AttrValue::Color(_, ref color) => color.as_ref(), - _ => panic!("Color not found"), - } - } - - /// Assumes the `AttrValue` is a `Dimension` and returns its value - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `Dimension` - pub fn as_dimension(&self) -> &LengthOrPercentageOrAuto { - match *self { - AttrValue::Dimension(_, ref l) => l, - _ => panic!("Dimension not found"), - } - } - - /// Assumes the `AttrValue` is a `ResolvedUrl` and returns its value. - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `ResolvedUrl` - pub fn as_resolved_url(&self) -> Option<&Arc<::url::Url>> { - match *self { - AttrValue::ResolvedUrl(_, ref url) => url.as_ref(), - _ => panic!("Url not found"), - } - } - - /// Return the AttrValue as its integer representation, if any. - /// This corresponds to attribute values returned as `AttrValue::UInt(_)` - /// by `VirtualMethods::parse_plain_attribute()`. - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `UInt` - pub fn as_uint(&self) -> u32 { - if let AttrValue::UInt(_, value) = *self { - value - } else { - panic!("Uint not found"); - } - } - - /// Return the AttrValue as a dimension computed from its integer - /// representation, assuming that integer representation specifies pixels. - /// - /// This corresponds to attribute values returned as `AttrValue::UInt(_)` - /// by `VirtualMethods::parse_plain_attribute()`. - /// - /// ## Panics - /// - /// Panics if the `AttrValue` is not a `UInt` - pub fn as_uint_px_dimension(&self) -> LengthOrPercentageOrAuto { - if let AttrValue::UInt(_, value) = *self { - LengthOrPercentageOrAuto::Length(Au::from_px(value as i32)) - } else { - panic!("Uint not found"); - } - } - - pub fn eval_selector(&self, selector: &AttrSelectorOperation<&AtomString>) -> bool { - // FIXME(SimonSapin) this can be more efficient by matching on `(self, selector)` variants - // and doing Atom comparisons instead of string comparisons where possible, - // with SelectorImpl::AttrValue changed to Atom. - selector.eval_str(self) - } -} - -impl ::std::ops::Deref for AttrValue { - type Target = str; - - fn deref(&self) -> &str { - match *self { - AttrValue::String(ref value) | - AttrValue::TokenList(ref value, _) | - AttrValue::UInt(ref value, _) | - AttrValue::Double(ref value, _) | - AttrValue::Length(ref value, _) | - AttrValue::Color(ref value, _) | - AttrValue::Int(ref value, _) | - AttrValue::ResolvedUrl(ref value, _) | - AttrValue::Declaration(ref value, _) | - AttrValue::Dimension(ref value, _) => &value, - AttrValue::Atom(ref value) => &value, - } - } -} - -impl PartialEq<Atom> for AttrValue { - fn eq(&self, other: &Atom) -> bool { - match *self { - AttrValue::Atom(ref value) => value == other, - _ => other == &**self, - } - } -} - -/// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-non-zero-dimension-values> -pub fn parse_nonzero_length(value: &str) -> LengthOrPercentageOrAuto { - match parse_length(value) { - LengthOrPercentageOrAuto::Length(x) if x == Au::zero() => LengthOrPercentageOrAuto::Auto, - LengthOrPercentageOrAuto::Percentage(x) if x == 0. => LengthOrPercentageOrAuto::Auto, - x => x, - } -} - -/// Parses a [legacy color][color]. If unparseable, `Err` is returned. -/// -/// [color]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-colour-value -pub fn parse_legacy_color(mut input: &str) -> Result<RGBA, ()> { - // Steps 1 and 2. - if input.is_empty() { - return Err(()); - } - - // Step 3. - input = input.trim_matches(HTML_SPACE_CHARACTERS); - - // Step 4. - if input.eq_ignore_ascii_case("transparent") { - return Err(()); - } - - // Step 5. - if let Ok(Color::Rgba(rgba)) = cssparser::parse_color_keyword(input) { - return Ok(rgba); - } - - // Step 6. - if input.len() == 4 { - if let (b'#', Ok(r), Ok(g), Ok(b)) = ( - input.as_bytes()[0], - hex(input.as_bytes()[1] as char), - hex(input.as_bytes()[2] as char), - hex(input.as_bytes()[3] as char), - ) { - return Ok(RGBA::new(Some(r * 17), Some(g * 17), Some(b * 17), Some(1.0))); - } - } - - // Step 7. - let mut new_input = String::new(); - for ch in input.chars() { - if ch as u32 > 0xffff { - new_input.push_str("00") - } else { - new_input.push(ch) - } - } - let mut input = &*new_input; - - // Step 8. - for (char_count, (index, _)) in input.char_indices().enumerate() { - if char_count == 128 { - input = &input[..index]; - break; - } - } - - // Step 9. - if input.as_bytes()[0] == b'#' { - input = &input[1..] - } - - // Step 10. - let mut new_input = Vec::new(); - for ch in input.chars() { - if hex(ch).is_ok() { - new_input.push(ch as u8) - } else { - new_input.push(b'0') - } - } - let mut input = new_input; - - // Step 11. - while input.is_empty() || (input.len() % 3) != 0 { - input.push(b'0') - } - - // Step 12. - let mut length = input.len() / 3; - let (mut red, mut green, mut blue) = ( - &input[..length], - &input[length..length * 2], - &input[length * 2..], - ); - - // Step 13. - if length > 8 { - red = &red[length - 8..]; - green = &green[length - 8..]; - blue = &blue[length - 8..]; - length = 8 - } - - // Step 14. - while length > 2 && red[0] == b'0' && green[0] == b'0' && blue[0] == b'0' { - red = &red[1..]; - green = &green[1..]; - blue = &blue[1..]; - length -= 1 - } - - // Steps 15-20. - return Ok(RGBA::new( - Some(hex_string(red).unwrap()), - Some(hex_string(green).unwrap()), - Some(hex_string(blue).unwrap()), - Some(1.0), - )); - - fn hex(ch: char) -> Result<u8, ()> { - match ch { - '0'..='9' => Ok((ch as u8) - b'0'), - 'a'..='f' => Ok((ch as u8) - b'a' + 10), - 'A'..='F' => Ok((ch as u8) - b'A' + 10), - _ => Err(()), - } - } - - fn hex_string(string: &[u8]) -> Result<u8, ()> { - match string.len() { - 0 => Err(()), - 1 => hex(string[0] as char), - _ => { - let upper = hex(string[0] as char)?; - let lower = hex(string[1] as char)?; - Ok((upper << 4) | lower) - }, - } - } -} - -/// Parses a [dimension value][dim]. If unparseable, `Auto` is returned. -/// -/// [dim]: https://html.spec.whatwg.org/multipage/#rules-for-parsing-dimension-values -// TODO: this function can be rewritten to return Result<LengthPercentage, _> -pub fn parse_length(mut value: &str) -> LengthOrPercentageOrAuto { - // Steps 1 & 2 are not relevant - - // Step 3 - value = value.trim_start_matches(HTML_SPACE_CHARACTERS); - - // Step 4 - match value.chars().nth(0) { - Some('0'..='9') => {}, - _ => return LengthOrPercentageOrAuto::Auto, - } - - // Steps 5 to 8 - // We trim the string length to the minimum of: - // 1. the end of the string - // 2. the first occurence of a '%' (U+0025 PERCENT SIGN) - // 3. the second occurrence of a '.' (U+002E FULL STOP) - // 4. the occurrence of a character that is neither a digit nor '%' nor '.' - // Note: Step 7.4 is directly subsumed by FromStr::from_str - let mut end_index = value.len(); - let (mut found_full_stop, mut found_percent) = (false, false); - for (i, ch) in value.chars().enumerate() { - match ch { - '0'..='9' => continue, - '%' => { - found_percent = true; - end_index = i; - break; - }, - '.' if !found_full_stop => { - found_full_stop = true; - continue; - }, - _ => { - end_index = i; - break; - }, - } - } - value = &value[..end_index]; - - if found_percent { - let result: Result<f32, _> = FromStr::from_str(value); - match result { - Ok(number) => return LengthOrPercentageOrAuto::Percentage((number as f32) / 100.0), - Err(_) => return LengthOrPercentageOrAuto::Auto, - } - } - - match FromStr::from_str(value) { - Ok(number) => LengthOrPercentageOrAuto::Length(Au::from_f64_px(number)), - Err(_) => LengthOrPercentageOrAuto::Auto, - } -} - -/// A struct that uniquely identifies an element's attribute. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct AttrIdentifier { - pub local_name: LocalName, - pub name: LocalName, - pub namespace: Namespace, - pub prefix: Option<Prefix>, -} diff --git a/components/style/author_styles.rs b/components/style/author_styles.rs deleted file mode 100644 index a0223dceccc..00000000000 --- a/components/style/author_styles.rs +++ /dev/null @@ -1,70 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A set of author stylesheets and their computed representation, such as the -//! ones used for ShadowRoot. - -use crate::dom::TElement; -use crate::invalidation::media_queries::ToMediaListKey; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheet_set::AuthorStylesheetSet; -use crate::stylesheets::StylesheetInDocument; -use crate::stylist::CascadeData; -use crate::stylist::Stylist; -use servo_arc::Arc; - -/// A set of author stylesheets and their computed representation, such as the -/// ones used for ShadowRoot. -#[derive(MallocSizeOf)] -pub struct GenericAuthorStyles<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The sheet collection, which holds the sheet pointers, the invalidations, - /// and all that stuff. - pub stylesheets: AuthorStylesheetSet<S>, - /// The actual cascade data computed from the stylesheets. - #[ignore_malloc_size_of = "Measured as part of the stylist"] - pub data: Arc<CascadeData>, -} - -pub use self::GenericAuthorStyles as AuthorStyles; - -lazy_static! { - static ref EMPTY_CASCADE_DATA: Arc<CascadeData> = Arc::new_leaked(CascadeData::new()); -} - -impl<S> GenericAuthorStyles<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Create an empty AuthorStyles. - #[inline] - pub fn new() -> Self { - Self { - stylesheets: AuthorStylesheetSet::new(), - data: EMPTY_CASCADE_DATA.clone(), - } - } - - /// Flush the pending sheet changes, updating `data` as appropriate. - /// - /// TODO(emilio): Need a host element and a snapshot map to do invalidation - /// properly. - #[inline] - pub fn flush<E>(&mut self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard) - where - E: TElement, - S: ToMediaListKey, - { - let flusher = self - .stylesheets - .flush::<E>(/* host = */ None, /* snapshot_map = */ None); - - let result = stylist.rebuild_author_data(&self.data, flusher.sheets, guard); - if let Ok(Some(new_data)) = result { - self.data = new_data; - } - } -} diff --git a/components/style/bezier.rs b/components/style/bezier.rs deleted file mode 100644 index dd520ac0ed5..00000000000 --- a/components/style/bezier.rs +++ /dev/null @@ -1,176 +0,0 @@ -/* 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/. */ - -//! Parametric Bézier curves. -//! -//! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit. - -#![deny(missing_docs)] - -use crate::values::CSSFloat; - -const NEWTON_METHOD_ITERATIONS: u8 = 8; - -/// A unit cubic Bézier curve, used for timing functions in CSS transitions and animations. -pub struct Bezier { - ax: f64, - bx: f64, - cx: f64, - ay: f64, - by: f64, - cy: f64, -} - -impl Bezier { - /// Calculate the output of a unit cubic Bézier curve from the two middle control points. - /// - /// X coordinate is time, Y coordinate is function advancement. - /// The nominal range for both is 0 to 1. - /// - /// The start and end points are always (0, 0) and (1, 1) so that a transition or animation - /// starts at 0% and ends at 100%. - pub fn calculate_bezier_output( - progress: f64, - epsilon: f64, - x1: f32, - y1: f32, - x2: f32, - y2: f32, - ) -> f64 { - // Check for a linear curve. - if x1 == y1 && x2 == y2 { - return progress; - } - - // Ensure that we return 0 or 1 on both edges. - if progress == 0.0 { - return 0.0; - } - if progress == 1.0 { - return 1.0; - } - - // For negative values, try to extrapolate with tangent (p1 - p0) or, - // if p1 is coincident with p0, with (p2 - p0). - if progress < 0.0 { - if x1 > 0.0 { - return progress * y1 as f64 / x1 as f64; - } - if y1 == 0.0 && x2 > 0.0 { - return progress * y2 as f64 / x2 as f64; - } - // If we can't calculate a sensible tangent, don't extrapolate at all. - return 0.0; - } - - // For values greater than 1, try to extrapolate with tangent (p2 - p3) or, - // if p2 is coincident with p3, with (p1 - p3). - if progress > 1.0 { - if x2 < 1.0 { - return 1.0 + (progress - 1.0) * (y2 as f64 - 1.0) / (x2 as f64 - 1.0); - } - if y2 == 1.0 && x1 < 1.0 { - return 1.0 + (progress - 1.0) * (y1 as f64 - 1.0) / (x1 as f64 - 1.0); - } - // If we can't calculate a sensible tangent, don't extrapolate at all. - return 1.0; - } - - Bezier::new(x1, y1, x2, y2).solve(progress, epsilon) - } - - #[inline] - fn new(x1: CSSFloat, y1: CSSFloat, x2: CSSFloat, y2: CSSFloat) -> Bezier { - let cx = 3. * x1 as f64; - let bx = 3. * (x2 as f64 - x1 as f64) - cx; - - let cy = 3. * y1 as f64; - let by = 3. * (y2 as f64 - y1 as f64) - cy; - - Bezier { - ax: 1.0 - cx - bx, - bx: bx, - cx: cx, - ay: 1.0 - cy - by, - by: by, - cy: cy, - } - } - - #[inline] - fn sample_curve_x(&self, t: f64) -> f64 { - // ax * t^3 + bx * t^2 + cx * t - ((self.ax * t + self.bx) * t + self.cx) * t - } - - #[inline] - fn sample_curve_y(&self, t: f64) -> f64 { - ((self.ay * t + self.by) * t + self.cy) * t - } - - #[inline] - fn sample_curve_derivative_x(&self, t: f64) -> f64 { - (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx - } - - #[inline] - fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 { - // Fast path: Use Newton's method. - let mut t = x; - for _ in 0..NEWTON_METHOD_ITERATIONS { - let x2 = self.sample_curve_x(t); - if x2.approx_eq(x, epsilon) { - return t; - } - let dx = self.sample_curve_derivative_x(t); - if dx.approx_eq(0.0, 1e-6) { - break; - } - t -= (x2 - x) / dx; - } - - // Slow path: Use bisection. - let (mut lo, mut hi, mut t) = (0.0, 1.0, x); - - if t < lo { - return lo; - } - if t > hi { - return hi; - } - - while lo < hi { - let x2 = self.sample_curve_x(t); - if x2.approx_eq(x, epsilon) { - return t; - } - if x > x2 { - lo = t - } else { - hi = t - } - t = (hi - lo) / 2.0 + lo - } - - t - } - - /// Solve the bezier curve for a given `x` and an `epsilon`, that should be - /// between zero and one. - #[inline] - fn solve(&self, x: f64, epsilon: f64) -> f64 { - self.sample_curve_y(self.solve_curve_x(x, epsilon)) - } -} - -trait ApproxEq { - fn approx_eq(self, value: Self, epsilon: Self) -> bool; -} - -impl ApproxEq for f64 { - #[inline] - fn approx_eq(self, value: f64, epsilon: f64) -> bool { - (self - value).abs() < epsilon - } -} diff --git a/components/style/bloom.rs b/components/style/bloom.rs deleted file mode 100644 index dc722b7bdba..00000000000 --- a/components/style/bloom.rs +++ /dev/null @@ -1,401 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The style bloom filter is used as an optimization when matching deep -//! descendant selectors. - -#![deny(missing_docs)] - -use crate::dom::{SendElement, TElement}; -use crate::LocalName; -use atomic_refcell::{AtomicRefCell, AtomicRefMut}; -use owning_ref::OwningHandle; -use selectors::bloom::BloomFilter; -use servo_arc::Arc; -use smallvec::SmallVec; -use std::mem::ManuallyDrop; - -thread_local! { - /// Bloom filters are large allocations, so we store them in thread-local storage - /// such that they can be reused across style traversals. StyleBloom is responsible - /// for ensuring that the bloom filter is zeroed when it is dropped. - /// - /// We intentionally leak this from TLS because we don't have the guarantee - /// of TLS destructors to run in worker threads. - /// - /// We could change this once https://github.com/rayon-rs/rayon/issues/688 - /// is fixed, hopefully. - static BLOOM_KEY: ManuallyDrop<Arc<AtomicRefCell<BloomFilter>>> = - ManuallyDrop::new(Arc::new_leaked(Default::default())); -} - -/// A struct that allows us to fast-reject deep descendant selectors avoiding -/// selector-matching. -/// -/// This is implemented using a counting bloom filter, and it's a standard -/// optimization. See Gecko's `AncestorFilter`, and Blink's and WebKit's -/// `SelectorFilter`. -/// -/// The constraints for Servo's style system are a bit different compared to -/// traditional style systems given Servo does a parallel breadth-first -/// traversal instead of a sequential depth-first traversal. -/// -/// This implies that we need to track a bit more state than other browsers to -/// ensure we're doing the correct thing during the traversal, and being able to -/// apply this optimization effectively. -/// -/// Concretely, we have a bloom filter instance per worker thread, and we track -/// the current DOM depth in order to find a common ancestor when it doesn't -/// match the previous element we've styled. -/// -/// This is usually a pretty fast operation (we use to be one level deeper than -/// the previous one), but in the case of work-stealing, we may needed to push -/// and pop multiple elements. -/// -/// See the `insert_parents_recovering`, where most of the magic happens. -/// -/// Regarding thread-safety, this struct is safe because: -/// -/// * We clear this after a restyle. -/// * The DOM shape and attributes (and every other thing we access here) are -/// immutable during a restyle. -/// -pub struct StyleBloom<E: TElement> { - /// A handle to the bloom filter from the thread upon which this StyleBloom - /// was created. We use AtomicRefCell so that this is all |Send|, which allows - /// StyleBloom to live in ThreadLocalStyleContext, which is dropped from the - /// parent thread. - filter: OwningHandle<Arc<AtomicRefCell<BloomFilter>>, AtomicRefMut<'static, BloomFilter>>, - - /// The stack of elements that this bloom filter contains, along with the - /// number of hashes pushed for each element. - elements: SmallVec<[PushedElement<E>; 16]>, - - /// Stack of hashes that have been pushed onto this filter. - pushed_hashes: SmallVec<[u32; 64]>, -} - -/// The very rough benchmarks in the selectors crate show clear() -/// costing about 25 times more than remove_hash(). We use this to implement -/// clear() more efficiently when only a small number of hashes have been -/// pushed. -/// -/// One subtly to note is that remove_hash() will not touch the value -/// if the filter overflowed. However, overflow can only occur if we -/// get 255 collisions on the same hash value, and 25 < 255. -const MEMSET_CLEAR_THRESHOLD: usize = 25; - -struct PushedElement<E: TElement> { - /// The element that was pushed. - element: SendElement<E>, - - /// The number of hashes pushed for the element. - num_hashes: usize, -} - -impl<E: TElement> PushedElement<E> { - fn new(el: E, num_hashes: usize) -> Self { - PushedElement { - element: unsafe { SendElement::new(el) }, - num_hashes, - } - } -} - -/// Returns whether the attribute name is excluded from the bloom filter. -/// -/// We do this for attributes that are very common but not commonly used in -/// selectors. -#[inline] -pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool { - return *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style") -} - -fn each_relevant_element_hash<E, F>(element: E, mut f: F) -where - E: TElement, - F: FnMut(u32), -{ - f(element.local_name().get_hash()); - f(element.namespace().get_hash()); - - if let Some(id) = element.id() { - f(id.get_hash()); - } - - element.each_class(|class| f(class.get_hash())); - - element.each_attr_name(|name| { - if !is_attr_name_excluded_from_filter(name) { - f(name.get_hash()) - } - }); -} - -impl<E: TElement> Drop for StyleBloom<E> { - fn drop(&mut self) { - // Leave the reusable bloom filter in a zeroed state. - self.clear(); - } -} - -impl<E: TElement> StyleBloom<E> { - /// Create an empty `StyleBloom`. Because StyleBloom acquires the thread- - /// local filter buffer, creating multiple live StyleBloom instances at - /// the same time on the same thread will panic. - - // Forced out of line to limit stack frame sizes after extra inlining from - // https://github.com/rust-lang/rust/pull/43931 - // - // See https://github.com/servo/servo/pull/18420#issuecomment-328769322 - #[inline(never)] - pub fn new() -> Self { - let bloom_arc = BLOOM_KEY.with(|b| Arc::clone(&*b)); - let filter = - OwningHandle::new_with_fn(bloom_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut()); - debug_assert!( - filter.is_zeroed(), - "Forgot to zero the bloom filter last time" - ); - StyleBloom { - filter, - elements: Default::default(), - pushed_hashes: Default::default(), - } - } - - /// Return the bloom filter used properly by the `selectors` crate. - pub fn filter(&self) -> &BloomFilter { - &*self.filter - } - - /// Push an element to the bloom filter, knowing that it's a child of the - /// last element parent. - pub fn push(&mut self, element: E) { - if cfg!(debug_assertions) { - if self.elements.is_empty() { - assert!(element.traversal_parent().is_none()); - } - } - self.push_internal(element); - } - - /// Same as `push`, but without asserting, in order to use it from - /// `rebuild`. - fn push_internal(&mut self, element: E) { - let mut count = 0; - each_relevant_element_hash(element, |hash| { - count += 1; - self.filter.insert_hash(hash); - self.pushed_hashes.push(hash); - }); - self.elements.push(PushedElement::new(element, count)); - } - - /// Pop the last element in the bloom filter and return it. - #[inline] - fn pop(&mut self) -> Option<E> { - let PushedElement { - element, - num_hashes, - } = self.elements.pop()?; - let popped_element = *element; - - // Verify that the pushed hashes match the ones we'd get from the element. - let mut expected_hashes = vec![]; - if cfg!(debug_assertions) { - each_relevant_element_hash(popped_element, |hash| expected_hashes.push(hash)); - } - - for _ in 0..num_hashes { - let hash = self.pushed_hashes.pop().unwrap(); - debug_assert_eq!(expected_hashes.pop().unwrap(), hash); - self.filter.remove_hash(hash); - } - - Some(popped_element) - } - - /// Returns the DOM depth of elements that can be correctly - /// matched against the bloom filter (that is, the number of - /// elements in our list). - pub fn matching_depth(&self) -> usize { - self.elements.len() - } - - /// Clears the bloom filter. - pub fn clear(&mut self) { - self.elements.clear(); - - if self.pushed_hashes.len() > MEMSET_CLEAR_THRESHOLD { - self.filter.clear(); - self.pushed_hashes.clear(); - } else { - for hash in self.pushed_hashes.drain(..) { - self.filter.remove_hash(hash); - } - debug_assert!(self.filter.is_zeroed()); - } - } - - /// Rebuilds the bloom filter up to the parent of the given element. - pub fn rebuild(&mut self, mut element: E) { - self.clear(); - - let mut parents_to_insert = SmallVec::<[E; 16]>::new(); - while let Some(parent) = element.traversal_parent() { - parents_to_insert.push(parent); - element = parent; - } - - for parent in parents_to_insert.drain(..).rev() { - self.push(parent); - } - } - - /// In debug builds, asserts that all the parents of `element` are in the - /// bloom filter. - /// - /// Goes away in release builds. - pub fn assert_complete(&self, mut element: E) { - if cfg!(debug_assertions) { - let mut checked = 0; - while let Some(parent) = element.traversal_parent() { - assert_eq!( - parent, - *(self.elements[self.elements.len() - 1 - checked].element) - ); - element = parent; - checked += 1; - } - assert_eq!(checked, self.elements.len()); - } - } - - /// Get the element that represents the chain of things inserted - /// into the filter right now. That chain is the given element - /// (if any) and its ancestors. - #[inline] - pub fn current_parent(&self) -> Option<E> { - self.elements.last().map(|ref el| *el.element) - } - - /// Insert the parents of an element in the bloom filter, trying to recover - /// the filter if the last element inserted doesn't match. - /// - /// Gets the element depth in the dom, to make it efficient, or if not - /// provided always rebuilds the filter from scratch. - /// - /// Returns the new bloom filter depth, that the traversal code is - /// responsible to keep around if it wants to get an effective filter. - pub fn insert_parents_recovering(&mut self, element: E, element_depth: usize) { - // Easy case, we're in a different restyle, or we're empty. - if self.elements.is_empty() { - self.rebuild(element); - return; - } - - let traversal_parent = match element.traversal_parent() { - Some(parent) => parent, - None => { - // Yay, another easy case. - self.clear(); - return; - }, - }; - - if self.current_parent() == Some(traversal_parent) { - // Ta da, cache hit, we're all done. - return; - } - - if element_depth == 0 { - self.clear(); - return; - } - - // We should've early exited above. - debug_assert!( - element_depth != 0, - "We should have already cleared the bloom filter" - ); - debug_assert!(!self.elements.is_empty(), "How! We should've just rebuilt!"); - - // Now the fun begins: We have the depth of the dom and the depth of the - // last element inserted in the filter, let's try to find a common - // parent. - // - // The current depth, that is, the depth of the last element inserted in - // the bloom filter, is the number of elements _minus one_, that is: if - // there's one element, it must be the root -> depth zero. - let mut current_depth = self.elements.len() - 1; - - // If the filter represents an element too deep in the dom, we need to - // pop ancestors. - while current_depth > element_depth - 1 { - self.pop().expect("Emilio is bad at math"); - current_depth -= 1; - } - - // Now let's try to find a common parent in the bloom filter chain, - // starting with traversal_parent. - let mut common_parent = traversal_parent; - let mut common_parent_depth = element_depth - 1; - - // Let's collect the parents we are going to need to insert once we've - // found the common one. - let mut parents_to_insert = SmallVec::<[E; 16]>::new(); - - // If the bloom filter still doesn't have enough elements, the common - // parent is up in the dom. - while common_parent_depth > current_depth { - // TODO(emilio): Seems like we could insert parents here, then - // reverse the slice. - parents_to_insert.push(common_parent); - common_parent = common_parent.traversal_parent().expect("We were lied to"); - common_parent_depth -= 1; - } - - // Now the two depths are the same. - debug_assert_eq!(common_parent_depth, current_depth); - - // Happy case: The parents match, we only need to push the ancestors - // we've collected and we'll never enter in this loop. - // - // Not-so-happy case: Parent's don't match, so we need to keep going up - // until we find a common ancestor. - // - // Gecko currently models native anonymous content that conceptually - // hangs off the document (such as scrollbars) as a separate subtree - // from the document root. - // - // Thus it's possible with Gecko that we do not find any common - // ancestor. - while *(self.elements.last().unwrap().element) != common_parent { - parents_to_insert.push(common_parent); - self.pop().unwrap(); - common_parent = match common_parent.traversal_parent() { - Some(parent) => parent, - None => { - debug_assert!(self.elements.is_empty()); - if cfg!(feature = "gecko") { - break; - } else { - panic!("should have found a common ancestor"); - } - }, - } - } - - // Now the parents match, so insert the stack of elements we have been - // collecting so far. - for parent in parents_to_insert.drain(..).rev() { - self.push(parent); - } - - debug_assert_eq!(self.elements.len(), element_depth); - - // We're done! Easy. - } -} diff --git a/components/style/build.rs b/components/style/build.rs deleted file mode 100644 index eacb9b3fc99..00000000000 --- a/components/style/build.rs +++ /dev/null @@ -1,87 +0,0 @@ -/* 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/. */ - -#[macro_use] -extern crate lazy_static; - -use std::env; -use std::path::Path; -use std::process::{exit, Command}; -use walkdir::WalkDir; - -#[cfg(feature = "gecko")] -mod build_gecko; - -#[cfg(not(feature = "gecko"))] -mod build_gecko { - pub fn generate() {} -} - -lazy_static! { - pub static ref PYTHON: String = env::var("PYTHON3").ok().unwrap_or_else(|| { - let candidates = if cfg!(windows) { - ["python.exe"] - } else { - ["python3"] - }; - for &name in &candidates { - if Command::new(name) - .arg("--version") - .output() - .ok() - .map_or(false, |out| out.status.success()) - { - return name.to_owned(); - } - } - panic!( - "Can't find python (tried {})! Try fixing PATH or setting the PYTHON3 env var", - candidates.join(", ") - ) - }); -} - -fn generate_properties(engine: &str) { - for entry in WalkDir::new("properties") { - let entry = entry.unwrap(); - match entry.path().extension().and_then(|e| e.to_str()) { - Some("mako") | Some("rs") | Some("py") | Some("zip") => { - println!("cargo:rerun-if-changed={}", entry.path().display()); - }, - _ => {}, - } - } - - let script = Path::new(&env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("properties") - .join("build.py"); - - let status = Command::new(&*PYTHON) - .arg(&script) - .arg(engine) - .arg("style-crate") - .status() - .unwrap(); - if !status.success() { - exit(1) - } -} - -fn main() { - let gecko = cfg!(feature = "gecko"); - let servo = cfg!(feature = "servo"); - let engine = match (gecko, servo) { - (true, false) => "gecko", - (false, true) => "servo", - _ => panic!( - "\n\n\ - The style crate requires enabling one of its 'servo' or 'gecko' feature flags. \ - \n\n" - ), - }; - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:out_dir={}", env::var("OUT_DIR").unwrap()); - generate_properties(engine); - build_gecko::generate(); -} diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs deleted file mode 100644 index a83c5dbc6d6..00000000000 --- a/components/style/build_gecko.rs +++ /dev/null @@ -1,400 +0,0 @@ -/* 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/. */ - -use super::PYTHON; -use bindgen::{Builder, CodegenConfig}; -use regex::Regex; -use std::cmp; -use std::collections::HashSet; -use std::env; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; -use std::process::{exit, Command}; -use std::slice; -use std::sync::Mutex; -use std::time::SystemTime; -use toml; -use toml::value::Table; - -lazy_static! { - static ref OUTDIR_PATH: PathBuf = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("gecko"); -} - -const STRUCTS_FILE: &'static str = "structs.rs"; - -fn read_config(path: &PathBuf) -> Table { - println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); - update_last_modified(&path); - - let mut contents = String::new(); - File::open(path) - .expect("Failed to open config file") - .read_to_string(&mut contents) - .expect("Failed to read config file"); - match toml::from_str::<Table>(&contents) { - Ok(result) => result, - Err(e) => panic!("Failed to parse config file: {}", e), - } -} - -lazy_static! { - static ref CONFIG: Table = { - // Load Gecko's binding generator config from the source tree. - let path = mozbuild::TOPSRCDIR.join("layout/style/ServoBindings.toml"); - read_config(&path) - }; - static ref BINDGEN_FLAGS: Vec<String> = { - // Load build-specific config overrides. - let path = mozbuild::TOPOBJDIR.join("layout/style/extra-bindgen-flags"); - println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); - fs::read_to_string(path).expect("Failed to read extra-bindgen-flags file") - .split_whitespace() - .map(std::borrow::ToOwned::to_owned) - .collect() - }; - static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap(); - static ref DISTDIR_PATH: PathBuf = mozbuild::TOPOBJDIR.join("dist"); - static ref SEARCH_PATHS: Vec<PathBuf> = vec![ - DISTDIR_PATH.join("include"), - DISTDIR_PATH.join("include/nspr"), - ]; - static ref ADDED_PATHS: Mutex<HashSet<PathBuf>> = Mutex::new(HashSet::new()); - static ref LAST_MODIFIED: Mutex<SystemTime> = - Mutex::new(get_modified_time(&env::current_exe().unwrap()) - .expect("Failed to get modified time of executable")); -} - -fn get_modified_time(file: &Path) -> Option<SystemTime> { - file.metadata().and_then(|m| m.modified()).ok() -} - -fn update_last_modified(file: &Path) { - let modified = get_modified_time(file).expect("Couldn't get file modification time"); - let mut last_modified = LAST_MODIFIED.lock().unwrap(); - *last_modified = cmp::max(modified, *last_modified); -} - -fn search_include(name: &str) -> Option<PathBuf> { - for path in SEARCH_PATHS.iter() { - let file = path.join(name); - if file.is_file() { - update_last_modified(&file); - return Some(file); - } - } - None -} - -fn add_headers_recursively(path: PathBuf, added_paths: &mut HashSet<PathBuf>) { - if added_paths.contains(&path) { - return; - } - let mut file = File::open(&path).unwrap(); - let mut content = String::new(); - file.read_to_string(&mut content).unwrap(); - added_paths.insert(path); - // Find all includes and add them recursively - for cap in INCLUDE_RE.captures_iter(&content) { - if let Some(path) = search_include(cap.get(1).unwrap().as_str()) { - add_headers_recursively(path, added_paths); - } - } -} - -fn add_include(name: &str) -> String { - let mut added_paths = ADDED_PATHS.lock().unwrap(); - let file = match search_include(name) { - Some(file) => file, - None => panic!("Include not found: {}", name), - }; - let result = String::from(file.to_str().unwrap()); - add_headers_recursively(file, &mut *added_paths); - result -} - -trait BuilderExt { - fn get_initial_builder() -> Builder; - fn include<T: Into<String>>(self, file: T) -> Builder; -} - -impl BuilderExt for Builder { - fn get_initial_builder() -> Builder { - // Disable rust unions, because we replace some types inside of - // them. - let mut builder = Builder::default() - .size_t_is_usize(true) - .disable_untagged_union(); - - let rustfmt_path = env::var_os("RUSTFMT") - // This can be replaced with - // > .filter(|p| !p.is_empty()).map(PathBuf::from) - // once we can use 1.27+. - .and_then(|p| { - if p.is_empty() { - None - } else { - Some(PathBuf::from(p)) - } - }); - if let Some(path) = rustfmt_path { - builder = builder.with_rustfmt(path); - } - - for dir in SEARCH_PATHS.iter() { - builder = builder.clang_arg("-I").clang_arg(dir.to_str().unwrap()); - } - - builder = builder.include(add_include("mozilla-config.h")); - - if env::var("CARGO_FEATURE_GECKO_DEBUG").is_ok() { - builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1"); - } - - for item in &*BINDGEN_FLAGS { - builder = builder.clang_arg(item); - } - - builder - } - fn include<T: Into<String>>(self, file: T) -> Builder { - self.clang_arg("-include").clang_arg(file) - } -} - -struct Fixup { - pat: String, - rep: String, -} - -fn write_binding_file(builder: Builder, file: &str, fixups: &[Fixup]) { - let out_file = OUTDIR_PATH.join(file); - if let Some(modified) = get_modified_time(&out_file) { - // Don't generate the file if nothing it depends on was modified. - let last_modified = LAST_MODIFIED.lock().unwrap(); - if *last_modified <= modified { - return; - } - } - let command_line_opts = builder.command_line_flags(); - let result = builder.generate(); - let mut result = match result { - Ok(bindings) => bindings.to_string(), - Err(_) => { - panic!( - "Failed to generate bindings, flags: {:?}", - command_line_opts - ); - }, - }; - - for fixup in fixups.iter() { - result = Regex::new(&fixup.pat) - .unwrap() - .replace_all(&result, &*fixup.rep) - .into_owned() - .into(); - } - let bytes = result.into_bytes(); - File::create(&out_file) - .unwrap() - .write_all(&bytes) - .expect("Unable to write output"); -} - -struct BuilderWithConfig<'a> { - builder: Builder, - config: &'a Table, - used_keys: HashSet<&'static str>, -} -impl<'a> BuilderWithConfig<'a> { - fn new(builder: Builder, config: &'a Table) -> Self { - BuilderWithConfig { - builder, - config, - used_keys: HashSet::new(), - } - } - - fn handle_list<F>(self, key: &'static str, func: F) -> BuilderWithConfig<'a> - where - F: FnOnce(Builder, slice::Iter<'a, toml::Value>) -> Builder, - { - let mut builder = self.builder; - let config = self.config; - let mut used_keys = self.used_keys; - if let Some(list) = config.get(key) { - used_keys.insert(key); - builder = func(builder, list.as_array().unwrap().as_slice().iter()); - } - BuilderWithConfig { - builder, - config, - used_keys, - } - } - fn handle_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a> - where - F: FnMut(Builder, &'a toml::Value) -> Builder, - { - self.handle_list(key, |b, iter| iter.fold(b, |b, item| func(b, item))) - } - fn handle_str_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a> - where - F: FnMut(Builder, &'a str) -> Builder, - { - self.handle_items(key, |b, item| func(b, item.as_str().unwrap())) - } - fn handle_table_items<F>(self, key: &'static str, mut func: F) -> BuilderWithConfig<'a> - where - F: FnMut(Builder, &'a Table) -> Builder, - { - self.handle_items(key, |b, item| func(b, item.as_table().unwrap())) - } - fn handle_common(self, fixups: &mut Vec<Fixup>) -> BuilderWithConfig<'a> { - self.handle_str_items("headers", |b, item| b.header(add_include(item))) - .handle_str_items("raw-lines", |b, item| b.raw_line(item)) - .handle_str_items("hide-types", |b, item| b.blocklist_type(item)) - .handle_table_items("fixups", |builder, item| { - fixups.push(Fixup { - pat: item["pat"].as_str().unwrap().into(), - rep: item["rep"].as_str().unwrap().into(), - }); - builder - }) - } - - fn get_builder(self) -> Builder { - for key in self.config.keys() { - if !self.used_keys.contains(key.as_str()) { - panic!("Unknown key: {}", key); - } - } - self.builder - } -} - -fn generate_structs() { - let builder = Builder::get_initial_builder() - .enable_cxx_namespaces() - .with_codegen_config(CodegenConfig::TYPES | CodegenConfig::VARS | CodegenConfig::FUNCTIONS); - let mut fixups = vec![]; - let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap()) - .handle_common(&mut fixups) - .handle_str_items("allowlist-functions", |b, item| b.allowlist_function(item)) - .handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item)) - .handle_str_items("rusty-enums", |b, item| b.rustified_enum(item)) - .handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item)) - .handle_str_items("allowlist-types", |b, item| b.allowlist_type(item)) - .handle_str_items("opaque-types", |b, item| b.opaque_type(item)) - .handle_table_items("cbindgen-types", |b, item| { - let gecko = item["gecko"].as_str().unwrap(); - let servo = item["servo"].as_str().unwrap(); - b.blocklist_type(format!("mozilla::{}", gecko)) - .module_raw_line("root::mozilla", format!("pub use {} as {};", servo, gecko)) - }) - .handle_table_items("mapped-generic-types", |builder, item| { - let generic = item["generic"].as_bool().unwrap(); - let gecko = item["gecko"].as_str().unwrap(); - let servo = item["servo"].as_str().unwrap(); - let gecko_name = gecko.rsplit("::").next().unwrap(); - let gecko = gecko - .split("::") - .map(|s| format!("\\s*{}\\s*", s)) - .collect::<Vec<_>>() - .join("::"); - - fixups.push(Fixup { - pat: format!("\\broot\\s*::\\s*{}\\b", gecko), - rep: format!("crate::gecko_bindings::structs::{}", gecko_name), - }); - builder.blocklist_type(gecko).raw_line(format!( - "pub type {0}{2} = {1}{2};", - gecko_name, - servo, - if generic { "<T>" } else { "" } - )) - }) - .get_builder(); - write_binding_file(builder, STRUCTS_FILE, &fixups); -} - -fn setup_logging() -> bool { - struct BuildLogger { - file: Option<Mutex<fs::File>>, - filter: String, - } - - impl log::Log for BuildLogger { - fn enabled(&self, meta: &log::Metadata) -> bool { - self.file.is_some() && meta.target().contains(&self.filter) - } - - fn log(&self, record: &log::Record) { - if !self.enabled(record.metadata()) { - return; - } - - let mut file = self.file.as_ref().unwrap().lock().unwrap(); - let _ = writeln!( - file, - "{} - {} - {} @ {}:{}", - record.level(), - record.target(), - record.args(), - record.file().unwrap_or("<unknown>"), - record.line().unwrap_or(0) - ); - } - - fn flush(&self) { - if let Some(ref file) = self.file { - file.lock().unwrap().flush().unwrap(); - } - } - } - - if let Some(path) = env::var_os("STYLO_BUILD_LOG") { - log::set_max_level(log::LevelFilter::Debug); - log::set_boxed_logger(Box::new(BuildLogger { - file: fs::File::create(path).ok().map(Mutex::new), - filter: env::var("STYLO_BUILD_FILTER") - .ok() - .unwrap_or_else(|| "bindgen".to_owned()), - })) - .expect("Failed to set logger."); - - true - } else { - false - } -} - -fn generate_atoms() { - let script = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()) - .join("gecko") - .join("regen_atoms.py"); - println!("cargo:rerun-if-changed={}", script.display()); - let status = Command::new(&*PYTHON) - .arg(&script) - .arg(DISTDIR_PATH.as_os_str()) - .arg(OUTDIR_PATH.as_os_str()) - .status() - .unwrap(); - if !status.success() { - exit(1); - } -} - -pub fn generate() { - println!("cargo:rerun-if-changed=build_gecko.rs"); - fs::create_dir_all(&*OUTDIR_PATH).unwrap(); - setup_logging(); - generate_structs(); - generate_atoms(); - - for path in ADDED_PATHS.lock().unwrap().iter() { - println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); - } -} diff --git a/components/style/color/convert.rs b/components/style/color/convert.rs deleted file mode 100644 index 4fa037f9d6d..00000000000 --- a/components/style/color/convert.rs +++ /dev/null @@ -1,888 +0,0 @@ -/* 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/. */ - -//! Color conversion algorithms. -//! -//! Algorithms, matrices and constants are from the [color-4] specification, -//! unless otherwise specified: -//! -//! https://drafts.csswg.org/css-color-4/#color-conversion-code -//! -//! NOTE: Matrices has to be transposed from the examples in the spec for use -//! with the `euclid` library. - -use crate::color::ColorComponents; -use std::f32::consts::PI; - -type Transform = euclid::default::Transform3D<f32>; -type Vector = euclid::default::Vector3D<f32>; - -const RAD_PER_DEG: f32 = PI / 180.0; -const DEG_PER_RAD: f32 = 180.0 / PI; - -/// Normalize hue into [0, 360). -#[inline] -fn normalize_hue(hue: f32) -> f32 { - hue - 360. * (hue / 360.).floor() -} - -/// Calculate the hue from RGB components and return it along with the min and -/// max RGB values. -#[inline] -fn rgb_to_hue_min_max(red: f32, green: f32, blue: f32) -> (f32, f32, f32) { - let max = red.max(green).max(blue); - let min = red.min(green).min(blue); - - let delta = max - min; - - let hue = if delta != 0.0 { - 60.0 * if max == red { - (green - blue) / delta + if green < blue { 6.0 } else { 0.0 } - } else if max == green { - (blue - red) / delta + 2.0 - } else { - (red - green) / delta + 4.0 - } - } else { - f32::NAN - }; - - (hue, min, max) -} - -/// Convert a hue value into red, green, blue components. -#[inline] -fn hue_to_rgb(t1: f32, t2: f32, hue: f32) -> f32 { - let hue = normalize_hue(hue); - - if hue * 6.0 < 360.0 { - t1 + (t2 - t1) * hue / 60.0 - } else if hue * 2.0 < 360.0 { - t2 - } else if hue * 3.0 < 720.0 { - t1 + (t2 - t1) * (240.0 - hue) / 60.0 - } else { - t1 - } -} - -/// Convert from HSL notation to RGB notation. -/// https://drafts.csswg.org/css-color-4/#hsl-to-rgb -#[inline] -pub fn hsl_to_rgb(from: &ColorComponents) -> ColorComponents { - let ColorComponents(hue, saturation, lightness) = *from; - - let t2 = if lightness <= 0.5 { - lightness * (saturation + 1.0) - } else { - lightness + saturation - lightness * saturation - }; - let t1 = lightness * 2.0 - t2; - - ColorComponents( - hue_to_rgb(t1, t2, hue + 120.0), - hue_to_rgb(t1, t2, hue), - hue_to_rgb(t1, t2, hue - 120.0), - ) -} - -/// Convert from RGB notation to HSL notation. -/// https://drafts.csswg.org/css-color-4/#rgb-to-hsl -pub fn rgb_to_hsl(from: &ColorComponents) -> ColorComponents { - let ColorComponents(red, green, blue) = *from; - - let (hue, min, max) = rgb_to_hue_min_max(red, green, blue); - - let lightness = (min + max) / 2.0; - let delta = max - min; - - let saturation = if delta != 0.0 { - if lightness == 0.0 || lightness == 1.0 { - 0.0 - } else { - (max - lightness) / lightness.min(1.0 - lightness) - } - } else { - 0.0 - }; - - ColorComponents(hue, saturation, lightness) -} - -/// Convert from HWB notation to RGB notation. -/// https://drafts.csswg.org/css-color-4/#hwb-to-rgb -#[inline] -pub fn hwb_to_rgb(from: &ColorComponents) -> ColorComponents { - let ColorComponents(hue, whiteness, blackness) = *from; - - if whiteness + blackness > 1.0 { - let gray = whiteness / (whiteness + blackness); - return ColorComponents(gray, gray, gray); - } - - let x = 1.0 - whiteness - blackness; - hsl_to_rgb(&ColorComponents(hue, 1.0, 0.5)).map(|v| v * x + whiteness) -} - -/// Convert from RGB notation to HWB notation. -/// https://drafts.csswg.org/css-color-4/#rgb-to-hwb -#[inline] -pub fn rgb_to_hwb(from: &ColorComponents) -> ColorComponents { - let ColorComponents(red, green, blue) = *from; - - let (hue, min, max) = rgb_to_hue_min_max(red, green, blue); - - let whiteness = min; - let blackness = 1.0 - max; - - ColorComponents(hue, whiteness, blackness) -} - -/// Convert from Lab to Lch. This calculation works for both Lab and Olab. -/// <https://drafts.csswg.org/css-color-4/#color-conversion-code> -#[inline] -pub fn lab_to_lch(from: &ColorComponents) -> ColorComponents { - let ColorComponents(lightness, a, b) = *from; - - let hue = normalize_hue(b.atan2(a) * 180.0 / PI); - let chroma = (a.powf(2.0) + b.powf(2.0)).sqrt(); - - ColorComponents(lightness, chroma, hue) -} - -/// Convert from Lch to Lab. This calculation works for both Lch and Oklch. -/// <https://drafts.csswg.org/css-color-4/#color-conversion-code> -#[inline] -pub fn lch_to_lab(from: &ColorComponents) -> ColorComponents { - let ColorComponents(lightness, chroma, hue) = *from; - - let a = chroma * (hue * PI / 180.0).cos(); - let b = chroma * (hue * PI / 180.0).sin(); - - ColorComponents(lightness, a, b) -} - -#[inline] -fn transform(from: &ColorComponents, mat: &Transform) -> ColorComponents { - let result = mat.transform_vector3d(Vector::new(from.0, from.1, from.2)); - ColorComponents(result.x, result.y, result.z) -} - -fn xyz_d65_to_xyz_d50(from: &ColorComponents) -> ColorComponents { - #[rustfmt::skip] - const MAT: Transform = Transform::new( - 1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0.0, - 0.022946793341019088, 0.990434484573249, 0.015055144896577895, 0.0, - -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - transform(from, &MAT) -} - -fn xyz_d50_to_xyz_d65(from: &ColorComponents) -> ColorComponents { - #[rustfmt::skip] - const MAT: Transform = Transform::new( - 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0.0, - -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0.0, - 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - transform(from, &MAT) -} - -/// A reference white that is used during color conversion. -pub enum WhitePoint { - /// D50 white reference. - D50, - /// D65 white reference. - D65, -} - -fn convert_white_point(from: WhitePoint, to: WhitePoint, components: &mut ColorComponents) { - match (from, to) { - (WhitePoint::D50, WhitePoint::D65) => *components = xyz_d50_to_xyz_d65(components), - (WhitePoint::D65, WhitePoint::D50) => *components = xyz_d65_to_xyz_d50(components), - - _ => {}, - } -} - -/// A trait that allows conversion of color spaces to and from XYZ coordinate -/// space with a specified white point. -/// -/// Allows following the specified method of converting between color spaces: -/// - Convert to values to sRGB linear light. -/// - Convert to XYZ coordinate space. -/// - Adjust white point to target white point. -/// - Convert to sRGB linear light in target color space. -/// - Convert to sRGB gamma encoded in target color space. -/// -/// https://drafts.csswg.org/css-color-4/#color-conversion -pub trait ColorSpaceConversion { - /// The white point that the implementer is represented in. - const WHITE_POINT: WhitePoint; - - /// Convert the components from sRGB gamma encoded values to sRGB linear - /// light values. - fn to_linear_light(from: &ColorComponents) -> ColorComponents; - - /// Convert the components from sRGB linear light values to XYZ coordinate - /// space. - fn to_xyz(from: &ColorComponents) -> ColorComponents; - - /// Convert the components from XYZ coordinate space to sRGB linear light - /// values. - fn from_xyz(from: &ColorComponents) -> ColorComponents; - - /// Convert the components from sRGB linear light values to sRGB gamma - /// encoded values. - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents; -} - -/// Convert the color components from the specified color space to XYZ and -/// return the components and the white point they are in. -pub fn to_xyz<From: ColorSpaceConversion>(from: &ColorComponents) -> (ColorComponents, WhitePoint) { - // Convert the color components where in-gamut values are in the range - // [0 - 1] to linear light (un-companded) form. - let result = From::to_linear_light(from); - - // Convert the color components from the source color space to XYZ. - (From::to_xyz(&result), From::WHITE_POINT) -} - -/// Convert the color components from XYZ at the given white point to the -/// specified color space. -pub fn from_xyz<To: ColorSpaceConversion>( - from: &ColorComponents, - white_point: WhitePoint, -) -> ColorComponents { - let mut xyz = from.clone(); - - // Convert the white point if needed. - convert_white_point(white_point, To::WHITE_POINT, &mut xyz); - - // Convert the color from XYZ to the target color space. - let result = To::from_xyz(&xyz); - - // Convert the color components of linear-light values in the range - // [0 - 1] to a gamma corrected form. - To::to_gamma_encoded(&result) -} - -/// The sRGB color space. -/// https://drafts.csswg.org/css-color-4/#predefined-sRGB -pub struct Srgb; - -impl Srgb { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.4123907992659595, 0.21263900587151036, 0.01933081871559185, 0.0, - 0.35758433938387796, 0.7151686787677559, 0.11919477979462599, 0.0, - 0.1804807884018343, 0.07219231536073371, 0.9505321522496606, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 3.2409699419045213, -0.9692436362808798, 0.05563007969699361, 0.0, - -1.5373831775700935, 1.8759675015077206, -0.20397695888897657, 0.0, - -0.4986107602930033, 0.04155505740717561, 1.0569715142428786, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for Srgb { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - let abs = value.abs(); - - if abs < 0.04045 { - value / 12.92 - } else { - value.signum() * ((abs + 0.055) / 1.055).powf(2.4) - } - }) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - let abs = value.abs(); - - if abs > 0.0031308 { - value.signum() * (1.055 * abs.powf(1.0 / 2.4) - 0.055) - } else { - 12.92 * value - } - }) - } -} - -/// Color specified with hue, saturation and lightness components. -pub struct Hsl; - -impl ColorSpaceConversion for Hsl { - const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - Srgb::to_linear_light(&hsl_to_rgb(from)) - } - - #[inline] - fn to_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::to_xyz(from) - } - - #[inline] - fn from_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::from_xyz(from) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - rgb_to_hsl(&Srgb::to_gamma_encoded(from)) - } -} - -/// Color specified with hue, whiteness and blackness components. -pub struct Hwb; - -impl ColorSpaceConversion for Hwb { - const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - Srgb::to_linear_light(&hwb_to_rgb(from)) - } - - #[inline] - fn to_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::to_xyz(from) - } - - #[inline] - fn from_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::from_xyz(from) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - rgb_to_hwb(&Srgb::to_gamma_encoded(from)) - } -} - -/// The same as sRGB color space, except the transfer function is linear light. -/// https://drafts.csswg.org/css-color-4/#predefined-sRGB-linear -pub struct SrgbLinear; - -impl ColorSpaceConversion for SrgbLinear { - const WHITE_POINT: WhitePoint = Srgb::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // Already in linear light form. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::to_xyz(from) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - Srgb::from_xyz(from) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // Stay in linear light form. - from.clone() - } -} - -/// The Display-P3 color space. -/// https://drafts.csswg.org/css-color-4/#predefined-display-p3 -pub struct DisplayP3; - -impl DisplayP3 { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.48657094864821626, 0.22897456406974884, 0.0, 0.0, - 0.26566769316909294, 0.6917385218365062, 0.045113381858902575, 0.0, - 0.1982172852343625, 0.079286914093745, 1.0439443689009757, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 2.4934969119414245, -0.829488969561575, 0.035845830243784335, 0.0, - -0.9313836179191236, 1.7626640603183468, -0.07617238926804171, 0.0, - -0.40271078445071684, 0.02362468584194359, 0.9568845240076873, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for DisplayP3 { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - Srgb::to_linear_light(from) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - Srgb::to_gamma_encoded(from) - } -} - -/// The a98-rgb color space. -/// https://drafts.csswg.org/css-color-4/#predefined-a98-rgb -pub struct A98Rgb; - -impl A98Rgb { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.5766690429101308, 0.29734497525053616, 0.027031361386412378, 0.0, - 0.18555823790654627, 0.627363566255466, 0.07068885253582714, 0.0, - 0.18822864623499472, 0.07529145849399789, 0.9913375368376389, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 2.041587903810746, -0.9692436362808798, 0.013444280632031024, 0.0, - -0.5650069742788596, 1.8759675015077206, -0.11836239223101824, 0.0, - -0.3447313507783295, 0.04155505740717561, 1.0151749943912054, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for A98Rgb { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|v| v.signum() * v.abs().powf(2.19921875)) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone() - .map(|v| v.signum() * v.abs().powf(0.4547069271758437)) - } -} - -/// The ProPhoto RGB color space. -/// https://drafts.csswg.org/css-color-4/#predefined-prophoto-rgb -pub struct ProphotoRgb; - -impl ProphotoRgb { - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.7977604896723027, 0.2880711282292934, 0.0, 0.0, - 0.13518583717574031, 0.7118432178101014, 0.0, 0.0, - 0.0313493495815248, 0.00008565396060525902, 0.8251046025104601, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 1.3457989731028281, -0.5446224939028347, 0.0, 0.0, - -0.25558010007997534, 1.5082327413132781, 0.0, 0.0, - -0.05110628506753401, 0.02053603239147973, 1.2119675456389454, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for ProphotoRgb { - const WHITE_POINT: WhitePoint = WhitePoint::D50; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - const ET2: f32 = 16.0 / 512.0; - - let abs = value.abs(); - - if abs <= ET2 { - value / 16.0 - } else { - value.signum() * abs.powf(1.8) - } - }) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - const ET: f32 = 1.0 / 512.0; - - from.clone().map(|v| { - let abs = v.abs(); - if abs >= ET { - v.signum() * abs.powf(1.0 / 1.8) - } else { - 16.0 * v - } - }) - } -} - -/// The Rec.2020 color space. -/// https://drafts.csswg.org/css-color-4/#predefined-rec2020 -pub struct Rec2020; - -impl Rec2020 { - const ALPHA: f32 = 1.09929682680944; - const BETA: f32 = 0.018053968510807; - - #[rustfmt::skip] - const TO_XYZ: Transform = Transform::new( - 0.6369580483012913, 0.26270021201126703, 0.0, 0.0, - 0.14461690358620838, 0.677998071518871, 0.028072693049087508, 0.0, - 0.16888097516417205, 0.059301716469861945, 1.0609850577107909, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const FROM_XYZ: Transform = Transform::new( - 1.7166511879712676, -0.666684351832489, 0.017639857445310915, 0.0, - -0.3556707837763924, 1.616481236634939, -0.042770613257808655, 0.0, - -0.2533662813736598, 0.01576854581391113, 0.942103121235474, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for Rec2020 { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone().map(|value| { - let abs = value.abs(); - - if abs < Self::BETA * 4.5 { - value / 4.5 - } else { - value.signum() * ((abs + Self::ALPHA - 1.0) / Self::ALPHA).powf(1.0 / 0.45) - } - }) - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - transform(from, &Self::FROM_XYZ) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone().map(|v| { - let abs = v.abs(); - - if abs > Self::BETA { - v.signum() * (Self::ALPHA * abs.powf(0.45) - (Self::ALPHA - 1.0)) - } else { - 4.5 * v - } - }) - } -} - -/// A color in the XYZ coordinate space with a D50 white reference. -/// https://drafts.csswg.org/css-color-4/#predefined-xyz -pub struct XyzD50; - -impl ColorSpaceConversion for XyzD50 { - const WHITE_POINT: WhitePoint = WhitePoint::D50; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone() - } -} - -/// A color in the XYZ coordinate space with a D65 white reference. -/// https://drafts.csswg.org/css-color-4/#predefined-xyz -pub struct XyzD65; - -impl ColorSpaceConversion for XyzD65 { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - from.clone() - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - from.clone() - } -} - -/// The Lab color space. -/// https://drafts.csswg.org/css-color-4/#specifying-lab-lch -pub struct Lab; - -impl Lab { - const KAPPA: f32 = 24389.0 / 27.0; - const EPSILON: f32 = 216.0 / 24389.0; - const WHITE: ColorComponents = ColorComponents(0.96422, 1.0, 0.82521); -} - -impl ColorSpaceConversion for Lab { - const WHITE_POINT: WhitePoint = WhitePoint::D50; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - /// Convert a CIELAB color to XYZ as specified in [1] and [2]. - /// - /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined - /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn to_xyz(from: &ColorComponents) -> ColorComponents { - let f1 = (from.0 + 16.0) / 116.0; - let f0 = (from.1 / 500.0) + f1; - let f2 = f1 - from.2 / 200.0; - - let x = if f0.powf(3.0) > Self::EPSILON { - f0.powf(3.) - } else { - (116.0 * f0 - 16.0) / Self::KAPPA - }; - let y = if from.0 > Self::KAPPA * Self::EPSILON { - ((from.0 + 16.0) / 116.0).powf(3.0) - } else { - from.0 / Self::KAPPA - }; - let z = if f2.powf(3.0) > Self::EPSILON { - f2.powf(3.0) - } else { - (116.0 * f2 - 16.0) / Self::KAPPA - }; - - ColorComponents(x * Self::WHITE.0, y * Self::WHITE.1, z * Self::WHITE.2) - } - - /// Convert an XYZ colour to LAB as specified in [1] and [2]. - /// - /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab - /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from_xyz(from: &ColorComponents) -> ColorComponents { - macro_rules! compute_f { - ($value:expr) => {{ - if $value > Self::EPSILON { - $value.cbrt() - } else { - (Self::KAPPA * $value + 16.0) / 116.0 - } - }}; - } - - // 4. Convert D50-adapted XYZ to Lab. - let f = [ - compute_f!(from.0 / Self::WHITE.0), - compute_f!(from.1 / Self::WHITE.1), - compute_f!(from.2 / Self::WHITE.2), - ]; - - let lightness = 116.0 * f[1] - 16.0; - let a = 500.0 * (f[0] - f[1]); - let b = 200.0 * (f[1] - f[2]); - - ColorComponents(lightness, a, b) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} - -/// The Lch color space. -/// https://drafts.csswg.org/css-color-4/#specifying-lab-lch -pub struct Lch; - -impl ColorSpaceConversion for Lch { - const WHITE_POINT: WhitePoint = Lab::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - // Convert LCH to Lab first. - let hue = from.2 * RAD_PER_DEG; - let a = from.1 * hue.cos(); - let b = from.1 * hue.sin(); - - let lab = ColorComponents(from.0, a, b); - - // Then convert the Lab to XYZ. - Lab::to_xyz(&lab) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - // First convert the XYZ to LAB. - let ColorComponents(lightness, a, b) = Lab::from_xyz(&from); - - // Then conver the Lab to LCH. - let hue = b.atan2(a) * DEG_PER_RAD; - let chroma = (a * a + b * b).sqrt(); - - ColorComponents(lightness, chroma, hue) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} - -/// The Oklab color space. -/// https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch -pub struct Oklab; - -impl Oklab { - #[rustfmt::skip] - const XYZ_TO_LMS: Transform = Transform::new( - 0.8190224432164319, 0.0329836671980271, 0.048177199566046255, 0.0, - 0.3619062562801221, 0.9292868468965546, 0.26423952494422764, 0.0, - -0.12887378261216414, 0.03614466816999844, 0.6335478258136937, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const LMS_TO_OKLAB: Transform = Transform::new( - 0.2104542553, 1.9779984951, 0.0259040371, 0.0, - 0.7936177850, -2.4285922050, 0.7827717662, 0.0, - -0.0040720468, 0.4505937099, -0.8086757660, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const LMS_TO_XYZ: Transform = Transform::new( - 1.2268798733741557, -0.04057576262431372, -0.07637294974672142, 0.0, - -0.5578149965554813, 1.1122868293970594, -0.4214933239627914, 0.0, - 0.28139105017721583, -0.07171106666151701, 1.5869240244272418, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); - - #[rustfmt::skip] - const OKLAB_TO_LMS: Transform = Transform::new( - 0.99999999845051981432, 1.0000000088817607767, 1.0000000546724109177, 0.0, - 0.39633779217376785678, -0.1055613423236563494, -0.089484182094965759684, 0.0, - 0.21580375806075880339, -0.063854174771705903402, -1.2914855378640917399, 0.0, - 0.0, 0.0, 0.0, 1.0, - ); -} - -impl ColorSpaceConversion for Oklab { - const WHITE_POINT: WhitePoint = WhitePoint::D65; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - let lms = transform(&from, &Self::OKLAB_TO_LMS); - let lms = lms.map(|v| v.powf(3.0)); - transform(&lms, &Self::LMS_TO_XYZ) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - let lms = transform(&from, &Self::XYZ_TO_LMS); - let lms = lms.map(|v| v.cbrt()); - transform(&lms, &Self::LMS_TO_OKLAB) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} - -/// The Oklch color space. -/// https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch -pub struct Oklch; - -impl ColorSpaceConversion for Oklch { - const WHITE_POINT: WhitePoint = Oklab::WHITE_POINT; - - fn to_linear_light(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } - - fn to_xyz(from: &ColorComponents) -> ColorComponents { - // First convert OkLCH to Oklab. - let hue = from.2 * RAD_PER_DEG; - let a = from.1 * hue.cos(); - let b = from.1 * hue.sin(); - let oklab = ColorComponents(from.0, a, b); - - // Then convert Oklab to XYZ. - Oklab::to_xyz(&oklab) - } - - fn from_xyz(from: &ColorComponents) -> ColorComponents { - // First convert XYZ to Oklab. - let ColorComponents(lightness, a, b) = Oklab::from_xyz(&from); - - // Then convert Oklab to OkLCH. - let hue = b.atan2(a) * DEG_PER_RAD; - let chroma = (a * a + b * b).sqrt(); - - ColorComponents(lightness, chroma, hue) - } - - fn to_gamma_encoded(from: &ColorComponents) -> ColorComponents { - // No need for conversion. - from.clone() - } -} diff --git a/components/style/color/mix.rs b/components/style/color/mix.rs deleted file mode 100644 index 455d0252659..00000000000 --- a/components/style/color/mix.rs +++ /dev/null @@ -1,475 +0,0 @@ -/* 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/. */ - -//! Color mixing/interpolation. - -use super::{AbsoluteColor, ColorComponents, ColorFlags, ColorSpace}; -use crate::parser::{Parse, ParserContext}; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// A hue-interpolation-method as defined in [1]. -/// -/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum HueInterpolationMethod { - /// https://drafts.csswg.org/css-color-4/#shorter - Shorter, - /// https://drafts.csswg.org/css-color-4/#longer - Longer, - /// https://drafts.csswg.org/css-color-4/#increasing - Increasing, - /// https://drafts.csswg.org/css-color-4/#decreasing - Decreasing, - /// https://drafts.csswg.org/css-color-4/#specified - Specified, -} - -/// https://drafts.csswg.org/css-color-4/#color-interpolation-method -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToShmem, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, -)] -#[repr(C)] -pub struct ColorInterpolationMethod { - /// The color-space the interpolation should be done in. - pub space: ColorSpace, - /// The hue interpolation method. - pub hue: HueInterpolationMethod, -} - -impl ColorInterpolationMethod { - /// Returns the srgb interpolation method. - pub const fn srgb() -> Self { - Self { - space: ColorSpace::Srgb, - hue: HueInterpolationMethod::Shorter, - } - } - - /// Return the oklab interpolation method used for default color - /// interpolcation. - pub const fn oklab() -> Self { - Self { - space: ColorSpace::Oklab, - hue: HueInterpolationMethod::Shorter, - } - } - - /// Decides the best method for interpolating between the given colors. - /// https://drafts.csswg.org/css-color-4/#interpolation-space - pub fn best_interpolation_between(left: &AbsoluteColor, right: &AbsoluteColor) -> Self { - // The preferred color space to use for interpolating colors is Oklab. - // However, if either of the colors are in legacy rgb(), hsl() or hwb(), - // then interpolation is done in sRGB. - if !left.is_legacy_color() || !right.is_legacy_color() { - Self::oklab() - } else { - Self::srgb() - } - } -} - -impl Parse for ColorInterpolationMethod { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_ident_matching("in")?; - let space = ColorSpace::parse(input)?; - // https://drafts.csswg.org/css-color-4/#hue-interpolation - // Unless otherwise specified, if no specific hue interpolation - // algorithm is selected by the host syntax, the default is shorter. - let hue = if space.is_polar() { - input - .try_parse(|input| -> Result<_, ParseError<'i>> { - let hue = HueInterpolationMethod::parse(input)?; - input.expect_ident_matching("hue")?; - Ok(hue) - }) - .unwrap_or(HueInterpolationMethod::Shorter) - } else { - HueInterpolationMethod::Shorter - }; - Ok(Self { space, hue }) - } -} - -impl ToCss for ColorInterpolationMethod { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("in ")?; - self.space.to_css(dest)?; - if self.hue != HueInterpolationMethod::Shorter { - dest.write_char(' ')?; - self.hue.to_css(dest)?; - dest.write_str(" hue")?; - } - Ok(()) - } -} - -/// Mix two colors into one. -pub fn mix( - interpolation: ColorInterpolationMethod, - left_color: &AbsoluteColor, - mut left_weight: f32, - right_color: &AbsoluteColor, - mut right_weight: f32, - normalize_weights: bool, -) -> AbsoluteColor { - // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm - let mut alpha_multiplier = 1.0; - if normalize_weights { - let sum = left_weight + right_weight; - if sum != 1.0 { - let scale = 1.0 / sum; - left_weight *= scale; - right_weight *= scale; - if sum < 1.0 { - alpha_multiplier = sum; - } - } - } - - mix_in( - interpolation.space, - left_color, - left_weight, - right_color, - right_weight, - interpolation.hue, - alpha_multiplier, - ) -} - -/// What the outcome of each component should be in a mix result. -#[derive(Clone, Copy)] -#[repr(u8)] -enum ComponentMixOutcome { - /// Mix the left and right sides to give the result. - Mix, - /// Carry the left side forward to the result. - UseLeft, - /// Carry the right side forward to the result. - UseRight, - /// The resulting component should also be none. - None, -} - -impl ComponentMixOutcome { - fn from_colors( - left: &AbsoluteColor, - right: &AbsoluteColor, - flags_to_check: ColorFlags, - ) -> Self { - match ( - left.flags.contains(flags_to_check), - right.flags.contains(flags_to_check), - ) { - (true, true) => Self::None, - (true, false) => Self::UseRight, - (false, true) => Self::UseLeft, - (false, false) => Self::Mix, - } - } -} - -fn mix_in( - color_space: ColorSpace, - left_color: &AbsoluteColor, - left_weight: f32, - right_color: &AbsoluteColor, - right_weight: f32, - hue_interpolation: HueInterpolationMethod, - alpha_multiplier: f32, -) -> AbsoluteColor { - let outcomes = [ - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C1_IS_NONE), - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C2_IS_NONE), - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::C3_IS_NONE), - ComponentMixOutcome::from_colors(left_color, right_color, ColorFlags::ALPHA_IS_NONE), - ]; - - // Convert both colors into the interpolation color space. - let left = left_color.to_color_space(color_space); - let left = left.raw_components(); - - let right = right_color.to_color_space(color_space); - let right = right.raw_components(); - - let (result, result_flags) = interpolate_premultiplied( - &left, - left_weight, - &right, - right_weight, - color_space.hue_index(), - hue_interpolation, - &outcomes, - ); - - let alpha = if alpha_multiplier != 1.0 { - result[3] * alpha_multiplier - } else { - result[3] - }; - - // FIXME: In rare cases we end up with 0.999995 in the alpha channel, - // so we reduce the precision to avoid serializing to - // rgba(?, ?, ?, 1). This is not ideal, so we should look into - // ways to avoid it. Maybe pre-multiply all color components and - // then divide after calculations? - let alpha = (alpha * 1000.0).round() / 1000.0; - - let mut result = AbsoluteColor::new( - color_space, - ColorComponents(result[0], result[1], result[2]), - alpha, - ); - - result.flags = result_flags; - // If both sides are legacy RGB, then the result stays in legacy RGB. - if !left_color.is_legacy_color() || !right_color.is_legacy_color() { - result.flags.insert(ColorFlags::AS_COLOR_FUNCTION); - } - - result -} - -fn interpolate_premultiplied_component( - left: f32, - left_weight: f32, - left_alpha: f32, - right: f32, - right_weight: f32, - right_alpha: f32, -) -> f32 { - left * left_weight * left_alpha + right * right_weight * right_alpha -} - -// Normalize hue into [0, 360) -#[inline] -fn normalize_hue(v: f32) -> f32 { - v - 360. * (v / 360.).floor() -} - -fn adjust_hue(left: &mut f32, right: &mut f32, hue_interpolation: HueInterpolationMethod) { - // Adjust the hue angle as per - // https://drafts.csswg.org/css-color/#hue-interpolation. - // - // If both hue angles are NAN, they should be set to 0. Otherwise, if a - // single hue angle is NAN, it should use the other hue angle. - if left.is_nan() { - if right.is_nan() { - *left = 0.; - *right = 0.; - } else { - *left = *right; - } - } else if right.is_nan() { - *right = *left; - } - - if hue_interpolation == HueInterpolationMethod::Specified { - // Angles are not adjusted. They are interpolated like any other - // component. - return; - } - - *left = normalize_hue(*left); - *right = normalize_hue(*right); - - match hue_interpolation { - // https://drafts.csswg.org/css-color/#shorter - HueInterpolationMethod::Shorter => { - let delta = *right - *left; - - if delta > 180. { - *left += 360.; - } else if delta < -180. { - *right += 360.; - } - }, - // https://drafts.csswg.org/css-color/#longer - HueInterpolationMethod::Longer => { - let delta = *right - *left; - if 0. < delta && delta < 180. { - *left += 360.; - } else if -180. < delta && delta < 0. { - *right += 360.; - } - }, - // https://drafts.csswg.org/css-color/#increasing - HueInterpolationMethod::Increasing => { - if *right < *left { - *right += 360.; - } - }, - // https://drafts.csswg.org/css-color/#decreasing - HueInterpolationMethod::Decreasing => { - if *left < *right { - *left += 360.; - } - }, - HueInterpolationMethod::Specified => unreachable!("Handled above"), - } -} - -fn interpolate_hue( - mut left: f32, - left_weight: f32, - mut right: f32, - right_weight: f32, - hue_interpolation: HueInterpolationMethod, -) -> f32 { - adjust_hue(&mut left, &mut right, hue_interpolation); - left * left_weight + right * right_weight -} - -struct InterpolatedAlpha { - /// The adjusted left alpha value. - left: f32, - /// The adjusted right alpha value. - right: f32, - /// The interpolated alpha value. - interpolated: f32, - /// Whether the alpha component should be `none`. - is_none: bool, -} - -fn interpolate_alpha( - left: f32, - left_weight: f32, - right: f32, - right_weight: f32, - outcome: ComponentMixOutcome, -) -> InterpolatedAlpha { - // <https://drafts.csswg.org/css-color-4/#interpolation-missing> - let mut result = match outcome { - ComponentMixOutcome::Mix => { - let interpolated = left * left_weight + right * right_weight; - InterpolatedAlpha { - left, - right, - interpolated, - is_none: false, - } - }, - ComponentMixOutcome::UseLeft => InterpolatedAlpha { - left, - right: left, - interpolated: left, - is_none: false, - }, - ComponentMixOutcome::UseRight => InterpolatedAlpha { - left: right, - right, - interpolated: right, - is_none: false, - }, - ComponentMixOutcome::None => InterpolatedAlpha { - left: 1.0, - right: 1.0, - interpolated: 0.0, - is_none: true, - }, - }; - - // Clip all alpha values to [0.0..1.0]. - result.left = result.left.clamp(0.0, 1.0); - result.right = result.right.clamp(0.0, 1.0); - result.interpolated = result.interpolated.clamp(0.0, 1.0); - - result -} - -fn interpolate_premultiplied( - left: &[f32; 4], - left_weight: f32, - right: &[f32; 4], - right_weight: f32, - hue_index: Option<usize>, - hue_interpolation: HueInterpolationMethod, - outcomes: &[ComponentMixOutcome; 4], -) -> ([f32; 4], ColorFlags) { - let alpha = interpolate_alpha(left[3], left_weight, right[3], right_weight, outcomes[3]); - let mut flags = if alpha.is_none { - ColorFlags::ALPHA_IS_NONE - } else { - ColorFlags::empty() - }; - - let mut result = [0.; 4]; - - for i in 0..3 { - match outcomes[i] { - ComponentMixOutcome::Mix => { - let is_hue = hue_index == Some(i); - result[i] = if is_hue { - normalize_hue(interpolate_hue( - left[i], - left_weight, - right[i], - right_weight, - hue_interpolation, - )) - } else { - let interpolated = interpolate_premultiplied_component( - left[i], - left_weight, - alpha.left, - right[i], - right_weight, - alpha.right, - ); - - if alpha.interpolated == 0.0 { - interpolated - } else { - interpolated / alpha.interpolated - } - }; - }, - ComponentMixOutcome::UseLeft => result[i] = left[i], - ComponentMixOutcome::UseRight => result[i] = right[i], - ComponentMixOutcome::None => { - result[i] = 0.0; - match i { - 0 => flags.insert(ColorFlags::C1_IS_NONE), - 1 => flags.insert(ColorFlags::C2_IS_NONE), - 2 => flags.insert(ColorFlags::C3_IS_NONE), - _ => unreachable!(), - } - }, - } - } - result[3] = alpha.interpolated; - - (result, flags) -} diff --git a/components/style/color/mod.rs b/components/style/color/mod.rs deleted file mode 100644 index f5a36aba2e8..00000000000 --- a/components/style/color/mod.rs +++ /dev/null @@ -1,465 +0,0 @@ -/* 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/. */ - -//! Color support functions. - -/// cbindgen:ignore -pub mod convert; -pub mod mix; - -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// The 3 components that make up a color. (Does not include the alpha component) -#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -#[repr(C)] -pub struct ColorComponents(pub f32, pub f32, pub f32); - -impl ColorComponents { - /// Apply a function to each of the 3 components of the color. - pub fn map(self, f: impl Fn(f32) -> f32) -> Self { - Self(f(self.0), f(self.1), f(self.2)) - } -} - -/// A color space representation in the CSS specification. -/// -/// https://drafts.csswg.org/css-color-4/#typedef-color-space -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ColorSpace { - /// A color specified in the sRGB color space with either the rgb/rgba(..) - /// functions or the newer color(srgb ..) function. If the color(..) - /// function is used, the AS_COLOR_FUNCTION flag will be set. Examples: - /// "color(srgb 0.691 0.139 0.259)", "rgb(176, 35, 66)" - Srgb = 0, - /// A color specified in the Hsl notation in the sRGB color space, e.g. - /// "hsl(289.18 93.136% 65.531%)" - /// https://drafts.csswg.org/css-color-4/#the-hsl-notation - Hsl, - /// A color specified in the Hwb notation in the sRGB color space, e.g. - /// "hwb(740deg 20% 30%)" - /// https://drafts.csswg.org/css-color-4/#the-hwb-notation - Hwb, - /// A color specified in the Lab color format, e.g. - /// "lab(29.2345% 39.3825 20.0664)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors - Lab, - /// A color specified in the Lch color format, e.g. - /// "lch(29.2345% 44.2 27)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors - Lch, - /// A color specified in the Oklab color format, e.g. - /// "oklab(40.101% 0.1147 0.0453)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lab-colors - Oklab, - /// A color specified in the Oklch color format, e.g. - /// "oklch(40.101% 0.12332 21.555)". - /// https://w3c.github.io/csswg-drafts/css-color-4/#lch-colors - Oklch, - /// A color specified with the color(..) function and the "srgb-linear" - /// color space, e.g. "color(srgb-linear 0.435 0.017 0.055)". - SrgbLinear, - /// A color specified with the color(..) function and the "display-p3" - /// color space, e.g. "color(display-p3 0.84 0.19 0.72)". - DisplayP3, - /// A color specified with the color(..) function and the "a98-rgb" color - /// space, e.g. "color(a98-rgb 0.44091 0.49971 0.37408)". - A98Rgb, - /// A color specified with the color(..) function and the "prophoto-rgb" - /// color space, e.g. "color(prophoto-rgb 0.36589 0.41717 0.31333)". - ProphotoRgb, - /// A color specified with the color(..) function and the "rec2020" color - /// space, e.g. "color(rec2020 0.42210 0.47580 0.35605)". - Rec2020, - /// A color specified with the color(..) function and the "xyz-d50" color - /// space, e.g. "color(xyz-d50 0.2005 0.14089 0.4472)". - XyzD50, - /// A color specified with the color(..) function and the "xyz-d65" or "xyz" - /// color space, e.g. "color(xyz-d65 0.21661 0.14602 0.59452)". - /// NOTE: https://drafts.csswg.org/css-color-4/#resolving-color-function-values - /// specifies that `xyz` is an alias for the `xyz-d65` color space. - #[parse(aliases = "xyz")] - XyzD65, -} - -impl ColorSpace { - /// Returns whether this is a `<rectangular-color-space>`. - #[inline] - pub fn is_rectangular(&self) -> bool { - !self.is_polar() - } - - /// Returns whether this is a `<polar-color-space>`. - #[inline] - pub fn is_polar(&self) -> bool { - matches!(self, Self::Hsl | Self::Hwb | Self::Lch | Self::Oklch) - } - - /// Returns an index of the hue component in the color space, otherwise - /// `None`. - #[inline] - pub fn hue_index(&self) -> Option<usize> { - match self { - Self::Hsl | Self::Hwb => Some(0), - Self::Lch | Self::Oklch => Some(2), - - _ => { - debug_assert!(!self.is_polar()); - None - }, - } - } -} - -bitflags! { - /// Flags used when serializing colors. - #[derive(Default, MallocSizeOf, ToShmem)] - #[repr(C)] - pub struct ColorFlags : u8 { - /// If set, serializes sRGB colors into `color(srgb ...)` instead of - /// `rgba(...)`. - const AS_COLOR_FUNCTION = 1 << 0; - /// Whether the 1st color component is `none`. - const C1_IS_NONE = 1 << 1; - /// Whether the 2nd color component is `none`. - const C2_IS_NONE = 1 << 2; - /// Whether the 3rd color component is `none`. - const C3_IS_NONE = 1 << 3; - /// Whether the alpha component is `none`. - const ALPHA_IS_NONE = 1 << 4; - } -} - -/// An absolutely specified color, using either rgb(), rgba(), lab(), lch(), -/// oklab(), oklch() or color(). -#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -#[repr(C)] -pub struct AbsoluteColor { - /// The 3 components that make up colors in any color space. - pub components: ColorComponents, - /// The alpha component of the color. - pub alpha: f32, - /// The current color space that the components represent. - pub color_space: ColorSpace, - /// Extra flags used durring serialization of this color. - pub flags: ColorFlags, -} - -/// Given an [`AbsoluteColor`], return the 4 float components as the type given, -/// e.g.: -/// -/// ```rust -/// let srgb = AbsoluteColor::new(ColorSpace::Srgb, 1.0, 0.0, 0.0, 0.0); -/// let floats = color_components_as!(&srgb, [f32; 4]); // [1.0, 0.0, 0.0, 0.0] -/// ``` -macro_rules! color_components_as { - ($c:expr, $t:ty) => {{ - // This macro is not an inline function, because we can't use the - // generic type ($t) in a constant expression as per: - // https://github.com/rust-lang/rust/issues/76560 - const_assert_eq!(std::mem::size_of::<$t>(), std::mem::size_of::<[f32; 4]>()); - const_assert_eq!(std::mem::align_of::<$t>(), std::mem::align_of::<[f32; 4]>()); - const_assert!(std::mem::size_of::<AbsoluteColor>() >= std::mem::size_of::<$t>()); - const_assert_eq!( - std::mem::align_of::<AbsoluteColor>(), - std::mem::align_of::<$t>() - ); - - std::mem::transmute::<&ColorComponents, &$t>(&$c.components) - }}; -} - -impl AbsoluteColor { - /// Create a new [`AbsoluteColor`] with the given [`ColorSpace`] and - /// components. - pub fn new(color_space: ColorSpace, components: ColorComponents, alpha: f32) -> Self { - let mut components = components; - - // Lightness must not be less than 0. - if matches!( - color_space, - ColorSpace::Lab | ColorSpace::Lch | ColorSpace::Oklab | ColorSpace::Oklch - ) { - components.0 = components.0.max(0.0); - } - - // Chroma must not be less than 0. - if matches!(color_space, ColorSpace::Lch | ColorSpace::Oklch) { - components.1 = components.1.max(0.0); - } - - Self { - components, - alpha: alpha.clamp(0.0, 1.0), - color_space, - flags: ColorFlags::empty(), - } - } - - /// Create a new [`AbsoluteColor`] from rgba values in the sRGB color space. - pub fn srgb(red: f32, green: f32, blue: f32, alpha: f32) -> Self { - Self::new(ColorSpace::Srgb, ColorComponents(red, green, blue), alpha) - } - - /// Create a new transparent color. - pub fn transparent() -> Self { - Self::srgb(0.0, 0.0, 0.0, 0.0) - } - - /// Create a new opaque black color. - pub fn black() -> Self { - Self::srgb(0.0, 0.0, 0.0, 1.0) - } - - /// Create a new opaque white color. - pub fn white() -> Self { - Self::srgb(1.0, 1.0, 1.0, 1.0) - } - - /// Return all the components of the color in an array. (Includes alpha) - #[inline] - pub fn raw_components(&self) -> &[f32; 4] { - unsafe { color_components_as!(self, [f32; 4]) } - } - - /// Returns true if this color is in one of the legacy color formats. - #[inline] - pub fn is_legacy_color(&self) -> bool { - // rgb(), rgba(), hsl(), hsla(), hwb(), hwba() - match self.color_space { - ColorSpace::Srgb => !self.flags.contains(ColorFlags::AS_COLOR_FUNCTION), - ColorSpace::Hsl | ColorSpace::Hwb => true, - _ => false, - } - } - - /// Return the alpha component. - #[inline] - pub fn alpha(&self) -> f32 { - self.alpha - } - - /// Convert this color to the specified color space. - pub fn to_color_space(&self, color_space: ColorSpace) -> Self { - use ColorSpace::*; - - if self.color_space == color_space { - return self.clone(); - } - - // We have simplified conversions that do not need to convert to XYZ - // first. This improves performance, because it skips 2 matrix - // multiplications and reduces float rounding errors. - match (self.color_space, color_space) { - (Srgb, Hsl) => { - return Self::new( - color_space, - convert::rgb_to_hsl(&self.components), - self.alpha, - ); - }, - - (Srgb, Hwb) => { - return Self::new( - color_space, - convert::rgb_to_hwb(&self.components), - self.alpha, - ); - }, - - (Hsl, Srgb) => { - return Self::new( - color_space, - convert::hsl_to_rgb(&self.components), - self.alpha, - ); - }, - - (Hwb, Srgb) => { - return Self::new( - color_space, - convert::hwb_to_rgb(&self.components), - self.alpha, - ); - }, - - (Lab, Lch) | (Oklab, Oklch) => { - return Self::new( - color_space, - convert::lab_to_lch(&self.components), - self.alpha, - ); - }, - - (Lch, Lab) | (Oklch, Oklab) => { - return Self::new( - color_space, - convert::lch_to_lab(&self.components), - self.alpha, - ); - }, - - _ => {}, - } - - let (xyz, white_point) = match self.color_space { - Lab => convert::to_xyz::<convert::Lab>(&self.components), - Lch => convert::to_xyz::<convert::Lch>(&self.components), - Oklab => convert::to_xyz::<convert::Oklab>(&self.components), - Oklch => convert::to_xyz::<convert::Oklch>(&self.components), - Srgb => convert::to_xyz::<convert::Srgb>(&self.components), - Hsl => convert::to_xyz::<convert::Hsl>(&self.components), - Hwb => convert::to_xyz::<convert::Hwb>(&self.components), - SrgbLinear => convert::to_xyz::<convert::SrgbLinear>(&self.components), - DisplayP3 => convert::to_xyz::<convert::DisplayP3>(&self.components), - A98Rgb => convert::to_xyz::<convert::A98Rgb>(&self.components), - ProphotoRgb => convert::to_xyz::<convert::ProphotoRgb>(&self.components), - Rec2020 => convert::to_xyz::<convert::Rec2020>(&self.components), - XyzD50 => convert::to_xyz::<convert::XyzD50>(&self.components), - XyzD65 => convert::to_xyz::<convert::XyzD65>(&self.components), - }; - - let result = match color_space { - Lab => convert::from_xyz::<convert::Lab>(&xyz, white_point), - Lch => convert::from_xyz::<convert::Lch>(&xyz, white_point), - Oklab => convert::from_xyz::<convert::Oklab>(&xyz, white_point), - Oklch => convert::from_xyz::<convert::Oklch>(&xyz, white_point), - Srgb => convert::from_xyz::<convert::Srgb>(&xyz, white_point), - Hsl => convert::from_xyz::<convert::Hsl>(&xyz, white_point), - Hwb => convert::from_xyz::<convert::Hwb>(&xyz, white_point), - SrgbLinear => convert::from_xyz::<convert::SrgbLinear>(&xyz, white_point), - DisplayP3 => convert::from_xyz::<convert::DisplayP3>(&xyz, white_point), - A98Rgb => convert::from_xyz::<convert::A98Rgb>(&xyz, white_point), - ProphotoRgb => convert::from_xyz::<convert::ProphotoRgb>(&xyz, white_point), - Rec2020 => convert::from_xyz::<convert::Rec2020>(&xyz, white_point), - XyzD50 => convert::from_xyz::<convert::XyzD50>(&xyz, white_point), - XyzD65 => convert::from_xyz::<convert::XyzD65>(&xyz, white_point), - }; - - Self::new(color_space, result, self.alpha) - } -} - -impl From<cssparser::PredefinedColorSpace> for ColorSpace { - fn from(value: cssparser::PredefinedColorSpace) -> Self { - match value { - cssparser::PredefinedColorSpace::Srgb => ColorSpace::Srgb, - cssparser::PredefinedColorSpace::SrgbLinear => ColorSpace::SrgbLinear, - cssparser::PredefinedColorSpace::DisplayP3 => ColorSpace::DisplayP3, - cssparser::PredefinedColorSpace::A98Rgb => ColorSpace::A98Rgb, - cssparser::PredefinedColorSpace::ProphotoRgb => ColorSpace::ProphotoRgb, - cssparser::PredefinedColorSpace::Rec2020 => ColorSpace::Rec2020, - cssparser::PredefinedColorSpace::XyzD50 => ColorSpace::XyzD50, - cssparser::PredefinedColorSpace::XyzD65 => ColorSpace::XyzD65, - } - } -} - -impl ToCss for AbsoluteColor { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - macro_rules! value_or_none { - ($v:expr,$flag:tt) => {{ - if self.flags.contains(ColorFlags::$flag) { - None - } else { - Some($v) - } - }}; - } - - let maybe_c1 = value_or_none!(self.components.0, C1_IS_NONE); - let maybe_c2 = value_or_none!(self.components.1, C2_IS_NONE); - let maybe_c3 = value_or_none!(self.components.2, C3_IS_NONE); - let maybe_alpha = value_or_none!(self.alpha, ALPHA_IS_NONE); - - match self.color_space { - ColorSpace::Hsl => { - let rgb = convert::hsl_to_rgb(&self.components); - Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest) - }, - - ColorSpace::Hwb => { - let rgb = convert::hwb_to_rgb(&self.components); - - Self::new(ColorSpace::Srgb, rgb, self.alpha).to_css(dest) - }, - - ColorSpace::Srgb if !self.flags.contains(ColorFlags::AS_COLOR_FUNCTION) => { - // Althought we are passing Option<_> in here, the to_css fn - // knows that the "none" keyword is not supported in the - // rgb/rgba legacy syntax. - cssparser::ToCss::to_css( - &cssparser::RGBA::from_floats(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ) - }, - ColorSpace::Lab => cssparser::ToCss::to_css( - &cssparser::Lab::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - ColorSpace::Lch => cssparser::ToCss::to_css( - &cssparser::Lch::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - ColorSpace::Oklab => cssparser::ToCss::to_css( - &cssparser::Oklab::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - ColorSpace::Oklch => cssparser::ToCss::to_css( - &cssparser::Oklch::new(maybe_c1, maybe_c2, maybe_c3, maybe_alpha), - dest, - ), - _ => { - let color_space = match self.color_space { - ColorSpace::Srgb => { - debug_assert!( - self.flags.contains(ColorFlags::AS_COLOR_FUNCTION), - "The case without this flag should be handled in the wrapping match case!!" - ); - - cssparser::PredefinedColorSpace::Srgb - }, - ColorSpace::SrgbLinear => cssparser::PredefinedColorSpace::SrgbLinear, - ColorSpace::DisplayP3 => cssparser::PredefinedColorSpace::DisplayP3, - ColorSpace::A98Rgb => cssparser::PredefinedColorSpace::A98Rgb, - ColorSpace::ProphotoRgb => cssparser::PredefinedColorSpace::ProphotoRgb, - ColorSpace::Rec2020 => cssparser::PredefinedColorSpace::Rec2020, - ColorSpace::XyzD50 => cssparser::PredefinedColorSpace::XyzD50, - ColorSpace::XyzD65 => cssparser::PredefinedColorSpace::XyzD65, - - _ => { - unreachable!("other color spaces do not support color() syntax") - }, - }; - - let color_function = cssparser::ColorFunction { - color_space, - c1: maybe_c1, - c2: maybe_c2, - c3: maybe_c3, - alpha: maybe_alpha, - }; - let color = cssparser::Color::ColorFunction(color_function); - cssparser::ToCss::to_css(&color, dest) - }, - } - } -} diff --git a/components/style/context.rs b/components/style/context.rs deleted file mode 100644 index 8f717eca61f..00000000000 --- a/components/style/context.rs +++ /dev/null @@ -1,698 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The context within which style is calculated. - -#[cfg(feature = "servo")] -use crate::animation::DocumentAnimationSet; -use crate::bloom::StyleBloom; -use crate::computed_value_flags::ComputedValueFlags; -use crate::data::{EagerPseudoStyles, ElementData}; -use crate::dom::{SendElement, TElement}; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::structs; -use crate::parallel::{STACK_SAFETY_MARGIN_KB, STYLE_THREAD_STACK_SIZE_KB}; -use crate::properties::ComputedValues; -#[cfg(feature = "servo")] -use crate::properties::PropertyId; -use crate::rule_cache::RuleCache; -use crate::rule_tree::StrongRuleNode; -use crate::selector_parser::{SnapshotMap, EAGER_PSEUDO_COUNT}; -use crate::shared_lock::StylesheetGuards; -use crate::sharing::StyleSharingCache; -use crate::stylist::Stylist; -use crate::thread_state::{self, ThreadState}; -use crate::traversal::DomTraversal; -use crate::traversal_flags::TraversalFlags; -use app_units::Au; -use euclid::default::Size2D; -use euclid::Scale; -#[cfg(feature = "servo")] -use fxhash::FxHashMap; -use selectors::NthIndexCache; -#[cfg(feature = "gecko")] -use servo_arc::Arc; -#[cfg(feature = "servo")] -use servo_atoms::Atom; -use std::fmt; -use std::ops; -use style_traits::CSSPixel; -use style_traits::DevicePixel; -#[cfg(feature = "servo")] -use style_traits::SpeculativePainter; -use time; - -pub use selectors::matching::QuirksMode; - -/// A global options structure for the style system. We use this instead of -/// opts to abstract across Gecko and Servo. -#[derive(Clone)] -pub struct StyleSystemOptions { - /// Whether the style sharing cache is disabled. - pub disable_style_sharing_cache: bool, - /// Whether we should dump statistics about the style system. - pub dump_style_statistics: bool, - /// The minimum number of elements that must be traversed to trigger a dump - /// of style statistics. - pub style_statistics_threshold: usize, -} - -#[cfg(feature = "gecko")] -fn get_env_bool(name: &str) -> bool { - use std::env; - match env::var(name) { - Ok(s) => !s.is_empty(), - Err(_) => false, - } -} - -const DEFAULT_STATISTICS_THRESHOLD: usize = 50; - -#[cfg(feature = "gecko")] -fn get_env_usize(name: &str) -> Option<usize> { - use std::env; - env::var(name).ok().map(|s| { - s.parse::<usize>() - .expect("Couldn't parse environmental variable as usize") - }) -} - -/// A global variable holding the state of -/// `StyleSystemOptions::default().disable_style_sharing_cache`. -/// See [#22854](https://github.com/servo/servo/issues/22854). -#[cfg(feature = "servo")] -pub static DEFAULT_DISABLE_STYLE_SHARING_CACHE: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(false); - -/// A global variable holding the state of -/// `StyleSystemOptions::default().dump_style_statistics`. -/// See [#22854](https://github.com/servo/servo/issues/22854). -#[cfg(feature = "servo")] -pub static DEFAULT_DUMP_STYLE_STATISTICS: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(false); - -impl Default for StyleSystemOptions { - #[cfg(feature = "servo")] - fn default() -> Self { - use std::sync::atomic::Ordering; - - StyleSystemOptions { - disable_style_sharing_cache: DEFAULT_DISABLE_STYLE_SHARING_CACHE - .load(Ordering::Relaxed), - dump_style_statistics: DEFAULT_DUMP_STYLE_STATISTICS.load(Ordering::Relaxed), - style_statistics_threshold: DEFAULT_STATISTICS_THRESHOLD, - } - } - - #[cfg(feature = "gecko")] - fn default() -> Self { - StyleSystemOptions { - disable_style_sharing_cache: get_env_bool("DISABLE_STYLE_SHARING_CACHE"), - dump_style_statistics: get_env_bool("DUMP_STYLE_STATISTICS"), - style_statistics_threshold: get_env_usize("STYLE_STATISTICS_THRESHOLD") - .unwrap_or(DEFAULT_STATISTICS_THRESHOLD), - } - } -} - -/// A shared style context. -/// -/// There's exactly one of these during a given restyle traversal, and it's -/// shared among the worker threads. -pub struct SharedStyleContext<'a> { - /// The CSS selector stylist. - pub stylist: &'a Stylist, - - /// Whether visited styles are enabled. - /// - /// They may be disabled when Gecko's pref layout.css.visited_links_enabled - /// is false, or when in private browsing mode. - pub visited_styles_enabled: bool, - - /// Configuration options. - pub options: StyleSystemOptions, - - /// Guards for pre-acquired locks - pub guards: StylesheetGuards<'a>, - - /// The current time for transitions and animations. This is needed to ensure - /// a consistent sampling time and also to adjust the time for testing. - pub current_time_for_animations: f64, - - /// Flags controlling how we traverse the tree. - pub traversal_flags: TraversalFlags, - - /// A map with our snapshots in order to handle restyle hints. - pub snapshot_map: &'a SnapshotMap, - - /// The state of all animations for our styled elements. - #[cfg(feature = "servo")] - pub animations: DocumentAnimationSet, - - /// Paint worklets - #[cfg(feature = "servo")] - pub registered_speculative_painters: &'a dyn RegisteredSpeculativePainters, -} - -impl<'a> SharedStyleContext<'a> { - /// Return a suitable viewport size in order to be used for viewport units. - pub fn viewport_size(&self) -> Size2D<Au> { - self.stylist.device().au_viewport_size() - } - - /// The device pixel ratio - pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> { - self.stylist.device().device_pixel_ratio() - } - - /// The quirks mode of the document. - pub fn quirks_mode(&self) -> QuirksMode { - self.stylist.quirks_mode() - } -} - -/// The structure holds various intermediate inputs that are eventually used by -/// by the cascade. -/// -/// The matching and cascading process stores them in this format temporarily -/// within the `CurrentElementInfo`. At the end of the cascade, they are folded -/// down into the main `ComputedValues` to reduce memory usage per element while -/// still remaining accessible. -#[derive(Clone, Debug, Default)] -pub struct CascadeInputs { - /// The rule node representing the ordered list of rules matched for this - /// node. - pub rules: Option<StrongRuleNode>, - - /// The rule node representing the ordered list of rules matched for this - /// node if visited, only computed if there's a relevant link for this - /// element. A element's "relevant link" is the element being matched if it - /// is a link or the nearest ancestor link. - pub visited_rules: Option<StrongRuleNode>, - - /// The set of flags from container queries that we need for invalidation. - pub flags: ComputedValueFlags, -} - -impl CascadeInputs { - /// Construct inputs from previous cascade results, if any. - pub fn new_from_style(style: &ComputedValues) -> Self { - Self { - rules: style.rules.clone(), - visited_rules: style.visited_style().and_then(|v| v.rules.clone()), - flags: style.flags.for_cascade_inputs(), - } - } -} - -/// A list of cascade inputs for eagerly-cascaded pseudo-elements. -/// The list is stored inline. -#[derive(Debug)] -pub struct EagerPseudoCascadeInputs(Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]>); - -// Manually implement `Clone` here because the derived impl of `Clone` for -// array types assumes the value inside is `Copy`. -impl Clone for EagerPseudoCascadeInputs { - fn clone(&self) -> Self { - if self.0.is_none() { - return EagerPseudoCascadeInputs(None); - } - let self_inputs = self.0.as_ref().unwrap(); - let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default(); - for i in 0..EAGER_PSEUDO_COUNT { - inputs[i] = self_inputs[i].clone(); - } - EagerPseudoCascadeInputs(Some(inputs)) - } -} - -impl EagerPseudoCascadeInputs { - /// Construct inputs from previous cascade results, if any. - fn new_from_style(styles: &EagerPseudoStyles) -> Self { - EagerPseudoCascadeInputs(styles.as_optional_array().map(|styles| { - let mut inputs: [Option<CascadeInputs>; EAGER_PSEUDO_COUNT] = Default::default(); - for i in 0..EAGER_PSEUDO_COUNT { - inputs[i] = styles[i].as_ref().map(|s| CascadeInputs::new_from_style(s)); - } - inputs - })) - } - - /// Returns the list of rules, if they exist. - pub fn into_array(self) -> Option<[Option<CascadeInputs>; EAGER_PSEUDO_COUNT]> { - self.0 - } -} - -/// The cascade inputs associated with a node, including those for any -/// pseudo-elements. -/// -/// The matching and cascading process stores them in this format temporarily -/// within the `CurrentElementInfo`. At the end of the cascade, they are folded -/// down into the main `ComputedValues` to reduce memory usage per element while -/// still remaining accessible. -#[derive(Clone, Debug)] -pub struct ElementCascadeInputs { - /// The element's cascade inputs. - pub primary: CascadeInputs, - /// A list of the inputs for the element's eagerly-cascaded pseudo-elements. - pub pseudos: EagerPseudoCascadeInputs, -} - -impl ElementCascadeInputs { - /// Construct inputs from previous cascade results, if any. - #[inline] - pub fn new_from_element_data(data: &ElementData) -> Self { - debug_assert!(data.has_styles()); - ElementCascadeInputs { - primary: CascadeInputs::new_from_style(data.styles.primary()), - pseudos: EagerPseudoCascadeInputs::new_from_style(&data.styles.pseudos), - } - } -} - -/// Statistics gathered during the traversal. We gather statistics on each -/// thread and then combine them after the threads join via the Add -/// implementation below. -#[derive(AddAssign, Clone, Default)] -pub struct PerThreadTraversalStatistics { - /// The total number of elements traversed. - pub elements_traversed: u32, - /// The number of elements where has_styles() went from false to true. - pub elements_styled: u32, - /// The number of elements for which we performed selector matching. - pub elements_matched: u32, - /// The number of cache hits from the StyleSharingCache. - pub styles_shared: u32, - /// The number of styles reused via rule node comparison from the - /// StyleSharingCache. - pub styles_reused: u32, -} - -/// Statistics gathered during the traversal plus some information from -/// other sources including stylist. -#[derive(Default)] -pub struct TraversalStatistics { - /// Aggregated statistics gathered during the traversal. - pub aggregated: PerThreadTraversalStatistics, - /// The number of selectors in the stylist. - pub selectors: u32, - /// The number of revalidation selectors. - pub revalidation_selectors: u32, - /// The number of state/attr dependencies in the dependency set. - pub dependency_selectors: u32, - /// The number of declarations in the stylist. - pub declarations: u32, - /// The number of times the stylist was rebuilt. - pub stylist_rebuilds: u32, - /// Time spent in the traversal, in milliseconds. - pub traversal_time_ms: f64, - /// Whether this was a parallel traversal. - pub is_parallel: bool, - /// Whether this is a "large" traversal. - pub is_large: bool, -} - -/// Format the statistics in a way that the performance test harness understands. -/// See https://bugzilla.mozilla.org/show_bug.cgi?id=1331856#c2 -impl fmt::Display for TraversalStatistics { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - debug_assert!( - self.traversal_time_ms != 0.0, - "should have set traversal time" - ); - writeln!(f, "[PERF] perf block start")?; - writeln!( - f, - "[PERF],traversal,{}", - if self.is_parallel { - "parallel" - } else { - "sequential" - } - )?; - writeln!( - f, - "[PERF],elements_traversed,{}", - self.aggregated.elements_traversed - )?; - writeln!( - f, - "[PERF],elements_styled,{}", - self.aggregated.elements_styled - )?; - writeln!( - f, - "[PERF],elements_matched,{}", - self.aggregated.elements_matched - )?; - writeln!(f, "[PERF],styles_shared,{}", self.aggregated.styles_shared)?; - writeln!(f, "[PERF],styles_reused,{}", self.aggregated.styles_reused)?; - writeln!(f, "[PERF],selectors,{}", self.selectors)?; - writeln!( - f, - "[PERF],revalidation_selectors,{}", - self.revalidation_selectors - )?; - writeln!( - f, - "[PERF],dependency_selectors,{}", - self.dependency_selectors - )?; - writeln!(f, "[PERF],declarations,{}", self.declarations)?; - writeln!(f, "[PERF],stylist_rebuilds,{}", self.stylist_rebuilds)?; - writeln!(f, "[PERF],traversal_time_ms,{}", self.traversal_time_ms)?; - writeln!(f, "[PERF] perf block end") - } -} - -impl TraversalStatistics { - /// Generate complete traversal statistics. - /// - /// The traversal time is computed given the start time in seconds. - pub fn new<E, D>( - aggregated: PerThreadTraversalStatistics, - traversal: &D, - parallel: bool, - start: f64, - ) -> TraversalStatistics - where - E: TElement, - D: DomTraversal<E>, - { - let threshold = traversal - .shared_context() - .options - .style_statistics_threshold; - let stylist = traversal.shared_context().stylist; - let is_large = aggregated.elements_traversed as usize >= threshold; - TraversalStatistics { - aggregated, - selectors: stylist.num_selectors() as u32, - revalidation_selectors: stylist.num_revalidation_selectors() as u32, - dependency_selectors: stylist.num_invalidations() as u32, - declarations: stylist.num_declarations() as u32, - stylist_rebuilds: stylist.num_rebuilds() as u32, - traversal_time_ms: (time::precise_time_s() - start) * 1000.0, - is_parallel: parallel, - is_large, - } - } -} - -#[cfg(feature = "gecko")] -bitflags! { - /// Represents which tasks are performed in a SequentialTask of - /// UpdateAnimations which is a result of normal restyle. - pub struct UpdateAnimationsTasks: u8 { - /// Update CSS Animations. - const CSS_ANIMATIONS = structs::UpdateAnimationsTasks_CSSAnimations; - /// Update CSS Transitions. - const CSS_TRANSITIONS = structs::UpdateAnimationsTasks_CSSTransitions; - /// Update effect properties. - const EFFECT_PROPERTIES = structs::UpdateAnimationsTasks_EffectProperties; - /// Update animation cacade results for animations running on the compositor. - const CASCADE_RESULTS = structs::UpdateAnimationsTasks_CascadeResults; - /// Display property was changed from none. - /// Script animations keep alive on display:none elements, so we need to trigger - /// the second animation restyles for the script animations in the case where - /// the display property was changed from 'none' to others. - const DISPLAY_CHANGED_FROM_NONE = structs::UpdateAnimationsTasks_DisplayChangedFromNone; - /// Update CSS named scroll progress timelines. - const SCROLL_TIMELINES = structs::UpdateAnimationsTasks_ScrollTimelines; - /// Update CSS named view progress timelines. - const VIEW_TIMELINES = structs::UpdateAnimationsTasks_ViewTimelines; - } -} - -#[cfg(feature = "gecko")] -bitflags! { - /// Represents which tasks are performed in a SequentialTask as a result of - /// animation-only restyle. - pub struct PostAnimationTasks: u8 { - /// Display property was changed from none in animation-only restyle so - /// that we need to resolve styles for descendants in a subsequent - /// normal restyle. - const DISPLAY_CHANGED_FROM_NONE_FOR_SMIL = 0x01; - } -} - -/// A task to be run in sequential mode on the parent (non-worker) thread. This -/// is used by the style system to queue up work which is not safe to do during -/// the parallel traversal. -pub enum SequentialTask<E: TElement> { - /// Entry to avoid an unused type parameter error on servo. - Unused(SendElement<E>), - - /// Performs one of a number of possible tasks related to updating - /// animations based on the |tasks| field. These include updating CSS - /// animations/transitions that changed as part of the non-animation style - /// traversal, and updating the computed effect properties. - #[cfg(feature = "gecko")] - UpdateAnimations { - /// The target element or pseudo-element. - el: SendElement<E>, - /// The before-change style for transitions. We use before-change style - /// as the initial value of its Keyframe. Required if |tasks| includes - /// CSSTransitions. - before_change_style: Option<Arc<ComputedValues>>, - /// The tasks which are performed in this SequentialTask. - tasks: UpdateAnimationsTasks, - }, - - /// Performs one of a number of possible tasks as a result of animation-only - /// restyle. - /// - /// Currently we do only process for resolving descendant elements that were - /// display:none subtree for SMIL animation. - #[cfg(feature = "gecko")] - PostAnimation { - /// The target element. - el: SendElement<E>, - /// The tasks which are performed in this SequentialTask. - tasks: PostAnimationTasks, - }, -} - -impl<E: TElement> SequentialTask<E> { - /// Executes this task. - pub fn execute(self) { - use self::SequentialTask::*; - debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); - match self { - Unused(_) => unreachable!(), - #[cfg(feature = "gecko")] - UpdateAnimations { - el, - before_change_style, - tasks, - } => { - el.update_animations(before_change_style, tasks); - }, - #[cfg(feature = "gecko")] - PostAnimation { el, tasks } => { - el.process_post_animation(tasks); - }, - } - } - - /// Creates a task to update various animation-related state on a given - /// (pseudo-)element. - #[cfg(feature = "gecko")] - pub fn update_animations( - el: E, - before_change_style: Option<Arc<ComputedValues>>, - tasks: UpdateAnimationsTasks, - ) -> Self { - use self::SequentialTask::*; - UpdateAnimations { - el: unsafe { SendElement::new(el) }, - before_change_style, - tasks, - } - } - - /// Creates a task to do post-process for a given element as a result of - /// animation-only restyle. - #[cfg(feature = "gecko")] - pub fn process_post_animation(el: E, tasks: PostAnimationTasks) -> Self { - use self::SequentialTask::*; - PostAnimation { - el: unsafe { SendElement::new(el) }, - tasks, - } - } -} - -/// A list of SequentialTasks that get executed on Drop. -pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>) -where - E: TElement; - -impl<E> ops::Deref for SequentialTaskList<E> -where - E: TElement, -{ - type Target = Vec<SequentialTask<E>>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl<E> ops::DerefMut for SequentialTaskList<E> -where - E: TElement, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl<E> Drop for SequentialTaskList<E> -where - E: TElement, -{ - fn drop(&mut self) { - debug_assert!(thread_state::get().contains(ThreadState::LAYOUT)); - for task in self.0.drain(..) { - task.execute() - } - } -} - -/// A helper type for stack limit checking. This assumes that stacks grow -/// down, which is true for all non-ancient CPU architectures. -pub struct StackLimitChecker { - lower_limit: usize, -} - -impl StackLimitChecker { - /// Create a new limit checker, for this thread, allowing further use - /// of up to |stack_size| bytes beyond (below) the current stack pointer. - #[inline(never)] - pub fn new(stack_size_limit: usize) -> Self { - StackLimitChecker { - lower_limit: StackLimitChecker::get_sp() - stack_size_limit, - } - } - - /// Checks whether the previously stored stack limit has now been exceeded. - #[inline(never)] - pub fn limit_exceeded(&self) -> bool { - let curr_sp = StackLimitChecker::get_sp(); - - // Do some sanity-checking to ensure that our invariants hold, even in - // the case where we've exceeded the soft limit. - // - // The correctness of depends on the assumption that no stack wraps - // around the end of the address space. - if cfg!(debug_assertions) { - // Compute the actual bottom of the stack by subtracting our safety - // margin from our soft limit. Note that this will be slightly below - // the actual bottom of the stack, because there are a few initial - // frames on the stack before we do the measurement that computes - // the limit. - let stack_bottom = self.lower_limit - STACK_SAFETY_MARGIN_KB * 1024; - - // The bottom of the stack should be below the current sp. If it - // isn't, that means we've either waited too long to check the limit - // and burned through our safety margin (in which case we probably - // would have segfaulted by now), or we're using a limit computed for - // a different thread. - debug_assert!(stack_bottom < curr_sp); - - // Compute the distance between the current sp and the bottom of - // the stack, and compare it against the current stack. It should be - // no further from us than the total stack size. We allow some slop - // to handle the fact that stack_bottom is a bit further than the - // bottom of the stack, as discussed above. - let distance_to_stack_bottom = curr_sp - stack_bottom; - let max_allowable_distance = (STYLE_THREAD_STACK_SIZE_KB + 10) * 1024; - debug_assert!(distance_to_stack_bottom <= max_allowable_distance); - } - - // The actual bounds check. - curr_sp <= self.lower_limit - } - - // Technically, rustc can optimize this away, but shouldn't for now. - // We should fix this once black_box is stable. - #[inline(always)] - fn get_sp() -> usize { - let mut foo: usize = 42; - (&mut foo as *mut usize) as usize - } -} - -/// A thread-local style context. -/// -/// This context contains data that needs to be used during restyling, but is -/// not required to be unique among worker threads, so we create one per worker -/// thread in order to be able to mutate it without locking. -pub struct ThreadLocalStyleContext<E: TElement> { - /// A cache to share style among siblings. - pub sharing_cache: StyleSharingCache<E>, - /// A cache from matched properties to elements that match those. - pub rule_cache: RuleCache, - /// The bloom filter used to fast-reject selector-matching. - pub bloom_filter: StyleBloom<E>, - /// A set of tasks to be run (on the parent thread) in sequential mode after - /// the rest of the styling is complete. This is useful for - /// infrequently-needed non-threadsafe operations. - /// - /// It's important that goes after the style sharing cache and the bloom - /// filter, to ensure they're dropped before we execute the tasks, which - /// could create another ThreadLocalStyleContext for style computation. - pub tasks: SequentialTaskList<E>, - /// Statistics about the traversal. - pub statistics: PerThreadTraversalStatistics, - /// A checker used to ensure that parallel.rs does not recurse indefinitely - /// even on arbitrarily deep trees. See Gecko bug 1376883. - pub stack_limit_checker: StackLimitChecker, - /// A cache for nth-index-like selectors. - pub nth_index_cache: NthIndexCache, -} - -impl<E: TElement> ThreadLocalStyleContext<E> { - /// Creates a new `ThreadLocalStyleContext` - pub fn new() -> Self { - ThreadLocalStyleContext { - sharing_cache: StyleSharingCache::new(), - rule_cache: RuleCache::new(), - bloom_filter: StyleBloom::new(), - tasks: SequentialTaskList(Vec::new()), - statistics: PerThreadTraversalStatistics::default(), - stack_limit_checker: StackLimitChecker::new( - (STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024, - ), - nth_index_cache: NthIndexCache::default(), - } - } -} - -/// A `StyleContext` is just a simple container for a immutable reference to a -/// shared style context, and a mutable reference to a local one. -pub struct StyleContext<'a, E: TElement + 'a> { - /// The shared style context reference. - pub shared: &'a SharedStyleContext<'a>, - /// The thread-local style context (mutable) reference. - pub thread_local: &'a mut ThreadLocalStyleContext<E>, -} - -/// A registered painter -#[cfg(feature = "servo")] -pub trait RegisteredSpeculativePainter: SpeculativePainter { - /// The name it was registered with - fn name(&self) -> Atom; - /// The properties it was registered with - fn properties(&self) -> &FxHashMap<Atom, PropertyId>; -} - -/// A set of registered painters -#[cfg(feature = "servo")] -pub trait RegisteredSpeculativePainters: Sync { - /// Look up a speculative painter - fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter>; -} diff --git a/components/style/counter_style/mod.rs b/components/style/counter_style/mod.rs deleted file mode 100644 index 65143d69906..00000000000 --- a/components/style/counter_style/mod.rs +++ /dev/null @@ -1,697 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The [`@counter-style`][counter-style] at-rule. -//! -//! [counter-style]: https://drafts.csswg.org/css-counter-styles/ - -use crate::error_reporting::ContextualParseError; -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::values::specified::Integer; -use crate::values::CustomIdent; -use crate::Atom; -use cssparser::{ - AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, -}; -use cssparser::{CowRcStr, Parser, SourceLocation, Token}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use std::mem; -use std::num::Wrapping; -use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError}; -use style_traits::{StyleParseErrorKind, ToCss}; - -/// Parse a counter style name reference. -/// -/// This allows the reserved counter style names "decimal" and "disc". -pub fn parse_counter_style_name<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<CustomIdent, ParseError<'i>> { - macro_rules! predefined { - ($($name: expr,)+) => { - { - ascii_case_insensitive_phf_map! { - // FIXME: use static atoms https://github.com/rust-lang/rust/issues/33156 - predefined -> &'static str = { - $( - $name => $name, - )+ - } - } - - let location = input.current_source_location(); - let ident = input.expect_ident()?; - if let Some(&lower_cased) = predefined(&ident) { - Ok(CustomIdent(Atom::from(lower_cased))) - } else { - // none is always an invalid <counter-style> value. - CustomIdent::from_ident(location, ident, &["none"]) - } - } - } - } - include!("predefined.rs") -} - -fn is_valid_name_definition(ident: &CustomIdent) -> bool { - ident.0 != atom!("decimal") && - ident.0 != atom!("disc") && - ident.0 != atom!("circle") && - ident.0 != atom!("square") && - ident.0 != atom!("disclosure-closed") && - ident.0 != atom!("disclosure-open") -} - -/// Parse the prelude of an @counter-style rule -pub fn parse_counter_style_name_definition<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<CustomIdent, ParseError<'i>> { - parse_counter_style_name(input).and_then(|ident| { - if !is_valid_name_definition(&ident) { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(ident) - } - }) -} - -/// Parse the body (inside `{}`) of an @counter-style rule -pub fn parse_counter_style_body<'i, 't>( - name: CustomIdent, - context: &ParserContext, - input: &mut Parser<'i, 't>, - location: SourceLocation, -) -> Result<CounterStyleRuleData, ParseError<'i>> { - let start = input.current_source_location(); - let mut rule = CounterStyleRuleData::empty(name, location); - { - let mut parser = CounterStyleRuleParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if let Err((error, slice)) = declaration { - let location = error.location; - let error = ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration( - slice, error, - ); - context.log_css_error(location, error) - } - } - } - let error = match *rule.resolved_system() { - ref system @ System::Cyclic | - ref system @ System::Fixed { .. } | - ref system @ System::Symbolic | - ref system @ System::Alphabetic | - ref system @ System::Numeric - if rule.symbols.is_none() => - { - let system = system.to_css_string(); - Some(ContextualParseError::InvalidCounterStyleWithoutSymbols( - system, - )) - }, - ref system @ System::Alphabetic | ref system @ System::Numeric - if rule.symbols().unwrap().0.len() < 2 => - { - let system = system.to_css_string(); - Some(ContextualParseError::InvalidCounterStyleNotEnoughSymbols( - system, - )) - }, - System::Additive if rule.additive_symbols.is_none() => { - Some(ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols) - }, - System::Extends(_) if rule.symbols.is_some() => { - Some(ContextualParseError::InvalidCounterStyleExtendsWithSymbols) - }, - System::Extends(_) if rule.additive_symbols.is_some() => { - Some(ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols) - }, - _ => None, - }; - if let Some(error) = error { - context.log_css_error(start, error); - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(rule) - } -} - -struct CounterStyleRuleParser<'a, 'b: 'a> { - context: &'a ParserContext<'b>, - rule: &'a mut CounterStyleRuleData, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for CounterStyleRuleParser<'a, 'b> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for CounterStyleRuleParser<'a, 'b> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for CounterStyleRuleParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - false - } - fn parse_declarations(&self) -> bool { - true - } -} - -macro_rules! checker { - ($self:ident._($value:ident)) => {}; - ($self:ident. $checker:ident($value:ident)) => { - if !$self.$checker(&$value) { - return false; - } - }; -} - -macro_rules! counter_style_descriptors { - ( - $( #[$doc: meta] $name: tt $ident: ident / $setter: ident [$checker: tt]: $ty: ty, )+ - ) => { - /// An @counter-style rule - #[derive(Clone, Debug, ToShmem)] - pub struct CounterStyleRuleData { - name: CustomIdent, - generation: Wrapping<u32>, - $( - #[$doc] - $ident: Option<$ty>, - )+ - /// Line and column of the @counter-style rule source code. - pub source_location: SourceLocation, - } - - impl CounterStyleRuleData { - fn empty(name: CustomIdent, source_location: SourceLocation) -> Self { - CounterStyleRuleData { - name: name, - generation: Wrapping(0), - $( - $ident: None, - )+ - source_location, - } - } - - $( - #[$doc] - pub fn $ident(&self) -> Option<&$ty> { - self.$ident.as_ref() - } - )+ - - $( - #[$doc] - pub fn $setter(&mut self, value: $ty) -> bool { - checker!(self.$checker(value)); - self.$ident = Some(value); - self.generation += Wrapping(1); - true - } - )+ - } - - impl<'a, 'b, 'i> DeclarationParser<'i> for CounterStyleRuleParser<'a, 'b> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - match_ignore_ascii_case! { &*name, - $( - $name => { - // DeclarationParser also calls parse_entirely so we’d normally not - // need to, but in this case we do because we set the value as a side - // effect rather than returning it. - let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; - self.rule.$ident = Some(value) - }, - )* - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - } - Ok(()) - } - } - - impl ToCssWithGuard for CounterStyleRuleData { - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@counter-style ")?; - self.name.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" { ")?; - $( - if let Some(ref value) = self.$ident { - dest.write_str(concat!($name, ": "))?; - ToCss::to_css(value, &mut CssWriter::new(dest))?; - dest.write_str("; ")?; - } - )+ - dest.write_char('}') - } - } - } -} - -counter_style_descriptors! { - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-system> - "system" system / set_system [check_system]: System, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative> - "negative" negative / set_negative [_]: Negative, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-prefix> - "prefix" prefix / set_prefix [_]: Symbol, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-suffix> - "suffix" suffix / set_suffix [_]: Symbol, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-range> - "range" range / set_range [_]: CounterRanges, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad> - "pad" pad / set_pad [_]: Pad, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback> - "fallback" fallback / set_fallback [_]: Fallback, - - /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols> - "symbols" symbols / set_symbols [check_symbols]: Symbols, - - /// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols> - "additive-symbols" additive_symbols / - set_additive_symbols [check_additive_symbols]: AdditiveSymbols, - - /// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as> - "speak-as" speak_as / set_speak_as [_]: SpeakAs, -} - -// Implements the special checkers for some setters. -// See <https://drafts.csswg.org/css-counter-styles/#the-csscounterstylerule-interface> -impl CounterStyleRuleData { - /// Check that the system is effectively not changed. Only params - /// of system descriptor is changeable. - fn check_system(&self, value: &System) -> bool { - mem::discriminant(self.resolved_system()) == mem::discriminant(value) - } - - fn check_symbols(&self, value: &Symbols) -> bool { - match *self.resolved_system() { - // These two systems require at least two symbols. - System::Numeric | System::Alphabetic => value.0.len() >= 2, - // No symbols should be set for extends system. - System::Extends(_) => false, - _ => true, - } - } - - fn check_additive_symbols(&self, _value: &AdditiveSymbols) -> bool { - match *self.resolved_system() { - // No additive symbols should be set for extends system. - System::Extends(_) => false, - _ => true, - } - } -} - -impl CounterStyleRuleData { - /// Get the name of the counter style rule. - pub fn name(&self) -> &CustomIdent { - &self.name - } - - /// Set the name of the counter style rule. Caller must ensure that - /// the name is valid. - pub fn set_name(&mut self, name: CustomIdent) { - debug_assert!(is_valid_name_definition(&name)); - self.name = name; - } - - /// Get the current generation of the counter style rule. - pub fn generation(&self) -> u32 { - self.generation.0 - } - - /// Get the system of this counter style rule, default to - /// `symbolic` if not specified. - pub fn resolved_system(&self) -> &System { - match self.system { - Some(ref system) => system, - None => &System::Symbolic, - } - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-system> -#[derive(Clone, Debug, ToShmem)] -pub enum System { - /// 'cyclic' - Cyclic, - /// 'numeric' - Numeric, - /// 'alphabetic' - Alphabetic, - /// 'symbolic' - Symbolic, - /// 'additive' - Additive, - /// 'fixed <integer>?' - Fixed { - /// '<integer>?' - first_symbol_value: Option<Integer>, - }, - /// 'extends <counter-style-name>' - Extends(CustomIdent), -} - -impl Parse for System { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - try_match_ident_ignore_ascii_case! { input, - "cyclic" => Ok(System::Cyclic), - "numeric" => Ok(System::Numeric), - "alphabetic" => Ok(System::Alphabetic), - "symbolic" => Ok(System::Symbolic), - "additive" => Ok(System::Additive), - "fixed" => { - let first_symbol_value = input.try_parse(|i| Integer::parse(context, i)).ok(); - Ok(System::Fixed { first_symbol_value }) - }, - "extends" => { - let other = parse_counter_style_name(input)?; - Ok(System::Extends(other)) - }, - } - } -} - -impl ToCss for System { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - System::Cyclic => dest.write_str("cyclic"), - System::Numeric => dest.write_str("numeric"), - System::Alphabetic => dest.write_str("alphabetic"), - System::Symbolic => dest.write_str("symbolic"), - System::Additive => dest.write_str("additive"), - System::Fixed { first_symbol_value } => { - if let Some(value) = first_symbol_value { - dest.write_str("fixed ")?; - value.to_css(dest) - } else { - dest.write_str("fixed") - } - }, - System::Extends(ref other) => { - dest.write_str("extends ")?; - other.to_css(dest) - }, - } - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#typedef-symbol> -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum Symbol { - /// <string> - String(crate::OwnedStr), - /// <custom-ident> - Ident(CustomIdent), - // Not implemented: - // /// <image> - // Image(Image), -} - -impl Parse for Symbol { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::QuotedString(ref s) => Ok(Symbol::String(s.as_ref().to_owned().into())), - Token::Ident(ref s) => Ok(Symbol::Ident(CustomIdent::from_ident(location, s, &[])?)), - ref t => Err(location.new_unexpected_token_error(t.clone())), - } - } -} - -impl Symbol { - /// Returns whether this symbol is allowed in symbols() function. - pub fn is_allowed_in_symbols(&self) -> bool { - match self { - // Identifier is not allowed. - &Symbol::Ident(_) => false, - _ => true, - } - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-negative> -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct Negative(pub Symbol, pub Option<Symbol>); - -impl Parse for Negative { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Negative( - Symbol::parse(context, input)?, - input.try_parse(|input| Symbol::parse(context, input)).ok(), - )) - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-range> -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct CounterRange { - /// The start of the range. - pub start: CounterBound, - /// The end of the range. - pub end: CounterBound, -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-range> -/// -/// Empty represents 'auto' -#[derive(Clone, Debug, ToCss, ToShmem)] -#[css(comma)] -pub struct CounterRanges(#[css(iterable, if_empty = "auto")] pub crate::OwnedSlice<CounterRange>); - -/// A bound found in `CounterRanges`. -#[derive(Clone, Copy, Debug, ToCss, ToShmem)] -pub enum CounterBound { - /// An integer bound. - Integer(Integer), - /// The infinite bound. - Infinite, -} - -impl Parse for CounterRanges { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("auto")) - .is_ok() - { - return Ok(CounterRanges(Default::default())); - } - - let ranges = input.parse_comma_separated(|input| { - let start = parse_bound(context, input)?; - let end = parse_bound(context, input)?; - if let (CounterBound::Integer(start), CounterBound::Integer(end)) = (start, end) { - if start > end { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - Ok(CounterRange { start, end }) - })?; - - Ok(CounterRanges(ranges.into())) - } -} - -fn parse_bound<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<CounterBound, ParseError<'i>> { - if let Ok(integer) = input.try_parse(|input| Integer::parse(context, input)) { - return Ok(CounterBound::Integer(integer)); - } - input.expect_ident_matching("infinite")?; - Ok(CounterBound::Infinite) -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-pad> -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct Pad(pub Integer, pub Symbol); - -impl Parse for Pad { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let pad_with = input.try_parse(|input| Symbol::parse(context, input)); - let min_length = Integer::parse_non_negative(context, input)?; - let pad_with = pad_with.or_else(|_| Symbol::parse(context, input))?; - Ok(Pad(min_length, pad_with)) - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-fallback> -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct Fallback(pub CustomIdent); - -impl Parse for Fallback { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Fallback(parse_counter_style_name(input)?)) - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-symbols> -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToCss, ToShmem, -)] -#[repr(C)] -pub struct Symbols(#[css(iterable)] pub crate::OwnedSlice<Symbol>); - -impl Parse for Symbols { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut symbols = Vec::new(); - while let Ok(s) = input.try_parse(|input| Symbol::parse(context, input)) { - symbols.push(s); - } - if symbols.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(Symbols(symbols.into())) - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#descdef-counter-style-additive-symbols> -#[derive(Clone, Debug, ToCss, ToShmem)] -#[css(comma)] -pub struct AdditiveSymbols(#[css(iterable)] pub crate::OwnedSlice<AdditiveTuple>); - -impl Parse for AdditiveSymbols { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let tuples = Vec::<AdditiveTuple>::parse(context, input)?; - // FIXME maybe? https://github.com/w3c/csswg-drafts/issues/1220 - if tuples - .windows(2) - .any(|window| window[0].weight <= window[1].weight) - { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(AdditiveSymbols(tuples.into())) - } -} - -/// <integer> && <symbol> -#[derive(Clone, Debug, ToCss, ToShmem)] -pub struct AdditiveTuple { - /// <integer> - pub weight: Integer, - /// <symbol> - pub symbol: Symbol, -} - -impl OneOrMoreSeparated for AdditiveTuple { - type S = Comma; -} - -impl Parse for AdditiveTuple { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let symbol = input.try_parse(|input| Symbol::parse(context, input)); - let weight = Integer::parse_non_negative(context, input)?; - let symbol = symbol.or_else(|_| Symbol::parse(context, input))?; - Ok(Self { weight, symbol }) - } -} - -/// <https://drafts.csswg.org/css-counter-styles/#counter-style-speak-as> -#[derive(Clone, Debug, ToCss, ToShmem)] -pub enum SpeakAs { - /// auto - Auto, - /// bullets - Bullets, - /// numbers - Numbers, - /// words - Words, - // /// spell-out, not supported, see bug 1024178 - // SpellOut, - /// <counter-style-name> - Other(CustomIdent), -} - -impl Parse for SpeakAs { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut is_spell_out = false; - let result = input.try_parse(|input| { - let ident = input.expect_ident().map_err(|_| ())?; - match_ignore_ascii_case! { &*ident, - "auto" => Ok(SpeakAs::Auto), - "bullets" => Ok(SpeakAs::Bullets), - "numbers" => Ok(SpeakAs::Numbers), - "words" => Ok(SpeakAs::Words), - "spell-out" => { - is_spell_out = true; - Err(()) - }, - _ => Err(()), - } - }); - if is_spell_out { - // spell-out is not supported, but don’t parse it as a <counter-style-name>. - // See bug 1024178. - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - result.or_else(|_| Ok(SpeakAs::Other(parse_counter_style_name(input)?))) - } -} diff --git a/components/style/counter_style/predefined.rs b/components/style/counter_style/predefined.rs deleted file mode 100644 index 7243e3b3f32..00000000000 --- a/components/style/counter_style/predefined.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* 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/. */ - -predefined! { - "decimal", - "decimal-leading-zero", - "arabic-indic", - "armenian", - "upper-armenian", - "lower-armenian", - "bengali", - "cambodian", - "khmer", - "cjk-decimal", - "devanagari", - "georgian", - "gujarati", - "gurmukhi", - "hebrew", - "kannada", - "lao", - "malayalam", - "mongolian", - "myanmar", - "oriya", - "persian", - "lower-roman", - "upper-roman", - "tamil", - "telugu", - "thai", - "tibetan", - "lower-alpha", - "lower-latin", - "upper-alpha", - "upper-latin", - "cjk-earthly-branch", - "cjk-heavenly-stem", - "lower-greek", - "hiragana", - "hiragana-iroha", - "katakana", - "katakana-iroha", - "disc", - "circle", - "square", - "disclosure-open", - "disclosure-closed", - "japanese-informal", - "japanese-formal", - "korean-hangul-formal", - "korean-hanja-informal", - "korean-hanja-formal", - "simp-chinese-informal", - "simp-chinese-formal", - "trad-chinese-informal", - "trad-chinese-formal", - "cjk-ideographic", - "ethiopic-numeric", -} diff --git a/components/style/counter_style/update_predefined.py b/components/style/counter_style/update_predefined.py deleted file mode 100755 index 1523958ff3e..00000000000 --- a/components/style/counter_style/update_predefined.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python - -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -import os.path -import re -import urllib - - -def main(filename): - names = [ - re.search('>([^>]+)(</dfn>|<a class="self-link")', line).group(1) - for line in urllib.urlopen("https://drafts.csswg.org/css-counter-styles/") - if 'data-dfn-for="<counter-style-name>"' in line - or 'data-dfn-for="<counter-style>"' in line - ] - with open(filename, "wb") as f: - f.write( - """\ -/* 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/. */ - -predefined! { -""" - ) - for name in names: - f.write(' "%s",\n' % name) - f.write("}\n") - - -if __name__ == "__main__": - main(os.path.join(os.path.dirname(__file__), "predefined.rs")) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs deleted file mode 100644 index f9516befc67..00000000000 --- a/components/style/custom_properties.rs +++ /dev/null @@ -1,1201 +0,0 @@ -/* 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/. */ - -//! Support for [custom properties for cascading variables][custom]. -//! -//! [custom]: https://drafts.csswg.org/css-variables/ - -use crate::applicable_declarations::CascadePriority; -use crate::media_queries::Device; -use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue}; -use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher}; -use crate::Atom; -use cssparser::{ - CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType, -}; -use indexmap::IndexMap; -use selectors::parser::SelectorParseErrorKind; -use servo_arc::Arc; -use smallvec::SmallVec; -use std::borrow::Cow; -use std::cmp; -use std::collections::hash_map::Entry; -use std::fmt::{self, Write}; -use std::hash::BuildHasherDefault; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// The environment from which to get `env` function values. -/// -/// TODO(emilio): If this becomes a bit more complex we should probably move it -/// to the `media_queries` module, or something. -#[derive(Debug, MallocSizeOf)] -pub struct CssEnvironment; - -type EnvironmentEvaluator = fn(device: &Device) -> VariableValue; - -struct EnvironmentVariable { - name: Atom, - evaluator: EnvironmentEvaluator, -} - -macro_rules! make_variable { - ($name:expr, $evaluator:expr) => {{ - EnvironmentVariable { - name: $name, - evaluator: $evaluator, - } - }}; -} - -fn get_safearea_inset_top(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().top) -} - -fn get_safearea_inset_bottom(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().bottom) -} - -fn get_safearea_inset_left(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().left) -} - -fn get_safearea_inset_right(device: &Device) -> VariableValue { - VariableValue::pixels(device.safe_area_insets().right) -} - -#[cfg(feature = "gecko")] -fn get_content_preferred_color_scheme(device: &Device) -> VariableValue { - use crate::gecko::media_features::PrefersColorScheme; - let prefers_color_scheme = unsafe { - crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme( - device.document(), - /* use_content = */ true, - ) - }; - VariableValue::ident(match prefers_color_scheme { - PrefersColorScheme::Light => "light", - PrefersColorScheme::Dark => "dark", - }) -} - -#[cfg(feature = "servo")] -fn get_content_preferred_color_scheme(_device: &Device) -> VariableValue { - // TODO: implement this. - VariableValue::ident("light") -} - -fn get_scrollbar_inline_size(device: &Device) -> VariableValue { - VariableValue::pixels(device.scrollbar_inline_size().px()) -} - -static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ - make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top), - make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom), - make_variable!(atom!("safe-area-inset-left"), get_safearea_inset_left), - make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), -]; - -#[cfg(feature = "gecko")] -macro_rules! lnf_int { - ($id:ident) => { - unsafe { - crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt( - crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32, - ) - } - }; -} - -#[cfg(feature = "servo")] -macro_rules! lnf_int { - ($id:ident) => { - // TODO: implement this. - 0 - }; -} - -macro_rules! lnf_int_variable { - ($atom:expr, $id:ident, $ctor:ident) => {{ - fn __eval(_: &Device) -> VariableValue { - VariableValue::$ctor(lnf_int!($id)) - } - make_variable!($atom, __eval) - }}; -} - -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [ - lnf_int_variable!( - atom!("-moz-gtk-csd-titlebar-radius"), - TitlebarRadius, - int_pixels - ), - lnf_int_variable!( - atom!("-moz-gtk-csd-close-button-position"), - GTKCSDCloseButtonPosition, - integer - ), - lnf_int_variable!( - atom!("-moz-gtk-csd-minimize-button-position"), - GTKCSDMinimizeButtonPosition, - integer - ), - lnf_int_variable!( - atom!("-moz-gtk-csd-maximize-button-position"), - GTKCSDMaximizeButtonPosition, - integer - ), - make_variable!( - atom!("-moz-content-preferred-color-scheme"), - get_content_preferred_color_scheme - ), - make_variable!(atom!("scrollbar-inline-size"), get_scrollbar_inline_size), -]; - -impl CssEnvironment { - #[inline] - fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> { - if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) { - return Some((var.evaluator)(device)); - } - if !device.chrome_rules_enabled_for_document() { - return None; - } - let var = CHROME_ENVIRONMENT_VARIABLES - .iter() - .find(|var| var.name == *name)?; - Some((var.evaluator)(device)) - } -} - -/// A custom property name is just an `Atom`. -/// -/// Note that this does not include the `--` prefix -pub type Name = Atom; - -/// Parse a custom property name. -/// -/// <https://drafts.csswg.org/css-variables/#typedef-custom-property-name> -pub fn parse_name(s: &str) -> Result<&str, ()> { - if s.starts_with("--") && s.len() > 2 { - Ok(&s[2..]) - } else { - Err(()) - } -} - -/// A value for a custom property is just a set of tokens. -/// -/// We preserve the original CSS for serialization, and also the variable -/// references to other custom property names. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct VariableValue { - css: String, - - first_token_type: TokenSerializationType, - last_token_type: TokenSerializationType, - - /// Whether a variable value has a reference to an environment variable. - /// - /// If this is the case, we need to perform variable substitution on the - /// value. - references_environment: bool, - - /// Custom property names in var() functions. - references: Box<[Name]>, -} - -impl ToCss for SpecifiedValue { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str(&self.css) - } -} - -/// A map from CSS variable names to CSS variable computed values, used for -/// resolving. -/// -/// A consistent ordering is required for CSSDeclaration objects in the -/// DOM. CSSDeclarations expose property names as indexed properties, which -/// need to be stable. So we keep an array of property names which order is -/// determined on the order that they are added to the name-value map. -/// -/// The variable values are guaranteed to not have references to other -/// properties. -pub type CustomPropertiesMap = - IndexMap<Name, Arc<VariableValue>, BuildHasherDefault<PrecomputedHasher>>; - -/// Both specified and computed values are VariableValues, the difference is -/// whether var() functions are expanded. -pub type SpecifiedValue = VariableValue; -/// Both specified and computed values are VariableValues, the difference is -/// whether var() functions are expanded. -pub type ComputedValue = VariableValue; - -/// A struct holding information about the external references to that a custom -/// property value may have. -#[derive(Default)] -struct VarOrEnvReferences { - custom_property_references: PrecomputedHashSet<Name>, - references_environment: bool, -} - -impl VariableValue { - fn empty() -> Self { - Self { - css: String::new(), - last_token_type: TokenSerializationType::nothing(), - first_token_type: TokenSerializationType::nothing(), - references: Default::default(), - references_environment: false, - } - } - - fn push<'i>( - &mut self, - input: &Parser<'i, '_>, - css: &str, - css_first_token_type: TokenSerializationType, - css_last_token_type: TokenSerializationType, - ) -> Result<(), ParseError<'i>> { - /// Prevent values from getting terribly big since you can use custom - /// properties exponentially. - /// - /// This number (2MB) is somewhat arbitrary, but silly enough that no - /// reasonable page should hit it. We could limit by number of total - /// substitutions, but that was very easy to work around in practice - /// (just choose a larger initial value and boom). - const MAX_VALUE_LENGTH_IN_BYTES: usize = 2 * 1024 * 1024; - - if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // This happens e.g. between two subsequent var() functions: - // `var(--a)var(--b)`. - // - // In that case, css_*_token_type is nonsensical. - if css.is_empty() { - return Ok(()); - } - - self.first_token_type.set_if_nothing(css_first_token_type); - // If self.first_token_type was nothing, - // self.last_token_type is also nothing and this will be false: - if self - .last_token_type - .needs_separator_when_before(css_first_token_type) - { - self.css.push_str("/**/") - } - self.css.push_str(css); - self.last_token_type = css_last_token_type; - Ok(()) - } - - fn push_from<'i>( - &mut self, - input: &Parser<'i, '_>, - position: (SourcePosition, TokenSerializationType), - last_token_type: TokenSerializationType, - ) -> Result<(), ParseError<'i>> { - self.push( - input, - input.slice_from(position.0), - position.1, - last_token_type, - ) - } - - fn push_variable<'i>( - &mut self, - input: &Parser<'i, '_>, - variable: &ComputedValue, - ) -> Result<(), ParseError<'i>> { - debug_assert!(variable.references.is_empty()); - self.push( - input, - &variable.css, - variable.first_token_type, - variable.last_token_type, - ) - } - - /// Parse a custom property value. - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Arc<Self>, ParseError<'i>> { - let mut references = VarOrEnvReferences::default(); - - let (first_token_type, css, last_token_type) = - parse_self_contained_declaration_value(input, Some(&mut references))?; - - let custom_property_references = references - .custom_property_references - .into_iter() - .collect::<Vec<_>>() - .into_boxed_slice(); - - let mut css = css.into_owned(); - css.shrink_to_fit(); - - Ok(Arc::new(VariableValue { - css, - first_token_type, - last_token_type, - references: custom_property_references, - references_environment: references.references_environment, - })) - } - - /// Create VariableValue from an int. - fn integer(number: i32) -> Self { - Self::from_token(Token::Number { - has_sign: false, - value: number as f32, - int_value: Some(number), - }) - } - - /// Create VariableValue from an int. - fn ident(ident: &'static str) -> Self { - Self::from_token(Token::Ident(ident.into())) - } - - /// Create VariableValue from a float amount of CSS pixels. - fn pixels(number: f32) -> Self { - // FIXME (https://github.com/servo/rust-cssparser/issues/266): - // No way to get TokenSerializationType::Dimension without creating - // Token object. - Self::from_token(Token::Dimension { - has_sign: false, - value: number, - int_value: None, - unit: CowRcStr::from("px"), - }) - } - - /// Create VariableValue from an integer amount of CSS pixels. - fn int_pixels(number: i32) -> Self { - Self::from_token(Token::Dimension { - has_sign: false, - value: number as f32, - int_value: Some(number), - unit: CowRcStr::from("px"), - }) - } - - fn from_token(token: Token) -> Self { - let token_type = token.serialization_type(); - let mut css = token.to_css_string(); - css.shrink_to_fit(); - - VariableValue { - css, - first_token_type: token_type, - last_token_type: token_type, - references: Default::default(), - references_environment: false, - } - } -} - -/// Parse the value of a non-custom property that contains `var()` references. -pub fn parse_non_custom_with_var<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<(TokenSerializationType, Cow<'i, str>), ParseError<'i>> { - let (first_token_type, css, _) = parse_self_contained_declaration_value(input, None)?; - Ok((first_token_type, css)) -} - -fn parse_self_contained_declaration_value<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, -) -> Result<(TokenSerializationType, Cow<'i, str>, TokenSerializationType), ParseError<'i>> { - let start_position = input.position(); - let mut missing_closing_characters = String::new(); - let (first, last) = - parse_declaration_value(input, references, &mut missing_closing_characters)?; - let mut css: Cow<str> = input.slice_from(start_position).into(); - if !missing_closing_characters.is_empty() { - // Unescaped backslash at EOF in a quoted string is ignored. - if css.ends_with("\\") && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') { - css.to_mut().pop(); - } - css.to_mut().push_str(&missing_closing_characters); - } - Ok((first, css, last)) -} - -/// <https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value> -fn parse_declaration_value<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, - missing_closing_characters: &mut String, -) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> { - input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { - parse_declaration_value_block(input, references, missing_closing_characters) - }) -} - -/// Like parse_declaration_value, but accept `!` and `;` since they are only -/// invalid at the top level -fn parse_declaration_value_block<'i, 't>( - input: &mut Parser<'i, 't>, - mut references: Option<&mut VarOrEnvReferences>, - missing_closing_characters: &mut String, -) -> Result<(TokenSerializationType, TokenSerializationType), ParseError<'i>> { - input.skip_whitespace(); - let mut token_start = input.position(); - let mut token = match input.next_including_whitespace_and_comments() { - Ok(token) => token, - Err(_) => { - return Ok(( - TokenSerializationType::nothing(), - TokenSerializationType::nothing(), - )); - }, - }; - let first_token_type = token.serialization_type(); - loop { - macro_rules! nested { - () => { - input.parse_nested_block(|input| { - parse_declaration_value_block( - input, - references.as_mut().map(|r| &mut **r), - missing_closing_characters, - ) - })? - }; - } - macro_rules! check_closed { - ($closing:expr) => { - if !input.slice_from(token_start).ends_with($closing) { - missing_closing_characters.push_str($closing) - } - }; - } - let last_token_type = match *token { - Token::Comment(_) => { - let serialization_type = token.serialization_type(); - let token_slice = input.slice_from(token_start); - if !token_slice.ends_with("*/") { - missing_closing_characters.push_str(if token_slice.ends_with('*') { - "/" - } else { - "*/" - }) - } - serialization_type - }, - Token::BadUrl(ref u) => { - let e = StyleParseErrorKind::BadUrlInDeclarationValueBlock(u.clone()); - return Err(input.new_custom_error(e)); - }, - Token::BadString(ref s) => { - let e = StyleParseErrorKind::BadStringInDeclarationValueBlock(s.clone()); - return Err(input.new_custom_error(e)); - }, - Token::CloseParenthesis => { - let e = StyleParseErrorKind::UnbalancedCloseParenthesisInDeclarationValueBlock; - return Err(input.new_custom_error(e)); - }, - Token::CloseSquareBracket => { - let e = StyleParseErrorKind::UnbalancedCloseSquareBracketInDeclarationValueBlock; - return Err(input.new_custom_error(e)); - }, - Token::CloseCurlyBracket => { - let e = StyleParseErrorKind::UnbalancedCloseCurlyBracketInDeclarationValueBlock; - return Err(input.new_custom_error(e)); - }, - Token::Function(ref name) => { - if name.eq_ignore_ascii_case("var") { - let args_start = input.state(); - input.parse_nested_block(|input| { - parse_var_function(input, references.as_mut().map(|r| &mut **r)) - })?; - input.reset(&args_start); - } else if name.eq_ignore_ascii_case("env") { - let args_start = input.state(); - input.parse_nested_block(|input| { - parse_env_function(input, references.as_mut().map(|r| &mut **r)) - })?; - input.reset(&args_start); - } - nested!(); - check_closed!(")"); - Token::CloseParenthesis.serialization_type() - }, - Token::ParenthesisBlock => { - nested!(); - check_closed!(")"); - Token::CloseParenthesis.serialization_type() - }, - Token::CurlyBracketBlock => { - nested!(); - check_closed!("}"); - Token::CloseCurlyBracket.serialization_type() - }, - Token::SquareBracketBlock => { - nested!(); - check_closed!("]"); - Token::CloseSquareBracket.serialization_type() - }, - Token::QuotedString(_) => { - let serialization_type = token.serialization_type(); - let token_slice = input.slice_from(token_start); - let quote = &token_slice[..1]; - debug_assert!(matches!(quote, "\"" | "'")); - if !(token_slice.ends_with(quote) && token_slice.len() > 1) { - missing_closing_characters.push_str(quote) - } - serialization_type - }, - Token::Ident(ref value) | - Token::AtKeyword(ref value) | - Token::Hash(ref value) | - Token::IDHash(ref value) | - Token::UnquotedUrl(ref value) | - Token::Dimension { - unit: ref value, .. - } => { - let serialization_type = token.serialization_type(); - let is_unquoted_url = matches!(token, Token::UnquotedUrl(_)); - if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") { - // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD - // Check the value in case the final backslash was itself escaped. - // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD. - // (Unescaped U+FFFD would also work, but removing the backslash is annoying.) - missing_closing_characters.push_str("�") - } - if is_unquoted_url { - check_closed!(")"); - } - serialization_type - }, - _ => token.serialization_type(), - }; - - token_start = input.position(); - token = match input.next_including_whitespace_and_comments() { - Ok(token) => token, - Err(..) => return Ok((first_token_type, last_token_type)), - }; - } -} - -fn parse_fallback<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { - // Exclude `!` and `;` at the top level - // https://drafts.csswg.org/css-syntax/#typedef-declaration-value - input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| { - // Skip until the end. - while input.next_including_whitespace_and_comments().is_ok() {} - Ok(()) - }) -} - -// If the var function is valid, return Ok((custom_property_name, fallback)) -fn parse_var_function<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, -) -> Result<(), ParseError<'i>> { - let name = input.expect_ident_cloned()?; - let name = parse_name(&name).map_err(|()| { - input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())) - })?; - if input.try_parse(|input| input.expect_comma()).is_ok() { - parse_fallback(input)?; - } - if let Some(refs) = references { - refs.custom_property_references.insert(Atom::from(name)); - } - Ok(()) -} - -fn parse_env_function<'i, 't>( - input: &mut Parser<'i, 't>, - references: Option<&mut VarOrEnvReferences>, -) -> Result<(), ParseError<'i>> { - // TODO(emilio): This should be <custom-ident> per spec, but no other - // browser does that, see https://github.com/w3c/csswg-drafts/issues/3262. - input.expect_ident()?; - if input.try_parse(|input| input.expect_comma()).is_ok() { - parse_fallback(input)?; - } - if let Some(references) = references { - references.references_environment = true; - } - Ok(()) -} - -/// A struct that takes care of encapsulating the cascade process for custom -/// properties. -pub struct CustomPropertiesBuilder<'a> { - seen: PrecomputedHashSet<&'a Name>, - may_have_cycles: bool, - custom_properties: Option<CustomPropertiesMap>, - inherited: Option<&'a Arc<CustomPropertiesMap>>, - reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>, - device: &'a Device, -} - -impl<'a> CustomPropertiesBuilder<'a> { - /// Create a new builder, inheriting from a given custom properties map. - pub fn new(inherited: Option<&'a Arc<CustomPropertiesMap>>, device: &'a Device) -> Self { - Self { - seen: PrecomputedHashSet::default(), - reverted: Default::default(), - may_have_cycles: false, - custom_properties: None, - inherited, - device, - } - } - - /// Cascade a given custom property declaration. - pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) { - let CustomDeclaration { - ref name, - ref value, - } = *declaration; - - if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) { - if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { - return; - } - } - - let was_already_present = !self.seen.insert(name); - if was_already_present { - return; - } - - if !self.value_may_affect_style(name, value) { - return; - } - - if self.custom_properties.is_none() { - self.custom_properties = Some(match self.inherited { - Some(inherited) => (**inherited).clone(), - None => CustomPropertiesMap::default(), - }); - } - - let map = self.custom_properties.as_mut().unwrap(); - match *value { - CustomDeclarationValue::Value(ref unparsed_value) => { - let has_references = !unparsed_value.references.is_empty(); - self.may_have_cycles |= has_references; - - // If the variable value has no references and it has an - // environment variable here, perform substitution here instead - // of forcing a full traversal in `substitute_all` afterwards. - let value = if !has_references && unparsed_value.references_environment { - let result = substitute_references_in_value(unparsed_value, &map, &self.device); - match result { - Ok(new_value) => new_value, - Err(..) => { - map.remove(name); - return; - }, - } - } else { - (*unparsed_value).clone() - }; - map.insert(name.clone(), value); - }, - CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword { - CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { - let origin_revert = keyword == CSSWideKeyword::Revert; - self.seen.remove(name); - self.reverted.insert(name, (priority, origin_revert)); - }, - CSSWideKeyword::Initial => { - map.remove(name); - }, - // handled in value_may_affect_style - CSSWideKeyword::Unset | CSSWideKeyword::Inherit => unreachable!(), - }, - } - } - - fn value_may_affect_style(&self, name: &Name, value: &CustomDeclarationValue) -> bool { - match *value { - CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) | - CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => { - // Custom properties are inherited by default. So - // explicit 'inherit' or 'unset' means we can just use - // any existing value in the inherited CustomPropertiesMap. - return false; - }, - _ => {}, - } - - let existing_value = self - .custom_properties - .as_ref() - .and_then(|m| m.get(name)) - .or_else(|| self.inherited.and_then(|m| m.get(name))); - - match (existing_value, value) { - (None, &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) => { - // The initial value of a custom property is the same as it - // not existing in the map. - return false; - }, - (Some(existing_value), &CustomDeclarationValue::Value(ref value)) => { - // Don't bother overwriting an existing inherited value with - // the same specified value. - if existing_value == value { - return false; - } - }, - _ => {}, - } - - true - } - - fn inherited_properties_match(&self, map: &CustomPropertiesMap) -> bool { - let inherited = match self.inherited { - Some(inherited) => inherited, - None => return false, - }; - if inherited.len() != map.len() { - return false; - } - for name in self.seen.iter() { - if inherited.get(*name) != map.get(*name) { - return false; - } - } - true - } - - /// Returns the final map of applicable custom properties. - /// - /// If there was any specified property, we've created a new map and now we - /// need to remove any potential cycles, and wrap it in an arc. - /// - /// Otherwise, just use the inherited custom properties map. - pub fn build(mut self) -> Option<Arc<CustomPropertiesMap>> { - let mut map = match self.custom_properties.take() { - Some(m) => m, - None => return self.inherited.cloned(), - }; - - if self.may_have_cycles { - substitute_all(&mut map, &self.seen, self.device); - } - - // Some pages apply a lot of redundant custom properties, see e.g. - // bug 1758974 comment 5. Try to detect the case where the values - // haven't really changed, and save some memory by reusing the inherited - // map in that case. - if self.inherited_properties_match(&map) { - return self.inherited.cloned(); - } - - map.shrink_to_fit(); - Some(Arc::new(map)) - } -} - -/// Resolve all custom properties to either substituted, invalid, or unset -/// (meaning we should use the inherited value). -/// -/// It does cycle dependencies removal at the same time as substitution. -fn substitute_all( - custom_properties_map: &mut CustomPropertiesMap, - seen: &PrecomputedHashSet<&Name>, - device: &Device, -) { - // The cycle dependencies removal in this function is a variant - // of Tarjan's algorithm. It is mostly based on the pseudo-code - // listed in - // https://en.wikipedia.org/w/index.php? - // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495 - - /// Struct recording necessary information for each variable. - #[derive(Debug)] - struct VarInfo { - /// The name of the variable. It will be taken to save addref - /// when the corresponding variable is popped from the stack. - /// This also serves as a mark for whether the variable is - /// currently in the stack below. - name: Option<Name>, - /// If the variable is in a dependency cycle, lowlink represents - /// a smaller index which corresponds to a variable in the same - /// strong connected component, which is known to be accessible - /// from this variable. It is not necessarily the root, though. - lowlink: usize, - } - /// Context struct for traversing the variable graph, so that we can - /// avoid referencing all the fields multiple times. - #[derive(Debug)] - struct Context<'a> { - /// Number of variables visited. This is used as the order index - /// when we visit a new unresolved variable. - count: usize, - /// The map from custom property name to its order index. - index_map: PrecomputedHashMap<Name, usize>, - /// Information of each variable indexed by the order index. - var_info: SmallVec<[VarInfo; 5]>, - /// The stack of order index of visited variables. It contains - /// all unfinished strong connected components. - stack: SmallVec<[usize; 5]>, - map: &'a mut CustomPropertiesMap, - /// To resolve the environment to substitute `env()` variables. - device: &'a Device, - } - - /// This function combines the traversal for cycle removal and value - /// substitution. It returns either a signal None if this variable - /// has been fully resolved (to either having no reference or being - /// marked invalid), or the order index for the given name. - /// - /// When it returns, the variable corresponds to the name would be - /// in one of the following states: - /// * It is still in context.stack, which means it is part of an - /// potentially incomplete dependency circle. - /// * It has been removed from the map. It can be either that the - /// substitution failed, or it is inside a dependency circle. - /// When this function removes a variable from the map because - /// of dependency circle, it would put all variables in the same - /// strong connected component to the set together. - /// * It doesn't have any reference, because either this variable - /// doesn't have reference at all in specified value, or it has - /// been completely resolved. - /// * There is no such variable at all. - fn traverse<'a>(name: &Name, context: &mut Context<'a>) -> Option<usize> { - // Some shortcut checks. - let (name, value) = { - let value = context.map.get(name)?; - - // Nothing to resolve. - if value.references.is_empty() { - debug_assert!( - !value.references_environment, - "Should've been handled earlier" - ); - return None; - } - - // Whether this variable has been visited in this traversal. - let key; - match context.index_map.entry(name.clone()) { - Entry::Occupied(entry) => { - return Some(*entry.get()); - }, - Entry::Vacant(entry) => { - key = entry.key().clone(); - entry.insert(context.count); - }, - } - - // Hold a strong reference to the value so that we don't - // need to keep reference to context.map. - (key, value.clone()) - }; - - // Add new entry to the information table. - let index = context.count; - context.count += 1; - debug_assert_eq!(index, context.var_info.len()); - context.var_info.push(VarInfo { - name: Some(name), - lowlink: index, - }); - context.stack.push(index); - - let mut self_ref = false; - let mut lowlink = index; - for next in value.references.iter() { - let next_index = match traverse(next, context) { - Some(index) => index, - // There is nothing to do if the next variable has been - // fully resolved at this point. - None => { - continue; - }, - }; - let next_info = &context.var_info[next_index]; - if next_index > index { - // The next variable has a larger index than us, so it - // must be inserted in the recursive call above. We want - // to get its lowlink. - lowlink = cmp::min(lowlink, next_info.lowlink); - } else if next_index == index { - self_ref = true; - } else if next_info.name.is_some() { - // The next variable has a smaller order index and it is - // in the stack, so we are at the same component. - lowlink = cmp::min(lowlink, next_index); - } - } - - context.var_info[index].lowlink = lowlink; - if lowlink != index { - // This variable is in a loop, but it is not the root of - // this strong connected component. We simply return for - // now, and the root would remove it from the map. - // - // This cannot be removed from the map here, because - // otherwise the shortcut check at the beginning of this - // function would return the wrong value. - return Some(index); - } - - // This is the root of a strong-connected component. - let mut in_loop = self_ref; - let name; - loop { - let var_index = context - .stack - .pop() - .expect("The current variable should still be in stack"); - let var_info = &mut context.var_info[var_index]; - // We should never visit the variable again, so it's safe - // to take the name away, so that we don't do additional - // reference count. - let var_name = var_info - .name - .take() - .expect("Variable should not be poped from stack twice"); - if var_index == index { - name = var_name; - break; - } - // Anything here is in a loop which can traverse to the - // variable we are handling, so remove it from the map, it's invalid - // at computed-value time. - context.map.remove(&var_name); - in_loop = true; - } - if in_loop { - // This variable is in loop. Resolve to invalid. - context.map.remove(&name); - return None; - } - - // Now we have shown that this variable is not in a loop, and all of its - // dependencies should have been resolved. We can start substitution - // now. - let result = substitute_references_in_value(&value, &context.map, &context.device); - match result { - Ok(computed_value) => { - context.map.insert(name, computed_value); - }, - Err(..) => { - // This is invalid, reset it to the guaranteed-invalid value. - context.map.remove(&name); - }, - } - - // All resolved, so return the signal value. - None - } - - // Note that `seen` doesn't contain names inherited from our parent, but - // those can't have variable references (since we inherit the computed - // variables) so we don't want to spend cycles traversing them anyway. - for name in seen { - let mut context = Context { - count: 0, - index_map: PrecomputedHashMap::default(), - stack: SmallVec::new(), - var_info: SmallVec::new(), - map: custom_properties_map, - device, - }; - traverse(name, &mut context); - } -} - -/// Replace `var()` and `env()` functions in a pre-existing variable value. -fn substitute_references_in_value<'i>( - value: &'i VariableValue, - custom_properties: &CustomPropertiesMap, - device: &Device, -) -> Result<Arc<ComputedValue>, ParseError<'i>> { - debug_assert!(!value.references.is_empty() || value.references_environment); - - let mut input = ParserInput::new(&value.css); - let mut input = Parser::new(&mut input); - let mut position = (input.position(), value.first_token_type); - let mut computed_value = ComputedValue::empty(); - - let last_token_type = substitute_block( - &mut input, - &mut position, - &mut computed_value, - custom_properties, - device, - )?; - - computed_value.push_from(&input, position, last_token_type)?; - computed_value.css.shrink_to_fit(); - Ok(Arc::new(computed_value)) -} - -/// Replace `var()` functions in an arbitrary bit of input. -/// -/// If the variable has its initial value, the callback should return `Err(())` -/// and leave `partial_computed_value` unchanged. -/// -/// Otherwise, it should push the value of the variable (with its own `var()` functions replaced) -/// to `partial_computed_value` and return `Ok(last_token_type of what was pushed)` -/// -/// Return `Err(())` if `input` is invalid at computed-value time. -/// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise. -fn substitute_block<'i>( - input: &mut Parser<'i, '_>, - position: &mut (SourcePosition, TokenSerializationType), - partial_computed_value: &mut ComputedValue, - custom_properties: &CustomPropertiesMap, - device: &Device, -) -> Result<TokenSerializationType, ParseError<'i>> { - let mut last_token_type = TokenSerializationType::nothing(); - let mut set_position_at_next_iteration = false; - loop { - let before_this_token = input.position(); - let next = input.next_including_whitespace_and_comments(); - if set_position_at_next_iteration { - *position = ( - before_this_token, - match next { - Ok(token) => token.serialization_type(), - Err(_) => TokenSerializationType::nothing(), - }, - ); - set_position_at_next_iteration = false; - } - let token = match next { - Ok(token) => token, - Err(..) => break, - }; - match token { - Token::Function(ref name) - if name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env") => - { - let is_env = name.eq_ignore_ascii_case("env"); - - partial_computed_value.push( - input, - input.slice(position.0..before_this_token), - position.1, - last_token_type, - )?; - input.parse_nested_block(|input| { - // parse_var_function() / parse_env_function() ensure neither .unwrap() will fail. - let name = { - let name = input.expect_ident().unwrap(); - if is_env { - Atom::from(&**name) - } else { - Atom::from(parse_name(&name).unwrap()) - } - }; - - let env_value; - let value = if is_env { - if let Some(v) = device.environment().get(&name, device) { - env_value = v; - Some(&env_value) - } else { - None - } - } else { - custom_properties.get(&name).map(|v| &**v) - }; - - if let Some(v) = value { - last_token_type = v.last_token_type; - partial_computed_value.push_variable(input, v)?; - // Skip over the fallback, as `parse_nested_block` would return `Err` - // if we don't consume all of `input`. - // FIXME: Add a specialized method to cssparser to do this with less work. - while input.next().is_ok() {} - } else { - input.expect_comma()?; - input.skip_whitespace(); - let after_comma = input.state(); - let first_token_type = input - .next_including_whitespace_and_comments() - .ok() - .map_or_else(TokenSerializationType::nothing, |t| { - t.serialization_type() - }); - input.reset(&after_comma); - let mut position = (after_comma.position(), first_token_type); - last_token_type = substitute_block( - input, - &mut position, - partial_computed_value, - custom_properties, - device, - )?; - partial_computed_value.push_from(input, position, last_token_type)?; - } - Ok(()) - })?; - set_position_at_next_iteration = true - }, - Token::Function(_) | - Token::ParenthesisBlock | - Token::CurlyBracketBlock | - Token::SquareBracketBlock => { - input.parse_nested_block(|input| { - substitute_block( - input, - position, - partial_computed_value, - custom_properties, - device, - ) - })?; - // It's the same type for CloseCurlyBracket and CloseSquareBracket. - last_token_type = Token::CloseParenthesis.serialization_type(); - }, - - _ => last_token_type = token.serialization_type(), - } - } - // FIXME: deal with things being implicitly closed at the end of the input. E.g. - // ```html - // <div style="--color: rgb(0,0,0"> - // <p style="background: var(--color) var(--image) top left; --image: url('a.png"></p> - // </div> - // ``` - Ok(last_token_type) -} - -/// Replace `var()` and `env()` functions for a non-custom property. -/// -/// Return `Err(())` for invalid at computed time. -pub fn substitute<'i>( - input: &'i str, - first_token_type: TokenSerializationType, - computed_values_map: Option<&Arc<CustomPropertiesMap>>, - device: &Device, -) -> Result<String, ParseError<'i>> { - let mut substituted = ComputedValue::empty(); - let mut input = ParserInput::new(input); - let mut input = Parser::new(&mut input); - let mut position = (input.position(), first_token_type); - let empty_map = CustomPropertiesMap::default(); - let custom_properties = match computed_values_map { - Some(m) => &**m, - None => &empty_map, - }; - let last_token_type = substitute_block( - &mut input, - &mut position, - &mut substituted, - &custom_properties, - device, - )?; - substituted.push_from(&input, position, last_token_type)?; - Ok(substituted.css) -} diff --git a/components/style/data.rs b/components/style/data.rs deleted file mode 100644 index 62dff225f8f..00000000000 --- a/components/style/data.rs +++ /dev/null @@ -1,545 +0,0 @@ -/* 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/. */ - -//! Per-node data used in style calculation. - -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{SharedStyleContext, StackLimitChecker}; -use crate::dom::TElement; -use crate::invalidation::element::invalidator::InvalidationResult; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::properties::ComputedValues; -use crate::selector_parser::{PseudoElement, RestyleDamage, EAGER_PSEUDO_COUNT}; -use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles, ResolvedStyle}; -#[cfg(feature = "gecko")] -use malloc_size_of::MallocSizeOfOps; -use selectors::NthIndexCache; -use servo_arc::Arc; -use std::fmt; -use std::mem; -use std::ops::{Deref, DerefMut}; - -bitflags! { - /// Various flags stored on ElementData. - #[derive(Default)] - pub struct ElementDataFlags: u8 { - /// Whether the styles changed for this restyle. - const WAS_RESTYLED = 1 << 0; - /// Whether the last traversal of this element did not do - /// any style computation. This is not true during the initial - /// styling pass, nor is it true when we restyle (in which case - /// WAS_RESTYLED is set). - /// - /// This bit always corresponds to the last time the element was - /// traversed, so each traversal simply updates it with the appropriate - /// value. - const TRAVERSED_WITHOUT_STYLING = 1 << 1; - - /// Whether the primary style of this element data was reused from - /// another element via a rule node comparison. This allows us to - /// differentiate between elements that shared styles because they met - /// all the criteria of the style sharing cache, compared to elements - /// that reused style structs via rule node identity. - /// - /// The former gives us stronger transitive guarantees that allows us to - /// apply the style sharing cache to cousins. - const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2; - } -} - -/// A lazily-allocated list of styles for eagerly-cascaded pseudo-elements. -/// -/// We use an Arc so that sharing these styles via the style sharing cache does -/// not require duplicate allocations. We leverage the copy-on-write semantics of -/// Arc::make_mut(), which is free (i.e. does not require atomic RMU operations) -/// in servo_arc. -#[derive(Clone, Debug, Default)] -pub struct EagerPseudoStyles(Option<Arc<EagerPseudoArray>>); - -#[derive(Default)] -struct EagerPseudoArray(EagerPseudoArrayInner); -type EagerPseudoArrayInner = [Option<Arc<ComputedValues>>; EAGER_PSEUDO_COUNT]; - -impl Deref for EagerPseudoArray { - type Target = EagerPseudoArrayInner; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for EagerPseudoArray { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -// Manually implement `Clone` here because the derived impl of `Clone` for -// array types assumes the value inside is `Copy`. -impl Clone for EagerPseudoArray { - fn clone(&self) -> Self { - let mut clone = Self::default(); - for i in 0..EAGER_PSEUDO_COUNT { - clone[i] = self.0[i].clone(); - } - clone - } -} - -// Override Debug to print which pseudos we have, and substitute the rule node -// for the much-more-verbose ComputedValues stringification. -impl fmt::Debug for EagerPseudoArray { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "EagerPseudoArray {{ ")?; - for i in 0..EAGER_PSEUDO_COUNT { - if let Some(ref values) = self[i] { - write!( - f, - "{:?}: {:?}, ", - PseudoElement::from_eager_index(i), - &values.rules - )?; - } - } - write!(f, "}}") - } -} - -// Can't use [None; EAGER_PSEUDO_COUNT] here because it complains -// about Copy not being implemented for our Arc type. -#[cfg(feature = "gecko")] -const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None, None]; -#[cfg(feature = "servo")] -const EMPTY_PSEUDO_ARRAY: &'static EagerPseudoArrayInner = &[None, None, None]; - -impl EagerPseudoStyles { - /// Returns whether there are any pseudo styles. - pub fn is_empty(&self) -> bool { - self.0.is_none() - } - - /// Grabs a reference to the list of styles, if they exist. - pub fn as_optional_array(&self) -> Option<&EagerPseudoArrayInner> { - match self.0 { - None => None, - Some(ref x) => Some(&x.0), - } - } - - /// Grabs a reference to the list of styles or a list of None if - /// there are no styles to be had. - pub fn as_array(&self) -> &EagerPseudoArrayInner { - self.as_optional_array().unwrap_or(EMPTY_PSEUDO_ARRAY) - } - - /// Returns a reference to the style for a given eager pseudo, if it exists. - pub fn get(&self, pseudo: &PseudoElement) -> Option<&Arc<ComputedValues>> { - debug_assert!(pseudo.is_eager()); - self.0 - .as_ref() - .and_then(|p| p[pseudo.eager_index()].as_ref()) - } - - /// Sets the style for the eager pseudo. - pub fn set(&mut self, pseudo: &PseudoElement, value: Arc<ComputedValues>) { - if self.0.is_none() { - self.0 = Some(Arc::new(Default::default())); - } - let arr = Arc::make_mut(self.0.as_mut().unwrap()); - arr[pseudo.eager_index()] = Some(value); - } -} - -/// The styles associated with a node, including the styles for any -/// pseudo-elements. -#[derive(Clone, Default)] -pub struct ElementStyles { - /// The element's style. - pub primary: Option<Arc<ComputedValues>>, - /// A list of the styles for the element's eagerly-cascaded pseudo-elements. - pub pseudos: EagerPseudoStyles, -} - -// There's one of these per rendered elements so it better be small. -size_of_test!(ElementStyles, 16); - -/// Information on how this element uses viewport units. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum ViewportUnitUsage { - /// No viewport units are used. - None = 0, - /// There are viewport units used from regular style rules (which means we - /// should re-cascade). - FromDeclaration, - /// There are viewport units used from container queries (which means we - /// need to re-selector-match). - FromQuery, -} - -impl ElementStyles { - /// Returns the primary style. - pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> { - self.primary.as_ref() - } - - /// Returns the primary style. Panic if no style available. - pub fn primary(&self) -> &Arc<ComputedValues> { - self.primary.as_ref().unwrap() - } - - /// Whether this element `display` value is `none`. - pub fn is_display_none(&self) -> bool { - self.primary().get_box().clone_display().is_none() - } - - /// Whether this element uses viewport units. - pub fn viewport_unit_usage(&self) -> ViewportUnitUsage { - fn usage_from_flags(flags: ComputedValueFlags) -> ViewportUnitUsage { - if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES) { - return ViewportUnitUsage::FromQuery; - } - if flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { - return ViewportUnitUsage::FromDeclaration; - } - ViewportUnitUsage::None - } - - let mut usage = usage_from_flags(self.primary().flags); - for pseudo_style in self.pseudos.as_array() { - if let Some(ref pseudo_style) = pseudo_style { - usage = std::cmp::max(usage, usage_from_flags(pseudo_style.flags)); - } - } - - usage - } - - #[cfg(feature = "gecko")] - fn size_of_excluding_cvs(&self, _ops: &mut MallocSizeOfOps) -> usize { - // As the method name suggests, we don't measures the ComputedValues - // here, because they are measured on the C++ side. - - // XXX: measure the EagerPseudoArray itself, but not the ComputedValues - // within it. - - 0 - } -} - -// We manually implement Debug for ElementStyles so that we can avoid the -// verbose stringification of every property in the ComputedValues. We -// substitute the rule node instead. -impl fmt::Debug for ElementStyles { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "ElementStyles {{ primary: {:?}, pseudos: {:?} }}", - self.primary.as_ref().map(|x| &x.rules), - self.pseudos - ) - } -} - -/// Style system data associated with an Element. -/// -/// In Gecko, this hangs directly off the Element. Servo, this is embedded -/// inside of layout data, which itself hangs directly off the Element. In -/// both cases, it is wrapped inside an AtomicRefCell to ensure thread safety. -#[derive(Debug, Default)] -pub struct ElementData { - /// The styles for the element and its pseudo-elements. - pub styles: ElementStyles, - - /// The restyle damage, indicating what kind of layout changes are required - /// afte restyling. - pub damage: RestyleDamage, - - /// The restyle hint, which indicates whether selectors need to be rematched - /// for this element, its children, and its descendants. - pub hint: RestyleHint, - - /// Flags. - pub flags: ElementDataFlags, -} - -// There's one of these per rendered elements so it better be small. -size_of_test!(ElementData, 24); - -/// The kind of restyle that a single element should do. -#[derive(Debug)] -pub enum RestyleKind { - /// We need to run selector matching plus re-cascade, that is, a full - /// restyle. - MatchAndCascade, - /// We need to recascade with some replacement rule, such as the style - /// attribute, or animation rules. - CascadeWithReplacements(RestyleHint), - /// We only need to recascade, for example, because only inherited - /// properties in the parent changed. - CascadeOnly, -} - -impl ElementData { - /// Invalidates style for this element, its descendants, and later siblings, - /// based on the snapshot of the element that we took when attributes or - /// state changed. - pub fn invalidate_style_if_needed<'a, E: TElement>( - &mut self, - element: E, - shared_context: &SharedStyleContext, - stack_limit_checker: Option<&StackLimitChecker>, - nth_index_cache: &mut NthIndexCache, - ) -> InvalidationResult { - // In animation-only restyle we shouldn't touch snapshot at all. - if shared_context.traversal_flags.for_animation_only() { - return InvalidationResult::empty(); - } - - use crate::invalidation::element::invalidator::TreeStyleInvalidator; - use crate::invalidation::element::state_and_attributes::StateAndAttrInvalidationProcessor; - - debug!( - "invalidate_style_if_needed: {:?}, flags: {:?}, has_snapshot: {}, \ - handled_snapshot: {}, pseudo: {:?}", - element, - shared_context.traversal_flags, - element.has_snapshot(), - element.handled_snapshot(), - element.implemented_pseudo_element() - ); - - if !element.has_snapshot() || element.handled_snapshot() { - return InvalidationResult::empty(); - } - - let mut processor = - StateAndAttrInvalidationProcessor::new(shared_context, element, self, nth_index_cache); - - let invalidator = TreeStyleInvalidator::new(element, stack_limit_checker, &mut processor); - - let result = invalidator.invalidate(); - - unsafe { element.set_handled_snapshot() } - debug_assert!(element.handled_snapshot()); - - result - } - - /// Returns true if this element has styles. - #[inline] - pub fn has_styles(&self) -> bool { - self.styles.primary.is_some() - } - - /// Returns this element's styles as resolved styles to use for sharing. - pub fn share_styles(&self) -> ResolvedElementStyles { - ResolvedElementStyles { - primary: self.share_primary_style(), - pseudos: self.styles.pseudos.clone(), - } - } - - /// Returns this element's primary style as a resolved style to use for sharing. - pub fn share_primary_style(&self) -> PrimaryStyle { - let reused_via_rule_node = self - .flags - .contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - - PrimaryStyle { - style: ResolvedStyle(self.styles.primary().clone()), - reused_via_rule_node, - } - } - - /// Sets a new set of styles, returning the old ones. - pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles { - if new_styles.primary.reused_via_rule_node { - self.flags - .insert(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - } else { - self.flags - .remove(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE); - } - mem::replace(&mut self.styles, new_styles.into()) - } - - /// Returns the kind of restyling that we're going to need to do on this - /// element, based of the stored restyle hint. - pub fn restyle_kind(&self, shared_context: &SharedStyleContext) -> Option<RestyleKind> { - if shared_context.traversal_flags.for_animation_only() { - return self.restyle_kind_for_animation(shared_context); - } - - let style = match self.styles.primary { - Some(ref s) => s, - None => return Some(RestyleKind::MatchAndCascade), - }; - - let hint = self.hint; - if hint.is_empty() { - return None; - } - - let needs_to_match_self = hint.intersects(RestyleHint::RESTYLE_SELF) || - (hint.intersects(RestyleHint::RESTYLE_SELF_IF_PSEUDO) && style.is_pseudo_style()); - if needs_to_match_self { - return Some(RestyleKind::MatchAndCascade); - } - - if hint.has_replacements() { - debug_assert!( - !hint.has_animation_hint(), - "Animation only restyle hint should have already processed" - ); - return Some(RestyleKind::CascadeWithReplacements( - hint & RestyleHint::replacements(), - )); - } - - let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) || - (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) && - style - .flags - .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); - if needs_to_recascade_self { - return Some(RestyleKind::CascadeOnly); - } - - None - } - - /// Returns the kind of restyling for animation-only restyle. - fn restyle_kind_for_animation( - &self, - shared_context: &SharedStyleContext, - ) -> Option<RestyleKind> { - debug_assert!(shared_context.traversal_flags.for_animation_only()); - debug_assert!( - self.has_styles(), - "animation traversal doesn't care about unstyled elements" - ); - - // FIXME: We should ideally restyle here, but it is a hack to work around our weird - // animation-only traversal stuff: If we're display: none and the rules we could - // match could change, we consider our style up-to-date. This is because re-cascading with - // and old style doesn't guarantee returning the correct animation style (that's - // bug 1393323). So if our display changed, and it changed from display: none, we would - // incorrectly forget about it and wouldn't be able to correctly style our descendants - // later. - // XXX Figure out if this still makes sense. - let hint = self.hint; - if self.styles.is_display_none() && hint.intersects(RestyleHint::RESTYLE_SELF) { - return None; - } - - let style = self.styles.primary(); - // Return either CascadeWithReplacements or CascadeOnly in case of - // animation-only restyle. I.e. animation-only restyle never does - // selector matching. - if hint.has_animation_hint() { - return Some(RestyleKind::CascadeWithReplacements( - hint & RestyleHint::for_animations(), - )); - } - - let needs_to_recascade_self = hint.intersects(RestyleHint::RECASCADE_SELF) || - (hint.intersects(RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE) && - style - .flags - .contains(ComputedValueFlags::INHERITS_RESET_STYLE)); - if needs_to_recascade_self { - return Some(RestyleKind::CascadeOnly); - } - return None; - } - - /// Drops any restyle state from the element. - /// - /// FIXME(bholley): The only caller of this should probably just assert that - /// the hint is empty and call clear_flags_and_damage(). - #[inline] - pub fn clear_restyle_state(&mut self) { - self.hint = RestyleHint::empty(); - self.clear_restyle_flags_and_damage(); - } - - /// Drops restyle flags and damage from the element. - #[inline] - pub fn clear_restyle_flags_and_damage(&mut self) { - self.damage = RestyleDamage::empty(); - self.flags.remove(ElementDataFlags::WAS_RESTYLED); - } - - /// Mark this element as restyled, which is useful to know whether we need - /// to do a post-traversal. - pub fn set_restyled(&mut self) { - self.flags.insert(ElementDataFlags::WAS_RESTYLED); - self.flags - .remove(ElementDataFlags::TRAVERSED_WITHOUT_STYLING); - } - - /// Returns true if this element was restyled. - #[inline] - pub fn is_restyle(&self) -> bool { - self.flags.contains(ElementDataFlags::WAS_RESTYLED) - } - - /// Mark that we traversed this element without computing any style for it. - pub fn set_traversed_without_styling(&mut self) { - self.flags - .insert(ElementDataFlags::TRAVERSED_WITHOUT_STYLING); - } - - /// Returns whether this element has been part of a restyle. - #[inline] - pub fn contains_restyle_data(&self) -> bool { - self.is_restyle() || !self.hint.is_empty() || !self.damage.is_empty() - } - - /// Returns whether it is safe to perform cousin sharing based on the ComputedValues - /// identity of the primary style in this ElementData. There are a few subtle things - /// to check. - /// - /// First, if a parent element was already styled and we traversed past it without - /// restyling it, that may be because our clever invalidation logic was able to prove - /// that the styles of that element would remain unchanged despite changes to the id - /// or class attributes. However, style sharing relies on the strong guarantee that all - /// the classes and ids up the respective parent chains are identical. As such, if we - /// skipped styling for one (or both) of the parents on this traversal, we can't share - /// styles across cousins. Note that this is a somewhat conservative check. We could - /// tighten it by having the invalidation logic explicitly flag elements for which it - /// ellided styling. - /// - /// Second, we want to only consider elements whose ComputedValues match due to a hit - /// in the style sharing cache, rather than due to the rule-node-based reuse that - /// happens later in the styling pipeline. The former gives us the stronger guarantees - /// we need for style sharing, the latter does not. - pub fn safe_for_cousin_sharing(&self) -> bool { - if self.flags.intersects( - ElementDataFlags::TRAVERSED_WITHOUT_STYLING | - ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE, - ) { - return false; - } - if !self - .styles - .primary() - .get_box() - .clone_container_type() - .is_normal() - { - return false; - } - true - } - - /// Measures memory usage. - #[cfg(feature = "gecko")] - pub fn size_of_excluding_cvs(&self, ops: &mut MallocSizeOfOps) -> usize { - let n = self.styles.size_of_excluding_cvs(ops); - - // We may measure more fields in the future if DMD says it's worth it. - - n - } -} diff --git a/components/style/dom.rs b/components/style/dom.rs deleted file mode 100644 index 7fc29280e69..00000000000 --- a/components/style/dom.rs +++ /dev/null @@ -1,940 +0,0 @@ -/* 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/. */ - -//! Types and traits used to access the DOM from style calculation. - -#![allow(unsafe_code)] -#![deny(missing_docs)] - -use crate::applicable_declarations::ApplicableDeclarationBlock; -use crate::context::SharedStyleContext; -#[cfg(feature = "gecko")] -use crate::context::{PostAnimationTasks, UpdateAnimationsTasks}; -use crate::data::ElementData; -use crate::media_queries::Device; -use crate::properties::{AnimationDeclarations, ComputedValues, PropertyDeclarationBlock}; -use crate::selector_parser::{AttrValue, Lang, PseudoElement, SelectorImpl}; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::stylist::CascadeData; -use crate::values::computed::Display; -use crate::values::AtomIdent; -use crate::{LocalName, WeakAtom}; -use atomic_refcell::{AtomicRef, AtomicRefMut}; -use selectors::matching::{QuirksMode, VisitedHandlingMode}; -use selectors::sink::Push; -use selectors::Element as SelectorsElement; -use servo_arc::{Arc, ArcBorrow}; -use std::fmt; -use std::fmt::Debug; -use std::hash::Hash; -use std::ops::Deref; -use style_traits::dom::ElementState; - -pub use style_traits::dom::OpaqueNode; - -/// Simple trait to provide basic information about the type of an element. -/// -/// We avoid exposing the full type id, since computing it in the general case -/// would be difficult for Gecko nodes. -pub trait NodeInfo { - /// Whether this node is an element. - fn is_element(&self) -> bool; - /// Whether this node is a text node. - fn is_text_node(&self) -> bool; -} - -/// A node iterator that only returns node that don't need layout. -pub struct LayoutIterator<T>(pub T); - -impl<T, N> Iterator for LayoutIterator<T> -where - T: Iterator<Item = N>, - N: NodeInfo, -{ - type Item = N; - - fn next(&mut self) -> Option<N> { - loop { - let n = self.0.next()?; - // Filter out nodes that layout should ignore. - if n.is_text_node() || n.is_element() { - return Some(n); - } - } - } -} - -/// An iterator over the DOM children of a node. -pub struct DomChildren<N>(Option<N>); -impl<N> Iterator for DomChildren<N> -where - N: TNode, -{ - type Item = N; - - fn next(&mut self) -> Option<N> { - let n = self.0.take()?; - self.0 = n.next_sibling(); - Some(n) - } -} - -/// An iterator over the DOM descendants of a node in pre-order. -pub struct DomDescendants<N> { - previous: Option<N>, - scope: N, -} - -impl<N> Iterator for DomDescendants<N> -where - N: TNode, -{ - type Item = N; - - #[inline] - fn next(&mut self) -> Option<N> { - let prev = self.previous.take()?; - self.previous = prev.next_in_preorder(self.scope); - self.previous - } -} - -/// The `TDocument` trait, to represent a document node. -pub trait TDocument: Sized + Copy + Clone { - /// The concrete `TNode` type. - type ConcreteNode: TNode<ConcreteDocument = Self>; - - /// Get this document as a `TNode`. - fn as_node(&self) -> Self::ConcreteNode; - - /// Returns whether this document is an HTML document. - fn is_html_document(&self) -> bool; - - /// Returns the quirks mode of this document. - fn quirks_mode(&self) -> QuirksMode; - - /// Get a list of elements with a given ID in this document, sorted by - /// tree position. - /// - /// Can return an error to signal that this list is not available, or also - /// return an empty slice. - fn elements_with_id<'a>( - &self, - _id: &AtomIdent, - ) -> Result<&'a [<Self::ConcreteNode as TNode>::ConcreteElement], ()> - where - Self: 'a, - { - Err(()) - } - - /// This document's shared lock. - fn shared_lock(&self) -> &SharedRwLock; -} - -/// The `TNode` trait. This is the main generic trait over which the style -/// system can be implemented. -pub trait TNode: Sized + Copy + Clone + Debug + NodeInfo + PartialEq { - /// The concrete `TElement` type. - type ConcreteElement: TElement<ConcreteNode = Self>; - - /// The concrete `TDocument` type. - type ConcreteDocument: TDocument<ConcreteNode = Self>; - - /// The concrete `TShadowRoot` type. - type ConcreteShadowRoot: TShadowRoot<ConcreteNode = Self>; - - /// Get this node's parent node. - fn parent_node(&self) -> Option<Self>; - - /// Get this node's first child. - fn first_child(&self) -> Option<Self>; - - /// Get this node's last child. - fn last_child(&self) -> Option<Self>; - - /// Get this node's previous sibling. - fn prev_sibling(&self) -> Option<Self>; - - /// Get this node's next sibling. - fn next_sibling(&self) -> Option<Self>; - - /// Get the owner document of this node. - fn owner_doc(&self) -> Self::ConcreteDocument; - - /// Iterate over the DOM children of a node. - #[inline(always)] - fn dom_children(&self) -> DomChildren<Self> { - DomChildren(self.first_child()) - } - - /// Returns whether the node is attached to a document. - fn is_in_document(&self) -> bool; - - /// Iterate over the DOM children of a node, in preorder. - #[inline(always)] - fn dom_descendants(&self) -> DomDescendants<Self> { - DomDescendants { - previous: Some(*self), - scope: *self, - } - } - - /// Returns the next node after this one, in a pre-order tree-traversal of - /// the subtree rooted at scoped_to. - #[inline] - fn next_in_preorder(&self, scoped_to: Self) -> Option<Self> { - if let Some(c) = self.first_child() { - return Some(c); - } - - let mut current = *self; - loop { - if current == scoped_to { - return None; - } - - if let Some(s) = current.next_sibling() { - return Some(s); - } - - debug_assert!( - current.parent_node().is_some(), - "Not a descendant of the scope?" - ); - current = current.parent_node()?; - } - } - - /// Get this node's parent element from the perspective of a restyle - /// traversal. - fn traversal_parent(&self) -> Option<Self::ConcreteElement>; - - /// Get this node's parent element if present. - fn parent_element(&self) -> Option<Self::ConcreteElement> { - self.parent_node().and_then(|n| n.as_element()) - } - - /// Get this node's parent element, or shadow host if it's a shadow root. - fn parent_element_or_host(&self) -> Option<Self::ConcreteElement> { - let parent = self.parent_node()?; - if let Some(e) = parent.as_element() { - return Some(e); - } - if let Some(root) = parent.as_shadow_root() { - return Some(root.host()); - } - None - } - - /// Converts self into an `OpaqueNode`. - fn opaque(&self) -> OpaqueNode; - - /// A debug id, only useful, mm... for debugging. - fn debug_id(self) -> usize; - - /// Get this node as an element, if it's one. - fn as_element(&self) -> Option<Self::ConcreteElement>; - - /// Get this node as a document, if it's one. - fn as_document(&self) -> Option<Self::ConcreteDocument>; - - /// Get this node as a ShadowRoot, if it's one. - fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot>; -} - -/// Wrapper to output the subtree rather than the single node when formatting -/// for Debug. -pub struct ShowSubtree<N: TNode>(pub N); -impl<N: TNode> Debug for ShowSubtree<N> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "DOM Subtree:")?; - fmt_subtree(f, &|f, n| write!(f, "{:?}", n), self.0, 1) - } -} - -/// Wrapper to output the subtree along with the ElementData when formatting -/// for Debug. -pub struct ShowSubtreeData<N: TNode>(pub N); -impl<N: TNode> Debug for ShowSubtreeData<N> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "DOM Subtree:")?; - fmt_subtree(f, &|f, n| fmt_with_data(f, n), self.0, 1) - } -} - -/// Wrapper to output the subtree along with the ElementData and primary -/// ComputedValues when formatting for Debug. This is extremely verbose. -#[cfg(feature = "servo")] -pub struct ShowSubtreeDataAndPrimaryValues<N: TNode>(pub N); -#[cfg(feature = "servo")] -impl<N: TNode> Debug for ShowSubtreeDataAndPrimaryValues<N> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "DOM Subtree:")?; - fmt_subtree(f, &|f, n| fmt_with_data_and_primary_values(f, n), self.0, 1) - } -} - -fn fmt_with_data<N: TNode>(f: &mut fmt::Formatter, n: N) -> fmt::Result { - if let Some(el) = n.as_element() { - write!( - f, - "{:?} dd={} aodd={} data={:?}", - el, - el.has_dirty_descendants(), - el.has_animation_only_dirty_descendants(), - el.borrow_data(), - ) - } else { - write!(f, "{:?}", n) - } -} - -#[cfg(feature = "servo")] -fn fmt_with_data_and_primary_values<N: TNode>(f: &mut fmt::Formatter, n: N) -> fmt::Result { - if let Some(el) = n.as_element() { - let dd = el.has_dirty_descendants(); - let aodd = el.has_animation_only_dirty_descendants(); - let data = el.borrow_data(); - let values = data.as_ref().and_then(|d| d.styles.get_primary()); - write!( - f, - "{:?} dd={} aodd={} data={:?} values={:?}", - el, dd, aodd, &data, values - ) - } else { - write!(f, "{:?}", n) - } -} - -fn fmt_subtree<F, N: TNode>(f: &mut fmt::Formatter, stringify: &F, n: N, indent: u32) -> fmt::Result -where - F: Fn(&mut fmt::Formatter, N) -> fmt::Result, -{ - for _ in 0..indent { - write!(f, " ")?; - } - stringify(f, n)?; - if let Some(e) = n.as_element() { - for kid in e.traversal_children() { - writeln!(f, "")?; - fmt_subtree(f, stringify, kid, indent + 1)?; - } - } - - Ok(()) -} - -/// The ShadowRoot trait. -pub trait TShadowRoot: Sized + Copy + Clone + Debug + PartialEq { - /// The concrete node type. - type ConcreteNode: TNode<ConcreteShadowRoot = Self>; - - /// Get this ShadowRoot as a node. - fn as_node(&self) -> Self::ConcreteNode; - - /// Get the shadow host that hosts this ShadowRoot. - fn host(&self) -> <Self::ConcreteNode as TNode>::ConcreteElement; - - /// Get the style data for this ShadowRoot. - fn style_data<'a>(&self) -> Option<&'a CascadeData> - where - Self: 'a; - - /// Get the list of shadow parts for this shadow root. - fn parts<'a>(&self) -> &[<Self::ConcreteNode as TNode>::ConcreteElement] - where - Self: 'a, - { - &[] - } - - /// Get a list of elements with a given ID in this shadow root, sorted by - /// tree position. - /// - /// Can return an error to signal that this list is not available, or also - /// return an empty slice. - fn elements_with_id<'a>( - &self, - _id: &AtomIdent, - ) -> Result<&'a [<Self::ConcreteNode as TNode>::ConcreteElement], ()> - where - Self: 'a, - { - Err(()) - } -} - -/// The element trait, the main abstraction the style crate acts over. -pub trait TElement: - Eq + PartialEq + Debug + Hash + Sized + Copy + Clone + SelectorsElement<Impl = SelectorImpl> -{ - /// The concrete node type. - type ConcreteNode: TNode<ConcreteElement = Self>; - - /// A concrete children iterator type in order to iterate over the `Node`s. - /// - /// TODO(emilio): We should eventually replace this with the `impl Trait` - /// syntax. - type TraversalChildrenIterator: Iterator<Item = Self::ConcreteNode>; - - /// Get this element as a node. - fn as_node(&self) -> Self::ConcreteNode; - - /// A debug-only check that the device's owner doc matches the actual doc - /// we're the root of. - /// - /// Otherwise we may set document-level state incorrectly, like the root - /// font-size used for rem units. - fn owner_doc_matches_for_testing(&self, _: &Device) -> bool { - true - } - - /// Whether this element should match user and content rules. - /// - /// We use this for Native Anonymous Content in Gecko. - fn matches_user_and_content_rules(&self) -> bool { - true - } - - /// Returns the depth of this element in the DOM. - fn depth(&self) -> usize { - let mut depth = 0; - let mut curr = *self; - while let Some(parent) = curr.traversal_parent() { - depth += 1; - curr = parent; - } - - depth - } - - /// Get this node's parent element from the perspective of a restyle - /// traversal. - fn traversal_parent(&self) -> Option<Self> { - self.as_node().traversal_parent() - } - - /// Get this node's children from the perspective of a restyle traversal. - fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator>; - - /// Returns the parent element we should inherit from. - /// - /// This is pretty much always the parent element itself, except in the case - /// of Gecko's Native Anonymous Content, which uses the traversal parent - /// (i.e. the flattened tree parent) and which also may need to find the - /// closest non-NAC ancestor. - fn inheritance_parent(&self) -> Option<Self> { - self.parent_element() - } - - /// The ::before pseudo-element of this element, if it exists. - fn before_pseudo_element(&self) -> Option<Self> { - None - } - - /// The ::after pseudo-element of this element, if it exists. - fn after_pseudo_element(&self) -> Option<Self> { - None - } - - /// The ::marker pseudo-element of this element, if it exists. - fn marker_pseudo_element(&self) -> Option<Self> { - None - } - - /// Execute `f` for each anonymous content child (apart from ::before and - /// ::after) whose originating element is `self`. - fn each_anonymous_content_child<F>(&self, _f: F) - where - F: FnMut(Self), - { - } - - /// Return whether this element is an element in the HTML namespace. - fn is_html_element(&self) -> bool; - - /// Return whether this element is an element in the MathML namespace. - fn is_mathml_element(&self) -> bool; - - /// Return whether this element is an element in the SVG namespace. - fn is_svg_element(&self) -> bool; - - /// Return whether this element is an element in the XUL namespace. - fn is_xul_element(&self) -> bool { - false - } - - /// Return the list of slotted nodes of this node. - fn slotted_nodes(&self) -> &[Self::ConcreteNode] { - &[] - } - - /// Get this element's style attribute. - fn style_attribute(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>; - - /// Unset the style attribute's dirty bit. - /// Servo doesn't need to manage ditry bit for style attribute. - fn unset_dirty_style_attribute(&self) {} - - /// Get this element's SMIL override declarations. - fn smil_override(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> { - None - } - - /// Get the combined animation and transition rules. - /// - /// FIXME(emilio): Is this really useful? - fn animation_declarations(&self, context: &SharedStyleContext) -> AnimationDeclarations { - if !self.may_have_animations() { - return Default::default(); - } - - AnimationDeclarations { - animations: self.animation_rule(context), - transitions: self.transition_rule(context), - } - } - - /// Get this element's animation rule. - fn animation_rule( - &self, - _: &SharedStyleContext, - ) -> Option<Arc<Locked<PropertyDeclarationBlock>>>; - - /// Get this element's transition rule. - fn transition_rule( - &self, - context: &SharedStyleContext, - ) -> Option<Arc<Locked<PropertyDeclarationBlock>>>; - - /// Get this element's state, for non-tree-structural pseudos. - fn state(&self) -> ElementState; - - /// Returns whether this element has a `part` attribute. - fn has_part_attr(&self) -> bool; - - /// Returns whether this element exports any part from its shadow tree. - fn exports_any_part(&self) -> bool; - - /// The ID for this element. - fn id(&self) -> Option<&WeakAtom>; - - /// Internal iterator for the classes of this element. - fn each_class<F>(&self, callback: F) - where - F: FnMut(&AtomIdent); - - /// Internal iterator for the part names of this element. - fn each_part<F>(&self, _callback: F) - where - F: FnMut(&AtomIdent), - { - } - - /// Internal iterator for the attribute names of this element. - fn each_attr_name<F>(&self, callback: F) - where - F: FnMut(&LocalName); - - /// Internal iterator for the part names that this element exports for a - /// given part name. - fn each_exported_part<F>(&self, _name: &AtomIdent, _callback: F) - where - F: FnMut(&AtomIdent), - { - } - - /// Whether a given element may generate a pseudo-element. - /// - /// This is useful to avoid computing, for example, pseudo styles for - /// `::-first-line` or `::-first-letter`, when we know it won't affect us. - /// - /// TODO(emilio, bz): actually implement the logic for it. - fn may_generate_pseudo(&self, pseudo: &PseudoElement, _primary_style: &ComputedValues) -> bool { - // ::before/::after are always supported for now, though we could try to - // optimize out leaf elements. - - // ::first-letter and ::first-line are only supported for block-inside - // things, and only in Gecko, not Servo. Unfortunately, Gecko has - // block-inside things that might have any computed display value due to - // things like fieldsets, legends, etc. Need to figure out how this - // should work. - debug_assert!( - pseudo.is_eager(), - "Someone called may_generate_pseudo with a non-eager pseudo." - ); - true - } - - /// Returns true if this element may have a descendant needing style processing. - /// - /// Note that we cannot guarantee the existence of such an element, because - /// it may have been removed from the DOM between marking it for restyle and - /// the actual restyle traversal. - fn has_dirty_descendants(&self) -> bool; - - /// Returns whether state or attributes that may change style have changed - /// on the element, and thus whether the element has been snapshotted to do - /// restyle hint computation. - fn has_snapshot(&self) -> bool; - - /// Returns whether the current snapshot if present has been handled. - fn handled_snapshot(&self) -> bool; - - /// Flags this element as having handled already its snapshot. - unsafe fn set_handled_snapshot(&self); - - /// Returns whether the element's styles are up-to-date after traversal - /// (i.e. in post traversal). - fn has_current_styles(&self, data: &ElementData) -> bool { - if self.has_snapshot() && !self.handled_snapshot() { - return false; - } - - data.has_styles() && - // TODO(hiro): When an animating element moved into subtree of - // contenteditable element, there remains animation restyle hints in - // post traversal. It's generally harmless since the hints will be - // processed in a next styling but ideally it should be processed soon. - // - // Without this, we get failures in: - // layout/style/crashtests/1383319.html - // layout/style/crashtests/1383001.html - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=1389675 tracks fixing - // this. - !data.hint.has_non_animation_invalidations() - } - - /// Flag that this element has a descendant for style processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn set_dirty_descendants(&self); - - /// Flag that this element has no descendant for style processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn unset_dirty_descendants(&self); - - /// Similar to the dirty_descendants but for representing a descendant of - /// the element needs to be updated in animation-only traversal. - fn has_animation_only_dirty_descendants(&self) -> bool { - false - } - - /// Flag that this element has a descendant for animation-only restyle - /// processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn set_animation_only_dirty_descendants(&self) {} - - /// Flag that this element has no descendant for animation-only restyle processing. - /// - /// Only safe to call with exclusive access to the element. - unsafe fn unset_animation_only_dirty_descendants(&self) {} - - /// Clear all bits related describing the dirtiness of descendants. - /// - /// In Gecko, this corresponds to the regular dirty descendants bit, the - /// animation-only dirty descendants bit, and the lazy frame construction - /// descendants bit. - unsafe fn clear_descendant_bits(&self) { - self.unset_dirty_descendants(); - } - - /// Returns true if this element is a visited link. - /// - /// Servo doesn't support visited styles yet. - fn is_visited_link(&self) -> bool { - false - } - - /// Returns the pseudo-element implemented by this element, if any. - /// - /// Gecko traverses pseudo-elements during the style traversal, and we need - /// to know this so we can properly grab the pseudo-element style from the - /// parent element. - /// - /// Note that we still need to compute the pseudo-elements before-hand, - /// given otherwise we don't know if we need to create an element or not. - /// - /// Servo doesn't have to deal with this. - fn implemented_pseudo_element(&self) -> Option<PseudoElement> { - None - } - - /// Atomically stores the number of children of this node that we will - /// need to process during bottom-up traversal. - fn store_children_to_process(&self, n: isize); - - /// Atomically notes that a child has been processed during bottom-up - /// traversal. Returns the number of children left to process. - fn did_process_child(&self) -> isize; - - /// Gets a reference to the ElementData container, or creates one. - /// - /// Unsafe because it can race to allocate and leak if not used with - /// exclusive access to the element. - unsafe fn ensure_data(&self) -> AtomicRefMut<ElementData>; - - /// Clears the element data reference, if any. - /// - /// Unsafe following the same reasoning as ensure_data. - unsafe fn clear_data(&self); - - /// Whether there is an ElementData container. - fn has_data(&self) -> bool; - - /// Immutably borrows the ElementData. - fn borrow_data(&self) -> Option<AtomicRef<ElementData>>; - - /// Mutably borrows the ElementData. - fn mutate_data(&self) -> Option<AtomicRefMut<ElementData>>; - - /// Whether we should skip any root- or item-based display property - /// blockification on this element. (This function exists so that Gecko - /// native anonymous content can opt out of this style fixup.) - fn skip_item_display_fixup(&self) -> bool; - - /// In Gecko, element has a flag that represents the element may have - /// any type of animations or not to bail out animation stuff early. - /// Whereas Servo doesn't have such flag. - fn may_have_animations(&self) -> bool; - - /// Creates a task to update various animation state on a given (pseudo-)element. - #[cfg(feature = "gecko")] - fn update_animations( - &self, - before_change_style: Option<Arc<ComputedValues>>, - tasks: UpdateAnimationsTasks, - ); - - /// Creates a task to process post animation on a given element. - #[cfg(feature = "gecko")] - fn process_post_animation(&self, tasks: PostAnimationTasks); - - /// Returns true if the element has relevant animations. Relevant - /// animations are those animations that are affecting the element's style - /// or are scheduled to do so in the future. - fn has_animations(&self, context: &SharedStyleContext) -> bool; - - /// Returns true if the element has a CSS animation. The `context` and `pseudo_element` - /// arguments are only used by Servo, since it stores animations globally and pseudo-elements - /// are not in the DOM. - fn has_css_animations( - &self, - context: &SharedStyleContext, - pseudo_element: Option<PseudoElement>, - ) -> bool; - - /// Returns true if the element has a CSS transition (including running transitions and - /// completed transitions). The `context` and `pseudo_element` arguments are only used - /// by Servo, since it stores animations globally and pseudo-elements are not in the DOM. - fn has_css_transitions( - &self, - context: &SharedStyleContext, - pseudo_element: Option<PseudoElement>, - ) -> bool; - - /// Returns true if the element has animation restyle hints. - fn has_animation_restyle_hints(&self) -> bool { - let data = match self.borrow_data() { - Some(d) => d, - None => return false, - }; - return data.hint.has_animation_hint(); - } - - /// The shadow root this element is a host of. - fn shadow_root(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot>; - - /// The shadow root which roots the subtree this element is contained in. - fn containing_shadow(&self) -> Option<<Self::ConcreteNode as TNode>::ConcreteShadowRoot>; - - /// Return the element which we can use to look up rules in the selector - /// maps. - /// - /// This is always the element itself, except in the case where we are an - /// element-backed pseudo-element, in which case we return the originating - /// element. - fn rule_hash_target(&self) -> Self { - if self.is_pseudo_element() { - self.pseudo_element_originating_element() - .expect("Trying to collect rules for a detached pseudo-element") - } else { - *self - } - } - - /// Executes the callback for each applicable style rule data which isn't - /// the main document's data (which stores UA / author rules). - /// - /// The element passed to the callback is the containing shadow host for the - /// data if it comes from Shadow DOM. - /// - /// Returns whether normal document author rules should apply. - /// - /// TODO(emilio): We could separate the invalidation data for elements - /// matching in other scopes to avoid over-invalidation. - fn each_applicable_non_document_style_rule_data<'a, F>(&self, mut f: F) -> bool - where - Self: 'a, - F: FnMut(&'a CascadeData, Self), - { - use crate::rule_collector::containing_shadow_ignoring_svg_use; - - let target = self.rule_hash_target(); - let matches_user_and_content_rules = target.matches_user_and_content_rules(); - let mut doc_rules_apply = matches_user_and_content_rules; - - // Use the same rules to look for the containing host as we do for rule - // collection. - if let Some(shadow) = containing_shadow_ignoring_svg_use(target) { - doc_rules_apply = false; - if let Some(data) = shadow.style_data() { - f(data, shadow.host()); - } - } - - if let Some(shadow) = target.shadow_root() { - if let Some(data) = shadow.style_data() { - f(data, shadow.host()); - } - } - - let mut current = target.assigned_slot(); - while let Some(slot) = current { - // Slots can only have assigned nodes when in a shadow tree. - let shadow = slot.containing_shadow().unwrap(); - if let Some(data) = shadow.style_data() { - if data.any_slotted_rule() { - f(data, shadow.host()); - } - } - current = slot.assigned_slot(); - } - - if target.has_part_attr() { - if let Some(mut inner_shadow) = target.containing_shadow() { - loop { - let inner_shadow_host = inner_shadow.host(); - match inner_shadow_host.containing_shadow() { - Some(shadow) => { - if let Some(data) = shadow.style_data() { - if data.any_part_rule() { - f(data, shadow.host()) - } - } - // TODO: Could be more granular. - if !inner_shadow_host.exports_any_part() { - break; - } - inner_shadow = shadow; - }, - None => { - // TODO(emilio): Should probably distinguish with - // MatchesDocumentRules::{No,Yes,IfPart} or something so that we could - // skip some work. - doc_rules_apply = matches_user_and_content_rules; - break; - }, - } - } - } - } - - doc_rules_apply - } - - /// Returns true if one of the transitions needs to be updated on this element. We check all - /// the transition properties to make sure that updating transitions is necessary. - /// This method should only be called if might_needs_transitions_update returns true when - /// passed the same parameters. - #[cfg(feature = "gecko")] - fn needs_transitions_update( - &self, - before_change_style: &ComputedValues, - after_change_style: &ComputedValues, - ) -> bool; - - /// Returns the value of the `xml:lang=""` attribute (or, if appropriate, - /// the `lang=""` attribute) on this element. - fn lang_attr(&self) -> Option<AttrValue>; - - /// Returns whether this element's language matches the language tag - /// `value`. If `override_lang` is not `None`, it specifies the value - /// of the `xml:lang=""` or `lang=""` attribute to use in place of - /// looking at the element and its ancestors. (This argument is used - /// to implement matching of `:lang()` against snapshots.) - fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, value: &Lang) -> bool; - - /// Returns whether this element is the main body element of the HTML - /// document it is on. - fn is_html_document_body_element(&self) -> bool; - - /// Generate the proper applicable declarations due to presentational hints, - /// and insert them into `hints`. - fn synthesize_presentational_hints_for_legacy_attributes<V>( - &self, - visited_handling: VisitedHandlingMode, - hints: &mut V, - ) where - V: Push<ApplicableDeclarationBlock>; - - /// Returns element's local name. - fn local_name(&self) -> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedLocalName; - - /// Returns element's namespace. - fn namespace(&self) - -> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedNamespaceUrl; - - /// Returns the size of the element to be used in container size queries. - /// This will usually be the size of the content area of the primary box, - /// but can be None if there is no box or if some axis lacks size containment. - fn query_container_size( - &self, - display: &Display, - ) -> euclid::default::Size2D<Option<app_units::Au>>; -} - -/// TNode and TElement aren't Send because we want to be careful and explicit -/// about our parallel traversal. However, there are certain situations -/// (including but not limited to the traversal) where we need to send DOM -/// objects to other threads. -/// -/// That's the reason why `SendNode` exists. -#[derive(Clone, Debug, PartialEq)] -pub struct SendNode<N: TNode>(N); -unsafe impl<N: TNode> Send for SendNode<N> {} -impl<N: TNode> SendNode<N> { - /// Unsafely construct a SendNode. - pub unsafe fn new(node: N) -> Self { - SendNode(node) - } -} -impl<N: TNode> Deref for SendNode<N> { - type Target = N; - fn deref(&self) -> &N { - &self.0 - } -} - -/// Same reason as for the existence of SendNode, SendElement does the proper -/// things for a given `TElement`. -#[derive(Debug, Eq, Hash, PartialEq)] -pub struct SendElement<E: TElement>(E); -unsafe impl<E: TElement> Send for SendElement<E> {} -impl<E: TElement> SendElement<E> { - /// Unsafely construct a SendElement. - pub unsafe fn new(el: E) -> Self { - SendElement(el) - } -} -impl<E: TElement> Deref for SendElement<E> { - type Target = E; - fn deref(&self) -> &E { - &self.0 - } -} diff --git a/components/style/dom_apis.rs b/components/style/dom_apis.rs deleted file mode 100644 index da5dc387f73..00000000000 --- a/components/style/dom_apis.rs +++ /dev/null @@ -1,767 +0,0 @@ -/* 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/. */ - -//! Generic implementations of some DOM APIs so they can be shared between Servo -//! and Gecko. - -use crate::context::QuirksMode; -use crate::dom::{TDocument, TElement, TNode, TShadowRoot}; -use crate::invalidation::element::invalidation_map::Dependency; -use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation}; -use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; -use crate::selector_parser::SelectorImpl; -use crate::values::AtomIdent; -use selectors::attr::CaseSensitivity; -use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags}; -use selectors::parser::{Combinator, Component, LocalName}; -use selectors::{Element, SelectorList}; -use smallvec::SmallVec; - -/// <https://dom.spec.whatwg.org/#dom-element-matches> -pub fn element_matches<E>( - element: &E, - selector_list: &SelectorList<E::Impl>, - quirks_mode: QuirksMode, -) -> bool -where - E: Element, -{ - let mut nth_index_cache = Default::default(); - - let mut context = MatchingContext::new( - MatchingMode::Normal, - None, - &mut nth_index_cache, - quirks_mode, - NeedsSelectorFlags::No, - ); - context.scope_element = Some(element.opaque()); - context.current_host = element.containing_shadow_host().map(|e| e.opaque()); - matching::matches_selector_list(selector_list, element, &mut context) -} - -/// <https://dom.spec.whatwg.org/#dom-element-closest> -pub fn element_closest<E>( - element: E, - selector_list: &SelectorList<E::Impl>, - quirks_mode: QuirksMode, -) -> Option<E> -where - E: Element, -{ - let mut nth_index_cache = Default::default(); - - let mut context = MatchingContext::new( - MatchingMode::Normal, - None, - &mut nth_index_cache, - quirks_mode, - NeedsSelectorFlags::No, - ); - context.scope_element = Some(element.opaque()); - context.current_host = element.containing_shadow_host().map(|e| e.opaque()); - - let mut current = Some(element); - while let Some(element) = current.take() { - if matching::matches_selector_list(selector_list, &element, &mut context) { - return Some(element); - } - current = element.parent_element(); - } - - return None; -} - -/// A selector query abstraction, in order to be generic over QuerySelector and -/// QuerySelectorAll. -pub trait SelectorQuery<E: TElement> { - /// The output of the query. - type Output; - - /// Whether the query should stop after the first element has been matched. - fn should_stop_after_first_match() -> bool; - - /// Append an element matching after the first query. - fn append_element(output: &mut Self::Output, element: E); - - /// Returns true if the output is empty. - fn is_empty(output: &Self::Output) -> bool; -} - -/// The result of a querySelectorAll call. -pub type QuerySelectorAllResult<E> = SmallVec<[E; 128]>; - -/// A query for all the elements in a subtree. -pub struct QueryAll; - -impl<E: TElement> SelectorQuery<E> for QueryAll { - type Output = QuerySelectorAllResult<E>; - - fn should_stop_after_first_match() -> bool { - false - } - - fn append_element(output: &mut Self::Output, element: E) { - output.push(element); - } - - fn is_empty(output: &Self::Output) -> bool { - output.is_empty() - } -} - -/// A query for the first in-tree match of all the elements in a subtree. -pub struct QueryFirst; - -impl<E: TElement> SelectorQuery<E> for QueryFirst { - type Output = Option<E>; - - fn should_stop_after_first_match() -> bool { - true - } - - fn append_element(output: &mut Self::Output, element: E) { - if output.is_none() { - *output = Some(element) - } - } - - fn is_empty(output: &Self::Output) -> bool { - output.is_none() - } -} - -struct QuerySelectorProcessor<'a, E, Q> -where - E: TElement + 'a, - Q: SelectorQuery<E>, - Q::Output: 'a, -{ - results: &'a mut Q::Output, - matching_context: MatchingContext<'a, E::Impl>, - dependencies: &'a [Dependency], -} - -impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q> -where - E: TElement + 'a, - Q: SelectorQuery<E>, - Q::Output: 'a, -{ - fn light_tree_only(&self) -> bool { - true - } - - fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool { - debug_assert!( - false, - "How? We should only have parent-less dependencies here!" - ); - true - } - - fn collect_invalidations( - &mut self, - element: E, - self_invalidations: &mut InvalidationVector<'a>, - descendant_invalidations: &mut DescendantInvalidationLists<'a>, - _sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool { - // TODO(emilio): If the element is not a root element, and - // selector_list has any descendant combinator, we need to do extra work - // in order to handle properly things like: - // - // <div id="a"> - // <div id="b"> - // <div id="c"></div> - // </div> - // </div> - // - // b.querySelector('#a div'); // Should return "c". - // - // For now, assert it's a root element. - debug_assert!(element.parent_element().is_none()); - - let target_vector = if self.matching_context.scope_element.is_some() { - &mut descendant_invalidations.dom_descendants - } else { - self_invalidations - }; - - for dependency in self.dependencies.iter() { - target_vector.push(Invalidation::new( - dependency, - self.matching_context.current_host.clone(), - )) - } - - false - } - - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { - &mut self.matching_context - } - - fn should_process_descendants(&mut self, _: E) -> bool { - if Q::should_stop_after_first_match() { - return Q::is_empty(&self.results); - } - - true - } - - fn invalidated_self(&mut self, e: E) { - Q::append_element(self.results, e); - } - - fn invalidated_sibling(&mut self, e: E, _of: E) { - Q::append_element(self.results, e); - } - - fn recursion_limit_exceeded(&mut self, _e: E) {} - fn invalidated_descendants(&mut self, _e: E, _child: E) {} -} - -fn collect_all_elements<E, Q, F>(root: E::ConcreteNode, results: &mut Q::Output, mut filter: F) -where - E: TElement, - Q: SelectorQuery<E>, - F: FnMut(E) -> bool, -{ - for node in root.dom_descendants() { - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - if !filter(element) { - continue; - } - - Q::append_element(results, element); - if Q::should_stop_after_first_match() { - return; - } - } -} - -/// Returns whether a given element connected to `root` is descendant of `root`. -/// -/// NOTE(emilio): if root == element, this returns false. -fn connected_element_is_descendant_of<E>(element: E, root: E::ConcreteNode) -> bool -where - E: TElement, -{ - // Optimize for when the root is a document or a shadow root and the element - // is connected to that root. - if root.as_document().is_some() { - debug_assert!(element.as_node().is_in_document(), "Not connected?"); - debug_assert_eq!( - root, - root.owner_doc().as_node(), - "Where did this element come from?", - ); - return true; - } - - if root.as_shadow_root().is_some() { - debug_assert_eq!( - element.containing_shadow().unwrap().as_node(), - root, - "Not connected?" - ); - return true; - } - - let mut current = element.as_node().parent_node(); - while let Some(n) = current.take() { - if n == root { - return true; - } - - current = n.parent_node(); - } - false -} - -/// Fast path for iterating over every element with a given id in the document -/// or shadow root that `root` is connected to. -fn fast_connected_elements_with_id<'a, N>( - root: N, - id: &AtomIdent, - case_sensitivity: CaseSensitivity, -) -> Result<&'a [N::ConcreteElement], ()> -where - N: TNode + 'a, -{ - if case_sensitivity != CaseSensitivity::CaseSensitive { - return Err(()); - } - - if root.is_in_document() { - return root.owner_doc().elements_with_id(id); - } - - if let Some(shadow) = root.as_shadow_root() { - return shadow.elements_with_id(id); - } - - if let Some(shadow) = root.as_element().and_then(|e| e.containing_shadow()) { - return shadow.elements_with_id(id); - } - - Err(()) -} - -/// Collects elements with a given id under `root`, that pass `filter`. -fn collect_elements_with_id<E, Q, F>( - root: E::ConcreteNode, - id: &AtomIdent, - results: &mut Q::Output, - class_and_id_case_sensitivity: CaseSensitivity, - mut filter: F, -) where - E: TElement, - Q: SelectorQuery<E>, - F: FnMut(E) -> bool, -{ - let elements = match fast_connected_elements_with_id(root, id, class_and_id_case_sensitivity) { - Ok(elements) => elements, - Err(()) => { - collect_all_elements::<E, Q, _>(root, results, |e| { - e.has_id(id, class_and_id_case_sensitivity) && filter(e) - }); - - return; - }, - }; - - for element in elements { - // If the element is not an actual descendant of the root, even though - // it's connected, we don't really care about it. - if !connected_element_is_descendant_of(*element, root) { - continue; - } - - if !filter(*element) { - continue; - } - - Q::append_element(results, *element); - if Q::should_stop_after_first_match() { - break; - } - } -} - -fn has_attr<E>(element: E, local_name: &crate::LocalName) -> bool -where - E: TElement, -{ - let mut found = false; - element.each_attr_name(|name| found |= name == local_name); - found -} - -#[inline(always)] -fn local_name_matches<E>(element: E, local_name: &LocalName<E::Impl>) -> bool -where - E: TElement, -{ - let LocalName { - ref name, - ref lower_name, - } = *local_name; - - let chosen_name = if name == lower_name || element.is_html_element_in_html_document() { - lower_name - } else { - name - }; - - element.local_name() == &**chosen_name -} - -fn get_attr_name(component: &Component<SelectorImpl>) -> Option<&crate::LocalName> { - let (name, name_lower) = match component { - Component::AttributeInNoNamespace { ref local_name, .. } => return Some(local_name), - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - .. - } => (local_name, local_name_lower), - Component::AttributeOther(ref attr) => (&attr.local_name, &attr.local_name_lower), - _ => return None, - }; - if name != name_lower { - return None; // TODO: Maybe optimize this? - } - Some(name) -} - -fn get_id(component: &Component<SelectorImpl>) -> Option<&AtomIdent> { - use selectors::attr::AttrSelectorOperator; - Some(match component { - Component::ID(ref id) => id, - Component::AttributeInNoNamespace { - ref operator, - ref local_name, - ref value, - .. - } => { - if *local_name != local_name!("id") { - return None; - } - if *operator != AttrSelectorOperator::Equal { - return None; - } - AtomIdent::cast(&value.0) - }, - _ => return None, - }) -} - -/// Fast paths for querySelector with a single simple selector. -fn query_selector_single_query<E, Q>( - root: E::ConcreteNode, - component: &Component<E::Impl>, - results: &mut Q::Output, - class_and_id_case_sensitivity: CaseSensitivity, -) -> Result<(), ()> -where - E: TElement, - Q: SelectorQuery<E>, -{ - // TODO: Maybe we could implement a fast path for [name=""]? - match *component { - Component::ExplicitUniversalType => { - collect_all_elements::<E, Q, _>(root, results, |_| true) - }, - Component::Class(ref class) => collect_all_elements::<E, Q, _>(root, results, |element| { - element.has_class(class, class_and_id_case_sensitivity) - }), - Component::LocalName(ref local_name) => { - collect_all_elements::<E, Q, _>(root, results, |element| { - local_name_matches(element, local_name) - }) - }, - ref other => { - let id = match get_id(other) { - Some(id) => id, - // TODO(emilio): More fast paths? - None => return Err(()), - }; - collect_elements_with_id::<E, Q, _>( - root, - id, - results, - class_and_id_case_sensitivity, - |_| true, - ); - }, - } - - Ok(()) -} - -enum SimpleFilter<'a> { - Class(&'a AtomIdent), - Attr(&'a crate::LocalName), - LocalName(&'a LocalName<SelectorImpl>), -} - -/// Fast paths for a given selector query. -/// -/// When there's only one component, we go directly to -/// `query_selector_single_query`, otherwise, we try to optimize by looking just -/// at the subtrees rooted at ids in the selector, and otherwise we try to look -/// up by class name or local name in the rightmost compound. -/// -/// FIXME(emilio, nbp): This may very well be a good candidate for code to be -/// replaced by HolyJit :) -fn query_selector_fast<E, Q>( - root: E::ConcreteNode, - selector_list: &SelectorList<E::Impl>, - results: &mut Q::Output, - matching_context: &mut MatchingContext<E::Impl>, -) -> Result<(), ()> -where - E: TElement, - Q: SelectorQuery<E>, -{ - // We need to return elements in document order, and reordering them - // afterwards is kinda silly. - if selector_list.0.len() > 1 { - return Err(()); - } - - let selector = &selector_list.0[0]; - let class_and_id_case_sensitivity = matching_context.classes_and_ids_case_sensitivity(); - // Let's just care about the easy cases for now. - if selector.len() == 1 { - if query_selector_single_query::<E, Q>( - root, - selector.iter().next().unwrap(), - results, - class_and_id_case_sensitivity, - ) - .is_ok() - { - return Ok(()); - } - } - - let mut iter = selector.iter(); - let mut combinator: Option<Combinator> = None; - - // We want to optimize some cases where there's no id involved whatsoever, - // like `.foo .bar`, but we don't want to make `#foo .bar` slower because of - // that. - let mut simple_filter = None; - - 'selector_loop: loop { - debug_assert!(combinator.map_or(true, |c| !c.is_sibling())); - - 'component_loop: for component in &mut iter { - match *component { - Component::Class(ref class) => { - if combinator.is_none() { - simple_filter = Some(SimpleFilter::Class(class)); - } - }, - Component::LocalName(ref local_name) => { - if combinator.is_none() { - // Prefer to look at class rather than local-name if - // both are present. - if let Some(SimpleFilter::Class(..)) = simple_filter { - continue; - } - simple_filter = Some(SimpleFilter::LocalName(local_name)); - } - }, - ref other => { - if let Some(id) = get_id(other) { - if combinator.is_none() { - // In the rightmost compound, just find descendants of root that match - // the selector list with that id. - collect_elements_with_id::<E, Q, _>( - root, - id, - results, - class_and_id_case_sensitivity, - |e| { - matching::matches_selector_list( - selector_list, - &e, - matching_context, - ) - }, - ); - return Ok(()); - } - - let elements = fast_connected_elements_with_id( - root, - id, - class_and_id_case_sensitivity, - )?; - if elements.is_empty() { - return Ok(()); - } - - // Results need to be in document order. Let's not bother - // reordering or deduplicating nodes, which we would need to - // do if one element with the given id were a descendant of - // another element with that given id. - if !Q::should_stop_after_first_match() && elements.len() > 1 { - continue; - } - - for element in elements { - // If the element is not a descendant of the root, then - // it may have descendants that match our selector that - // _are_ descendants of the root, and other descendants - // that match our selector that are _not_. - // - // So we can't just walk over the element's descendants - // and match the selector against all of them, nor can - // we skip looking at this element's descendants. - // - // Give up on trying to optimize based on this id and - // keep walking our selector. - if !connected_element_is_descendant_of(*element, root) { - continue 'component_loop; - } - - query_selector_slow::<E, Q>( - element.as_node(), - selector_list, - results, - matching_context, - ); - - if Q::should_stop_after_first_match() && !Q::is_empty(&results) { - break; - } - } - - return Ok(()); - } - if combinator.is_none() && simple_filter.is_none() { - if let Some(attr_name) = get_attr_name(other) { - simple_filter = Some(SimpleFilter::Attr(attr_name)); - } - } - }, - } - } - - loop { - let next_combinator = match iter.next_sequence() { - None => break 'selector_loop, - Some(c) => c, - }; - - // We don't want to scan stuff affected by sibling combinators, - // given we scan the subtree of elements with a given id (and we - // don't want to care about scanning the siblings' subtrees). - if next_combinator.is_sibling() { - // Advance to the next combinator. - for _ in &mut iter {} - continue; - } - - combinator = Some(next_combinator); - break; - } - } - - // We got here without finding any ID or such that we could handle. Try to - // use one of the simple filters. - let simple_filter = match simple_filter { - Some(f) => f, - None => return Err(()), - }; - - match simple_filter { - SimpleFilter::Class(ref class) => { - collect_all_elements::<E, Q, _>(root, results, |element| { - element.has_class(class, class_and_id_case_sensitivity) && - matching::matches_selector_list(selector_list, &element, matching_context) - }); - }, - SimpleFilter::LocalName(ref local_name) => { - collect_all_elements::<E, Q, _>(root, results, |element| { - local_name_matches(element, local_name) && - matching::matches_selector_list(selector_list, &element, matching_context) - }); - }, - SimpleFilter::Attr(ref local_name) => { - collect_all_elements::<E, Q, _>(root, results, |element| { - has_attr(element, local_name) && - matching::matches_selector_list(selector_list, &element, matching_context) - }); - }, - } - - Ok(()) -} - -// Slow path for a given selector query. -fn query_selector_slow<E, Q>( - root: E::ConcreteNode, - selector_list: &SelectorList<E::Impl>, - results: &mut Q::Output, - matching_context: &mut MatchingContext<E::Impl>, -) where - E: TElement, - Q: SelectorQuery<E>, -{ - collect_all_elements::<E, Q, _>(root, results, |element| { - matching::matches_selector_list(selector_list, &element, matching_context) - }); -} - -/// Whether the invalidation machinery should be used for this query. -#[derive(PartialEq)] -pub enum MayUseInvalidation { - /// We may use it if we deem it useful. - Yes, - /// Don't use it. - No, -} - -/// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector> -pub fn query_selector<E, Q>( - root: E::ConcreteNode, - selector_list: &SelectorList<E::Impl>, - results: &mut Q::Output, - may_use_invalidation: MayUseInvalidation, -) where - E: TElement, - Q: SelectorQuery<E>, -{ - use crate::invalidation::element::invalidator::TreeStyleInvalidator; - - let mut nth_index_cache = Default::default(); - let quirks_mode = root.owner_doc().quirks_mode(); - - let mut matching_context = MatchingContext::new( - MatchingMode::Normal, - None, - &mut nth_index_cache, - quirks_mode, - NeedsSelectorFlags::No, - ); - let root_element = root.as_element(); - matching_context.scope_element = root_element.map(|e| e.opaque()); - matching_context.current_host = match root_element { - Some(root) => root.containing_shadow_host().map(|host| host.opaque()), - None => root.as_shadow_root().map(|root| root.host().opaque()), - }; - - let fast_result = - query_selector_fast::<E, Q>(root, selector_list, results, &mut matching_context); - - if fast_result.is_ok() { - return; - } - - // Slow path: Use the invalidation machinery if we're a root, and tree - // traversal otherwise. - // - // See the comment in collect_invalidations to see why only if we're a root. - // - // The invalidation mechanism is only useful in presence of combinators. - // - // We could do that check properly here, though checking the length of the - // selectors is a good heuristic. - // - // A selector with a combinator needs to have a length of at least 3: A - // simple selector, a combinator, and another simple selector. - let invalidation_may_be_useful = may_use_invalidation == MayUseInvalidation::Yes && - selector_list.0.iter().any(|s| s.len() > 2); - - if root_element.is_some() || !invalidation_may_be_useful { - query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context); - } else { - let dependencies = selector_list - .0 - .iter() - .map(|selector| Dependency::for_full_selector_invalidation(selector.clone())) - .collect::<SmallVec<[_; 5]>>(); - let mut processor = QuerySelectorProcessor::<E, Q> { - results, - matching_context, - dependencies: &dependencies, - }; - - for node in root.dom_children() { - if let Some(e) = node.as_element() { - TreeStyleInvalidator::new(e, /* stack_limit_checker = */ None, &mut processor) - .invalidate(); - } - } - } -} diff --git a/components/style/driver.rs b/components/style/driver.rs deleted file mode 100644 index cf48d831fdd..00000000000 --- a/components/style/driver.rs +++ /dev/null @@ -1,161 +0,0 @@ -/* 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/. */ - -//! Implements traversal over the DOM tree. The traversal starts in sequential -//! mode, and optionally parallelizes as it discovers work. - -#![deny(missing_docs)] - -use crate::context::{PerThreadTraversalStatistics, StyleContext}; -use crate::context::{ThreadLocalStyleContext, TraversalStatistics}; -use crate::dom::{SendNode, TElement, TNode}; -use crate::parallel; -use crate::scoped_tls::ScopedTLS; -use crate::traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken}; -use rayon; -use std::collections::VecDeque; -use std::mem; -use time; - -#[cfg(feature = "servo")] -fn should_report_statistics() -> bool { - false -} - -#[cfg(feature = "gecko")] -fn should_report_statistics() -> bool { - unsafe { crate::gecko_bindings::structs::ServoTraversalStatistics_sActive } -} - -#[cfg(feature = "servo")] -fn report_statistics(_stats: &PerThreadTraversalStatistics) { - unreachable!("Servo never report stats"); -} - -#[cfg(feature = "gecko")] -fn report_statistics(stats: &PerThreadTraversalStatistics) { - // This should only be called in the main thread, or it may be racy - // to update the statistics in a global variable. - debug_assert!(unsafe { crate::gecko_bindings::bindings::Gecko_IsMainThread() }); - let gecko_stats = - unsafe { &mut crate::gecko_bindings::structs::ServoTraversalStatistics_sSingleton }; - gecko_stats.mElementsTraversed += stats.elements_traversed; - gecko_stats.mElementsStyled += stats.elements_styled; - gecko_stats.mElementsMatched += stats.elements_matched; - gecko_stats.mStylesShared += stats.styles_shared; - gecko_stats.mStylesReused += stats.styles_reused; -} - -fn with_pool_in_place_scope<'scope, R>( - work_unit_max: usize, - pool: Option<&rayon::ThreadPool>, - closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) -> R, -) -> R { - if work_unit_max == 0 || pool.is_none() { - closure(None) - } else { - pool.unwrap().in_place_scope_fifo(|scope| { - closure(Some(scope)) - }) - } -} - -/// See documentation of the pref for performance characteristics. -fn work_unit_max() -> usize { - static_prefs::pref!("layout.css.stylo-work-unit-size") as usize -} - -/// Do a DOM traversal for top-down and (optionally) bottom-up processing, generic over `D`. -/// -/// We use an adaptive traversal strategy. We start out with simple sequential processing, until we -/// arrive at a wide enough level in the DOM that the parallel traversal would parallelize it. -/// If a thread pool is provided, we then transfer control over to the parallel traversal. -/// -/// Returns true if the traversal was parallel, and also returns the statistics object containing -/// information on nodes traversed (on nightly only). Not all of its fields will be initialized -/// since we don't call finish(). -pub fn traverse_dom<E, D>( - traversal: &D, - token: PreTraverseToken<E>, - pool: Option<&rayon::ThreadPool>, -) -> E -where - E: TElement, - D: DomTraversal<E>, -{ - let root = token - .traversal_root() - .expect("Should've ensured we needed to traverse"); - - let report_stats = should_report_statistics(); - let dump_stats = traversal.shared_context().options.dump_style_statistics; - let start_time = if dump_stats { - Some(time::precise_time_s()) - } else { - None - }; - - // Declare the main-thread context, as well as the worker-thread contexts, - // which we may or may not instantiate. It's important to declare the worker- - // thread contexts first, so that they get dropped second. This matters because: - // * ThreadLocalContexts borrow AtomicRefCells in TLS. - // * Dropping a ThreadLocalContext can run SequentialTasks. - // * Sequential tasks may call into functions like - // Servo_StyleSet_GetBaseComputedValuesForElement, which instantiate a - // ThreadLocalStyleContext on the main thread. If the main thread - // ThreadLocalStyleContext has not released its TLS borrow by that point, - // we'll panic on double-borrow. - let mut scoped_tls = pool.map(ScopedTLS::<ThreadLocalStyleContext<E>>::new); - let mut tlc = ThreadLocalStyleContext::new(); - let mut context = StyleContext { - shared: traversal.shared_context(), - thread_local: &mut tlc, - }; - - // Process the nodes breadth-first. This helps keep similar traversal characteristics for the - // style sharing cache. - let work_unit_max = work_unit_max(); - with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| { - let mut discovered = VecDeque::with_capacity(work_unit_max * 2); - discovered.push_back(unsafe { SendNode::new(root.as_node()) }); - parallel::style_trees( - &mut context, - discovered, - root.as_node().opaque(), - work_unit_max, - static_prefs::pref!("layout.css.stylo-local-work-queue.in-main-thread") as usize, - PerLevelTraversalData { current_dom_depth: root.depth() }, - maybe_scope, - traversal, - scoped_tls.as_ref(), - ); - }); - - // Collect statistics from thread-locals if requested. - if dump_stats || report_stats { - let mut aggregate = mem::replace(&mut context.thread_local.statistics, Default::default()); - let parallel = pool.is_some(); - if let Some(ref mut tls) = scoped_tls { - for slot in tls.slots() { - if let Some(cx) = slot.get_mut() { - aggregate += cx.statistics.clone(); - } - } - } - - if report_stats { - report_statistics(&aggregate); - } - // dump statistics to stdout if requested - if dump_stats { - let stats = - TraversalStatistics::new(aggregate, traversal, parallel, start_time.unwrap()); - if stats.is_large { - println!("{}", stats); - } - } - } - - root -} diff --git a/components/style/encoding_support.rs b/components/style/encoding_support.rs deleted file mode 100644 index c144ad0b3bc..00000000000 --- a/components/style/encoding_support.rs +++ /dev/null @@ -1,105 +0,0 @@ -/* 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/. */ - -//! Parsing stylesheets from bytes (not `&str`). - -use crate::context::QuirksMode; -use crate::error_reporting::ParseErrorReporter; -use crate::media_queries::MediaList; -use crate::shared_lock::SharedRwLock; -use crate::stylesheets::{AllowImportRules, Origin, Stylesheet, StylesheetLoader, UrlExtraData}; -use cssparser::{stylesheet_encoding, EncodingSupport}; -use servo_arc::Arc; -use std::borrow::Cow; -use std::str; - -struct EncodingRs; - -impl EncodingSupport for EncodingRs { - type Encoding = &'static encoding_rs::Encoding; - - fn utf8() -> Self::Encoding { - encoding_rs::UTF_8 - } - - fn is_utf16_be_or_le(encoding: &Self::Encoding) -> bool { - *encoding == encoding_rs::UTF_16LE || *encoding == encoding_rs::UTF_16BE - } - - fn from_label(ascii_label: &[u8]) -> Option<Self::Encoding> { - encoding_rs::Encoding::for_label(ascii_label) - } -} - -fn decode_stylesheet_bytes<'a>( - css: &'a [u8], - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, -) -> Cow<'a, str> { - let fallback_encoding = stylesheet_encoding::<EncodingRs>( - css, - protocol_encoding_label.map(str::as_bytes), - environment_encoding, - ); - let (result, _used_encoding, _) = fallback_encoding.decode(&css); - // FIXME record used encoding for environment encoding of @import - result -} - -impl Stylesheet { - /// Parse a stylesheet from a set of bytes, potentially received over the - /// network. - /// - /// Takes care of decoding the network bytes and forwards the resulting - /// string to `Stylesheet::from_str`. - pub fn from_bytes( - bytes: &[u8], - url_data: UrlExtraData, - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, - origin: Origin, - media: MediaList, - shared_lock: SharedRwLock, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - quirks_mode: QuirksMode, - ) -> Stylesheet { - let string = decode_stylesheet_bytes(bytes, protocol_encoding_label, environment_encoding); - Stylesheet::from_str( - &string, - url_data, - origin, - Arc::new(shared_lock.wrap(media)), - shared_lock, - stylesheet_loader, - error_reporter, - quirks_mode, - 0, - AllowImportRules::Yes, - ) - } - - /// Updates an empty stylesheet with a set of bytes that reached over the - /// network. - pub fn update_from_bytes( - existing: &Stylesheet, - bytes: &[u8], - protocol_encoding_label: Option<&str>, - environment_encoding: Option<&'static encoding_rs::Encoding>, - url_data: UrlExtraData, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - ) { - let string = decode_stylesheet_bytes(bytes, protocol_encoding_label, environment_encoding); - Self::update_from_str( - existing, - &string, - url_data, - stylesheet_loader, - error_reporter, - 0, - AllowImportRules::Yes, - ) - } -} diff --git a/components/style/error_reporting.rs b/components/style/error_reporting.rs deleted file mode 100644 index 258c7c44ef4..00000000000 --- a/components/style/error_reporting.rs +++ /dev/null @@ -1,283 +0,0 @@ -/* 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/. */ - -//! Types used to report parsing errors. - -#![deny(missing_docs)] - -use crate::selector_parser::SelectorImpl; -use crate::stylesheets::UrlExtraData; -use cssparser::{BasicParseErrorKind, ParseErrorKind, SourceLocation, Token}; -use selectors::SelectorList; -use std::fmt; -use style_traits::ParseError; - -/// Errors that can be encountered while parsing CSS. -#[derive(Debug)] -pub enum ContextualParseError<'a> { - /// A property declaration was not recognized. - UnsupportedPropertyDeclaration( - &'a str, - ParseError<'a>, - Option<&'a SelectorList<SelectorImpl>>, - ), - /// A property descriptor was not recognized. - UnsupportedPropertyDescriptor(&'a str, ParseError<'a>), - /// A font face descriptor was not recognized. - UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>), - /// A font feature values descriptor was not recognized. - UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError<'a>), - /// A font palette values descriptor was not recognized. - UnsupportedFontPaletteValuesDescriptor(&'a str, ParseError<'a>), - /// A keyframe rule was not valid. - InvalidKeyframeRule(&'a str, ParseError<'a>), - /// A font feature values rule was not valid. - InvalidFontFeatureValuesRule(&'a str, ParseError<'a>), - /// A keyframe property declaration was not recognized. - UnsupportedKeyframePropertyDeclaration(&'a str, ParseError<'a>), - /// A rule was invalid for some reason. - InvalidRule(&'a str, ParseError<'a>), - /// A rule was not recognized. - UnsupportedRule(&'a str, ParseError<'a>), - /// A viewport descriptor declaration was not recognized. - UnsupportedViewportDescriptorDeclaration(&'a str, ParseError<'a>), - /// A counter style descriptor declaration was not recognized. - UnsupportedCounterStyleDescriptorDeclaration(&'a str, ParseError<'a>), - /// A counter style rule had no symbols. - InvalidCounterStyleWithoutSymbols(String), - /// A counter style rule had less than two symbols. - InvalidCounterStyleNotEnoughSymbols(String), - /// A counter style rule did not have additive-symbols. - InvalidCounterStyleWithoutAdditiveSymbols, - /// A counter style rule had extends with symbols. - InvalidCounterStyleExtendsWithSymbols, - /// A counter style rule had extends with additive-symbols. - InvalidCounterStyleExtendsWithAdditiveSymbols, - /// A media rule was invalid for some reason. - InvalidMediaRule(&'a str, ParseError<'a>), - /// A value was not recognized. - UnsupportedValue(&'a str, ParseError<'a>), - /// A never-matching `:host` selector was found. - NeverMatchingHostSelector(String), -} - -impl<'a> fmt::Display for ContextualParseError<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fn token_to_str(t: &Token, f: &mut fmt::Formatter) -> fmt::Result { - match *t { - Token::Ident(ref i) => write!(f, "identifier {}", i), - Token::AtKeyword(ref kw) => write!(f, "keyword @{}", kw), - Token::Hash(ref h) => write!(f, "hash #{}", h), - Token::IDHash(ref h) => write!(f, "id selector #{}", h), - Token::QuotedString(ref s) => write!(f, "quoted string \"{}\"", s), - Token::UnquotedUrl(ref u) => write!(f, "url {}", u), - Token::Delim(ref d) => write!(f, "delimiter {}", d), - Token::Number { - int_value: Some(i), .. - } => write!(f, "number {}", i), - Token::Number { value, .. } => write!(f, "number {}", value), - Token::Percentage { - int_value: Some(i), .. - } => write!(f, "percentage {}", i), - Token::Percentage { unit_value, .. } => { - write!(f, "percentage {}", unit_value * 100.) - }, - Token::Dimension { - value, ref unit, .. - } => write!(f, "dimension {}{}", value, unit), - Token::WhiteSpace(_) => write!(f, "whitespace"), - Token::Comment(_) => write!(f, "comment"), - Token::Colon => write!(f, "colon (:)"), - Token::Semicolon => write!(f, "semicolon (;)"), - Token::Comma => write!(f, "comma (,)"), - Token::IncludeMatch => write!(f, "include match (~=)"), - Token::DashMatch => write!(f, "dash match (|=)"), - Token::PrefixMatch => write!(f, "prefix match (^=)"), - Token::SuffixMatch => write!(f, "suffix match ($=)"), - Token::SubstringMatch => write!(f, "substring match (*=)"), - Token::CDO => write!(f, "CDO (<!--)"), - Token::CDC => write!(f, "CDC (-->)"), - Token::Function(ref name) => write!(f, "function {}", name), - Token::ParenthesisBlock => write!(f, "parenthesis ("), - Token::SquareBracketBlock => write!(f, "square bracket ["), - Token::CurlyBracketBlock => write!(f, "curly bracket {{"), - Token::BadUrl(ref _u) => write!(f, "bad url parse error"), - Token::BadString(ref _s) => write!(f, "bad string parse error"), - Token::CloseParenthesis => write!(f, "unmatched close parenthesis"), - Token::CloseSquareBracket => write!(f, "unmatched close square bracket"), - Token::CloseCurlyBracket => write!(f, "unmatched close curly bracket"), - } - } - - fn parse_error_to_str(err: &ParseError, f: &mut fmt::Formatter) -> fmt::Result { - match err.kind { - ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(ref t)) => { - write!(f, "found unexpected ")?; - token_to_str(t, f) - }, - ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => { - write!(f, "unexpected end of input") - }, - ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(ref i)) => { - write!(f, "@ rule invalid: {}", i) - }, - ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid) => { - write!(f, "@ rule invalid") - }, - ParseErrorKind::Basic(BasicParseErrorKind::QualifiedRuleInvalid) => { - write!(f, "qualified rule invalid") - }, - ParseErrorKind::Custom(ref err) => write!(f, "{:?}", err), - } - } - - match *self { - ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err, _selectors) => { - write!(f, "Unsupported property declaration: '{}', ", decl)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedPropertyDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @property descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @font-face descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedFontFeatureValuesDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @font-feature-values descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedFontPaletteValuesDescriptor(decl, ref err) => { - write!( - f, - "Unsupported @font-palette-values descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidKeyframeRule(rule, ref err) => { - write!(f, "Invalid keyframe rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidFontFeatureValuesRule(rule, ref err) => { - write!(f, "Invalid font feature value rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedKeyframePropertyDeclaration(decl, ref err) => { - write!(f, "Unsupported keyframe property declaration: '{}', ", decl)?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidRule(rule, ref err) => { - write!(f, "Invalid rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedRule(rule, ref err) => { - write!(f, "Unsupported rule: '{}', ", rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) => { - write!( - f, - "Unsupported @viewport descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(decl, ref err) => { - write!( - f, - "Unsupported @counter-style descriptor declaration: '{}', ", - decl - )?; - parse_error_to_str(err, f) - }, - ContextualParseError::InvalidCounterStyleWithoutSymbols(ref system) => write!( - f, - "Invalid @counter-style rule: 'system: {}' without 'symbols'", - system - ), - ContextualParseError::InvalidCounterStyleNotEnoughSymbols(ref system) => write!( - f, - "Invalid @counter-style rule: 'system: {}' less than two 'symbols'", - system - ), - ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols => write!( - f, - "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'" - ), - ContextualParseError::InvalidCounterStyleExtendsWithSymbols => write!( - f, - "Invalid @counter-style rule: 'system: extends …' with 'symbols'" - ), - ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols => write!( - f, - "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'" - ), - ContextualParseError::InvalidMediaRule(media_rule, ref err) => { - write!(f, "Invalid media rule: {}, ", media_rule)?; - parse_error_to_str(err, f) - }, - ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f), - ContextualParseError::NeverMatchingHostSelector(ref selector) => { - write!(f, ":host selector is not featureless: {}", selector) - }, - } - } -} - -/// A generic trait for an error reporter. -pub trait ParseErrorReporter { - /// Called when the style engine detects an error. - /// - /// Returns the current input being parsed, the source location it was - /// reported from, and a message. - fn report_error( - &self, - url: &UrlExtraData, - location: SourceLocation, - error: ContextualParseError, - ); -} - -/// An error reporter that uses [the `log` crate](https://github.com/rust-lang-nursery/log) -/// at `info` level. -/// -/// This logging is silent by default, and can be enabled with a `RUST_LOG=style=info` -/// environment variable. -/// (See [`env_logger`](https://rust-lang-nursery.github.io/log/env_logger/).) -#[cfg(feature = "servo")] -pub struct RustLogReporter; - -#[cfg(feature = "servo")] -impl ParseErrorReporter for RustLogReporter { - fn report_error( - &self, - url: &UrlExtraData, - location: SourceLocation, - error: ContextualParseError, - ) { - if log_enabled!(log::Level::Info) { - info!( - "Url:\t{}\n{}:{} {}", - url.as_str(), - location.line, - location.column, - error - ) - } - } -} diff --git a/components/style/font_face.rs b/components/style/font_face.rs deleted file mode 100644 index 1e193d3eb79..00000000000 --- a/components/style/font_face.rs +++ /dev/null @@ -1,835 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The [`@font-face`][ff] at-rule. -//! -//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule - -use crate::error_reporting::ContextualParseError; -use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] -use crate::properties::longhands::font_language_override; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::values::computed::font::{FamilyName, FontStretch}; -use crate::values::generics::font::FontStyle as GenericFontStyle; -#[cfg(feature = "gecko")] -use crate::values::specified::font::MetricsOverride; -use crate::values::specified::font::SpecifiedFontStyle; -use crate::values::specified::font::{AbsoluteFontWeight, FontStretch as SpecifiedFontStretch}; -#[cfg(feature = "gecko")] -use crate::values::specified::font::{FontFeatureSettings, FontVariationSettings}; -use crate::values::specified::url::SpecifiedUrl; -use crate::values::specified::Angle; -#[cfg(feature = "gecko")] -use crate::values::specified::NonNegativePercentage; -#[cfg(feature = "gecko")] -use cssparser::UnicodeRange; -use cssparser::{ - AtRuleParser, CowRcStr, DeclarationParser, Parser, QualifiedRuleParser, RuleBodyItemParser, - RuleBodyParser, SourceLocation, -}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError}; -use style_traits::{StyleParseErrorKind, ToCss}; - -/// A source for a font-face rule. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -pub enum Source { - /// A `url()` source. - Url(UrlSource), - /// A `local()` source. - #[css(function)] - Local(FamilyName), -} - -/// A list of sources for the font-face src descriptor. -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -#[css(comma)] -pub struct SourceList(#[css(iterable)] pub Vec<Source>); - -// We can't just use OneOrMoreSeparated to derive Parse for the Source list, -// because we want to filter out components that parsed as None, then fail if no -// valid components remain. So we provide our own implementation here. -impl Parse for SourceList { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // Parse the comma-separated list, then let filter_map discard any None items. - let list = input - .parse_comma_separated(|input| { - let s = input.parse_entirely(|input| Source::parse(context, input)); - while input.next().is_ok() {} - Ok(s.ok()) - })? - .into_iter() - .filter_map(|s| s) - .collect::<Vec<Source>>(); - if list.is_empty() { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(SourceList(list)) - } - } -} - -/// Keywords for the font-face src descriptor's format() function. -/// ('None' and 'Unknown' are for internal use in gfx, not exposed to CSS.) -#[derive(Clone, Copy, Debug, Eq, Parse, PartialEq, ToCss, ToShmem)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(u8)] -#[allow(missing_docs)] -pub enum FontFaceSourceFormatKeyword { - #[css(skip)] - None, - Collection, - EmbeddedOpentype, - Opentype, - Svg, - Truetype, - Woff, - Woff2, - #[css(skip)] - Unknown, -} - -bitflags! { - /// Flags for the @font-face tech() function, indicating font technologies - /// required by the resource. - #[derive(ToShmem)] - #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] - #[repr(C)] - pub struct FontFaceSourceTechFlags: u16 { - /// Font requires OpenType feature support. - const FEATURES_OPENTYPE = 1 << 0; - /// Font requires Apple Advanced Typography support. - const FEATURES_AAT = 1 << 1; - /// Font requires Graphite shaping support. - const FEATURES_GRAPHITE = 1 << 2; - /// Font requires COLRv0 rendering support (simple list of colored layers). - const COLOR_COLRV0 = 1 << 3; - /// Font requires COLRv1 rendering support (graph of paint operations). - const COLOR_COLRV1 = 1 << 4; - /// Font requires SVG glyph rendering support. - const COLOR_SVG = 1 << 5; - /// Font has bitmap glyphs in 'sbix' format. - const COLOR_SBIX = 1 << 6; - /// Font has bitmap glyphs in 'CBDT' format. - const COLOR_CBDT = 1 << 7; - /// Font requires OpenType Variations support. - const VARIATIONS = 1 << 8; - /// Font requires CPAL palette selection support. - const PALETTES = 1 << 9; - /// Font requires support for incremental downloading. - const INCREMENTAL = 1 << 10; - } -} - -impl FontFaceSourceTechFlags { - /// Parse a single font-technology keyword and return its flag. - pub fn parse_one<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "features-opentype" => Self::FEATURES_OPENTYPE, - "features-aat" => Self::FEATURES_AAT, - "features-graphite" => Self::FEATURES_GRAPHITE, - "color-colrv0" => Self::COLOR_COLRV0, - "color-colrv1" => Self::COLOR_COLRV1, - "color-svg" => Self::COLOR_SVG, - "color-sbix" => Self::COLOR_SBIX, - "color-cbdt" => Self::COLOR_CBDT, - "variations" => Self::VARIATIONS, - "palettes" => Self::PALETTES, - "incremental" => Self::INCREMENTAL, - }) - } -} - -impl Parse for FontFaceSourceTechFlags { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - // We don't actually care about the return value of parse_comma_separated, - // because we insert the flags into result as we go. - let mut result = Self::empty(); - input.parse_comma_separated(|input| { - let flag = Self::parse_one(input)?; - result.insert(flag); - Ok(()) - })?; - if !result.is_empty() { - Ok(result) - } else { - Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -#[allow(unused_assignments)] -impl ToCss for FontFaceSourceTechFlags { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - let mut first = true; - - macro_rules! write_if_flag { - ($s:expr => $f:ident) => { - if self.contains(Self::$f) { - if first { - first = false; - } else { - dest.write_str(", ")?; - } - dest.write_str($s)?; - } - }; - } - - write_if_flag!("features-opentype" => FEATURES_OPENTYPE); - write_if_flag!("features-aat" => FEATURES_AAT); - write_if_flag!("features-graphite" => FEATURES_GRAPHITE); - write_if_flag!("color-colrv0" => COLOR_COLRV0); - write_if_flag!("color-colrv1" => COLOR_COLRV1); - write_if_flag!("color-svg" => COLOR_SVG); - write_if_flag!("color-sbix" => COLOR_SBIX); - write_if_flag!("color-cbdt" => COLOR_CBDT); - write_if_flag!("variations" => VARIATIONS); - write_if_flag!("palettes" => PALETTES); - write_if_flag!("incremental" => INCREMENTAL); - - Ok(()) - } -} - -/// A POD representation for Gecko. All pointers here are non-owned and as such -/// can't outlive the rule they came from, but we can't enforce that via C++. -/// -/// All the strings are of course utf8. -#[cfg(feature = "gecko")] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum FontFaceSourceListComponent { - Url(*const crate::gecko::url::CssUrl), - Local(*mut crate::gecko_bindings::structs::nsAtom), - FormatHintKeyword(FontFaceSourceFormatKeyword), - FormatHintString { - length: usize, - utf8_bytes: *const u8, - }, - TechFlags(FontFaceSourceTechFlags), -} - -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(u8)] -#[allow(missing_docs)] -pub enum FontFaceSourceFormat { - Keyword(FontFaceSourceFormatKeyword), - String(String), -} - -/// A `UrlSource` represents a font-face source that has been specified with a -/// `url()` function. -/// -/// <https://drafts.csswg.org/css-fonts/#src-desc> -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Debug, Eq, PartialEq, ToShmem)] -pub struct UrlSource { - /// The specified url. - pub url: SpecifiedUrl, - /// The format hint specified with the `format()` function, if present. - pub format_hint: Option<FontFaceSourceFormat>, - /// The font technology flags specified with the `tech()` function, if any. - pub tech_flags: FontFaceSourceTechFlags, -} - -impl ToCss for UrlSource { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.url.to_css(dest)?; - if let Some(hint) = &self.format_hint { - dest.write_str(" format(")?; - hint.to_css(dest)?; - dest.write_char(')')?; - } - if !self.tech_flags.is_empty() { - dest.write_str(" tech(")?; - self.tech_flags.to_css(dest)?; - dest.write_char(')')?; - } - Ok(()) - } -} - -/// A font-display value for a @font-face rule. -/// The font-display descriptor determines how a font face is displayed based -/// on whether and when it is downloaded and ready to use. -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum FontDisplay { - Auto, - Block, - Swap, - Fallback, - Optional, -} - -macro_rules! impl_range { - ($range:ident, $component:ident) => { - impl Parse for $range { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let first = $component::parse(context, input)?; - let second = input - .try_parse(|input| $component::parse(context, input)) - .unwrap_or_else(|_| first.clone()); - Ok($range(first, second)) - } - } - impl ToCss for $range { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.0.to_css(dest)?; - if self.0 != self.1 { - dest.write_char(' ')?; - self.1.to_css(dest)?; - } - Ok(()) - } - } - }; -} - -/// The font-weight descriptor: -/// -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight -#[derive(Clone, Debug, PartialEq, ToShmem)] -pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight); -impl_range!(FontWeightRange, AbsoluteFontWeight); - -/// The computed representation of the above so Gecko can read them easily. -/// -/// This one is needed because cbindgen doesn't know how to generate -/// specified::Number. -#[repr(C)] -#[allow(missing_docs)] -pub struct ComputedFontWeightRange(f32, f32); - -#[inline] -fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) { - if a > b { - (b, a) - } else { - (a, b) - } -} - -impl FontWeightRange { - /// Returns a computed font-stretch range. - pub fn compute(&self) -> ComputedFontWeightRange { - let (min, max) = sort_range(self.0.compute().value(), self.1.compute().value()); - ComputedFontWeightRange(min, max) - } -} - -/// The font-stretch descriptor: -/// -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch -#[derive(Clone, Debug, PartialEq, ToShmem)] -pub struct FontStretchRange(pub SpecifiedFontStretch, pub SpecifiedFontStretch); -impl_range!(FontStretchRange, SpecifiedFontStretch); - -/// The computed representation of the above, so that Gecko can read them -/// easily. -#[repr(C)] -#[allow(missing_docs)] -pub struct ComputedFontStretchRange(FontStretch, FontStretch); - -impl FontStretchRange { - /// Returns a computed font-stretch range. - pub fn compute(&self) -> ComputedFontStretchRange { - fn compute_stretch(s: &SpecifiedFontStretch) -> FontStretch { - match *s { - SpecifiedFontStretch::Keyword(ref kw) => kw.compute(), - SpecifiedFontStretch::Stretch(ref p) => FontStretch::from_percentage(p.0.get()), - SpecifiedFontStretch::System(..) => unreachable!(), - } - } - - let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1)); - ComputedFontStretchRange(min, max) - } -} - -/// The font-style descriptor: -/// -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style -#[derive(Clone, Debug, PartialEq, ToShmem)] -#[allow(missing_docs)] -pub enum FontStyle { - Normal, - Italic, - Oblique(Angle, Angle), -} - -/// The computed representation of the above, with angles in degrees, so that -/// Gecko can read them easily. -#[repr(u8)] -#[allow(missing_docs)] -pub enum ComputedFontStyleDescriptor { - Normal, - Italic, - Oblique(f32, f32), -} - -impl Parse for FontStyle { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let style = SpecifiedFontStyle::parse(context, input)?; - Ok(match style { - GenericFontStyle::Normal => FontStyle::Normal, - GenericFontStyle::Italic => FontStyle::Italic, - GenericFontStyle::Oblique(angle) => { - let second_angle = input - .try_parse(|input| SpecifiedFontStyle::parse_angle(context, input)) - .unwrap_or_else(|_| angle.clone()); - - FontStyle::Oblique(angle, second_angle) - }, - }) - } -} - -impl ToCss for FontStyle { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - match *self { - FontStyle::Normal => dest.write_str("normal"), - FontStyle::Italic => dest.write_str("italic"), - FontStyle::Oblique(ref first, ref second) => { - dest.write_str("oblique")?; - if *first != SpecifiedFontStyle::default_angle() || first != second { - dest.write_char(' ')?; - first.to_css(dest)?; - } - if first != second { - dest.write_char(' ')?; - second.to_css(dest)?; - } - Ok(()) - }, - } - } -} - -impl FontStyle { - /// Returns a computed font-style descriptor. - pub fn compute(&self) -> ComputedFontStyleDescriptor { - match *self { - FontStyle::Normal => ComputedFontStyleDescriptor::Normal, - FontStyle::Italic => ComputedFontStyleDescriptor::Italic, - FontStyle::Oblique(ref first, ref second) => { - let (min, max) = sort_range( - SpecifiedFontStyle::compute_angle_degrees(first), - SpecifiedFontStyle::compute_angle_degrees(second), - ); - ComputedFontStyleDescriptor::Oblique(min, max) - }, - } - } -} - -/// Parse the block inside a `@font-face` rule. -/// -/// Note that the prelude parsing code lives in the `stylesheets` module. -pub fn parse_font_face_block( - context: &ParserContext, - input: &mut Parser, - location: SourceLocation, -) -> FontFaceRuleData { - let mut rule = FontFaceRuleData::empty(location); - { - let mut parser = FontFaceRuleParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if let Err((error, slice)) = declaration { - let location = error.location; - let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error); - context.log_css_error(location, error) - } - } - } - rule -} - -/// A @font-face rule that is known to have font-family and src declarations. -#[cfg(feature = "servo")] -pub struct FontFace<'a>(&'a FontFaceRuleData); - -/// A list of effective sources that we send over through IPC to the font cache. -#[cfg(feature = "servo")] -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -pub struct EffectiveSources(Vec<Source>); - -#[cfg(feature = "servo")] -impl<'a> FontFace<'a> { - /// Returns the list of effective sources for that font-face, that is the - /// sources which don't list any format hint, or the ones which list at - /// least "truetype" or "opentype". - pub fn effective_sources(&self) -> EffectiveSources { - EffectiveSources( - self.sources() - .0 - .iter() - .rev() - .filter(|source| { - if let Source::Url(ref url_source) = **source { - // We support only opentype fonts and truetype is an alias for - // that format. Sources without format hints need to be - // downloaded in case we support them. - url_source - .format_hint - .as_ref() - .map_or(true, |hint| match hint { - FontFaceSourceFormat::Keyword( - FontFaceSourceFormatKeyword::Truetype - | FontFaceSourceFormatKeyword::Opentype - | FontFaceSourceFormatKeyword::Woff, - ) => true, - FontFaceSourceFormat::String(s) => { - s == "truetype" || s == "opentype" || s == "woff" - } - _ => false, - }) - } else { - true - } - }) - .cloned() - .collect(), - ) - } -} - -#[cfg(feature = "servo")] -impl Iterator for EffectiveSources { - type Item = Source; - fn next(&mut self) -> Option<Source> { - self.0.pop() - } - - fn size_hint(&self) -> (usize, Option<usize>) { - (self.0.len(), Some(self.0.len())) - } -} - -struct FontFaceRuleParser<'a, 'b: 'a> { - context: &'a ParserContext<'b>, - rule: &'a mut FontFaceRuleData, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for FontFaceRuleParser<'a, 'b> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for FontFaceRuleParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - false - } - fn parse_declarations(&self) -> bool { - true - } -} - -impl Parse for Source { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Source, ParseError<'i>> { - if input - .try_parse(|input| input.expect_function_matching("local")) - .is_ok() - { - return input - .parse_nested_block(|input| FamilyName::parse(context, input)) - .map(Source::Local); - } - - let url = SpecifiedUrl::parse(context, input)?; - - // Parsing optional format() - let format_hint = if input - .try_parse(|input| input.expect_function_matching("format")) - .is_ok() - { - input.parse_nested_block(|input| { - if let Ok(kw) = input.try_parse(FontFaceSourceFormatKeyword::parse) { - Ok(Some(FontFaceSourceFormat::Keyword(kw))) - } else { - let s = input.expect_string()?.as_ref().to_owned(); - Ok(Some(FontFaceSourceFormat::String(s))) - } - })? - } else { - None - }; - - // Parse optional tech() - let tech_flags = if static_prefs::pref!("layout.css.font-tech.enabled") && - input - .try_parse(|input| input.expect_function_matching("tech")) - .is_ok() - { - input.parse_nested_block(|input| FontFaceSourceTechFlags::parse(context, input))? - } else { - FontFaceSourceTechFlags::empty() - }; - - Ok(Source::Url(UrlSource { - url, - format_hint, - tech_flags, - })) - } -} - -macro_rules! is_descriptor_enabled { - ("font-display") => { - static_prefs::pref!("layout.css.font-display.enabled") - }; - ("font-variation-settings") => { - static_prefs::pref!("layout.css.font-variations.enabled") - }; - ("ascent-override") => { - static_prefs::pref!("layout.css.font-metrics-overrides.enabled") - }; - ("descent-override") => { - static_prefs::pref!("layout.css.font-metrics-overrides.enabled") - }; - ("line-gap-override") => { - static_prefs::pref!("layout.css.font-metrics-overrides.enabled") - }; - ("size-adjust") => { - static_prefs::pref!("layout.css.size-adjust.enabled") - }; - ($name:tt) => { - true - }; -} - -macro_rules! font_face_descriptors_common { - ( - $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )* - ) => { - /// Data inside a `@font-face` rule. - /// - /// <https://drafts.csswg.org/css-fonts/#font-face-rule> - #[derive(Clone, Debug, PartialEq, ToShmem)] - pub struct FontFaceRuleData { - $( - #[$doc] - pub $ident: Option<$ty>, - )* - /// Line and column of the @font-face rule source code. - pub source_location: SourceLocation, - } - - impl FontFaceRuleData { - /// Create an empty font-face rule - pub fn empty(location: SourceLocation) -> Self { - FontFaceRuleData { - $( - $ident: None, - )* - source_location: location, - } - } - - /// Serialization of declarations in the FontFaceRule - pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { - $( - if let Some(ref value) = self.$ident { - dest.write_str(concat!($name, ": "))?; - value.to_css(&mut CssWriter::new(dest))?; - dest.write_str("; ")?; - } - )* - Ok(()) - } - } - - impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - match_ignore_ascii_case! { &*name, - $( - $name if is_descriptor_enabled!($name) => { - // DeclarationParser also calls parse_entirely - // so we’d normally not need to, - // but in this case we do because we set the value as a side effect - // rather than returning it. - let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; - self.rule.$ident = Some(value) - }, - )* - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - } - Ok(()) - } - } - } -} - -impl ToCssWithGuard for FontFaceRuleData { - // Serialization of FontFaceRule is not specced. - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@font-face { ")?; - self.decl_to_css(dest)?; - dest.write_char('}') - } -} - -macro_rules! font_face_descriptors { - ( - mandatory descriptors = [ - $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )* - ] - optional descriptors = [ - $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )* - ] - ) => { - font_face_descriptors_common! { - $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )* - $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )* - } - - impl FontFaceRuleData { - /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule - /// is valid as far as the CSS parser is concerned even if it doesn’t have - /// a font-family or src declaration. - /// - /// However both are required for the rule to represent an actual font face. - #[cfg(feature = "servo")] - pub fn font_face(&self) -> Option<FontFace> { - if $( self.$m_ident.is_some() )&&* { - Some(FontFace(self)) - } else { - None - } - } - } - - #[cfg(feature = "servo")] - impl<'a> FontFace<'a> { - $( - #[$m_doc] - pub fn $m_ident(&self) -> &$m_ty { - self.0 .$m_ident.as_ref().unwrap() - } - )* - } - } -} - -#[cfg(feature = "gecko")] -font_face_descriptors! { - mandatory descriptors = [ - /// The name of this font face - "font-family" family / mFamily: FamilyName, - - /// The alternative sources for this font face. - "src" sources / mSrc: SourceList, - ] - optional descriptors = [ - /// The style of this font face. - "font-style" style / mStyle: FontStyle, - - /// The weight of this font face. - "font-weight" weight / mWeight: FontWeightRange, - - /// The stretch of this font face. - "font-stretch" stretch / mStretch: FontStretchRange, - - /// The display of this font face. - "font-display" display / mDisplay: FontDisplay, - - /// The ranges of code points outside of which this font face should not be used. - "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>, - - /// The feature settings of this font face. - "font-feature-settings" feature_settings / mFontFeatureSettings: FontFeatureSettings, - - /// The variation settings of this font face. - "font-variation-settings" variation_settings / mFontVariationSettings: FontVariationSettings, - - /// The language override of this font face. - "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue, - - /// The ascent override for this font face. - "ascent-override" ascent_override / mAscentOverride: MetricsOverride, - - /// The descent override for this font face. - "descent-override" descent_override / mDescentOverride: MetricsOverride, - - /// The line-gap override for this font face. - "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride, - - /// The size adjustment for this font face. - "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage, - ] -} - -#[cfg(feature = "servo")] -font_face_descriptors! { - mandatory descriptors = [ - /// The name of this font face - "font-family" family / mFamily: FamilyName, - - /// The alternative sources for this font face. - "src" sources / mSrc: SourceList, - ] - optional descriptors = [ - ] -} diff --git a/components/style/font_metrics.rs b/components/style/font_metrics.rs deleted file mode 100644 index 391d3653ee6..00000000000 --- a/components/style/font_metrics.rs +++ /dev/null @@ -1,58 +0,0 @@ -/* 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/. */ - -//! Access to font metrics from the style system. - -#![deny(missing_docs)] - -use crate::values::computed::Length; - -/// Represents the font metrics that style needs from a font to compute the -/// value of certain CSS units like `ex`. -#[derive(Clone, Debug, PartialEq)] -pub struct FontMetrics { - /// The x-height of the font. - pub x_height: Option<Length>, - /// The zero advance. This is usually writing mode dependent - pub zero_advance_measure: Option<Length>, - /// The cap-height of the font. - pub cap_height: Option<Length>, - /// The ideographic-width of the font. - pub ic_width: Option<Length>, - /// The ascent of the font (a value is always available for this). - pub ascent: Length, - /// Script scale down factor for math-depth 1. - /// https://w3c.github.io/mathml-core/#dfn-scriptpercentscaledown - pub script_percent_scale_down: Option<f32>, - /// Script scale down factor for math-depth 2. - /// https://w3c.github.io/mathml-core/#dfn-scriptscriptpercentscaledown - pub script_script_percent_scale_down: Option<f32>, -} - -impl Default for FontMetrics { - fn default() -> Self { - FontMetrics { - x_height: None, - zero_advance_measure: None, - cap_height: None, - ic_width: None, - ascent: Length::new(0.0), - script_percent_scale_down: None, - script_script_percent_scale_down: None, - } - } -} - -/// Type of font metrics to retrieve. -#[derive(Clone, Debug, PartialEq)] -pub enum FontMetricsOrientation { - /// Get metrics for horizontal or vertical according to the Context's - /// writing mode, using horizontal metrics for vertical/mixed - MatchContextPreferHorizontal, - /// Get metrics for horizontal or vertical according to the Context's - /// writing mode, using vertical metrics for vertical/mixed - MatchContextPreferVertical, - /// Force getting horizontal metrics. - Horizontal, -} diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs deleted file mode 100644 index 24bf22d69a7..00000000000 --- a/components/style/gecko/arc_types.rs +++ /dev/null @@ -1,171 +0,0 @@ -/* 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 lists all arc FFI types and defines corresponding addref and release functions. This -//! list loosely corresponds to ServoLockedArcTypeList.h file in Gecko. - -#![allow(non_snake_case, missing_docs)] - -use crate::gecko::url::CssUrlData; -use crate::media_queries::MediaList; -use crate::properties::animated_properties::AnimationValue; -use crate::properties::{ComputedValues, PropertyDeclarationBlock}; -use crate::shared_lock::Locked; -use crate::stylesheets::keyframes_rule::Keyframe; -use crate::stylesheets::{ - ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, - FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, - MediaRule, NamespaceRule, PageRule, PropertyRule, StyleRule, StylesheetContents, SupportsRule, -}; -use servo_arc::Arc; - -macro_rules! impl_simple_arc_ffi { - ($ty:ty, $addref:ident, $release:ident) => { - #[no_mangle] - pub unsafe extern "C" fn $addref(obj: *const $ty) { - std::mem::forget(Arc::from_raw_addrefed(obj)); - } - - #[no_mangle] - pub unsafe extern "C" fn $release(obj: *const $ty) { - let _ = Arc::from_raw(obj); - } - }; -} - -macro_rules! impl_locked_arc_ffi { - ($servo_type:ty, $alias:ident, $addref:ident, $release:ident) => { - /// A simple alias for a locked type. - pub type $alias = Locked<$servo_type>; - impl_simple_arc_ffi!($alias, $addref, $release); - }; -} - -impl_locked_arc_ffi!( - CssRules, - LockedCssRules, - Servo_CssRules_AddRef, - Servo_CssRules_Release -); -impl_locked_arc_ffi!( - PropertyDeclarationBlock, - LockedDeclarationBlock, - Servo_DeclarationBlock_AddRef, - Servo_DeclarationBlock_Release -); -impl_locked_arc_ffi!( - StyleRule, - LockedStyleRule, - Servo_StyleRule_AddRef, - Servo_StyleRule_Release -); -impl_locked_arc_ffi!( - ImportRule, - LockedImportRule, - Servo_ImportRule_AddRef, - Servo_ImportRule_Release -); -impl_locked_arc_ffi!( - Keyframe, - LockedKeyframe, - Servo_Keyframe_AddRef, - Servo_Keyframe_Release -); -impl_locked_arc_ffi!( - KeyframesRule, - LockedKeyframesRule, - Servo_KeyframesRule_AddRef, - Servo_KeyframesRule_Release -); -impl_simple_arc_ffi!( - LayerBlockRule, - Servo_LayerBlockRule_AddRef, - Servo_LayerBlockRule_Release -); -impl_simple_arc_ffi!( - LayerStatementRule, - Servo_LayerStatementRule_AddRef, - Servo_LayerStatementRule_Release -); -impl_locked_arc_ffi!( - MediaList, - LockedMediaList, - Servo_MediaList_AddRef, - Servo_MediaList_Release -); -impl_simple_arc_ffi!(MediaRule, Servo_MediaRule_AddRef, Servo_MediaRule_Release); -impl_simple_arc_ffi!( - NamespaceRule, - Servo_NamespaceRule_AddRef, - Servo_NamespaceRule_Release -); -impl_locked_arc_ffi!( - PageRule, - LockedPageRule, - Servo_PageRule_AddRef, - Servo_PageRule_Release -); -impl_simple_arc_ffi!( - PropertyRule, - Servo_PropertyRule_AddRef, - Servo_PropertyRule_Release -); -impl_simple_arc_ffi!( - SupportsRule, - Servo_SupportsRule_AddRef, - Servo_SupportsRule_Release -); -impl_simple_arc_ffi!( - ContainerRule, - Servo_ContainerRule_AddRef, - Servo_ContainerRule_Release -); -impl_simple_arc_ffi!( - DocumentRule, - Servo_DocumentRule_AddRef, - Servo_DocumentRule_Release -); -impl_simple_arc_ffi!( - FontFeatureValuesRule, - Servo_FontFeatureValuesRule_AddRef, - Servo_FontFeatureValuesRule_Release -); -impl_simple_arc_ffi!( - FontPaletteValuesRule, - Servo_FontPaletteValuesRule_AddRef, - Servo_FontPaletteValuesRule_Release -); -impl_locked_arc_ffi!( - FontFaceRule, - LockedFontFaceRule, - Servo_FontFaceRule_AddRef, - Servo_FontFaceRule_Release -); -impl_locked_arc_ffi!( - CounterStyleRule, - LockedCounterStyleRule, - Servo_CounterStyleRule_AddRef, - Servo_CounterStyleRule_Release -); - -impl_simple_arc_ffi!( - StylesheetContents, - Servo_StyleSheetContents_AddRef, - Servo_StyleSheetContents_Release -); -impl_simple_arc_ffi!( - CssUrlData, - Servo_CssUrlData_AddRef, - Servo_CssUrlData_Release -); -impl_simple_arc_ffi!( - ComputedValues, - Servo_ComputedStyle_AddRef, - Servo_ComputedStyle_Release -); -impl_simple_arc_ffi!( - AnimationValue, - Servo_AnimationValue_AddRef, - Servo_AnimationValue_Release -); diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs deleted file mode 100644 index ea3700a3235..00000000000 --- a/components/style/gecko/conversions.rs +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 module contains conversion helpers between Servo and Gecko types -//! Ideally, it would be in geckolib itself, but coherence -//! forces us to keep the traits and implementations here -//! -//! FIXME(emilio): This file should generally just die. - -#![allow(unsafe_code)] - -use crate::gecko_bindings::structs::{nsresult, Matrix4x4Components}; -use crate::stylesheets::RulesMutateError; -use crate::values::computed::transform::Matrix3D; - -impl From<RulesMutateError> for nsresult { - fn from(other: RulesMutateError) -> Self { - match other { - RulesMutateError::Syntax => nsresult::NS_ERROR_DOM_SYNTAX_ERR, - RulesMutateError::IndexSize => nsresult::NS_ERROR_DOM_INDEX_SIZE_ERR, - RulesMutateError::HierarchyRequest => nsresult::NS_ERROR_DOM_HIERARCHY_REQUEST_ERR, - RulesMutateError::InvalidState => nsresult::NS_ERROR_DOM_INVALID_STATE_ERR, - } - } -} - -impl<'a> From<&'a Matrix4x4Components> for Matrix3D { - fn from(m: &'a Matrix4x4Components) -> Matrix3D { - Matrix3D { - m11: m[0], - m12: m[1], - m13: m[2], - m14: m[3], - m21: m[4], - m22: m[5], - m23: m[6], - m24: m[7], - m31: m[8], - m32: m[9], - m33: m[10], - m34: m[11], - m41: m[12], - m42: m[13], - m43: m[14], - m44: m[15], - } - } -} - -impl From<Matrix3D> for Matrix4x4Components { - fn from(matrix: Matrix3D) -> Self { - [ - matrix.m11, matrix.m12, matrix.m13, matrix.m14, matrix.m21, matrix.m22, matrix.m23, - matrix.m24, matrix.m31, matrix.m32, matrix.m33, matrix.m34, matrix.m41, matrix.m42, - matrix.m43, matrix.m44, - ] - } -} diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs deleted file mode 100644 index c4a5554c5e5..00000000000 --- a/components/style/gecko/data.rs +++ /dev/null @@ -1,198 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Data needed to style a Gecko document. - -use crate::dom::TElement; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{ - self, ServoStyleSetSizes, StyleSheet as DomStyleSheet, StyleSheetInfo, -}; -use crate::invalidation::media_queries::{MediaListKey, ToMediaListKey}; -use crate::media_queries::{Device, MediaList}; -use crate::properties::ComputedValues; -use crate::selector_parser::SnapshotMap; -use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards}; -use crate::stylesheets::{StylesheetContents, StylesheetInDocument}; -use crate::stylist::Stylist; -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use malloc_size_of::MallocSizeOfOps; -use servo_arc::Arc; -use std::fmt; - -/// Little wrapper to a Gecko style sheet. -#[derive(Eq, PartialEq)] -pub struct GeckoStyleSheet(*const DomStyleSheet); - -// NOTE(emilio): These are kind of a lie. We allow to make these Send + Sync so that other data -// structures can also be Send and Sync, but Gecko's stylesheets are main-thread-reference-counted. -// -// We assert that we reference-count in the right thread (in the Addref/Release implementations). -// Sending these to a different thread can't really happen (it could theoretically really happen if -// we allowed @import rules inside a nested style rule, but that can't happen per spec and would be -// a parser bug, caught by the asserts). -unsafe impl Send for GeckoStyleSheet {} -unsafe impl Sync for GeckoStyleSheet {} - -impl fmt::Debug for GeckoStyleSheet { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - let contents = self.contents(); - formatter - .debug_struct("GeckoStyleSheet") - .field("origin", &contents.origin) - .field("url_data", &*contents.url_data.read()) - .finish() - } -} - -impl ToMediaListKey for crate::gecko::data::GeckoStyleSheet { - fn to_media_list_key(&self) -> MediaListKey { - use std::mem; - unsafe { MediaListKey::from_raw(mem::transmute(self.0)) } - } -} - -impl GeckoStyleSheet { - /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer. - #[inline] - pub unsafe fn new(s: *const DomStyleSheet) -> Self { - debug_assert!(!s.is_null()); - bindings::Gecko_StyleSheet_AddRef(s); - Self::from_addrefed(s) - } - - /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer that - /// already holds a strong reference. - #[inline] - pub unsafe fn from_addrefed(s: *const DomStyleSheet) -> Self { - assert!(!s.is_null()); - GeckoStyleSheet(s) - } - - /// HACK(emilio): This is so that we can avoid crashing release due to - /// bug 1719963 and can hopefully get a useful report from fuzzers. - #[inline] - pub fn hack_is_null(&self) -> bool { - self.0.is_null() - } - - /// Get the raw `StyleSheet` that we're wrapping. - pub fn raw(&self) -> &DomStyleSheet { - unsafe { &*self.0 } - } - - fn inner(&self) -> &StyleSheetInfo { - unsafe { &*(self.raw().mInner as *const StyleSheetInfo) } - } -} - -impl Drop for GeckoStyleSheet { - fn drop(&mut self) { - unsafe { bindings::Gecko_StyleSheet_Release(self.0) }; - } -} - -impl Clone for GeckoStyleSheet { - fn clone(&self) -> Self { - unsafe { bindings::Gecko_StyleSheet_AddRef(self.0) }; - GeckoStyleSheet(self.0) - } -} - -impl StylesheetInDocument for GeckoStyleSheet { - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - use crate::gecko_bindings::structs::mozilla::dom::MediaList as DomMediaList; - unsafe { - let dom_media_list = self.raw().mMedia.mRawPtr as *const DomMediaList; - if dom_media_list.is_null() { - return None; - } - let list = &*(*dom_media_list).mRawList.mRawPtr; - Some(list.read_with(guard)) - } - } - - // All the stylesheets Servo knows about are enabled, because that state is - // handled externally by Gecko. - #[inline] - fn enabled(&self) -> bool { - true - } - - #[inline] - fn contents(&self) -> &StylesheetContents { - debug_assert!(!self.inner().mContents.mRawPtr.is_null()); - unsafe { &*self.inner().mContents.mRawPtr } - } -} - -/// The container for data that a Servo-backed Gecko document needs to style -/// itself. -pub struct PerDocumentStyleDataImpl { - /// Rule processor. - pub stylist: Stylist, - - /// A cache from element to resolved style. - pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache, - - /// The generation for which our cache is valid. - pub undisplayed_style_cache_generation: u64, -} - -/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics -/// and unexpected races while trying to mutate it. -pub struct PerDocumentStyleData(AtomicRefCell<PerDocumentStyleDataImpl>); - -impl PerDocumentStyleData { - /// Create a `PerDocumentStyleData`. - pub fn new(document: *const structs::Document) -> Self { - let device = Device::new(document); - let quirks_mode = device.document().mCompatMode; - - PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { - stylist: Stylist::new(device, quirks_mode.into()), - undisplayed_style_cache: Default::default(), - undisplayed_style_cache_generation: 0, - })) - } - - /// Get an immutable reference to this style data. - pub fn borrow(&self) -> AtomicRef<PerDocumentStyleDataImpl> { - self.0.borrow() - } - - /// Get an mutable reference to this style data. - pub fn borrow_mut(&self) -> AtomicRefMut<PerDocumentStyleDataImpl> { - self.0.borrow_mut() - } -} - -impl PerDocumentStyleDataImpl { - /// Recreate the style data if the stylesheets have changed. - pub fn flush_stylesheets<E>( - &mut self, - guard: &SharedRwLockReadGuard, - document_element: Option<E>, - snapshots: Option<&SnapshotMap>, - ) -> bool - where - E: TElement, - { - self.stylist - .flush(&StylesheetGuards::same(guard), document_element, snapshots) - } - - /// Get the default computed values for this document. - pub fn default_computed_values(&self) -> &Arc<ComputedValues> { - self.stylist.device().default_computed_values_arc() - } - - /// Measure heap usage. - pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.stylist.add_size_of(ops, sizes); - } -} - -/// The gecko-specific AuthorStyles instantiation. -pub type AuthorStyles = crate::author_styles::AuthorStyles<GeckoStyleSheet>; diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs deleted file mode 100644 index 4ca746ea84d..00000000000 --- a/components/style/gecko/media_features.rs +++ /dev/null @@ -1,1028 +0,0 @@ -/* 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/. */ - -//! Gecko's media feature list and evaluator. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::ScreenColorGamut; -use crate::media_queries::{Device, MediaType}; -use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::Orientation; -use crate::values::computed::{CSSPixelLength, Context, Ratio, Resolution}; -use app_units::Au; -use euclid::default::Size2D; - -fn device_size(device: &Device) -> Size2D<Au> { - let mut width = 0; - let mut height = 0; - unsafe { - bindings::Gecko_MediaFeatures_GetDeviceSize(device.document(), &mut width, &mut height); - } - Size2D::new(Au(width), Au(height)) -} - -/// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) -} - -/// https://drafts.csswg.org/mediaqueries-4/#device-width -fn eval_device_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(device_size(context.device()).width.to_f32_px()) -} - -/// https://drafts.csswg.org/mediaqueries-4/#height -fn eval_height(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px()) -} - -/// https://drafts.csswg.org/mediaqueries-4/#device-height -fn eval_device_height(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(device_size(context.device()).height.to_f32_px()) -} - -fn eval_aspect_ratio_for<F>(context: &Context, get_size: F) -> Ratio -where - F: FnOnce(&Device) -> Size2D<Au>, -{ - let size = get_size(context.device()); - Ratio::new(size.width.0 as f32, size.height.0 as f32) -} - -/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio -fn eval_aspect_ratio(context: &Context) -> Ratio { - eval_aspect_ratio_for(context, Device::au_viewport_size) -} - -/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio -fn eval_device_aspect_ratio(context: &Context) -> Ratio { - eval_aspect_ratio_for(context, device_size) -} - -/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio -fn eval_device_pixel_ratio(context: &Context) -> f32 { - eval_resolution(context).dppx() -} - -/// https://drafts.csswg.org/mediaqueries-4/#orientation -fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool { - Orientation::eval(context.device().au_viewport_size(), value) -} - -/// FIXME: There's no spec for `-moz-device-orientation`. -fn eval_device_orientation(context: &Context, value: Option<Orientation>) -> bool { - Orientation::eval(device_size(context.device()), value) -} - -/// Values for the display-mode media feature. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum DisplayMode { - Browser = 0, - MinimalUi, - Standalone, - Fullscreen, -} - -/// https://w3c.github.io/manifest/#the-display-mode-media-feature -fn eval_display_mode(context: &Context, query_value: Option<DisplayMode>) -> bool { - match query_value { - Some(v) => { - v == unsafe { - bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) - } - }, - None => true, - } -} - -/// https://drafts.csswg.org/mediaqueries-4/#grid -fn eval_grid(_: &Context) -> bool { - // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature - // is always 0. - false -} - -/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d -fn eval_transform_3d(_: &Context) -> bool { - true -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Scan { - Progressive, - Interlace, -} - -/// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Context, _: Option<Scan>) -> bool { - // Since Gecko doesn't support the 'tv' media type, the 'scan' feature never - // matches. - false -} - -/// https://drafts.csswg.org/mediaqueries-4/#color -fn eval_color(context: &Context) -> i32 { - unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) } -} - -/// https://drafts.csswg.org/mediaqueries-4/#color-index -fn eval_color_index(_: &Context) -> i32 { - // We should return zero if the device does not use a color lookup table. - 0 -} - -/// https://drafts.csswg.org/mediaqueries-4/#monochrome -fn eval_monochrome(context: &Context) -> i32 { - // For color devices we should return 0. - unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) } -} - -/// Values for the color-gamut media feature. -/// This implements PartialOrd so that lower values will correctly match -/// higher capabilities. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)] -#[repr(u8)] -enum ColorGamut { - /// The sRGB gamut. - Srgb, - /// The gamut specified by the Display P3 Color Space. - P3, - /// The gamut specified by the ITU-R Recommendation BT.2020 Color Space. - Rec2020, -} - -/// https://drafts.csswg.org/mediaqueries-4/#color-gamut -fn eval_color_gamut(context: &Context, query_value: Option<ColorGamut>) -> bool { - let query_value = match query_value { - Some(v) => v, - None => return false, - }; - let color_gamut = - unsafe { bindings::Gecko_MediaFeatures_ColorGamut(context.device().document()) }; - // Match if our color gamut is at least as wide as the query value - query_value <= - match color_gamut { - // EndGuard_ is not a valid color gamut, so the default color-gamut is used. - ScreenColorGamut::Srgb | ScreenColorGamut::EndGuard_ => ColorGamut::Srgb, - ScreenColorGamut::P3 => ColorGamut::P3, - ScreenColorGamut::Rec2020 => ColorGamut::Rec2020, - } -} - -/// https://drafts.csswg.org/mediaqueries-4/#resolution -fn eval_resolution(context: &Context) -> Resolution { - let resolution_dppx = - unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) }; - Resolution::from_dppx(resolution_dppx) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum PrefersReducedMotion { - NoPreference, - Reduce, -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum PrefersReducedTransparency { - NoPreference, - Reduce, -} - -/// Values for the prefers-color-scheme media feature. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum PrefersColorScheme { - Light, - Dark, -} - -/// Values for the dynamic-range and video-dynamic-range media features. -/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range -/// This implements PartialOrd so that lower values will correctly match -/// higher capabilities. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum DynamicRange { - Standard, - High, -} - -/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion -fn eval_prefers_reduced_motion( - context: &Context, - query_value: Option<PrefersReducedMotion>, -) -> bool { - let prefers_reduced = - unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) }; - let query_value = match query_value { - Some(v) => v, - None => return prefers_reduced, - }; - - match query_value { - PrefersReducedMotion::NoPreference => !prefers_reduced, - PrefersReducedMotion::Reduce => prefers_reduced, - } -} - -/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-transparency -fn eval_prefers_reduced_transparency( - context: &Context, - query_value: Option<PrefersReducedTransparency>, -) -> bool { - let prefers_reduced = unsafe { - bindings::Gecko_MediaFeatures_PrefersReducedTransparency(context.device().document()) - }; - let query_value = match query_value { - Some(v) => v, - None => return prefers_reduced, - }; - - match query_value { - PrefersReducedTransparency::NoPreference => !prefers_reduced, - PrefersReducedTransparency::Reduce => prefers_reduced, - } -} - -/// Possible values for prefers-contrast media query. -/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -pub enum PrefersContrast { - /// More contrast is preferred. - More, - /// Low contrast is preferred. - Less, - /// Custom (not more, not less). - Custom, - /// The default value if neither high or low contrast is enabled. - NoPreference, -} - -/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast -fn eval_prefers_contrast(context: &Context, query_value: Option<PrefersContrast>) -> bool { - let prefers_contrast = - unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) }; - match query_value { - Some(v) => v == prefers_contrast, - None => prefers_contrast != PrefersContrast::NoPreference, - } -} - -/// Possible values for the forced-colors media query. -/// https://drafts.csswg.org/mediaqueries-5/#forced-colors -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -pub enum ForcedColors { - /// Page colors are not being forced. - None, - /// Page colors are being forced. - Active, -} - -/// https://drafts.csswg.org/mediaqueries-5/#forced-colors -fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool { - let forced = !context.device().use_document_colors(); - match query_value { - Some(query_value) => forced == (query_value == ForcedColors::Active), - None => forced, - } -} - -/// Possible values for the inverted-colors media query. -/// https://drafts.csswg.org/mediaqueries-5/#inverted -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum InvertedColors { - /// Colors are displayed normally. - None, - /// All pixels within the displayed area have been inverted. - Inverted, -} - -/// https://drafts.csswg.org/mediaqueries-5/#inverted -fn eval_inverted_colors(context: &Context, query_value: Option<InvertedColors>) -> bool { - let inverted_colors = - unsafe { bindings::Gecko_MediaFeatures_InvertedColors(context.device().document()) }; - let query_value = match query_value { - Some(v) => v, - None => return inverted_colors, - }; - - match query_value { - InvertedColors::None => !inverted_colors, - InvertedColors::Inverted => inverted_colors, - } -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum OverflowBlock { - None, - Scroll, - Paged, -} - -/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block -fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) -> bool { - // For the time being, assume that printing (including previews) - // is the only time when we paginate, and we are otherwise always - // scrolling. This is true at the moment in Firefox, but may need - // updating in the future (e.g., ebook readers built with Stylo, a - // billboard mode that doesn't support overflow at all). - // - // If this ever changes, don't forget to change eval_overflow_inline too. - let scrolling = context.device().media_type() != MediaType::print(); - let query_value = match query_value { - Some(v) => v, - None => return true, - }; - - match query_value { - OverflowBlock::None => false, - OverflowBlock::Scroll => scrolling, - OverflowBlock::Paged => !scrolling, - } -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum OverflowInline { - None, - Scroll, -} - -/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline -fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>) -> bool { - // See the note in eval_overflow_block. - let scrolling = context.device().media_type() != MediaType::print(); - let query_value = match query_value { - Some(v) => v, - None => return scrolling, - }; - - match query_value { - OverflowInline::None => !scrolling, - OverflowInline::Scroll => scrolling, - } -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Update { - None, - Slow, - Fast, -} - -/// https://drafts.csswg.org/mediaqueries-4/#update -fn eval_update(context: &Context, query_value: Option<Update>) -> bool { - // This has similar caveats to those described in eval_overflow_block. - // For now, we report that print (incl. print media simulation, - // which can in fact update but is limited to the developer tools) - // is `update: none` and that all other contexts are `update: fast`, - // which may not be true for future platforms, like e-ink devices. - let can_update = context.device().media_type() != MediaType::print(); - let query_value = match query_value { - Some(v) => v, - None => return can_update, - }; - - match query_value { - Update::None => !can_update, - Update::Slow => false, - Update::Fast => can_update, - } -} - -fn do_eval_prefers_color_scheme( - context: &Context, - use_content: bool, - query_value: Option<PrefersColorScheme>, -) -> bool { - let prefers_color_scheme = unsafe { - bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) - }; - match query_value { - Some(v) => prefers_color_scheme == v, - None => true, - } -} - -/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme -fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool { - do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value) -} - -fn eval_content_prefers_color_scheme( - context: &Context, - query_value: Option<PrefersColorScheme>, -) -> bool { - do_eval_prefers_color_scheme(context, /* use_content = */ true, query_value) -} - -/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range -fn eval_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool { - let dynamic_range = - unsafe { bindings::Gecko_MediaFeatures_DynamicRange(context.device().document()) }; - match query_value { - Some(v) => dynamic_range >= v, - None => false, - } -} -/// https://drafts.csswg.org/mediaqueries-5/#video-dynamic-range -fn eval_video_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool { - let dynamic_range = - unsafe { bindings::Gecko_MediaFeatures_VideoDynamicRange(context.device().document()) }; - match query_value { - Some(v) => dynamic_range >= v, - None => false, - } -} - -bitflags! { - /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction - struct PointerCapabilities: u8 { - const COARSE = structs::PointerCapabilities_Coarse; - const FINE = structs::PointerCapabilities_Fine; - const HOVER = structs::PointerCapabilities_Hover; - } -} - -fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities { - PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document()) - }) -} - -fn all_pointer_capabilities(context: &Context) -> PointerCapabilities { - PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document()) - }) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Pointer { - None, - Coarse, - Fine, -} - -fn eval_pointer_capabilities( - query_value: Option<Pointer>, - pointer_capabilities: PointerCapabilities, -) -> bool { - let query_value = match query_value { - Some(v) => v, - None => return !pointer_capabilities.is_empty(), - }; - - match query_value { - Pointer::None => pointer_capabilities.is_empty(), - Pointer::Coarse => pointer_capabilities.intersects(PointerCapabilities::COARSE), - Pointer::Fine => pointer_capabilities.intersects(PointerCapabilities::FINE), - } -} - -/// https://drafts.csswg.org/mediaqueries-4/#pointer -fn eval_pointer(context: &Context, query_value: Option<Pointer>) -> bool { - eval_pointer_capabilities(query_value, primary_pointer_capabilities(context)) -} - -/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer -fn eval_any_pointer(context: &Context, query_value: Option<Pointer>) -> bool { - eval_pointer_capabilities(query_value, all_pointer_capabilities(context)) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Hover { - None, - Hover, -} - -fn eval_hover_capabilities( - query_value: Option<Hover>, - pointer_capabilities: PointerCapabilities, -) -> bool { - let can_hover = pointer_capabilities.intersects(PointerCapabilities::HOVER); - let query_value = match query_value { - Some(v) => v, - None => return can_hover, - }; - - match query_value { - Hover::None => !can_hover, - Hover::Hover => can_hover, - } -} - -/// https://drafts.csswg.org/mediaqueries-4/#hover -fn eval_hover(context: &Context, query_value: Option<Hover>) -> bool { - eval_hover_capabilities(query_value, primary_pointer_capabilities(context)) -} - -/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover -fn eval_any_hover(context: &Context, query_value: Option<Hover>) -> bool { - eval_hover_capabilities(query_value, all_pointer_capabilities(context)) -} - -fn eval_moz_is_glyph(context: &Context) -> bool { - context.device().document().mIsSVGGlyphsDocument() -} - -fn eval_moz_print_preview(context: &Context) -> bool { - let is_print_preview = context.device().is_print_preview(); - if is_print_preview { - debug_assert_eq!(context.device().media_type(), MediaType::print()); - } - is_print_preview -} - -fn eval_moz_non_native_content_theme(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) } -} - -fn eval_moz_is_resource_document(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) } -} - -/// Allows front-end CSS to discern platform via media queries. -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -pub enum Platform { - /// Matches any Android version. - Android, - /// For our purposes here, "linux" is just "gtk" (so unix-but-not-mac). - /// There's no need for our front-end code to differentiate between those - /// platforms and they already use the "linux" string elsewhere (e.g., - /// toolkit/themes/linux). - Linux, - /// Matches any macOS version. - Macos, - /// Matches any Windows version. - Windows, - /// Matches only Windows 7. - WindowsWin7, - /// Matches only Windows 8. - WindowsWin8, - /// Matches windows 10 and actually matches windows 11 too, as of right now. - WindowsWin10, -} - -fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool { - let query_value = match query_value { - Some(v) => v, - None => return false, - }; - - unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } -} - -/// Values for the scripting media feature. -/// https://drafts.csswg.org/mediaqueries-5/#scripting -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] -#[repr(u8)] -pub enum Scripting { - /// Scripting is not supported or not enabled - None, - /// Scripting is supported and enabled, but only for initial page load - /// We will never match this value as it is intended for non-browser user agents, - /// but it is part of the spec so we should still parse it. - /// See: https://github.com/w3c/csswg-drafts/issues/8621 - InitialOnly, - /// Scripting is supported and enabled - Enabled, -} - -/// https://drafts.csswg.org/mediaqueries-5/#scripting -fn eval_scripting(context: &Context, query_value: Option<Scripting>) -> bool { - let scripting = unsafe { bindings::Gecko_MediaFeatures_Scripting(context.device().document()) }; - match query_value { - Some(v) => v == scripting, - None => scripting != Scripting::None, - } -} - -fn eval_moz_windows_non_native_menus(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_WindowsNonNativeMenus(context.device().document()) } -} - -fn eval_moz_overlay_scrollbars(context: &Context) -> bool { - unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) } -} - -fn get_lnf_int(int_id: i32) -> i32 { - unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } -} - -fn get_lnf_int_as_bool(int_id: i32) -> bool { - get_lnf_int(int_id) != 0 -} - -fn get_scrollbar_start_backward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartBackward as i32) != 0 -} - -fn get_scrollbar_start_forward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartForward as i32) != 0 -} - -fn get_scrollbar_end_backward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndBackward as i32) != 0 -} - -fn get_scrollbar_end_forward(int_id: i32) -> bool { - (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndForward as i32) != 0 -} - -macro_rules! lnf_int_feature { - ($feature_name:expr, $int_id:ident, $get_value:ident) => {{ - fn __eval(_: &Context) -> bool { - $get_value(bindings::LookAndFeel_IntID::$int_id as i32) - } - - feature!( - $feature_name, - AllowsRanges::No, - Evaluator::BoolInteger(__eval), - FeatureFlags::CHROME_AND_UA_ONLY, - ) - }}; - ($feature_name:expr, $int_id:ident) => {{ - lnf_int_feature!($feature_name, $int_id, get_lnf_int_as_bool) - }}; -} - -/// bool pref-based features are an slightly less convenient to start using -/// version of @supports -moz-bool-pref, but with some benefits, mainly that -/// they can support dynamic changes, and don't require a pref lookup every time -/// they're used. -/// -/// In order to use them you need to make sure that the pref defined as a static -/// pref, with `rust: true`. The feature name needs to be defined in -/// `StaticAtoms.py` just like the others. In order to support dynamic changes, -/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp -#[allow(unused)] -macro_rules! bool_pref_feature { - ($feature_name:expr, $pref:tt) => {{ - fn __eval(_: &Context) -> bool { - static_prefs::pref!($pref) - } - - feature!( - $feature_name, - AllowsRanges::No, - Evaluator::BoolInteger(__eval), - FeatureFlags::CHROME_AND_UA_ONLY, - ) - }}; -} - -/// Adding new media features requires (1) adding the new feature to this -/// array, with appropriate entries (and potentially any new code needed -/// to support new types in these entries and (2) ensuring that either -/// nsPresContext::MediaFeatureValuesChanged is called when the value that -/// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [QueryFeatureDescription; 67] = [ - feature!( - atom!("width"), - AllowsRanges::Yes, - Evaluator::Length(eval_width), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("height"), - AllowsRanges::Yes, - Evaluator::Length(eval_height), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("aspect-ratio"), - AllowsRanges::Yes, - Evaluator::NumberRatio(eval_aspect_ratio), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("orientation"), - AllowsRanges::No, - keyword_evaluator!(eval_orientation, Orientation), - FeatureFlags::VIEWPORT_DEPENDENT, - ), - feature!( - atom!("device-width"), - AllowsRanges::Yes, - Evaluator::Length(eval_device_width), - FeatureFlags::empty(), - ), - feature!( - atom!("device-height"), - AllowsRanges::Yes, - Evaluator::Length(eval_device_height), - FeatureFlags::empty(), - ), - feature!( - atom!("device-aspect-ratio"), - AllowsRanges::Yes, - Evaluator::NumberRatio(eval_device_aspect_ratio), - FeatureFlags::empty(), - ), - feature!( - atom!("-moz-device-orientation"), - AllowsRanges::No, - keyword_evaluator!(eval_device_orientation, Orientation), - FeatureFlags::empty(), - ), - // Webkit extensions that we support for de-facto web compatibility. - // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): - feature!( - atom!("device-pixel-ratio"), - AllowsRanges::Yes, - Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::WEBKIT_PREFIX, - ), - // -webkit-transform-3d. - feature!( - atom!("transform-3d"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_transform_3d), - FeatureFlags::WEBKIT_PREFIX, - ), - feature!( - atom!("-moz-device-pixel-ratio"), - AllowsRanges::Yes, - Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::empty(), - ), - feature!( - atom!("resolution"), - AllowsRanges::Yes, - Evaluator::Resolution(eval_resolution), - FeatureFlags::empty(), - ), - feature!( - atom!("display-mode"), - AllowsRanges::No, - keyword_evaluator!(eval_display_mode, DisplayMode), - FeatureFlags::empty(), - ), - feature!( - atom!("grid"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_grid), - FeatureFlags::empty(), - ), - feature!( - atom!("scan"), - AllowsRanges::No, - keyword_evaluator!(eval_scan, Scan), - FeatureFlags::empty(), - ), - feature!( - atom!("color"), - AllowsRanges::Yes, - Evaluator::Integer(eval_color), - FeatureFlags::empty(), - ), - feature!( - atom!("color-index"), - AllowsRanges::Yes, - Evaluator::Integer(eval_color_index), - FeatureFlags::empty(), - ), - feature!( - atom!("monochrome"), - AllowsRanges::Yes, - Evaluator::Integer(eval_monochrome), - FeatureFlags::empty(), - ), - feature!( - atom!("color-gamut"), - AllowsRanges::No, - keyword_evaluator!(eval_color_gamut, ColorGamut), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-reduced-motion"), - AllowsRanges::No, - keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-reduced-transparency"), - AllowsRanges::No, - keyword_evaluator!( - eval_prefers_reduced_transparency, - PrefersReducedTransparency - ), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-contrast"), - AllowsRanges::No, - keyword_evaluator!(eval_prefers_contrast, PrefersContrast), - // Note: by default this is only enabled in browser chrome and - // ua. It can be enabled on the web via the - // layout.css.prefers-contrast.enabled preference. See - // disabed_by_pref in media_feature_expression.rs for how that - // is done. - FeatureFlags::empty(), - ), - feature!( - atom!("forced-colors"), - AllowsRanges::No, - keyword_evaluator!(eval_forced_colors, ForcedColors), - FeatureFlags::empty(), - ), - feature!( - atom!("inverted-colors"), - AllowsRanges::No, - keyword_evaluator!(eval_inverted_colors, InvertedColors), - FeatureFlags::empty(), - ), - feature!( - atom!("overflow-block"), - AllowsRanges::No, - keyword_evaluator!(eval_overflow_block, OverflowBlock), - FeatureFlags::empty(), - ), - feature!( - atom!("overflow-inline"), - AllowsRanges::No, - keyword_evaluator!(eval_overflow_inline, OverflowInline), - FeatureFlags::empty(), - ), - feature!( - atom!("update"), - AllowsRanges::No, - keyword_evaluator!(eval_update, Update), - FeatureFlags::empty(), - ), - feature!( - atom!("prefers-color-scheme"), - AllowsRanges::No, - keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), - FeatureFlags::empty(), - ), - feature!( - atom!("dynamic-range"), - AllowsRanges::No, - keyword_evaluator!(eval_dynamic_range, DynamicRange), - FeatureFlags::empty(), - ), - feature!( - atom!("video-dynamic-range"), - AllowsRanges::No, - keyword_evaluator!(eval_video_dynamic_range, DynamicRange), - FeatureFlags::empty(), - ), - feature!( - atom!("scripting"), - AllowsRanges::No, - keyword_evaluator!(eval_scripting, Scripting), - FeatureFlags::empty(), - ), - // Evaluates to the preferred color scheme for content. Only useful in - // chrome context, where the chrome color-scheme and the content - // color-scheme might differ. - feature!( - atom!("-moz-content-prefers-color-scheme"), - AllowsRanges::No, - keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("pointer"), - AllowsRanges::No, - keyword_evaluator!(eval_pointer, Pointer), - FeatureFlags::empty(), - ), - feature!( - atom!("any-pointer"), - AllowsRanges::No, - keyword_evaluator!(eval_any_pointer, Pointer), - FeatureFlags::empty(), - ), - feature!( - atom!("hover"), - AllowsRanges::No, - keyword_evaluator!(eval_hover, Hover), - FeatureFlags::empty(), - ), - feature!( - atom!("any-hover"), - AllowsRanges::No, - keyword_evaluator!(eval_any_hover, Hover), - FeatureFlags::empty(), - ), - // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. - // Internal because it is really only useful in the user agent anyway - // and therefore not worth standardizing. - feature!( - atom!("-moz-is-glyph"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_is_glyph), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-is-resource-document"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_is_resource_document), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-platform"), - AllowsRanges::No, - keyword_evaluator!(eval_moz_platform, Platform), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-print-preview"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_print_preview), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-non-native-content-theme"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_non_native_content_theme), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-windows-non-native-menus"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_windows_non_native_menus), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - feature!( - atom!("-moz-overlay-scrollbars"), - AllowsRanges::No, - Evaluator::BoolInteger(eval_moz_overlay_scrollbars), - FeatureFlags::CHROME_AND_UA_ONLY, - ), - lnf_int_feature!( - atom!("-moz-scrollbar-start-backward"), - ScrollArrowStyle, - get_scrollbar_start_backward - ), - lnf_int_feature!( - atom!("-moz-scrollbar-start-forward"), - ScrollArrowStyle, - get_scrollbar_start_forward - ), - lnf_int_feature!( - atom!("-moz-scrollbar-end-backward"), - ScrollArrowStyle, - get_scrollbar_end_backward - ), - lnf_int_feature!( - atom!("-moz-scrollbar-end-forward"), - ScrollArrowStyle, - get_scrollbar_end_forward - ), - lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), - lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), - lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), - lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme), - lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL), - lnf_int_feature!( - atom!("-moz-windows-accent-color-in-titlebar"), - WindowsAccentColorInTitlebar - ), - lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), - lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), - lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), - lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled), - lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable), - lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), - lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), - lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), - lnf_int_feature!( - atom!("-moz-gtk-csd-reversed-placement"), - GTKCSDReversedPlacement - ), - lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), - lnf_int_feature!(atom!("-moz-panel-animations"), PanelAnimations), - // media query for MathML Core's implementation of maction/semantics - bool_pref_feature!( - atom!("-moz-mathml-core-maction-and-semantics"), - "mathml.legacy_maction_and_semantics_implementations.disabled" - ), - // media query for MathML Core's implementation of ms - bool_pref_feature!( - atom!("-moz-mathml-core-ms"), - "mathml.ms_lquote_rquote_attributes.disabled" - ), - // media query for popover attribute - bool_pref_feature!(atom!("-moz-popover-enabled"), "dom.element.popover.enabled"), -]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs deleted file mode 100644 index 2ea4229133a..00000000000 --- a/components/style/gecko/media_queries.rs +++ /dev/null @@ -1,560 +0,0 @@ -/* 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/. */ - -//! Gecko's media-query device and expression representation. - -use crate::color::AbsoluteColor; -use crate::context::QuirksMode; -use crate::custom_properties::CssEnvironment; -use crate::font_metrics::FontMetrics; -use crate::gecko::values::{convert_absolute_color_to_nscolor, convert_nscolor_to_absolute_color}; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::media_queries::MediaType; -use crate::properties::ComputedValues; -use crate::string_cache::Atom; -use crate::values::computed::font::GenericFontFamily; -use crate::values::computed::{ColorScheme, Length, NonNegativeLength}; -use crate::values::specified::color::SystemColor; -use crate::values::specified::font::FONT_MEDIUM_PX; -use crate::values::specified::ViewportVariant; -use crate::values::{CustomIdent, KeyframesName}; -use app_units::{Au, AU_PER_PX}; -use euclid::default::Size2D; -use euclid::{Scale, SideOffsets2D}; -use servo_arc::Arc; -use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; -use std::{cmp, fmt}; -use style_traits::{CSSPixel, DevicePixel}; - -/// The `Device` in Gecko wraps a pres context, has a default values computed, -/// and contains all the viewport rule state. -pub struct Device { - /// NB: The document owns the styleset, who owns the stylist, and thus the - /// `Device`, so having a raw document pointer here is fine. - document: *const structs::Document, - default_values: Arc<ComputedValues>, - /// The font size of the root element. - /// - /// This is set when computing the style of the root element, and used for - /// rem units in other elements. - /// - /// When computing the style of the root element, there can't be any other - /// style being computed at the same time, given we need the style of the - /// parent to compute everything else. So it is correct to just use a - /// relaxed atomic here. - root_font_size: AtomicU32, - /// The body text color, stored as an `nscolor`, used for the "tables - /// inherit from body" quirk. - /// - /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk> - body_text_color: AtomicUsize, - /// Whether any styles computed in the document relied on the root font-size - /// by using rem units. - used_root_font_size: AtomicBool, - /// Whether any styles computed in the document relied on font metrics. - used_font_metrics: AtomicBool, - /// Whether any styles computed in the document relied on the viewport size - /// by using vw/vh/vmin/vmax units. - used_viewport_size: AtomicBool, - /// Whether any styles computed in the document relied on the viewport size - /// by using dvw/dvh/dvmin/dvmax units. - used_dynamic_viewport_size: AtomicBool, - /// The CssEnvironment object responsible of getting CSS environment - /// variables. - environment: CssEnvironment, -} - -impl fmt::Debug for Device { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use nsstring::nsCString; - - let mut doc_uri = nsCString::new(); - unsafe { - bindings::Gecko_nsIURI_Debug((*self.document()).mDocumentURI.raw(), &mut doc_uri) - }; - - f.debug_struct("Device") - .field("document_url", &doc_uri) - .finish() - } -} - -unsafe impl Sync for Device {} -unsafe impl Send for Device {} - -impl Device { - /// Trivially constructs a new `Device`. - pub fn new(document: *const structs::Document) -> Self { - assert!(!document.is_null()); - let doc = unsafe { &*document }; - let prefs = unsafe { &*bindings::Gecko_GetPrefSheetPrefs(doc) }; - Device { - document, - default_values: ComputedValues::default_values(doc), - root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), - // This gets updated when we see the <body>, so it doesn't really - // matter which color-scheme we look at here. - body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize), - used_root_font_size: AtomicBool::new(false), - used_font_metrics: AtomicBool::new(false), - used_viewport_size: AtomicBool::new(false), - used_dynamic_viewport_size: AtomicBool::new(false), - environment: CssEnvironment, - } - } - - /// Get the relevant environment to resolve `env()` functions. - #[inline] - pub fn environment(&self) -> &CssEnvironment { - &self.environment - } - - /// Returns the computed line-height for the font in a given computed values instance. - /// - /// If you pass down an element, then the used line-height is returned. - pub fn calc_line_height( - &self, - line_height: &crate::values::computed::LineHeight, - vertical: bool, - font: &crate::properties::style_structs::Font, - element: Option<super::wrapper::GeckoElement>, - ) -> NonNegativeLength { - let pres_context = self.pres_context(); - let au = Au(unsafe { - bindings::Gecko_CalcLineHeight( - line_height, - pres_context.map_or(std::ptr::null(), |pc| pc), - vertical, - &**font, - element.map_or(std::ptr::null(), |e| e.0), - ) - }); - NonNegativeLength::new(au.to_f32_px()) - } - - /// Whether any animation name may be referenced from the style of any - /// element. - pub fn animation_name_may_be_referenced(&self, name: &KeyframesName) -> bool { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return false, - }; - - unsafe { - bindings::Gecko_AnimationNameMayBeReferencedFromStyle(pc, name.as_atom().as_ptr()) - } - } - - /// Returns the default computed values as a reference, in order to match - /// Servo. - pub fn default_computed_values(&self) -> &ComputedValues { - &self.default_values - } - - /// Returns the default computed values as an `Arc`. - pub fn default_computed_values_arc(&self) -> &Arc<ComputedValues> { - &self.default_values - } - - /// Get the font size of the root element (for rem) - pub fn root_font_size(&self) -> Length { - self.used_root_font_size.store(true, Ordering::Relaxed); - Length::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed))) - } - - /// Set the font size of the root element (for rem) - pub fn set_root_font_size(&self, size: Length) { - self.root_font_size - .store(size.px().to_bits(), Ordering::Relaxed) - } - - /// The quirks mode of the document. - pub fn quirks_mode(&self) -> QuirksMode { - self.document().mCompatMode.into() - } - - /// Sets the body text color for the "inherit color from body" quirk. - /// - /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk> - pub fn set_body_text_color(&self, color: AbsoluteColor) { - self.body_text_color.store( - convert_absolute_color_to_nscolor(&color) as usize, - Ordering::Relaxed, - ) - } - - /// Gets the base size given a generic font family and a language. - pub fn base_size_for_generic(&self, language: &Atom, generic: GenericFontFamily) -> Length { - unsafe { bindings::Gecko_GetBaseSize(self.document(), language.as_ptr(), generic) } - } - - /// Gets the size of the scrollbar in CSS pixels. - pub fn scrollbar_inline_size(&self) -> Length { - let pc = match self.pres_context() { - Some(pc) => pc, - // XXX: we could have a more reasonable default perhaps. - None => return Length::new(0.0), - }; - Length::new(unsafe { bindings::Gecko_GetScrollbarInlineSize(pc) }) - } - - /// Queries font metrics - pub fn query_font_metrics( - &self, - vertical: bool, - font: &crate::properties::style_structs::Font, - base_size: Length, - in_media_query: bool, - retrieve_math_scales: bool, - ) -> FontMetrics { - self.used_font_metrics.store(true, Ordering::Relaxed); - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Default::default(), - }; - let gecko_metrics = unsafe { - bindings::Gecko_GetFontMetrics( - pc, - vertical, - &**font, - base_size, - // we don't use the user font set in a media query - !in_media_query, - retrieve_math_scales, - ) - }; - FontMetrics { - x_height: Some(gecko_metrics.mXSize), - zero_advance_measure: if gecko_metrics.mChSize.px() >= 0. { - Some(gecko_metrics.mChSize) - } else { - None - }, - cap_height: if gecko_metrics.mCapHeight.px() >= 0. { - Some(gecko_metrics.mCapHeight) - } else { - None - }, - ic_width: if gecko_metrics.mIcWidth.px() >= 0. { - Some(gecko_metrics.mIcWidth) - } else { - None - }, - ascent: gecko_metrics.mAscent, - script_percent_scale_down: if gecko_metrics.mScriptPercentScaleDown > 0. { - Some(gecko_metrics.mScriptPercentScaleDown) - } else { - None - }, - script_script_percent_scale_down: if gecko_metrics.mScriptScriptPercentScaleDown > 0. { - Some(gecko_metrics.mScriptScriptPercentScaleDown) - } else { - None - }, - } - } - - /// Returns the body text color. - pub fn body_text_color(&self) -> AbsoluteColor { - convert_nscolor_to_absolute_color(self.body_text_color.load(Ordering::Relaxed) as u32) - } - - /// Gets the document pointer. - #[inline] - pub fn document(&self) -> &structs::Document { - unsafe { &*self.document } - } - - /// Gets the pres context associated with this document. - #[inline] - pub fn pres_context(&self) -> Option<&structs::nsPresContext> { - unsafe { - self.document() - .mPresShell - .as_ref()? - .mPresContext - .mRawPtr - .as_ref() - } - } - - /// Gets the preference stylesheet prefs for our document. - #[inline] - pub fn pref_sheet_prefs(&self) -> &structs::PreferenceSheet_Prefs { - unsafe { &*bindings::Gecko_GetPrefSheetPrefs(self.document()) } - } - - /// Recreates the default computed values. - pub fn reset_computed_values(&mut self) { - self.default_values = ComputedValues::default_values(self.document()); - } - - /// Rebuild all the cached data. - pub fn rebuild_cached_data(&mut self) { - self.reset_computed_values(); - self.used_root_font_size.store(false, Ordering::Relaxed); - self.used_font_metrics.store(false, Ordering::Relaxed); - self.used_viewport_size.store(false, Ordering::Relaxed); - self.used_dynamic_viewport_size - .store(false, Ordering::Relaxed); - } - - /// Returns whether we ever looked up the root font size of the Device. - pub fn used_root_font_size(&self) -> bool { - self.used_root_font_size.load(Ordering::Relaxed) - } - - /// Recreates all the temporary state that the `Device` stores. - /// - /// This includes the viewport override from `@viewport` rules, and also the - /// default computed values. - pub fn reset(&mut self) { - self.reset_computed_values(); - } - - /// Returns whether this document is in print preview. - pub fn is_print_preview(&self) -> bool { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return false, - }; - pc.mType == structs::nsPresContext_nsPresContextType_eContext_PrintPreview - } - - /// Returns the current media type of the device. - pub fn media_type(&self) -> MediaType { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return MediaType::screen(), - }; - - // Gecko allows emulating random media with mMediaEmulationData.mMedium. - let medium_to_use = if !pc.mMediaEmulationData.mMedium.mRawPtr.is_null() { - pc.mMediaEmulationData.mMedium.mRawPtr - } else { - pc.mMedium as *const structs::nsAtom as *mut _ - }; - - MediaType(CustomIdent(unsafe { Atom::from_raw(medium_to_use) })) - } - - // It may make sense to account for @page rule margins here somehow, however - // it's not clear how that'd work, see: - // https://github.com/w3c/csswg-drafts/issues/5437 - fn page_size_minus_default_margin(&self, pc: &structs::nsPresContext) -> Size2D<Au> { - debug_assert!(pc.mIsRootPaginatedDocument() != 0); - let area = &pc.mPageSize; - let margin = &pc.mDefaultPageMargin; - let width = area.width - margin.left - margin.right; - let height = area.height - margin.top - margin.bottom; - Size2D::new(Au(cmp::max(width, 0)), Au(cmp::max(height, 0))) - } - - /// Returns the current viewport size in app units. - pub fn au_viewport_size(&self) -> Size2D<Au> { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Size2D::new(Au(0), Au(0)), - }; - - if pc.mIsRootPaginatedDocument() != 0 { - return self.page_size_minus_default_margin(pc); - } - - let area = &pc.mVisibleArea; - Size2D::new(Au(area.width), Au(area.height)) - } - - /// Returns the current viewport size in app units, recording that it's been - /// used for viewport unit resolution. - pub fn au_viewport_size_for_viewport_unit_resolution( - &self, - variant: ViewportVariant, - ) -> Size2D<Au> { - self.used_viewport_size.store(true, Ordering::Relaxed); - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Size2D::new(Au(0), Au(0)), - }; - - if pc.mIsRootPaginatedDocument() != 0 { - return self.page_size_minus_default_margin(pc); - } - - match variant { - ViewportVariant::UADefault => { - let size = &pc.mSizeForViewportUnits; - Size2D::new(Au(size.width), Au(size.height)) - }, - ViewportVariant::Small => { - let size = &pc.mVisibleArea; - Size2D::new(Au(size.width), Au(size.height)) - }, - ViewportVariant::Large => { - let size = &pc.mVisibleArea; - // Looks like IntCoordTyped is treated as if it's u32 in Rust. - debug_assert!( - /* pc.mDynamicToolbarMaxHeight >=0 && */ - pc.mDynamicToolbarMaxHeight < i32::MAX as u32 - ); - Size2D::new( - Au(size.width), - Au(size.height + - pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel), - ) - }, - ViewportVariant::Dynamic => { - self.used_dynamic_viewport_size - .store(true, Ordering::Relaxed); - let size = &pc.mVisibleArea; - // Looks like IntCoordTyped is treated as if it's u32 in Rust. - debug_assert!( - /* pc.mDynamicToolbarHeight >=0 && */ - pc.mDynamicToolbarHeight < i32::MAX as u32 - ); - Size2D::new( - Au(size.width), - Au(size.height + - (pc.mDynamicToolbarMaxHeight - pc.mDynamicToolbarHeight) as i32 * - pc.mCurAppUnitsPerDevPixel), - ) - }, - } - } - - /// Returns whether we ever looked up the viewport size of the Device. - pub fn used_viewport_size(&self) -> bool { - self.used_viewport_size.load(Ordering::Relaxed) - } - - /// Returns whether we ever looked up the dynamic viewport size of the Device. - pub fn used_dynamic_viewport_size(&self) -> bool { - self.used_dynamic_viewport_size.load(Ordering::Relaxed) - } - - /// Returns whether font metrics have been queried. - pub fn used_font_metrics(&self) -> bool { - self.used_font_metrics.load(Ordering::Relaxed) - } - - /// Returns whether visited styles are enabled. - pub fn visited_styles_enabled(&self) -> bool { - unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) } - } - - /// Returns the number of app units per device pixel we're using currently. - pub fn app_units_per_device_pixel(&self) -> i32 { - match self.pres_context() { - Some(pc) => pc.mCurAppUnitsPerDevPixel, - None => AU_PER_PX, - } - } - - /// Returns the device pixel ratio. - pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return Scale::new(1.), - }; - - if pc.mMediaEmulationData.mDPPX > 0.0 { - return Scale::new(pc.mMediaEmulationData.mDPPX); - } - - let au_per_dpx = pc.mCurAppUnitsPerDevPixel as f32; - let au_per_px = AU_PER_PX as f32; - Scale::new(au_per_px / au_per_dpx) - } - - /// Returns whether document colors are enabled. - #[inline] - pub fn use_document_colors(&self) -> bool { - let doc = self.document(); - if doc.mIsBeingUsedAsImage() { - return true; - } - self.pref_sheet_prefs().mUseDocumentColors - } - - /// Computes a system color and returns it as an nscolor. - pub(crate) fn system_nscolor( - &self, - system_color: SystemColor, - color_scheme: &ColorScheme, - ) -> u32 { - unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) } - } - - /// Returns the default background color. - /// - /// This is only for forced-colors/high-contrast, so looking at light colors - /// is ok. - pub fn default_background_color(&self) -> AbsoluteColor { - let normal = ColorScheme::normal(); - convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvas, &normal)) - } - - /// Returns the default foreground color. - /// - /// See above for looking at light colors only. - pub fn default_color(&self) -> AbsoluteColor { - let normal = ColorScheme::normal(); - convert_nscolor_to_absolute_color(self.system_nscolor(SystemColor::Canvastext, &normal)) - } - - /// Returns the current effective text zoom. - #[inline] - fn text_zoom(&self) -> f32 { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return 1., - }; - pc.mTextZoom - } - - /// Applies text zoom to a font-size or line-height value (see nsStyleFont::ZoomText). - #[inline] - pub fn zoom_text(&self, size: Length) -> Length { - size.scale_by(self.text_zoom()) - } - - /// Un-apply text zoom. - #[inline] - pub fn unzoom_text(&self, size: Length) -> Length { - size.scale_by(1. / self.text_zoom()) - } - - /// Returns safe area insets - pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> { - let pc = match self.pres_context() { - Some(pc) => pc, - None => return SideOffsets2D::zero(), - }; - let mut top = 0.0; - let mut right = 0.0; - let mut bottom = 0.0; - let mut left = 0.0; - unsafe { - bindings::Gecko_GetSafeAreaInsets(pc, &mut top, &mut right, &mut bottom, &mut left) - }; - SideOffsets2D::new(top, right, bottom, left) - } - - /// Returns true if the given MIME type is supported - pub fn is_supported_mime_type(&self, mime_type: &str) -> bool { - unsafe { - bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32) - } - } - - /// Return whether the document is a chrome document. - /// - /// This check is consistent with how we enable chrome rules for chrome:// and resource:// - /// stylesheets (and thus chrome:// documents). - #[inline] - pub fn chrome_rules_enabled_for_document(&self) -> bool { - self.document().mChromeRulesEnabled() - } -} diff --git a/components/style/gecko/mod.rs b/components/style/gecko/mod.rs deleted file mode 100644 index c32ded14f33..00000000000 --- a/components/style/gecko/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* 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/. */ - -//! Gecko-specific style-system bits. - -#[macro_use] -mod non_ts_pseudo_class_list; - -pub mod arc_types; -pub mod conversions; -pub mod data; -pub mod media_features; -pub mod media_queries; -pub mod pseudo_element; -pub mod restyle_damage; -pub mod selector_parser; -pub mod snapshot; -pub mod snapshot_helpers; -pub mod traversal; -pub mod url; -pub mod values; -pub mod wrapper; diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs deleted file mode 100644 index e494082047f..00000000000 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ /dev/null @@ -1,100 +0,0 @@ -/* 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 contains a helper macro includes all supported non-tree-structural - * pseudo-classes. - * - * FIXME: Find a way to autogenerate this file. - * - * Expected usage is as follows: - * ``` - * macro_rules! pseudo_class_macro{ - * ([$(($css:expr, $name:ident, $gecko_type:tt, $state:tt, $flags:tt),)*]) => { - * // do stuff - * } - * } - * apply_non_ts_list!(pseudo_class_macro) - * ``` - * - * $gecko_type can be either "_" or an ident in Gecko's CSSPseudoClassType. - * $state can be either "_" or an expression of type ElementState. If present, - * the semantics are that the pseudo-class matches if any of the bits in - * $state are set on the element. - * $flags can be either "_" or an expression of type NonTSPseudoClassFlag, - * see selector_parser.rs for more details. - */ - -macro_rules! apply_non_ts_list { - ($apply_macro:ident) => { - $apply_macro! { - [ - ("-moz-table-border-nonzero", MozTableBorderNonzero, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-browser-frame", MozBrowserFrame, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-select-list-box", MozSelectListBox, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("link", Link, UNVISITED, _), - ("any-link", AnyLink, VISITED_OR_UNVISITED, _), - ("visited", Visited, VISITED, _), - ("active", Active, ACTIVE, _), - ("autofill", Autofill, AUTOFILL, _), - ("checked", Checked, CHECKED, _), - ("defined", Defined, DEFINED, _), - ("disabled", Disabled, DISABLED, _), - ("enabled", Enabled, ENABLED, _), - ("focus", Focus, FOCUS, _), - ("focus-within", FocusWithin, FOCUS_WITHIN, _), - ("focus-visible", FocusVisible, FOCUSRING, _), - ("hover", Hover, HOVER, _), - ("-moz-drag-over", MozDragOver, DRAGOVER, _), - ("target", Target, URLTARGET, _), - ("indeterminate", Indeterminate, INDETERMINATE, _), - ("-moz-inert", MozInert, INERT, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-devtools-highlighted", MozDevtoolsHighlighted, DEVTOOLS_HIGHLIGHTED, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, STYLEEDITOR_TRANSITIONING, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("fullscreen", Fullscreen, FULLSCREEN, _), - ("modal", Modal, MODAL, _), - ("-moz-topmost-modal", MozTopmostModal, TOPMOST_MODAL, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-broken", MozBroken, BROKEN, _), - ("-moz-loading", MozLoading, LOADING, _), - ("-moz-has-dir-attr", MozHasDirAttr, HAS_DIR_ATTR, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-dir-attr-ltr", MozDirAttrLTR, HAS_DIR_ATTR_LTR, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-dir-attr-rtl", MozDirAttrRTL, HAS_DIR_ATTR_RTL, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-dir-attr-like-auto", MozDirAttrLikeAuto, HAS_DIR_ATTR_LIKE_AUTO, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - - ("-moz-autofill-preview", MozAutofillPreview, AUTOFILL_PREVIEW, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-value-empty", MozValueEmpty, VALUE_EMPTY, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-revealed", MozRevealed, REVEALED, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - - ("-moz-math-increment-script-level", MozMathIncrementScriptLevel, INCREMENT_SCRIPT_LEVEL, _), - - ("required", Required, REQUIRED, _), - ("popover-open", PopoverOpen, POPOVER_OPEN, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("optional", Optional, OPTIONAL_, _), - ("valid", Valid, VALID, _), - ("invalid", Invalid, INVALID, _), - ("in-range", InRange, INRANGE, _), - ("out-of-range", OutOfRange, OUTOFRANGE, _), - ("default", Default, DEFAULT, _), - ("placeholder-shown", PlaceholderShown, PLACEHOLDER_SHOWN, _), - ("read-only", ReadOnly, READONLY, _), - ("read-write", ReadWrite, READWRITE, _), - ("user-valid", UserValid, USER_VALID, _), - ("user-invalid", UserInvalid, USER_INVALID, _), - ("-moz-meter-optimum", MozMeterOptimum, OPTIMUM, _), - ("-moz-meter-sub-optimum", MozMeterSubOptimum, SUB_OPTIMUM, _), - ("-moz-meter-sub-sub-optimum", MozMeterSubSubOptimum, SUB_SUB_OPTIMUM, _), - - ("-moz-first-node", MozFirstNode, _, _), - ("-moz-last-node", MozLastNode, _, _), - ("-moz-only-whitespace", MozOnlyWhitespace, _, _), - ("-moz-native-anonymous", MozNativeAnonymous, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-use-shadow-tree-root", MozUseShadowTreeRoot, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), - ("-moz-is-html", MozIsHTML, _, _), - ("-moz-placeholder", MozPlaceholder, _, _), - ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - ("-moz-window-inactive", MozWindowInactive, _, _), - ] - } - } -} diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs deleted file mode 100644 index d0c47d51a41..00000000000 --- a/components/style/gecko/pseudo_element.rs +++ /dev/null @@ -1,237 +0,0 @@ -/* 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/. */ - -//! Gecko's definition of a pseudo-element. -//! -//! Note that a few autogenerated bits of this live in -//! `pseudo_element_definition.mako.rs`. If you touch that file, you probably -//! need to update the checked-in files for Servo. - -use crate::gecko_bindings::structs::{self, PseudoStyleType}; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::{ComputedValues, PropertyFlags}; -use crate::selector_parser::{PseudoElementCascadeType, SelectorImpl}; -use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; -use crate::string_cache::Atom; -use crate::values::serialize_atom_identifier; -use crate::values::AtomIdent; -use cssparser::ToCss; -use static_prefs::pref; -use std::fmt; - -include!(concat!( - env!("OUT_DIR"), - "/gecko/pseudo_element_definition.rs" -)); - -impl ::selectors::parser::PseudoElement for PseudoElement { - type Impl = SelectorImpl; - - // ::slotted() should support all tree-abiding pseudo-elements, see - // https://drafts.csswg.org/css-scoping/#slotted-pseudo - // https://drafts.csswg.org/css-pseudo-4/#treelike - #[inline] - fn valid_after_slotted(&self) -> bool { - matches!( - *self, - PseudoElement::Before | - PseudoElement::After | - PseudoElement::Marker | - PseudoElement::Placeholder | - PseudoElement::FileSelectorButton - ) - } - - #[inline] - fn accepts_state_pseudo_classes(&self) -> bool { - self.supports_user_action_state() - } -} - -impl PseudoElement { - /// Returns the kind of cascade type that a given pseudo is going to use. - /// - /// In Gecko we only compute ::before and ::after eagerly. We save the rules - /// for anonymous boxes separately, so we resolve them as precomputed - /// pseudos. - /// - /// We resolve the others lazily, see `Servo_ResolvePseudoStyle`. - pub fn cascade_type(&self) -> PseudoElementCascadeType { - if self.is_eager() { - debug_assert!(!self.is_anon_box()); - return PseudoElementCascadeType::Eager; - } - - if self.is_precomputed() { - return PseudoElementCascadeType::Precomputed; - } - - PseudoElementCascadeType::Lazy - } - - /// Whether the pseudo-element should inherit from the default computed - /// values instead of from the parent element. - /// - /// This is not the common thing, but there are some pseudos (namely: - /// ::backdrop), that shouldn't inherit from the parent element. - pub fn inherits_from_default_values(&self) -> bool { - matches!(*self, PseudoElement::Backdrop) - } - - /// Gets the canonical index of this eagerly-cascaded pseudo-element. - #[inline] - pub fn eager_index(&self) -> usize { - EAGER_PSEUDOS - .iter() - .position(|p| p == self) - .expect("Not an eager pseudo") - } - - /// Creates a pseudo-element from an eager index. - #[inline] - pub fn from_eager_index(i: usize) -> Self { - EAGER_PSEUDOS[i].clone() - } - - /// Whether animations for the current pseudo element are stored in the - /// parent element. - #[inline] - pub fn animations_stored_in_parent(&self) -> bool { - matches!(*self, Self::Before | Self::After | Self::Marker) - } - - /// Whether the current pseudo element is ::before or ::after. - #[inline] - pub fn is_before_or_after(&self) -> bool { - self.is_before() || self.is_after() - } - - /// Whether this pseudo-element is the ::before pseudo. - #[inline] - pub fn is_before(&self) -> bool { - *self == PseudoElement::Before - } - - /// Whether this pseudo-element is the ::after pseudo. - #[inline] - pub fn is_after(&self) -> bool { - *self == PseudoElement::After - } - - /// Whether this pseudo-element is the ::marker pseudo. - #[inline] - pub fn is_marker(&self) -> bool { - *self == PseudoElement::Marker - } - - /// Whether this pseudo-element is the ::selection pseudo. - #[inline] - pub fn is_selection(&self) -> bool { - *self == PseudoElement::Selection - } - - /// Whether this pseudo-element is ::first-letter. - #[inline] - pub fn is_first_letter(&self) -> bool { - *self == PseudoElement::FirstLetter - } - - /// Whether this pseudo-element is ::first-line. - #[inline] - pub fn is_first_line(&self) -> bool { - *self == PseudoElement::FirstLine - } - - /// Whether this pseudo-element is the ::-moz-color-swatch pseudo. - #[inline] - pub fn is_color_swatch(&self) -> bool { - *self == PseudoElement::MozColorSwatch - } - - /// Whether this pseudo-element is lazily-cascaded. - #[inline] - pub fn is_lazy(&self) -> bool { - !self.is_eager() && !self.is_precomputed() - } - - /// The identifier of the highlight this pseudo-element represents. - pub fn highlight_name(&self) -> Option<&AtomIdent> { - match &*self { - PseudoElement::Highlight(name) => Some(&name), - _ => None, - } - } - - /// Whether this pseudo-element is the ::highlight pseudo. - pub fn is_highlight(&self) -> bool { - matches!(*self, PseudoElement::Highlight(_)) - } - - /// Whether this pseudo-element supports user action selectors. - pub fn supports_user_action_state(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE) != 0 - } - - /// Whether this pseudo-element is enabled for all content. - pub fn enabled_in_content(&self) -> bool { - if self.is_highlight() && !pref!("dom.customHighlightAPI.enabled") { - return false; - } - return self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME == 0; - } - - /// Whether this pseudo is enabled explicitly in UA sheets. - pub fn enabled_in_ua_sheets(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS) != 0 - } - - /// Whether this pseudo is enabled explicitly in chrome sheets. - pub fn enabled_in_chrome(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_CHROME) != 0 - } - - /// Whether this pseudo-element skips flex/grid container display-based - /// fixup. - #[inline] - pub fn skip_item_display_fixup(&self) -> bool { - (self.flags() & structs::CSS_PSEUDO_ELEMENT_IS_FLEX_OR_GRID_ITEM) == 0 - } - - /// Whether this pseudo-element is precomputed. - #[inline] - pub fn is_precomputed(&self) -> bool { - self.is_anon_box() && !self.is_tree_pseudo_element() - } - - /// Property flag that properties must have to apply to this pseudo-element. - #[inline] - pub fn property_restriction(&self) -> Option<PropertyFlags> { - Some(match *self { - PseudoElement::FirstLetter => PropertyFlags::APPLIES_TO_FIRST_LETTER, - PseudoElement::FirstLine => PropertyFlags::APPLIES_TO_FIRST_LINE, - PseudoElement::Placeholder => PropertyFlags::APPLIES_TO_PLACEHOLDER, - PseudoElement::Cue => PropertyFlags::APPLIES_TO_CUE, - PseudoElement::Marker if static_prefs::pref!("layout.css.marker.restricted") => { - PropertyFlags::APPLIES_TO_MARKER - }, - _ => return None, - }) - } - - /// Whether this pseudo-element should actually exist if it has - /// the given styles. - pub fn should_exist(&self, style: &ComputedValues) -> bool { - debug_assert!(self.is_eager()); - - if style.get_box().clone_display() == Display::None { - return false; - } - - if self.is_before_or_after() && style.ineffective_content_property() { - return false; - } - - true - } -} diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs deleted file mode 100644 index 73e7893c998..00000000000 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ /dev/null @@ -1,276 +0,0 @@ -/* 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/. */ - -/// Gecko's pseudo-element definition. -/// -/// We intentionally double-box legacy ::-moz-tree pseudo-elements to keep the -/// size of PseudoElement (and thus selector components) small. -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] -pub enum PseudoElement { - % for pseudo in PSEUDOS: - /// ${pseudo.value} - % if pseudo.is_tree_pseudo_element(): - ${pseudo.capitalized_pseudo()}(thin_vec::ThinVec<Atom>), - % elif pseudo.pseudo_ident == "highlight": - ${pseudo.capitalized_pseudo()}(AtomIdent), - % else: - ${pseudo.capitalized_pseudo()}, - % endif - % endfor - /// ::-webkit-* that we don't recognize - /// https://github.com/whatwg/compat/issues/103 - UnknownWebkit(Atom), -} - -/// Important: If you change this, you should also update Gecko's -/// nsCSSPseudoElements::IsEagerlyCascadedInServo. -<% EAGER_PSEUDOS = ["Before", "After", "FirstLine", "FirstLetter"] %> -<% TREE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_tree_pseudo_element()] %> -<% SIMPLE_PSEUDOS = [pseudo for pseudo in PSEUDOS if pseudo.is_simple_pseudo_element()] %> - -/// The number of eager pseudo-elements. -pub const EAGER_PSEUDO_COUNT: usize = ${len(EAGER_PSEUDOS)}; - -/// The number of non-functional pseudo-elements. -pub const SIMPLE_PSEUDO_COUNT: usize = ${len(SIMPLE_PSEUDOS)}; - -/// The number of tree pseudo-elements. -pub const TREE_PSEUDO_COUNT: usize = ${len(TREE_PSEUDOS)}; - -/// The number of all pseudo-elements. -pub const PSEUDO_COUNT: usize = ${len(PSEUDOS)}; - -/// The list of eager pseudos. -pub const EAGER_PSEUDOS: [PseudoElement; EAGER_PSEUDO_COUNT] = [ - % for eager_pseudo_name in EAGER_PSEUDOS: - PseudoElement::${eager_pseudo_name}, - % endfor -]; - -<%def name="pseudo_element_variant(pseudo, tree_arg='..')">\ -PseudoElement::${pseudo.capitalized_pseudo()}${"({})".format(tree_arg) if not pseudo.is_simple_pseudo_element() else ""}\ -</%def> - -impl PseudoElement { - /// Returns an index of the pseudo-element. - #[inline] - pub fn index(&self) -> usize { - match *self { - % for i, pseudo in enumerate(PSEUDOS): - ${pseudo_element_variant(pseudo)} => ${i}, - % endfor - PseudoElement::UnknownWebkit(..) => unreachable!(), - } - } - - /// Returns an array of `None` values. - /// - /// FIXME(emilio): Integer generics can't come soon enough. - pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] { - [ - ${",\n ".join(["None" for pseudo in PSEUDOS])} - ] - } - - /// Whether this pseudo-element is an anonymous box. - #[inline] - pub fn is_anon_box(&self) -> bool { - match *self { - % for pseudo in PSEUDOS: - % if pseudo.is_anon_box(): - ${pseudo_element_variant(pseudo)} => true, - % endif - % endfor - _ => false, - } - } - - /// Whether this pseudo-element is eagerly-cascaded. - #[inline] - pub fn is_eager(&self) -> bool { - matches!(*self, - ${" | ".join(map(lambda name: "PseudoElement::{}".format(name), EAGER_PSEUDOS))}) - } - - /// Whether this pseudo-element is tree pseudo-element. - #[inline] - pub fn is_tree_pseudo_element(&self) -> bool { - match *self { - % for pseudo in TREE_PSEUDOS: - ${pseudo_element_variant(pseudo)} => true, - % endfor - _ => false, - } - } - - /// Whether this pseudo-element is an unknown Webkit-prefixed pseudo-element. - #[inline] - pub fn is_unknown_webkit_pseudo_element(&self) -> bool { - matches!(*self, PseudoElement::UnknownWebkit(..)) - } - - /// Gets the flags associated to this pseudo-element, or 0 if it's an - /// anonymous box. - pub fn flags(&self) -> u32 { - match *self { - % for pseudo in PSEUDOS: - ${pseudo_element_variant(pseudo)} => - % if pseudo.is_tree_pseudo_element(): - structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME, - % elif pseudo.is_anon_box(): - structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS, - % else: - structs::SERVO_CSS_PSEUDO_ELEMENT_FLAGS_${pseudo.pseudo_ident}, - % endif - % endfor - PseudoElement::UnknownWebkit(..) => 0, - } - } - - /// Construct a pseudo-element from a `PseudoStyleType`. - #[inline] - pub fn from_pseudo_type(type_: PseudoStyleType) -> Option<Self> { - match type_ { - % for pseudo in PSEUDOS: - % if pseudo.is_simple_pseudo_element(): - PseudoStyleType::${pseudo.pseudo_ident} => { - Some(${pseudo_element_variant(pseudo)}) - }, - % endif - % endfor - _ => None, - } - } - - /// Construct a `PseudoStyleType` from a pseudo-element - #[inline] - pub fn pseudo_type(&self) -> PseudoStyleType { - match *self { - % for pseudo in PSEUDOS: - % if pseudo.is_tree_pseudo_element(): - PseudoElement::${pseudo.capitalized_pseudo()}(..) => PseudoStyleType::XULTree, - % elif pseudo.pseudo_ident == "highlight": - PseudoElement::${pseudo.capitalized_pseudo()}(..) => PseudoStyleType::${pseudo.pseudo_ident}, - % else: - PseudoElement::${pseudo.capitalized_pseudo()} => PseudoStyleType::${pseudo.pseudo_ident}, - % endif - % endfor - PseudoElement::UnknownWebkit(..) => unreachable!(), - } - } - - /// Get the argument list of a tree pseudo-element. - #[inline] - pub fn tree_pseudo_args(&self) -> Option<<&[Atom]> { - match *self { - % for pseudo in TREE_PSEUDOS: - PseudoElement::${pseudo.capitalized_pseudo()}(ref args) => Some(args), - % endfor - _ => None, - } - } - - /// Construct a tree pseudo-element from atom and args. - #[inline] - pub fn from_tree_pseudo_atom(atom: &Atom, args: Box<[Atom]>) -> Option<Self> { - % for pseudo in PSEUDOS: - % if pseudo.is_tree_pseudo_element(): - if atom == &atom!("${pseudo.value}") { - return Some(PseudoElement::${pseudo.capitalized_pseudo()}(args.into())); - } - % endif - % endfor - None - } - - /// Constructs a pseudo-element from a string of text. - /// - /// Returns `None` if the pseudo-element is not recognised. - #[inline] - pub fn from_slice(name: &str, allow_unkown_webkit: bool) -> Option<Self> { - // We don't need to support tree pseudos because functional - // pseudo-elements needs arguments, and thus should be created - // via other methods. - match_ignore_ascii_case! { name, - % for pseudo in SIMPLE_PSEUDOS: - "${pseudo.value[1:]}" => { - return Some(${pseudo_element_variant(pseudo)}) - }, - % endfor - // Alias some legacy prefixed pseudos to their standardized name at parse time: - "-moz-selection" => { - return Some(PseudoElement::Selection); - }, - "-moz-placeholder" => { - return Some(PseudoElement::Placeholder); - }, - "-moz-list-bullet" | "-moz-list-number" => { - return Some(PseudoElement::Marker); - }, - _ => { - if starts_with_ignore_ascii_case(name, "-moz-tree-") { - return PseudoElement::tree_pseudo_element(name, Default::default()) - } - const WEBKIT_PREFIX: &str = "-webkit-"; - if allow_unkown_webkit && starts_with_ignore_ascii_case(name, WEBKIT_PREFIX) { - let part = string_as_ascii_lowercase(&name[WEBKIT_PREFIX.len()..]); - return Some(PseudoElement::UnknownWebkit(part.into())); - } - } - } - - None - } - - /// Constructs a tree pseudo-element from the given name and arguments. - /// "name" must start with "-moz-tree-". - /// - /// Returns `None` if the pseudo-element is not recognized. - #[inline] - pub fn tree_pseudo_element(name: &str, args: thin_vec::ThinVec<Atom>) -> Option<Self> { - debug_assert!(starts_with_ignore_ascii_case(name, "-moz-tree-")); - let tree_part = &name[10..]; - % for pseudo in TREE_PSEUDOS: - if tree_part.eq_ignore_ascii_case("${pseudo.value[11:]}") { - return Some(${pseudo_element_variant(pseudo, "args")}); - } - % endfor - None - } -} - -impl ToCss for PseudoElement { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - dest.write_char(':')?; - match *self { - % for pseudo in (p for p in PSEUDOS if p.pseudo_ident != "highlight"): - ${pseudo_element_variant(pseudo)} => dest.write_str("${pseudo.value}")?, - % endfor - PseudoElement::Highlight(ref name) => { - dest.write_str(":highlight(")?; - serialize_atom_identifier(name, dest)?; - dest.write_char(')')?; - } - PseudoElement::UnknownWebkit(ref atom) => { - dest.write_str(":-webkit-")?; - serialize_atom_identifier(atom, dest)?; - } - } - if let Some(args) = self.tree_pseudo_args() { - if !args.is_empty() { - dest.write_char('(')?; - let mut iter = args.iter(); - if let Some(first) = iter.next() { - serialize_atom_identifier(&first, dest)?; - for item in iter { - dest.write_str(", ")?; - serialize_atom_identifier(item, dest)?; - } - } - dest.write_char(')')?; - } - } - Ok(()) - } -} diff --git a/components/style/gecko/regen_atoms.py b/components/style/gecko/regen_atoms.py deleted file mode 100755 index 61f2fc4c635..00000000000 --- a/components/style/gecko/regen_atoms.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python - -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at https://mozilla.org/MPL/2.0/. - -import re -import os -import sys - -from io import BytesIO - -GECKO_DIR = os.path.dirname(__file__.replace("\\", "/")) -sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties")) - -import build - - -# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`. -PATTERN = re.compile( - '^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)', - re.MULTILINE, -) -FILE = "include/nsGkAtomList.h" - - -def map_atom(ident): - if ident in { - "box", - "loop", - "match", - "mod", - "ref", - "self", - "type", - "use", - "where", - "in", - }: - return ident + "_" - return ident - - -class Atom: - def __init__(self, ident, value, hash, ty, atom_type): - self.ident = "nsGkAtoms_{}".format(ident) - self.original_ident = ident - self.value = value - self.hash = hash - # The Gecko type: "nsStaticAtom", "nsCSSPseudoElementStaticAtom", or - # "nsAnonBoxPseudoStaticAtom". - self.ty = ty - # The type of atom: "Atom", "PseudoElement", "NonInheritingAnonBox", - # or "InheritingAnonBox". - self.atom_type = atom_type - - if ( - self.is_pseudo_element() - or self.is_anon_box() - or self.is_tree_pseudo_element() - ): - self.pseudo_ident = (ident.split("_", 1))[1] - - if self.is_anon_box(): - assert self.is_inheriting_anon_box() or self.is_non_inheriting_anon_box() - - def type(self): - return self.ty - - def capitalized_pseudo(self): - return self.pseudo_ident[0].upper() + self.pseudo_ident[1:] - - def is_pseudo_element(self): - return self.atom_type == "PseudoElementAtom" - - def is_anon_box(self): - if self.is_tree_pseudo_element(): - return False - return self.is_non_inheriting_anon_box() or self.is_inheriting_anon_box() - - def is_non_inheriting_anon_box(self): - assert not self.is_tree_pseudo_element() - return self.atom_type == "NonInheritingAnonBoxAtom" - - def is_inheriting_anon_box(self): - if self.is_tree_pseudo_element(): - return False - return self.atom_type == "InheritingAnonBoxAtom" - - def is_tree_pseudo_element(self): - return self.value.startswith(":-moz-tree-") - - def is_simple_pseudo_element(self) -> bool: - return not (self.is_tree_pseudo_element() or self.pseudo_ident == "highlight") - - -def collect_atoms(objdir): - atoms = [] - path = os.path.abspath(os.path.join(objdir, FILE)) - print("cargo:rerun-if-changed={}".format(path)) - with open(path) as f: - content = f.read() - for result in PATTERN.finditer(content): - atoms.append( - Atom( - result.group(1), - result.group(2), - result.group(3), - result.group(4), - result.group(5), - ) - ) - return atoms - - -class FileAvoidWrite(BytesIO): - """File-like object that buffers output and only writes if content changed.""" - - def __init__(self, filename): - BytesIO.__init__(self) - self.name = filename - - def write(self, buf): - if isinstance(buf, str): - buf = buf.encode("utf-8") - BytesIO.write(self, buf) - - def close(self): - buf = self.getvalue() - BytesIO.close(self) - try: - with open(self.name, "rb") as f: - old_content = f.read() - if old_content == buf: - print("{} is not changed, skip".format(self.name)) - return - except IOError: - pass - with open(self.name, "wb") as f: - f.write(buf) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - if not self.closed: - self.close() - - -PRELUDE = """ -/* 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/. */ - -// Autogenerated file created by components/style/gecko/regen_atoms.py. -// DO NOT EDIT DIRECTLY -"""[ - 1: -] - -RULE_TEMPLATE = """ - ("{atom}") => {{{{ - #[allow(unsafe_code)] #[allow(unused_unsafe)] - unsafe {{ $crate::string_cache::Atom::from_index_unchecked({index}) }} - }}}}; -"""[ - 1: -] - -MACRO_TEMPLATE = """ -/// Returns a static atom by passing the literal string it represents. -#[macro_export] -macro_rules! atom {{ -{body}\ -}} -""" - - -def write_atom_macro(atoms, file_name): - with FileAvoidWrite(file_name) as f: - f.write(PRELUDE) - macro_rules = [ - RULE_TEMPLATE.format(atom=atom.value, name=atom.ident, index=i) - for (i, atom) in enumerate(atoms) - ] - f.write(MACRO_TEMPLATE.format(body="".join(macro_rules))) - - -def write_pseudo_elements(atoms, target_filename): - pseudos = [] - for atom in atoms: - if ( - atom.type() == "nsCSSPseudoElementStaticAtom" - or atom.type() == "nsCSSAnonBoxPseudoStaticAtom" - ): - pseudos.append(atom) - - pseudo_definition_template = os.path.join( - GECKO_DIR, "pseudo_element_definition.mako.rs" - ) - print("cargo:rerun-if-changed={}".format(pseudo_definition_template)) - contents = build.render(pseudo_definition_template, PSEUDOS=pseudos) - - with FileAvoidWrite(target_filename) as f: - f.write(contents) - - -def generate_atoms(dist, out): - atoms = collect_atoms(dist) - write_atom_macro(atoms, os.path.join(out, "atom_macro.rs")) - write_pseudo_elements(atoms, os.path.join(out, "pseudo_element_definition.rs")) - - -if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: {} dist out".format(sys.argv[0])) - exit(2) - generate_atoms(sys.argv[1], sys.argv[2]) diff --git a/components/style/gecko/restyle_damage.rs b/components/style/gecko/restyle_damage.rs deleted file mode 100644 index 4749daea183..00000000000 --- a/components/style/gecko/restyle_damage.rs +++ /dev/null @@ -1,121 +0,0 @@ -/* 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/. */ - -//! Gecko's restyle damage computation (aka change hints, aka `nsChangeHint`). - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::nsChangeHint; -use crate::matching::{StyleChange, StyleDifference}; -use crate::properties::ComputedValues; -use std::ops::{BitAnd, BitOr, BitOrAssign, Not}; - -/// The representation of Gecko's restyle damage is just a wrapper over -/// `nsChangeHint`. -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct GeckoRestyleDamage(nsChangeHint); - -impl GeckoRestyleDamage { - /// Trivially construct a new `GeckoRestyleDamage`. - #[inline] - pub fn new(raw: nsChangeHint) -> Self { - GeckoRestyleDamage(raw) - } - - /// Get the inner change hint for this damage. - #[inline] - pub fn as_change_hint(&self) -> nsChangeHint { - self.0 - } - - /// Get an empty change hint, that is (`nsChangeHint(0)`). - #[inline] - pub fn empty() -> Self { - GeckoRestyleDamage(nsChangeHint(0)) - } - - /// Returns whether this restyle damage represents the empty damage. - #[inline] - pub fn is_empty(&self) -> bool { - self.0 == nsChangeHint(0) - } - - /// Computes the `StyleDifference` (including the appropriate change hint) - /// given an old and a new style. - pub fn compute_style_difference( - old_style: &ComputedValues, - new_style: &ComputedValues, - ) -> StyleDifference { - let mut any_style_changed = false; - let mut reset_only = false; - let hint = unsafe { - bindings::Gecko_CalcStyleDifference( - old_style.as_gecko_computed_style(), - new_style.as_gecko_computed_style(), - &mut any_style_changed, - &mut reset_only, - ) - }; - if reset_only && !old_style.custom_properties_equal(new_style) { - // The Gecko_CalcStyleDifference call only checks the non-custom - // property structs, so we check the custom properties here. Since - // they generate no damage themselves, we can skip this check if we - // already know we had some inherited (regular) property - // differences. - any_style_changed = true; - reset_only = false; - } - let change = if any_style_changed { - StyleChange::Changed { reset_only } - } else { - StyleChange::Unchanged - }; - let damage = GeckoRestyleDamage(nsChangeHint(hint)); - StyleDifference { damage, change } - } - - /// Returns true if this restyle damage contains all the damage of |other|. - pub fn contains(self, other: Self) -> bool { - self & other == other - } - - /// Gets restyle damage to reconstruct the entire frame, subsuming all - /// other damage. - pub fn reconstruct() -> Self { - GeckoRestyleDamage(structs::nsChangeHint::nsChangeHint_ReconstructFrame) - } -} - -impl Default for GeckoRestyleDamage { - fn default() -> Self { - Self::empty() - } -} - -impl BitOr for GeckoRestyleDamage { - type Output = Self; - fn bitor(self, other: Self) -> Self { - GeckoRestyleDamage(self.0 | other.0) - } -} - -impl BitOrAssign for GeckoRestyleDamage { - fn bitor_assign(&mut self, other: Self) { - *self = *self | other; - } -} - -impl BitAnd for GeckoRestyleDamage { - type Output = Self; - fn bitand(self, other: Self) -> Self { - GeckoRestyleDamage(nsChangeHint((self.0).0 & (other.0).0)) - } -} - -impl Not for GeckoRestyleDamage { - type Output = Self; - fn not(self) -> Self { - GeckoRestyleDamage(nsChangeHint(!(self.0).0)) - } -} diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs deleted file mode 100644 index 52549f9ecbe..00000000000 --- a/components/style/gecko/selector_parser.rs +++ /dev/null @@ -1,497 +0,0 @@ -/* 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/. */ - -//! Gecko-specific bits for selector-parsing. - -use crate::computed_value_flags::ComputedValueFlags; -use crate::invalidation::element::document_state::InvalidationMatchingData; -use crate::properties::ComputedValues; -use crate::selector_parser::{Direction, HorizontalDirection, SelectorParser}; -use crate::str::starts_with_ignore_ascii_case; -use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; -use crate::values::{AtomIdent, AtomString}; -use cssparser::{BasicParseError, BasicParseErrorKind, Parser}; -use cssparser::{CowRcStr, SourceLocation, ToCss, Token}; -use dom::{DocumentState, ElementState}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss as ToCss_}; -use thin_vec::ThinVec; - -pub use crate::gecko::pseudo_element::{ - PseudoElement, EAGER_PSEUDOS, EAGER_PSEUDO_COUNT, PSEUDO_COUNT, -}; -pub use crate::gecko::snapshot::SnapshotMap; - -bitflags! { - // See NonTSPseudoClass::is_enabled_in() - struct NonTSPseudoClassFlag: u8 { - const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS = 1 << 0; - const PSEUDO_CLASS_ENABLED_IN_CHROME = 1 << 1; - const PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME = - NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS.bits | - NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME.bits; - } -} - -/// The type used to store the language argument to the `:lang` pseudo-class. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToShmem)] -#[css(comma)] -pub struct Lang(#[css(iterable)] pub ThinVec<AtomIdent>); - -macro_rules! pseudo_class_name { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - /// Our representation of a non tree-structural pseudo-class. - #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] - pub enum NonTSPseudoClass { - $( - #[doc = $css] - $name, - )* - /// The `:lang` pseudo-class. - Lang(Lang), - /// The `:dir` pseudo-class. - Dir(Direction), - /// The non-standard `:-moz-locale-dir` pseudo-class. - MozLocaleDir(Direction), - } - } -} -apply_non_ts_list!(pseudo_class_name); - -impl ToCss for NonTSPseudoClass { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - macro_rules! pseudo_class_serialize { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match *self { - $(NonTSPseudoClass::$name => concat!(":", $css),)* - NonTSPseudoClass::Lang(ref lang) => { - dest.write_str(":lang(")?; - lang.to_css(&mut CssWriter::new(dest))?; - return dest.write_char(')'); - }, - NonTSPseudoClass::MozLocaleDir(ref dir) => { - dest.write_str(":-moz-locale-dir(")?; - dir.to_css(&mut CssWriter::new(dest))?; - return dest.write_char(')') - }, - NonTSPseudoClass::Dir(ref dir) => { - dest.write_str(":dir(")?; - dir.to_css(&mut CssWriter::new(dest))?; - return dest.write_char(')') - }, - } - } - } - let ser = apply_non_ts_list!(pseudo_class_serialize); - dest.write_str(ser) - } -} - -impl NonTSPseudoClass { - /// Parses the name and returns a non-ts-pseudo-class if succeeds. - /// None otherwise. It doesn't check whether the pseudo-class is enabled - /// in a particular state. - pub fn parse_non_functional(name: &str) -> Option<Self> { - macro_rules! pseudo_class_parse { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match_ignore_ascii_case! { &name, - $($css => Some(NonTSPseudoClass::$name),)* - "-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen), - "-moz-read-only" => Some(NonTSPseudoClass::ReadOnly), - "-moz-read-write" => Some(NonTSPseudoClass::ReadWrite), - "-moz-focusring" => Some(NonTSPseudoClass::FocusVisible), - "-moz-ui-valid" => Some(NonTSPseudoClass::UserValid), - "-moz-ui-invalid" => Some(NonTSPseudoClass::UserInvalid), - "-webkit-autofill" => Some(NonTSPseudoClass::Autofill), - _ => None, - } - } - } - apply_non_ts_list!(pseudo_class_parse) - } - - /// Returns true if this pseudo-class has any of the given flags set. - fn has_any_flag(&self, flags: NonTSPseudoClassFlag) -> bool { - macro_rules! check_flag { - (_) => { - false - }; - ($flags:ident) => { - NonTSPseudoClassFlag::$flags.intersects(flags) - }; - } - macro_rules! pseudo_class_check_is_enabled_in { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match *self { - $(NonTSPseudoClass::$name => check_flag!($flags),)* - NonTSPseudoClass::MozLocaleDir(_) => check_flag!(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), - NonTSPseudoClass::Lang(_) | - NonTSPseudoClass::Dir(_) => false, - } - } - } - apply_non_ts_list!(pseudo_class_check_is_enabled_in) - } - - /// Returns whether the pseudo-class is enabled in content sheets. - #[inline] - fn is_enabled_in_content(&self) -> bool { - if matches!(*self, Self::PopoverOpen) { - return static_prefs::pref!("dom.element.popover.enabled"); - } - !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) - } - - /// Get the state flag associated with a pseudo-class, if any. - pub fn state_flag(&self) -> ElementState { - macro_rules! flag { - (_) => { - ElementState::empty() - }; - ($state:ident) => { - ElementState::$state - }; - } - macro_rules! pseudo_class_state { - ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { - match *self { - $(NonTSPseudoClass::$name => flag!($state),)* - NonTSPseudoClass::Dir(ref dir) => dir.element_state(), - NonTSPseudoClass::MozLocaleDir(..) | - NonTSPseudoClass::Lang(..) => ElementState::empty(), - } - } - } - apply_non_ts_list!(pseudo_class_state) - } - - /// Get the document state flag associated with a pseudo-class, if any. - pub fn document_state_flag(&self) -> DocumentState { - match *self { - NonTSPseudoClass::MozLocaleDir(ref dir) => match dir.as_horizontal_direction() { - Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, - Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, - None => DocumentState::empty(), - }, - NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, - NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, - _ => DocumentState::empty(), - } - } - - /// Returns true if the given pseudoclass should trigger style sharing cache - /// revalidation. - pub fn needs_cache_revalidation(&self) -> bool { - self.state_flag().is_empty() && - !matches!( - *self, - // :dir() depends on state only, but may have an empty - // state_flag for invalid arguments. - NonTSPseudoClass::Dir(_) | - // :-moz-is-html only depends on the state of the document and - // the namespace of the element; the former is invariant - // across all the elements involved and the latter is already - // checked for by our caching precondtions. - NonTSPseudoClass::MozIsHTML | - // We prevent style sharing for NAC. - NonTSPseudoClass::MozNativeAnonymous | - // :-moz-placeholder is parsed but never matches. - NonTSPseudoClass::MozPlaceholder | - // :-moz-lwtheme, :-moz-locale-dir and - // :-moz-window-inactive depend only on the state of the - // document, which is invariant across all the elements - // involved in a given style cache. - NonTSPseudoClass::MozLWTheme | - NonTSPseudoClass::MozLocaleDir(_) | - NonTSPseudoClass::MozWindowInactive - ) - } -} - -impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { - type Impl = SelectorImpl; - - #[inline] - fn is_active_or_hover(&self) -> bool { - matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover) - } - - /// We intentionally skip the link-related ones. - #[inline] - fn is_user_action_state(&self) -> bool { - matches!( - *self, - NonTSPseudoClass::Hover | NonTSPseudoClass::Active | NonTSPseudoClass::Focus - ) - } -} - -/// The dummy struct we use to implement our selector parsing. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct SelectorImpl; - -/// A set of extra data to carry along with the matching context, either for -/// selector-matching or invalidation. -#[derive(Default)] -pub struct ExtraMatchingData<'a> { - /// The invalidation data to invalidate doc-state pseudo-classes correctly. - pub invalidation_data: InvalidationMatchingData, - - /// The invalidation bits from matching container queries. These are here - /// just for convenience mostly. - pub cascade_input_flags: ComputedValueFlags, - - /// The style of the originating element in order to evaluate @container - /// size queries affecting pseudo-elements. - pub originating_element_style: Option<&'a ComputedValues>, -} - -impl ::selectors::SelectorImpl for SelectorImpl { - type ExtraMatchingData<'a> = ExtraMatchingData<'a>; - type AttrValue = AtomString; - type Identifier = AtomIdent; - type LocalName = AtomIdent; - type NamespacePrefix = AtomIdent; - type NamespaceUrl = Namespace; - type BorrowedNamespaceUrl = WeakNamespace; - type BorrowedLocalName = WeakAtom; - - type PseudoElement = PseudoElement; - type NonTSPseudoClass = NonTSPseudoClass; - - fn should_collect_attr_hash(name: &AtomIdent) -> bool { - !crate::bloom::is_attr_name_excluded_from_filter(name) - } -} - -impl<'a> SelectorParser<'a> { - fn is_pseudo_class_enabled(&self, pseudo_class: &NonTSPseudoClass) -> bool { - if pseudo_class.is_enabled_in_content() { - return true; - } - - if self.in_user_agent_stylesheet() && - pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS) - { - return true; - } - - if self.chrome_rules_enabled() && - pseudo_class.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_CHROME) - { - return true; - } - - return false; - } - - fn is_pseudo_element_enabled(&self, pseudo_element: &PseudoElement) -> bool { - if pseudo_element.enabled_in_content() { - return true; - } - - if self.in_user_agent_stylesheet() && pseudo_element.enabled_in_ua_sheets() { - return true; - } - - if self.chrome_rules_enabled() && pseudo_element.enabled_in_chrome() { - return true; - } - - return false; - } -} - -impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { - type Impl = SelectorImpl; - type Error = StyleParseErrorKind<'i>; - - fn parse_parent_selector(&self) -> bool { - static_prefs::pref!("layout.css.nesting.enabled") - } - - #[inline] - fn parse_slotted(&self) -> bool { - true - } - - #[inline] - fn parse_host(&self) -> bool { - true - } - - #[inline] - fn parse_nth_child_of(&self) -> bool { - true - } - - #[inline] - fn parse_is_and_where(&self) -> bool { - true - } - - #[inline] - fn parse_has(&self) -> bool { - static_prefs::pref!("layout.css.has-selector.enabled") - } - - #[inline] - fn parse_part(&self) -> bool { - true - } - - #[inline] - fn is_is_alias(&self, function: &str) -> bool { - function.eq_ignore_ascii_case("-moz-any") - } - - #[inline] - fn allow_forgiving_selectors(&self) -> bool { - !self.for_supports_rule - } - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<NonTSPseudoClass, ParseError<'i>> { - if let Some(pseudo_class) = NonTSPseudoClass::parse_non_functional(&name) { - if self.is_pseudo_class_enabled(&pseudo_class) { - return Ok(pseudo_class); - } - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut Parser<'i, 't>, - ) -> Result<NonTSPseudoClass, ParseError<'i>> { - let pseudo_class = match_ignore_ascii_case! { &name, - "lang" => { - let result = parser.parse_comma_separated(|input| { - Ok(AtomIdent::from(input.expect_ident_or_string()?.as_ref())) - })?; - if result.is_empty() { - return Err(parser.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - NonTSPseudoClass::Lang(Lang(result.into())) - }, - "-moz-locale-dir" => { - NonTSPseudoClass::MozLocaleDir(Direction::parse(parser)?) - }, - "dir" => { - NonTSPseudoClass::Dir(Direction::parse(parser)?) - }, - _ => return Err(parser.new_custom_error( - SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name.clone()) - )) - }; - if self.is_pseudo_class_enabled(&pseudo_class) { - Ok(pseudo_class) - } else { - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<PseudoElement, ParseError<'i>> { - let allow_unkown_webkit = !self.for_supports_rule; - if let Some(pseudo) = PseudoElement::from_slice(&name, allow_unkown_webkit) { - if self.is_pseudo_element_enabled(&pseudo) { - return Ok(pseudo); - } - } - - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut Parser<'i, 't>, - ) -> Result<PseudoElement, ParseError<'i>> { - if starts_with_ignore_ascii_case(&name, "-moz-tree-") { - // Tree pseudo-elements can have zero or more arguments, separated - // by either comma or space. - let mut args = ThinVec::new(); - loop { - let location = parser.current_source_location(); - match parser.next() { - Ok(&Token::Ident(ref ident)) => args.push(Atom::from(ident.as_ref())), - Ok(&Token::Comma) => {}, - Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), - Err(BasicParseError { - kind: BasicParseErrorKind::EndOfInput, - .. - }) => break, - _ => unreachable!("Parser::next() shouldn't return any other error"), - } - } - if let Some(pseudo) = PseudoElement::tree_pseudo_element(&name, args) { - if self.is_pseudo_element_enabled(&pseudo) { - return Ok(pseudo); - } - } - } else if name.eq_ignore_ascii_case("highlight") { - let pseudo = PseudoElement::Highlight(AtomIdent::from(parser.expect_ident()?.as_ref())); - if self.is_pseudo_element_enabled(&pseudo) { - return Ok(pseudo); - } - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<Namespace> { - self.namespaces.default.clone() - } - - fn namespace_for_prefix(&self, prefix: &AtomIdent) -> Option<Namespace> { - self.namespaces.prefixes.get(prefix).cloned() - } -} - -impl SelectorImpl { - /// A helper to traverse each eagerly cascaded pseudo-element, executing - /// `fun` on it. - #[inline] - pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F) - where - F: FnMut(PseudoElement), - { - for pseudo in &EAGER_PSEUDOS { - fun(pseudo.clone()) - } - } -} - -// Selector and component sizes are important for matching performance. -size_of_test!(selectors::parser::Selector<SelectorImpl>, 8); -size_of_test!(selectors::parser::Component<SelectorImpl>, 24); -size_of_test!(PseudoElement, 16); -size_of_test!(NonTSPseudoClass, 16); diff --git a/components/style/gecko/snapshot.rs b/components/style/gecko/snapshot.rs deleted file mode 100644 index 372e7fdb7f5..00000000000 --- a/components/style/gecko/snapshot.rs +++ /dev/null @@ -1,174 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A gecko snapshot, that stores the element attributes and state before they -//! change in order to properly calculate restyle hints. - -use crate::dom::TElement; -use crate::gecko::snapshot_helpers; -use crate::gecko::wrapper::GeckoElement; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::ServoElementSnapshot; -use crate::gecko_bindings::structs::ServoElementSnapshotFlags as Flags; -use crate::gecko_bindings::structs::ServoElementSnapshotTable; -use crate::invalidation::element::element_wrapper::ElementSnapshot; -use crate::selector_parser::AttrValue; -use crate::string_cache::{Atom, Namespace}; -use crate::values::{AtomIdent, AtomString}; -use crate::LocalName; -use crate::WeakAtom; -use dom::ElementState; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; - -/// A snapshot of a Gecko element. -pub type GeckoElementSnapshot = ServoElementSnapshot; - -/// A map from elements to snapshots for Gecko's style back-end. -pub type SnapshotMap = ServoElementSnapshotTable; - -impl SnapshotMap { - /// Gets the snapshot for this element, if any. - /// - /// FIXME(emilio): The transmute() business we do here is kind of nasty, but - /// it's a consequence of the map being a OpaqueNode -> Snapshot table in - /// Servo and an Element -> Snapshot table in Gecko. - /// - /// We should be able to make this a more type-safe with type annotations by - /// making SnapshotMap a trait and moving the implementations outside, but - /// that's a pain because it implies parameterizing SharedStyleContext. - pub fn get<E: TElement>(&self, element: &E) -> Option<&GeckoElementSnapshot> { - debug_assert!(element.has_snapshot()); - - unsafe { - let element = ::std::mem::transmute::<&E, &GeckoElement>(element); - bindings::Gecko_GetElementSnapshot(self, element.0).as_ref() - } - } -} - -impl GeckoElementSnapshot { - #[inline] - fn has_any(&self, flags: Flags) -> bool { - (self.mContains as u8 & flags as u8) != 0 - } - - /// Returns true if the snapshot has stored state for pseudo-classes - /// that depend on things other than `ElementState`. - #[inline] - pub fn has_other_pseudo_class_state(&self) -> bool { - self.has_any(Flags::OtherPseudoClassState) - } - - /// Returns true if the snapshot recorded an id change. - #[inline] - pub fn id_changed(&self) -> bool { - self.mIdAttributeChanged() - } - - /// Returns true if the snapshot recorded a class attribute change. - #[inline] - pub fn class_changed(&self) -> bool { - self.mClassAttributeChanged() - } - - /// Executes the callback once for each attribute that changed. - #[inline] - pub fn each_attr_changed<F>(&self, mut callback: F) - where - F: FnMut(&AtomIdent), - { - for attr in self.mChangedAttrNames.iter() { - unsafe { AtomIdent::with(attr.mRawPtr, &mut callback) } - } - } - - /// selectors::Element::attr_matches - pub fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, - ) -> bool { - snapshot_helpers::attr_matches(self.mAttrs.iter(), ns, local_name, operation) - } -} - -impl ElementSnapshot for GeckoElementSnapshot { - fn debug_list_attributes(&self) -> String { - use nsstring::nsCString; - let mut string = nsCString::new(); - unsafe { - bindings::Gecko_Snapshot_DebugListAttributes(self, &mut string); - } - String::from_utf8_lossy(&*string).into_owned() - } - - fn state(&self) -> Option<ElementState> { - if self.has_any(Flags::State) { - Some(ElementState::from_bits_truncate(self.mState)) - } else { - None - } - } - - #[inline] - fn has_attrs(&self) -> bool { - self.has_any(Flags::Attributes) - } - - #[inline] - fn id_attr(&self) -> Option<&WeakAtom> { - if !self.has_any(Flags::Id) { - return None; - } - - snapshot_helpers::get_id(&*self.mAttrs) - } - - #[inline] - fn is_part(&self, name: &AtomIdent) -> bool { - let attr = match snapshot_helpers::find_attr(&*self.mAttrs, &atom!("part")) { - Some(attr) => attr, - None => return false, - }; - - snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr) - } - - #[inline] - fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> { - snapshot_helpers::imported_part(&*self.mAttrs, name) - } - - #[inline] - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - if !self.has_any(Flags::MaybeClass) { - return false; - } - - snapshot_helpers::has_class_or_part(name, case_sensitivity, &self.mClass) - } - - #[inline] - fn each_class<F>(&self, callback: F) - where - F: FnMut(&AtomIdent), - { - if !self.has_any(Flags::MaybeClass) { - return; - } - - snapshot_helpers::each_class_or_part(&self.mClass, callback) - } - - #[inline] - fn lang_attr(&self) -> Option<AtomString> { - let ptr = unsafe { bindings::Gecko_SnapshotLangValue(self) }; - if ptr.is_null() { - None - } else { - Some(AtomString(unsafe { Atom::from_addrefed(ptr) })) - } - } -} diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs deleted file mode 100644 index 818e767e0e9..00000000000 --- a/components/style/gecko/snapshot_helpers.rs +++ /dev/null @@ -1,309 +0,0 @@ -/* 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/. */ - -//! Element an snapshot common logic. - -use crate::dom::TElement; -use crate::gecko::wrapper::namespace_id_to_atom; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{self, nsAtom}; -use crate::invalidation::element::element_wrapper::ElementSnapshot; -use crate::selector_parser::{AttrValue, SnapshotMap}; -use crate::string_cache::WeakAtom; -use crate::values::AtomIdent; -use crate::{Atom, CaseSensitivityExt, LocalName, Namespace}; -use selectors::attr::{CaseSensitivity, NamespaceConstraint, AttrSelectorOperation, AttrSelectorOperator}; -use smallvec::SmallVec; - -/// A function that, given an element of type `T`, allows you to get a single -/// class or a class list. -enum Class<'a> { - None, - One(*const nsAtom), - More(&'a [structs::RefPtr<nsAtom>]), -} - -#[inline(always)] -fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType { - (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType -} - -#[inline(always)] -unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T { - (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T -} - -#[inline(always)] -unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class { - debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr)); - let base_type = base_type(attr); - if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase { - return Class::One(ptr::<nsAtom>(attr)); - } - if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase { - let container = ptr::<structs::MiscContainer>(attr); - debug_assert_eq!( - (*container).mType, - structs::nsAttrValue_ValueType_eAtomArray - ); - // NOTE: Bindgen doesn't deal with AutoTArray, so cast it below. - let attr_array: *mut _ = *(*container) - .__bindgen_anon_1 - .mValue - .as_ref() - .__bindgen_anon_1 - .mAtomArray - .as_ref(); - let array = - (*attr_array).mArray.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>; - return Class::More(&**array); - } - debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase); - Class::None -} - -#[inline(always)] -unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom { - debug_assert_eq!( - base_type(attr), - structs::nsAttrValue_ValueBaseType_eAtomBase - ); - WeakAtom::new(ptr::<nsAtom>(attr)) -} - -impl structs::nsAttrName { - #[inline] - fn is_nodeinfo(&self) -> bool { - self.mBits & 1 != 0 - } - - #[inline] - unsafe fn as_nodeinfo(&self) -> &structs::NodeInfo { - debug_assert!(self.is_nodeinfo()); - &*((self.mBits & !1) as *const structs::NodeInfo) - } - - #[inline] - fn namespace_id(&self) -> i32 { - if !self.is_nodeinfo() { - return structs::kNameSpaceID_None; - } - unsafe { self.as_nodeinfo() }.mInner.mNamespaceID - } - - /// Returns the attribute name as an atom pointer. - #[inline] - pub fn name(&self) -> *const nsAtom { - if self.is_nodeinfo() { - unsafe { self.as_nodeinfo() }.mInner.mName - } else { - self.mBits as *const nsAtom - } - } -} - -/// Find an attribute value with a given name and no namespace. -#[inline(always)] -pub fn find_attr<'a>( - attrs: &'a [structs::AttrArray_InternalAttr], - name: &Atom, -) -> Option<&'a structs::nsAttrValue> { - attrs - .iter() - .find(|attr| attr.mName.mBits == name.as_ptr() as usize) - .map(|attr| &attr.mValue) -} - -/// Finds the id attribute from a list of attributes. -#[inline(always)] -pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> { - Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) }) -} - -#[inline(always)] -pub(super) fn each_exported_part( - attrs: &[structs::AttrArray_InternalAttr], - name: &AtomIdent, - mut callback: impl FnMut(&AtomIdent), -) { - let attr = match find_attr(attrs, &atom!("exportparts")) { - Some(attr) => attr, - None => return, - }; - let mut length = 0; - let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) }; - if atoms.is_null() { - return; - } - - unsafe { - for atom in std::slice::from_raw_parts(atoms, length) { - AtomIdent::with(*atom, &mut callback) - } - } -} - -#[inline(always)] -pub(super) fn imported_part( - attrs: &[structs::AttrArray_InternalAttr], - name: &AtomIdent, -) -> Option<AtomIdent> { - let attr = find_attr(attrs, &atom!("exportparts"))?; - let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) }; - if atom.is_null() { - return None; - } - Some(AtomIdent(unsafe { Atom::from_raw(atom) })) -} - -/// Given a class or part name, a case sensitivity, and an array of attributes, -/// returns whether the attribute has that name. -#[inline(always)] -pub fn has_class_or_part( - name: &AtomIdent, - case_sensitivity: CaseSensitivity, - attr: &structs::nsAttrValue, -) -> bool { - match unsafe { get_class_or_part_from_attr(attr) } { - Class::None => false, - Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) }, - Class::More(atoms) => match case_sensitivity { - CaseSensitivity::CaseSensitive => { - let name_ptr = name.as_ptr(); - atoms.iter().any(|atom| atom.mRawPtr == name_ptr) - }, - CaseSensitivity::AsciiCaseInsensitive => unsafe { - atoms - .iter() - .any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name)) - }, - }, - } -} - -/// Given an item, a callback, and a getter, execute `callback` for each class -/// or part name this `item` has. -#[inline(always)] -pub fn each_class_or_part<F>(attr: &structs::nsAttrValue, mut callback: F) -where - F: FnMut(&AtomIdent), -{ - unsafe { - match get_class_or_part_from_attr(attr) { - Class::None => {}, - Class::One(atom) => AtomIdent::with(atom, callback), - Class::More(atoms) => { - for atom in atoms { - AtomIdent::with(atom.mRawPtr, &mut callback) - } - }, - } - } -} - -/// Returns a list of classes that were either added to or removed from the -/// element since the snapshot. -pub fn classes_changed<E: TElement>(element: &E, snapshots: &SnapshotMap) -> SmallVec<[Atom; 8]> { - debug_assert!(element.has_snapshot(), "Why bothering?"); - let snapshot = snapshots.get(element).expect("has_snapshot lied"); - if !snapshot.class_changed() { - return SmallVec::new(); - } - - let mut classes_changed = SmallVec::<[Atom; 8]>::new(); - snapshot.each_class(|c| { - if !element.has_class(c, CaseSensitivity::CaseSensitive) { - classes_changed.push(c.0.clone()); - } - }); - element.each_class(|c| { - if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) { - classes_changed.push(c.0.clone()); - } - }); - - classes_changed -} - -/// Returns whether a given attribute selector matches given the internal attrs. -pub(crate) fn attr_matches<'a>( - iter: impl Iterator<Item = &'a structs::AttrArray_InternalAttr>, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, -) -> bool { - let name_ptr = local_name.as_ptr(); - for attr in iter { - if attr.mName.name() != name_ptr { - continue; - } - - let ns_matches = match *ns { - NamespaceConstraint::Any => true, - NamespaceConstraint::Specific(ns) => { - if *ns == ns!() { - !attr.mName.is_nodeinfo() - } else { - ns.as_ptr() == unsafe { namespace_id_to_atom(attr.mName.namespace_id()) } - } - }, - }; - - if !ns_matches { - continue; - } - - let (operator, case_sensitivity, value) = match *operation { - AttrSelectorOperation::Exists => return true, - AttrSelectorOperation::WithValue { - operator, - case_sensitivity, - value, - } => (operator, case_sensitivity, value), - }; - let ignore_case = match case_sensitivity { - CaseSensitivity::CaseSensitive => false, - CaseSensitivity::AsciiCaseInsensitive => true, - }; - let value = value.as_ptr(); - let matches = unsafe { - match operator { - AttrSelectorOperator::Equal => bindings::Gecko_AttrEquals( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Includes => bindings::Gecko_AttrIncludes( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::DashMatch => bindings::Gecko_AttrDashEquals( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Prefix => bindings::Gecko_AttrHasPrefix( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Suffix => bindings::Gecko_AttrHasSuffix( - &attr.mValue, - value, - ignore_case, - ), - AttrSelectorOperator::Substring => bindings::Gecko_AttrHasSubstring( - &attr.mValue, - value, - ignore_case, - ), - } - }; - if matches || *ns != NamespaceConstraint::Any { - return matches; - } - } - false -} diff --git a/components/style/gecko/traversal.rs b/components/style/gecko/traversal.rs deleted file mode 100644 index 71d1a2f949b..00000000000 --- a/components/style/gecko/traversal.rs +++ /dev/null @@ -1,53 +0,0 @@ -/* 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/. */ - -//! Gecko-specific bits for the styling DOM traversal. - -use crate::context::{SharedStyleContext, StyleContext}; -use crate::dom::{TElement, TNode}; -use crate::gecko::wrapper::{GeckoElement, GeckoNode}; -use crate::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData}; - -/// This is the simple struct that Gecko uses to encapsulate a DOM traversal for -/// styling. -pub struct RecalcStyleOnly<'a> { - shared: SharedStyleContext<'a>, -} - -impl<'a> RecalcStyleOnly<'a> { - /// Create a `RecalcStyleOnly` traversal from a `SharedStyleContext`. - pub fn new(shared: SharedStyleContext<'a>) -> Self { - RecalcStyleOnly { shared: shared } - } -} - -impl<'recalc, 'le> DomTraversal<GeckoElement<'le>> for RecalcStyleOnly<'recalc> { - fn process_preorder<F>( - &self, - traversal_data: &PerLevelTraversalData, - context: &mut StyleContext<GeckoElement<'le>>, - node: GeckoNode<'le>, - note_child: F, - ) where - F: FnMut(GeckoNode<'le>), - { - if let Some(el) = node.as_element() { - let mut data = unsafe { el.ensure_data() }; - recalc_style_at(self, traversal_data, context, el, &mut data, note_child); - } - } - - fn process_postorder(&self, _: &mut StyleContext<GeckoElement<'le>>, _: GeckoNode<'le>) { - unreachable!(); - } - - /// We don't use the post-order traversal for anything. - fn needs_postorder_traversal() -> bool { - false - } - - fn shared_context(&self) -> &SharedStyleContext { - &self.shared - } -} diff --git a/components/style/gecko/url.rs b/components/style/gecko/url.rs deleted file mode 100644 index 8cf4aa51c0a..00000000000 --- a/components/style/gecko/url.rs +++ /dev/null @@ -1,383 +0,0 @@ -/* 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/. */ - -//! Common handling for the specified value CSS url() values. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs; -use crate::parser::{Parse, ParserContext}; -use crate::stylesheets::{CorsMode, UrlExtraData}; -use crate::values::computed::{Context, ToComputedValue}; -use cssparser::Parser; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use nsstring::nsCString; -use servo_arc::Arc; -use std::collections::HashMap; -use std::fmt::{self, Write}; -use std::mem::ManuallyDrop; -use std::sync::RwLock; -use style_traits::{CssWriter, ParseError, ToCss}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -/// A CSS url() value for gecko. -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -#[css(function = "url")] -#[repr(C)] -pub struct CssUrl(pub Arc<CssUrlData>); - -/// Data shared between CssUrls. -/// -/// cbindgen:derive-eq=false -/// cbindgen:derive-neq=false -#[derive(Debug, SpecifiedValueInfo, ToCss, ToShmem)] -#[repr(C)] -pub struct CssUrlData { - /// The URL in unresolved string form. - serialization: crate::OwnedStr, - - /// The URL extra data. - #[css(skip)] - pub extra_data: UrlExtraData, - - /// The CORS mode that will be used for the load. - #[css(skip)] - cors_mode: CorsMode, - - /// Data to trigger a load from Gecko. This is mutable in C++. - /// - /// TODO(emilio): Maybe we can eagerly resolve URLs and make this immutable? - #[css(skip)] - load_data: LoadDataSource, -} - -impl PartialEq for CssUrlData { - fn eq(&self, other: &Self) -> bool { - self.serialization == other.serialization && - self.extra_data == other.extra_data && - self.cors_mode == other.cors_mode - } -} - -impl CssUrl { - fn parse_with_cors_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - ) -> Result<Self, ParseError<'i>> { - let url = input.expect_url()?; - Ok(Self::parse_from_string( - url.as_ref().to_owned(), - context, - cors_mode, - )) - } - - /// Parse a URL from a string value that is a valid CSS token for a URL. - pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self { - CssUrl(Arc::new(CssUrlData { - serialization: url.into(), - extra_data: context.url_data.clone(), - cors_mode, - load_data: LoadDataSource::Owned(LoadData::default()), - })) - } - - /// Returns true if the URL is definitely invalid. We don't eagerly resolve - /// URLs in gecko, so we just return false here. - /// use its |resolved| status. - pub fn is_invalid(&self) -> bool { - false - } - - /// Returns true if this URL looks like a fragment. - /// See https://drafts.csswg.org/css-values/#local-urls - #[inline] - pub fn is_fragment(&self) -> bool { - self.0.is_fragment() - } - - /// Return the unresolved url as string, or the empty string if it's - /// invalid. - #[inline] - pub fn as_str(&self) -> &str { - self.0.as_str() - } -} - -impl CssUrlData { - /// Returns true if this URL looks like a fragment. - /// See https://drafts.csswg.org/css-values/#local-urls - pub fn is_fragment(&self) -> bool { - self.as_str() - .as_bytes() - .iter() - .next() - .map_or(false, |b| *b == b'#') - } - - /// Return the unresolved url as string, or the empty string if it's - /// invalid. - pub fn as_str(&self) -> &str { - &*self.serialization - } -} - -impl Parse for CssUrl { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_cors_mode(context, input, CorsMode::None) - } -} - -impl Eq for CssUrl {} - -impl MallocSizeOf for CssUrl { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - // XXX: measure `serialization` once bug 1397971 lands - - // We ignore `extra_data`, because RefPtr is tricky, and there aren't - // many of them in practise (sharing is common). - - 0 - } -} - -/// A key type for LOAD_DATA_TABLE. -#[derive(Eq, Hash, PartialEq)] -struct LoadDataKey(*const LoadDataSource); - -unsafe impl Sync for LoadDataKey {} -unsafe impl Send for LoadDataKey {} - -bitflags! { - /// Various bits of mutable state that are kept for image loads. - #[repr(C)] - pub struct LoadDataFlags: u8 { - /// Whether we tried to resolve the uri at least once. - const TRIED_TO_RESOLVE_URI = 1 << 0; - /// Whether we tried to resolve the image at least once. - const TRIED_TO_RESOLVE_IMAGE = 1 << 1; - } -} - -/// This is usable and movable from multiple threads just fine, as long as it's -/// not cloned (it is not clonable), and the methods that mutate it run only on -/// the main thread (when all the other threads we care about are paused). -unsafe impl Sync for LoadData {} -unsafe impl Send for LoadData {} - -/// The load data for a given URL. This is mutable from C++, and shouldn't be -/// accessed from rust for anything. -#[repr(C)] -#[derive(Debug)] -pub struct LoadData { - /// A strong reference to the imgRequestProxy, if any, that should be - /// released on drop. - /// - /// These are raw pointers because they are not safe to reference-count off - /// the main thread. - resolved_image: *mut structs::imgRequestProxy, - /// A strong reference to the resolved URI of this image. - resolved_uri: *mut structs::nsIURI, - /// A few flags that are set when resolving the image or such. - flags: LoadDataFlags, -} - -impl Drop for LoadData { - fn drop(&mut self) { - unsafe { bindings::Gecko_LoadData_Drop(self) } - } -} - -impl Default for LoadData { - fn default() -> Self { - Self { - resolved_image: std::ptr::null_mut(), - resolved_uri: std::ptr::null_mut(), - flags: LoadDataFlags::empty(), - } - } -} - -/// The data for a load, or a lazy-loaded, static member that will be stored in -/// LOAD_DATA_TABLE, keyed by the memory location of this object, which is -/// always in the heap because it's inside the CssUrlData object. -/// -/// This type is meant not to be used from C++ so we don't derive helper -/// methods. -/// -/// cbindgen:derive-helper-methods=false -#[derive(Debug)] -#[repr(u8, C)] -pub enum LoadDataSource { - /// An owned copy of the load data. - Owned(LoadData), - /// A lazily-resolved copy of it. - Lazy, -} - -impl LoadDataSource { - /// Gets the load data associated with the source. - /// - /// This relies on the source on being in a stable location if lazy. - #[inline] - pub unsafe fn get(&self) -> *const LoadData { - match *self { - LoadDataSource::Owned(ref d) => return d, - LoadDataSource::Lazy => {}, - } - - let key = LoadDataKey(self); - - { - let guard = LOAD_DATA_TABLE.read().unwrap(); - if let Some(r) = guard.get(&key) { - return &**r; - } - } - let mut guard = LOAD_DATA_TABLE.write().unwrap(); - let r = guard.entry(key).or_insert_with(Default::default); - &**r - } -} - -impl ToShmem for LoadDataSource { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - Ok(ManuallyDrop::new(match self { - LoadDataSource::Owned(..) => LoadDataSource::Lazy, - LoadDataSource::Lazy => LoadDataSource::Lazy, - })) - } -} - -/// A specified non-image `url()` value. -pub type SpecifiedUrl = CssUrl; - -/// Clears LOAD_DATA_TABLE. Entries in this table, which are for specified URL -/// values that come from shared memory style sheets, would otherwise persist -/// until the end of the process and be reported as leaks. -pub fn shutdown() { - LOAD_DATA_TABLE.write().unwrap().clear(); -} - -impl ToComputedValue for SpecifiedUrl { - type ComputedValue = ComputedUrl; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - ComputedUrl(self.clone()) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - computed.0.clone() - } -} - -/// A specified image `url()` value. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct SpecifiedImageUrl(pub SpecifiedUrl); - -impl SpecifiedImageUrl { - /// Parse a URL from a string value that is a valid CSS token for a URL. - pub fn parse_from_string(url: String, context: &ParserContext, cors_mode: CorsMode) -> Self { - SpecifiedImageUrl(SpecifiedUrl::parse_from_string(url, context, cors_mode)) - } - - /// Provides an alternate method for parsing that associates the URL - /// with anonymous CORS headers. - pub fn parse_with_cors_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - ) -> Result<Self, ParseError<'i>> { - Ok(SpecifiedImageUrl(SpecifiedUrl::parse_with_cors_mode( - context, input, cors_mode, - )?)) - } -} - -impl Parse for SpecifiedImageUrl { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - SpecifiedUrl::parse(context, input).map(SpecifiedImageUrl) - } -} - -impl ToComputedValue for SpecifiedImageUrl { - type ComputedValue = ComputedImageUrl; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - ComputedImageUrl(self.0.to_computed_value(context)) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - SpecifiedImageUrl(ToComputedValue::from_computed_value(&computed.0)) - } -} - -/// The computed value of a CSS non-image `url()`. -/// -/// The only difference between specified and computed URLs is the -/// serialization. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] -#[repr(C)] -pub struct ComputedUrl(pub SpecifiedUrl); - -impl ComputedUrl { - fn serialize_with<W>( - &self, - function: unsafe extern "C" fn(*const Self, *mut nsCString), - dest: &mut CssWriter<W>, - ) -> fmt::Result - where - W: Write, - { - dest.write_str("url(")?; - unsafe { - let mut string = nsCString::new(); - function(self, &mut string); - string.as_str_unchecked().to_css(dest)?; - } - dest.write_char(')') - } -} - -impl ToCss for ComputedUrl { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.serialize_with(bindings::Gecko_GetComputedURLSpec, dest) - } -} - -/// The computed value of a CSS image `url()`. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq)] -#[repr(transparent)] -pub struct ComputedImageUrl(pub ComputedUrl); - -impl ToCss for ComputedImageUrl { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0 - .serialize_with(bindings::Gecko_GetComputedImageURLSpec, dest) - } -} - -lazy_static! { - /// A table mapping CssUrlData objects to their lazily created LoadData - /// objects. - static ref LOAD_DATA_TABLE: RwLock<HashMap<LoadDataKey, Box<LoadData>>> = { - Default::default() - }; -} diff --git a/components/style/gecko/values.rs b/components/style/gecko/values.rs deleted file mode 100644 index fbdb02c6bad..00000000000 --- a/components/style/gecko/values.rs +++ /dev/null @@ -1,72 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] - -//! Different kind of helpers to interact with Gecko values. - -use crate::color::{AbsoluteColor, ColorSpace}; -use crate::counter_style::{Symbol, Symbols}; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::CounterStylePtr; -use crate::values::generics::CounterStyle; -use crate::values::Either; -use crate::Atom; - -/// Convert a color value to `nscolor`. -pub fn convert_absolute_color_to_nscolor(color: &AbsoluteColor) -> u32 { - let srgb = color.to_color_space(ColorSpace::Srgb); - u32::from_le_bytes([ - (srgb.components.0 * 255.0).round() as u8, - (srgb.components.1 * 255.0).round() as u8, - (srgb.components.2 * 255.0).round() as u8, - (srgb.alpha * 255.0).round() as u8, - ]) -} - -/// Convert a given `nscolor` to a Servo AbsoluteColor value. -pub fn convert_nscolor_to_absolute_color(color: u32) -> AbsoluteColor { - let [r, g, b, a] = color.to_le_bytes(); - AbsoluteColor::srgb( - r as f32 / 255.0, - g as f32 / 255.0, - b as f32 / 255.0, - a as f32 / 255.0, - ) -} - -impl CounterStyle { - /// Convert this counter style to a Gecko CounterStylePtr. - #[inline] - pub fn to_gecko_value(&self, gecko_value: &mut CounterStylePtr) { - unsafe { bindings::Gecko_CounterStyle_ToPtr(self, gecko_value) } - } - - /// Convert Gecko CounterStylePtr to CounterStyle or String. - pub fn from_gecko_value(gecko_value: &CounterStylePtr) -> Either<Self, String> { - use crate::values::CustomIdent; - - let name = unsafe { bindings::Gecko_CounterStyle_GetName(gecko_value) }; - if !name.is_null() { - let name = unsafe { Atom::from_raw(name) }; - debug_assert_ne!(name, atom!("none")); - Either::First(CounterStyle::Name(CustomIdent(name))) - } else { - let anonymous = - unsafe { bindings::Gecko_CounterStyle_GetAnonymous(gecko_value).as_ref() }.unwrap(); - let symbols = &anonymous.mSymbols; - if anonymous.mSingleString { - debug_assert_eq!(symbols.len(), 1); - Either::Second(symbols[0].to_string()) - } else { - let symbol_type = anonymous.mSymbolsType; - let symbols = symbols - .iter() - .map(|gecko_symbol| Symbol::String(gecko_symbol.to_string().into())) - .collect(); - Either::First(CounterStyle::Symbols(symbol_type, Symbols(symbols))) - } - } - } -} diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs deleted file mode 100644 index 490c063fbe7..00000000000 --- a/components/style/gecko/wrapper.rs +++ /dev/null @@ -1,2125 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] - -//! Wrapper definitions on top of Gecko types in order to be used in the style -//! system. -//! -//! This really follows the Servo pattern in -//! `components/script/layout_wrapper.rs`. -//! -//! This theoretically should live in its own crate, but now it lives in the -//! style system it's kind of pointless in the Stylo case, and only Servo forces -//! the separation between the style system implementation and everything else. - -use crate::applicable_declarations::ApplicableDeclarationBlock; -use crate::context::{PostAnimationTasks, QuirksMode, SharedStyleContext, UpdateAnimationsTasks}; -use crate::data::ElementData; -use crate::dom::{LayoutIterator, NodeInfo, OpaqueNode, TDocument, TElement, TNode, TShadowRoot}; -use crate::gecko::selector_parser::{NonTSPseudoClass, PseudoElement, SelectorImpl}; -use crate::gecko::snapshot_helpers; -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations; -use crate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; -use crate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; -use crate::gecko_bindings::bindings::Gecko_ElementState; -use crate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount; -use crate::gecko_bindings::bindings::Gecko_GetAnimationRule; -use crate::gecko_bindings::bindings::Gecko_GetExtraContentStyleDeclarations; -use crate::gecko_bindings::bindings::Gecko_GetHTMLPresentationAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetStyleAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetUnvisitedLinkAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_GetVisitedLinkAttrDeclarationBlock; -use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; -use crate::gecko_bindings::bindings::Gecko_MatchLang; -use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; -use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::nsChangeHint; -use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; -use crate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT; -use crate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO; -use crate::gecko_bindings::structs::ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO; -use crate::gecko_bindings::structs::ELEMENT_HAS_SNAPSHOT; -use crate::gecko_bindings::structs::NODE_DESCENDANTS_NEED_FRAMES; -use crate::gecko_bindings::structs::NODE_NEEDS_FRAME; -use crate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag}; -use crate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement}; -use crate::global_style_data::GLOBAL_STYLE_DATA; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::media_queries::Device; -use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; -use crate::properties::{ComputedValues, LonghandId}; -use crate::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock}; -use crate::rule_tree::CascadeLevel as ServoCascadeLevel; -use crate::selector_parser::{AttrValue, Lang}; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; -use crate::stylist::CascadeData; -use crate::values::computed::Display; -use crate::values::{AtomIdent, AtomString}; -use crate::CaseSensitivityExt; -use crate::LocalName; -use app_units::Au; -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use dom::{DocumentState, ElementState}; -use euclid::default::Size2D; -use fxhash::FxHashMap; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::matching::VisitedHandlingMode; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; -use selectors::sink::Push; -use selectors::{Element, OpaqueElement}; -use servo_arc::{Arc, ArcBorrow}; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::mem; -use std::ptr; -use std::sync::atomic::{AtomicU32, Ordering}; - -#[inline] -fn elements_with_id<'a, 'le>( - array: *const structs::nsTArray<*mut RawGeckoElement>, -) -> &'a [GeckoElement<'le>] { - unsafe { - if array.is_null() { - return &[]; - } - - let elements: &[*mut RawGeckoElement] = &**array; - - // NOTE(emilio): We rely on the in-memory representation of - // GeckoElement<'ld> and *mut RawGeckoElement being the same. - #[allow(dead_code)] - unsafe fn static_assert() { - mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _); - } - - mem::transmute(elements) - } -} - -/// A simple wrapper over `Document`. -#[derive(Clone, Copy)] -pub struct GeckoDocument<'ld>(pub &'ld structs::Document); - -impl<'ld> TDocument for GeckoDocument<'ld> { - type ConcreteNode = GeckoNode<'ld>; - - #[inline] - fn as_node(&self) -> Self::ConcreteNode { - GeckoNode(&self.0._base) - } - - #[inline] - fn is_html_document(&self) -> bool { - self.0.mType == structs::Document_Type::eHTML - } - - #[inline] - fn quirks_mode(&self) -> QuirksMode { - self.0.mCompatMode.into() - } - - #[inline] - fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'ld>], ()> - where - Self: 'a, - { - Ok(elements_with_id(unsafe { - bindings::Gecko_Document_GetElementsWithId(self.0, id.as_ptr()) - })) - } - - fn shared_lock(&self) -> &SharedRwLock { - &GLOBAL_STYLE_DATA.shared_lock - } -} - -/// A simple wrapper over `ShadowRoot`. -#[derive(Clone, Copy)] -pub struct GeckoShadowRoot<'lr>(pub &'lr structs::ShadowRoot); - -impl<'ln> fmt::Debug for GeckoShadowRoot<'ln> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // TODO(emilio): Maybe print the host or something? - write!(f, "<shadow-root> ({:#x})", self.as_node().opaque().0) - } -} - -impl<'lr> PartialEq for GeckoShadowRoot<'lr> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0 as *const _ == other.0 as *const _ - } -} - -impl<'lr> TShadowRoot for GeckoShadowRoot<'lr> { - type ConcreteNode = GeckoNode<'lr>; - - #[inline] - fn as_node(&self) -> Self::ConcreteNode { - GeckoNode(&self.0._base._base._base._base) - } - - #[inline] - fn host(&self) -> GeckoElement<'lr> { - GeckoElement(unsafe { &*self.0._base.mHost.mRawPtr }) - } - - #[inline] - fn style_data<'a>(&self) -> Option<&'a CascadeData> - where - Self: 'a, - { - let author_styles = unsafe { self.0.mServoStyles.mPtr.as_ref()? }; - Some(&author_styles.data) - } - - #[inline] - fn elements_with_id<'a>(&self, id: &AtomIdent) -> Result<&'a [GeckoElement<'lr>], ()> - where - Self: 'a, - { - Ok(elements_with_id(unsafe { - bindings::Gecko_ShadowRoot_GetElementsWithId(self.0, id.as_ptr()) - })) - } - - #[inline] - fn parts<'a>(&self) -> &[<Self::ConcreteNode as TNode>::ConcreteElement] - where - Self: 'a, - { - let slice: &[*const RawGeckoElement] = &*self.0.mParts; - - #[allow(dead_code)] - unsafe fn static_assert() { - mem::transmute::<*const RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *const _); - } - - unsafe { mem::transmute(slice) } - } -} - -/// A simple wrapper over a non-null Gecko node (`nsINode`) pointer. -/// -/// Important: We don't currently refcount the DOM, because the wrapper lifetime -/// magic guarantees that our LayoutFoo references won't outlive the root, and -/// we don't mutate any of the references on the Gecko side during restyle. -/// -/// We could implement refcounting if need be (at a potentially non-trivial -/// performance cost) by implementing Drop and making LayoutFoo non-Copy. -#[derive(Clone, Copy)] -pub struct GeckoNode<'ln>(pub &'ln RawGeckoNode); - -impl<'ln> PartialEq for GeckoNode<'ln> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0 as *const _ == other.0 as *const _ - } -} - -impl<'ln> fmt::Debug for GeckoNode<'ln> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(el) = self.as_element() { - return el.fmt(f); - } - - if self.is_text_node() { - return write!(f, "<text node> ({:#x})", self.opaque().0); - } - - if self.is_document() { - return write!(f, "<document> ({:#x})", self.opaque().0); - } - - if let Some(sr) = self.as_shadow_root() { - return sr.fmt(f); - } - - write!(f, "<non-text node> ({:#x})", self.opaque().0) - } -} - -impl<'ln> GeckoNode<'ln> { - #[inline] - fn is_document(&self) -> bool { - // This is a DOM constant that isn't going to change. - const DOCUMENT_NODE: u16 = 9; - self.node_info().mInner.mNodeType == DOCUMENT_NODE - } - - #[inline] - fn is_shadow_root(&self) -> bool { - self.is_in_shadow_tree() && self.parent_node().is_none() - } - - #[inline] - fn from_content(content: &'ln nsIContent) -> Self { - GeckoNode(&content._base) - } - - #[inline] - fn set_flags(&self, flags: u32) { - self.flags_atomic().fetch_or(flags, Ordering::Relaxed); - } - - #[inline] - fn flags_atomic(&self) -> &AtomicU32 { - use std::cell::Cell; - let flags: &Cell<u32> = &(self.0)._base._base_1.mFlags; - - #[allow(dead_code)] - fn static_assert() { - let _: [u8; std::mem::size_of::<Cell<u32>>()] = [0u8; std::mem::size_of::<AtomicU32>()]; - let _: [u8; std::mem::align_of::<Cell<u32>>()] = - [0u8; std::mem::align_of::<AtomicU32>()]; - } - - // Rust doesn't provide standalone atomic functions like GCC/clang do - // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees - // that the memory representation of u32 and AtomicU32 matches: - // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html - unsafe { std::mem::transmute::<&Cell<u32>, &AtomicU32>(flags) } - } - - #[inline] - fn flags(&self) -> u32 { - self.flags_atomic().load(Ordering::Relaxed) - } - - #[inline] - fn node_info(&self) -> &structs::NodeInfo { - debug_assert!(!self.0.mNodeInfo.mRawPtr.is_null()); - unsafe { &*self.0.mNodeInfo.mRawPtr } - } - - // These live in different locations depending on processor architecture. - #[cfg(target_pointer_width = "64")] - #[inline] - fn bool_flags(&self) -> u32 { - (self.0)._base._base_1.mBoolFlags - } - - #[cfg(target_pointer_width = "32")] - #[inline] - fn bool_flags(&self) -> u32 { - (self.0).mBoolFlags - } - - #[inline] - fn get_bool_flag(&self, flag: nsINode_BooleanFlag) -> bool { - self.bool_flags() & (1u32 << flag as u32) != 0 - } - - /// This logic is duplicate in Gecko's nsINode::IsInShadowTree(). - #[inline] - fn is_in_shadow_tree(&self) -> bool { - use crate::gecko_bindings::structs::NODE_IS_IN_SHADOW_TREE; - self.flags() & NODE_IS_IN_SHADOW_TREE != 0 - } - - /// Returns true if we know for sure that `flattened_tree_parent` and `parent_node` return the - /// same thing. - /// - /// TODO(emilio): Measure and consider not doing this fast-path, it's only a function call and - /// from profiles it seems that keeping this fast path makes the compiler not inline - /// `flattened_tree_parent` as a whole, so we're not gaining much either. - #[inline] - fn flattened_tree_parent_is_parent(&self) -> bool { - use crate::gecko_bindings::structs::*; - let flags = self.flags(); - - let parent = match self.parent_node() { - Some(p) => p, - None => return true, - }; - - if parent.is_shadow_root() { - return false; - } - - if let Some(parent) = parent.as_element() { - if flags & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0 && parent.is_root() { - return false; - } - if parent.shadow_root().is_some() || parent.is_html_slot_element() { - return false; - } - } - - true - } - - #[inline] - fn flattened_tree_parent(&self) -> Option<Self> { - if self.flattened_tree_parent_is_parent() { - debug_assert_eq!( - unsafe { - bindings::Gecko_GetFlattenedTreeParentNode(self.0) - .as_ref() - .map(GeckoNode) - }, - self.parent_node(), - "Fast path stopped holding!" - ); - return self.parent_node(); - } - - // NOTE(emilio): If this call is too expensive, we could manually inline more aggressively. - unsafe { - bindings::Gecko_GetFlattenedTreeParentNode(self.0) - .as_ref() - .map(GeckoNode) - } - } - - #[inline] - fn contains_non_whitespace_content(&self) -> bool { - unsafe { Gecko_IsSignificantChild(self.0, false) } - } -} - -impl<'ln> NodeInfo for GeckoNode<'ln> { - #[inline] - fn is_element(&self) -> bool { - self.get_bool_flag(nsINode_BooleanFlag::NodeIsElement) - } - - fn is_text_node(&self) -> bool { - // This is a DOM constant that isn't going to change. - const TEXT_NODE: u16 = 3; - self.node_info().mInner.mNodeType == TEXT_NODE - } -} - -impl<'ln> TNode for GeckoNode<'ln> { - type ConcreteDocument = GeckoDocument<'ln>; - type ConcreteShadowRoot = GeckoShadowRoot<'ln>; - type ConcreteElement = GeckoElement<'ln>; - - #[inline] - fn parent_node(&self) -> Option<Self> { - unsafe { self.0.mParent.as_ref().map(GeckoNode) } - } - - #[inline] - fn first_child(&self) -> Option<Self> { - unsafe { - self.0 - .mFirstChild - .raw() - .as_ref() - .map(GeckoNode::from_content) - } - } - - #[inline] - fn last_child(&self) -> Option<Self> { - unsafe { bindings::Gecko_GetLastChild(self.0).as_ref().map(GeckoNode) } - } - - #[inline] - fn prev_sibling(&self) -> Option<Self> { - unsafe { - let prev_or_last = GeckoNode::from_content(self.0.mPreviousOrLastSibling.as_ref()?); - if prev_or_last.0.mNextSibling.raw().is_null() { - return None; - } - Some(prev_or_last) - } - } - - #[inline] - fn next_sibling(&self) -> Option<Self> { - unsafe { - self.0 - .mNextSibling - .raw() - .as_ref() - .map(GeckoNode::from_content) - } - } - - #[inline] - fn owner_doc(&self) -> Self::ConcreteDocument { - debug_assert!(!self.node_info().mDocument.is_null()); - GeckoDocument(unsafe { &*self.node_info().mDocument }) - } - - #[inline] - fn is_in_document(&self) -> bool { - self.get_bool_flag(nsINode_BooleanFlag::IsInDocument) - } - - fn traversal_parent(&self) -> Option<GeckoElement<'ln>> { - self.flattened_tree_parent().and_then(|n| n.as_element()) - } - - #[inline] - fn opaque(&self) -> OpaqueNode { - let ptr: usize = self.0 as *const _ as usize; - OpaqueNode(ptr) - } - - fn debug_id(self) -> usize { - unimplemented!() - } - - #[inline] - fn as_element(&self) -> Option<GeckoElement<'ln>> { - if !self.is_element() { - return None; - } - - Some(GeckoElement(unsafe { - &*(self.0 as *const _ as *const RawGeckoElement) - })) - } - - #[inline] - fn as_document(&self) -> Option<Self::ConcreteDocument> { - if !self.is_document() { - return None; - } - - debug_assert_eq!(self.owner_doc().as_node(), *self, "How?"); - Some(self.owner_doc()) - } - - #[inline] - fn as_shadow_root(&self) -> Option<Self::ConcreteShadowRoot> { - if !self.is_shadow_root() { - return None; - } - - Some(GeckoShadowRoot(unsafe { - &*(self.0 as *const _ as *const structs::ShadowRoot) - })) - } -} - -/// A wrapper on top of two kind of iterators, depending on the parent being -/// iterated. -/// -/// We generally iterate children by traversing the light-tree siblings of the -/// first child like Servo does. -/// -/// However, for nodes with anonymous children, we use a custom (heavier-weight) -/// Gecko-implemented iterator. -/// -/// FIXME(emilio): If we take into account shadow DOM, we're going to need the -/// flat tree pretty much always. We can try to optimize the case where there's -/// no shadow root sibling, probably. -pub enum GeckoChildrenIterator<'a> { - /// A simple iterator that tracks the current node being iterated and - /// replaces it with the next sibling when requested. - Current(Option<GeckoNode<'a>>), - /// A Gecko-implemented iterator we need to drop appropriately. - GeckoIterator(structs::StyleChildrenIterator), -} - -impl<'a> Drop for GeckoChildrenIterator<'a> { - fn drop(&mut self) { - if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self { - unsafe { - bindings::Gecko_DestroyStyleChildrenIterator(it); - } - } - } -} - -impl<'a> Iterator for GeckoChildrenIterator<'a> { - type Item = GeckoNode<'a>; - fn next(&mut self) -> Option<GeckoNode<'a>> { - match *self { - GeckoChildrenIterator::Current(curr) => { - let next = curr.and_then(|node| node.next_sibling()); - *self = GeckoChildrenIterator::Current(next); - curr - }, - GeckoChildrenIterator::GeckoIterator(ref mut it) => unsafe { - // We do this unsafe lengthening of the lifetime here because - // structs::StyleChildrenIterator is actually StyleChildrenIterator<'a>, - // however we can't express this easily with bindgen, and it would - // introduce functions with two input lifetimes into bindgen, - // which would be out of scope for elision. - bindings::Gecko_GetNextStyleChild(&mut *(it as *mut _)) - .as_ref() - .map(GeckoNode) - }, - } - } -} - -/// A simple wrapper over a non-null Gecko `Element` pointer. -#[derive(Clone, Copy)] -pub struct GeckoElement<'le>(pub &'le RawGeckoElement); - -impl<'le> fmt::Debug for GeckoElement<'le> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use nsstring::nsCString; - - write!(f, "<{}", self.local_name())?; - - let mut attrs = nsCString::new(); - unsafe { - bindings::Gecko_Element_DebugListAttributes(self.0, &mut attrs); - } - write!(f, "{}", attrs)?; - write!(f, "> ({:#x})", self.as_node().opaque().0) - } -} - -impl<'le> GeckoElement<'le> { - /// Gets the raw `ElementData` refcell for the element. - #[inline(always)] - pub fn get_data(&self) -> Option<&AtomicRefCell<ElementData>> { - unsafe { self.0.mServoData.get().as_ref() } - } - - /// Returns whether any animation applies to this element. - #[inline] - pub fn has_any_animation(&self) -> bool { - self.may_have_animations() && unsafe { Gecko_ElementHasAnimations(self.0) } - } - - #[inline(always)] - fn attrs(&self) -> Option<&structs::AttrArray_Impl> { - unsafe { self.0.mAttrs.mImpl.mPtr.as_ref() } - } - - #[inline(always)] - fn non_mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] { - let attrs = match self.attrs() { - Some(attrs) => attrs, - None => return &[], - }; - unsafe { - attrs.mBuffer.as_slice(attrs.mAttrCount as usize) - } - } - - #[inline(always)] - fn mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] { - let attrs = match self.attrs() { - Some(attrs) => attrs, - None => return &[], - }; - unsafe { - let attrs = match attrs.mMappedAttrs.as_ref() { - Some(attrs) => attrs, - None => return &[], - }; - - attrs.mBuffer.as_slice(attrs.mAttrCount as usize) - } - } - - #[inline] - fn iter_attrs(&self) -> impl Iterator<Item = &structs::AttrArray_InternalAttr> { - self.non_mapped_attrs().iter().chain(self.mapped_attrs().iter()) - } - - #[inline(always)] - fn get_part_attr(&self) -> Option<&structs::nsAttrValue> { - if !self.has_part_attr() { - return None; - } - snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("part")) - } - - #[inline(always)] - fn get_class_attr(&self) -> Option<&structs::nsAttrValue> { - if !self.may_have_class() { - return None; - } - - if self.is_svg_element() { - let svg_class = unsafe { bindings::Gecko_GetSVGAnimatedClass(self.0).as_ref() }; - if let Some(c) = svg_class { - return Some(c); - } - } - - snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("class")) - } - - #[inline] - fn may_have_anonymous_children(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveAnonymousChildren) - } - - #[inline] - fn flags(&self) -> u32 { - self.as_node().flags() - } - - #[inline] - fn set_flags(&self, flags: u32) { - self.as_node().set_flags(flags); - } - - #[inline] - unsafe fn unset_flags(&self, flags: u32) { - self.as_node() - .flags_atomic() - .fetch_and(!flags, Ordering::Relaxed); - } - - /// Returns true if this element has descendants for lazy frame construction. - #[inline] - pub fn descendants_need_frames(&self) -> bool { - self.flags() & NODE_DESCENDANTS_NEED_FRAMES != 0 - } - - /// Returns true if this element needs lazy frame construction. - #[inline] - pub fn needs_frame(&self) -> bool { - self.flags() & NODE_NEEDS_FRAME != 0 - } - - /// Returns a reference to the DOM slots for this Element, if they exist. - #[inline] - fn dom_slots(&self) -> Option<&structs::FragmentOrElement_nsDOMSlots> { - let slots = self.as_node().0.mSlots as *const structs::FragmentOrElement_nsDOMSlots; - unsafe { slots.as_ref() } - } - - /// Returns a reference to the extended DOM slots for this Element. - #[inline] - fn extended_slots(&self) -> Option<&structs::FragmentOrElement_nsExtendedDOMSlots> { - self.dom_slots().and_then(|s| unsafe { - // For the bit usage, see nsContentSlots::GetExtendedSlots. - let e_slots = s._base.mExtendedSlots & - !structs::nsIContent_nsContentSlots_sNonOwningExtendedSlotsFlag; - (e_slots as *const structs::FragmentOrElement_nsExtendedDOMSlots).as_ref() - }) - } - - #[inline] - fn namespace_id(&self) -> i32 { - self.as_node().node_info().mInner.mNamespaceID - } - - #[inline] - fn has_id(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasID) - } - - #[inline] - fn state_internal(&self) -> u64 { - if !self - .as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasLockedStyleStates) - { - return self.0.mState.bits; - } - unsafe { Gecko_ElementState(self.0) } - } - - #[inline] - fn document_state(&self) -> DocumentState { - DocumentState::from_bits_truncate(self.as_node().owner_doc().0.mDocumentState.bits) - } - - #[inline] - fn may_have_class(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveClass) - } - - #[inline] - fn has_properties(&self) -> bool { - use crate::gecko_bindings::structs::NODE_HAS_PROPERTIES; - - self.flags() & NODE_HAS_PROPERTIES != 0 - } - - #[inline] - fn before_or_after_pseudo(&self, is_before: bool) -> Option<Self> { - if !self.has_properties() { - return None; - } - - unsafe { - bindings::Gecko_GetBeforeOrAfterPseudo(self.0, is_before) - .as_ref() - .map(GeckoElement) - } - } - - #[inline] - fn may_have_style_attribute(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle) - } - - /// Only safe to call on the main thread, with exclusive access to the - /// element and its ancestors. - /// - /// This function is also called after display property changed for SMIL - /// animation. - /// - /// Also this function schedules style flush. - pub unsafe fn note_explicit_hints(&self, restyle_hint: RestyleHint, change_hint: nsChangeHint) { - use crate::gecko::restyle_damage::GeckoRestyleDamage; - - let damage = GeckoRestyleDamage::new(change_hint); - debug!( - "note_explicit_hints: {:?}, restyle_hint={:?}, change_hint={:?}", - self, restyle_hint, change_hint - ); - - debug_assert!( - !(restyle_hint.has_animation_hint() && restyle_hint.has_non_animation_hint()), - "Animation restyle hints should not appear with non-animation restyle hints" - ); - - let mut data = match self.mutate_data() { - Some(d) => d, - None => { - debug!("(Element not styled, discarding hints)"); - return; - }, - }; - - debug_assert!(data.has_styles(), "how?"); - - // Propagate the bit up the chain. - if restyle_hint.has_animation_hint() { - bindings::Gecko_NoteAnimationOnlyDirtyElement(self.0); - } else { - bindings::Gecko_NoteDirtyElement(self.0); - } - - data.hint.insert(restyle_hint); - data.damage |= damage; - } - - /// This logic is duplicated in Gecko's nsIContent::IsRootOfNativeAnonymousSubtree. - #[inline] - fn is_root_of_native_anonymous_subtree(&self) -> bool { - use crate::gecko_bindings::structs::NODE_IS_NATIVE_ANONYMOUS_ROOT; - return self.flags() & NODE_IS_NATIVE_ANONYMOUS_ROOT != 0; - } - - /// Returns true if this node is the shadow root of an use-element shadow tree. - #[inline] - fn is_root_of_use_element_shadow_tree(&self) -> bool { - if !self.as_node().is_in_shadow_tree() { - return false; - } - if !self.parent_node_is_shadow_root() { - return false; - } - let host = self.containing_shadow_host().unwrap(); - host.is_svg_element() && host.local_name() == &**local_name!("use") - } - - fn css_transitions_info(&self) -> FxHashMap<LonghandId, Arc<AnimationValue>> { - use crate::gecko_bindings::bindings::Gecko_ElementTransitions_EndValueAt; - use crate::gecko_bindings::bindings::Gecko_ElementTransitions_Length; - - let collection_length = unsafe { Gecko_ElementTransitions_Length(self.0) } as usize; - let mut map = FxHashMap::with_capacity_and_hasher(collection_length, Default::default()); - - for i in 0..collection_length { - let end_value = - unsafe { Arc::from_raw_addrefed(Gecko_ElementTransitions_EndValueAt(self.0, i)) }; - let property = end_value.id(); - debug_assert!(!property.is_logical()); - map.insert(property, end_value); - } - map - } - - fn needs_transitions_update_per_property( - &self, - longhand_id: LonghandId, - combined_duration_seconds: f32, - before_change_style: &ComputedValues, - after_change_style: &ComputedValues, - existing_transitions: &FxHashMap<LonghandId, Arc<AnimationValue>>, - ) -> bool { - use crate::values::animated::{Animate, Procedure}; - debug_assert!(!longhand_id.is_logical()); - - // If there is an existing transition, update only if the end value - // differs. - // - // If the end value has not changed, we should leave the currently - // running transition as-is since we don't want to interrupt its timing - // function. - if let Some(ref existing) = existing_transitions.get(&longhand_id) { - let after_value = - AnimationValue::from_computed_values(longhand_id, after_change_style).unwrap(); - - return ***existing != after_value; - } - - let from = AnimationValue::from_computed_values(longhand_id, before_change_style); - let to = AnimationValue::from_computed_values(longhand_id, after_change_style); - - debug_assert_eq!(to.is_some(), from.is_some()); - - combined_duration_seconds > 0.0f32 && - from != to && - from.unwrap() - .animate( - to.as_ref().unwrap(), - Procedure::Interpolate { progress: 0.5 }, - ) - .is_ok() - } -} - -/// Converts flags from the layout used by rust-selectors to the layout used -/// by Gecko. We could align these and then do this without conditionals, but -/// it's probably not worth the trouble. -fn selector_flags_to_node_flags(flags: ElementSelectorFlags) -> u32 { - use crate::gecko_bindings::structs::*; - let mut gecko_flags = 0u32; - if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR) { - gecko_flags |= NODE_HAS_SLOW_SELECTOR; - } - if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS) { - gecko_flags |= NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS; - } - if flags.contains(ElementSelectorFlags::HAS_SLOW_SELECTOR_NTH_OF) { - gecko_flags |= NODE_HAS_SLOW_SELECTOR_NTH_OF; - } - if flags.contains(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) { - gecko_flags |= NODE_HAS_EDGE_CHILD_SELECTOR; - } - if flags.contains(ElementSelectorFlags::HAS_EMPTY_SELECTOR) { - gecko_flags |= NODE_HAS_EMPTY_SELECTOR; - } - - gecko_flags -} - -fn get_animation_rule( - element: &GeckoElement, - cascade_level: CascadeLevel, -) -> Option<Arc<Locked<PropertyDeclarationBlock>>> { - use crate::properties::longhands::ANIMATABLE_PROPERTY_COUNT; - - // There's a very rough correlation between the number of effects - // (animations) on an element and the number of properties it is likely to - // animate, so we use that as an initial guess for the size of the - // AnimationValueMap in order to reduce the number of re-allocations needed. - let effect_count = unsafe { Gecko_GetAnimationEffectCount(element.0) }; - // Also, we should try to reuse the PDB, to avoid creating extra rule nodes. - let mut animation_values = AnimationValueMap::with_capacity_and_hasher( - effect_count.min(ANIMATABLE_PROPERTY_COUNT), - Default::default(), - ); - if unsafe { Gecko_GetAnimationRule(element.0, cascade_level, &mut animation_values) } { - let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; - Some(Arc::new(shared_lock.wrap( - PropertyDeclarationBlock::from_animation_value_map(&animation_values), - ))) - } else { - None - } -} - -/// Turns a gecko namespace id into an atom. Might panic if you pass any random thing that isn't a -/// namespace id. -#[inline(always)] -pub unsafe fn namespace_id_to_atom(id: i32) -> *mut nsAtom { - unsafe { - let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr; - (*namespace_manager).mURIArray[id as usize].mRawPtr - } -} - -impl<'le> TElement for GeckoElement<'le> { - type ConcreteNode = GeckoNode<'le>; - type TraversalChildrenIterator = GeckoChildrenIterator<'le>; - - fn inheritance_parent(&self) -> Option<Self> { - if self.is_pseudo_element() { - return self.pseudo_element_originating_element(); - } - - self.as_node() - .flattened_tree_parent() - .and_then(|n| n.as_element()) - } - - fn traversal_children(&self) -> LayoutIterator<GeckoChildrenIterator<'le>> { - // This condition is similar to the check that - // StyleChildrenIterator::IsNeeded does, except that it might return - // true if we used to (but no longer) have anonymous content from - // ::before/::after, or nsIAnonymousContentCreators. - if self.is_html_slot_element() || - self.shadow_root().is_some() || - self.may_have_anonymous_children() - { - unsafe { - let mut iter: structs::StyleChildrenIterator = ::std::mem::zeroed(); - bindings::Gecko_ConstructStyleChildrenIterator(self.0, &mut iter); - return LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter)); - } - } - - LayoutIterator(GeckoChildrenIterator::Current(self.as_node().first_child())) - } - - fn before_pseudo_element(&self) -> Option<Self> { - self.before_or_after_pseudo(/* is_before = */ true) - } - - fn after_pseudo_element(&self) -> Option<Self> { - self.before_or_after_pseudo(/* is_before = */ false) - } - - fn marker_pseudo_element(&self) -> Option<Self> { - if !self.has_properties() { - return None; - } - - unsafe { - bindings::Gecko_GetMarkerPseudo(self.0) - .as_ref() - .map(GeckoElement) - } - } - - #[inline] - fn is_html_element(&self) -> bool { - self.namespace_id() == structs::kNameSpaceID_XHTML as i32 - } - - #[inline] - fn is_mathml_element(&self) -> bool { - self.namespace_id() == structs::kNameSpaceID_MathML as i32 - } - - #[inline] - fn is_svg_element(&self) -> bool { - self.namespace_id() == structs::kNameSpaceID_SVG as i32 - } - - #[inline] - fn is_xul_element(&self) -> bool { - self.namespace_id() == structs::root::kNameSpaceID_XUL as i32 - } - - #[inline] - fn local_name(&self) -> &WeakAtom { - unsafe { WeakAtom::new(self.as_node().node_info().mInner.mName) } - } - - #[inline] - fn namespace(&self) -> &WeakNamespace { - unsafe { WeakNamespace::new(namespace_id_to_atom(self.namespace_id())) } - } - - #[inline] - fn query_container_size(&self, display: &Display) -> Size2D<Option<Au>> { - // If an element gets 'display: contents' and its nsIFrame has not been removed yet, - // Gecko_GetQueryContainerSize will not notice that it can't have size containment. - // Other cases like 'display: inline' will be handled once the new nsIFrame is created. - if display.is_contents() { - return Size2D::new(None, None); - } - - let mut width = -1; - let mut height = -1; - unsafe { - bindings::Gecko_GetQueryContainerSize(self.0, &mut width, &mut height); - } - Size2D::new( - if width >= 0 { Some(Au(width)) } else { None }, - if height >= 0 { Some(Au(height)) } else { None }, - ) - } - - /// Return the list of slotted nodes of this node. - #[inline] - fn slotted_nodes(&self) -> &[Self::ConcreteNode] { - if !self.is_html_slot_element() || !self.as_node().is_in_shadow_tree() { - return &[]; - } - - let slot: &structs::HTMLSlotElement = unsafe { mem::transmute(self.0) }; - - if cfg!(debug_assertions) { - let base: &RawGeckoElement = &slot._base._base._base._base; - assert_eq!(base as *const _, self.0 as *const _, "Bad cast"); - } - - // FIXME(emilio): Workaround a bindgen bug on Android that causes - // mAssignedNodes to be at the wrong offset. See bug 1466406. - // - // Bug 1466580 tracks running the Android layout tests on automation. - // - // The actual bindgen bug still needs reduction. - let assigned_nodes: &[structs::RefPtr<structs::nsINode>] = if !cfg!(target_os = "android") { - debug_assert_eq!( - unsafe { bindings::Gecko_GetAssignedNodes(self.0) }, - &slot.mAssignedNodes as *const _, - ); - - &*slot.mAssignedNodes - } else { - unsafe { &**bindings::Gecko_GetAssignedNodes(self.0) } - }; - - debug_assert_eq!( - mem::size_of::<structs::RefPtr<structs::nsINode>>(), - mem::size_of::<Self::ConcreteNode>(), - "Bad cast!" - ); - - unsafe { mem::transmute(assigned_nodes) } - } - - #[inline] - fn shadow_root(&self) -> Option<GeckoShadowRoot<'le>> { - let slots = self.extended_slots()?; - unsafe { slots.mShadowRoot.mRawPtr.as_ref().map(GeckoShadowRoot) } - } - - #[inline] - fn containing_shadow(&self) -> Option<GeckoShadowRoot<'le>> { - let slots = self.extended_slots()?; - unsafe { - slots - ._base - .mContainingShadow - .mRawPtr - .as_ref() - .map(GeckoShadowRoot) - } - } - - fn each_anonymous_content_child<F>(&self, mut f: F) - where - F: FnMut(Self), - { - if !self.may_have_anonymous_children() { - return; - } - - let array: *mut structs::nsTArray<*mut nsIContent> = - unsafe { bindings::Gecko_GetAnonymousContentForElement(self.0) }; - - if array.is_null() { - return; - } - - for content in unsafe { &**array } { - let node = GeckoNode::from_content(unsafe { &**content }); - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - f(element); - } - - unsafe { bindings::Gecko_DestroyAnonymousContentList(array) }; - } - - #[inline] - fn as_node(&self) -> Self::ConcreteNode { - unsafe { GeckoNode(&*(self.0 as *const _ as *const RawGeckoNode)) } - } - - fn owner_doc_matches_for_testing(&self, device: &Device) -> bool { - self.as_node().owner_doc().0 as *const structs::Document == device.document() as *const _ - } - - fn style_attribute(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> { - if !self.may_have_style_attribute() { - return None; - } - - unsafe { - let declarations = Gecko_GetStyleAttrDeclarationBlock(self.0).as_ref()?; - Some(ArcBorrow::from_ref(declarations)) - } - } - - fn unset_dirty_style_attribute(&self) { - if !self.may_have_style_attribute() { - return; - } - - unsafe { Gecko_UnsetDirtyStyleAttr(self.0) }; - } - - fn smil_override(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> { - unsafe { - let slots = self.extended_slots()?; - - let declaration: &structs::DeclarationBlock = - slots.mSMILOverrideStyleDeclaration.mRawPtr.as_ref()?; - - let raw: &structs::StyleLockedDeclarationBlock = declaration.mRaw.mRawPtr.as_ref()?; - Some(ArcBorrow::from_ref(raw)) - } - } - - fn animation_rule( - &self, - _: &SharedStyleContext, - ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> { - get_animation_rule(self, CascadeLevel::Animations) - } - - fn transition_rule( - &self, - _: &SharedStyleContext, - ) -> Option<Arc<Locked<PropertyDeclarationBlock>>> { - get_animation_rule(self, CascadeLevel::Transitions) - } - - #[inline] - fn state(&self) -> ElementState { - ElementState::from_bits_truncate(self.state_internal()) - } - - #[inline] - fn has_part_attr(&self) -> bool { - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasPart) - } - - #[inline] - fn exports_any_part(&self) -> bool { - snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("exportparts")).is_some() - } - - // FIXME(emilio): we should probably just return a reference to the Atom. - #[inline] - fn id(&self) -> Option<&WeakAtom> { - if !self.has_id() { - return None; - } - - snapshot_helpers::get_id(self.non_mapped_attrs()) - } - - fn each_attr_name<F>(&self, mut callback: F) - where - F: FnMut(&AtomIdent), - { - for attr in self.iter_attrs() { - unsafe { - AtomIdent::with(attr.mName.name(), |a| callback(a)) - } - } - } - - fn each_class<F>(&self, callback: F) - where - F: FnMut(&AtomIdent), - { - let attr = match self.get_class_attr() { - Some(c) => c, - None => return, - }; - - snapshot_helpers::each_class_or_part(attr, callback) - } - - #[inline] - fn each_exported_part<F>(&self, name: &AtomIdent, callback: F) - where - F: FnMut(&AtomIdent), - { - snapshot_helpers::each_exported_part(self.non_mapped_attrs(), name, callback) - } - - fn each_part<F>(&self, callback: F) - where - F: FnMut(&AtomIdent), - { - let attr = match self.get_part_attr() { - Some(c) => c, - None => return, - }; - - snapshot_helpers::each_class_or_part(attr, callback) - } - - #[inline] - fn has_snapshot(&self) -> bool { - self.flags() & ELEMENT_HAS_SNAPSHOT != 0 - } - - #[inline] - fn handled_snapshot(&self) -> bool { - self.flags() & ELEMENT_HANDLED_SNAPSHOT != 0 - } - - unsafe fn set_handled_snapshot(&self) { - debug_assert!(self.has_data()); - self.set_flags(ELEMENT_HANDLED_SNAPSHOT) - } - - #[inline] - fn has_dirty_descendants(&self) -> bool { - self.flags() & ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO != 0 - } - - unsafe fn set_dirty_descendants(&self) { - debug_assert!(self.has_data()); - debug!("Setting dirty descendants: {:?}", self); - self.set_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO) - } - - unsafe fn unset_dirty_descendants(&self) { - self.unset_flags(ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO) - } - - #[inline] - fn has_animation_only_dirty_descendants(&self) -> bool { - self.flags() & ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO != 0 - } - - unsafe fn set_animation_only_dirty_descendants(&self) { - self.set_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO) - } - - unsafe fn unset_animation_only_dirty_descendants(&self) { - self.unset_flags(ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO) - } - - unsafe fn clear_descendant_bits(&self) { - self.unset_flags( - ELEMENT_HAS_DIRTY_DESCENDANTS_FOR_SERVO | - ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO | - NODE_DESCENDANTS_NEED_FRAMES, - ) - } - - fn is_visited_link(&self) -> bool { - self.state().intersects(ElementState::VISITED) - } - - /// We want to match rules from the same tree in all cases, except for native anonymous content - /// that _isn't_ part directly of a UA widget (e.g., such generated by form controls, or - /// pseudo-elements). - #[inline] - fn matches_user_and_content_rules(&self) -> bool { - use crate::gecko_bindings::structs::{ - NODE_HAS_BEEN_IN_UA_WIDGET, NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE, - }; - let flags = self.flags(); - (flags & NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE) == 0 || - (flags & NODE_HAS_BEEN_IN_UA_WIDGET) != 0 - } - - #[inline] - fn implemented_pseudo_element(&self) -> Option<PseudoElement> { - if self.matches_user_and_content_rules() { - return None; - } - - if !self.has_properties() { - return None; - } - - PseudoElement::from_pseudo_type(unsafe { bindings::Gecko_GetImplementedPseudo(self.0) }) - } - - #[inline] - fn store_children_to_process(&self, _: isize) { - // This is only used for bottom-up traversal, and is thus a no-op for Gecko. - } - - fn did_process_child(&self) -> isize { - panic!("Atomic child count not implemented in Gecko"); - } - - unsafe fn ensure_data(&self) -> AtomicRefMut<ElementData> { - if !self.has_data() { - debug!("Creating ElementData for {:?}", self); - let ptr = Box::into_raw(Box::new(AtomicRefCell::new(ElementData::default()))); - self.0.mServoData.set(ptr); - } - self.mutate_data().unwrap() - } - - unsafe fn clear_data(&self) { - let ptr = self.0.mServoData.get(); - self.unset_flags( - ELEMENT_HAS_SNAPSHOT | - ELEMENT_HANDLED_SNAPSHOT | - structs::Element_kAllServoDescendantBits | - NODE_NEEDS_FRAME, - ); - if !ptr.is_null() { - debug!("Dropping ElementData for {:?}", self); - let data = Box::from_raw(self.0.mServoData.get()); - self.0.mServoData.set(ptr::null_mut()); - - // Perform a mutable borrow of the data in debug builds. This - // serves as an assertion that there are no outstanding borrows - // when we destroy the data. - debug_assert!({ - let _ = data.borrow_mut(); - true - }); - } - } - - #[inline] - fn skip_item_display_fixup(&self) -> bool { - debug_assert!( - !self.is_pseudo_element(), - "Just don't call me if I'm a pseudo, you should know the answer already" - ); - self.is_root_of_native_anonymous_subtree() - } - - #[inline] - fn may_have_animations(&self) -> bool { - if let Some(pseudo) = self.implemented_pseudo_element() { - if pseudo.animations_stored_in_parent() { - // FIXME(emilio): When would the parent of a ::before / ::after - // pseudo-element be null? - return self.parent_element().map_or(false, |p| { - p.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) - }); - } - } - self.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) - } - - /// Process various tasks that are a result of animation-only restyle. - fn process_post_animation(&self, tasks: PostAnimationTasks) { - debug_assert!(!tasks.is_empty(), "Should be involved a task"); - - // If display style was changed from none to other, we need to resolve - // the descendants in the display:none subtree. Instead of resolving - // those styles in animation-only restyle, we defer it to a subsequent - // normal restyle. - if tasks.intersects(PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL) { - debug_assert!( - self.implemented_pseudo_element() - .map_or(true, |p| !p.is_before_or_after()), - "display property animation shouldn't run on pseudo elements \ - since it's only for SMIL" - ); - unsafe { - self.note_explicit_hints( - RestyleHint::restyle_subtree(), - nsChangeHint::nsChangeHint_Empty, - ); - } - } - } - - /// Update various animation-related state on a given (pseudo-)element as - /// results of normal restyle. - fn update_animations( - &self, - before_change_style: Option<Arc<ComputedValues>>, - tasks: UpdateAnimationsTasks, - ) { - // We have to update animations even if the element has no computed - // style since it means the element is in a display:none subtree, we - // should destroy all CSS animations in display:none subtree. - let computed_data = self.borrow_data(); - let computed_values = computed_data.as_ref().map(|d| d.styles.primary()); - let before_change_values = before_change_style - .as_ref() - .map_or(ptr::null(), |x| x.as_gecko_computed_style()); - let computed_values_opt = computed_values - .as_ref() - .map_or(ptr::null(), |x| x.as_gecko_computed_style()); - unsafe { - Gecko_UpdateAnimations( - self.0, - before_change_values, - computed_values_opt, - tasks.bits(), - ); - } - } - - #[inline] - fn has_animations(&self, _: &SharedStyleContext) -> bool { - self.has_any_animation() - } - - fn has_css_animations(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool { - self.may_have_animations() && unsafe { Gecko_ElementHasCSSAnimations(self.0) } - } - - fn has_css_transitions(&self, _: &SharedStyleContext, _: Option<PseudoElement>) -> bool { - self.may_have_animations() && unsafe { Gecko_ElementHasCSSTransitions(self.0) } - } - - // Detect if there are any changes that require us to update transitions. - // - // This is used as a more thoroughgoing check than the cheaper - // might_need_transitions_update check. - // - // The following logic shadows the logic used on the Gecko side - // (nsTransitionManager::DoUpdateTransitions) where we actually perform the - // update. - // - // https://drafts.csswg.org/css-transitions/#starting - fn needs_transitions_update( - &self, - before_change_style: &ComputedValues, - after_change_style: &ComputedValues, - ) -> bool { - use crate::properties::LonghandIdSet; - - let after_change_ui_style = after_change_style.get_ui(); - let existing_transitions = self.css_transitions_info(); - - if after_change_style.get_box().clone_display().is_none() { - // We need to cancel existing transitions. - return !existing_transitions.is_empty(); - } - - let mut transitions_to_keep = LonghandIdSet::new(); - for transition_property in after_change_style.transition_properties() { - let physical_longhand = transition_property - .longhand_id - .to_physical(after_change_style.writing_mode); - transitions_to_keep.insert(physical_longhand); - if self.needs_transitions_update_per_property( - physical_longhand, - after_change_ui_style - .transition_combined_duration_at(transition_property.index) - .seconds(), - before_change_style, - after_change_style, - &existing_transitions, - ) { - return true; - } - } - - // Check if we have to cancel the running transition because this is not - // a matching transition-property value. - existing_transitions - .keys() - .any(|property| !transitions_to_keep.contains(*property)) - } - - /// Whether there is an ElementData container. - #[inline] - fn has_data(&self) -> bool { - self.get_data().is_some() - } - - /// Immutably borrows the ElementData. - fn borrow_data(&self) -> Option<AtomicRef<ElementData>> { - self.get_data().map(|x| x.borrow()) - } - - /// Mutably borrows the ElementData. - fn mutate_data(&self) -> Option<AtomicRefMut<ElementData>> { - self.get_data().map(|x| x.borrow_mut()) - } - - #[inline] - fn lang_attr(&self) -> Option<AttrValue> { - let ptr = unsafe { bindings::Gecko_LangValue(self.0) }; - if ptr.is_null() { - None - } else { - Some(AtomString(unsafe { Atom::from_addrefed(ptr) })) - } - } - - fn match_element_lang(&self, override_lang: Option<Option<AttrValue>>, value: &Lang) -> bool { - // Gecko supports :lang() from CSS Selectors 4, which accepts a list - // of language tags, and does BCP47-style range matching. - let override_lang_ptr = match override_lang { - Some(Some(ref atom)) => atom.as_ptr(), - _ => ptr::null_mut(), - }; - value.0.iter().any(|lang| unsafe { - Gecko_MatchLang( - self.0, - override_lang_ptr, - override_lang.is_some(), - lang.as_slice().as_ptr(), - ) - }) - } - - fn is_html_document_body_element(&self) -> bool { - if self.local_name() != &**local_name!("body") { - return false; - } - - if !self.is_html_element() { - return false; - } - - unsafe { bindings::Gecko_IsDocumentBody(self.0) } - } - - fn synthesize_presentational_hints_for_legacy_attributes<V>( - &self, - visited_handling: VisitedHandlingMode, - hints: &mut V, - ) where - V: Push<ApplicableDeclarationBlock>, - { - use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang; - use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; - use crate::stylesheets::layer_rule::LayerOrder; - use crate::values::specified::{color::Color, font::XTextScale}; - lazy_static! { - static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::Color(SpecifiedColor(Color::InheritFromBodyQuirk.into())), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; - static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::XLang(SpecifiedLang(atom!("x-math"))), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; - static ref SVG_TEXT_DISABLE_SCALE_RULE: ApplicableDeclarationBlock = { - let global_style_data = &*GLOBAL_STYLE_DATA; - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::XTextScale(XTextScale::None), - Importance::Normal, - ); - let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - ) - }; - }; - - let ns = self.namespace_id(); - // <th> elements get a default MozCenterOrInherit which may get overridden - if ns == structs::kNameSpaceID_XHTML as i32 { - if self.local_name().as_ptr() == atom!("table").as_ptr() && - self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks - { - hints.push(TABLE_COLOR_RULE.clone()); - } - } - if ns == structs::kNameSpaceID_SVG as i32 { - if self.local_name().as_ptr() == atom!("text").as_ptr() { - hints.push(SVG_TEXT_DISABLE_SCALE_RULE.clone()); - } - } - let declarations = - unsafe { Gecko_GetHTMLPresentationAttrDeclarationBlock(self.0).as_ref() }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - - // Support for link, vlink, and alink presentation hints on <body> - if self.is_link() { - // Unvisited vs. visited styles are computed up-front based on the - // visited mode (not the element's actual state). - let declarations = match visited_handling { - VisitedHandlingMode::AllLinksVisitedAndUnvisited => { - unreachable!( - "We should never try to selector match with \ - AllLinksVisitedAndUnvisited" - ); - }, - VisitedHandlingMode::AllLinksUnvisited => unsafe { - Gecko_GetUnvisitedLinkAttrDeclarationBlock(self.0).as_ref() - }, - VisitedHandlingMode::RelevantLinkVisited => unsafe { - Gecko_GetVisitedLinkAttrDeclarationBlock(self.0).as_ref() - }, - }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - - let active = self - .state() - .intersects(NonTSPseudoClass::Active.state_flag()); - if active { - let declarations = - unsafe { Gecko_GetActiveLinkAttrDeclarationBlock(self.0).as_ref() }; - if let Some(decl) = declarations { - hints.push(ApplicableDeclarationBlock::from_declarations( - unsafe { Arc::from_raw_addrefed(decl) }, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )); - } - } - } - - // xml:lang has precedence over lang, which can be - // set by Gecko_GetHTMLPresentationAttrDeclarationBlock - // - // http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#language - let ptr = unsafe { bindings::Gecko_GetXMLLangValue(self.0) }; - if !ptr.is_null() { - let global_style_data = &*GLOBAL_STYLE_DATA; - - let pdb = PropertyDeclarationBlock::with_one( - PropertyDeclaration::XLang(SpecifiedLang(unsafe { Atom::from_addrefed(ptr) })), - Importance::Normal, - ); - let arc = Arc::new(global_style_data.shared_lock.wrap(pdb)); - hints.push(ApplicableDeclarationBlock::from_declarations( - arc, - ServoCascadeLevel::PresHints, - LayerOrder::root(), - )) - } - // MathML's default lang has precedence over both `lang` and `xml:lang` - if ns == structs::kNameSpaceID_MathML as i32 { - if self.local_name().as_ptr() == atom!("math").as_ptr() { - hints.push(MATHML_LANG_RULE.clone()); - } - } - } -} - -impl<'le> PartialEq for GeckoElement<'le> { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.0 as *const _ == other.0 as *const _ - } -} - -impl<'le> Eq for GeckoElement<'le> {} - -impl<'le> Hash for GeckoElement<'le> { - #[inline] - fn hash<H: Hasher>(&self, state: &mut H) { - (self.0 as *const RawGeckoElement).hash(state); - } -} - -impl<'le> ::selectors::Element for GeckoElement<'le> { - type Impl = SelectorImpl; - - #[inline] - fn opaque(&self) -> OpaqueElement { - OpaqueElement::new(self.0) - } - - #[inline] - fn parent_element(&self) -> Option<Self> { - let parent_node = self.as_node().parent_node(); - parent_node.and_then(|n| n.as_element()) - } - - #[inline] - fn parent_node_is_shadow_root(&self) -> bool { - self.as_node() - .parent_node() - .map_or(false, |p| p.is_shadow_root()) - } - - #[inline] - fn containing_shadow_host(&self) -> Option<Self> { - let shadow = self.containing_shadow()?; - Some(shadow.host()) - } - - #[inline] - fn is_pseudo_element(&self) -> bool { - self.implemented_pseudo_element().is_some() - } - - #[inline] - fn pseudo_element_originating_element(&self) -> Option<Self> { - debug_assert!(self.is_pseudo_element()); - debug_assert!(!self.matches_user_and_content_rules()); - let mut current = *self; - loop { - if current.is_root_of_native_anonymous_subtree() { - return current.traversal_parent(); - } - - current = current.traversal_parent()?; - } - } - - #[inline] - fn assigned_slot(&self) -> Option<Self> { - let slot = self.extended_slots()?._base.mAssignedSlot.mRawPtr; - - unsafe { Some(GeckoElement(&slot.as_ref()?._base._base._base._base)) } - } - - #[inline] - fn prev_sibling_element(&self) -> Option<Self> { - let mut sibling = self.as_node().prev_sibling(); - while let Some(sibling_node) = sibling { - if let Some(el) = sibling_node.as_element() { - return Some(el); - } - sibling = sibling_node.prev_sibling(); - } - None - } - - #[inline] - fn next_sibling_element(&self) -> Option<Self> { - let mut sibling = self.as_node().next_sibling(); - while let Some(sibling_node) = sibling { - if let Some(el) = sibling_node.as_element() { - return Some(el); - } - sibling = sibling_node.next_sibling(); - } - None - } - - #[inline] - fn first_element_child(&self) -> Option<Self> { - let mut child = self.as_node().first_child(); - while let Some(child_node) = child { - if let Some(el) = child_node.as_element() { - return Some(el); - } - child = child_node.next_sibling(); - } - None - } - - fn apply_selector_flags(&self, flags: ElementSelectorFlags) { - // Handle flags that apply to the element. - let self_flags = flags.for_self(); - if !self_flags.is_empty() { - self.set_flags(selector_flags_to_node_flags(flags)) - } - - // Handle flags that apply to the parent. - let parent_flags = flags.for_parent(); - if !parent_flags.is_empty() { - if let Some(p) = self.as_node().parent_node() { - if p.is_element() || p.is_shadow_root() { - p.set_flags(selector_flags_to_node_flags(parent_flags)); - } - } - } - } - - fn has_attr_in_no_namespace(&self, local_name: &LocalName) -> bool { - for attr in self.iter_attrs() { - if attr.mName.mBits == local_name.as_ptr() as usize { - return true; - } - } - false - } - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, - ) -> bool { - if self.attrs().is_none() { - return false; - } - snapshot_helpers::attr_matches(self.iter_attrs(), ns, local_name, operation) - } - - #[inline] - fn is_root(&self) -> bool { - if self - .as_node() - .get_bool_flag(nsINode_BooleanFlag::ParentIsContent) - { - return false; - } - - if !self.as_node().is_in_document() { - return false; - } - - debug_assert!(self - .as_node() - .parent_node() - .map_or(false, |p| p.is_document())); - // XXX this should always return true at this point, shouldn't it? - unsafe { bindings::Gecko_IsRootElement(self.0) } - } - - fn is_empty(&self) -> bool { - !self - .as_node() - .dom_children() - .any(|child| unsafe { Gecko_IsSignificantChild(child.0, true) }) - } - - #[inline] - fn has_local_name(&self, name: &WeakAtom) -> bool { - self.local_name() == name - } - - #[inline] - fn has_namespace(&self, ns: &WeakNamespace) -> bool { - self.namespace() == ns - } - - #[inline] - fn is_same_type(&self, other: &Self) -> bool { - self.local_name() == other.local_name() && self.namespace() == other.namespace() - } - - fn match_non_ts_pseudo_class( - &self, - pseudo_class: &NonTSPseudoClass, - context: &mut MatchingContext<Self::Impl>, - ) -> bool { - use selectors::matching::*; - match *pseudo_class { - NonTSPseudoClass::Autofill | - NonTSPseudoClass::Defined | - NonTSPseudoClass::Focus | - NonTSPseudoClass::Enabled | - NonTSPseudoClass::Disabled | - NonTSPseudoClass::Checked | - NonTSPseudoClass::Fullscreen | - NonTSPseudoClass::Indeterminate | - NonTSPseudoClass::MozInert | - NonTSPseudoClass::PopoverOpen | - NonTSPseudoClass::PlaceholderShown | - NonTSPseudoClass::Target | - NonTSPseudoClass::Valid | - NonTSPseudoClass::Invalid | - NonTSPseudoClass::MozBroken | - NonTSPseudoClass::MozLoading | - NonTSPseudoClass::Required | - NonTSPseudoClass::Optional | - NonTSPseudoClass::ReadOnly | - NonTSPseudoClass::ReadWrite | - NonTSPseudoClass::FocusWithin | - NonTSPseudoClass::FocusVisible | - NonTSPseudoClass::MozDragOver | - NonTSPseudoClass::MozDevtoolsHighlighted | - NonTSPseudoClass::MozStyleeditorTransitioning | - NonTSPseudoClass::MozMathIncrementScriptLevel | - NonTSPseudoClass::InRange | - NonTSPseudoClass::OutOfRange | - NonTSPseudoClass::Default | - NonTSPseudoClass::UserValid | - NonTSPseudoClass::UserInvalid | - NonTSPseudoClass::MozMeterOptimum | - NonTSPseudoClass::MozMeterSubOptimum | - NonTSPseudoClass::MozMeterSubSubOptimum | - NonTSPseudoClass::MozHasDirAttr | - NonTSPseudoClass::MozDirAttrLTR | - NonTSPseudoClass::MozDirAttrRTL | - NonTSPseudoClass::MozDirAttrLikeAuto | - NonTSPseudoClass::Modal | - NonTSPseudoClass::MozTopmostModal | - NonTSPseudoClass::Active | - NonTSPseudoClass::Hover | - NonTSPseudoClass::MozAutofillPreview | - NonTSPseudoClass::MozRevealed | - NonTSPseudoClass::MozValueEmpty | - NonTSPseudoClass::Dir(..) => self.state().intersects(pseudo_class.state_flag()), - NonTSPseudoClass::AnyLink => self.is_link(), - NonTSPseudoClass::Link => { - self.is_link() && context.visited_handling().matches_unvisited() - }, - NonTSPseudoClass::Visited => { - self.is_link() && context.visited_handling().matches_visited() - }, - NonTSPseudoClass::MozFirstNode => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } - let mut elem = self.as_node(); - while let Some(prev) = elem.prev_sibling() { - if prev.contains_non_whitespace_content() { - return false; - } - elem = prev; - } - true - }, - NonTSPseudoClass::MozLastNode => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - } - let mut elem = self.as_node(); - while let Some(next) = elem.next_sibling() { - if next.contains_non_whitespace_content() { - return false; - } - elem = next; - } - true - }, - NonTSPseudoClass::MozOnlyWhitespace => { - if context.needs_selector_flags() { - self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); - } - if self - .as_node() - .dom_children() - .any(|c| c.contains_non_whitespace_content()) - { - return false; - } - true - }, - NonTSPseudoClass::MozNativeAnonymous => !self.matches_user_and_content_rules(), - NonTSPseudoClass::MozUseShadowTreeRoot => self.is_root_of_use_element_shadow_tree(), - NonTSPseudoClass::MozTableBorderNonzero => unsafe { - bindings::Gecko_IsTableBorderNonzero(self.0) - }, - NonTSPseudoClass::MozBrowserFrame => unsafe { bindings::Gecko_IsBrowserFrame(self.0) }, - NonTSPseudoClass::MozSelectListBox => unsafe { - bindings::Gecko_IsSelectListBox(self.0) - }, - NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(), - - NonTSPseudoClass::MozLWTheme | - NonTSPseudoClass::MozLocaleDir(..) | - NonTSPseudoClass::MozWindowInactive => { - let state_bit = pseudo_class.document_state_flag(); - if state_bit.is_empty() { - debug_assert!( - matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), - "Only moz-locale-dir should ever return an empty state" - ); - return false; - } - if context - .extra_data - .invalidation_data - .document_state - .intersects(state_bit) - { - return !context.in_negation(); - } - self.document_state().contains(state_bit) - }, - NonTSPseudoClass::MozPlaceholder => false, - NonTSPseudoClass::Lang(ref lang_arg) => self.match_element_lang(None, lang_arg), - } - } - - fn match_pseudo_element( - &self, - pseudo_element: &PseudoElement, - _context: &mut MatchingContext<Self::Impl>, - ) -> bool { - // TODO(emilio): I believe we could assert we are a pseudo-element and - // match the proper pseudo-element, given how we rulehash the stuff - // based on the pseudo. - match self.implemented_pseudo_element() { - Some(ref pseudo) => *pseudo == *pseudo_element, - None => false, - } - } - - #[inline] - fn is_link(&self) -> bool { - self.state().intersects(ElementState::VISITED_OR_UNVISITED) - } - - #[inline] - fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - if !self.has_id() { - return false; - } - - let element_id = match snapshot_helpers::get_id(self.non_mapped_attrs()) { - Some(id) => id, - None => return false, - }; - - case_sensitivity.eq_atom(element_id, id) - } - - #[inline] - fn is_part(&self, name: &AtomIdent) -> bool { - let attr = match self.get_part_attr() { - Some(c) => c, - None => return false, - }; - - snapshot_helpers::has_class_or_part(name, CaseSensitivity::CaseSensitive, attr) - } - - #[inline] - fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> { - snapshot_helpers::imported_part(self.non_mapped_attrs(), name) - } - - #[inline(always)] - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - let attr = match self.get_class_attr() { - Some(c) => c, - None => return false, - }; - - snapshot_helpers::has_class_or_part(name, case_sensitivity, attr) - } - - #[inline] - fn is_html_element_in_html_document(&self) -> bool { - self.is_html_element() && self.as_node().owner_doc().is_html_document() - } - - #[inline] - fn is_html_slot_element(&self) -> bool { - self.is_html_element() && self.local_name().as_ptr() == local_name!("slot").as_ptr() - } - - #[inline] - fn ignores_nth_child_selectors(&self) -> bool { - self.is_root_of_native_anonymous_subtree() - } -} diff --git a/components/style/gecko_bindings/mod.rs b/components/style/gecko_bindings/mod.rs deleted file mode 100644 index f0b0adc7ecb..00000000000 --- a/components/style/gecko_bindings/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -/* 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/. */ - -//! Gecko's C++ bindings, along with some rust helpers to ease its use. - -// FIXME: We allow `improper_ctypes` (for now), because the lint doesn't allow -// foreign structs to have `PhantomData`. We should remove this once the lint -// ignores this case. - -#[allow( - dead_code, - improper_ctypes, - non_camel_case_types, - non_snake_case, - non_upper_case_globals, - missing_docs -)] -// TODO: Remove this when updating bindgen, see -// https://github.com/rust-lang/rust-bindgen/issues/1651 -#[cfg_attr(test, allow(deref_nullptr))] -pub mod structs { - include!(concat!(env!("OUT_DIR"), "/gecko/structs.rs")); -} - -pub use self::structs as bindings; - -pub mod sugar; diff --git a/components/style/gecko_bindings/sugar/mod.rs b/components/style/gecko_bindings/sugar/mod.rs deleted file mode 100644 index 00faf63ba66..00000000000 --- a/components/style/gecko_bindings/sugar/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -/* 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/. */ - -//! Rust sugar and convenience methods for Gecko types. - -mod ns_com_ptr; -mod ns_compatibility; -mod ns_style_auto_array; -mod ns_t_array; -pub mod origin_flags; -pub mod ownership; -pub mod refptr; diff --git a/components/style/gecko_bindings/sugar/ns_com_ptr.rs b/components/style/gecko_bindings/sugar/ns_com_ptr.rs deleted file mode 100644 index 1c54541bd80..00000000000 --- a/components/style/gecko_bindings/sugar/ns_com_ptr.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* 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/. */ - -//! Little helpers for `nsCOMPtr`. - -use crate::gecko_bindings::structs::nsCOMPtr; - -impl<T> nsCOMPtr<T> { - /// Get this pointer as a raw pointer. - #[inline] - pub fn raw(&self) -> *mut T { - self.mRawPtr - } -} diff --git a/components/style/gecko_bindings/sugar/ns_compatibility.rs b/components/style/gecko_bindings/sugar/ns_compatibility.rs deleted file mode 100644 index f4b81e9f790..00000000000 --- a/components/style/gecko_bindings/sugar/ns_compatibility.rs +++ /dev/null @@ -1,19 +0,0 @@ -/* 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/. */ - -//! Little helper for `nsCompatibility`. - -use crate::context::QuirksMode; -use crate::gecko_bindings::structs::nsCompatibility; - -impl From<nsCompatibility> for QuirksMode { - #[inline] - fn from(mode: nsCompatibility) -> QuirksMode { - match mode { - nsCompatibility::eCompatibility_FullStandards => QuirksMode::NoQuirks, - nsCompatibility::eCompatibility_AlmostStandards => QuirksMode::LimitedQuirks, - nsCompatibility::eCompatibility_NavQuirks => QuirksMode::Quirks, - } - } -} diff --git a/components/style/gecko_bindings/sugar/ns_style_auto_array.rs b/components/style/gecko_bindings/sugar/ns_style_auto_array.rs deleted file mode 100644 index b5772a6c77a..00000000000 --- a/components/style/gecko_bindings/sugar/ns_style_auto_array.rs +++ /dev/null @@ -1,111 +0,0 @@ -/* 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/. */ - -//! Rust helpers for Gecko's `nsStyleAutoArray`. - -use crate::gecko_bindings::bindings::Gecko_EnsureStyleAnimationArrayLength; -use crate::gecko_bindings::bindings::Gecko_EnsureStyleScrollTimelineArrayLength; -use crate::gecko_bindings::bindings::Gecko_EnsureStyleTransitionArrayLength; -use crate::gecko_bindings::bindings::Gecko_EnsureStyleViewTimelineArrayLength; -use crate::gecko_bindings::structs::nsStyleAutoArray; -use crate::gecko_bindings::structs::{StyleAnimation, StyleTransition}; -use crate::gecko_bindings::structs::{StyleScrollTimeline, StyleViewTimeline}; -use std::iter::{once, Chain, IntoIterator, Once}; -use std::ops::{Index, IndexMut}; -use std::slice::{Iter, IterMut}; - -impl<T> Index<usize> for nsStyleAutoArray<T> { - type Output = T; - fn index(&self, index: usize) -> &T { - match index { - 0 => &self.mFirstElement, - _ => &self.mOtherElements[index - 1], - } - } -} - -impl<T> IndexMut<usize> for nsStyleAutoArray<T> { - fn index_mut(&mut self, index: usize) -> &mut T { - match index { - 0 => &mut self.mFirstElement, - _ => &mut self.mOtherElements[index - 1], - } - } -} - -impl<T> nsStyleAutoArray<T> { - /// Mutably iterate over the array elements. - pub fn iter_mut(&mut self) -> Chain<Once<&mut T>, IterMut<T>> { - once(&mut self.mFirstElement).chain(self.mOtherElements.iter_mut()) - } - - /// Iterate over the array elements. - pub fn iter(&self) -> Chain<Once<&T>, Iter<T>> { - once(&self.mFirstElement).chain(self.mOtherElements.iter()) - } - - /// Returns the length of the array. - /// - /// Note that often structs containing autoarrays will have additional - /// member fields that contain the length, which must be kept in sync. - pub fn len(&self) -> usize { - 1 + self.mOtherElements.len() - } -} - -impl nsStyleAutoArray<StyleAnimation> { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleAnimationArrayLength( - self as *mut nsStyleAutoArray<StyleAnimation> as *mut _, - len, - ); - } - } -} - -impl nsStyleAutoArray<StyleTransition> { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleTransitionArrayLength( - self as *mut nsStyleAutoArray<StyleTransition> as *mut _, - len, - ); - } - } -} - -impl nsStyleAutoArray<StyleViewTimeline> { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleViewTimelineArrayLength( - self as *mut nsStyleAutoArray<StyleViewTimeline> as *mut _, - len, - ); - } - } -} - -impl nsStyleAutoArray<StyleScrollTimeline> { - /// Ensures that the array has length at least the given length. - pub fn ensure_len(&mut self, len: usize) { - unsafe { - Gecko_EnsureStyleScrollTimelineArrayLength( - self as *mut nsStyleAutoArray<StyleScrollTimeline> as *mut _, - len, - ); - } - } -} - -impl<'a, T> IntoIterator for &'a mut nsStyleAutoArray<T> { - type Item = &'a mut T; - type IntoIter = Chain<Once<&'a mut T>, IterMut<'a, T>>; - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} diff --git a/components/style/gecko_bindings/sugar/ns_t_array.rs b/components/style/gecko_bindings/sugar/ns_t_array.rs deleted file mode 100644 index d10ed420dd8..00000000000 --- a/components/style/gecko_bindings/sugar/ns_t_array.rs +++ /dev/null @@ -1,144 +0,0 @@ -/* 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/. */ - -//! Rust helpers for Gecko's nsTArray. - -use crate::gecko_bindings::bindings; -use crate::gecko_bindings::structs::{nsTArray, nsTArrayHeader, CopyableTArray}; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::slice; - -impl<T> Deref for nsTArray<T> { - type Target = [T]; - - #[inline] - fn deref<'a>(&'a self) -> &'a [T] { - unsafe { slice::from_raw_parts(self.slice_begin(), self.header().mLength as usize) } - } -} - -impl<T> DerefMut for nsTArray<T> { - fn deref_mut<'a>(&'a mut self) -> &'a mut [T] { - unsafe { slice::from_raw_parts_mut(self.slice_begin(), self.header().mLength as usize) } - } -} - -impl<T> nsTArray<T> { - #[inline] - fn header<'a>(&'a self) -> &'a nsTArrayHeader { - debug_assert!(!self.mBuffer.is_null()); - unsafe { mem::transmute(self.mBuffer) } - } - - // unsafe, since header may be in shared static or something - unsafe fn header_mut<'a>(&'a mut self) -> &'a mut nsTArrayHeader { - debug_assert!(!self.mBuffer.is_null()); - - mem::transmute(self.mBuffer) - } - - #[inline] - unsafe fn slice_begin(&self) -> *mut T { - debug_assert!(!self.mBuffer.is_null()); - (self.mBuffer as *const nsTArrayHeader).offset(1) as *mut _ - } - - /// Ensures the array has enough capacity at least to hold `cap` elements. - /// - /// NOTE: This doesn't call the constructor on the values! - pub fn ensure_capacity(&mut self, cap: usize) { - if cap >= self.len() { - unsafe { - bindings::Gecko_EnsureTArrayCapacity( - self as *mut nsTArray<T> as *mut _, - cap, - mem::size_of::<T>(), - ) - } - } - } - - /// Clears the array storage without calling the destructor on the values. - #[inline] - pub unsafe fn clear(&mut self) { - if self.len() != 0 { - bindings::Gecko_ClearPODTArray( - self as *mut nsTArray<T> as *mut _, - mem::size_of::<T>(), - mem::align_of::<T>(), - ); - } - } - - /// Clears a POD array. This is safe since copy types are memcopyable. - #[inline] - pub fn clear_pod(&mut self) - where - T: Copy, - { - unsafe { self.clear() } - } - - /// Resize and set the length of the array to `len`. - /// - /// unsafe because this may leave the array with uninitialized elements. - /// - /// This will not call constructors. If you need that, either manually add - /// bindings or run the typed `EnsureCapacity` call on the gecko side. - pub unsafe fn set_len(&mut self, len: u32) { - // this can leak - debug_assert!(len >= self.len() as u32); - if self.len() == len as usize { - return; - } - self.ensure_capacity(len as usize); - self.header_mut().mLength = len; - } - - /// Resizes an array containing only POD elements - /// - /// unsafe because this may leave the array with uninitialized elements. - /// - /// This will not leak since it only works on POD types (and thus doesn't assert) - pub unsafe fn set_len_pod(&mut self, len: u32) - where - T: Copy, - { - if self.len() == len as usize { - return; - } - self.ensure_capacity(len as usize); - let header = self.header_mut(); - header.mLength = len; - } - - /// Collects the given iterator into this array. - /// - /// Not unsafe because we won't leave uninitialized elements in the array. - pub fn assign_from_iter_pod<I>(&mut self, iter: I) - where - T: Copy, - I: ExactSizeIterator + Iterator<Item = T>, - { - debug_assert!(iter.len() <= 0xFFFFFFFF); - unsafe { - self.set_len_pod(iter.len() as u32); - } - self.iter_mut().zip(iter).for_each(|(r, v)| *r = v); - } -} - -impl<T> Deref for CopyableTArray<T> { - type Target = nsTArray<T>; - fn deref(&self) -> &Self::Target { - &self._base - } -} - -impl<T> DerefMut for CopyableTArray<T> { - fn deref_mut(&mut self) -> &mut nsTArray<T> { - &mut self._base - } -} diff --git a/components/style/gecko_bindings/sugar/origin_flags.rs b/components/style/gecko_bindings/sugar/origin_flags.rs deleted file mode 100644 index e0f0981c5d8..00000000000 --- a/components/style/gecko_bindings/sugar/origin_flags.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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/. */ - -//! Helper to iterate over `OriginFlags` bits. - -use crate::gecko_bindings::structs::OriginFlags; -use crate::stylesheets::OriginSet; - -/// Checks that the values for OriginFlags are the ones we expect. -pub fn assert_flags_match() { - use crate::stylesheets::origin::*; - debug_assert_eq!( - OriginFlags::UserAgent.0, - OriginSet::ORIGIN_USER_AGENT.bits() - ); - debug_assert_eq!(OriginFlags::Author.0, OriginSet::ORIGIN_AUTHOR.bits()); - debug_assert_eq!(OriginFlags::User.0, OriginSet::ORIGIN_USER.bits()); -} - -impl From<OriginFlags> for OriginSet { - fn from(flags: OriginFlags) -> Self { - Self::from_bits_truncate(flags.0) - } -} - -impl From<OriginSet> for OriginFlags { - fn from(set: OriginSet) -> Self { - OriginFlags(set.bits()) - } -} diff --git a/components/style/gecko_bindings/sugar/ownership.rs b/components/style/gecko_bindings/sugar/ownership.rs deleted file mode 100644 index 31b512cf1e8..00000000000 --- a/components/style/gecko_bindings/sugar/ownership.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* 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/. */ - -//! Helpers for different FFI pointer kinds that Gecko's FFI layer uses. - -use crate::gecko_bindings::structs::root::mozilla::detail::CopyablePtr; -use servo_arc::Arc; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::ptr; - -/// Gecko-FFI-safe Arc (T is an ArcInner). -/// -/// This can be null. -/// -/// Leaks on drop. Please don't drop this. -#[repr(C)] -pub struct Strong<GeckoType> { - ptr: *const GeckoType, - _marker: PhantomData<GeckoType>, -} - -impl<T> From<Arc<T>> for Strong<T> { - fn from(arc: Arc<T>) -> Self { - Self { - ptr: Arc::into_raw(arc), - _marker: PhantomData, - } - } -} - -impl<GeckoType> Strong<GeckoType> { - #[inline] - /// Returns whether this reference is null. - pub fn is_null(&self) -> bool { - self.ptr.is_null() - } - - #[inline] - /// Returns a null pointer - pub fn null() -> Self { - Self { - ptr: ptr::null(), - _marker: PhantomData, - } - } -} - -impl<T> Deref for CopyablePtr<T> { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.mPtr - } -} - -impl<T> DerefMut for CopyablePtr<T> { - fn deref_mut<'a>(&'a mut self) -> &'a mut T { - &mut self.mPtr - } -} diff --git a/components/style/gecko_bindings/sugar/refptr.rs b/components/style/gecko_bindings/sugar/refptr.rs deleted file mode 100644 index c4a0479a077..00000000000 --- a/components/style/gecko_bindings/sugar/refptr.rs +++ /dev/null @@ -1,289 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A rust helper to ease the use of Gecko's refcounted types. - -use crate::gecko_bindings::{bindings, structs}; -use crate::Atom; -use servo_arc::Arc; -use std::fmt::Write; -use std::marker::PhantomData; -use std::ops::Deref; -use std::{fmt, mem, ptr}; - -/// Trait for all objects that have Addref() and Release -/// methods and can be placed inside RefPtr<T> -pub unsafe trait RefCounted { - /// Bump the reference count. - fn addref(&self); - /// Decrease the reference count. - unsafe fn release(&self); -} - -/// Trait for types which can be shared across threads in RefPtr. -pub unsafe trait ThreadSafeRefCounted: RefCounted {} - -/// A custom RefPtr implementation to take into account Drop semantics and -/// a bit less-painful memory management. -pub struct RefPtr<T: RefCounted> { - ptr: *mut T, - _marker: PhantomData<T>, -} - -impl<T: RefCounted> fmt::Debug for RefPtr<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("RefPtr { ")?; - self.ptr.fmt(f)?; - f.write_char('}') - } -} - -impl<T: RefCounted> RefPtr<T> { - /// Create a new RefPtr from an already addrefed pointer obtained from FFI. - /// - /// The pointer must be valid, non-null and have been addrefed. - pub unsafe fn from_addrefed(ptr: *mut T) -> Self { - debug_assert!(!ptr.is_null()); - RefPtr { - ptr, - _marker: PhantomData, - } - } - - /// Returns whether the current pointer is null. - pub fn is_null(&self) -> bool { - self.ptr.is_null() - } - - /// Returns a null pointer. - pub fn null() -> Self { - Self { - ptr: ptr::null_mut(), - _marker: PhantomData, - } - } - - /// Create a new RefPtr from a pointer obtained from FFI. - /// - /// This method calls addref() internally - pub unsafe fn new(ptr: *mut T) -> Self { - let ret = RefPtr { - ptr, - _marker: PhantomData, - }; - ret.addref(); - ret - } - - /// Produces an FFI-compatible RefPtr that can be stored in style structs. - /// - /// structs::RefPtr does not have a destructor, so this may leak - pub fn forget(self) -> structs::RefPtr<T> { - let ret = structs::RefPtr { - mRawPtr: self.ptr, - _phantom_0: PhantomData, - }; - mem::forget(self); - ret - } - - /// Returns the raw inner pointer to be fed back into FFI. - pub fn get(&self) -> *mut T { - self.ptr - } - - /// Addref the inner data, obviously leaky on its own. - pub fn addref(&self) { - if !self.ptr.is_null() { - unsafe { - (*self.ptr).addref(); - } - } - } - - /// Release the inner data. - /// - /// Call only when the data actually needs releasing. - pub unsafe fn release(&self) { - if !self.ptr.is_null() { - (*self.ptr).release(); - } - } -} - -impl<T: RefCounted> Deref for RefPtr<T> { - type Target = T; - fn deref(&self) -> &T { - debug_assert!(!self.ptr.is_null()); - unsafe { &*self.ptr } - } -} - -impl<T: RefCounted> structs::RefPtr<T> { - /// Produces a Rust-side RefPtr from an FFI RefPtr, bumping the refcount. - /// - /// Must be called on a valid, non-null structs::RefPtr<T>. - pub unsafe fn to_safe(&self) -> RefPtr<T> { - let r = RefPtr { - ptr: self.mRawPtr, - _marker: PhantomData, - }; - r.addref(); - r - } - /// Produces a Rust-side RefPtr, consuming the existing one (and not bumping - /// the refcount). - pub unsafe fn into_safe(self) -> RefPtr<T> { - debug_assert!(!self.mRawPtr.is_null()); - RefPtr { - ptr: self.mRawPtr, - _marker: PhantomData, - } - } - - /// Replace a structs::RefPtr<T> with a different one, appropriately - /// addref/releasing. - /// - /// Both `self` and `other` must be valid, but can be null. - /// - /// Safe when called on an aliased pointer because the refcount in that case - /// needs to be at least two. - pub unsafe fn set(&mut self, other: &Self) { - self.clear(); - if !other.mRawPtr.is_null() { - *self = other.to_safe().forget(); - } - } - - /// Clear an instance of the structs::RefPtr<T>, by releasing - /// it and setting its contents to null. - /// - /// `self` must be valid, but can be null. - pub unsafe fn clear(&mut self) { - if !self.mRawPtr.is_null() { - (*self.mRawPtr).release(); - self.mRawPtr = ptr::null_mut(); - } - } - - /// Replace a `structs::RefPtr<T>` with a `RefPtr<T>`, - /// consuming the `RefPtr<T>`, and releasing the old - /// value in `self` if necessary. - /// - /// `self` must be valid, possibly null. - pub fn set_move(&mut self, other: RefPtr<T>) { - if !self.mRawPtr.is_null() { - unsafe { - (*self.mRawPtr).release(); - } - } - *self = other.forget(); - } -} - -impl<T> structs::RefPtr<T> { - /// Returns a new, null refptr. - pub fn null() -> Self { - Self { - mRawPtr: ptr::null_mut(), - _phantom_0: PhantomData, - } - } - - /// Create a new RefPtr from an arc. - pub fn from_arc(s: Arc<T>) -> Self { - Self { - mRawPtr: Arc::into_raw(s) as *mut _, - _phantom_0: PhantomData, - } - } - - /// Sets the contents to an Arc<T>. - pub fn set_arc(&mut self, other: Arc<T>) { - unsafe { - if !self.mRawPtr.is_null() { - let _ = Arc::from_raw(self.mRawPtr); - } - self.mRawPtr = Arc::into_raw(other) as *mut _; - } - } -} - -impl<T: RefCounted> Drop for RefPtr<T> { - fn drop(&mut self) { - unsafe { self.release() } - } -} - -impl<T: RefCounted> Clone for RefPtr<T> { - fn clone(&self) -> Self { - self.addref(); - RefPtr { - ptr: self.ptr, - _marker: PhantomData, - } - } -} - -impl<T: RefCounted> PartialEq for RefPtr<T> { - fn eq(&self, other: &Self) -> bool { - self.ptr == other.ptr - } -} - -unsafe impl<T: ThreadSafeRefCounted> Send for RefPtr<T> {} -unsafe impl<T: ThreadSafeRefCounted> Sync for RefPtr<T> {} - -macro_rules! impl_refcount { - ($t:ty, $addref:path, $release:path) => { - unsafe impl RefCounted for $t { - #[inline] - fn addref(&self) { - unsafe { $addref(self as *const _ as *mut _) } - } - - #[inline] - unsafe fn release(&self) { - $release(self as *const _ as *mut _) - } - } - }; -} - -// Companion of NS_DECL_THREADSAFE_FFI_REFCOUNTING. -// -// Gets you a free RefCounted impl implemented via FFI. -macro_rules! impl_threadsafe_refcount { - ($t:ty, $addref:path, $release:path) => { - impl_refcount!($t, $addref, $release); - unsafe impl ThreadSafeRefCounted for $t {} - }; -} - -impl_threadsafe_refcount!( - structs::mozilla::URLExtraData, - bindings::Gecko_AddRefURLExtraDataArbitraryThread, - bindings::Gecko_ReleaseURLExtraDataArbitraryThread -); -impl_threadsafe_refcount!( - structs::nsIURI, - bindings::Gecko_AddRefnsIURIArbitraryThread, - bindings::Gecko_ReleasensIURIArbitraryThread -); -impl_threadsafe_refcount!( - structs::SheetLoadDataHolder, - bindings::Gecko_AddRefSheetLoadDataHolderArbitraryThread, - bindings::Gecko_ReleaseSheetLoadDataHolderArbitraryThread -); - -#[inline] -unsafe fn addref_atom(atom: *mut structs::nsAtom) { - mem::forget(Atom::from_raw(atom)); -} - -#[inline] -unsafe fn release_atom(atom: *mut structs::nsAtom) { - let _ = Atom::from_addrefed(atom); -} -impl_threadsafe_refcount!(structs::nsAtom, addref_atom, release_atom); diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs deleted file mode 100644 index cb040390cfe..00000000000 --- a/components/style/gecko_string_cache/mod.rs +++ /dev/null @@ -1,532 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] -// This is needed for the constants in atom_macro.rs, because we have some -// atoms whose names differ only by case, e.g. datetime and dateTime. -#![allow(non_upper_case_globals)] - -//! A drop-in replacement for string_cache, but backed by Gecko `nsAtom`s. - -use crate::gecko_bindings::bindings::Gecko_AddRefAtom; -use crate::gecko_bindings::bindings::Gecko_Atomize; -use crate::gecko_bindings::bindings::Gecko_Atomize16; -use crate::gecko_bindings::bindings::Gecko_ReleaseAtom; -use crate::gecko_bindings::structs::root::mozilla::detail::gGkAtoms; -use crate::gecko_bindings::structs::root::mozilla::detail::kGkAtomsArrayOffset; -use crate::gecko_bindings::structs::root::mozilla::detail::GkAtoms_Atoms_AtomsCount; -use crate::gecko_bindings::structs::{nsAtom, nsDynamicAtom, nsStaticAtom}; -use nsstring::{nsAString, nsStr}; -use precomputed_hash::PrecomputedHash; -use std::borrow::{Borrow, Cow}; -use std::char::{self, DecodeUtf16}; -use std::fmt::{self, Write}; -use std::hash::{Hash, Hasher}; -use std::iter::Cloned; -use std::mem::{self, ManuallyDrop}; -use std::num::NonZeroUsize; -use std::ops::Deref; -use std::{slice, str}; -use style_traits::SpecifiedValueInfo; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -#[macro_use] -#[allow(improper_ctypes, non_camel_case_types, missing_docs)] -pub mod atom_macro { - include!(concat!(env!("OUT_DIR"), "/gecko/atom_macro.rs")); -} - -#[macro_use] -pub mod namespace; - -pub use self::namespace::{Namespace, WeakNamespace}; - -/// A handle to a Gecko atom. This is a type that can represent either: -/// -/// * A strong reference to a dynamic atom (an `nsAtom` pointer), in which case -/// the `usize` just holds the pointer value. -/// -/// * A byte offset from `gGkAtoms` to the `nsStaticAtom` object (shifted to -/// the left one bit, and with the lower bit set to `1` to differentiate it -/// from the above), so `(offset << 1 | 1)`. -/// -#[derive(Eq, PartialEq)] -#[repr(C)] -pub struct Atom(NonZeroUsize); - -/// An atom *without* a strong reference. -/// -/// Only usable as `&'a WeakAtom`, -/// where `'a` is the lifetime of something that holds a strong reference to that atom. -pub struct WeakAtom(nsAtom); - -/// The number of static atoms we have. -const STATIC_ATOM_COUNT: usize = GkAtoms_Atoms_AtomsCount as usize; - -/// Returns the Gecko static atom array. -/// -/// We have this rather than use rust-bindgen to generate -/// mozilla::detail::gGkAtoms and then just reference gGkAtoms.mAtoms, so we -/// avoid a problem with lld-link.exe on Windows. -/// -/// https://bugzilla.mozilla.org/show_bug.cgi?id=1517685 -#[inline] -fn static_atoms() -> &'static [nsStaticAtom; STATIC_ATOM_COUNT] { - unsafe { - let addr = &gGkAtoms as *const _ as usize + kGkAtomsArrayOffset as usize; - &*(addr as *const _) - } -} - -/// Returns whether the specified address points to one of the nsStaticAtom -/// objects in the Gecko static atom array. -#[inline] -fn valid_static_atom_addr(addr: usize) -> bool { - unsafe { - let atoms = static_atoms(); - let start = atoms.as_ptr(); - let end = atoms.get_unchecked(STATIC_ATOM_COUNT) as *const _; - let in_range = addr >= start as usize && addr < end as usize; - let aligned = addr % mem::align_of::<nsStaticAtom>() == 0; - in_range && aligned - } -} - -impl Deref for Atom { - type Target = WeakAtom; - - #[inline] - fn deref(&self) -> &WeakAtom { - unsafe { - let addr = if self.is_static() { - (&gGkAtoms as *const _ as usize) + (self.0.get() >> 1) - } else { - self.0.get() - }; - debug_assert!(!self.is_static() || valid_static_atom_addr(addr)); - WeakAtom::new(addr as *const nsAtom) - } - } -} - -impl PrecomputedHash for Atom { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.get_hash() - } -} - -impl Borrow<WeakAtom> for Atom { - #[inline] - fn borrow(&self) -> &WeakAtom { - self - } -} - -impl ToShmem for Atom { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - if !self.is_static() { - return Err(format!( - "ToShmem failed for Atom: must be a static atom: {}", - self - )); - } - - Ok(ManuallyDrop::new(Atom(self.0))) - } -} - -impl Eq for WeakAtom {} -impl PartialEq for WeakAtom { - #[inline] - fn eq(&self, other: &Self) -> bool { - let weak: *const WeakAtom = self; - let other: *const WeakAtom = other; - weak == other - } -} - -impl PartialEq<Atom> for WeakAtom { - #[inline] - fn eq(&self, other: &Atom) -> bool { - self == &**other - } -} - -unsafe impl Send for Atom {} -unsafe impl Sync for Atom {} -unsafe impl Sync for WeakAtom {} - -impl WeakAtom { - /// Construct a `WeakAtom` from a raw `nsAtom`. - #[inline] - pub unsafe fn new<'a>(atom: *const nsAtom) -> &'a mut Self { - &mut *(atom as *mut WeakAtom) - } - - /// Clone this atom, bumping the refcount if the atom is not static. - #[inline] - pub fn clone(&self) -> Atom { - unsafe { Atom::from_raw(self.as_ptr()) } - } - - /// Get the atom hash. - #[inline] - pub fn get_hash(&self) -> u32 { - self.0.mHash - } - - /// Get the atom as a slice of utf-16 chars. - #[inline] - pub fn as_slice(&self) -> &[u16] { - let string = if self.is_static() { - let atom_ptr = self.as_ptr() as *const nsStaticAtom; - let string_offset = unsafe { (*atom_ptr).mStringOffset }; - let string_offset = -(string_offset as isize); - let u8_ptr = atom_ptr as *const u8; - // It is safe to use offset() here because both addresses are within - // the same struct, e.g. mozilla::detail::gGkAtoms. - unsafe { u8_ptr.offset(string_offset) as *const u16 } - } else { - let atom_ptr = self.as_ptr() as *const nsDynamicAtom; - // Dynamic atom chars are stored at the end of the object. - unsafe { atom_ptr.offset(1) as *const u16 } - }; - unsafe { slice::from_raw_parts(string, self.len() as usize) } - } - - // NOTE: don't expose this, since it's slow, and easy to be misused. - fn chars(&self) -> DecodeUtf16<Cloned<slice::Iter<u16>>> { - char::decode_utf16(self.as_slice().iter().cloned()) - } - - /// Execute `cb` with the string that this atom represents. - /// - /// Find alternatives to this function when possible, please, since it's - /// pretty slow. - pub fn with_str<F, Output>(&self, cb: F) -> Output - where - F: FnOnce(&str) -> Output, - { - let mut buffer = mem::MaybeUninit::<[u8; 64]>::uninit(); - let buffer = unsafe { &mut *buffer.as_mut_ptr() }; - - // The total string length in utf16 is going to be less than or equal - // the slice length (each utf16 character is going to take at least one - // and at most 2 items in the utf16 slice). - // - // Each of those characters will take at most four bytes in the utf8 - // one. Thus if the slice is less than 64 / 4 (16) we can guarantee that - // we'll decode it in place. - let owned_string; - let len = self.len(); - let utf8_slice = if len <= 16 { - let mut total_len = 0; - - for c in self.chars() { - let c = c.unwrap_or(char::REPLACEMENT_CHARACTER); - let utf8_len = c.encode_utf8(&mut buffer[total_len..]).len(); - total_len += utf8_len; - } - - let slice = unsafe { str::from_utf8_unchecked(&buffer[..total_len]) }; - debug_assert_eq!(slice, String::from_utf16_lossy(self.as_slice())); - slice - } else { - owned_string = String::from_utf16_lossy(self.as_slice()); - &*owned_string - }; - - cb(utf8_slice) - } - - /// Returns whether this atom is static. - #[inline] - pub fn is_static(&self) -> bool { - self.0.mIsStatic() != 0 - } - - /// Returns whether this atom is ascii lowercase. - #[inline] - fn is_ascii_lowercase(&self) -> bool { - self.0.mIsAsciiLowercase() != 0 - } - - /// Returns the length of the atom string. - #[inline] - pub fn len(&self) -> u32 { - self.0.mLength() - } - - /// Returns whether this atom is the empty string. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the atom as a mutable pointer. - #[inline] - pub fn as_ptr(&self) -> *mut nsAtom { - let const_ptr: *const nsAtom = &self.0; - const_ptr as *mut nsAtom - } - - /// Convert this atom to ASCII lower-case - pub fn to_ascii_lowercase(&self) -> Atom { - if self.is_ascii_lowercase() { - return self.clone(); - } - - let slice = self.as_slice(); - let mut buffer = mem::MaybeUninit::<[u16; 64]>::uninit(); - let buffer = unsafe { &mut *buffer.as_mut_ptr() }; - let mut vec; - let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) { - buffer_prefix.copy_from_slice(slice); - buffer_prefix - } else { - vec = slice.to_vec(); - &mut vec - }; - for char16 in &mut *mutable_slice { - if *char16 <= 0x7F { - *char16 = (*char16 as u8).to_ascii_lowercase() as u16 - } - } - Atom::from(&*mutable_slice) - } - - /// Return whether two atoms are ASCII-case-insensitive matches - #[inline] - pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { - if self == other { - return true; - } - - // If we know both atoms are ascii-lowercase, then we can stick with - // pointer equality. - if self.is_ascii_lowercase() && other.is_ascii_lowercase() { - debug_assert!(!self.eq_ignore_ascii_case_slow(other)); - return false; - } - - self.eq_ignore_ascii_case_slow(other) - } - - fn eq_ignore_ascii_case_slow(&self, other: &Self) -> bool { - let a = self.as_slice(); - let b = other.as_slice(); - - if a.len() != b.len() { - return false; - } - - a.iter().zip(b).all(|(&a16, &b16)| { - if a16 <= 0x7F && b16 <= 0x7F { - (a16 as u8).eq_ignore_ascii_case(&(b16 as u8)) - } else { - a16 == b16 - } - }) - } -} - -impl fmt::Debug for WeakAtom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - write!(w, "Gecko WeakAtom({:p}, {})", self, self) - } -} - -impl fmt::Display for WeakAtom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - for c in self.chars() { - w.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER))? - } - Ok(()) - } -} - -#[inline] -unsafe fn make_handle(ptr: *const nsAtom) -> NonZeroUsize { - debug_assert!(!ptr.is_null()); - if !WeakAtom::new(ptr).is_static() { - NonZeroUsize::new_unchecked(ptr as usize) - } else { - make_static_handle(ptr as *mut nsStaticAtom) - } -} - -#[inline] -unsafe fn make_static_handle(ptr: *const nsStaticAtom) -> NonZeroUsize { - // FIXME(heycam): Use offset_from once it's stabilized. - // https://github.com/rust-lang/rust/issues/41079 - debug_assert!(valid_static_atom_addr(ptr as usize)); - let base = &gGkAtoms as *const _; - let offset = ptr as usize - base as usize; - NonZeroUsize::new_unchecked((offset << 1) | 1) -} - -impl Atom { - #[inline] - fn is_static(&self) -> bool { - self.0.get() & 1 == 1 - } - - /// Execute a callback with the atom represented by `ptr`. - pub unsafe fn with<F, R>(ptr: *const nsAtom, callback: F) -> R - where - F: FnOnce(&Atom) -> R, - { - let atom = Atom(make_handle(ptr as *mut nsAtom)); - let ret = callback(&atom); - mem::forget(atom); - ret - } - - /// Creates a static atom from its index in the static atom table, without - /// checking. - #[inline] - pub const unsafe fn from_index_unchecked(index: u16) -> Self { - // FIXME(emilio): No support for debug_assert! in const fn for now. Note - // that violating this invariant will debug-assert in the `Deref` impl - // though. - // - // debug_assert!((index as usize) < STATIC_ATOM_COUNT); - let offset = - (index as usize) * std::mem::size_of::<nsStaticAtom>() + kGkAtomsArrayOffset as usize; - Atom(NonZeroUsize::new_unchecked((offset << 1) | 1)) - } - - /// Creates an atom from an atom pointer. - #[inline(always)] - pub unsafe fn from_raw(ptr: *mut nsAtom) -> Self { - let atom = Atom(make_handle(ptr)); - if !atom.is_static() { - Gecko_AddRefAtom(ptr); - } - atom - } - - /// Creates an atom from an atom pointer that has already had AddRef - /// called on it. This may be a static or dynamic atom. - #[inline] - pub unsafe fn from_addrefed(ptr: *mut nsAtom) -> Self { - assert!(!ptr.is_null()); - Atom(make_handle(ptr)) - } - - /// Convert this atom into an addrefed nsAtom pointer. - #[inline] - pub fn into_addrefed(self) -> *mut nsAtom { - let ptr = self.as_ptr(); - mem::forget(self); - ptr - } -} - -impl Hash for Atom { - fn hash<H>(&self, state: &mut H) - where - H: Hasher, - { - state.write_u32(self.get_hash()); - } -} - -impl Hash for WeakAtom { - fn hash<H>(&self, state: &mut H) - where - H: Hasher, - { - state.write_u32(self.get_hash()); - } -} - -impl Clone for Atom { - #[inline(always)] - fn clone(&self) -> Atom { - unsafe { - let atom = Atom(self.0); - if !atom.is_static() { - Gecko_AddRefAtom(atom.as_ptr()); - } - atom - } - } -} - -impl Drop for Atom { - #[inline] - fn drop(&mut self) { - if !self.is_static() { - unsafe { - Gecko_ReleaseAtom(self.as_ptr()); - } - } - } -} - -impl Default for Atom { - #[inline] - fn default() -> Self { - atom!("") - } -} - -impl fmt::Debug for Atom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - write!(w, "Atom(0x{:08x}, {})", self.0, self) - } -} - -impl fmt::Display for Atom { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - self.deref().fmt(w) - } -} - -impl<'a> From<&'a str> for Atom { - #[inline] - fn from(string: &str) -> Atom { - debug_assert!(string.len() <= u32::max_value() as usize); - unsafe { - Atom::from_addrefed(Gecko_Atomize( - string.as_ptr() as *const _, - string.len() as u32, - )) - } - } -} - -impl<'a> From<&'a [u16]> for Atom { - #[inline] - fn from(slice: &[u16]) -> Atom { - Atom::from(&*nsStr::from(slice)) - } -} - -impl<'a> From<&'a nsAString> for Atom { - #[inline] - fn from(string: &nsAString) -> Atom { - unsafe { Atom::from_addrefed(Gecko_Atomize16(string)) } - } -} - -impl<'a> From<Cow<'a, str>> for Atom { - #[inline] - fn from(string: Cow<'a, str>) -> Atom { - Atom::from(&*string) - } -} - -impl From<String> for Atom { - #[inline] - fn from(string: String) -> Atom { - Atom::from(&*string) - } -} - -malloc_size_of_is_0!(Atom); - -impl SpecifiedValueInfo for Atom {} diff --git a/components/style/gecko_string_cache/namespace.rs b/components/style/gecko_string_cache/namespace.rs deleted file mode 100644 index d9745b9e21f..00000000000 --- a/components/style/gecko_string_cache/namespace.rs +++ /dev/null @@ -1,105 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A type to represent a namespace. - -use crate::gecko_bindings::structs::nsAtom; -use crate::string_cache::{Atom, WeakAtom}; -use precomputed_hash::PrecomputedHash; -use std::borrow::Borrow; -use std::fmt; -use std::ops::Deref; - -/// In Gecko namespaces are just regular atoms, so this is a simple macro to -/// forward one macro to the other. -#[macro_export] -macro_rules! ns { - () => { - $crate::string_cache::Namespace(atom!("")) - }; - ($s:tt) => { - $crate::string_cache::Namespace(atom!($s)) - }; -} - -/// A Gecko namespace is just a wrapped atom. -#[derive( - Clone, - Debug, - Default, - Eq, - Hash, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct Namespace(pub Atom); - -impl PrecomputedHash for Namespace { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.0.precomputed_hash() - } -} - -/// A Gecko WeakNamespace is a wrapped WeakAtom. -#[derive(Deref, Hash)] -pub struct WeakNamespace(WeakAtom); - -impl Deref for Namespace { - type Target = WeakNamespace; - - #[inline] - fn deref(&self) -> &WeakNamespace { - let weak: *const WeakAtom = &*self.0; - unsafe { &*(weak as *const WeakNamespace) } - } -} - -impl<'a> From<&'a str> for Namespace { - fn from(s: &'a str) -> Self { - Namespace(Atom::from(s)) - } -} - -impl fmt::Display for Namespace { - fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(w) - } -} - -impl Borrow<WeakNamespace> for Namespace { - #[inline] - fn borrow(&self) -> &WeakNamespace { - self - } -} - -impl WeakNamespace { - /// Trivially construct a WeakNamespace. - #[inline] - pub unsafe fn new<'a>(atom: *mut nsAtom) -> &'a Self { - &*(atom as *const WeakNamespace) - } - - /// Clone this WeakNamespace to obtain a strong reference to the same - /// underlying namespace. - #[inline] - pub fn clone(&self) -> Namespace { - Namespace(self.0.clone()) - } -} - -impl Eq for WeakNamespace {} -impl PartialEq for WeakNamespace { - #[inline] - fn eq(&self, other: &Self) -> bool { - let weak: *const WeakNamespace = self; - let other: *const WeakNamespace = other; - weak == other - } -} diff --git a/components/style/global_style_data.rs b/components/style/global_style_data.rs deleted file mode 100644 index 8b5f3c89024..00000000000 --- a/components/style/global_style_data.rs +++ /dev/null @@ -1,173 +0,0 @@ -/* 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/. */ - -//! Global style data - -use crate::context::StyleSystemOptions; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::bindings; -use crate::parallel::STYLE_THREAD_STACK_SIZE_KB; -use crate::shared_lock::SharedRwLock; -use crate::thread_state; -#[cfg(feature = "gecko")] -use gecko_profiler; -use parking_lot::{Mutex, RwLock, RwLockReadGuard}; -use rayon; -use std::{io, thread}; - -/// Global style data -pub struct GlobalStyleData { - /// Shared RWLock for CSSOM objects - pub shared_lock: SharedRwLock, - - /// Global style system options determined by env vars. - pub options: StyleSystemOptions, -} - -/// Global thread pool. -pub struct StyleThreadPool { - /// How many threads parallel styling can use. If not using a thread pool, this is set to `None`. - pub num_threads: Option<usize>, - - /// The parallel styling thread pool. - /// - /// For leak-checking purposes, we want to terminate the thread-pool, which - /// waits for all the async jobs to complete. Thus the RwLock. - style_thread_pool: RwLock<Option<rayon::ThreadPool>>, -} - -fn thread_name(index: usize) -> String { - format!("Style#{}", index) -} - -lazy_static! { - /// JoinHandles for spawned style threads. These will be joined during - /// StyleThreadPool::shutdown() after exiting the thread pool. - /// - /// This would be quite inefficient if rayon destroyed and re-created - /// threads regularly during threadpool operation in response to demand, - /// however rayon actually never destroys its threads until the entire - /// thread pool is shut-down, so the size of this list is bounded. - static ref STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> = - Mutex::new(Vec::new()); -} - -fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> { - let mut b = thread::Builder::new(); - if let Some(name) = options.name() { - b = b.name(name.to_owned()); - } - if let Some(stack_size) = options.stack_size() { - b = b.stack_size(stack_size); - } - let join_handle = b.spawn(|| options.run())?; - STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle); - Ok(()) -} - -fn thread_startup(_index: usize) { - thread_state::initialize_layout_worker_thread(); - #[cfg(feature = "gecko")] - unsafe { - bindings::Gecko_SetJemallocThreadLocalArena(true); - let name = thread_name(_index); - gecko_profiler::register_thread(&name); - } -} - -fn thread_shutdown(_: usize) { - #[cfg(feature = "gecko")] - unsafe { - gecko_profiler::unregister_thread(); - bindings::Gecko_SetJemallocThreadLocalArena(false); - } -} - -impl StyleThreadPool { - /// Shuts down the thread pool, waiting for all work to complete. - pub fn shutdown() { - if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() { - return; - } - { - // Drop the pool. - let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take(); - } - - // Join spawned threads until all of the threads have been joined. This - // will usually be pretty fast, as on shutdown there should be basically - // no threads left running. - while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() { - let _ = join_handle.join(); - } - } - - /// Returns a reference to the thread pool. - /// - /// We only really want to give read-only access to the pool, except - /// for shutdown(). - pub fn pool(&self) -> RwLockReadGuard<Option<rayon::ThreadPool>> { - self.style_thread_pool.read() - } -} - -#[cfg(feature = "servo")] -fn stylo_threads_pref() -> i32 { - style_config::get_i32("layout.threads") -} - -#[cfg(feature = "gecko")] -fn stylo_threads_pref() -> i32 { - static_prefs::pref!("layout.css.stylo-threads") -} - -/// The performance benefit of additional threads seems to level off at around six, so we cap it -/// there on many-core machines (see bug 1431285 comment 14). -pub(crate) const STYLO_MAX_THREADS: usize = 6; - -lazy_static! { - /// Global thread pool - pub static ref STYLE_THREAD_POOL: std::sync::Mutex<StyleThreadPool> = { - use std::cmp; - // We always set this pref on startup, before layout or script have had a chance of - // accessing (and thus creating) the thread-pool. - let threads_pref: i32 = stylo_threads_pref(); - let num_threads = if threads_pref >= 0 { - threads_pref as usize - } else { - use num_cpus; - // The default heuristic is num_virtual_cores * .75. This gives us three threads on a - // hyper-threaded dual core, and six threads on a hyper-threaded quad core. - let threads = cmp::max(num_cpus::get() * 3 / 4, 1); - // There's no point in creating a thread pool if there's one thread. - if threads == 1 { 0 } else { threads } - }; - - let num_threads = cmp::min(num_threads, STYLO_MAX_THREADS); - let (pool, num_threads) = if num_threads < 1 { - (None, None) - } else { - let workers = rayon::ThreadPoolBuilder::new() - .spawn_handler(thread_spawn) - .num_threads(num_threads) - .thread_name(thread_name) - .start_handler(thread_startup) - .exit_handler(thread_shutdown) - .stack_size(STYLE_THREAD_STACK_SIZE_KB * 1024) - .build(); - (workers.ok(), Some(num_threads)) - }; - - std::sync::Mutex::new(StyleThreadPool { - num_threads, - style_thread_pool: RwLock::new(pool), - }) - }; - - /// Global style data - pub static ref GLOBAL_STYLE_DATA: GlobalStyleData = GlobalStyleData { - shared_lock: SharedRwLock::new_leaked(), - options: StyleSystemOptions::default(), - }; -} diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs deleted file mode 100644 index b0bbce3b855..00000000000 --- a/components/style/invalidation/element/document_state.rs +++ /dev/null @@ -1,142 +0,0 @@ -/* 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/. */ - -//! An invalidation processor for style changes due to document state changes. - -use crate::dom::TElement; -use crate::invalidation::element::invalidation_map::Dependency; -use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector}; -use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; -use crate::invalidation::element::state_and_attributes; -use crate::stylist::CascadeData; -use selectors::matching::{ - MatchingContext, MatchingMode, NeedsSelectorFlags, QuirksMode, VisitedHandlingMode, -}; -use selectors::NthIndexCache; -use style_traits::dom::DocumentState; - -/// A struct holding the members necessary to invalidate document state -/// selectors. -#[derive(Debug)] -pub struct InvalidationMatchingData { - /// The document state that has changed, which makes it always match. - pub document_state: DocumentState, -} - -impl Default for InvalidationMatchingData { - #[inline(always)] - fn default() -> Self { - Self { - document_state: DocumentState::empty(), - } - } -} - -/// An invalidation processor for style changes due to state and attribute -/// changes. -pub struct DocumentStateInvalidationProcessor<'a, E: TElement, I> { - rules: I, - matching_context: MatchingContext<'a, E::Impl>, - document_states_changed: DocumentState, -} - -impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> { - /// Creates a new DocumentStateInvalidationProcessor. - #[inline] - pub fn new( - rules: I, - document_states_changed: DocumentState, - nth_index_cache: &'a mut NthIndexCache, - quirks_mode: QuirksMode, - ) -> Self { - let mut matching_context = MatchingContext::<'a, E::Impl>::new_for_visited( - MatchingMode::Normal, - None, - nth_index_cache, - VisitedHandlingMode::AllLinksVisitedAndUnvisited, - quirks_mode, - NeedsSelectorFlags::No, - ); - - matching_context.extra_data.invalidation_data.document_state = document_states_changed; - - Self { - rules, - document_states_changed, - matching_context, - } - } -} - -impl<'a, E, I> InvalidationProcessor<'a, E> for DocumentStateInvalidationProcessor<'a, E, I> -where - E: TElement, - I: Iterator<Item = &'a CascadeData>, -{ - fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool { - debug_assert!( - false, - "how, we should only have parent-less dependencies here!" - ); - true - } - - fn collect_invalidations( - &mut self, - _element: E, - self_invalidations: &mut InvalidationVector<'a>, - _descendant_invalidations: &mut DescendantInvalidationLists<'a>, - _sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool { - for cascade_data in &mut self.rules { - let map = cascade_data.invalidation_map(); - for dependency in &map.document_state_selectors { - if !dependency.state.intersects(self.document_states_changed) { - continue; - } - - // We pass `None` as a scope, as document state selectors aren't - // affected by the current scope. - // - // FIXME(emilio): We should really pass the relevant host for - // self.rules, so that we invalidate correctly if the selector - // happens to have something like :host(:-moz-window-inactive) - // for example. - self_invalidations.push(Invalidation::new( - &dependency.dependency, - /* scope = */ None, - )); - } - } - - false - } - - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { - &mut self.matching_context - } - - fn recursion_limit_exceeded(&mut self, _: E) { - unreachable!("We don't run document state invalidation with stack limits") - } - - fn should_process_descendants(&mut self, element: E) -> bool { - match element.borrow_data() { - Some(d) => state_and_attributes::should_process_descendants(&d), - None => false, - } - } - - fn invalidated_descendants(&mut self, element: E, child: E) { - state_and_attributes::invalidated_descendants(element, child) - } - - fn invalidated_self(&mut self, element: E) { - state_and_attributes::invalidated_self(element); - } - - fn invalidated_sibling(&mut self, sibling: E, of: E) { - state_and_attributes::invalidated_sibling(sibling, of); - } -} diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs deleted file mode 100644 index 8c25153d1a8..00000000000 --- a/components/style/invalidation/element/element_wrapper.rs +++ /dev/null @@ -1,391 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A wrapper over an element and a snapshot, that allows us to selector-match -//! against a past state of the element. - -use crate::dom::TElement; -use crate::selector_parser::{AttrValue, NonTSPseudoClass, PseudoElement, SelectorImpl}; -use crate::selector_parser::{Snapshot, SnapshotMap}; -use crate::values::AtomIdent; -use crate::{CaseSensitivityExt, LocalName, Namespace, WeakAtom}; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::matching::{ElementSelectorFlags, MatchingContext}; -use selectors::{Element, OpaqueElement}; -use std::cell::Cell; -use std::fmt; -use style_traits::dom::ElementState; - -/// In order to compute restyle hints, we perform a selector match against a -/// list of partial selectors whose rightmost simple selector may be sensitive -/// to the thing being changed. We do this matching twice, once for the element -/// as it exists now and once for the element as it existed at the time of the -/// last restyle. If the results of the selector match differ, that means that -/// the given partial selector is sensitive to the change, and we compute a -/// restyle hint based on its combinator. -/// -/// In order to run selector matching against the old element state, we generate -/// a wrapper for the element which claims to have the old state. This is the -/// ElementWrapper logic below. -/// -/// Gecko does this differently for element states, and passes a mask called -/// mStateMask, which indicates the states that need to be ignored during -/// selector matching. This saves an ElementWrapper allocation and an additional -/// selector match call at the expense of additional complexity inside the -/// selector matching logic. This only works for boolean states though, so we -/// still need to take the ElementWrapper approach for attribute-dependent -/// style. So we do it the same both ways for now to reduce complexity, but it's -/// worth measuring the performance impact (if any) of the mStateMask approach. -pub trait ElementSnapshot: Sized { - /// The state of the snapshot, if any. - fn state(&self) -> Option<ElementState>; - - /// If this snapshot contains attribute information. - fn has_attrs(&self) -> bool; - - /// Gets the attribute information of the snapshot as a string. - /// - /// Only for debugging purposes. - fn debug_list_attributes(&self) -> String { - String::new() - } - - /// The ID attribute per this snapshot. Should only be called if - /// `has_attrs()` returns true. - fn id_attr(&self) -> Option<&WeakAtom>; - - /// Whether this snapshot contains the class `name`. Should only be called - /// if `has_attrs()` returns true. - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool; - - /// Whether this snapshot represents the part named `name`. Should only be - /// called if `has_attrs()` returns true. - fn is_part(&self, name: &AtomIdent) -> bool; - - /// See Element::imported_part. - fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent>; - - /// A callback that should be called for each class of the snapshot. Should - /// only be called if `has_attrs()` returns true. - fn each_class<F>(&self, _: F) - where - F: FnMut(&AtomIdent); - - /// The `xml:lang=""` or `lang=""` attribute value per this snapshot. - fn lang_attr(&self) -> Option<AttrValue>; -} - -/// A simple wrapper over an element and a snapshot, that allows us to -/// selector-match against a past state of the element. -#[derive(Clone)] -pub struct ElementWrapper<'a, E> -where - E: TElement, -{ - element: E, - cached_snapshot: Cell<Option<&'a Snapshot>>, - snapshot_map: &'a SnapshotMap, -} - -impl<'a, E> ElementWrapper<'a, E> -where - E: TElement, -{ - /// Trivially constructs an `ElementWrapper`. - pub fn new(el: E, snapshot_map: &'a SnapshotMap) -> Self { - ElementWrapper { - element: el, - cached_snapshot: Cell::new(None), - snapshot_map: snapshot_map, - } - } - - /// Gets the snapshot associated with this element, if any. - pub fn snapshot(&self) -> Option<&'a Snapshot> { - if !self.element.has_snapshot() { - return None; - } - - if let Some(s) = self.cached_snapshot.get() { - return Some(s); - } - - let snapshot = self.snapshot_map.get(&self.element); - debug_assert!(snapshot.is_some(), "has_snapshot lied!"); - - self.cached_snapshot.set(snapshot); - - snapshot - } - - /// Returns the states that have changed since the element was snapshotted. - pub fn state_changes(&self) -> ElementState { - let snapshot = match self.snapshot() { - Some(s) => s, - None => return ElementState::empty(), - }; - - match snapshot.state() { - Some(state) => state ^ self.element.state(), - None => ElementState::empty(), - } - } - - /// Returns the value of the `xml:lang=""` (or, if appropriate, `lang=""`) - /// attribute from this element's snapshot or the closest ancestor - /// element snapshot with the attribute specified. - fn get_lang(&self) -> Option<AttrValue> { - let mut current = self.clone(); - loop { - let lang = match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.lang_attr(), - _ => current.element.lang_attr(), - }; - if lang.is_some() { - return lang; - } - current = current.parent_element()?; - } - } -} - -impl<'a, E> fmt::Debug for ElementWrapper<'a, E> -where - E: TElement, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Ignore other fields for now, can change later if needed. - self.element.fmt(f) - } -} - -impl<'a, E> Element for ElementWrapper<'a, E> -where - E: TElement, -{ - type Impl = SelectorImpl; - - fn match_non_ts_pseudo_class( - &self, - pseudo_class: &NonTSPseudoClass, - context: &mut MatchingContext<Self::Impl>, - ) -> bool { - // Some pseudo-classes need special handling to evaluate them against - // the snapshot. - match *pseudo_class { - // For :link and :visited, we don't actually want to test the - // element state directly. - // - // Instead, we use the `visited_handling` to determine if they - // match. - NonTSPseudoClass::Link => { - return self.is_link() && context.visited_handling().matches_unvisited(); - }, - NonTSPseudoClass::Visited => { - return self.is_link() && context.visited_handling().matches_visited(); - }, - - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozTableBorderNonzero => { - if let Some(snapshot) = self.snapshot() { - if snapshot.has_other_pseudo_class_state() { - return snapshot.mIsTableBorderNonzero(); - } - } - }, - - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozBrowserFrame => { - if let Some(snapshot) = self.snapshot() { - if snapshot.has_other_pseudo_class_state() { - return snapshot.mIsMozBrowserFrame(); - } - } - }, - - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozSelectListBox => { - if let Some(snapshot) = self.snapshot() { - if snapshot.has_other_pseudo_class_state() { - return snapshot.mIsSelectListBox(); - } - } - }, - - // :lang() needs to match using the closest ancestor xml:lang="" or - // lang="" attribtue from snapshots. - NonTSPseudoClass::Lang(ref lang_arg) => { - return self - .element - .match_element_lang(Some(self.get_lang()), lang_arg); - }, - - _ => {}, - } - - let flag = pseudo_class.state_flag(); - if flag.is_empty() { - return self - .element - .match_non_ts_pseudo_class(pseudo_class, context); - } - match self.snapshot().and_then(|s| s.state()) { - Some(snapshot_state) => snapshot_state.intersects(flag), - None => self - .element - .match_non_ts_pseudo_class(pseudo_class, context), - } - } - - fn apply_selector_flags(&self, _flags: ElementSelectorFlags) { - debug_assert!(false, "Shouldn't need selector flags for invalidation"); - } - - fn match_pseudo_element( - &self, - pseudo_element: &PseudoElement, - context: &mut MatchingContext<Self::Impl>, - ) -> bool { - self.element.match_pseudo_element(pseudo_element, context) - } - - fn is_link(&self) -> bool { - match self.snapshot().and_then(|s| s.state()) { - Some(state) => state.intersects(ElementState::VISITED_OR_UNVISITED), - None => self.element.is_link(), - } - } - - fn opaque(&self) -> OpaqueElement { - self.element.opaque() - } - - fn parent_element(&self) -> Option<Self> { - let parent = self.element.parent_element()?; - Some(Self::new(parent, self.snapshot_map)) - } - - fn parent_node_is_shadow_root(&self) -> bool { - self.element.parent_node_is_shadow_root() - } - - fn containing_shadow_host(&self) -> Option<Self> { - let host = self.element.containing_shadow_host()?; - Some(Self::new(host, self.snapshot_map)) - } - - fn prev_sibling_element(&self) -> Option<Self> { - let sibling = self.element.prev_sibling_element()?; - Some(Self::new(sibling, self.snapshot_map)) - } - - fn next_sibling_element(&self) -> Option<Self> { - let sibling = self.element.next_sibling_element()?; - Some(Self::new(sibling, self.snapshot_map)) - } - - fn first_element_child(&self) -> Option<Self> { - let child = self.element.first_element_child()?; - Some(Self::new(child, self.snapshot_map)) - } - - #[inline] - fn is_html_element_in_html_document(&self) -> bool { - self.element.is_html_element_in_html_document() - } - - #[inline] - fn is_html_slot_element(&self) -> bool { - self.element.is_html_slot_element() - } - - #[inline] - fn has_local_name( - &self, - local_name: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedLocalName, - ) -> bool { - self.element.has_local_name(local_name) - } - - #[inline] - fn has_namespace( - &self, - ns: &<Self::Impl as ::selectors::SelectorImpl>::BorrowedNamespaceUrl, - ) -> bool { - self.element.has_namespace(ns) - } - - #[inline] - fn is_same_type(&self, other: &Self) -> bool { - self.element.is_same_type(&other.element) - } - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AttrValue>, - ) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => { - snapshot.attr_matches(ns, local_name, operation) - }, - _ => self.element.attr_matches(ns, local_name, operation), - } - } - - fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot - .id_attr() - .map_or(false, |atom| case_sensitivity.eq_atom(&atom, id)), - _ => self.element.has_id(id, case_sensitivity), - } - } - - fn is_part(&self, name: &AtomIdent) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.is_part(name), - _ => self.element.is_part(name), - } - } - - fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.imported_part(name), - _ => self.element.imported_part(name), - } - } - - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - match self.snapshot() { - Some(snapshot) if snapshot.has_attrs() => snapshot.has_class(name, case_sensitivity), - _ => self.element.has_class(name, case_sensitivity), - } - } - - fn is_empty(&self) -> bool { - self.element.is_empty() - } - - fn is_root(&self) -> bool { - self.element.is_root() - } - - fn is_pseudo_element(&self) -> bool { - self.element.is_pseudo_element() - } - - fn pseudo_element_originating_element(&self) -> Option<Self> { - self.element - .pseudo_element_originating_element() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) - } - - fn assigned_slot(&self) -> Option<Self> { - self.element - .assigned_slot() - .map(|e| ElementWrapper::new(e, self.snapshot_map)) - } -} diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs deleted file mode 100644 index 1cfc2fa7c05..00000000000 --- a/components/style/invalidation/element/invalidation_map.rs +++ /dev/null @@ -1,547 +0,0 @@ -/* 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/. */ - -//! Code for invalidations due to state or attribute changes. - -use crate::context::QuirksMode; -use crate::selector_map::{ - MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry, -}; -use crate::selector_parser::SelectorImpl; -use crate::AllocErr; -use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; -use selectors::attr::NamespaceConstraint; -use selectors::parser::{Combinator, Component}; -use selectors::parser::{Selector, SelectorIter}; -use selectors::visitor::{SelectorListKind, SelectorVisitor}; -use smallvec::SmallVec; -use style_traits::dom::{DocumentState, ElementState}; - -/// Mapping between (partial) CompoundSelectors (and the combinator to their -/// right) and the states and attributes they depend on. -/// -/// In general, for all selectors in all applicable stylesheets of the form: -/// -/// |a _ b _ c _ d _ e| -/// -/// Where: -/// * |b| and |d| are simple selectors that depend on state (like :hover) or -/// attributes (like [attr...], .foo, or #foo). -/// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on -/// state or attributes. -/// -/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, -/// even though those selectors may not appear on their own in any stylesheet. -/// This allows us to quickly scan through the dependency sites of all style -/// rules and determine the maximum effect that a given state or attribute -/// change may have on the style of elements in the document. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct Dependency { - /// The dependency selector. - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "CssRules have primary refs, we measure there" - )] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - pub selector: Selector<SelectorImpl>, - - /// The offset into the selector that we should match on. - pub selector_offset: usize, - - /// The parent dependency for an ancestor selector. For example, consider - /// the following: - /// - /// .foo .bar:where(.baz span) .qux - /// ^ ^ ^ - /// A B C - /// - /// We'd generate: - /// - /// * One dependency for .qux (offset: 0, parent: None) - /// * One dependency for .baz pointing to B with parent being a - /// dependency pointing to C. - /// * One dependency from .bar pointing to C (parent: None) - /// * One dependency from .foo pointing to A (parent: None) - /// - pub parent: Option<Box<Dependency>>, -} - -size_of_test!(Dependency, 24); - -/// The kind of elements down the tree this dependency may affect. -#[derive(Debug, Eq, PartialEq)] -pub enum DependencyInvalidationKind { - /// This dependency may affect the element that changed itself. - Element, - /// This dependency affects the style of the element itself, and also the - /// style of its descendants. - /// - /// TODO(emilio): Each time this feels more of a hack for eager pseudos... - ElementAndDescendants, - /// This dependency may affect descendants down the tree. - Descendants, - /// This dependency may affect siblings to the right of the element that - /// changed. - Siblings, - /// This dependency may affect slotted elements of the element that changed. - SlottedElements, - /// This dependency may affect parts of the element that changed. - Parts, -} - -impl Dependency { - /// Creates a dummy dependency to invalidate the whole selector. - /// - /// This is necessary because document state invalidation wants to - /// invalidate all elements in the document. - /// - /// The offset is such as that Invalidation::new(self) returns a zero - /// offset. That is, it points to a virtual "combinator" outside of the - /// selector, so calling combinator() on such a dependency will panic. - pub fn for_full_selector_invalidation(selector: Selector<SelectorImpl>) -> Self { - Self { - selector_offset: selector.len() + 1, - selector, - parent: None, - } - } - - /// Returns the combinator to the right of the partial selector this - /// dependency represents. - /// - /// TODO(emilio): Consider storing inline if it helps cache locality? - pub fn combinator(&self) -> Option<Combinator> { - if self.selector_offset == 0 { - return None; - } - - Some( - self.selector - .combinator_at_match_order(self.selector_offset - 1), - ) - } - - /// The kind of invalidation that this would generate. - pub fn invalidation_kind(&self) -> DependencyInvalidationKind { - match self.combinator() { - None => DependencyInvalidationKind::Element, - Some(Combinator::Child) | Some(Combinator::Descendant) => { - DependencyInvalidationKind::Descendants - }, - Some(Combinator::LaterSibling) | Some(Combinator::NextSibling) => { - DependencyInvalidationKind::Siblings - }, - // TODO(emilio): We could look at the selector itself to see if it's - // an eager pseudo, and return only Descendants here if not. - Some(Combinator::PseudoElement) => DependencyInvalidationKind::ElementAndDescendants, - Some(Combinator::SlotAssignment) => DependencyInvalidationKind::SlottedElements, - Some(Combinator::Part) => DependencyInvalidationKind::Parts, - } - } -} - -impl SelectorMapEntry for Dependency { - fn selector(&self) -> SelectorIter<SelectorImpl> { - self.selector.iter_from(self.selector_offset) - } -} - -/// The same, but for state selectors, which can track more exactly what state -/// do they track. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct StateDependency { - /// The other dependency fields. - pub dep: Dependency, - /// The state this dependency is affected by. - pub state: ElementState, -} - -impl SelectorMapEntry for StateDependency { - fn selector(&self) -> SelectorIter<SelectorImpl> { - self.dep.selector() - } -} - -/// The same, but for document state selectors. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct DocumentStateDependency { - /// We track `Dependency` even though we don't need to track an offset, - /// since when it changes it changes for the whole document anyway. - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "CssRules have primary refs, we measure there" - )] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - pub dependency: Dependency, - /// The state this dependency is affected by. - pub state: DocumentState, -} - -/// A map where we store invalidations. -/// -/// This is slightly different to a SelectorMap, in the sense of that the same -/// selector may appear multiple times. -/// -/// In particular, we want to lookup as few things as possible to get the fewer -/// selectors the better, so this looks up by id, class, or looks at the list of -/// state/other attribute affecting selectors. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct InvalidationMap { - /// A map from a given class name to all the selectors with that class - /// selector. - pub class_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>, - /// A map from a given id to all the selectors with that ID in the - /// stylesheets currently applying to the document. - pub id_to_selector: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[Dependency; 1]>>, - /// A map of all the state dependencies. - pub state_affecting_selectors: SelectorMap<StateDependency>, - /// A list of document state dependencies in the rules we represent. - pub document_state_selectors: Vec<DocumentStateDependency>, - /// A map of other attribute affecting selectors. - pub other_attribute_affecting_selectors: - PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>, -} - -impl InvalidationMap { - /// Creates an empty `InvalidationMap`. - pub fn new() -> Self { - Self { - class_to_selector: MaybeCaseInsensitiveHashMap::new(), - id_to_selector: MaybeCaseInsensitiveHashMap::new(), - state_affecting_selectors: SelectorMap::new(), - document_state_selectors: Vec::new(), - other_attribute_affecting_selectors: PrecomputedHashMap::default(), - } - } - - /// Returns the number of dependencies stored in the invalidation map. - pub fn len(&self) -> usize { - self.state_affecting_selectors.len() + - self.document_state_selectors.len() + - self.other_attribute_affecting_selectors - .iter() - .fold(0, |accum, (_, ref v)| accum + v.len()) + - self.id_to_selector - .iter() - .fold(0, |accum, (_, ref v)| accum + v.len()) + - self.class_to_selector - .iter() - .fold(0, |accum, (_, ref v)| accum + v.len()) - } - - /// Clears this map, leaving it empty. - pub fn clear(&mut self) { - self.class_to_selector.clear(); - self.id_to_selector.clear(); - self.state_affecting_selectors.clear(); - self.document_state_selectors.clear(); - self.other_attribute_affecting_selectors.clear(); - } - - /// Shrink the capacity of hash maps if needed. - pub fn shrink_if_needed(&mut self) { - self.class_to_selector.shrink_if_needed(); - self.id_to_selector.shrink_if_needed(); - self.state_affecting_selectors.shrink_if_needed(); - self.other_attribute_affecting_selectors.shrink_if_needed(); - } - - /// Adds a selector to this `InvalidationMap`. Returns Err(..) to - /// signify OOM. - pub fn note_selector( - &mut self, - selector: &Selector<SelectorImpl>, - quirks_mode: QuirksMode, - ) -> Result<(), AllocErr> { - debug!("InvalidationMap::note_selector({:?})", selector); - - let mut document_state = DocumentState::empty(); - - { - let mut parent_stack = SmallVec::new(); - let mut alloc_error = None; - let mut collector = SelectorDependencyCollector { - map: self, - document_state: &mut document_state, - selector, - parent_selectors: &mut parent_stack, - quirks_mode, - compound_state: PerCompoundState::new(0), - alloc_error: &mut alloc_error, - }; - - let visit_result = collector.visit_whole_selector(); - debug_assert_eq!(!visit_result, alloc_error.is_some()); - if let Some(alloc_error) = alloc_error { - return Err(alloc_error); - } - } - - if !document_state.is_empty() { - let dep = DocumentStateDependency { - state: document_state, - dependency: Dependency::for_full_selector_invalidation(selector.clone()), - }; - self.document_state_selectors.try_reserve(1)?; - self.document_state_selectors.push(dep); - } - - Ok(()) - } -} - -struct PerCompoundState { - /// The offset at which our compound starts. - offset: usize, - - /// The state this compound selector is affected by. - element_state: ElementState, -} - -impl PerCompoundState { - fn new(offset: usize) -> Self { - Self { - offset, - element_state: ElementState::empty(), - } - } -} - -/// A struct that collects invalidations for a given compound selector. -struct SelectorDependencyCollector<'a> { - map: &'a mut InvalidationMap, - - /// The document this _complex_ selector is affected by. - /// - /// We don't need to track state per compound selector, since it's global - /// state and it changes for everything. - document_state: &'a mut DocumentState, - - /// The current selector and offset we're iterating. - selector: &'a Selector<SelectorImpl>, - - /// The stack of parent selectors that we have, and at which offset of the - /// sequence. - /// - /// This starts empty. It grows when we find nested :is and :where selector - /// lists. - parent_selectors: &'a mut SmallVec<[(Selector<SelectorImpl>, usize); 5]>, - - /// The quirks mode of the document where we're inserting dependencies. - quirks_mode: QuirksMode, - - /// State relevant to a given compound selector. - compound_state: PerCompoundState, - - /// The allocation error, if we OOM. - alloc_error: &'a mut Option<AllocErr>, -} - -impl<'a> SelectorDependencyCollector<'a> { - fn visit_whole_selector(&mut self) -> bool { - let iter = self.selector.iter(); - self.visit_whole_selector_from(iter, 0) - } - - fn visit_whole_selector_from( - &mut self, - mut iter: SelectorIter<SelectorImpl>, - mut index: usize, - ) -> bool { - loop { - // Reset the compound state. - self.compound_state = PerCompoundState::new(index); - - // Visit all the simple selectors in this sequence. - for ss in &mut iter { - if !ss.visit(self) { - return false; - } - index += 1; // Account for the simple selector. - } - - if !self.compound_state.element_state.is_empty() { - let dependency = self.dependency(); - let result = self.map.state_affecting_selectors.insert( - StateDependency { - dep: dependency, - state: self.compound_state.element_state, - }, - self.quirks_mode, - ); - if let Err(alloc_error) = result { - *self.alloc_error = Some(alloc_error.into()); - return false; - } - } - - let combinator = iter.next_sequence(); - if combinator.is_none() { - return true; - } - index += 1; // account for the combinator - } - } - - fn add_attr_dependency(&mut self, name: LocalName) -> bool { - let dependency = self.dependency(); - - let map = &mut self.map.other_attribute_affecting_selectors; - if let Err(err) = map.try_reserve(1) { - *self.alloc_error = Some(err.into()); - return false; - } - let vec = map.entry(name).or_default(); - if let Err(err) = vec.try_reserve(1) { - *self.alloc_error = Some(err.into()); - return false; - } - vec.push(dependency); - true - } - - fn dependency(&self) -> Dependency { - let mut parent = None; - - // TODO(emilio): Maybe we should refcount the parent dependencies, or - // cache them or something. - for &(ref selector, ref selector_offset) in self.parent_selectors.iter() { - debug_assert_ne!( - self.compound_state.offset, 0, - "Shouldn't bother creating nested dependencies for the rightmost compound", - ); - let new_parent = Dependency { - selector: selector.clone(), - selector_offset: *selector_offset, - parent, - }; - parent = Some(Box::new(new_parent)); - } - - Dependency { - selector: self.selector.clone(), - selector_offset: self.compound_state.offset, - parent, - } - } -} - -impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> { - type Impl = SelectorImpl; - - fn visit_selector_list( - &mut self, - _list_kind: SelectorListKind, - list: &[Selector<SelectorImpl>], - ) -> bool { - for selector in list { - // Here we cheat a bit: We can visit the rightmost compound with - // the "outer" visitor, and it'd be fine. This reduces the amount of - // state and attribute invalidations, and we need to check the outer - // selector to the left anyway to avoid over-invalidation, so it - // avoids matching it twice uselessly. - let mut iter = selector.iter(); - let mut index = 0; - - for ss in &mut iter { - if !ss.visit(self) { - return false; - } - index += 1; - } - - let combinator = iter.next_sequence(); - if combinator.is_none() { - continue; - } - - index += 1; // account for the combinator. - - self.parent_selectors - .push((self.selector.clone(), self.compound_state.offset)); - let mut nested = SelectorDependencyCollector { - map: &mut *self.map, - document_state: &mut *self.document_state, - selector, - parent_selectors: &mut *self.parent_selectors, - quirks_mode: self.quirks_mode, - compound_state: PerCompoundState::new(index), - alloc_error: &mut *self.alloc_error, - }; - if !nested.visit_whole_selector_from(iter, index) { - return false; - } - self.parent_selectors.pop(); - } - true - } - - fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool { - use crate::selector_parser::NonTSPseudoClass; - - match *s { - Component::ID(ref atom) | Component::Class(ref atom) => { - let dependency = self.dependency(); - let map = match *s { - Component::ID(..) => &mut self.map.id_to_selector, - Component::Class(..) => &mut self.map.class_to_selector, - _ => unreachable!(), - }; - let entry = match map.try_entry(atom.0.clone(), self.quirks_mode) { - Ok(entry) => entry, - Err(err) => { - *self.alloc_error = Some(err.into()); - return false; - }, - }; - let vec = entry.or_insert_with(SmallVec::new); - if let Err(err) = vec.try_reserve(1) { - *self.alloc_error = Some(err.into()); - return false; - } - vec.push(dependency); - true - }, - Component::NonTSPseudoClass(ref pc) => { - self.compound_state.element_state |= pc.state_flag(); - *self.document_state |= pc.document_state_flag(); - - let attr_name = match *pc { - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"), - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozBrowserFrame => local_name!("mozbrowser"), - #[cfg(feature = "gecko")] - NonTSPseudoClass::MozSelectListBox => { - // This depends on two attributes. - return self.add_attr_dependency(local_name!("multiple")) && - self.add_attr_dependency(local_name!("size")); - }, - NonTSPseudoClass::Lang(..) => local_name!("lang"), - _ => return true, - }; - - self.add_attr_dependency(attr_name) - }, - _ => true, - } - } - - fn visit_attribute_selector( - &mut self, - _: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - local_name_lower: &LocalName, - ) -> bool { - if !self.add_attr_dependency(local_name.clone()) { - return false; - } - - if local_name != local_name_lower && !self.add_attr_dependency(local_name_lower.clone()) { - return false; - } - - true - } -} diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs deleted file mode 100644 index 00f714c5b15..00000000000 --- a/components/style/invalidation/element/invalidator.rs +++ /dev/null @@ -1,1017 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The struct that takes care of encapsulating all the logic on where and how -//! element styles need to be invalidated. - -use crate::context::StackLimitChecker; -use crate::dom::{TElement, TNode, TShadowRoot}; -use crate::invalidation::element::invalidation_map::{Dependency, DependencyInvalidationKind}; -use selectors::matching::matches_compound_selector_from; -use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext}; -use selectors::parser::{Combinator, Component}; -use selectors::OpaqueElement; -use smallvec::SmallVec; -use std::fmt; -use std::fmt::Write; - -/// A trait to abstract the collection of invalidations for a given pass. -pub trait InvalidationProcessor<'a, E> -where - E: TElement, -{ - /// Whether an invalidation that contains only a pseudo-element selector - /// like ::before or ::after triggers invalidation of the element that would - /// originate it. - fn invalidates_on_pseudo_element(&self) -> bool { - false - } - - /// Whether the invalidation processor only cares about light-tree - /// descendants of a given element, that is, doesn't invalidate - /// pseudo-elements, NAC, shadow dom... - fn light_tree_only(&self) -> bool { - false - } - - /// When a dependency from a :where or :is selector matches, it may still be - /// the case that we don't need to invalidate the full style. Consider the - /// case of: - /// - /// div .foo:where(.bar *, .baz) .qux - /// - /// We can get to the `*` part after a .bar class change, but you only need - /// to restyle the element if it also matches .foo. - /// - /// Similarly, you only need to restyle .baz if the whole result of matching - /// the selector changes. - /// - /// This function is called to check the result of matching the "outer" - /// dependency that we generate for the parent of the `:where` selector, - /// that is, in the case above it should match - /// `div .foo:where(.bar *, .baz)`. - /// - /// Returning true unconditionally here is over-optimistic and may - /// over-invalidate. - fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool; - - /// The matching context that should be used to process invalidations. - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>; - - /// Collect invalidations for a given element's descendants and siblings. - /// - /// Returns whether the element itself was invalidated. - fn collect_invalidations( - &mut self, - element: E, - self_invalidations: &mut InvalidationVector<'a>, - descendant_invalidations: &mut DescendantInvalidationLists<'a>, - sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool; - - /// Returns whether the invalidation process should process the descendants - /// of the given element. - fn should_process_descendants(&mut self, element: E) -> bool; - - /// Executes an arbitrary action when the recursion limit is exceded (if - /// any). - fn recursion_limit_exceeded(&mut self, element: E); - - /// Executes an action when `Self` is invalidated. - fn invalidated_self(&mut self, element: E); - - /// Executes an action when `sibling` is invalidated as a sibling of - /// `of`. - fn invalidated_sibling(&mut self, sibling: E, of: E); - - /// Executes an action when any descendant of `Self` is invalidated. - fn invalidated_descendants(&mut self, element: E, child: E); -} - -/// Different invalidation lists for descendants. -#[derive(Debug, Default)] -pub struct DescendantInvalidationLists<'a> { - /// Invalidations for normal DOM children and pseudo-elements. - /// - /// TODO(emilio): Having a list of invalidations just for pseudo-elements - /// may save some work here and there. - pub dom_descendants: InvalidationVector<'a>, - /// Invalidations for slotted children of an element. - pub slotted_descendants: InvalidationVector<'a>, - /// Invalidations for ::part()s of an element. - pub parts: InvalidationVector<'a>, -} - -impl<'a> DescendantInvalidationLists<'a> { - fn is_empty(&self) -> bool { - self.dom_descendants.is_empty() && - self.slotted_descendants.is_empty() && - self.parts.is_empty() - } -} - -/// The struct that takes care of encapsulating all the logic on where and how -/// element styles need to be invalidated. -pub struct TreeStyleInvalidator<'a, 'b, E, P: 'a> -where - 'b: 'a, - E: TElement, - P: InvalidationProcessor<'b, E>, -{ - element: E, - stack_limit_checker: Option<&'a StackLimitChecker>, - processor: &'a mut P, - _marker: ::std::marker::PhantomData<&'b ()>, -} - -/// A vector of invalidations, optimized for small invalidation sets. -pub type InvalidationVector<'a> = SmallVec<[Invalidation<'a>; 10]>; - -/// The kind of descendant invalidation we're processing. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum DescendantInvalidationKind { - /// A DOM descendant invalidation. - Dom, - /// A ::slotted() descendant invalidation. - Slotted, - /// A ::part() descendant invalidation. - Part, -} - -/// The kind of invalidation we're processing. -/// -/// We can use this to avoid pushing invalidations of the same kind to our -/// descendants or siblings. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum InvalidationKind { - Descendant(DescendantInvalidationKind), - Sibling, -} - -/// An `Invalidation` is a complex selector that describes which elements, -/// relative to a current element we are processing, must be restyled. -#[derive(Clone)] -pub struct Invalidation<'a> { - /// The dependency that generated this invalidation. - /// - /// Note that the offset inside the dependency is not really useful after - /// construction. - dependency: &'a Dependency, - /// The right shadow host from where the rule came from, if any. - /// - /// This is needed to ensure that we match the selector with the right - /// state, as whether some selectors like :host and ::part() match depends - /// on it. - scope: Option<OpaqueElement>, - /// The offset of the selector pointing to a compound selector. - /// - /// This order is a "parse order" offset, that is, zero is the leftmost part - /// of the selector written as parsed / serialized. - /// - /// It is initialized from the offset from `dependency`. - offset: usize, - /// Whether the invalidation was already matched by any previous sibling or - /// ancestor. - /// - /// If this is the case, we can avoid pushing invalidations generated by - /// this one if the generated invalidation is effective for all the siblings - /// or descendants after us. - matched_by_any_previous: bool, -} - -impl<'a> Invalidation<'a> { - /// Create a new invalidation for matching a dependency. - pub fn new(dependency: &'a Dependency, scope: Option<OpaqueElement>) -> Self { - debug_assert!( - dependency.selector_offset == dependency.selector.len() + 1 || - dependency.invalidation_kind() != DependencyInvalidationKind::Element, - "No point to this, if the dependency matched the element we should just invalidate it" - ); - Self { - dependency, - scope, - // + 1 to go past the combinator. - offset: dependency.selector.len() + 1 - dependency.selector_offset, - matched_by_any_previous: false, - } - } - - /// Whether this invalidation is effective for the next sibling or - /// descendant after us. - fn effective_for_next(&self) -> bool { - if self.offset == 0 { - return true; - } - - // TODO(emilio): For pseudo-elements this should be mostly false, except - // for the weird pseudos in <input type="number">. - // - // We should be able to do better here! - match self - .dependency - .selector - .combinator_at_parse_order(self.offset - 1) - { - Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true, - Combinator::Part | - Combinator::SlotAssignment | - Combinator::NextSibling | - Combinator::Child => false, - } - } - - fn kind(&self) -> InvalidationKind { - if self.offset == 0 { - return InvalidationKind::Descendant(DescendantInvalidationKind::Dom); - } - - match self - .dependency - .selector - .combinator_at_parse_order(self.offset - 1) - { - Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => { - InvalidationKind::Descendant(DescendantInvalidationKind::Dom) - }, - Combinator::Part => InvalidationKind::Descendant(DescendantInvalidationKind::Part), - Combinator::SlotAssignment => { - InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) - }, - Combinator::NextSibling | Combinator::LaterSibling => InvalidationKind::Sibling, - } - } -} - -impl<'a> fmt::Debug for Invalidation<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use cssparser::ToCss; - - f.write_str("Invalidation(")?; - for component in self - .dependency - .selector - .iter_raw_parse_order_from(self.offset) - { - if matches!(*component, Component::Combinator(..)) { - break; - } - component.to_css(f)?; - } - f.write_char(')') - } -} - -/// The result of processing a single invalidation for a given element. -struct SingleInvalidationResult { - /// Whether the element itself was invalidated. - invalidated_self: bool, - /// Whether the invalidation matched, either invalidating the element or - /// generating another invalidation. - matched: bool, -} - -/// The result of a whole invalidation process for a given element. -pub struct InvalidationResult { - /// Whether the element itself was invalidated. - invalidated_self: bool, - /// Whether the element's descendants were invalidated. - invalidated_descendants: bool, - /// Whether the element's siblings were invalidated. - invalidated_siblings: bool, -} - -impl InvalidationResult { - /// Create an emtpy result. - pub fn empty() -> Self { - Self { - invalidated_self: false, - invalidated_descendants: false, - invalidated_siblings: false, - } - } - - /// Whether the invalidation has invalidate the element itself. - pub fn has_invalidated_self(&self) -> bool { - self.invalidated_self - } - - /// Whether the invalidation has invalidate desendants. - pub fn has_invalidated_descendants(&self) -> bool { - self.invalidated_descendants - } - - /// Whether the invalidation has invalidate siblings. - pub fn has_invalidated_siblings(&self) -> bool { - self.invalidated_siblings - } -} - -impl<'a, 'b, E, P: 'a> TreeStyleInvalidator<'a, 'b, E, P> -where - 'b: 'a, - E: TElement, - P: InvalidationProcessor<'b, E>, -{ - /// Trivially constructs a new `TreeStyleInvalidator`. - pub fn new( - element: E, - stack_limit_checker: Option<&'a StackLimitChecker>, - processor: &'a mut P, - ) -> Self { - Self { - element, - stack_limit_checker, - processor, - _marker: ::std::marker::PhantomData, - } - } - - /// Perform the invalidation pass. - pub fn invalidate(mut self) -> InvalidationResult { - debug!("StyleTreeInvalidator::invalidate({:?})", self.element); - - let mut self_invalidations = InvalidationVector::new(); - let mut descendant_invalidations = DescendantInvalidationLists::default(); - let mut sibling_invalidations = InvalidationVector::new(); - - let mut invalidated_self = self.processor.collect_invalidations( - self.element, - &mut self_invalidations, - &mut descendant_invalidations, - &mut sibling_invalidations, - ); - - debug!("Collected invalidations (self: {}): ", invalidated_self); - debug!( - " > self: {}, {:?}", - self_invalidations.len(), - self_invalidations - ); - debug!(" > descendants: {:?}", descendant_invalidations); - debug!( - " > siblings: {}, {:?}", - sibling_invalidations.len(), - sibling_invalidations - ); - - let invalidated_self_from_collection = invalidated_self; - - invalidated_self |= self.process_descendant_invalidations( - &self_invalidations, - &mut descendant_invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Dom, - ); - - if invalidated_self && !invalidated_self_from_collection { - self.processor.invalidated_self(self.element); - } - - let invalidated_descendants = self.invalidate_descendants(&descendant_invalidations); - let invalidated_siblings = self.invalidate_siblings(&mut sibling_invalidations); - - InvalidationResult { - invalidated_self, - invalidated_descendants, - invalidated_siblings, - } - } - - /// Go through later DOM siblings, invalidating style as needed using the - /// `sibling_invalidations` list. - /// - /// Returns whether any sibling's style or any sibling descendant's style - /// was invalidated. - fn invalidate_siblings(&mut self, sibling_invalidations: &mut InvalidationVector<'b>) -> bool { - if sibling_invalidations.is_empty() { - return false; - } - - let mut current = self.element.next_sibling_element(); - let mut any_invalidated = false; - - while let Some(sibling) = current { - let mut sibling_invalidator = - TreeStyleInvalidator::new(sibling, self.stack_limit_checker, self.processor); - - let mut invalidations_for_descendants = DescendantInvalidationLists::default(); - let invalidated_sibling = sibling_invalidator.process_sibling_invalidations( - &mut invalidations_for_descendants, - sibling_invalidations, - ); - - if invalidated_sibling { - sibling_invalidator - .processor - .invalidated_sibling(sibling, self.element); - } - - any_invalidated |= invalidated_sibling; - - any_invalidated |= - sibling_invalidator.invalidate_descendants(&invalidations_for_descendants); - - if sibling_invalidations.is_empty() { - break; - } - - current = sibling.next_sibling_element(); - } - - any_invalidated - } - - fn invalidate_pseudo_element_or_nac( - &mut self, - child: E, - invalidations: &[Invalidation<'b>], - ) -> bool { - let mut sibling_invalidations = InvalidationVector::new(); - - let result = self.invalidate_child( - child, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Dom, - ); - - // Roots of NAC subtrees can indeed generate sibling invalidations, but - // they can be just ignored, since they have no siblings. - // - // Note that we can end up testing selectors that wouldn't end up - // matching due to this being NAC, like those coming from document - // rules, but we overinvalidate instead of checking this. - - result - } - - /// Invalidate a child and recurse down invalidating its descendants if - /// needed. - fn invalidate_child( - &mut self, - child: E, - invalidations: &[Invalidation<'b>], - sibling_invalidations: &mut InvalidationVector<'b>, - descendant_invalidation_kind: DescendantInvalidationKind, - ) -> bool { - let mut invalidations_for_descendants = DescendantInvalidationLists::default(); - - let mut invalidated_child = false; - let invalidated_descendants = { - let mut child_invalidator = - TreeStyleInvalidator::new(child, self.stack_limit_checker, self.processor); - - invalidated_child |= child_invalidator.process_sibling_invalidations( - &mut invalidations_for_descendants, - sibling_invalidations, - ); - - invalidated_child |= child_invalidator.process_descendant_invalidations( - invalidations, - &mut invalidations_for_descendants, - sibling_invalidations, - descendant_invalidation_kind, - ); - - if invalidated_child { - child_invalidator.processor.invalidated_self(child); - } - - child_invalidator.invalidate_descendants(&invalidations_for_descendants) - }; - - // The child may not be a flattened tree child of the current element, - // but may be arbitrarily deep. - // - // Since we keep the traversal flags in terms of the flattened tree, - // we need to propagate it as appropriate. - if invalidated_child || invalidated_descendants { - self.processor.invalidated_descendants(self.element, child); - } - - invalidated_child || invalidated_descendants - } - - fn invalidate_nac(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - let mut any_nac_root = false; - - let element = self.element; - element.each_anonymous_content_child(|nac| { - any_nac_root |= self.invalidate_pseudo_element_or_nac(nac, invalidations); - }); - - any_nac_root - } - - // NB: It's important that this operates on DOM children, which is what - // selector-matching operates on. - fn invalidate_dom_descendants_of( - &mut self, - parent: E::ConcreteNode, - invalidations: &[Invalidation<'b>], - ) -> bool { - let mut any_descendant = false; - - let mut sibling_invalidations = InvalidationVector::new(); - for child in parent.dom_children() { - let child = match child.as_element() { - Some(e) => e, - None => continue, - }; - - any_descendant |= self.invalidate_child( - child, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Dom, - ); - } - - any_descendant - } - - fn invalidate_parts_in_shadow_tree( - &mut self, - shadow: <E::ConcreteNode as TNode>::ConcreteShadowRoot, - invalidations: &[Invalidation<'b>], - ) -> bool { - debug_assert!(!invalidations.is_empty()); - - let mut any = false; - let mut sibling_invalidations = InvalidationVector::new(); - - for node in shadow.as_node().dom_descendants() { - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - if element.has_part_attr() { - any |= self.invalidate_child( - element, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Part, - ); - debug_assert!( - sibling_invalidations.is_empty(), - "::part() shouldn't have sibling combinators to the right, \ - this makes no sense! {:?}", - sibling_invalidations - ); - } - - if let Some(shadow) = element.shadow_root() { - if element.exports_any_part() { - any |= self.invalidate_parts_in_shadow_tree(shadow, invalidations) - } - } - } - - any - } - - fn invalidate_parts(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - if invalidations.is_empty() { - return false; - } - - let shadow = match self.element.shadow_root() { - Some(s) => s, - None => return false, - }; - - self.invalidate_parts_in_shadow_tree(shadow, invalidations) - } - - fn invalidate_slotted_elements(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - if invalidations.is_empty() { - return false; - } - - let slot = self.element; - self.invalidate_slotted_elements_in_slot(slot, invalidations) - } - - fn invalidate_slotted_elements_in_slot( - &mut self, - slot: E, - invalidations: &[Invalidation<'b>], - ) -> bool { - let mut any = false; - - let mut sibling_invalidations = InvalidationVector::new(); - for node in slot.slotted_nodes() { - let element = match node.as_element() { - Some(e) => e, - None => continue, - }; - - if element.is_html_slot_element() { - any |= self.invalidate_slotted_elements_in_slot(element, invalidations); - } else { - any |= self.invalidate_child( - element, - invalidations, - &mut sibling_invalidations, - DescendantInvalidationKind::Slotted, - ); - } - - debug_assert!( - sibling_invalidations.is_empty(), - "::slotted() shouldn't have sibling combinators to the right, \ - this makes no sense! {:?}", - sibling_invalidations - ); - } - - any - } - - fn invalidate_non_slotted_descendants(&mut self, invalidations: &[Invalidation<'b>]) -> bool { - if invalidations.is_empty() { - return false; - } - - if self.processor.light_tree_only() { - let node = self.element.as_node(); - return self.invalidate_dom_descendants_of(node, invalidations); - } - - let mut any_descendant = false; - - // NOTE(emilio): This is only needed for Shadow DOM to invalidate - // correctly on :host(..) changes. Instead of doing this, we could add - // a third kind of invalidation list that walks shadow root children, - // but it's not clear it's worth it. - // - // Also, it's needed as of right now for document state invalidation, - // where we rely on iterating every element that ends up in the composed - // doc, but we could fix that invalidating per subtree. - if let Some(root) = self.element.shadow_root() { - any_descendant |= self.invalidate_dom_descendants_of(root.as_node(), invalidations); - } - - if let Some(marker) = self.element.marker_pseudo_element() { - any_descendant |= self.invalidate_pseudo_element_or_nac(marker, invalidations); - } - - if let Some(before) = self.element.before_pseudo_element() { - any_descendant |= self.invalidate_pseudo_element_or_nac(before, invalidations); - } - - let node = self.element.as_node(); - any_descendant |= self.invalidate_dom_descendants_of(node, invalidations); - - if let Some(after) = self.element.after_pseudo_element() { - any_descendant |= self.invalidate_pseudo_element_or_nac(after, invalidations); - } - - any_descendant |= self.invalidate_nac(invalidations); - - any_descendant - } - - /// Given the descendant invalidation lists, go through the current - /// element's descendants, and invalidate style on them. - fn invalidate_descendants(&mut self, invalidations: &DescendantInvalidationLists<'b>) -> bool { - if invalidations.is_empty() { - return false; - } - - debug!( - "StyleTreeInvalidator::invalidate_descendants({:?})", - self.element - ); - debug!(" > {:?}", invalidations); - - let should_process = self.processor.should_process_descendants(self.element); - - if !should_process { - return false; - } - - if let Some(checker) = self.stack_limit_checker { - if checker.limit_exceeded() { - self.processor.recursion_limit_exceeded(self.element); - return true; - } - } - - let mut any_descendant = false; - - any_descendant |= self.invalidate_non_slotted_descendants(&invalidations.dom_descendants); - any_descendant |= self.invalidate_slotted_elements(&invalidations.slotted_descendants); - any_descendant |= self.invalidate_parts(&invalidations.parts); - - any_descendant - } - - /// Process the given sibling invalidations coming from our previous - /// sibling. - /// - /// The sibling invalidations are somewhat special because they can be - /// modified on the fly. New invalidations may be added and removed. - /// - /// In particular, all descendants get the same set of invalidations from - /// the parent, but the invalidations from a given sibling depend on the - /// ones we got from the previous one. - /// - /// Returns whether invalidated the current element's style. - fn process_sibling_invalidations( - &mut self, - descendant_invalidations: &mut DescendantInvalidationLists<'b>, - sibling_invalidations: &mut InvalidationVector<'b>, - ) -> bool { - let mut i = 0; - let mut new_sibling_invalidations = InvalidationVector::new(); - let mut invalidated_self = false; - - while i < sibling_invalidations.len() { - let result = self.process_invalidation( - &sibling_invalidations[i], - descendant_invalidations, - &mut new_sibling_invalidations, - InvalidationKind::Sibling, - ); - - invalidated_self |= result.invalidated_self; - sibling_invalidations[i].matched_by_any_previous |= result.matched; - if sibling_invalidations[i].effective_for_next() { - i += 1; - } else { - sibling_invalidations.remove(i); - } - } - - sibling_invalidations.extend(new_sibling_invalidations.drain(..)); - invalidated_self - } - - /// Process a given invalidation list coming from our parent, - /// adding to `descendant_invalidations` and `sibling_invalidations` as - /// needed. - /// - /// Returns whether our style was invalidated as a result. - fn process_descendant_invalidations( - &mut self, - invalidations: &[Invalidation<'b>], - descendant_invalidations: &mut DescendantInvalidationLists<'b>, - sibling_invalidations: &mut InvalidationVector<'b>, - descendant_invalidation_kind: DescendantInvalidationKind, - ) -> bool { - let mut invalidated = false; - - for invalidation in invalidations { - let result = self.process_invalidation( - invalidation, - descendant_invalidations, - sibling_invalidations, - InvalidationKind::Descendant(descendant_invalidation_kind), - ); - - invalidated |= result.invalidated_self; - if invalidation.effective_for_next() { - let mut invalidation = invalidation.clone(); - invalidation.matched_by_any_previous |= result.matched; - debug_assert_eq!( - descendant_invalidation_kind, - DescendantInvalidationKind::Dom, - "Slotted or part invalidations don't propagate." - ); - descendant_invalidations.dom_descendants.push(invalidation); - } - } - - invalidated - } - - /// Processes a given invalidation, potentially invalidating the style of - /// the current element. - /// - /// Returns whether invalidated the style of the element, and whether the - /// invalidation should be effective to subsequent siblings or descendants - /// down in the tree. - fn process_invalidation( - &mut self, - invalidation: &Invalidation<'b>, - descendant_invalidations: &mut DescendantInvalidationLists<'b>, - sibling_invalidations: &mut InvalidationVector<'b>, - invalidation_kind: InvalidationKind, - ) -> SingleInvalidationResult { - debug!( - "TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})", - self.element, invalidation, invalidation_kind - ); - - let matching_result = { - let context = self.processor.matching_context(); - context.current_host = invalidation.scope; - - matches_compound_selector_from( - &invalidation.dependency.selector, - invalidation.offset, - context, - &self.element, - ) - }; - - let next_invalidation = match matching_result { - CompoundSelectorMatchingResult::NotMatched => { - return SingleInvalidationResult { - invalidated_self: false, - matched: false, - } - }, - CompoundSelectorMatchingResult::FullyMatched => { - debug!(" > Invalidation matched completely"); - // We matched completely. If we're an inner selector now we need - // to go outside our selector and carry on invalidating. - let mut cur_dependency = invalidation.dependency; - loop { - cur_dependency = match cur_dependency.parent { - None => { - return SingleInvalidationResult { - invalidated_self: true, - matched: true, - } - }, - Some(ref p) => &**p, - }; - - debug!(" > Checking outer dependency {:?}", cur_dependency); - - // The inner selector changed, now check if the full - // previous part of the selector did, before keeping - // checking for descendants. - if !self - .processor - .check_outer_dependency(cur_dependency, self.element) - { - return SingleInvalidationResult { - invalidated_self: false, - matched: false, - }; - } - - if cur_dependency.invalidation_kind() == DependencyInvalidationKind::Element { - continue; - } - - debug!(" > Generating invalidation"); - break Invalidation::new(cur_dependency, invalidation.scope); - } - }, - CompoundSelectorMatchingResult::Matched { - next_combinator_offset, - } => Invalidation { - dependency: invalidation.dependency, - scope: invalidation.scope, - offset: next_combinator_offset + 1, - matched_by_any_previous: false, - }, - }; - - debug_assert_ne!( - next_invalidation.offset, 0, - "Rightmost selectors shouldn't generate more invalidations", - ); - - let mut invalidated_self = false; - let next_combinator = next_invalidation - .dependency - .selector - .combinator_at_parse_order(next_invalidation.offset - 1); - - if matches!(next_combinator, Combinator::PseudoElement) && - self.processor.invalidates_on_pseudo_element() - { - // We need to invalidate the element whenever pseudos change, for - // two reasons: - // - // * Eager pseudo styles are stored as part of the originating - // element's computed style. - // - // * Lazy pseudo-styles might be cached on the originating - // element's pseudo-style cache. - // - // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS - // hint?). - // - // Note that we'll also restyle the pseudo-element because it would - // match this invalidation. - // - // FIXME: For non-element-backed pseudos this is still not quite - // correct. For example for ::selection even though we invalidate - // the style properly there's nothing that triggers a repaint - // necessarily. Though this matches old Gecko behavior, and the - // ::selection implementation needs to change significantly anyway - // to implement https://github.com/w3c/csswg-drafts/issues/2474 for - // example. - invalidated_self = true; - } - - debug!( - " > Invalidation matched, next: {:?}, ({:?})", - next_invalidation, next_combinator - ); - - let next_invalidation_kind = next_invalidation.kind(); - - // We can skip pushing under some circumstances, and we should - // because otherwise the invalidation list could grow - // exponentially. - // - // * First of all, both invalidations need to be of the same - // kind. This is because of how we propagate them going to - // the right of the tree for sibling invalidations and going - // down the tree for children invalidations. A sibling - // invalidation that ends up generating a children - // invalidation ends up (correctly) in five different lists, - // not in the same list five different times. - // - // * Then, the invalidation needs to be matched by a previous - // ancestor/sibling, in order to know that this invalidation - // has been generated already. - // - // * Finally, the new invalidation needs to be - // `effective_for_next()`, in order for us to know that it is - // still in the list, since we remove the dependencies that - // aren't from the lists for our children / siblings. - // - // To go through an example, let's imagine we are processing a - // dom subtree like: - // - // <div><address><div><div/></div></address></div> - // - // And an invalidation list with a single invalidation like: - // - // [div div div] - // - // When we process the invalidation list for the outer div, we - // match it, and generate a `div div` invalidation, so for the - // <address> child we have: - // - // [div div div, div div] - // - // With the first of them marked as `matched`. - // - // When we process the <address> child, we don't match any of - // them, so both invalidations go untouched to our children. - // - // When we process the second <div>, we match _both_ - // invalidations. - // - // However, when matching the first, we can tell it's been - // matched, and not push the corresponding `div div` - // invalidation, since we know it's necessarily already on the - // list. - // - // Thus, without skipping the push, we'll arrive to the - // innermost <div> with: - // - // [div div div, div div, div div, div] - // - // While skipping it, we won't arrive here with duplicating - // dependencies: - // - // [div div div, div div, div] - // - let can_skip_pushing = next_invalidation_kind == invalidation_kind && - invalidation.matched_by_any_previous && - next_invalidation.effective_for_next(); - - if can_skip_pushing { - debug!( - " > Can avoid push, since the invalidation had \ - already been matched before" - ); - } else { - match next_invalidation_kind { - InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => { - descendant_invalidations - .dom_descendants - .push(next_invalidation); - }, - InvalidationKind::Descendant(DescendantInvalidationKind::Part) => { - descendant_invalidations.parts.push(next_invalidation); - }, - InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => { - descendant_invalidations - .slotted_descendants - .push(next_invalidation); - }, - InvalidationKind::Sibling => { - sibling_invalidations.push(next_invalidation); - }, - } - } - - SingleInvalidationResult { - invalidated_self, - matched: true, - } - } -} diff --git a/components/style/invalidation/element/mod.rs b/components/style/invalidation/element/mod.rs deleted file mode 100644 index 1f19cc54f59..00000000000 --- a/components/style/invalidation/element/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -/* 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/. */ - -//! Invalidation of element styles due to attribute or style changes. - -pub mod document_state; -pub mod element_wrapper; -pub mod invalidation_map; -pub mod invalidator; -pub mod restyle_hints; -pub mod state_and_attributes; diff --git a/components/style/invalidation/element/restyle_hints.rs b/components/style/invalidation/element/restyle_hints.rs deleted file mode 100644 index ffadecd7aa2..00000000000 --- a/components/style/invalidation/element/restyle_hints.rs +++ /dev/null @@ -1,190 +0,0 @@ -/* 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/. */ - -//! Restyle hints: an optimization to avoid unnecessarily matching selectors. - -use crate::traversal_flags::TraversalFlags; - -bitflags! { - /// The kind of restyle we need to do for a given element. - #[repr(C)] - pub struct RestyleHint: u16 { - /// Do a selector match of the element. - const RESTYLE_SELF = 1 << 0; - - /// Do a selector match of the element's pseudo-elements. Always to be combined with - /// RESTYLE_SELF. - const RESTYLE_PSEUDOS = 1 << 1; - - /// Do a selector match if the element is a pseudo-element. - const RESTYLE_SELF_IF_PSEUDO = 1 << 2; - - /// Do a selector match of the element's descendants. - const RESTYLE_DESCENDANTS = 1 << 3; - - /// Recascade the current element. - const RECASCADE_SELF = 1 << 4; - - /// Recascade the current element if it inherits any reset style. - const RECASCADE_SELF_IF_INHERIT_RESET_STYLE = 1 << 5; - - /// Recascade all descendant elements. - const RECASCADE_DESCENDANTS = 1 << 6; - - /// Replace the style data coming from CSS transitions without updating - /// any other style data. This hint is only processed in animation-only - /// traversal which is prior to normal traversal. - const RESTYLE_CSS_TRANSITIONS = 1 << 7; - - /// Replace the style data coming from CSS animations without updating - /// any other style data. This hint is only processed in animation-only - /// traversal which is prior to normal traversal. - const RESTYLE_CSS_ANIMATIONS = 1 << 8; - - /// Don't re-run selector-matching on the element, only the style - /// attribute has changed, and this change didn't have any other - /// dependencies. - const RESTYLE_STYLE_ATTRIBUTE = 1 << 9; - - /// Replace the style data coming from SMIL animations without updating - /// any other style data. This hint is only processed in animation-only - /// traversal which is prior to normal traversal. - const RESTYLE_SMIL = 1 << 10; - } -} - -impl RestyleHint { - /// Creates a new `RestyleHint` indicating that the current element and all - /// its descendants must be fully restyled. - pub fn restyle_subtree() -> Self { - RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS - } - - /// Creates a new `RestyleHint` indicating that the current element and all - /// its descendants must be recascaded. - pub fn recascade_subtree() -> Self { - RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS - } - - /// Returns whether this hint invalidates the element and all its - /// descendants. - pub fn contains_subtree(&self) -> bool { - self.contains(Self::restyle_subtree()) - } - - /// Returns whether we'll recascade all of the descendants. - pub fn will_recascade_subtree(&self) -> bool { - self.contains_subtree() || self.contains(Self::recascade_subtree()) - } - - /// Returns whether we need to restyle this element. - pub fn has_non_animation_invalidations(&self) -> bool { - !(*self & !Self::for_animations()).is_empty() - } - - /// Propagates this restyle hint to a child element. - pub fn propagate(&mut self, traversal_flags: &TraversalFlags) -> Self { - use std::mem; - - // In the middle of an animation only restyle, we don't need to - // propagate any restyle hints, and we need to remove ourselves. - if traversal_flags.for_animation_only() { - self.remove_animation_hints(); - return Self::empty(); - } - - debug_assert!( - !self.has_animation_hint(), - "There should not be any animation restyle hints \ - during normal traversal" - ); - - // Else we should clear ourselves, and return the propagated hint. - mem::replace(self, Self::empty()).propagate_for_non_animation_restyle() - } - - /// Returns a new `RestyleHint` appropriate for children of the current element. - fn propagate_for_non_animation_restyle(&self) -> Self { - if self.contains(RestyleHint::RESTYLE_DESCENDANTS) { - return Self::restyle_subtree(); - } - let mut result = Self::empty(); - if self.contains(RestyleHint::RESTYLE_PSEUDOS) { - result |= Self::RESTYLE_SELF_IF_PSEUDO; - } - if self.contains(RestyleHint::RECASCADE_DESCENDANTS) { - result |= Self::recascade_subtree(); - } - result - } - - /// Returns a hint that contains all the replacement hints. - pub fn replacements() -> Self { - RestyleHint::RESTYLE_STYLE_ATTRIBUTE | Self::for_animations() - } - - /// The replacements for the animation cascade levels. - #[inline] - pub fn for_animations() -> Self { - RestyleHint::RESTYLE_SMIL | - RestyleHint::RESTYLE_CSS_ANIMATIONS | - RestyleHint::RESTYLE_CSS_TRANSITIONS - } - - /// Returns whether the hint specifies that an animation cascade level must - /// be replaced. - #[inline] - pub fn has_animation_hint(&self) -> bool { - self.intersects(Self::for_animations()) - } - - /// Returns whether the hint specifies that an animation cascade level must - /// be replaced. - #[inline] - pub fn has_animation_hint_or_recascade(&self) -> bool { - self.intersects( - Self::for_animations() | - Self::RECASCADE_SELF | - Self::RECASCADE_SELF_IF_INHERIT_RESET_STYLE, - ) - } - - /// Returns whether the hint specifies some restyle work other than an - /// animation cascade level replacement. - #[inline] - pub fn has_non_animation_hint(&self) -> bool { - !(*self & !Self::for_animations()).is_empty() - } - - /// Returns whether the hint specifies that some cascade levels must be - /// replaced. - #[inline] - pub fn has_replacements(&self) -> bool { - self.intersects(Self::replacements()) - } - - /// Removes all of the animation-related hints. - #[inline] - pub fn remove_animation_hints(&mut self) { - self.remove(Self::for_animations()); - - // While RECASCADE_SELF is not animation-specific, we only ever add and process it during - // traversal. If we are here, removing animation hints, then we are in an animation-only - // traversal, and we know that any RECASCADE_SELF flag must have been set due to changes in - // inherited values after restyling for animations, and thus we want to remove it so that - // we don't later try to restyle the element during a normal restyle. - // (We could have separate RECASCADE_SELF_NORMAL and RECASCADE_SELF_ANIMATIONS flags to - // make it clear, but this isn't currently necessary.) - self.remove(Self::RECASCADE_SELF | Self::RECASCADE_SELF_IF_INHERIT_RESET_STYLE); - } -} - -impl Default for RestyleHint { - fn default() -> Self { - Self::empty() - } -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(RestyleHint); diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs deleted file mode 100644 index bd69f35c66c..00000000000 --- a/components/style/invalidation/element/state_and_attributes.rs +++ /dev/null @@ -1,552 +0,0 @@ -/* 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/. */ - -//! An invalidation processor for style changes due to state and attribute -//! changes. - -use crate::context::SharedStyleContext; -use crate::data::ElementData; -use crate::dom::{TElement, TNode}; -use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; -use crate::invalidation::element::invalidation_map::*; -use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector}; -use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::selector_map::SelectorMap; -use crate::selector_parser::Snapshot; -use crate::stylesheets::origin::OriginSet; -use crate::{Atom, WeakAtom}; -use selectors::attr::CaseSensitivity; -use selectors::matching::{ - matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags, VisitedHandlingMode, -}; -use selectors::NthIndexCache; -use smallvec::SmallVec; -use style_traits::dom::ElementState; - -/// The collector implementation. -struct Collector<'a, 'b: 'a, 'selectors: 'a, E> -where - E: TElement, -{ - element: E, - wrapper: ElementWrapper<'b, E>, - snapshot: &'a Snapshot, - matching_context: &'a mut MatchingContext<'b, E::Impl>, - lookup_element: E, - removed_id: Option<&'a WeakAtom>, - added_id: Option<&'a WeakAtom>, - classes_removed: &'a SmallVec<[Atom; 8]>, - classes_added: &'a SmallVec<[Atom; 8]>, - state_changes: ElementState, - descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>, - sibling_invalidations: &'a mut InvalidationVector<'selectors>, - invalidates_self: bool, -} - -/// An invalidation processor for style changes due to state and attribute -/// changes. -pub struct StateAndAttrInvalidationProcessor<'a, 'b: 'a, E: TElement> { - shared_context: &'a SharedStyleContext<'b>, - element: E, - data: &'a mut ElementData, - matching_context: MatchingContext<'a, E::Impl>, -} - -impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E> { - /// Creates a new StateAndAttrInvalidationProcessor. - pub fn new( - shared_context: &'a SharedStyleContext<'b>, - element: E, - data: &'a mut ElementData, - nth_index_cache: &'a mut NthIndexCache, - ) -> Self { - let matching_context = MatchingContext::new_for_visited( - MatchingMode::Normal, - None, - nth_index_cache, - VisitedHandlingMode::AllLinksVisitedAndUnvisited, - shared_context.quirks_mode(), - NeedsSelectorFlags::No, - ); - - Self { - shared_context, - element, - data, - matching_context, - } - } -} - -/// Checks a dependency against a given element and wrapper, to see if something -/// changed. -pub fn check_dependency<E, W>( - dependency: &Dependency, - element: &E, - wrapper: &W, - context: &mut MatchingContext<'_, E::Impl>, -) -> bool -where - E: TElement, - W: selectors::Element<Impl = E::Impl>, -{ - let matches_now = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - element, - context, - ); - - let matched_then = matches_selector( - &dependency.selector, - dependency.selector_offset, - None, - wrapper, - context, - ); - - matched_then != matches_now -} - -/// Whether we should process the descendants of a given element for style -/// invalidation. -pub fn should_process_descendants(data: &ElementData) -> bool { - !data.styles.is_display_none() && !data.hint.contains(RestyleHint::RESTYLE_DESCENDANTS) -} - -/// Propagates the bits after invalidating a descendant child. -pub fn propagate_dirty_bit_up_to<E>(ancestor: E, child: E) -where - E: TElement, -{ - // The child may not be a flattened tree child of the current element, - // but may be arbitrarily deep. - // - // Since we keep the traversal flags in terms of the flattened tree, - // we need to propagate it as appropriate. - let mut current = child.traversal_parent(); - while let Some(parent) = current.take() { - unsafe { parent.set_dirty_descendants() }; - current = parent.traversal_parent(); - - if parent == ancestor { - return; - } - } - debug_assert!( - false, - "Should've found {:?} as an ancestor of {:?}", - ancestor, child - ); -} - -/// Propagates the bits after invalidating a descendant child, if needed. -pub fn invalidated_descendants<E>(element: E, child: E) -where - E: TElement, -{ - if !child.has_data() { - return; - } - propagate_dirty_bit_up_to(element, child) -} - -/// Sets the appropriate restyle hint after invalidating the style of a given -/// element. -pub fn invalidated_self<E>(element: E) -> bool -where - E: TElement, -{ - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - data.hint.insert(RestyleHint::RESTYLE_SELF); - true -} - -/// Sets the appropriate hint after invalidating the style of a sibling. -pub fn invalidated_sibling<E>(element: E, of: E) -where - E: TElement, -{ - debug_assert_eq!( - element.as_node().parent_node(), - of.as_node().parent_node(), - "Should be siblings" - ); - if !invalidated_self(element) { - return; - } - if element.traversal_parent() != of.traversal_parent() { - let parent = element.as_node().parent_element_or_host(); - debug_assert!( - parent.is_some(), - "How can we have siblings without parent nodes?" - ); - if let Some(e) = parent { - propagate_dirty_bit_up_to(e, element) - } - } -} - -impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'a, E> - for StateAndAttrInvalidationProcessor<'a, 'b, E> -where - E: TElement, -{ - /// We need to invalidate style on pseudo-elements, in order to process - /// changes that could otherwise end up in ::before or ::after content being - /// generated, and invalidate lazy pseudo caches. - fn invalidates_on_pseudo_element(&self) -> bool { - true - } - - fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool { - // We cannot assert about `element` having a snapshot here (in fact it - // most likely won't), because it may be an arbitrary descendant or - // later-sibling of the element we started invalidating with. - let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map); - check_dependency(dependency, &element, &wrapper, &mut self.matching_context) - } - - fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { - &mut self.matching_context - } - - fn collect_invalidations( - &mut self, - element: E, - _self_invalidations: &mut InvalidationVector<'a>, - descendant_invalidations: &mut DescendantInvalidationLists<'a>, - sibling_invalidations: &mut InvalidationVector<'a>, - ) -> bool { - debug_assert_eq!(element, self.element); - debug_assert!(element.has_snapshot(), "Why bothering?"); - - let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map); - - let state_changes = wrapper.state_changes(); - let snapshot = wrapper.snapshot().expect("has_snapshot lied"); - - if !snapshot.has_attrs() && state_changes.is_empty() { - return false; - } - - let mut classes_removed = SmallVec::<[Atom; 8]>::new(); - let mut classes_added = SmallVec::<[Atom; 8]>::new(); - if snapshot.class_changed() { - // TODO(emilio): Do this more efficiently! - snapshot.each_class(|c| { - if !element.has_class(c, CaseSensitivity::CaseSensitive) { - classes_removed.push(c.0.clone()) - } - }); - - element.each_class(|c| { - if !snapshot.has_class(c, CaseSensitivity::CaseSensitive) { - classes_added.push(c.0.clone()) - } - }) - } - - let mut id_removed = None; - let mut id_added = None; - if snapshot.id_changed() { - let old_id = snapshot.id_attr(); - let current_id = element.id(); - - if old_id != current_id { - id_removed = old_id; - id_added = current_id; - } - } - - if log_enabled!(::log::Level::Debug) { - debug!("Collecting changes for: {:?}", element); - if !state_changes.is_empty() { - debug!(" > state: {:?}", state_changes); - } - if snapshot.id_changed() { - debug!(" > id changed: +{:?} -{:?}", id_added, id_removed); - } - if snapshot.class_changed() { - debug!( - " > class changed: +{:?} -{:?}", - classes_added, classes_removed - ); - } - let mut attributes_changed = false; - snapshot.each_attr_changed(|_| { - attributes_changed = true; - }); - if attributes_changed { - debug!( - " > attributes changed, old: {}", - snapshot.debug_list_attributes() - ) - } - } - - let lookup_element = if element.implemented_pseudo_element().is_some() { - element.pseudo_element_originating_element().unwrap() - } else { - element - }; - - let mut shadow_rule_datas = SmallVec::<[_; 3]>::new(); - let matches_document_author_rules = - element.each_applicable_non_document_style_rule_data(|data, host| { - shadow_rule_datas.push((data, host.opaque())) - }); - - let invalidated_self = { - let mut collector = Collector { - wrapper, - lookup_element, - state_changes, - element, - snapshot: &snapshot, - matching_context: &mut self.matching_context, - removed_id: id_removed, - added_id: id_added, - classes_removed: &classes_removed, - classes_added: &classes_added, - descendant_invalidations, - sibling_invalidations, - invalidates_self: false, - }; - - let document_origins = if !matches_document_author_rules { - OriginSet::ORIGIN_USER_AGENT | OriginSet::ORIGIN_USER - } else { - OriginSet::all() - }; - - for (cascade_data, origin) in self.shared_context.stylist.iter_origins() { - if document_origins.contains(origin.into()) { - collector - .collect_dependencies_in_invalidation_map(cascade_data.invalidation_map()); - } - } - - for &(ref data, ref host) in &shadow_rule_datas { - collector.matching_context.current_host = Some(host.clone()); - collector.collect_dependencies_in_invalidation_map(data.invalidation_map()); - } - - collector.invalidates_self - }; - - // If we generated a ton of descendant invalidations, it's probably not - // worth to go ahead and try to process them. - // - // Just restyle the descendants directly. - // - // This number is completely made-up, but the page that made us add this - // code generated 1960+ invalidations (bug 1420741). - // - // We don't look at slotted_descendants because those don't propagate - // down more than one level anyway. - if descendant_invalidations.dom_descendants.len() > 150 { - self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS); - } - - if invalidated_self { - self.data.hint.insert(RestyleHint::RESTYLE_SELF); - } - - invalidated_self - } - - fn should_process_descendants(&mut self, element: E) -> bool { - if element == self.element { - return should_process_descendants(&self.data); - } - - match element.borrow_data() { - Some(d) => should_process_descendants(&d), - None => return false, - } - } - - fn recursion_limit_exceeded(&mut self, element: E) { - if element == self.element { - self.data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS); - return; - } - - if let Some(mut data) = element.mutate_data() { - data.hint.insert(RestyleHint::RESTYLE_DESCENDANTS); - } - } - - fn invalidated_descendants(&mut self, element: E, child: E) { - invalidated_descendants(element, child) - } - - fn invalidated_self(&mut self, element: E) { - debug_assert_ne!(element, self.element); - invalidated_self(element); - } - - fn invalidated_sibling(&mut self, element: E, of: E) { - debug_assert_ne!(element, self.element); - invalidated_sibling(element, of); - } -} - -impl<'a, 'b, 'selectors, E> Collector<'a, 'b, 'selectors, E> -where - E: TElement, - 'selectors: 'a, -{ - fn collect_dependencies_in_invalidation_map(&mut self, map: &'selectors InvalidationMap) { - let quirks_mode = self.matching_context.quirks_mode(); - let removed_id = self.removed_id; - if let Some(ref id) = removed_id { - if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { - for dep in deps { - self.scan_dependency(dep); - } - } - } - - let added_id = self.added_id; - if let Some(ref id) = added_id { - if let Some(deps) = map.id_to_selector.get(id, quirks_mode) { - for dep in deps { - self.scan_dependency(dep); - } - } - } - - for class in self.classes_added.iter().chain(self.classes_removed.iter()) { - if let Some(deps) = map.class_to_selector.get(class, quirks_mode) { - for dep in deps { - self.scan_dependency(dep); - } - } - } - - self.snapshot.each_attr_changed(|attribute| { - if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) { - for dep in deps { - self.scan_dependency(dep); - } - } - }); - - self.collect_state_dependencies(&map.state_affecting_selectors) - } - - fn collect_state_dependencies(&mut self, map: &'selectors SelectorMap<StateDependency>) { - if self.state_changes.is_empty() { - return; - } - map.lookup_with_additional( - self.lookup_element, - self.matching_context.quirks_mode(), - self.removed_id, - self.classes_removed, - self.state_changes, - |dependency| { - if !dependency.state.intersects(self.state_changes) { - return true; - } - self.scan_dependency(&dependency.dep); - true - }, - ); - } - - /// Check whether a dependency should be taken into account. - #[inline] - fn check_dependency(&mut self, dependency: &Dependency) -> bool { - check_dependency( - dependency, - &self.element, - &self.wrapper, - &mut self.matching_context, - ) - } - - fn scan_dependency(&mut self, dependency: &'selectors Dependency) { - debug!( - "TreeStyleInvalidator::scan_dependency({:?}, {:?})", - self.element, dependency - ); - - if !self.dependency_may_be_relevant(dependency) { - return; - } - - if self.check_dependency(dependency) { - return self.note_dependency(dependency); - } - } - - fn note_dependency(&mut self, dependency: &'selectors Dependency) { - debug_assert!(self.dependency_may_be_relevant(dependency)); - - let invalidation_kind = dependency.invalidation_kind(); - if matches!(invalidation_kind, DependencyInvalidationKind::Element) { - if let Some(ref parent) = dependency.parent { - // We know something changed in the inner selector, go outwards - // now. - self.scan_dependency(parent); - } else { - self.invalidates_self = true; - } - return; - } - - debug_assert_ne!(dependency.selector_offset, 0); - debug_assert_ne!(dependency.selector_offset, dependency.selector.len()); - - let invalidation = - Invalidation::new(&dependency, self.matching_context.current_host.clone()); - - match invalidation_kind { - DependencyInvalidationKind::Element => unreachable!(), - DependencyInvalidationKind::ElementAndDescendants => { - self.invalidates_self = true; - self.descendant_invalidations - .dom_descendants - .push(invalidation); - }, - DependencyInvalidationKind::Descendants => { - self.descendant_invalidations - .dom_descendants - .push(invalidation); - }, - DependencyInvalidationKind::Siblings => { - self.sibling_invalidations.push(invalidation); - }, - DependencyInvalidationKind::Parts => { - self.descendant_invalidations.parts.push(invalidation); - }, - DependencyInvalidationKind::SlottedElements => { - self.descendant_invalidations - .slotted_descendants - .push(invalidation); - }, - } - } - - /// Returns whether `dependency` may cause us to invalidate the style of - /// more elements than what we've already invalidated. - fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool { - match dependency.invalidation_kind() { - DependencyInvalidationKind::Element => !self.invalidates_self, - DependencyInvalidationKind::SlottedElements => self.element.is_html_slot_element(), - DependencyInvalidationKind::Parts => self.element.shadow_root().is_some(), - DependencyInvalidationKind::ElementAndDescendants | - DependencyInvalidationKind::Siblings | - DependencyInvalidationKind::Descendants => true, - } - } -} diff --git a/components/style/invalidation/media_queries.rs b/components/style/invalidation/media_queries.rs deleted file mode 100644 index 6928b29d3d9..00000000000 --- a/components/style/invalidation/media_queries.rs +++ /dev/null @@ -1,130 +0,0 @@ -/* 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/. */ - -//! Code related to the invalidation of media-query-affected rules. - -use crate::context::QuirksMode; -use crate::media_queries::Device; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{DocumentRule, ImportRule, MediaRule}; -use crate::stylesheets::{NestedRuleIterationCondition, StylesheetContents, SupportsRule}; -use fxhash::FxHashSet; - -/// A key for a given media query result. -/// -/// NOTE: It happens to be the case that all the media lists we care about -/// happen to have a stable address, so we can just use an opaque pointer to -/// represent them. -/// -/// Also, note that right now when a rule or stylesheet is removed, we do a full -/// style flush, so there's no need to worry about other item created with the -/// same pointer address. -/// -/// If this changes, though, we may need to remove the item from the cache if -/// present before it goes away. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub struct MediaListKey(usize); - -impl MediaListKey { - /// Create a MediaListKey from a raw usize. - pub fn from_raw(k: usize) -> Self { - MediaListKey(k) - } -} - -/// A trait to get a given `MediaListKey` for a given item that can hold a -/// `MediaList`. -pub trait ToMediaListKey: Sized { - /// Get a `MediaListKey` for this item. This key needs to uniquely identify - /// the item. - fn to_media_list_key(&self) -> MediaListKey { - MediaListKey(self as *const Self as usize) - } -} - -impl ToMediaListKey for StylesheetContents {} -impl ToMediaListKey for ImportRule {} -impl ToMediaListKey for MediaRule {} - -/// A struct that holds the result of a media query evaluation pass for the -/// media queries that evaluated successfully. -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub struct EffectiveMediaQueryResults { - /// The set of media lists that matched last time. - set: FxHashSet<MediaListKey>, -} - -impl EffectiveMediaQueryResults { - /// Trivially constructs an empty `EffectiveMediaQueryResults`. - pub fn new() -> Self { - Self { - set: FxHashSet::default(), - } - } - - /// Resets the results, using an empty key. - pub fn clear(&mut self) { - self.set.clear() - } - - /// Returns whether a given item was known to be effective when the results - /// were cached. - pub fn was_effective<T>(&self, item: &T) -> bool - where - T: ToMediaListKey, - { - self.set.contains(&item.to_media_list_key()) - } - - /// Notices that an effective item has been seen, and caches it as matching. - pub fn saw_effective<T>(&mut self, item: &T) - where - T: ToMediaListKey, - { - // NOTE(emilio): We can't assert that we don't cache the same item twice - // because of stylesheet reusing... shrug. - self.set.insert(item.to_media_list_key()); - } -} - -/// A filter that filters over effective rules, but allowing all potentially -/// effective `@media` rules. -pub struct PotentiallyEffectiveMediaRules; - -impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules { - fn process_import( - _: &SharedRwLockReadGuard, - _: &Device, - _: QuirksMode, - _: &ImportRule, - ) -> bool { - true - } - - fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool { - true - } - - /// Whether we should process the nested rules in a given `@-moz-document` rule. - fn process_document( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &DocumentRule, - ) -> bool { - use crate::stylesheets::EffectiveRules; - EffectiveRules::process_document(guard, device, quirks_mode, rule) - } - - /// Whether we should process the nested rules in a given `@supports` rule. - fn process_supports( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &SupportsRule, - ) -> bool { - use crate::stylesheets::EffectiveRules; - EffectiveRules::process_supports(guard, device, quirks_mode, rule) - } -} diff --git a/components/style/invalidation/mod.rs b/components/style/invalidation/mod.rs deleted file mode 100644 index 12b0d06853b..00000000000 --- a/components/style/invalidation/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -/* 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/. */ - -//! Different bits of code related to invalidating style. - -pub mod element; -pub mod media_queries; -pub mod stylesheets; -pub mod viewport_units; diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs deleted file mode 100644 index 6ae67452dbd..00000000000 --- a/components/style/invalidation/stylesheets.rs +++ /dev/null @@ -1,655 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A collection of invalidations due to changes in which stylesheets affect a -//! document. - -#![deny(unsafe_code)] - -use crate::context::QuirksMode; -use crate::dom::{TDocument, TElement, TNode}; -use crate::invalidation::element::element_wrapper::{ElementSnapshot, ElementWrapper}; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::media_queries::Device; -use crate::selector_map::{MaybeCaseInsensitiveHashMap, PrecomputedHashMap}; -use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap}; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{CssRule, StylesheetInDocument}; -use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator}; -use crate::values::AtomIdent; -use crate::LocalName as SelectorLocalName; -use crate::{Atom, ShrinkIfNeeded}; -use selectors::parser::{Component, LocalName, Selector}; - -/// The kind of change that happened for a given rule. -#[repr(u32)] -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub enum RuleChangeKind { - /// The rule was inserted. - Insertion, - /// The rule was removed. - Removal, - /// Some change in the rule which we don't know about, and could have made - /// the rule change in any way. - Generic, - /// A change in the declarations of a style rule. - StyleRuleDeclarations, -} - -/// A style sheet invalidation represents a kind of element or subtree that may -/// need to be restyled. Whether it represents a whole subtree or just a single -/// element is determined by the given InvalidationKind in -/// StylesheetInvalidationSet's maps. -#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)] -enum Invalidation { - /// An element with a given id. - ID(AtomIdent), - /// An element with a given class name. - Class(AtomIdent), - /// An element with a given local name. - LocalName { - name: SelectorLocalName, - lower_name: SelectorLocalName, - }, -} - -impl Invalidation { - fn is_id(&self) -> bool { - matches!(*self, Invalidation::ID(..)) - } - - fn is_id_or_class(&self) -> bool { - matches!(*self, Invalidation::ID(..) | Invalidation::Class(..)) - } -} - -/// Whether we should invalidate just the element, or the whole subtree within -/// it. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)] -enum InvalidationKind { - None = 0, - Element, - Scope, -} - -impl std::ops::BitOrAssign for InvalidationKind { - #[inline] - fn bitor_assign(&mut self, other: Self) { - *self = std::cmp::max(*self, other); - } -} - -impl InvalidationKind { - #[inline] - fn is_scope(self) -> bool { - matches!(self, Self::Scope) - } - - #[inline] - fn add(&mut self, other: Option<&InvalidationKind>) { - if let Some(other) = other { - *self |= *other; - } - } -} - -/// A set of invalidations due to stylesheet additions. -/// -/// TODO(emilio): We might be able to do the same analysis for media query -/// changes too (or even selector changes?). -#[derive(Debug, Default, MallocSizeOf)] -pub struct StylesheetInvalidationSet { - classes: MaybeCaseInsensitiveHashMap<Atom, InvalidationKind>, - ids: MaybeCaseInsensitiveHashMap<Atom, InvalidationKind>, - local_names: PrecomputedHashMap<SelectorLocalName, InvalidationKind>, - fully_invalid: bool, -} - -impl StylesheetInvalidationSet { - /// Create an empty `StylesheetInvalidationSet`. - pub fn new() -> Self { - Default::default() - } - - /// Mark the DOM tree styles' as fully invalid. - pub fn invalidate_fully(&mut self) { - debug!("StylesheetInvalidationSet::invalidate_fully"); - self.clear(); - self.fully_invalid = true; - } - - fn shrink_if_needed(&mut self) { - if self.fully_invalid { - return; - } - self.classes.shrink_if_needed(); - self.ids.shrink_if_needed(); - self.local_names.shrink_if_needed(); - } - - /// Analyze the given stylesheet, and collect invalidations from their - /// rules, in order to avoid doing a full restyle when we style the document - /// next time. - pub fn collect_invalidations_for<S>( - &mut self, - device: &Device, - stylesheet: &S, - guard: &SharedRwLockReadGuard, - ) where - S: StylesheetInDocument, - { - debug!("StylesheetInvalidationSet::collect_invalidations_for"); - if self.fully_invalid { - debug!(" > Fully invalid already"); - return; - } - - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - debug!(" > Stylesheet was not effective"); - return; // Nothing to do here. - } - - let quirks_mode = device.quirks_mode(); - for rule in stylesheet.effective_rules(device, guard) { - self.collect_invalidations_for_rule(rule, guard, device, quirks_mode); - if self.fully_invalid { - break; - } - } - - self.shrink_if_needed(); - - debug!(" > resulting class invalidations: {:?}", self.classes); - debug!(" > resulting id invalidations: {:?}", self.ids); - debug!( - " > resulting local name invalidations: {:?}", - self.local_names - ); - debug!(" > fully_invalid: {}", self.fully_invalid); - } - - /// Clears the invalidation set, invalidating elements as needed if - /// `document_element` is provided. - /// - /// Returns true if any invalidations ocurred. - pub fn flush<E>(&mut self, document_element: Option<E>, snapshots: Option<&SnapshotMap>) -> bool - where - E: TElement, - { - debug!( - "Stylist::flush({:?}, snapshots: {})", - document_element, - snapshots.is_some() - ); - let have_invalidations = match document_element { - Some(e) => self.process_invalidations(e, snapshots), - None => false, - }; - self.clear(); - have_invalidations - } - - /// Returns whether there's no invalidation to process. - pub fn is_empty(&self) -> bool { - !self.fully_invalid && - self.classes.is_empty() && - self.ids.is_empty() && - self.local_names.is_empty() - } - - fn invalidation_kind_for<E>( - &self, - element: E, - snapshot: Option<&Snapshot>, - quirks_mode: QuirksMode, - ) -> InvalidationKind - where - E: TElement, - { - debug_assert!(!self.fully_invalid); - - let mut kind = InvalidationKind::None; - - if !self.classes.is_empty() { - element.each_class(|c| { - kind.add(self.classes.get(c, quirks_mode)); - }); - - if kind.is_scope() { - return kind; - } - - if let Some(snapshot) = snapshot { - snapshot.each_class(|c| { - kind.add(self.classes.get(c, quirks_mode)); - }); - - if kind.is_scope() { - return kind; - } - } - } - - if !self.ids.is_empty() { - if let Some(ref id) = element.id() { - kind.add(self.ids.get(id, quirks_mode)); - if kind.is_scope() { - return kind; - } - } - - if let Some(ref old_id) = snapshot.and_then(|s| s.id_attr()) { - kind.add(self.ids.get(old_id, quirks_mode)); - if kind.is_scope() { - return kind; - } - } - } - - if !self.local_names.is_empty() { - kind.add(self.local_names.get(element.local_name())); - } - - kind - } - - /// Clears the invalidation set without processing. - pub fn clear(&mut self) { - self.classes.clear(); - self.ids.clear(); - self.local_names.clear(); - self.fully_invalid = false; - debug_assert!(self.is_empty()); - } - - fn process_invalidations<E>(&self, element: E, snapshots: Option<&SnapshotMap>) -> bool - where - E: TElement, - { - debug!("Stylist::process_invalidations({:?}, {:?})", element, self); - - { - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if self.fully_invalid { - debug!("process_invalidations: fully_invalid({:?})", element); - data.hint.insert(RestyleHint::restyle_subtree()); - return true; - } - } - - if self.is_empty() { - debug!("process_invalidations: empty invalidation set"); - return false; - } - - let quirks_mode = element.as_node().owner_doc().quirks_mode(); - self.process_invalidations_in_subtree(element, snapshots, quirks_mode) - } - - /// Process style invalidations in a given subtree. This traverses the - /// subtree looking for elements that match the invalidations in our hash - /// map members. - /// - /// Returns whether it invalidated at least one element's style. - #[allow(unsafe_code)] - fn process_invalidations_in_subtree<E>( - &self, - element: E, - snapshots: Option<&SnapshotMap>, - quirks_mode: QuirksMode, - ) -> bool - where - E: TElement, - { - debug!("process_invalidations_in_subtree({:?})", element); - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if !data.has_styles() { - return false; - } - - if data.hint.contains_subtree() { - debug!( - "process_invalidations_in_subtree: {:?} was already invalid", - element - ); - return false; - } - - let element_wrapper = snapshots.map(|s| ElementWrapper::new(element, s)); - let snapshot = element_wrapper.as_ref().and_then(|e| e.snapshot()); - - match self.invalidation_kind_for(element, snapshot, quirks_mode) { - InvalidationKind::None => {}, - InvalidationKind::Element => { - debug!( - "process_invalidations_in_subtree: {:?} matched self", - element - ); - data.hint.insert(RestyleHint::RESTYLE_SELF); - }, - InvalidationKind::Scope => { - debug!( - "process_invalidations_in_subtree: {:?} matched subtree", - element - ); - data.hint.insert(RestyleHint::restyle_subtree()); - return true; - }, - } - - let mut any_children_invalid = false; - - for child in element.traversal_children() { - let child = match child.as_element() { - Some(e) => e, - None => continue, - }; - - any_children_invalid |= - self.process_invalidations_in_subtree(child, snapshots, quirks_mode); - } - - if any_children_invalid { - debug!( - "Children of {:?} changed, setting dirty descendants", - element - ); - unsafe { element.set_dirty_descendants() } - } - - data.hint.contains(RestyleHint::RESTYLE_SELF) || any_children_invalid - } - - /// TODO(emilio): Reuse the bucket stuff from selectormap? That handles - /// :is() / :where() etc. - fn scan_component( - component: &Component<SelectorImpl>, - invalidation: &mut Option<Invalidation>, - ) { - match *component { - Component::LocalName(LocalName { - ref name, - ref lower_name, - }) => { - if invalidation.is_none() { - *invalidation = Some(Invalidation::LocalName { - name: name.clone(), - lower_name: lower_name.clone(), - }); - } - }, - Component::Class(ref class) => { - if invalidation.as_ref().map_or(true, |s| !s.is_id_or_class()) { - *invalidation = Some(Invalidation::Class(class.clone())); - } - }, - Component::ID(ref id) => { - if invalidation.as_ref().map_or(true, |s| !s.is_id()) { - *invalidation = Some(Invalidation::ID(id.clone())); - } - }, - _ => { - // Ignore everything else, at least for now. - }, - } - } - - /// Collect invalidations for a given selector. - /// - /// We look at the outermost local name, class, or ID selector to the left - /// of an ancestor combinator, in order to restyle only a given subtree. - /// - /// If the selector has no ancestor combinator, then we do the same for - /// the only sequence it has, but record it as an element invalidation - /// instead of a subtree invalidation. - /// - /// We prefer IDs to classs, and classes to local names, on the basis - /// that the former should be more specific than the latter. We also - /// prefer to generate subtree invalidations for the outermost part - /// of the selector, to reduce the amount of traversal we need to do - /// when flushing invalidations. - fn collect_invalidations( - &mut self, - selector: &Selector<SelectorImpl>, - quirks_mode: QuirksMode, - ) { - debug!( - "StylesheetInvalidationSet::collect_invalidations({:?})", - selector - ); - - let mut element_invalidation: Option<Invalidation> = None; - let mut subtree_invalidation: Option<Invalidation> = None; - - let mut scan_for_element_invalidation = true; - let mut scan_for_subtree_invalidation = false; - - let mut iter = selector.iter(); - - loop { - for component in &mut iter { - if scan_for_element_invalidation { - Self::scan_component(component, &mut element_invalidation); - } else if scan_for_subtree_invalidation { - Self::scan_component(component, &mut subtree_invalidation); - } - } - match iter.next_sequence() { - None => break, - Some(combinator) => { - scan_for_subtree_invalidation = combinator.is_ancestor(); - }, - } - scan_for_element_invalidation = false; - } - - if let Some(s) = subtree_invalidation { - debug!(" > Found subtree invalidation: {:?}", s); - if self.insert_invalidation(s, InvalidationKind::Scope, quirks_mode) { - return; - } - } - - if let Some(s) = element_invalidation { - debug!(" > Found element invalidation: {:?}", s); - if self.insert_invalidation(s, InvalidationKind::Element, quirks_mode) { - return; - } - } - - // The selector was of a form that we can't handle. Any element could - // match it, so let's just bail out. - debug!(" > Can't handle selector or OOMd, marking fully invalid"); - self.invalidate_fully() - } - - fn insert_invalidation( - &mut self, - invalidation: Invalidation, - kind: InvalidationKind, - quirks_mode: QuirksMode, - ) -> bool { - match invalidation { - Invalidation::Class(c) => { - let entry = match self.classes.try_entry(c.0, quirks_mode) { - Ok(e) => e, - Err(..) => return false, - }; - *entry.or_insert(InvalidationKind::None) |= kind; - }, - Invalidation::ID(i) => { - let entry = match self.ids.try_entry(i.0, quirks_mode) { - Ok(e) => e, - Err(..) => return false, - }; - *entry.or_insert(InvalidationKind::None) |= kind; - }, - Invalidation::LocalName { name, lower_name } => { - let insert_lower = name != lower_name; - if self.local_names.try_reserve(1).is_err() { - return false; - } - let entry = self.local_names.entry(name); - *entry.or_insert(InvalidationKind::None) |= kind; - if insert_lower { - if self.local_names.try_reserve(1).is_err() { - return false; - } - let entry = self.local_names.entry(lower_name); - *entry.or_insert(InvalidationKind::None) |= kind; - } - }, - } - - true - } - - /// Collects invalidations for a given CSS rule, if not fully invalid - /// already. - /// - /// TODO(emilio): we can't check whether the rule is inside a non-effective - /// subtree, we potentially could do that. - pub fn rule_changed<S>( - &mut self, - stylesheet: &S, - rule: &CssRule, - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - change_kind: RuleChangeKind, - ) where - S: StylesheetInDocument, - { - use crate::stylesheets::CssRule::*; - - debug!("StylesheetInvalidationSet::rule_changed"); - if self.fully_invalid { - return; - } - - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - debug!(" > Stylesheet was not effective"); - return; // Nothing to do here. - } - - let is_generic_change = change_kind == RuleChangeKind::Generic; - - match *rule { - Namespace(..) => { - // It's not clear what handling changes for this correctly would - // look like. - }, - LayerStatement(..) => { - // Layer statement insertions might alter styling order, so we need to always - // invalidate fully. - return self.invalidate_fully(); - }, - CounterStyle(..) | - Page(..) | - Property(..) | - FontFeatureValues(..) | - FontPaletteValues(..) | - FontFace(..) | - Keyframes(..) | - Container(..) | - Style(..) => { - if is_generic_change { - // TODO(emilio): We need to do this for selector / keyframe - // name / font-face changes, because we don't have the old - // selector / name. If we distinguish those changes - // specially, then we can at least use this invalidation for - // style declaration changes. - return self.invalidate_fully(); - } - - self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) - }, - Document(..) | Import(..) | Media(..) | Supports(..) | LayerBlock(..) => { - if !is_generic_change && - !EffectiveRules::is_effective(guard, device, quirks_mode, rule) - { - return; - } - - let rules = - EffectiveRulesIterator::effective_children(device, quirks_mode, guard, rule); - for rule in rules { - self.collect_invalidations_for_rule(rule, guard, device, quirks_mode); - if self.fully_invalid { - break; - } - } - }, - } - } - - /// Collects invalidations for a given CSS rule. - fn collect_invalidations_for_rule( - &mut self, - rule: &CssRule, - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - ) { - use crate::stylesheets::CssRule::*; - debug!("StylesheetInvalidationSet::collect_invalidations_for_rule"); - debug_assert!(!self.fully_invalid, "Not worth to be here!"); - - match *rule { - Style(ref lock) => { - let style_rule = lock.read_with(guard); - for selector in &style_rule.selectors.0 { - self.collect_invalidations(selector, quirks_mode); - if self.fully_invalid { - return; - } - } - }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | - Container(..) | LayerStatement(..) | LayerBlock(..) => { - // Do nothing, relevant nested rules are visited as part of the - // iteration. - }, - FontFace(..) => { - // Do nothing, @font-face doesn't affect computed style - // information. We'll restyle when the font face loads, if - // needed. - }, - Keyframes(ref lock) => { - let keyframes_rule = lock.read_with(guard); - if device.animation_name_may_be_referenced(&keyframes_rule.name) { - debug!( - " > Found @keyframes rule potentially referenced \ - from the page, marking the whole tree invalid." - ); - self.fully_invalid = true; - } else { - // Do nothing, this animation can't affect the style of - // existing elements. - } - }, - CounterStyle(..) | - Page(..) | - Property(..) | - FontFeatureValues(..) | - FontPaletteValues(..) => { - debug!(" > Found unsupported rule, marking the whole subtree invalid."); - - // TODO(emilio): Can we do better here? - // - // At least in `@page`, we could check the relevant media, I - // guess. - self.fully_invalid = true; - }, - } - } -} diff --git a/components/style/invalidation/viewport_units.rs b/components/style/invalidation/viewport_units.rs deleted file mode 100644 index 06faeb14c46..00000000000 --- a/components/style/invalidation/viewport_units.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* 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/. */ - -//! Invalidates style of all elements that depend on viewport units. - -use crate::data::ViewportUnitUsage; -use crate::dom::{TElement, TNode}; -use crate::invalidation::element::restyle_hints::RestyleHint; - -/// Invalidates style of all elements that depend on viewport units. -/// -/// Returns whether any element was invalidated. -pub fn invalidate<E>(root: E) -> bool -where - E: TElement, -{ - debug!("invalidation::viewport_units::invalidate({:?})", root); - invalidate_recursively(root) -} - -fn invalidate_recursively<E>(element: E) -> bool -where - E: TElement, -{ - let mut data = match element.mutate_data() { - Some(data) => data, - None => return false, - }; - - if data.hint.will_recascade_subtree() { - debug!("invalidate_recursively: {:?} was already invalid", element); - return false; - } - - let usage = data.styles.viewport_unit_usage(); - let uses_viewport_units = usage != ViewportUnitUsage::None; - if uses_viewport_units { - debug!( - "invalidate_recursively: {:?} uses viewport units {:?}", - element, usage - ); - } - - match usage { - ViewportUnitUsage::None => {}, - ViewportUnitUsage::FromQuery => { - data.hint.insert(RestyleHint::RESTYLE_SELF); - }, - ViewportUnitUsage::FromDeclaration => { - data.hint.insert(RestyleHint::RECASCADE_SELF); - }, - } - - let mut any_children_invalid = false; - for child in element.traversal_children() { - if let Some(child) = child.as_element() { - any_children_invalid |= invalidate_recursively(child); - } - } - - if any_children_invalid { - debug!( - "invalidate_recursively: Children of {:?} changed, setting dirty descendants", - element - ); - unsafe { element.set_dirty_descendants() } - } - - uses_viewport_units || any_children_invalid -} diff --git a/components/style/lib.rs b/components/style/lib.rs deleted file mode 100644 index 90a52e567b1..00000000000 --- a/components/style/lib.rs +++ /dev/null @@ -1,329 +0,0 @@ -/* 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/. */ - -//! Calculate [specified][specified] and [computed values][computed] from a -//! tree of DOM nodes and a set of stylesheets. -//! -//! [computed]: https://drafts.csswg.org/css-cascade/#computed -//! [specified]: https://drafts.csswg.org/css-cascade/#specified -//! -//! In particular, this crate contains the definitions of supported properties, -//! the code to parse them into specified values and calculate the computed -//! values based on the specified values, as well as the code to serialize both -//! specified and computed values. -//! -//! The main entry point is [`recalc_style_at`][recalc_style_at]. -//! -//! [recalc_style_at]: traversal/fn.recalc_style_at.html -//! -//! Major dependencies are the [cssparser][cssparser] and [selectors][selectors] -//! crates. -//! -//! [cssparser]: ../cssparser/index.html -//! [selectors]: ../selectors/index.html - -#![deny(missing_docs)] - -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate cssparser; -#[macro_use] -extern crate debug_unreachable; -#[macro_use] -extern crate derive_more; -#[macro_use] -#[cfg(feature = "gecko")] -extern crate gecko_profiler; -#[cfg(feature = "gecko")] -#[macro_use] -pub mod gecko_string_cache; -#[cfg(feature = "servo")] -#[macro_use] -extern crate html5ever; -#[macro_use] -extern crate lazy_static; -#[macro_use] -extern crate log; -#[macro_use] -extern crate malloc_size_of; -#[macro_use] -extern crate malloc_size_of_derive; -#[cfg(feature = "gecko")] -pub use nsstring; -#[cfg(feature = "gecko")] -extern crate num_cpus; -#[macro_use] -extern crate num_derive; -#[macro_use] -extern crate serde; -pub use servo_arc; -#[cfg(feature = "servo")] -#[macro_use] -extern crate servo_atoms; -#[macro_use] -extern crate static_assertions; -#[macro_use] -extern crate style_derive; -#[macro_use] -extern crate to_shmem_derive; - -#[macro_use] -mod macros; - -pub mod animation; -pub mod applicable_declarations; -#[allow(missing_docs)] // TODO. -#[cfg(feature = "servo")] -pub mod attr; -pub mod author_styles; -pub mod bezier; -pub mod bloom; -pub mod color; -#[path = "properties/computed_value_flags.rs"] -pub mod computed_value_flags; -pub mod context; -pub mod counter_style; -pub mod custom_properties; -pub mod data; -pub mod dom; -pub mod dom_apis; -pub mod driver; -#[cfg(feature = "servo")] -mod encoding_support; -pub mod error_reporting; -pub mod font_face; -pub mod font_metrics; -#[cfg(feature = "gecko")] -#[allow(unsafe_code)] -pub mod gecko_bindings; -pub mod global_style_data; -pub mod invalidation; -#[allow(missing_docs)] // TODO. -pub mod logical_geometry; -pub mod matching; -pub mod media_queries; -pub mod parallel; -pub mod parser; -pub mod piecewise_linear; -pub mod properties_and_values; -#[macro_use] -pub mod queries; -pub mod rule_cache; -pub mod rule_collector; -pub mod rule_tree; -pub mod scoped_tls; -pub mod selector_map; -pub mod selector_parser; -pub mod shared_lock; -pub mod sharing; -pub mod str; -pub mod style_adjuster; -pub mod style_resolver; -pub mod stylesheet_set; -pub mod stylesheets; -pub mod stylist; -pub mod thread_state; -pub mod traversal; -pub mod traversal_flags; -pub mod use_counters; -#[macro_use] -#[allow(non_camel_case_types)] -pub mod values; - -#[cfg(feature = "gecko")] -pub use crate::gecko_string_cache as string_cache; -#[cfg(feature = "gecko")] -pub use crate::gecko_string_cache::Atom; -/// The namespace prefix type for Gecko, which is just an atom. -#[cfg(feature = "gecko")] -pub type Prefix = crate::values::AtomIdent; -/// The local name of an element for Gecko, which is just an atom. -#[cfg(feature = "gecko")] -pub type LocalName = crate::values::AtomIdent; -#[cfg(feature = "gecko")] -pub use crate::gecko_string_cache::Namespace; - -#[cfg(feature = "servo")] -pub use servo_atoms::Atom; - -#[cfg(feature = "servo")] -#[allow(missing_docs)] -pub type LocalName = crate::values::GenericAtomIdent<html5ever::LocalNameStaticSet>; -#[cfg(feature = "servo")] -#[allow(missing_docs)] -pub type Namespace = crate::values::GenericAtomIdent<html5ever::NamespaceStaticSet>; -#[cfg(feature = "servo")] -#[allow(missing_docs)] -pub type Prefix = crate::values::GenericAtomIdent<html5ever::PrefixStaticSet>; - -pub use style_traits::arc_slice::ArcSlice; -pub use style_traits::owned_slice::OwnedSlice; -pub use style_traits::owned_str::OwnedStr; - -use std::hash::{BuildHasher, Hash}; - -#[macro_use] -pub mod properties; - -#[cfg(feature = "gecko")] -#[allow(unsafe_code)] -pub mod gecko; - -// uses a macro from properties -#[cfg(feature = "servo")] -#[allow(unsafe_code)] -pub mod servo; - -macro_rules! reexport_computed_values { - ( $( { $name: ident } )+ ) => { - /// Types for [computed values][computed]. - /// - /// [computed]: https://drafts.csswg.org/css-cascade/#computed - pub mod computed_values { - $( - pub use crate::properties::longhands::$name::computed_value as $name; - )+ - // Don't use a side-specific name needlessly: - pub use crate::properties::longhands::border_top_style::computed_value as border_style; - } - } -} -longhand_properties_idents!(reexport_computed_values); -#[cfg(feature = "gecko")] -use crate::gecko_string_cache::WeakAtom; -#[cfg(feature = "servo")] -use servo_atoms::Atom as WeakAtom; - -/// Extension methods for selectors::attr::CaseSensitivity -pub trait CaseSensitivityExt { - /// Return whether two atoms compare equal according to this case sensitivity. - fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool; -} - -impl CaseSensitivityExt for selectors::attr::CaseSensitivity { - fn eq_atom(self, a: &WeakAtom, b: &WeakAtom) -> bool { - match self { - selectors::attr::CaseSensitivity::CaseSensitive => a == b, - selectors::attr::CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), - } - } -} - -/// A trait pretty much similar to num_traits::Zero, but without the need of -/// implementing `Add`. -pub trait Zero { - /// Returns the zero value. - fn zero() -> Self; - - /// Returns whether this value is zero. - fn is_zero(&self) -> bool; -} - -impl<T> Zero for T -where - T: num_traits::Zero, -{ - fn zero() -> Self { - <Self as num_traits::Zero>::zero() - } - - fn is_zero(&self) -> bool { - <Self as num_traits::Zero>::is_zero(self) - } -} - -/// A trait implementing a function to tell if the number is zero without a percent -pub trait ZeroNoPercent { - /// So, `0px` should return `true`, but `0%` or `1px` should return `false` - fn is_zero_no_percent(&self) -> bool; -} - -/// A trait pretty much similar to num_traits::One, but without the need of -/// implementing `Mul`. -pub trait One { - /// Reutrns the one value. - fn one() -> Self; - - /// Returns whether this value is one. - fn is_one(&self) -> bool; -} - -impl<T> One for T -where - T: num_traits::One + PartialEq, -{ - fn one() -> Self { - <Self as num_traits::One>::one() - } - - fn is_one(&self) -> bool { - *self == One::one() - } -} - -/// An allocation error. -/// -/// TODO(emilio): Would be nice to have more information here, or for SmallVec -/// to return the standard error type (and then we can just return that). -/// -/// But given we use these mostly to bail out and ignore them, it's not a big -/// deal. -#[derive(Debug)] -pub struct AllocErr; - -impl From<smallvec::CollectionAllocErr> for AllocErr { - #[inline] - fn from(_: smallvec::CollectionAllocErr) -> Self { - Self - } -} - -impl From<std::collections::TryReserveError> for AllocErr { - #[inline] - fn from(_: std::collections::TryReserveError) -> Self { - Self - } -} - -/// Shrink the capacity of the collection if needed. -pub(crate) trait ShrinkIfNeeded { - fn shrink_if_needed(&mut self); -} - -/// We shrink the capacity of a collection if we're wasting more than a 25% of -/// its capacity, and if the collection is arbitrarily big enough -/// (>= CAPACITY_THRESHOLD entries). -#[inline] -fn should_shrink(len: usize, capacity: usize) -> bool { - const CAPACITY_THRESHOLD: usize = 64; - capacity >= CAPACITY_THRESHOLD && len + capacity / 4 < capacity -} - -impl<K, V, H> ShrinkIfNeeded for std::collections::HashMap<K, V, H> -where - K: Eq + Hash, - H: BuildHasher, -{ - fn shrink_if_needed(&mut self) { - if should_shrink(self.len(), self.capacity()) { - self.shrink_to_fit(); - } - } -} - -impl<T, H> ShrinkIfNeeded for std::collections::HashSet<T, H> -where - T: Eq + Hash, - H: BuildHasher, -{ - fn shrink_if_needed(&mut self) { - if should_shrink(self.len(), self.capacity()) { - self.shrink_to_fit(); - } - } -} - -// TODO(emilio): Measure and see if we're wasting a lot of memory on Vec / -// SmallVec, and if so consider shrinking those as well. diff --git a/components/style/logical_geometry.rs b/components/style/logical_geometry.rs deleted file mode 100644 index 9a823a5a24a..00000000000 --- a/components/style/logical_geometry.rs +++ /dev/null @@ -1,1522 +0,0 @@ -/* 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/. */ - -//! Geometry in flow-relative space. - -use crate::properties::style_structs; -use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D}; -use euclid::num::Zero; -use std::cmp::{max, min}; -use std::fmt::{self, Debug, Error, Formatter}; -use std::ops::{Add, Sub}; -use unicode_bidi as bidi; - -pub enum BlockFlowDirection { - TopToBottom, - RightToLeft, - LeftToRight, -} - -pub enum InlineBaseDirection { - LeftToRight, - RightToLeft, -} - -// TODO: improve the readability of the WritingMode serialization, refer to the Debug:fmt() -bitflags!( - #[cfg_attr(feature = "servo", derive(MallocSizeOf, Serialize))] - #[repr(C)] - pub struct WritingMode: u8 { - /// A vertical writing mode; writing-mode is vertical-rl, - /// vertical-lr, sideways-lr, or sideways-rl. - const VERTICAL = 1 << 0; - /// The inline flow direction is reversed against the physical - /// direction (i.e. right-to-left or bottom-to-top); writing-mode is - /// sideways-lr or direction is rtl (but not both). - /// - /// (This bit can be derived from the others, but we store it for - /// convenience.) - const INLINE_REVERSED = 1 << 1; - /// A vertical writing mode whose block progression direction is left- - /// to-right; writing-mode is vertical-lr or sideways-lr. - /// - /// Never set without VERTICAL. - const VERTICAL_LR = 1 << 2; - /// The line-over/line-under sides are inverted with respect to the - /// block-start/block-end edge; writing-mode is vertical-lr. - /// - /// Never set without VERTICAL and VERTICAL_LR. - const LINE_INVERTED = 1 << 3; - /// direction is rtl. - const RTL = 1 << 4; - /// All text within a vertical writing mode is displayed sideways - /// and runs top-to-bottom or bottom-to-top; set in these cases: - /// - /// * writing-mode: sideways-rl; - /// * writing-mode: sideways-lr; - /// - /// Never set without VERTICAL. - const VERTICAL_SIDEWAYS = 1 << 5; - /// Similar to VERTICAL_SIDEWAYS, but is set via text-orientation; - /// set in these cases: - /// - /// * writing-mode: vertical-rl; text-orientation: sideways; - /// * writing-mode: vertical-lr; text-orientation: sideways; - /// - /// Never set without VERTICAL. - const TEXT_SIDEWAYS = 1 << 6; - /// Horizontal text within a vertical writing mode is displayed with each - /// glyph upright; set in these cases: - /// - /// * writing-mode: vertical-rl; text-orientation: upright; - /// * writing-mode: vertical-lr: text-orientation: upright; - /// - /// Never set without VERTICAL. - const UPRIGHT = 1 << 7; - } -); - -impl WritingMode { - /// Return a WritingMode bitflags from the relevant CSS properties. - pub fn new(inheritedbox_style: &style_structs::InheritedBox) -> Self { - use crate::properties::longhands::direction::computed_value::T as Direction; - use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode; - - let mut flags = WritingMode::empty(); - - let direction = inheritedbox_style.clone_direction(); - let writing_mode = inheritedbox_style.clone_writing_mode(); - - match direction { - Direction::Ltr => {}, - Direction::Rtl => { - flags.insert(WritingMode::RTL); - }, - } - - match writing_mode { - SpecifiedWritingMode::HorizontalTb => { - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - SpecifiedWritingMode::VerticalRl => { - flags.insert(WritingMode::VERTICAL); - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - SpecifiedWritingMode::VerticalLr => { - flags.insert(WritingMode::VERTICAL); - flags.insert(WritingMode::VERTICAL_LR); - flags.insert(WritingMode::LINE_INVERTED); - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - #[cfg(feature = "gecko")] - SpecifiedWritingMode::SidewaysRl => { - flags.insert(WritingMode::VERTICAL); - flags.insert(WritingMode::VERTICAL_SIDEWAYS); - if direction == Direction::Rtl { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - #[cfg(feature = "gecko")] - SpecifiedWritingMode::SidewaysLr => { - flags.insert(WritingMode::VERTICAL); - flags.insert(WritingMode::VERTICAL_LR); - flags.insert(WritingMode::VERTICAL_SIDEWAYS); - if direction == Direction::Ltr { - flags.insert(WritingMode::INLINE_REVERSED); - } - }, - } - - #[cfg(feature = "gecko")] - { - use crate::properties::longhands::text_orientation::computed_value::T as TextOrientation; - - // text-orientation only has an effect for vertical-rl and - // vertical-lr values of writing-mode. - match writing_mode { - SpecifiedWritingMode::VerticalRl | SpecifiedWritingMode::VerticalLr => { - match inheritedbox_style.clone_text_orientation() { - TextOrientation::Mixed => {}, - TextOrientation::Upright => { - flags.insert(WritingMode::UPRIGHT); - - // https://drafts.csswg.org/css-writing-modes-3/#valdef-text-orientation-upright: - // - // > This value causes the used value of direction - // > to be ltr, and for the purposes of bidi - // > reordering, causes all characters to be treated - // > as strong LTR. - flags.remove(WritingMode::RTL); - flags.remove(WritingMode::INLINE_REVERSED); - }, - TextOrientation::Sideways => { - flags.insert(WritingMode::TEXT_SIDEWAYS); - }, - } - }, - _ => {}, - } - } - - flags - } - - /// Returns the `horizontal-tb` value. - pub fn horizontal_tb() -> Self { - Self::from_bits_truncate(0) - } - - #[inline] - pub fn is_vertical(&self) -> bool { - self.intersects(WritingMode::VERTICAL) - } - - #[inline] - pub fn is_horizontal(&self) -> bool { - !self.is_vertical() - } - - /// Assuming .is_vertical(), does the block direction go left to right? - #[inline] - pub fn is_vertical_lr(&self) -> bool { - self.intersects(WritingMode::VERTICAL_LR) - } - - /// Assuming .is_vertical(), does the inline direction go top to bottom? - #[inline] - pub fn is_inline_tb(&self) -> bool { - // https://drafts.csswg.org/css-writing-modes-3/#logical-to-physical - !self.intersects(WritingMode::INLINE_REVERSED) - } - - #[inline] - pub fn is_bidi_ltr(&self) -> bool { - !self.intersects(WritingMode::RTL) - } - - #[inline] - pub fn is_sideways(&self) -> bool { - self.intersects(WritingMode::VERTICAL_SIDEWAYS | WritingMode::TEXT_SIDEWAYS) - } - - #[inline] - pub fn is_upright(&self) -> bool { - self.intersects(WritingMode::UPRIGHT) - } - - /// https://drafts.csswg.org/css-writing-modes/#logical-to-physical - /// - /// | Return | line-left is… | line-right is… | - /// |---------|---------------|----------------| - /// | `true` | inline-start | inline-end | - /// | `false` | inline-end | inline-start | - #[inline] - pub fn line_left_is_inline_start(&self) -> bool { - // https://drafts.csswg.org/css-writing-modes/#inline-start - // “For boxes with a used direction value of ltr, this means the line-left side. - // For boxes with a used direction value of rtl, this means the line-right side.” - self.is_bidi_ltr() - } - - #[inline] - pub fn inline_start_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) { - (false, _, true) => PhysicalSide::Left, - (false, _, false) => PhysicalSide::Right, - (true, true, _) => PhysicalSide::Top, - (true, false, _) => PhysicalSide::Bottom, - } - } - - #[inline] - pub fn inline_end_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) { - (false, _, true) => PhysicalSide::Right, - (false, _, false) => PhysicalSide::Left, - (true, true, _) => PhysicalSide::Bottom, - (true, false, _) => PhysicalSide::Top, - } - } - - #[inline] - pub fn block_start_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_vertical_lr()) { - (false, _) => PhysicalSide::Top, - (true, true) => PhysicalSide::Left, - (true, false) => PhysicalSide::Right, - } - } - - #[inline] - pub fn block_end_physical_side(&self) -> PhysicalSide { - match (self.is_vertical(), self.is_vertical_lr()) { - (false, _) => PhysicalSide::Bottom, - (true, true) => PhysicalSide::Right, - (true, false) => PhysicalSide::Left, - } - } - - #[inline] - fn physical_sides_to_corner( - block_side: PhysicalSide, - inline_side: PhysicalSide, - ) -> PhysicalCorner { - match (block_side, inline_side) { - (PhysicalSide::Top, PhysicalSide::Left) | (PhysicalSide::Left, PhysicalSide::Top) => { - PhysicalCorner::TopLeft - }, - (PhysicalSide::Top, PhysicalSide::Right) | (PhysicalSide::Right, PhysicalSide::Top) => { - PhysicalCorner::TopRight - }, - (PhysicalSide::Bottom, PhysicalSide::Right) | - (PhysicalSide::Right, PhysicalSide::Bottom) => PhysicalCorner::BottomRight, - (PhysicalSide::Bottom, PhysicalSide::Left) | - (PhysicalSide::Left, PhysicalSide::Bottom) => PhysicalCorner::BottomLeft, - _ => unreachable!("block and inline sides must be orthogonal"), - } - } - - #[inline] - pub fn start_start_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_start_physical_side(), - self.inline_start_physical_side(), - ) - } - - #[inline] - pub fn start_end_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_start_physical_side(), - self.inline_end_physical_side(), - ) - } - - #[inline] - pub fn end_start_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_end_physical_side(), - self.inline_start_physical_side(), - ) - } - - #[inline] - pub fn end_end_physical_corner(&self) -> PhysicalCorner { - WritingMode::physical_sides_to_corner( - self.block_end_physical_side(), - self.inline_end_physical_side(), - ) - } - - #[inline] - pub fn block_flow_direction(&self) -> BlockFlowDirection { - match (self.is_vertical(), self.is_vertical_lr()) { - (false, _) => BlockFlowDirection::TopToBottom, - (true, true) => BlockFlowDirection::LeftToRight, - (true, false) => BlockFlowDirection::RightToLeft, - } - } - - #[inline] - pub fn inline_base_direction(&self) -> InlineBaseDirection { - if self.intersects(WritingMode::RTL) { - InlineBaseDirection::RightToLeft - } else { - InlineBaseDirection::LeftToRight - } - } - - #[inline] - /// The default bidirectional embedding level for this writing mode. - /// - /// Returns bidi level 0 if the mode is LTR, or 1 otherwise. - pub fn to_bidi_level(&self) -> bidi::Level { - if self.is_bidi_ltr() { - bidi::Level::ltr() - } else { - bidi::Level::rtl() - } - } -} - -impl fmt::Display for WritingMode { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - if self.is_vertical() { - write!(formatter, "V")?; - if self.is_vertical_lr() { - write!(formatter, " LR")?; - } else { - write!(formatter, " RL")?; - } - if self.is_sideways() { - write!(formatter, " Sideways")?; - } - if self.intersects(WritingMode::LINE_INVERTED) { - write!(formatter, " Inverted")?; - } - } else { - write!(formatter, "H")?; - } - if self.is_bidi_ltr() { - write!(formatter, " LTR") - } else { - write!(formatter, " RTL") - } - } -} - -/// Wherever logical geometry is used, the writing mode is known based on context: -/// every method takes a `mode` parameter. -/// However, this context is easy to get wrong. -/// In debug builds only, logical geometry objects store their writing mode -/// (in addition to taking it as a parameter to methods) and check it. -/// In non-debug builds, make this storage zero-size and the checks no-ops. -#[cfg(not(debug_assertions))] -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -struct DebugWritingMode; - -#[cfg(debug_assertions)] -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -struct DebugWritingMode { - mode: WritingMode, -} - -#[cfg(not(debug_assertions))] -impl DebugWritingMode { - #[inline] - fn check(&self, _other: WritingMode) {} - - #[inline] - fn check_debug(&self, _other: DebugWritingMode) {} - - #[inline] - fn new(_mode: WritingMode) -> DebugWritingMode { - DebugWritingMode - } -} - -#[cfg(debug_assertions)] -impl DebugWritingMode { - #[inline] - fn check(&self, other: WritingMode) { - assert_eq!(self.mode, other) - } - - #[inline] - fn check_debug(&self, other: DebugWritingMode) { - assert_eq!(self.mode, other.mode) - } - - #[inline] - fn new(mode: WritingMode) -> DebugWritingMode { - DebugWritingMode { mode: mode } - } -} - -impl Debug for DebugWritingMode { - #[cfg(not(debug_assertions))] - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!(formatter, "?") - } - - #[cfg(debug_assertions)] - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!(formatter, "{}", self.mode) - } -} - -// Used to specify the logical direction. -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub enum Direction { - Inline, - Block, -} - -/// A 2D size in flow-relative dimensions -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalSize<T> { - pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure - pub block: T, // block-size, a.k.a. logical height, a.k.a. extent - debug_writing_mode: DebugWritingMode, -} - -impl<T: Debug> Debug for LogicalSize<T> { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!( - formatter, - "LogicalSize({:?}, i{:?}×b{:?})", - self.debug_writing_mode, self.inline, self.block - ) - } -} - -// Can not implement the Zero trait: its zero() method does not have the `mode` parameter. -impl<T: Zero> LogicalSize<T> { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalSize<T> { - LogicalSize { - inline: Zero::zero(), - block: Zero::zero(), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl<T> LogicalSize<T> { - #[inline] - pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> { - LogicalSize { - inline: inline, - block: block, - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> { - if mode.is_vertical() { - LogicalSize::new(mode, size.height, size.width) - } else { - LogicalSize::new(mode, size.width, size.height) - } - } -} - -impl<T: Copy> LogicalSize<T> { - #[inline] - pub fn width(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.block - } else { - self.inline - } - } - - #[inline] - pub fn set_width(&mut self, mode: WritingMode, width: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.block = width - } else { - self.inline = width - } - } - - #[inline] - pub fn height(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.inline - } else { - self.block - } - } - - #[inline] - pub fn set_height(&mut self, mode: WritingMode, height: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.inline = height - } else { - self.block = height - } - } - - #[inline] - pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - Size2D::new(self.block, self.inline) - } else { - Size2D::new(self.inline, self.block) - } - } - - #[inline] - pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize<T> { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalSize::from_physical(mode_to, self.to_physical(mode_from)) - } - } -} - -impl<T: Add<T, Output = T>> Add for LogicalSize<T> { - type Output = LogicalSize<T>; - - #[inline] - fn add(self, other: LogicalSize<T>) -> LogicalSize<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalSize { - debug_writing_mode: self.debug_writing_mode, - inline: self.inline + other.inline, - block: self.block + other.block, - } - } -} - -// TODO(servo#30577) remove this once underlying bugs are fixed -impl<T: Add<T, Output = T>> LogicalSize<T> { - #[inline] - pub fn add_or_warn(self, other: LogicalSize<T>) -> LogicalSize<T> { - #[cfg(debug_assertions)] - if !(self.debug_writing_mode.mode == other.debug_writing_mode.mode) { - log::warn!("debug assertion failed! self.debug_writing_mode.mode == other.debug_writing_mode.mode"); - } - LogicalSize { - debug_writing_mode: self.debug_writing_mode, - inline: self.inline + other.inline, - block: self.block + other.block, - } - } -} - -impl<T: Sub<T, Output = T>> Sub for LogicalSize<T> { - type Output = LogicalSize<T>; - - #[inline] - fn sub(self, other: LogicalSize<T>) -> LogicalSize<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalSize { - debug_writing_mode: self.debug_writing_mode, - inline: self.inline - other.inline, - block: self.block - other.block, - } - } -} - -/// A 2D point in flow-relative dimensions -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalPoint<T> { - /// inline-axis coordinate - pub i: T, - /// block-axis coordinate - pub b: T, - debug_writing_mode: DebugWritingMode, -} - -impl<T: Debug> Debug for LogicalPoint<T> { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - write!( - formatter, - "LogicalPoint({:?} (i{:?}, b{:?}))", - self.debug_writing_mode, self.i, self.b - ) - } -} - -// Can not implement the Zero trait: its zero() method does not have the `mode` parameter. -impl<T: Zero> LogicalPoint<T> { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalPoint<T> { - LogicalPoint { - i: Zero::zero(), - b: Zero::zero(), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl<T: Copy> LogicalPoint<T> { - #[inline] - pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> { - LogicalPoint { - i: i, - b: b, - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl<T: Copy + Sub<T, Output = T>> LogicalPoint<T> { - #[inline] - pub fn from_physical( - mode: WritingMode, - point: Point2D<T>, - container_size: Size2D<T>, - ) -> LogicalPoint<T> { - if mode.is_vertical() { - LogicalPoint { - i: if mode.is_inline_tb() { - point.y - } else { - container_size.height - point.y - }, - b: if mode.is_vertical_lr() { - point.x - } else { - container_size.width - point.x - }, - debug_writing_mode: DebugWritingMode::new(mode), - } - } else { - LogicalPoint { - i: if mode.is_bidi_ltr() { - point.x - } else { - container_size.width - point.x - }, - b: point.y, - debug_writing_mode: DebugWritingMode::new(mode), - } - } - } - - #[inline] - pub fn x(&self, mode: WritingMode, container_size: Size2D<T>) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.b - } else { - container_size.width - self.b - } - } else { - if mode.is_bidi_ltr() { - self.i - } else { - container_size.width - self.i - } - } - } - - #[inline] - pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.b = if mode.is_vertical_lr() { - x - } else { - container_size.width - x - } - } else { - self.i = if mode.is_bidi_ltr() { - x - } else { - container_size.width - x - } - } - } - - #[inline] - pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.i - } else { - container_size.height - self.i - } - } else { - self.b - } - } - - #[inline] - pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.i = if mode.is_inline_tb() { - y - } else { - container_size.height - y - } - } else { - self.b = y - } - } - - #[inline] - pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - Point2D::new( - if mode.is_vertical_lr() { - self.b - } else { - container_size.width - self.b - }, - if mode.is_inline_tb() { - self.i - } else { - container_size.height - self.i - }, - ) - } else { - Point2D::new( - if mode.is_bidi_ltr() { - self.i - } else { - container_size.width - self.i - }, - self.b, - ) - } - } - - // TODO(servo#30577) remove this once underlying bugs are fixed - #[inline] - pub fn to_physical_or_warn(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> { - #[cfg(debug_assertions)] - if !(self.debug_writing_mode.mode == mode) { - log::warn!("debug assertion failed! self.debug_writing_mode.mode == mode"); - } - if mode.is_vertical() { - Point2D::new( - if mode.is_vertical_lr() { - self.b - } else { - container_size.width - self.b - }, - if mode.is_inline_tb() { - self.i - } else { - container_size.height - self.i - }, - ) - } else { - Point2D::new( - if mode.is_bidi_ltr() { - self.i - } else { - container_size.width - self.i - }, - self.b, - ) - } - } - - #[inline] - pub fn convert( - &self, - mode_from: WritingMode, - mode_to: WritingMode, - container_size: Size2D<T>, - ) -> LogicalPoint<T> { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalPoint::from_physical( - mode_to, - self.to_physical(mode_from, container_size), - container_size, - ) - } - } -} - -impl<T: Copy + Add<T, Output = T>> LogicalPoint<T> { - /// This doesn’t really makes sense, - /// but happens when dealing with multiple origins. - #[inline] - pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalPoint { - debug_writing_mode: self.debug_writing_mode, - i: self.i + other.i, - b: self.b + other.b, - } - } -} - -impl<T: Copy + Add<T, Output = T>> Add<LogicalSize<T>> for LogicalPoint<T> { - type Output = LogicalPoint<T>; - - #[inline] - fn add(self, other: LogicalSize<T>) -> LogicalPoint<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalPoint { - debug_writing_mode: self.debug_writing_mode, - i: self.i + other.inline, - b: self.b + other.block, - } - } -} - -impl<T: Copy + Sub<T, Output = T>> Sub<LogicalSize<T>> for LogicalPoint<T> { - type Output = LogicalPoint<T>; - - #[inline] - fn sub(self, other: LogicalSize<T>) -> LogicalPoint<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalPoint { - debug_writing_mode: self.debug_writing_mode, - i: self.i - other.inline, - b: self.b - other.block, - } - } -} - -/// A "margin" in flow-relative dimensions -/// Represents the four sides of the margins, borders, or padding of a CSS box, -/// or a combination of those. -/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle. -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalMargin<T> { - pub block_start: T, - pub inline_end: T, - pub block_end: T, - pub inline_start: T, - debug_writing_mode: DebugWritingMode, -} - -impl<T: Debug> Debug for LogicalMargin<T> { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - let writing_mode_string = if cfg!(debug_assertions) { - format!("{:?}, ", self.debug_writing_mode) - } else { - "".to_owned() - }; - - write!( - formatter, - "LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})", - writing_mode_string, - self.inline_start, - self.inline_end, - self.block_start, - self.block_end - ) - } -} - -impl<T: Zero> LogicalMargin<T> { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalMargin<T> { - LogicalMargin { - block_start: Zero::zero(), - inline_end: Zero::zero(), - block_end: Zero::zero(), - inline_start: Zero::zero(), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl<T> LogicalMargin<T> { - #[inline] - pub fn new( - mode: WritingMode, - block_start: T, - inline_end: T, - block_end: T, - inline_start: T, - ) -> LogicalMargin<T> { - LogicalMargin { - block_start, - inline_end, - block_end, - inline_start, - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> { - let block_start; - let inline_end; - let block_end; - let inline_start; - if mode.is_vertical() { - if mode.is_vertical_lr() { - block_start = offsets.left; - block_end = offsets.right; - } else { - block_start = offsets.right; - block_end = offsets.left; - } - if mode.is_inline_tb() { - inline_start = offsets.top; - inline_end = offsets.bottom; - } else { - inline_start = offsets.bottom; - inline_end = offsets.top; - } - } else { - block_start = offsets.top; - block_end = offsets.bottom; - if mode.is_bidi_ltr() { - inline_start = offsets.left; - inline_end = offsets.right; - } else { - inline_start = offsets.right; - inline_end = offsets.left; - } - } - LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start) - } -} - -impl<T: Copy> LogicalMargin<T> { - #[inline] - pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> { - LogicalMargin::new(mode, value, value, value, value) - } - - #[inline] - pub fn top(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_start - } else { - self.inline_end - } - } else { - self.block_start - } - } - - #[inline] - pub fn set_top(&mut self, mode: WritingMode, top: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_start = top - } else { - self.inline_end = top - } - } else { - self.block_start = top - } - } - - #[inline] - pub fn right(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_end - } else { - self.block_start - } - } else { - if mode.is_bidi_ltr() { - self.inline_end - } else { - self.inline_start - } - } - } - - #[inline] - pub fn set_right(&mut self, mode: WritingMode, right: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_end = right - } else { - self.block_start = right - } - } else { - if mode.is_bidi_ltr() { - self.inline_end = right - } else { - self.inline_start = right - } - } - } - - #[inline] - pub fn bottom(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_end - } else { - self.inline_start - } - } else { - self.block_end - } - } - - #[inline] - pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_inline_tb() { - self.inline_end = bottom - } else { - self.inline_start = bottom - } - } else { - self.block_end = bottom - } - } - - #[inline] - pub fn left(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_start - } else { - self.block_end - } - } else { - if mode.is_bidi_ltr() { - self.inline_start - } else { - self.inline_end - } - } - } - - #[inline] - pub fn set_left(&mut self, mode: WritingMode, left: T) { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - if mode.is_vertical_lr() { - self.block_start = left - } else { - self.block_end = left - } - } else { - if mode.is_bidi_ltr() { - self.inline_start = left - } else { - self.inline_end = left - } - } - } - - #[inline] - pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalMargin::from_physical(mode_to, self.to_physical(mode_from)) - } - } -} - -impl<T: Clone> LogicalMargin<T> { - #[inline] - pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> { - self.debug_writing_mode.check(mode); - let top; - let right; - let bottom; - let left; - if mode.is_vertical() { - if mode.is_vertical_lr() { - left = self.block_start.clone(); - right = self.block_end.clone(); - } else { - right = self.block_start.clone(); - left = self.block_end.clone(); - } - if mode.is_inline_tb() { - top = self.inline_start.clone(); - bottom = self.inline_end.clone(); - } else { - bottom = self.inline_start.clone(); - top = self.inline_end.clone(); - } - } else { - top = self.block_start.clone(); - bottom = self.block_end.clone(); - if mode.is_bidi_ltr() { - left = self.inline_start.clone(); - right = self.inline_end.clone(); - } else { - right = self.inline_start.clone(); - left = self.inline_end.clone(); - } - } - SideOffsets2D::new(top, right, bottom, left) - } -} - -impl<T: PartialEq + Zero> LogicalMargin<T> { - #[inline] - pub fn is_zero(&self) -> bool { - self.block_start == Zero::zero() && - self.inline_end == Zero::zero() && - self.block_end == Zero::zero() && - self.inline_start == Zero::zero() - } -} - -impl<T: Copy + Add<T, Output = T>> LogicalMargin<T> { - #[inline] - pub fn inline_start_end(&self) -> T { - self.inline_start + self.inline_end - } - - #[inline] - pub fn block_start_end(&self) -> T { - self.block_start + self.block_end - } - - #[inline] - pub fn start_end(&self, direction: Direction) -> T { - match direction { - Direction::Inline => self.inline_start + self.inline_end, - Direction::Block => self.block_start + self.block_end, - } - } - - #[inline] - pub fn top_bottom(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.inline_start_end() - } else { - self.block_start_end() - } - } - - #[inline] - pub fn left_right(&self, mode: WritingMode) -> T { - self.debug_writing_mode.check(mode); - if mode.is_vertical() { - self.block_start_end() - } else { - self.inline_start_end() - } - } -} - -impl<T: Add<T, Output = T>> Add for LogicalMargin<T> { - type Output = LogicalMargin<T>; - - #[inline] - fn add(self, other: LogicalMargin<T>) -> LogicalMargin<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalMargin { - debug_writing_mode: self.debug_writing_mode, - block_start: self.block_start + other.block_start, - inline_end: self.inline_end + other.inline_end, - block_end: self.block_end + other.block_end, - inline_start: self.inline_start + other.inline_start, - } - } -} - -impl<T: Sub<T, Output = T>> Sub for LogicalMargin<T> { - type Output = LogicalMargin<T>; - - #[inline] - fn sub(self, other: LogicalMargin<T>) -> LogicalMargin<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalMargin { - debug_writing_mode: self.debug_writing_mode, - block_start: self.block_start - other.block_start, - inline_end: self.inline_end - other.inline_end, - block_end: self.block_end - other.block_end, - inline_start: self.inline_start - other.inline_start, - } - } -} - -/// A rectangle in flow-relative dimensions -#[derive(Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "servo", derive(Serialize))] -pub struct LogicalRect<T> { - pub start: LogicalPoint<T>, - pub size: LogicalSize<T>, - debug_writing_mode: DebugWritingMode, -} - -impl<T: Debug> Debug for LogicalRect<T> { - fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> { - let writing_mode_string = if cfg!(debug_assertions) { - format!("{:?}, ", self.debug_writing_mode) - } else { - "".to_owned() - }; - - write!( - formatter, - "LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))", - writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b - ) - } -} - -impl<T: Zero> LogicalRect<T> { - #[inline] - pub fn zero(mode: WritingMode) -> LogicalRect<T> { - LogicalRect { - start: LogicalPoint::zero(mode), - size: LogicalSize::zero(mode), - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl<T: Copy> LogicalRect<T> { - #[inline] - pub fn new( - mode: WritingMode, - inline_start: T, - block_start: T, - inline: T, - block: T, - ) -> LogicalRect<T> { - LogicalRect { - start: LogicalPoint::new(mode, inline_start, block_start), - size: LogicalSize::new(mode, inline, block), - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn from_point_size( - mode: WritingMode, - start: LogicalPoint<T>, - size: LogicalSize<T>, - ) -> LogicalRect<T> { - start.debug_writing_mode.check(mode); - size.debug_writing_mode.check(mode); - LogicalRect { - start: start, - size: size, - debug_writing_mode: DebugWritingMode::new(mode), - } - } -} - -impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> { - #[inline] - pub fn from_physical( - mode: WritingMode, - rect: Rect<T>, - container_size: Size2D<T>, - ) -> LogicalRect<T> { - let inline_start; - let block_start; - let inline; - let block; - if mode.is_vertical() { - inline = rect.size.height; - block = rect.size.width; - if mode.is_vertical_lr() { - block_start = rect.origin.x; - } else { - block_start = container_size.width - (rect.origin.x + rect.size.width); - } - if mode.is_inline_tb() { - inline_start = rect.origin.y; - } else { - inline_start = container_size.height - (rect.origin.y + rect.size.height); - } - } else { - inline = rect.size.width; - block = rect.size.height; - block_start = rect.origin.y; - if mode.is_bidi_ltr() { - inline_start = rect.origin.x; - } else { - inline_start = container_size.width - (rect.origin.x + rect.size.width); - } - } - LogicalRect { - start: LogicalPoint::new(mode, inline_start, block_start), - size: LogicalSize::new(mode, inline, block), - debug_writing_mode: DebugWritingMode::new(mode), - } - } - - #[inline] - pub fn inline_end(&self) -> T { - self.start.i + self.size.inline - } - - #[inline] - pub fn block_end(&self) -> T { - self.start.b + self.size.block - } - - #[inline] - pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> { - self.debug_writing_mode.check(mode); - let x; - let y; - let width; - let height; - if mode.is_vertical() { - width = self.size.block; - height = self.size.inline; - if mode.is_vertical_lr() { - x = self.start.b; - } else { - x = container_size.width - self.block_end(); - } - if mode.is_inline_tb() { - y = self.start.i; - } else { - y = container_size.height - self.inline_end(); - } - } else { - width = self.size.inline; - height = self.size.block; - y = self.start.b; - if mode.is_bidi_ltr() { - x = self.start.i; - } else { - x = container_size.width - self.inline_end(); - } - } - Rect { - origin: Point2D::new(x, y), - size: Size2D::new(width, height), - } - } - - #[inline] - pub fn convert( - &self, - mode_from: WritingMode, - mode_to: WritingMode, - container_size: Size2D<T>, - ) -> LogicalRect<T> { - if mode_from == mode_to { - self.debug_writing_mode.check(mode_from); - *self - } else { - LogicalRect::from_physical( - mode_to, - self.to_physical(mode_from, container_size), - container_size, - ) - } - } - - pub fn translate_by_size(&self, offset: LogicalSize<T>) -> LogicalRect<T> { - LogicalRect { - start: self.start + offset, - ..*self - } - } - - pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> { - LogicalRect { - start: self.start + - LogicalSize { - inline: offset.i, - block: offset.b, - debug_writing_mode: offset.debug_writing_mode, - }, - size: self.size, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -impl<T: Copy + Ord + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> { - #[inline] - pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - - let inline_start = min(self.start.i, other.start.i); - let block_start = min(self.start.b, other.start.b); - LogicalRect { - start: LogicalPoint { - i: inline_start, - b: block_start, - debug_writing_mode: self.debug_writing_mode, - }, - size: LogicalSize { - inline: max(self.inline_end(), other.inline_end()) - inline_start, - block: max(self.block_end(), other.block_end()) - block_start, - debug_writing_mode: self.debug_writing_mode, - }, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Add<LogicalMargin<T>> for LogicalRect<T> { - type Output = LogicalRect<T>; - - #[inline] - fn add(self, other: LogicalMargin<T>) -> LogicalRect<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalRect { - start: LogicalPoint { - // Growing a rectangle on the start side means pushing its - // start point on the negative direction. - i: self.start.i - other.inline_start, - b: self.start.b - other.block_start, - debug_writing_mode: self.debug_writing_mode, - }, - size: LogicalSize { - inline: self.size.inline + other.inline_start_end(), - block: self.size.block + other.block_start_end(), - debug_writing_mode: self.debug_writing_mode, - }, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Sub<LogicalMargin<T>> for LogicalRect<T> { - type Output = LogicalRect<T>; - - #[inline] - fn sub(self, other: LogicalMargin<T>) -> LogicalRect<T> { - self.debug_writing_mode - .check_debug(other.debug_writing_mode); - LogicalRect { - start: LogicalPoint { - // Shrinking a rectangle on the start side means pushing its - // start point on the positive direction. - i: self.start.i + other.inline_start, - b: self.start.b + other.block_start, - debug_writing_mode: self.debug_writing_mode, - }, - size: LogicalSize { - inline: self.size.inline - other.inline_start_end(), - block: self.size.block - other.block_start_end(), - debug_writing_mode: self.debug_writing_mode, - }, - debug_writing_mode: self.debug_writing_mode, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PhysicalSide { - Top, - Right, - Bottom, - Left, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum PhysicalCorner { - TopLeft, - TopRight, - BottomRight, - BottomLeft, -} diff --git a/components/style/macros.rs b/components/style/macros.rs deleted file mode 100644 index 5f3a1ea463b..00000000000 --- a/components/style/macros.rs +++ /dev/null @@ -1,98 +0,0 @@ -/* 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/. */ - -//! Various macro helpers. - -macro_rules! exclusive_value { - (($value:ident, $set:expr) => $ident:path) => { - if $value.intersects($set) { - return Err(()); - } else { - $ident - } - }; -} - -#[cfg(feature = "gecko")] -macro_rules! impl_gecko_keyword_conversions { - ($name:ident, $utype:ty) => { - impl From<$utype> for $name { - fn from(bits: $utype) -> $name { - $name::from_gecko_keyword(bits) - } - } - - impl From<$name> for $utype { - fn from(v: $name) -> $utype { - v.to_gecko_keyword() - } - } - }; -} - -macro_rules! trivial_to_computed_value { - ($name:ty) => { - impl $crate::values::computed::ToComputedValue for $name { - type ComputedValue = $name; - - fn to_computed_value(&self, _: &$crate::values::computed::Context) -> Self { - self.clone() - } - - fn from_computed_value(other: &Self) -> Self { - other.clone() - } - } - }; -} - -/// A macro to parse an identifier, or return an `UnexpectedIdent` error -/// otherwise. -/// -/// FIXME(emilio): The fact that `UnexpectedIdent` is a `SelectorParseError` -/// doesn't make a lot of sense to me. -macro_rules! try_match_ident_ignore_ascii_case { - ($input:expr, $( $match_body:tt )*) => {{ - let location = $input.current_source_location(); - let ident = $input.expect_ident_cloned()?; - match_ignore_ascii_case! { &ident, - $( $match_body )* - _ => return Err(location.new_custom_error( - ::selectors::parser::SelectorParseErrorKind::UnexpectedIdent(ident.clone()) - )) - } - }} -} - -#[cfg(feature = "servo")] -macro_rules! local_name { - ($s:tt) => { - $crate::values::GenericAtomIdent(html5ever::local_name!($s)) - }; -} - -#[cfg(feature = "servo")] -macro_rules! ns { - () => { - $crate::values::GenericAtomIdent(html5ever::ns!()) - }; - ($s:tt) => { - $crate::values::GenericAtomIdent(html5ever::ns!($s)) - }; -} - -#[cfg(feature = "gecko")] -macro_rules! local_name { - ($s:tt) => { - $crate::values::AtomIdent(atom!($s)) - }; -} - -/// Asserts the size of a type at compile time. -macro_rules! size_of_test { - ($t: ty, $expected_size: expr) => { - #[cfg(target_pointer_width = "64")] - const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); - }; -} diff --git a/components/style/matching.rs b/components/style/matching.rs deleted file mode 100644 index bc9c797a5d2..00000000000 --- a/components/style/matching.rs +++ /dev/null @@ -1,1096 +0,0 @@ -/* 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/. */ - -//! High-level interface to CSS selector matching. - -#![allow(unsafe_code)] -#![deny(missing_docs)] - -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode}; -use crate::context::{SharedStyleContext, StyleContext}; -use crate::data::{ElementData, ElementStyles}; -use crate::dom::TElement; -#[cfg(feature = "servo")] -use crate::dom::TNode; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::ComputedValues; -use crate::properties::PropertyDeclarationBlock; -use crate::rule_tree::{CascadeLevel, StrongRuleNode}; -use crate::selector_parser::{PseudoElement, RestyleDamage}; -use crate::shared_lock::Locked; -use crate::style_resolver::ResolvedElementStyles; -use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; -use crate::stylesheets::layer_rule::LayerOrder; -use crate::stylist::RuleInclusion; -use crate::traversal_flags::TraversalFlags; -use servo_arc::{Arc, ArcBorrow}; - -/// Represents the result of comparing an element's old and new style. -#[derive(Debug)] -pub struct StyleDifference { - /// The resulting damage. - pub damage: RestyleDamage, - - /// Whether any styles changed. - pub change: StyleChange, -} - -/// Represents whether or not the style of an element has changed. -#[derive(Clone, Copy, Debug)] -pub enum StyleChange { - /// The style hasn't changed. - Unchanged, - /// The style has changed. - Changed { - /// Whether only reset structs changed. - reset_only: bool, - }, -} - -/// Whether or not newly computed values for an element need to be cascaded to -/// children (or children might need to be re-matched, e.g., for container -/// queries). -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub enum ChildRestyleRequirement { - /// Old and new computed values were the same, or we otherwise know that - /// we won't bother recomputing style for children, so we can skip cascading - /// the new values into child elements. - CanSkipCascade = 0, - /// The same as `MustCascadeChildren`, but we only need to actually - /// recascade if the child inherits any explicit reset style. - MustCascadeChildrenIfInheritResetStyle = 1, - /// Old and new computed values were different, so we must cascade the - /// new values to children. - MustCascadeChildren = 2, - /// The same as `MustCascadeChildren`, but for the entire subtree. This is - /// used to handle root font-size updates needing to recascade the whole - /// document. - MustCascadeDescendants = 3, - /// We need to re-match the whole subttree. This is used to handle container - /// query relative unit changes for example. Container query size changes - /// also trigger re-match, but after layout. - MustMatchDescendants = 4, -} - -/// Determines which styles are being cascaded currently. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum CascadeVisitedMode { - /// Cascade the regular, unvisited styles. - Unvisited, - /// Cascade the styles used when an element's relevant link is visited. A - /// "relevant link" is the element being matched if it is a link or the - /// nearest ancestor link. - Visited, -} - -trait PrivateMatchMethods: TElement { - fn replace_single_rule_node( - context: &SharedStyleContext, - level: CascadeLevel, - layer_order: LayerOrder, - pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, - path: &mut StrongRuleNode, - ) -> bool { - let stylist = &context.stylist; - let guards = &context.guards; - - let mut important_rules_changed = false; - let new_node = stylist.rule_tree().update_rule_at_level( - level, - layer_order, - pdb, - path, - guards, - &mut important_rules_changed, - ); - if let Some(n) = new_node { - *path = n; - } - important_rules_changed - } - - /// Updates the rule nodes without re-running selector matching, using just - /// the rule tree, for a specific visited mode. - /// - /// Returns true if an !important rule was replaced. - fn replace_rules_internal( - &self, - replacements: RestyleHint, - context: &mut StyleContext<Self>, - cascade_visited: CascadeVisitedMode, - cascade_inputs: &mut ElementCascadeInputs, - ) -> bool { - debug_assert!( - replacements.intersects(RestyleHint::replacements()) && - (replacements & !RestyleHint::replacements()).is_empty() - ); - - let primary_rules = match cascade_visited { - CascadeVisitedMode::Unvisited => cascade_inputs.primary.rules.as_mut(), - CascadeVisitedMode::Visited => cascade_inputs.primary.visited_rules.as_mut(), - }; - - let primary_rules = match primary_rules { - Some(r) => r, - None => return false, - }; - - if !context.shared.traversal_flags.for_animation_only() { - let mut result = false; - if replacements.contains(RestyleHint::RESTYLE_STYLE_ATTRIBUTE) { - let style_attribute = self.style_attribute(); - result |= Self::replace_single_rule_node( - context.shared, - CascadeLevel::same_tree_author_normal(), - LayerOrder::root(), - style_attribute, - primary_rules, - ); - result |= Self::replace_single_rule_node( - context.shared, - CascadeLevel::same_tree_author_important(), - LayerOrder::root(), - style_attribute, - primary_rules, - ); - // FIXME(emilio): Still a hack! - self.unset_dirty_style_attribute(); - } - return result; - } - - // Animation restyle hints are processed prior to other restyle - // hints in the animation-only traversal. - // - // Non-animation restyle hints will be processed in a subsequent - // normal traversal. - if replacements.intersects(RestyleHint::for_animations()) { - debug_assert!(context.shared.traversal_flags.for_animation_only()); - - if replacements.contains(RestyleHint::RESTYLE_SMIL) { - Self::replace_single_rule_node( - context.shared, - CascadeLevel::SMILOverride, - LayerOrder::root(), - self.smil_override(), - primary_rules, - ); - } - - if replacements.contains(RestyleHint::RESTYLE_CSS_TRANSITIONS) { - Self::replace_single_rule_node( - context.shared, - CascadeLevel::Transitions, - LayerOrder::root(), - self.transition_rule(&context.shared) - .as_ref() - .map(|a| a.borrow_arc()), - primary_rules, - ); - } - - if replacements.contains(RestyleHint::RESTYLE_CSS_ANIMATIONS) { - Self::replace_single_rule_node( - context.shared, - CascadeLevel::Animations, - LayerOrder::root(), - self.animation_rule(&context.shared) - .as_ref() - .map(|a| a.borrow_arc()), - primary_rules, - ); - } - } - - false - } - - /// If there is no transition rule in the ComputedValues, it returns None. - fn after_change_style( - &self, - context: &mut StyleContext<Self>, - primary_style: &Arc<ComputedValues>, - ) -> Option<Arc<ComputedValues>> { - let rule_node = primary_style.rules(); - let without_transition_rules = context - .shared - .stylist - .rule_tree() - .remove_transition_rule_if_applicable(rule_node); - if without_transition_rules == *rule_node { - // We don't have transition rule in this case, so return None to let - // the caller use the original ComputedValues. - return None; - } - - // FIXME(bug 868975): We probably need to transition visited style as - // well. - let inputs = CascadeInputs { - rules: Some(without_transition_rules), - visited_rules: primary_style.visited_rules().cloned(), - flags: primary_style.flags.for_cascade_inputs(), - }; - - // Actually `PseudoElementResolution` doesn't really matter. - let style = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ) - .cascade_style_and_visited_with_default_parents(inputs); - - Some(style.0) - } - - fn needs_animations_update( - &self, - context: &mut StyleContext<Self>, - old_style: Option<&ComputedValues>, - new_style: &ComputedValues, - pseudo_element: Option<PseudoElement>, - ) -> bool { - let new_ui_style = new_style.get_ui(); - let new_style_specifies_animations = new_ui_style.specifies_animations(); - - let has_animations = self.has_css_animations(&context.shared, pseudo_element); - if !new_style_specifies_animations && !has_animations { - return false; - } - - let old_style = match old_style { - Some(old) => old, - // If we have no old style but have animations, we may be a - // pseudo-element which was re-created without style changes. - // - // This can happen when we reframe the pseudo-element without - // restyling it (due to content insertion on a flex container or - // such, for example). See bug 1564366. - // - // FIXME(emilio): The really right fix for this is keeping the - // pseudo-element itself around on reframes, but that's a bit - // harder. If we do that we can probably remove quite a lot of the - // EffectSet complexity though, since right now it's stored on the - // parent element for pseudo-elements given we need to keep it - // around... - None => { - return new_style_specifies_animations || new_style.is_pseudo_style(); - }, - }; - - let old_ui_style = old_style.get_ui(); - - let keyframes_could_have_changed = context - .shared - .traversal_flags - .contains(TraversalFlags::ForCSSRuleChanges); - - // If the traversal is triggered due to changes in CSS rules changes, we - // need to try to update all CSS animations on the element if the - // element has or will have CSS animation style regardless of whether - // the animation is running or not. - // - // TODO: We should check which @keyframes were added/changed/deleted and - // update only animations corresponding to those @keyframes. - if keyframes_could_have_changed { - return true; - } - - // If the animations changed, well... - if !old_ui_style.animations_equals(new_ui_style) { - return true; - } - - let old_display = old_style.clone_display(); - let new_display = new_style.clone_display(); - - // If we were display: none, we may need to trigger animations. - if old_display == Display::None && new_display != Display::None { - return new_style_specifies_animations; - } - - // If we are becoming display: none, we may need to stop animations. - if old_display != Display::None && new_display == Display::None { - return has_animations; - } - - // We might need to update animations if writing-mode or direction - // changed, and any of the animations contained logical properties. - // - // We may want to be more granular, but it's probably not worth it. - if new_style.writing_mode != old_style.writing_mode { - return has_animations; - } - - false - } - - fn might_need_transitions_update( - &self, - context: &StyleContext<Self>, - old_style: Option<&ComputedValues>, - new_style: &ComputedValues, - pseudo_element: Option<PseudoElement>, - ) -> bool { - let old_style = match old_style { - Some(v) => v, - None => return false, - }; - - if !self.has_css_transitions(context.shared, pseudo_element) && - !new_style.get_ui().specifies_transitions() - { - return false; - } - - if old_style.clone_display().is_none() { - return false; - } - - return true; - } - - /// Create a SequentialTask for resolving descendants in a SMIL display - /// property animation if the display property changed from none. - #[cfg(feature = "gecko")] - fn handle_display_change_for_smil_if_needed( - &self, - context: &mut StyleContext<Self>, - old_values: Option<&ComputedValues>, - new_values: &ComputedValues, - restyle_hints: RestyleHint, - ) { - use crate::context::PostAnimationTasks; - - if !restyle_hints.intersects(RestyleHint::RESTYLE_SMIL) { - return; - } - - if new_values.is_display_property_changed_from_none(old_values) { - // When display value is changed from none to other, we need to - // traverse descendant elements in a subsequent normal - // traversal (we can't traverse them in this animation-only restyle - // since we have no way to know whether the decendants - // need to be traversed at the beginning of the animation-only - // restyle). - let task = crate::context::SequentialTask::process_post_animation( - *self, - PostAnimationTasks::DISPLAY_CHANGED_FROM_NONE_FOR_SMIL, - ); - context.thread_local.tasks.push(task); - } - } - - #[cfg(feature = "gecko")] - fn process_animations( - &self, - context: &mut StyleContext<Self>, - old_styles: &mut ElementStyles, - new_styles: &mut ResolvedElementStyles, - restyle_hint: RestyleHint, - important_rules_changed: bool, - ) { - use crate::context::UpdateAnimationsTasks; - - let new_values = new_styles.primary_style_mut(); - let old_values = &old_styles.primary; - if context.shared.traversal_flags.for_animation_only() { - self.handle_display_change_for_smil_if_needed( - context, - old_values.as_deref(), - new_values, - restyle_hint, - ); - return; - } - - // Bug 868975: These steps should examine and update the visited styles - // in addition to the unvisited styles. - - let mut tasks = UpdateAnimationsTasks::empty(); - - if old_values.as_deref().map_or_else( - || new_values.get_ui().specifies_scroll_timelines(), - |old| !old.get_ui().scroll_timelines_equals(new_values.get_ui()), - ) { - tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES); - } - - if old_values.as_deref().map_or_else( - || new_values.get_ui().specifies_view_timelines(), - |old| !old.get_ui().view_timelines_equals(new_values.get_ui()), - ) { - tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES); - } - - if self.needs_animations_update( - context, - old_values.as_deref(), - new_values, - /* pseudo_element = */ None, - ) { - tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS); - } - - let before_change_style = if self.might_need_transitions_update( - context, - old_values.as_deref(), - new_values, - /* pseudo_element = */ None, - ) { - let after_change_style = - if self.has_css_transitions(context.shared, /* pseudo_element = */ None) { - self.after_change_style(context, new_values) - } else { - None - }; - - // In order to avoid creating a SequentialTask for transitions which - // may not be updated, we check it per property to make sure Gecko - // side will really update transition. - let needs_transitions_update = { - // We borrow new_values here, so need to add a scope to make - // sure we release it before assigning a new value to it. - let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values); - - self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref) - }; - - if needs_transitions_update { - if let Some(values_without_transitions) = after_change_style { - *new_values = values_without_transitions; - } - tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS); - - // We need to clone old_values into SequentialTask, so we can - // use it later. - old_values.clone() - } else { - None - } - } else { - None - }; - - if self.has_animations(&context.shared) { - tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES); - if important_rules_changed { - tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS); - } - if new_values.is_display_property_changed_from_none(old_values.as_deref()) { - tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE); - } - } - - if !tasks.is_empty() { - let task = crate::context::SequentialTask::update_animations( - *self, - before_change_style, - tasks, - ); - context.thread_local.tasks.push(task); - } - } - - #[cfg(feature = "servo")] - fn process_animations( - &self, - context: &mut StyleContext<Self>, - old_styles: &mut ElementStyles, - new_resolved_styles: &mut ResolvedElementStyles, - _restyle_hint: RestyleHint, - _important_rules_changed: bool, - ) { - use crate::animation::AnimationSetKey; - use crate::dom::TDocument; - - let style_changed = self.process_animations_for_style( - context, - &mut old_styles.primary, - new_resolved_styles.primary_style_mut(), - /* pseudo_element = */ None, - ); - - // If we have modified animation or transitions, we recascade style for this node. - if style_changed { - let primary_style = new_resolved_styles.primary_style(); - let mut rule_node = primary_style.rules().clone(); - let declarations = context.shared.animations.get_all_declarations( - &AnimationSetKey::new_for_non_pseudo(self.as_node().opaque()), - context.shared.current_time_for_animations, - self.as_node().owner_doc().shared_lock(), - ); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Transitions, - LayerOrder::root(), - declarations.transitions.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Animations, - LayerOrder::root(), - declarations.animations.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - - if rule_node != *primary_style.rules() { - let inputs = CascadeInputs { - rules: Some(rule_node), - visited_rules: primary_style.visited_rules().cloned(), - flags: primary_style.flags.for_cascade_inputs(), - }; - - new_resolved_styles.primary.style = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ) - .cascade_style_and_visited_with_default_parents(inputs); - } - } - - self.process_animations_for_pseudo( - context, - old_styles, - new_resolved_styles, - PseudoElement::Before, - ); - self.process_animations_for_pseudo( - context, - old_styles, - new_resolved_styles, - PseudoElement::After, - ); - } - - #[cfg(feature = "servo")] - fn process_animations_for_pseudo( - &self, - context: &mut StyleContext<Self>, - old_styles: &mut ElementStyles, - new_resolved_styles: &mut ResolvedElementStyles, - pseudo_element: PseudoElement, - ) { - use crate::animation::AnimationSetKey; - use crate::dom::TDocument; - - let key = AnimationSetKey::new_for_pseudo(self.as_node().opaque(), pseudo_element.clone()); - let mut style = match new_resolved_styles.pseudos.get(&pseudo_element) { - Some(style) => Arc::clone(style), - None => { - context - .shared - .animations - .cancel_all_animations_for_key(&key); - return; - }, - }; - - let mut old_style = old_styles.pseudos.get(&pseudo_element).cloned(); - self.process_animations_for_style( - context, - &mut old_style, - &mut style, - Some(pseudo_element.clone()), - ); - - let declarations = context.shared.animations.get_all_declarations( - &key, - context.shared.current_time_for_animations, - self.as_node().owner_doc().shared_lock(), - ); - if declarations.is_empty() { - return; - } - - let mut rule_node = style.rules().clone(); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Transitions, - LayerOrder::root(), - declarations.transitions.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - Self::replace_single_rule_node( - &context.shared, - CascadeLevel::Animations, - LayerOrder::root(), - declarations.animations.as_ref().map(|a| a.borrow_arc()), - &mut rule_node, - ); - if rule_node == *style.rules() { - return; - } - - let inputs = CascadeInputs { - rules: Some(rule_node), - visited_rules: style.visited_rules().cloned(), - flags: style.flags.for_cascade_inputs(), - }; - - let new_style = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ) - .cascade_style_and_visited_for_pseudo_with_default_parents( - inputs, - &pseudo_element, - &new_resolved_styles.primary, - ); - - new_resolved_styles - .pseudos - .set(&pseudo_element, new_style.0); - } - - #[cfg(feature = "servo")] - fn process_animations_for_style( - &self, - context: &mut StyleContext<Self>, - old_values: &mut Option<Arc<ComputedValues>>, - new_values: &mut Arc<ComputedValues>, - pseudo_element: Option<PseudoElement>, - ) -> bool { - use crate::animation::{AnimationSetKey, AnimationState}; - - // We need to call this before accessing the `ElementAnimationSet` from the - // map because this call will do a RwLock::read(). - let needs_animations_update = self.needs_animations_update( - context, - old_values.as_deref(), - new_values, - pseudo_element, - ); - - let might_need_transitions_update = self.might_need_transitions_update( - context, - old_values.as_deref(), - new_values, - pseudo_element, - ); - - let mut after_change_style = None; - if might_need_transitions_update { - after_change_style = self.after_change_style(context, new_values); - } - - let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); - let shared_context = context.shared; - let mut animation_set = shared_context - .animations - .sets - .write() - .remove(&key) - .unwrap_or_default(); - - // Starting animations is expensive, because we have to recalculate the style - // for all the keyframes. We only want to do this if we think that there's a - // chance that the animations really changed. - if needs_animations_update { - let mut resolver = StyleResolverForElement::new( - *self, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ); - - animation_set.update_animations_for_new_style::<Self>( - *self, - &shared_context, - &new_values, - &mut resolver, - ); - } - - animation_set.update_transitions_for_new_style( - might_need_transitions_update, - &shared_context, - old_values.as_ref(), - after_change_style.as_ref().unwrap_or(new_values), - ); - - // We clear away any finished transitions, but retain animations, because they - // might still be used for proper calculation of `animation-fill-mode`. This - // should change the computed values in the style, so we don't need to mark - // this set as dirty. - animation_set - .transitions - .retain(|transition| transition.state != AnimationState::Finished); - - // If the ElementAnimationSet is empty, and don't store it in order to - // save memory and to avoid extra processing later. - let changed_animations = animation_set.dirty; - if !animation_set.is_empty() { - animation_set.dirty = false; - shared_context - .animations - .sets - .write() - .insert(key, animation_set); - } - - changed_animations - } - - /// Computes and applies non-redundant damage. - fn accumulate_damage_for( - &self, - shared_context: &SharedStyleContext, - damage: &mut RestyleDamage, - old_values: &ComputedValues, - new_values: &ComputedValues, - pseudo: Option<&PseudoElement>, - ) -> ChildRestyleRequirement { - debug!("accumulate_damage_for: {:?}", self); - debug_assert!(!shared_context - .traversal_flags - .contains(TraversalFlags::FinalAnimationTraversal)); - - let difference = self.compute_style_difference(old_values, new_values, pseudo); - - *damage |= difference.damage; - - debug!(" > style difference: {:?}", difference); - - // We need to cascade the children in order to ensure the correct - // propagation of inherited computed value flags. - if old_values.flags.maybe_inherited() != new_values.flags.maybe_inherited() { - debug!( - " > flags changed: {:?} != {:?}", - old_values.flags, new_values.flags - ); - return ChildRestyleRequirement::MustCascadeChildren; - } - - match difference.change { - StyleChange::Unchanged => return ChildRestyleRequirement::CanSkipCascade, - StyleChange::Changed { reset_only } => { - // If inherited properties changed, the best we can do is - // cascade the children. - if !reset_only { - return ChildRestyleRequirement::MustCascadeChildren; - } - }, - } - - let old_display = old_values.clone_display(); - let new_display = new_values.clone_display(); - - if old_display != new_display { - // If we used to be a display: none element, and no longer are, our - // children need to be restyled because they're unstyled. - if old_display == Display::None { - return ChildRestyleRequirement::MustCascadeChildren; - } - // Blockification of children may depend on our display value, - // so we need to actually do the recascade. We could potentially - // do better, but it doesn't seem worth it. - if old_display.is_item_container() != new_display.is_item_container() { - return ChildRestyleRequirement::MustCascadeChildren; - } - // We may also need to blockify and un-blockify descendants if our - // display goes from / to display: contents, since the "layout - // parent style" changes. - if old_display.is_contents() || new_display.is_contents() { - return ChildRestyleRequirement::MustCascadeChildren; - } - // Line break suppression may also be affected if the display - // type changes from ruby to non-ruby. - #[cfg(feature = "gecko")] - { - if old_display.is_ruby_type() != new_display.is_ruby_type() { - return ChildRestyleRequirement::MustCascadeChildren; - } - } - } - - // Children with justify-items: auto may depend on our - // justify-items property value. - // - // Similarly, we could potentially do better, but this really - // seems not common enough to care about. - #[cfg(feature = "gecko")] - { - use crate::values::specified::align::AlignFlags; - - let old_justify_items = old_values.get_position().clone_justify_items(); - let new_justify_items = new_values.get_position().clone_justify_items(); - - let was_legacy_justify_items = - old_justify_items.computed.0.contains(AlignFlags::LEGACY); - - let is_legacy_justify_items = new_justify_items.computed.0.contains(AlignFlags::LEGACY); - - if is_legacy_justify_items != was_legacy_justify_items { - return ChildRestyleRequirement::MustCascadeChildren; - } - - if was_legacy_justify_items && old_justify_items.computed != new_justify_items.computed - { - return ChildRestyleRequirement::MustCascadeChildren; - } - } - - #[cfg(feature = "servo")] - { - // We may need to set or propagate the CAN_BE_FRAGMENTED bit - // on our children. - if old_values.is_multicol() != new_values.is_multicol() { - return ChildRestyleRequirement::MustCascadeChildren; - } - } - - // We could prove that, if our children don't inherit reset - // properties, we can stop the cascade. - ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle - } -} - -impl<E: TElement> PrivateMatchMethods for E {} - -/// The public API that elements expose for selector matching. -pub trait MatchMethods: TElement { - /// Returns the closest parent element that doesn't have a display: contents - /// style (and thus generates a box). - /// - /// This is needed to correctly handle blockification of flex and grid - /// items. - /// - /// Returns itself if the element has no parent. In practice this doesn't - /// happen because the root element is blockified per spec, but it could - /// happen if we decide to not blockify for roots of disconnected subtrees, - /// which is a kind of dubious behavior. - fn layout_parent(&self) -> Self { - let mut current = self.clone(); - loop { - current = match current.traversal_parent() { - Some(el) => el, - None => return current, - }; - - let is_display_contents = current - .borrow_data() - .unwrap() - .styles - .primary() - .is_display_contents(); - - if !is_display_contents { - return current; - } - } - } - - /// Updates the styles with the new ones, diffs them, and stores the restyle - /// damage. - fn finish_restyle( - &self, - context: &mut StyleContext<Self>, - data: &mut ElementData, - mut new_styles: ResolvedElementStyles, - important_rules_changed: bool, - ) -> ChildRestyleRequirement { - use std::cmp; - - self.process_animations( - context, - &mut data.styles, - &mut new_styles, - data.hint, - important_rules_changed, - ); - - // First of all, update the styles. - let old_styles = data.set_styles(new_styles); - - let new_primary_style = data.styles.primary.as_ref().unwrap(); - - let mut restyle_requirement = ChildRestyleRequirement::CanSkipCascade; - let is_root = new_primary_style - .flags - .contains(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE); - let is_container = !new_primary_style - .get_box() - .clone_container_type() - .is_normal(); - if is_root || is_container { - let new_font_size = new_primary_style.get_font().clone_font_size(); - let old_font_size = old_styles - .primary - .as_ref() - .map(|s| s.get_font().clone_font_size()); - - if old_font_size != Some(new_font_size) { - if is_root { - let device = context.shared.stylist.device(); - debug_assert!(self.owner_doc_matches_for_testing(device)); - device.set_root_font_size(new_font_size.computed_size().into()); - if device.used_root_font_size() { - // If the root font-size changed since last time, and something - // in the document did use rem units, ensure we recascade the - // entire tree. - restyle_requirement = ChildRestyleRequirement::MustCascadeDescendants; - } - } - - if is_container && old_font_size.is_some() { - // TODO(emilio): Maybe only do this if we were matched - // against relative font sizes? - // Also, maybe we should do this as well for font-family / - // etc changes (for ex/ch/ic units to work correctly)? We - // should probably do the optimization mentioned above if - // so. - restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; - } - } - } - - if context.shared.stylist.quirks_mode() == QuirksMode::Quirks { - if self.is_html_document_body_element() { - // NOTE(emilio): We _could_ handle dynamic changes to it if it - // changes and before we reach our children the cascade stops, - // but we don't track right now whether we use the document body - // color, and nobody else handles that properly anyway. - let device = context.shared.stylist.device(); - - // Needed for the "inherit from body" quirk. - let text_color = new_primary_style.get_inherited_text().clone_color(); - device.set_body_text_color(text_color); - } - } - - // Don't accumulate damage if we're in the final animation traversal. - if context - .shared - .traversal_flags - .contains(TraversalFlags::FinalAnimationTraversal) - { - return ChildRestyleRequirement::MustCascadeChildren; - } - - // Also, don't do anything if there was no style. - let old_primary_style = match old_styles.primary { - Some(s) => s, - None => return ChildRestyleRequirement::MustCascadeChildren, - }; - - let old_container_type = old_primary_style.clone_container_type(); - let new_container_type = new_primary_style.clone_container_type(); - if old_container_type != new_container_type && !new_container_type.is_size_container_type() - { - // Stopped being a size container. Re-evaluate container queries and units on all our descendants. - // Changes into and between different size containment is handled in `UpdateContainerQueryStyles`. - restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; - } else if old_container_type.is_size_container_type() && - !old_primary_style.is_display_contents() && - new_primary_style.is_display_contents() - { - // Also re-evaluate when a container gets 'display: contents', since size queries will now evaluate to unknown. - // Other displays like 'inline' will keep generating a box, so they are handled in `UpdateContainerQueryStyles`. - restyle_requirement = ChildRestyleRequirement::MustMatchDescendants; - } - - restyle_requirement = cmp::max( - restyle_requirement, - self.accumulate_damage_for( - context.shared, - &mut data.damage, - &old_primary_style, - new_primary_style, - None, - ), - ); - - if data.styles.pseudos.is_empty() && old_styles.pseudos.is_empty() { - // This is the common case; no need to examine pseudos here. - return restyle_requirement; - } - - let pseudo_styles = old_styles - .pseudos - .as_array() - .iter() - .zip(data.styles.pseudos.as_array().iter()); - - for (i, (old, new)) in pseudo_styles.enumerate() { - match (old, new) { - (&Some(ref old), &Some(ref new)) => { - self.accumulate_damage_for( - context.shared, - &mut data.damage, - old, - new, - Some(&PseudoElement::from_eager_index(i)), - ); - }, - (&None, &None) => {}, - _ => { - // It's possible that we're switching from not having - // ::before/::after at all to having styles for them but not - // actually having a useful pseudo-element. Check for that - // case. - let pseudo = PseudoElement::from_eager_index(i); - let new_pseudo_should_exist = - new.as_ref().map_or(false, |s| pseudo.should_exist(s)); - let old_pseudo_should_exist = - old.as_ref().map_or(false, |s| pseudo.should_exist(s)); - if new_pseudo_should_exist != old_pseudo_should_exist { - data.damage |= RestyleDamage::reconstruct(); - return restyle_requirement; - } - }, - } - } - - restyle_requirement - } - - /// Updates the rule nodes without re-running selector matching, using just - /// the rule tree. - /// - /// Returns true if an !important rule was replaced. - fn replace_rules( - &self, - replacements: RestyleHint, - context: &mut StyleContext<Self>, - cascade_inputs: &mut ElementCascadeInputs, - ) -> bool { - let mut result = false; - result |= self.replace_rules_internal( - replacements, - context, - CascadeVisitedMode::Unvisited, - cascade_inputs, - ); - result |= self.replace_rules_internal( - replacements, - context, - CascadeVisitedMode::Visited, - cascade_inputs, - ); - result - } - - /// Given the old and new style of this element, and whether it's a - /// pseudo-element, compute the restyle damage used to determine which - /// kind of layout or painting operations we'll need. - fn compute_style_difference( - &self, - old_values: &ComputedValues, - new_values: &ComputedValues, - pseudo: Option<&PseudoElement>, - ) -> StyleDifference { - debug_assert!(pseudo.map_or(true, |p| p.is_eager())); - RestyleDamage::compute_style_difference(old_values, new_values) - } -} - -impl<E: TElement> MatchMethods for E {} diff --git a/components/style/media_queries/media_list.rs b/components/style/media_queries/media_list.rs deleted file mode 100644 index 3c2ba9ee5c1..00000000000 --- a/components/style/media_queries/media_list.rs +++ /dev/null @@ -1,150 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A media query list: -//! -//! https://drafts.csswg.org/mediaqueries/#typedef-media-query-list - -use super::{Device, MediaQuery, Qualifier}; -use crate::context::QuirksMode; -use crate::error_reporting::ContextualParseError; -use crate::parser::ParserContext; -use crate::queries::condition::KleeneValue; -use crate::values::computed; -use cssparser::{Delimiter, Parser}; -use cssparser::{ParserInput, Token}; - -/// A type that encapsulates a media query list. -#[derive(Clone, MallocSizeOf, ToCss, ToShmem)] -#[css(comma, derive_debug)] -pub struct MediaList { - /// The list of media queries. - #[css(iterable)] - pub media_queries: Vec<MediaQuery>, -} - -impl MediaList { - /// Parse a media query list from CSS. - /// - /// Always returns a media query list. If any invalid media query is - /// found, the media query list is only filled with the equivalent of - /// "not all", see: - /// - /// <https://drafts.csswg.org/mediaqueries/#error-handling> - pub fn parse(context: &ParserContext, input: &mut Parser) -> Self { - if input.is_exhausted() { - return Self::empty(); - } - - let mut media_queries = vec![]; - loop { - let start_position = input.position(); - match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) { - Ok(mq) => { - media_queries.push(mq); - }, - Err(err) => { - media_queries.push(MediaQuery::never_matching()); - let location = err.location; - let error = ContextualParseError::InvalidMediaRule( - input.slice_from(start_position), - err, - ); - context.log_css_error(location, error); - }, - } - - match input.next() { - Ok(&Token::Comma) => {}, - Ok(_) => unreachable!(), - Err(_) => break, - } - } - - MediaList { media_queries } - } - - /// Create an empty MediaList. - pub fn empty() -> Self { - MediaList { - media_queries: vec![], - } - } - - /// Evaluate a whole `MediaList` against `Device`. - pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool { - // Check if it is an empty media query list or any queries match. - // https://drafts.csswg.org/mediaqueries-4/#mq-list - if self.media_queries.is_empty() { - return true; - } - - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - self.media_queries.iter().any(|mq| { - let mut query_match = if mq.media_type.matches(device.media_type()) { - mq.condition - .as_ref() - .map_or(KleeneValue::True, |c| c.matches(context)) - } else { - KleeneValue::False - }; - - // Apply the logical NOT qualifier to the result - if matches!(mq.qualifier, Some(Qualifier::Not)) { - query_match = !query_match; - } - query_match.to_bool(/* unknown = */ false) - }) - }) - } - - /// Whether this `MediaList` contains no media queries. - pub fn is_empty(&self) -> bool { - self.media_queries.is_empty() - } - - /// Whether this `MediaList` depends on the viewport size. - pub fn is_viewport_dependent(&self) -> bool { - self.media_queries.iter().any(|q| q.is_viewport_dependent()) - } - - /// Append a new media query item to the media list. - /// <https://drafts.csswg.org/cssom/#dom-medialist-appendmedium> - /// - /// Returns true if added, false if fail to parse the medium string. - pub fn append_medium(&mut self, context: &ParserContext, new_medium: &str) -> bool { - let mut input = ParserInput::new(new_medium); - let mut parser = Parser::new(&mut input); - let new_query = match MediaQuery::parse(&context, &mut parser) { - Ok(query) => query, - Err(_) => { - return false; - }, - }; - // This algorithm doesn't actually matches the current spec, - // but it matches the behavior of Gecko and Edge. - // See https://github.com/w3c/csswg-drafts/issues/697 - self.media_queries.retain(|query| query != &new_query); - self.media_queries.push(new_query); - true - } - - /// Delete a media query from the media list. - /// <https://drafts.csswg.org/cssom/#dom-medialist-deletemedium> - /// - /// Returns true if found and deleted, false otherwise. - pub fn delete_medium(&mut self, context: &ParserContext, old_medium: &str) -> bool { - let mut input = ParserInput::new(old_medium); - let mut parser = Parser::new(&mut input); - let old_query = match MediaQuery::parse(context, &mut parser) { - Ok(query) => query, - Err(_) => { - return false; - }, - }; - let old_len = self.media_queries.len(); - self.media_queries.retain(|query| query != &old_query); - old_len != self.media_queries.len() - } -} diff --git a/components/style/media_queries/media_query.rs b/components/style/media_queries/media_query.rs deleted file mode 100644 index c30a4453930..00000000000 --- a/components/style/media_queries/media_query.rs +++ /dev/null @@ -1,193 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A media query: -//! -//! https://drafts.csswg.org/mediaqueries/#typedef-media-query - -use crate::parser::ParserContext; -use crate::queries::{FeatureFlags, FeatureType, QueryCondition}; -use crate::str::string_as_ascii_lowercase; -use crate::values::CustomIdent; -use crate::Atom; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// <https://drafts.csswg.org/mediaqueries/#mq-prefix> -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -pub enum Qualifier { - /// Hide a media query from legacy UAs: - /// <https://drafts.csswg.org/mediaqueries/#mq-only> - Only, - /// Negate a media query: - /// <https://drafts.csswg.org/mediaqueries/#mq-not> - Not, -} - -/// <https://drafts.csswg.org/mediaqueries/#media-types> -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub struct MediaType(pub CustomIdent); - -impl MediaType { - /// The `screen` media type. - pub fn screen() -> Self { - MediaType(CustomIdent(atom!("screen"))) - } - - /// The `print` media type. - pub fn print() -> Self { - MediaType(CustomIdent(atom!("print"))) - } - - fn parse(name: &str) -> Result<Self, ()> { - // From https://drafts.csswg.org/mediaqueries/#mq-syntax: - // - // The <media-type> production does not include the keywords only, not, and, or, and layer. - // - // Here we also perform the to-ascii-lowercase part of the serialization - // algorithm: https://drafts.csswg.org/cssom/#serializing-media-queries - match_ignore_ascii_case! { name, - "not" | "or" | "and" | "only" | "layer" => Err(()), - _ => Ok(MediaType(CustomIdent(Atom::from(string_as_ascii_lowercase(name))))), - } - } -} - -/// A [media query][mq]. -/// -/// [mq]: https://drafts.csswg.org/mediaqueries/ -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct MediaQuery { - /// The qualifier for this query. - pub qualifier: Option<Qualifier>, - /// The media type for this query, that can be known, unknown, or "all". - pub media_type: MediaQueryType, - /// The condition that this media query contains. This cannot have `or` - /// in the first level. - pub condition: Option<QueryCondition>, -} - -impl ToCss for MediaQuery { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if let Some(qual) = self.qualifier { - qual.to_css(dest)?; - dest.write_char(' ')?; - } - - match self.media_type { - MediaQueryType::All => { - // We need to print "all" if there's a qualifier, or there's - // just an empty list of expressions. - // - // Otherwise, we'd serialize media queries like "(min-width: - // 40px)" in "all (min-width: 40px)", which is unexpected. - if self.qualifier.is_some() || self.condition.is_none() { - dest.write_str("all")?; - } - }, - MediaQueryType::Concrete(MediaType(ref desc)) => desc.to_css(dest)?, - } - - let condition = match self.condition { - Some(ref c) => c, - None => return Ok(()), - }; - - if self.media_type != MediaQueryType::All || self.qualifier.is_some() { - dest.write_str(" and ")?; - } - - condition.to_css(dest) - } -} - -impl MediaQuery { - /// Return a media query that never matches, used for when we fail to parse - /// a given media query. - pub fn never_matching() -> Self { - Self { - qualifier: Some(Qualifier::Not), - media_type: MediaQueryType::All, - condition: None, - } - } - - /// Returns whether this media query depends on the viewport. - pub fn is_viewport_dependent(&self) -> bool { - self.condition.as_ref().map_or(false, |c| { - return c - .cumulative_flags() - .contains(FeatureFlags::VIEWPORT_DEPENDENT); - }) - } - - /// Parse a media query given css input. - /// - /// Returns an error if any of the expressions is unknown. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let (qualifier, explicit_media_type) = input - .try_parse(|input| -> Result<_, ()> { - let qualifier = input.try_parse(Qualifier::parse).ok(); - let ident = input.expect_ident().map_err(|_| ())?; - let media_type = MediaQueryType::parse(&ident)?; - Ok((qualifier, Some(media_type))) - }) - .unwrap_or_default(); - - let condition = if explicit_media_type.is_none() { - Some(QueryCondition::parse(context, input, FeatureType::Media)?) - } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() { - Some(QueryCondition::parse_disallow_or( - context, - input, - FeatureType::Media, - )?) - } else { - None - }; - - let media_type = explicit_media_type.unwrap_or(MediaQueryType::All); - Ok(Self { - qualifier, - media_type, - condition, - }) - } -} - -/// <http://dev.w3.org/csswg/mediaqueries-3/#media0> -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum MediaQueryType { - /// A media type that matches every device. - All, - /// A specific media type. - Concrete(MediaType), -} - -impl MediaQueryType { - fn parse(ident: &str) -> Result<Self, ()> { - match_ignore_ascii_case! { ident, - "all" => return Ok(MediaQueryType::All), - _ => (), - }; - - // If parseable, accept this type as a concrete type. - MediaType::parse(ident).map(MediaQueryType::Concrete) - } - - /// Returns whether this media query type matches a MediaType. - pub fn matches(&self, other: MediaType) -> bool { - match *self { - MediaQueryType::All => true, - MediaQueryType::Concrete(ref known_type) => *known_type == other, - } - } -} diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs deleted file mode 100644 index 833f6f53cb9..00000000000 --- a/components/style/media_queries/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -/* 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/. */ - -//! [Media queries][mq]. -//! -//! [mq]: https://drafts.csswg.org/mediaqueries/ - -mod media_list; -mod media_query; - -pub use self::media_list::MediaList; -pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier}; - -#[cfg(feature = "gecko")] -pub use crate::gecko::media_queries::Device; -#[cfg(feature = "servo")] -pub use crate::servo::media_queries::Device; diff --git a/components/style/parallel.rs b/components/style/parallel.rs deleted file mode 100644 index 2d0e0c7ffcc..00000000000 --- a/components/style/parallel.rs +++ /dev/null @@ -1,197 +0,0 @@ -/* 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/. */ - -//! Implements parallel traversal over the DOM tree. -//! -//! This traversal is based on Rayon, and therefore its safety is largely -//! verified by the type system. -//! -//! The primary trickiness and fine print for the above relates to the -//! thread safety of the DOM nodes themselves. Accessing a DOM element -//! concurrently on multiple threads is actually mostly "safe", since all -//! the mutable state is protected by an AtomicRefCell, and so we'll -//! generally panic if something goes wrong. Still, we try to to enforce our -//! thread invariants at compile time whenever possible. As such, TNode and -//! TElement are not Send, so ordinary style system code cannot accidentally -//! share them with other threads. In the parallel traversal, we explicitly -//! invoke |unsafe { SendNode::new(n) }| to put nodes in containers that may -//! be sent to other threads. This occurs in only a handful of places and is -//! easy to grep for. At the time of this writing, there is no other unsafe -//! code in the parallel traversal. - -#![deny(missing_docs)] - -use crate::context::{StyleContext, ThreadLocalStyleContext}; -use crate::dom::{OpaqueNode, SendNode, TElement}; -use crate::scoped_tls::ScopedTLS; -use crate::traversal::{DomTraversal, PerLevelTraversalData}; -use rayon; -use std::collections::VecDeque; - -/// The minimum stack size for a thread in the styling pool, in kilobytes. -#[cfg(feature = "gecko")] -pub const STYLE_THREAD_STACK_SIZE_KB: usize = 256; - -/// The minimum stack size for a thread in the styling pool, in kilobytes. -/// Servo requires a bigger stack in debug builds. -#[cfg(feature = "servo")] -pub const STYLE_THREAD_STACK_SIZE_KB: usize = 512; - -/// The stack margin. If we get this deep in the stack, we will skip recursive -/// optimizations to ensure that there is sufficient room for non-recursive work. -/// -/// We allocate large safety margins because certain OS calls can use very large -/// amounts of stack space [1]. Reserving a larger-than-necessary stack costs us -/// address space, but if we keep our safety margin big, we will generally avoid -/// committing those extra pages, and only use them in edge cases that would -/// otherwise cause crashes. -/// -/// When measured with 128KB stacks and 40KB margin, we could support 53 -/// levels of recursion before the limiter kicks in, on x86_64-Linux [2]. When -/// we doubled the stack size, we added it all to the safety margin, so we should -/// be able to get the same amount of recursion. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1395708#c15 -/// [2] See Gecko bug 1376883 for more discussion on the measurements. -pub const STACK_SAFETY_MARGIN_KB: usize = 168; - -/// A callback to create our thread local context. This needs to be -/// out of line so we don't allocate stack space for the entire struct -/// in the caller. -#[inline(never)] -fn create_thread_local_context<'scope, E>(slot: &mut Option<ThreadLocalStyleContext<E>>) -where - E: TElement + 'scope, -{ - *slot = Some(ThreadLocalStyleContext::new()); -} - -// Sends one chunk of work to the thread-pool. -fn distribute_one_chunk<'a, 'scope, E, D>( - items: VecDeque<SendNode<E::ConcreteNode>>, - traversal_root: OpaqueNode, - work_unit_max: usize, - traversal_data: PerLevelTraversalData, - scope: &'a rayon::ScopeFifo<'scope>, - traversal: &'scope D, - tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>, -) where - E: TElement + 'scope, - D: DomTraversal<E>, -{ - scope.spawn_fifo(move |scope| { - #[cfg(feature = "gecko")] - gecko_profiler_label!(Layout, StyleComputation); - let mut tlc = tls.ensure(create_thread_local_context); - let mut context = StyleContext { - shared: traversal.shared_context(), - thread_local: &mut *tlc, - }; - style_trees( - &mut context, - items, - traversal_root, - work_unit_max, - static_prefs::pref!("layout.css.stylo-local-work-queue.in-worker") as usize, - traversal_data, - Some(scope), - traversal, - Some(tls), - ); - }) -} - -/// Distributes all items into the thread pool, in `work_unit_max` chunks. -fn distribute_work<'a, 'scope, E, D>( - mut items: VecDeque<SendNode<E::ConcreteNode>>, - traversal_root: OpaqueNode, - work_unit_max: usize, - traversal_data: PerLevelTraversalData, - scope: &'a rayon::ScopeFifo<'scope>, - traversal: &'scope D, - tls: &'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>, -) where - E: TElement + 'scope, - D: DomTraversal<E>, -{ - while items.len() > work_unit_max { - let rest = items.split_off(work_unit_max); - distribute_one_chunk( - items, - traversal_root, - work_unit_max, - traversal_data, - scope, - traversal, - tls, - ); - items = rest; - } - distribute_one_chunk( - items, - traversal_root, - work_unit_max, - traversal_data, - scope, - traversal, - tls, - ); -} - -/// Processes `discovered` items, possibly spawning work in other threads as needed. -#[inline] -pub fn style_trees<'a, 'scope, E, D>( - context: &mut StyleContext<E>, - mut discovered: VecDeque<SendNode<E::ConcreteNode>>, - traversal_root: OpaqueNode, - work_unit_max: usize, - local_queue_size: usize, - mut traversal_data: PerLevelTraversalData, - scope: Option<&'a rayon::ScopeFifo<'scope>>, - traversal: &'scope D, - tls: Option<&'scope ScopedTLS<'scope, ThreadLocalStyleContext<E>>>, -) where - E: TElement + 'scope, - D: DomTraversal<E>, -{ - let mut nodes_remaining_at_current_depth = discovered.len(); - while let Some(node) = discovered.pop_front() { - let mut children_to_process = 0isize; - traversal.process_preorder(&traversal_data, context, *node, |n| { - children_to_process += 1; - discovered.push_back(unsafe { SendNode::new(n) }); - }); - - traversal.handle_postorder_traversal(context, traversal_root, *node, children_to_process); - - nodes_remaining_at_current_depth -= 1; - - // If we have enough children at the next depth in the DOM, spawn them to a different job - // relatively soon, while keeping always at least `local_queue_size` worth of work for - // ourselves. - let discovered_children = discovered.len() - nodes_remaining_at_current_depth; - if discovered_children >= work_unit_max && - discovered.len() >= local_queue_size + work_unit_max && - scope.is_some() - { - let kept_work = std::cmp::max(nodes_remaining_at_current_depth, local_queue_size); - let mut traversal_data_copy = traversal_data.clone(); - traversal_data_copy.current_dom_depth += 1; - distribute_work( - discovered.split_off(kept_work), - traversal_root, - work_unit_max, - traversal_data_copy, - scope.unwrap(), - traversal, - tls.unwrap(), - ); - } - - if nodes_remaining_at_current_depth == 0 { - traversal_data.current_dom_depth += 1; - nodes_remaining_at_current_depth = discovered.len(); - } - } -} diff --git a/components/style/parser.rs b/components/style/parser.rs deleted file mode 100644 index 8d8d408f53f..00000000000 --- a/components/style/parser.rs +++ /dev/null @@ -1,210 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The context within which CSS code is parsed. - -use crate::context::QuirksMode; -use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; -use crate::stylesheets::{CssRuleType, CssRuleTypes, Namespaces, Origin, UrlExtraData}; -use crate::use_counters::UseCounters; -use cssparser::{Parser, SourceLocation, UnicodeRange}; -use std::borrow::Cow; -use style_traits::{OneOrMoreSeparated, ParseError, ParsingMode, Separator}; - -/// Asserts that all ParsingMode flags have a matching ParsingMode value in gecko. -#[cfg(feature = "gecko")] -#[inline] -pub fn assert_parsing_mode_match() { - use crate::gecko_bindings::structs; - - macro_rules! check_parsing_modes { - ( $( $a:ident => $b:path ),*, ) => { - if cfg!(debug_assertions) { - let mut modes = ParsingMode::all(); - $( - assert_eq!(structs::$a as usize, $b.bits() as usize, stringify!($b)); - modes.remove($b); - )* - assert_eq!(modes, ParsingMode::empty(), "all ParsingMode bits should have an assertion"); - } - } - } - - check_parsing_modes! { - ParsingMode_Default => ParsingMode::DEFAULT, - ParsingMode_AllowUnitlessLength => ParsingMode::ALLOW_UNITLESS_LENGTH, - ParsingMode_AllowAllNumericValues => ParsingMode::ALLOW_ALL_NUMERIC_VALUES, - } -} - -/// The data that the parser needs from outside in order to parse a stylesheet. -pub struct ParserContext<'a> { - /// The `Origin` of the stylesheet, whether it's a user, author or - /// user-agent stylesheet. - pub stylesheet_origin: Origin, - /// The extra data we need for resolving url values. - pub url_data: &'a UrlExtraData, - /// The current rule types, if any. - pub rule_types: CssRuleTypes, - /// The mode to use when parsing. - pub parsing_mode: ParsingMode, - /// The quirks mode of this stylesheet. - pub quirks_mode: QuirksMode, - /// The active error reporter, or none if error reporting is disabled. - error_reporter: Option<&'a dyn ParseErrorReporter>, - /// The currently active namespaces. - pub namespaces: Cow<'a, Namespaces>, - /// The use counters we want to record while parsing style rules, if any. - pub use_counters: Option<&'a UseCounters>, -} - -impl<'a> ParserContext<'a> { - /// Create a parser context. - #[inline] - pub fn new( - stylesheet_origin: Origin, - url_data: &'a UrlExtraData, - rule_type: Option<CssRuleType>, - parsing_mode: ParsingMode, - quirks_mode: QuirksMode, - namespaces: Cow<'a, Namespaces>, - error_reporter: Option<&'a dyn ParseErrorReporter>, - use_counters: Option<&'a UseCounters>, - ) -> Self { - Self { - stylesheet_origin, - url_data, - rule_types: rule_type.map(CssRuleTypes::from).unwrap_or_default(), - parsing_mode, - quirks_mode, - error_reporter, - namespaces, - use_counters, - } - } - - /// Temporarily sets the rule_type and executes the callback function, returning its result. - pub fn nest_for_rule<R>( - &mut self, - rule_type: CssRuleType, - cb: impl FnOnce(&mut Self) -> R, - ) -> R { - let old_rule_types = self.rule_types; - self.rule_types.insert(rule_type); - let r = cb(self); - self.rule_types = old_rule_types; - r - } - - /// Whether we're in a @page rule. - #[inline] - pub fn in_page_rule(&self) -> bool { - self.rule_types.contains(CssRuleType::Page) - } - - /// Get the rule type, which assumes that one is available. - pub fn rule_types(&self) -> CssRuleTypes { - self.rule_types - } - - /// Returns whether CSS error reporting is enabled. - #[inline] - pub fn error_reporting_enabled(&self) -> bool { - self.error_reporter.is_some() - } - - /// Record a CSS parse error with this context’s error reporting. - pub fn log_css_error(&self, location: SourceLocation, error: ContextualParseError) { - let error_reporter = match self.error_reporter { - Some(r) => r, - None => return, - }; - - error_reporter.report_error(self.url_data, location, error) - } - - /// Whether we're in a user-agent stylesheet. - #[inline] - pub fn in_ua_sheet(&self) -> bool { - self.stylesheet_origin == Origin::UserAgent - } - - /// Returns whether chrome-only rules should be parsed. - #[inline] - pub fn chrome_rules_enabled(&self) -> bool { - self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User - } - - /// Whether we're in a user-agent stylesheet or chrome rules are enabled. - #[inline] - pub fn in_ua_or_chrome_sheet(&self) -> bool { - self.in_ua_sheet() || self.chrome_rules_enabled() - } -} - -/// A trait to abstract parsing of a specified value given a `ParserContext` and -/// CSS input. -/// -/// This can be derived on keywords with `#[derive(Parse)]`. -/// -/// The derive code understands the following attributes on each of the variants: -/// -/// * `#[parse(aliases = "foo,bar")]` can be used to alias a value with another -/// at parse-time. -/// -/// * `#[parse(condition = "function")]` can be used to make the parsing of the -/// value conditional on `function`, which needs to fulfill -/// `fn(&ParserContext) -> bool`. -pub trait Parse: Sized { - /// Parse a value of this type. - /// - /// Returns an error on failure. - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>>; -} - -impl<T> Parse for Vec<T> -where - T: Parse + OneOrMoreSeparated, - <T as OneOrMoreSeparated>::S: Separator, -{ - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - <T as OneOrMoreSeparated>::S::parse(input, |i| T::parse(context, i)) - } -} - -impl<T> Parse for Box<T> -where - T: Parse, -{ - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - T::parse(context, input).map(Box::new) - } -} - -impl Parse for crate::OwnedStr { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(input.expect_string()?.as_ref().to_owned().into()) - } -} - -impl Parse for UnicodeRange { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(UnicodeRange::parse(input)?) - } -} diff --git a/components/style/piecewise_linear.rs b/components/style/piecewise_linear.rs deleted file mode 100644 index 84ccb7061c3..00000000000 --- a/components/style/piecewise_linear.rs +++ /dev/null @@ -1,293 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A piecewise linear function, following CSS linear easing -use crate::values::computed::Percentage; -use core::slice::Iter; -/// draft as in https://github.com/w3c/csswg-drafts/pull/6533. -use euclid::approxeq::ApproxEq; -use itertools::Itertools; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -use crate::values::CSSFloat; - -type ValueType = CSSFloat; -/// a single entry in a piecewise linear function. -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToResolvedValue, - Serialize, - Deserialize, -)] -#[repr(C)] -pub struct PiecewiseLinearFunctionEntry { - pub x: ValueType, - pub y: ValueType, -} - -impl ToCss for PiecewiseLinearFunctionEntry { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.y.to_css(dest)?; - dest.write_char(' ')?; - Percentage(self.x).to_css(dest) - } -} - -/// Representation of a piecewise linear function, a series of linear functions. -#[derive( - Default, - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToResolvedValue, - ToCss, - Serialize, - Deserialize, -)] -#[repr(C)] -#[css(comma)] -pub struct PiecewiseLinearFunction { - #[css(iterable)] - entries: crate::OwnedSlice<PiecewiseLinearFunctionEntry>, -} - -/// Parameters to define one linear stop. -pub type PiecewiseLinearFunctionBuildParameters = (CSSFloat, Option<CSSFloat>); - -impl PiecewiseLinearFunction { - /// Interpolate y value given x and two points. The linear function will be rooted at the asymptote. - fn interpolate( - x: ValueType, - prev: PiecewiseLinearFunctionEntry, - next: PiecewiseLinearFunctionEntry, - asymptote: &PiecewiseLinearFunctionEntry, - ) -> ValueType { - // Short circuit if the x is on prev or next. - // `next` point is preferred as per spec. - if x.approx_eq(&next.x) { - return next.y; - } - if x.approx_eq(&prev.x) { - return prev.y; - } - // Avoid division by zero. - if prev.x.approx_eq(&next.x) { - return next.y; - } - let slope = (next.y - prev.y) / (next.x - prev.x); - return slope * (x - asymptote.x) + asymptote.y; - } - - /// Get the y value of the piecewise linear function given the x value, as per - /// https://drafts.csswg.org/css-easing-2/#linear-easing-function-output - pub fn at(&self, x: ValueType) -> ValueType { - if !x.is_finite() { - return if x > 0.0 { 1.0 } else { 0.0 }; - } - if self.entries.is_empty() { - // Implied y = x, as per spec. - return x; - } - if self.entries.len() == 1 { - // Implied y = <constant>, as per spec. - return self.entries[0].y; - } - // Spec dictates the valid input domain is [0, 1]. Outside of this range, the output - // should be calculated as if the slopes at start and end extend to infinity. However, if the - // start/end have two points of the same position, the line should extend along the x-axis. - // The function doesn't have to cover the input domain, in which case the extension logic - // applies even if the input falls in the input domain. - // Also, we're guaranteed to have at least two elements at this point. - if x < self.entries[0].x { - return Self::interpolate(x, self.entries[0], self.entries[1], &self.entries[0]); - } - let mut rev_iter = self.entries.iter().rev(); - let last = rev_iter.next().unwrap(); - if x >= last.x { - let second_last = rev_iter.next().unwrap(); - return Self::interpolate(x, *second_last, *last, last); - } - - // Now we know the input sits within the domain explicitly defined by our function. - for (point_b, point_a) in self.entries.iter().rev().tuple_windows() { - // Need to let point A be the _last_ point where its x is less than the input x, - // hence the reverse traversal. - if x < point_a.x { - continue; - } - return Self::interpolate(x, *point_a, *point_b, point_a); - } - unreachable!("Input is supposed to be within the entries' min & max!"); - } - - /// Create the piecewise linear function from an iterator that generates the parameter tuple. - pub fn from_iter<Iter>(iter: Iter) -> Self - where - Iter: Iterator<Item = PiecewiseLinearFunctionBuildParameters> + ExactSizeIterator, - { - let mut builder = PiecewiseLinearFunctionBuilder::with_capacity(iter.len()); - for (y, x_start) in iter { - builder = builder.push(y, x_start); - } - builder.build() - } - - #[allow(missing_docs)] - pub fn iter(&self) -> Iter<PiecewiseLinearFunctionEntry> { - self.entries.iter() - } -} - -/// Entry of a piecewise linear function while building, where the calculation of x value can be deferred. -#[derive(Clone, Copy)] -struct BuildEntry { - x: Option<ValueType>, - y: ValueType, -} - -/// Builder object to generate a linear function. -#[derive(Default)] -pub struct PiecewiseLinearFunctionBuilder { - largest_x: Option<ValueType>, - smallest_x: Option<ValueType>, - entries: Vec<BuildEntry>, -} - -impl PiecewiseLinearFunctionBuilder { - #[allow(missing_docs)] - pub fn new() -> Self { - PiecewiseLinearFunctionBuilder::default() - } - - /// Create a builder for a known amount of linear stop entries. - pub fn with_capacity(len: usize) -> Self { - PiecewiseLinearFunctionBuilder { - largest_x: None, - smallest_x: None, - entries: Vec::with_capacity(len), - } - } - - fn create_entry(&mut self, y: ValueType, x: Option<ValueType>) { - let x = match x { - Some(x) if x.is_finite() => x, - _ if self.entries.is_empty() => 0.0, // First x is 0 if not specified (Or not finite) - _ => { - self.entries.push(BuildEntry { x: None, y }); - return; - }, - }; - // Specified x value cannot regress, as per spec. - let x = match self.largest_x { - Some(largest_x) => x.max(largest_x), - None => x, - }; - self.largest_x = Some(x); - // Whatever we see the earliest is the smallest value. - if self.smallest_x.is_none() { - self.smallest_x = Some(x); - } - self.entries.push(BuildEntry { x: Some(x), y }); - } - - /// Add a new entry into the piecewise linear function with specified y value. - /// If the start x value is given, that is where the x value will be. Otherwise, - /// the x value is calculated later. If the end x value is specified, a flat segment - /// is generated. If start x value is not specified but end x is, it is treated as - /// start x. - pub fn push(mut self, y: CSSFloat, x_start: Option<CSSFloat>) -> Self { - self.create_entry(y, x_start); - self - } - - /// Finish building the piecewise linear function by resolving all undefined x values, - /// then return the result. - pub fn build(mut self) -> PiecewiseLinearFunction { - if self.entries.is_empty() { - return PiecewiseLinearFunction::default(); - } - if self.entries.len() == 1 { - // Don't bother resolving anything. - return PiecewiseLinearFunction { - entries: crate::OwnedSlice::from_slice(&[PiecewiseLinearFunctionEntry { - x: 0., - y: self.entries[0].y, - }]), - }; - } - // Guaranteed at least two elements. - // Start element's x value should've been assigned when the first value was pushed. - debug_assert!( - self.entries[0].x.is_some(), - "Expected an entry with x defined!" - ); - // Spec asserts that if the last entry does not have an x value, it is assigned the largest seen x value. - self.entries - .last_mut() - .unwrap() - .x - .get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0)); - // Now we have at least two elements with x values, with start & end x values guaranteed. - - let mut result = Vec::with_capacity(self.entries.len()); - result.push(PiecewiseLinearFunctionEntry { - x: self.entries[0].x.unwrap(), - y: self.entries[0].y, - }); - for (i, e) in self.entries.iter().enumerate().skip(1) { - if e.x.is_none() { - // Need to calculate x values by first finding an entry with the first - // defined x value (Guaranteed to exist as the list end has it defined). - continue; - } - // x is defined for this element. - let divisor = i - result.len() + 1; - // Any element(s) with undefined x to assign? - if divisor != 1 { - // Have at least one element in result at all times. - let start_x = result.last().unwrap().x; - let increment = (e.x.unwrap() - start_x) / divisor as ValueType; - // Grab every element with undefined x to this point, which starts at the end of the result - // array, and ending right before the current index. Then, assigned the evenly divided - // x values. - result.extend( - self.entries[result.len()..i] - .iter() - .enumerate() - .map(|(j, e)| { - debug_assert!(e.x.is_none(), "Expected an entry with x undefined!"); - PiecewiseLinearFunctionEntry { - x: increment * (j + 1) as ValueType + start_x, - y: e.y, - } - }), - ); - } - result.push(PiecewiseLinearFunctionEntry { - x: e.x.unwrap(), - y: e.y, - }); - } - debug_assert_eq!( - result.len(), - self.entries.len(), - "Should've mapped one-to-one!" - ); - PiecewiseLinearFunction { - entries: result.into(), - } - } -} diff --git a/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl b/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl Binary files differdeleted file mode 100644 index 9593025a473..00000000000 --- a/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl +++ /dev/null diff --git a/components/style/properties/build.py b/components/style/properties/build.py deleted file mode 100644 index c03d9023a7e..00000000000 --- a/components/style/properties/build.py +++ /dev/null @@ -1,172 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at https://mozilla.org/MPL/2.0/. - -import json -import os.path -import re -import sys - -BASE = os.path.dirname(__file__.replace("\\", "/")) -sys.path.insert(0, os.path.join(BASE, "Mako-1.1.2-py2.py3-none-any.whl")) -sys.path.insert(0, BASE) # For importing `data.py` - -from mako import exceptions -from mako.lookup import TemplateLookup -from mako.template import Template - -import data - -RE_PYTHON_ADDR = re.compile(r"<.+? object at 0x[0-9a-fA-F]+>") - -OUT_DIR = os.environ.get("OUT_DIR", "") - -STYLE_STRUCT_LIST = [ - "background", - "border", - "box", - "column", - "counters", - "effects", - "font", - "inherited_box", - "inherited_svg", - "inherited_table", - "inherited_text", - "inherited_ui", - "list", - "margin", - "outline", - "page", - "padding", - "position", - "svg", - "table", - "text", - "ui", - "xul", -] - - -def main(): - usage = ( - "Usage: %s [ servo | gecko ] [ style-crate | geckolib <template> | html ]" - % sys.argv[0] - ) - if len(sys.argv) < 3: - abort(usage) - engine = sys.argv[1] - output = sys.argv[2] - - if engine not in ["servo", "gecko"] or output not in [ - "style-crate", - "geckolib", - "html", - ]: - abort(usage) - - properties = data.PropertiesData(engine=engine) - files = {} - for kind in ["longhands", "shorthands"]: - files[kind] = {} - for struct in STYLE_STRUCT_LIST: - file_name = os.path.join(BASE, kind, "{}.mako.rs".format(struct)) - if kind == "shorthands" and not os.path.exists(file_name): - files[kind][struct] = "" - continue - files[kind][struct] = render( - file_name, - engine=engine, - data=properties, - ) - properties_template = os.path.join(BASE, "properties.mako.rs") - files["properties"] = render( - properties_template, - engine=engine, - data=properties, - __file__=properties_template, - OUT_DIR=OUT_DIR, - ) - if output == "style-crate": - write(OUT_DIR, "properties.rs", files["properties"]) - for kind in ["longhands", "shorthands"]: - for struct in files[kind]: - write( - os.path.join(OUT_DIR, kind), - "{}.rs".format(struct), - files[kind][struct], - ) - - if engine == "gecko": - template = os.path.join(BASE, "gecko.mako.rs") - rust = render(template, data=properties) - write(OUT_DIR, "gecko_properties.rs", rust) - - if engine == "servo": - properties_dict = { - kind: { - p.name: {"pref": getattr(p, "servo_pref")} - for prop in properties_list - if prop.enabled_in_content() - for p in [prop] + prop.aliases - } - for kind, properties_list in [ - ("longhands", properties.longhands), - ("shorthands", properties.shorthands), - ] - } - as_html = render( - os.path.join(BASE, "properties.html.mako"), properties=properties_dict - ) - as_json = json.dumps(properties_dict, indent=4, sort_keys=True) - doc_servo = os.path.join(BASE, "..", "..", "..", "target", "doc", "servo") - write(doc_servo, "css-properties.html", as_html) - write(doc_servo, "css-properties.json", as_json) - write(OUT_DIR, "css-properties.json", as_json) - elif output == "geckolib": - if len(sys.argv) < 4: - abort(usage) - template = sys.argv[3] - header = render(template, data=properties) - sys.stdout.write(header) - - -def abort(message): - print(message, file=sys.stderr) - sys.exit(1) - - -def render(filename, **context): - try: - lookup = TemplateLookup( - directories=[BASE], input_encoding="utf8", strict_undefined=True - ) - template = Template( - open(filename, "rb").read(), - filename=filename, - input_encoding="utf8", - lookup=lookup, - strict_undefined=True, - ) - # Uncomment to debug generated Python code: - # write("/tmp", "mako_%s.py" % os.path.basename(filename), template.code) - return template.render(**context) - except Exception: - # Uncomment to see a traceback in generated Python code: - # raise - abort(exceptions.text_error_template().render()) - - -def write(directory, filename, content): - if not os.path.exists(directory): - os.makedirs(directory) - full_path = os.path.join(directory, filename) - open(full_path, "w", encoding="utf-8").write(content) - - python_addr = RE_PYTHON_ADDR.search(content) - if python_addr: - abort('Found "{}" in {} ({})'.format(python_addr.group(0), filename, full_path)) - - -if __name__ == "__main__": - main() diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs deleted file mode 100644 index f9951e2cfc5..00000000000 --- a/components/style/properties/cascade.rs +++ /dev/null @@ -1,1258 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The main cascading algorithm of the style system. - -use crate::applicable_declarations::CascadePriority; -use crate::color::AbsoluteColor; -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::QuirksMode; -use crate::custom_properties::CustomPropertiesBuilder; -use crate::dom::TElement; -use crate::logical_geometry::WritingMode; -use crate::media_queries::Device; -use crate::properties::declaration_block::{DeclarationImportanceIterator, Importance}; -use crate::properties::generated::{ - CSSWideKeyword, ComputedValues, LonghandId, LonghandIdSet, PropertyDeclaration, - PropertyDeclarationId, PropertyFlags, ShorthandsWithPropertyReferencesCache, StyleBuilder, - CASCADE_PROPERTY, -}; -use crate::rule_cache::{RuleCache, RuleCacheConditions}; -use crate::rule_tree::{CascadeLevel, StrongRuleNode}; -use crate::selector_parser::PseudoElement; -use crate::shared_lock::StylesheetGuards; -use crate::style_adjuster::StyleAdjuster; -use crate::stylesheets::container_rule::ContainerSizeQuery; -use crate::stylesheets::{layer_rule::LayerOrder, Origin}; -#[cfg(feature = "gecko")] -use crate::values::specified::length::FontBaseSize; -use crate::values::{computed, specified}; -use fxhash::FxHashMap; -use servo_arc::Arc; -use smallvec::SmallVec; -use std::borrow::Cow; -#[cfg(feature = "gecko")] -use std::mem; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum CanHaveLogicalProperties { - No, - Yes, -} - -/// Performs the CSS cascade, computing new styles for an element from its parent style. -/// -/// The arguments are: -/// -/// * `device`: Used to get the initial viewport and other external state. -/// -/// * `rule_node`: The rule node in the tree that represent the CSS rules that -/// matched. -/// -/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node. -/// -/// Returns the computed values. -/// * `flags`: Various flags. -/// -pub fn cascade<E>( - device: &Device, - pseudo: Option<&PseudoElement>, - rule_node: &StrongRuleNode, - guards: &StylesheetGuards, - originating_element_style: Option<&ComputedValues>, - parent_style: Option<&ComputedValues>, - parent_style_ignoring_first_line: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - visited_rules: Option<&StrongRuleNode>, - cascade_input_flags: ComputedValueFlags, - quirks_mode: QuirksMode, - rule_cache: Option<&RuleCache>, - rule_cache_conditions: &mut RuleCacheConditions, - element: Option<E>, -) -> Arc<ComputedValues> -where - E: TElement, -{ - cascade_rules( - device, - pseudo, - rule_node, - guards, - originating_element_style, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - CascadeMode::Unvisited { visited_rules }, - cascade_input_flags, - quirks_mode, - rule_cache, - rule_cache_conditions, - element, - ) -} - -struct DeclarationIterator<'a> { - // Global to the iteration. - guards: &'a StylesheetGuards<'a>, - restriction: Option<PropertyFlags>, - // The rule we're iterating over. - current_rule_node: Option<&'a StrongRuleNode>, - // Per rule state. - declarations: DeclarationImportanceIterator<'a>, - origin: Origin, - importance: Importance, - priority: CascadePriority, -} - -impl<'a> DeclarationIterator<'a> { - #[inline] - fn new( - rule_node: &'a StrongRuleNode, - guards: &'a StylesheetGuards, - pseudo: Option<&PseudoElement>, - ) -> Self { - let restriction = pseudo.and_then(|p| p.property_restriction()); - let mut iter = Self { - guards, - current_rule_node: Some(rule_node), - origin: Origin::UserAgent, - importance: Importance::Normal, - priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), - declarations: DeclarationImportanceIterator::default(), - restriction, - }; - iter.update_for_node(rule_node); - iter - } - - fn update_for_node(&mut self, node: &'a StrongRuleNode) { - self.priority = node.cascade_priority(); - let level = self.priority.cascade_level(); - self.origin = level.origin(); - self.importance = level.importance(); - let guard = match self.origin { - Origin::Author => self.guards.author, - Origin::User | Origin::UserAgent => self.guards.ua_or_user, - }; - self.declarations = match node.style_source() { - Some(source) => source.read(guard).declaration_importance_iter(), - None => DeclarationImportanceIterator::default(), - }; - } -} - -impl<'a> Iterator for DeclarationIterator<'a> { - type Item = (&'a PropertyDeclaration, CascadePriority); - - #[inline] - fn next(&mut self) -> Option<Self::Item> { - loop { - if let Some((decl, importance)) = self.declarations.next_back() { - if self.importance != importance { - continue; - } - - if let Some(restriction) = self.restriction { - // decl.id() is either a longhand or a custom - // property. Custom properties are always allowed, but - // longhands are only allowed if they have our - // restriction flag set. - if let PropertyDeclarationId::Longhand(id) = decl.id() { - if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { - continue; - } - } - } - - return Some((decl, self.priority)); - } - - let next_node = self.current_rule_node.take()?.parent()?; - self.current_rule_node = Some(next_node); - self.update_for_node(next_node); - } - } -} - -fn cascade_rules<E>( - device: &Device, - pseudo: Option<&PseudoElement>, - rule_node: &StrongRuleNode, - guards: &StylesheetGuards, - originating_element_style: Option<&ComputedValues>, - parent_style: Option<&ComputedValues>, - parent_style_ignoring_first_line: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - cascade_mode: CascadeMode, - cascade_input_flags: ComputedValueFlags, - quirks_mode: QuirksMode, - rule_cache: Option<&RuleCache>, - rule_cache_conditions: &mut RuleCacheConditions, - element: Option<E>, -) -> Arc<ComputedValues> -where - E: TElement, -{ - debug_assert_eq!( - parent_style.is_some(), - parent_style_ignoring_first_line.is_some() - ); - apply_declarations( - device, - pseudo, - rule_node, - guards, - DeclarationIterator::new(rule_node, guards, pseudo), - originating_element_style, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - cascade_mode, - cascade_input_flags, - quirks_mode, - rule_cache, - rule_cache_conditions, - element, - ) -} - -/// Whether we're cascading for visited or unvisited styles. -#[derive(Clone, Copy)] -pub enum CascadeMode<'a> { - /// We're cascading for unvisited styles. - Unvisited { - /// The visited rules that should match the visited style. - visited_rules: Option<&'a StrongRuleNode>, - }, - /// We're cascading for visited styles. - Visited { - /// The writing mode of our unvisited style, needed to correctly resolve - /// logical properties.. - writing_mode: WritingMode, - }, -} - -/// NOTE: This function expects the declaration with more priority to appear -/// first. -pub fn apply_declarations<'a, E, I>( - device: &Device, - pseudo: Option<&PseudoElement>, - rules: &StrongRuleNode, - guards: &StylesheetGuards, - iter: I, - originating_element_style: Option<&ComputedValues>, - parent_style: Option<&ComputedValues>, - parent_style_ignoring_first_line: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - cascade_mode: CascadeMode, - cascade_input_flags: ComputedValueFlags, - quirks_mode: QuirksMode, - rule_cache: Option<&RuleCache>, - rule_cache_conditions: &mut RuleCacheConditions, - element: Option<E>, -) -> Arc<ComputedValues> -where - E: TElement, - I: Iterator<Item = (&'a PropertyDeclaration, CascadePriority)>, -{ - debug_assert_eq!( - originating_element_style.is_some(), - element.is_some() && pseudo.is_some() - ); - debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); - 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 inherited_style = parent_style.unwrap_or(device.default_computed_values()); - - let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new(); - let mut referenced_properties = LonghandIdSet::default(); - let custom_properties = { - let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device); - - for (declaration, priority) in iter { - declarations.push((declaration, priority)); - if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, priority); - } else { - referenced_properties.insert(declaration.id().as_longhand().unwrap()); - } - } - - builder.build() - }; - - let is_root_element = pseudo.is_none() && element.map_or(false, |e| e.is_root()); - let container_size_query = - ContainerSizeQuery::for_option_element(element, originating_element_style); - - let mut context = computed::Context::new( - // We'd really like to own the rules here to avoid refcount traffic, but - // animation's usage of `apply_declarations` make this tricky. See bug - // 1375525. - StyleBuilder::new( - device, - parent_style, - parent_style_ignoring_first_line, - pseudo, - Some(rules.clone()), - custom_properties, - is_root_element, - ), - quirks_mode, - rule_cache_conditions, - container_size_query, - ); - - context.style().add_flags(cascade_input_flags); - - let using_cached_reset_properties; - let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties); - let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); - - let properties_to_apply = match cascade.cascade_mode { - CascadeMode::Visited { writing_mode } => { - cascade.context.builder.writing_mode = writing_mode; - // We never insert visited styles into the cache so we don't need to - // try looking it up. It also wouldn't be super-profitable, only a - // handful reset properties are non-inherited. - using_cached_reset_properties = false; - LonghandIdSet::visited_dependent() - }, - CascadeMode::Unvisited { visited_rules } => { - if cascade.apply_properties( - CanHaveLogicalProperties::No, - LonghandIdSet::writing_mode_group(), - declarations.iter().cloned(), - &mut shorthand_cache, - ) { - cascade.compute_writing_mode(); - } - - if cascade.apply_properties( - CanHaveLogicalProperties::No, - LonghandIdSet::fonts_and_color_group(), - declarations.iter().cloned(), - &mut shorthand_cache, - ) { - cascade.fixup_font_stuff(); - } - - if let Some(visited_rules) = visited_rules { - cascade.compute_visited_style_if_needed( - element, - originating_element_style, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - visited_rules, - guards, - ); - } - - using_cached_reset_properties = - cascade.try_to_use_cached_reset_properties(rule_cache, guards); - - if using_cached_reset_properties { - LonghandIdSet::late_group_only_inherited() - } else { - LonghandIdSet::late_group() - } - }, - }; - - cascade.apply_properties( - CanHaveLogicalProperties::Yes, - properties_to_apply, - declarations.iter().cloned(), - &mut shorthand_cache, - ); - - cascade.finished_applying_properties(); - - context.builder.clear_modified_reset(); - - if matches!(cascade_mode, CascadeMode::Unvisited { .. }) { - StyleAdjuster::new(&mut context.builder) - .adjust(layout_parent_style.unwrap_or(inherited_style), element); - } - - if context.builder.modified_reset() || using_cached_reset_properties { - // If we adjusted any reset structs, we can't cache this ComputedValues. - // - // Also, if we re-used existing reset structs, don't bother caching it - // back again. (Aside from being wasted effort, it will be wrong, since - // context.rule_cache_conditions won't be set appropriately if we didn't - // compute those reset properties.) - context.rule_cache_conditions.borrow_mut().set_uncacheable(); - } - - context.builder.build() -} - -/// For ignored colors mode, we sometimes want to do something equivalent to -/// "revert-or-initial", where we `revert` for a given origin, but then apply a -/// given initial value if nothing in other origins did override it. -/// -/// This is a bit of a clunky way of achieving this. -type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>; - -fn tweak_when_ignoring_colors( - context: &computed::Context, - longhand_id: LonghandId, - origin: Origin, - declaration: &mut Cow<PropertyDeclaration>, - declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden, -) { - use crate::values::computed::ToComputedValue; - use crate::values::specified::Color; - - if !longhand_id.ignored_when_document_colors_disabled() { - return; - } - - let is_ua_or_user_rule = matches!(origin, Origin::User | Origin::UserAgent); - if is_ua_or_user_rule { - return; - } - - // Always honor colors if forced-color-adjust is set to none. - #[cfg(feature = "gecko")] { - let forced = context - .builder - .get_inherited_text() - .clone_forced_color_adjust(); - if forced == computed::ForcedColorAdjust::None { - return; - } - } - - // Don't override background-color on ::-moz-color-swatch. It is set as an - // author style (via the style attribute), but it's pretty important for it - // to show up for obvious reasons :) - if context - .builder - .pseudo - .map_or(false, |p| p.is_color_swatch()) && - longhand_id == LonghandId::BackgroundColor - { - return; - } - - fn alpha_channel(color: &Color, context: &computed::Context) -> f32 { - // We assume here currentColor is opaque. - color - .to_computed_value(context) - .resolve_to_absolute(&AbsoluteColor::black()) - .alpha - } - - // A few special-cases ahead. - match **declaration { - PropertyDeclaration::BackgroundColor(ref color) => { - // We honor system colors and transparent colors unconditionally. - // - // NOTE(emilio): We honor transparent unconditionally, like we do - // for color, even though it causes issues like bug 1625036. The - // reasoning is that the conditions that trigger that (having - // mismatched widget and default backgrounds) are both uncommon, and - // broken in other applications as well, and not honoring - // transparent makes stuff uglier or break unconditionally - // (bug 1666059, bug 1755713). - if color.honored_in_forced_colors_mode(/* allow_transparent = */ true) { - return; - } - // For background-color, we revert or initial-with-preserved-alpha - // otherwise, this is needed to preserve semi-transparent - // backgrounds. - let alpha = alpha_channel(color, context); - if alpha == 0.0 { - return; - } - let mut color = context.builder.device.default_background_color(); - color.alpha = alpha; - declarations_to_apply_unless_overriden - .push(PropertyDeclaration::BackgroundColor(color.into())) - }, - PropertyDeclaration::Color(ref color) => { - // We honor color: transparent and system colors. - if color - .0 - .honored_in_forced_colors_mode(/* allow_transparent = */ true) - { - return; - } - // If the inherited color would be transparent, but we would - // override this with a non-transparent color, then override it with - // the default color. Otherwise just let it inherit through. - if context - .builder - .get_parent_inherited_text() - .clone_color() - .alpha == - 0.0 - { - let color = context.builder.device.default_color(); - declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( - specified::ColorPropertyValue(color.into()), - )) - } - }, - // We honor url background-images if backplating. - #[cfg(feature = "gecko")] - PropertyDeclaration::BackgroundImage(ref bkg) => { - use crate::values::generics::image::Image; - if static_prefs::pref!("browser.display.permit_backplate") { - if bkg - .0 - .iter() - .all(|image| matches!(*image, Image::Url(..) | Image::None)) - { - return; - } - } - }, - _ => { - // We honor system colors more generally for all colors. - // - // We used to honor transparent but that causes accessibility - // regressions like bug 1740924. - // - // NOTE(emilio): This doesn't handle caret-color and accent-color - // because those use a slightly different syntax (<color> | auto for - // example). - // - // That's probably fine though, as using a system color for - // caret-color doesn't make sense (using currentColor is fine), and - // we ignore accent-color in high-contrast-mode anyways. - if let Some(color) = declaration.color_value() { - if color.honored_in_forced_colors_mode(/* allow_transparent = */ false) { - return; - } - } - }, - } - - *declaration.to_mut() = - PropertyDeclaration::css_wide_keyword(longhand_id, CSSWideKeyword::Revert); -} - -struct Cascade<'a, 'b: 'a> { - context: &'a mut computed::Context<'b>, - cascade_mode: CascadeMode<'a>, - /// All the properties that have a declaration in the cascade. - referenced: &'a LonghandIdSet, - seen: LonghandIdSet, - author_specified: LonghandIdSet, - reverted_set: LonghandIdSet, - reverted: FxHashMap<LonghandId, (CascadePriority, bool)>, -} - -impl<'a, 'b: 'a> Cascade<'a, 'b> { - fn new( - context: &'a mut computed::Context<'b>, - cascade_mode: CascadeMode<'a>, - referenced: &'a LonghandIdSet, - ) -> Self { - Self { - context, - cascade_mode, - referenced, - seen: LonghandIdSet::default(), - author_specified: LonghandIdSet::default(), - reverted_set: Default::default(), - reverted: Default::default(), - } - } - - fn substitute_variables_if_needed<'decl, 'cache>( - &mut self, - declaration: &'decl PropertyDeclaration, - cache: &'cache mut ShorthandsWithPropertyReferencesCache, - ) -> Cow<'decl, PropertyDeclaration> - where - 'cache: 'decl, - { - let declaration = match *declaration { - PropertyDeclaration::WithVariables(ref declaration) => declaration, - ref d => return Cow::Borrowed(d), - }; - - if !declaration.id.inherited() { - self.context - .rule_cache_conditions - .borrow_mut() - .set_uncacheable(); - - // NOTE(emilio): We only really need to add the `display` / - // `content` flag if the CSS variable has not been specified on our - // declarations, but we don't have that information at this point, - // and it doesn't seem like an important enough optimization to - // warrant it. - match declaration.id { - LonghandId::Display => { - self.context - .builder - .add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); - }, - LonghandId::Content => { - self.context - .builder - .add_flags(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE); - }, - _ => {}, - } - } - - declaration.value.substitute_variables( - declaration.id, - self.context.builder.writing_mode, - self.context.builder.custom_properties(), - self.context.quirks_mode, - self.context.device(), - cache, - ) - } - - #[inline(always)] - fn apply_declaration(&mut self, longhand_id: LonghandId, declaration: &PropertyDeclaration) { - // We could (and used to) use a pattern match here, but that bloats this - // function to over 100K of compiled code! - // - // To improve i-cache behavior, we outline the individual functions and - // use virtual dispatch instead. - let discriminant = longhand_id as usize; - (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context); - } - - fn apply_properties<'decls, I>( - &mut self, - can_have_logical_properties: CanHaveLogicalProperties, - properties_to_apply: &'a LonghandIdSet, - declarations: I, - mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, - ) -> bool - where - I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>, - { - if !self.referenced.contains_any(properties_to_apply) { - return false; - } - - let can_have_logical_properties = - can_have_logical_properties == CanHaveLogicalProperties::Yes; - - let ignore_colors = !self.context.builder.device.use_document_colors(); - let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); - - for (declaration, priority) in declarations { - let origin = priority.cascade_level().origin(); - - let declaration_id = declaration.id(); - let longhand_id = match declaration_id { - PropertyDeclarationId::Longhand(id) => id, - PropertyDeclarationId::Custom(..) => continue, - }; - - if !properties_to_apply.contains(longhand_id) { - continue; - } - - debug_assert!(can_have_logical_properties || !longhand_id.is_logical()); - let physical_longhand_id = if can_have_logical_properties { - longhand_id.to_physical(self.context.builder.writing_mode) - } else { - longhand_id - }; - - if self.seen.contains(physical_longhand_id) { - continue; - } - - if self.reverted_set.contains(physical_longhand_id) { - if let Some(&(reverted_priority, is_origin_revert)) = - self.reverted.get(&physical_longhand_id) - { - if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { - continue; - } - } - } - - let mut declaration = - self.substitute_variables_if_needed(declaration, &mut shorthand_cache); - - // When document colors are disabled, do special handling of - // properties that are marked as ignored in that mode. - if ignore_colors { - tweak_when_ignoring_colors( - &self.context, - longhand_id, - origin, - &mut declaration, - &mut declarations_to_apply_unless_overriden, - ); - debug_assert_eq!( - declaration.id(), - PropertyDeclarationId::Longhand(longhand_id), - "Shouldn't change the declaration id!", - ); - } - - let is_unset = match declaration.get_css_wide_keyword() { - Some(keyword) => match keyword { - CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { - let origin_revert = keyword == CSSWideKeyword::Revert; - // We intentionally don't want to insert it into - // `self.seen`, `reverted` takes care of rejecting other - // declarations as needed. - self.reverted_set.insert(physical_longhand_id); - self.reverted - .insert(physical_longhand_id, (priority, origin_revert)); - continue; - }, - CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => longhand_id.inherited(), - CSSWideKeyword::Initial => !longhand_id.inherited(), - }, - None => false, - }; - - self.seen.insert(physical_longhand_id); - if origin == Origin::Author { - self.author_specified.insert(physical_longhand_id); - } - - if is_unset { - continue; - } - - // FIXME(emilio): We should avoid generating code for logical - // longhands and just use the physical ones, then rename - // physical_longhand_id to just longhand_id. - self.apply_declaration(longhand_id, &*declaration); - } - - if ignore_colors { - for declaration in declarations_to_apply_unless_overriden.iter() { - let longhand_id = match declaration.id() { - PropertyDeclarationId::Longhand(id) => id, - PropertyDeclarationId::Custom(..) => unreachable!(), - }; - debug_assert!(!longhand_id.is_logical()); - if self.seen.contains(longhand_id) { - continue; - } - self.apply_declaration(longhand_id, declaration); - } - } - - true - } - - fn compute_writing_mode(&mut self) { - debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); - self.context.builder.writing_mode = - WritingMode::new(self.context.builder.get_inherited_box()) - } - - fn compute_visited_style_if_needed<E>( - &mut self, - element: Option<E>, - originating_element_style: Option<&ComputedValues>, - parent_style: Option<&ComputedValues>, - parent_style_ignoring_first_line: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - visited_rules: &StrongRuleNode, - guards: &StylesheetGuards, - ) where - E: TElement, - { - debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); - let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link(); - - macro_rules! visited_parent { - ($parent:expr) => { - if is_link { - $parent - } else { - $parent.map(|p| p.visited_style().unwrap_or(p)) - } - }; - } - - let writing_mode = self.context.builder.writing_mode; - - // We could call apply_declarations directly, but that'd cause - // another instantiation of this function which is not great. - let style = cascade_rules( - self.context.builder.device, - self.context.builder.pseudo, - visited_rules, - guards, - visited_parent!(originating_element_style), - visited_parent!(parent_style), - visited_parent!(parent_style_ignoring_first_line), - visited_parent!(layout_parent_style), - CascadeMode::Visited { writing_mode }, - // Cascade input flags don't matter for the visited style, they are - // in the main (unvisited) style. - Default::default(), - self.context.quirks_mode, - // The rule cache doesn't care about caching :visited - // styles, we cache the unvisited style instead. We still do - // need to set the caching dependencies properly if present - // though, so the cache conditions need to match. - None, // rule_cache - &mut *self.context.rule_cache_conditions.borrow_mut(), - element, - ); - self.context.builder.visited_style = Some(style); - } - - fn finished_applying_properties(&mut self) { - let builder = &mut self.context.builder; - - #[cfg(feature = "gecko")] - { - if let Some(bg) = builder.get_background_if_mutated() { - bg.fill_arrays(); - } - - if let Some(svg) = builder.get_svg_if_mutated() { - svg.fill_arrays(); - } - } - - if self - .author_specified - .contains_any(LonghandIdSet::border_background_properties()) - { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); - } - - if self.author_specified.contains(LonghandId::FontFamily) { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY); - } - - if self.author_specified.contains(LonghandId::LetterSpacing) { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING); - } - - if self.author_specified.contains(LonghandId::WordSpacing) { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING); - } - - #[cfg(feature = "gecko")] - if self - .author_specified - .contains(LonghandId::FontSynthesisWeight) - { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT); - } - - #[cfg(feature = "gecko")] - if self - .author_specified - .contains(LonghandId::FontSynthesisStyle) - { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE); - } - - #[cfg(feature = "servo")] - { - if let Some(font) = builder.get_font_if_mutated() { - font.compute_font_hash(); - } - } - } - - fn try_to_use_cached_reset_properties( - &mut self, - cache: Option<&'b RuleCache>, - guards: &StylesheetGuards, - ) -> bool { - let cache = match cache { - Some(cache) => cache, - None => return false, - }; - - let builder = &mut self.context.builder; - - let cached_style = match cache.find(guards, &builder) { - Some(style) => style, - None => return false, - }; - - builder.copy_reset_from(cached_style); - - // We're using the same reset style as another element, and we'll skip - // applying the relevant properties. So we need to do the relevant - // bookkeeping here to keep these bits correct. - // - // Note that the border/background properties are non-inherited, so we - // don't need to do anything else other than just copying the bits over. - // - // When using this optimization, we also need to copy whether the old - // style specified viewport units / used font-relative lengths, this one - // would as well. It matches the same rules, so it is the right thing - // to do anyways, even if it's only used on inherited properties. - let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | - ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS | - ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS | - ComputedValueFlags::USES_VIEWPORT_UNITS; - builder.add_flags(cached_style.flags & bits_to_copy); - - true - } - - /// The initial font depends on the current lang group so we may need to - /// recompute it if the language changed. - #[inline] - #[cfg(feature = "gecko")] - fn recompute_initial_font_family_if_needed(&mut self) { - use crate::gecko_bindings::bindings; - use crate::values::computed::font::FontFamily; - - if !self.seen.contains(LonghandId::XLang) { - return; - } - - let builder = &mut self.context.builder; - let default_font_type = { - let font = builder.get_font(); - - if !font.mFont.family.is_initial { - return; - } - - let default_font_type = unsafe { - bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( - builder.device.document(), - font.mLanguage.mRawPtr, - ) - }; - - let initial_generic = font.mFont.family.families.single_generic(); - debug_assert!( - initial_generic.is_some(), - "Initial font should be just one generic font" - ); - if initial_generic == Some(default_font_type) { - return; - } - - default_font_type - }; - - // NOTE: Leaves is_initial untouched. - builder.mutate_font().mFont.family.families = - FontFamily::generic(default_font_type).families.clone(); - } - - /// Prioritize user fonts if needed by pref. - #[inline] - #[cfg(feature = "gecko")] - fn prioritize_user_fonts_if_needed(&mut self) { - use crate::gecko_bindings::bindings; - - if !self.seen.contains(LonghandId::FontFamily) { - return; - } - - if static_prefs::pref!("browser.display.use_document_fonts") != 0 { - return; - } - - let builder = &mut self.context.builder; - let default_font_type = { - let font = builder.get_font(); - - if font.mFont.family.is_system_font { - return; - } - - if !font.mFont.family.families.needs_user_font_prioritization() { - return; - } - - unsafe { - bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( - builder.device.document(), - font.mLanguage.mRawPtr, - ) - } - }; - - let font = builder.mutate_font(); - font.mFont - .family - .families - .prioritize_first_generic_or_prepend(default_font_type); - } - - /// Some keyword sizes depend on the font family and language. - #[cfg(feature = "gecko")] - fn recompute_keyword_font_size_if_needed(&mut self) { - use crate::values::computed::ToComputedValue; - - if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { - return; - } - - let new_size = { - let font = self.context.builder.get_font(); - let info = font.clone_font_size().keyword_info; - let new_size = match info.kw { - specified::FontSizeKeyword::None => return, - _ => { - self.context.for_non_inherited_property = false; - specified::FontSize::Keyword(info).to_computed_value(self.context) - }, - }; - - if font.mScriptUnconstrainedSize == new_size.computed_size { - return; - } - - new_size - }; - - self.context.builder.mutate_font().set_font_size(new_size); - } - - /// Some properties, plus setting font-size itself, may make us go out of - /// our minimum font-size range. - #[cfg(feature = "gecko")] - fn constrain_font_size_if_needed(&mut self) { - use crate::gecko_bindings::bindings; - use crate::values::generics::NonNegative; - - if !self.seen.contains(LonghandId::XLang) && - !self.seen.contains(LonghandId::FontFamily) && - !self.seen.contains(LonghandId::MozMinFontSizeRatio) && - !self.seen.contains(LonghandId::FontSize) - { - return; - } - - let builder = &mut self.context.builder; - let min_font_size = { - let font = builder.get_font(); - let min_font_size = unsafe { - bindings::Gecko_nsStyleFont_ComputeMinSize(&**font, builder.device.document()) - }; - - if font.mFont.size.0 >= min_font_size { - return; - } - - NonNegative(min_font_size) - }; - - builder.mutate_font().mFont.size = min_font_size; - } - - /// <svg:text> is not affected by text zoom, and it uses a preshint to disable it. We fix up - /// the struct when this happens by unzooming its contained font values, which will have been - /// zoomed in the parent. - /// - /// FIXME(emilio): Why doing this _before_ handling font-size? That sounds wrong. - #[cfg(feature = "gecko")] - fn unzoom_fonts_if_needed(&mut self) { - if !self.seen.contains(LonghandId::XTextScale) { - return; - } - - let builder = &mut self.context.builder; - - let parent_text_scale = builder.get_parent_font().clone__x_text_scale(); - let text_scale = builder.get_font().clone__x_text_scale(); - if parent_text_scale == text_scale { - return; - } - debug_assert_ne!( - parent_text_scale.text_zoom_enabled(), - text_scale.text_zoom_enabled(), - "There's only one value that disables it" - ); - debug_assert!( - !text_scale.text_zoom_enabled(), - "We only ever disable text zoom (in svg:text), never enable it" - ); - let device = builder.device; - builder.mutate_font().unzoom_fonts(device); - } - - /// MathML script* attributes do some very weird shit with font-size. - /// - /// Handle them specially here, separate from other font-size stuff. - /// - /// How this should interact with lang="" and font-family-dependent sizes is - /// not clear to me. For now just pretend those don't exist here. - #[cfg(feature = "gecko")] - fn handle_mathml_scriptlevel_if_needed(&mut self) { - use crate::values::generics::NonNegative; - - if !self.seen.contains(LonghandId::MathDepth) && - !self.seen.contains(LonghandId::MozScriptMinSize) && - !self.seen.contains(LonghandId::MozScriptSizeMultiplier) - { - return; - } - - // If the user specifies a font-size, just let it be. - if self.seen.contains(LonghandId::FontSize) { - return; - } - - const SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE: f32 = 0.71; - - // Helper function that calculates the scale factor applied to font-size - // when math-depth goes from parent_math_depth to computed_math_depth. - // This function is essentially a modification of the MathML3's formula - // 0.71^(parent_math_depth - computed_math_depth) so that a scale factor - // of parent_script_percent_scale_down is applied when math-depth goes - // from 0 to 1 and parent_script_script_percent_scale_down is applied - // when math-depth goes from 0 to 2. This is also a straightforward - // implementation of the specification's algorithm: - // https://w3c.github.io/mathml-core/#the-math-script-level-property - fn scale_factor_for_math_depth_change( - parent_math_depth: i32, - computed_math_depth: i32, - parent_script_percent_scale_down: Option<f32>, - parent_script_script_percent_scale_down: Option<f32>, - ) -> f32 { - let mut a = parent_math_depth; - let mut b = computed_math_depth; - let c = SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE; - let scale_between_0_and_1 = parent_script_percent_scale_down.unwrap_or_else(|| c); - let scale_between_0_and_2 = - parent_script_script_percent_scale_down.unwrap_or_else(|| c * c); - let mut s = 1.0; - let mut invert_scale_factor = false; - if a == b { - return s; - } - if b < a { - mem::swap(&mut a, &mut b); - invert_scale_factor = true; - } - let mut e = b - a; - if a <= 0 && b >= 2 { - s *= scale_between_0_and_2; - e -= 2; - } else if a == 1 { - s *= scale_between_0_and_2 / scale_between_0_and_1; - e -= 1; - } else if b == 1 { - s *= scale_between_0_and_1; - e -= 1; - } - s *= (c as f32).powi(e); - if invert_scale_factor { - 1.0 / s.max(f32::MIN_POSITIVE) - } else { - s - } - } - - let (new_size, new_unconstrained_size) = { - let builder = &self.context.builder; - let font = builder.get_font(); - let parent_font = builder.get_parent_font(); - - let delta = font.mMathDepth.saturating_sub(parent_font.mMathDepth); - - if delta == 0 { - return; - } - - let mut min = parent_font.mScriptMinSize; - if font.mXTextScale.text_zoom_enabled() { - min = builder.device.zoom_text(min); - } - - // If the scriptsizemultiplier has been set to something other than - // the default scale, use MathML3's implementation for backward - // compatibility. Otherwise, follow MathML Core's algorithm. - let scale = if parent_font.mScriptSizeMultiplier != - SCALE_FACTOR_WHEN_INCREMENTING_MATH_DEPTH_BY_ONE - { - (parent_font.mScriptSizeMultiplier as f32).powi(delta as i32) - } else { - // Script scale factors are independent of orientation. - let font_metrics = self.context.query_font_metrics( - FontBaseSize::InheritedStyle, - FontMetricsOrientation::Horizontal, - /* retrieve_math_scales = */ true, - ); - scale_factor_for_math_depth_change( - parent_font.mMathDepth as i32, - font.mMathDepth as i32, - font_metrics.script_percent_scale_down, - font_metrics.script_script_percent_scale_down, - ) - }; - - let parent_size = parent_font.mSize.0; - let parent_unconstrained_size = parent_font.mScriptUnconstrainedSize.0; - let new_size = parent_size.scale_by(scale); - let new_unconstrained_size = parent_unconstrained_size.scale_by(scale); - - if scale <= 1. { - // The parent size can be smaller than scriptminsize, e.g. if it - // was specified explicitly. Don't scale in this case, but we - // don't want to set it to scriptminsize either since that will - // make it larger. - if parent_size <= min { - (parent_size, new_unconstrained_size) - } else { - (min.max(new_size), new_unconstrained_size) - } - } else { - // If the new unconstrained size is larger than the min size, - // this means we have escaped the grasp of scriptminsize and can - // revert to using the unconstrained size. - // However, if the new size is even larger (perhaps due to usage - // of em units), use that instead. - ( - new_size.min(new_unconstrained_size.max(min)), - new_unconstrained_size, - ) - } - }; - let font = self.context.builder.mutate_font(); - font.mFont.size = NonNegative(new_size); - font.mSize = NonNegative(new_size); - font.mScriptUnconstrainedSize = NonNegative(new_unconstrained_size); - } - - /// Various properties affect how font-size and font-family are computed. - /// - /// These need to be handled here, since relative lengths and ex / ch units - /// for late properties depend on these. - fn fixup_font_stuff(&mut self) { - #[cfg(feature = "gecko")] - { - self.unzoom_fonts_if_needed(); - self.recompute_initial_font_family_if_needed(); - self.prioritize_user_fonts_if_needed(); - self.recompute_keyword_font_size_if_needed(); - self.handle_mathml_scriptlevel_if_needed(); - self.constrain_font_size_if_needed() - } - } -} diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs deleted file mode 100644 index f12760aa8b4..00000000000 --- a/components/style/properties/computed_value_flags.rs +++ /dev/null @@ -1,190 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Misc information about a given computed style. - -bitflags! { - /// Misc information about a given computed style. - /// - /// All flags are currently inherited for text, pseudo elements, and - /// anonymous boxes, see StyleBuilder::for_inheritance and its callsites. - /// If we ever want to add some flags that shouldn't inherit for them, - /// we might want to add a function to handle this. - #[repr(C)] - pub struct ComputedValueFlags: u32 { - /// Whether the style or any of the ancestors has a text-decoration-line - /// property that should get propagated to descendants. - /// - /// text-decoration-line is a reset property, but gets propagated in the - /// frame/box tree. - const HAS_TEXT_DECORATION_LINES = 1 << 0; - - /// Whether line break inside should be suppressed. - /// - /// If this flag is set, the line should not be broken inside, - /// which means inlines act as if nowrap is set, <br> element is - /// suppressed, and blocks are inlinized. - /// - /// This bit is propagated to all children of line participants. - /// It is currently used by ruby to make its content unbreakable. - const SHOULD_SUPPRESS_LINEBREAK = 1 << 1; - - /// A flag used to mark text that that has text-combine-upright. - /// - /// This is used from Gecko's layout engine. - const IS_TEXT_COMBINED = 1 << 2; - - /// A flag used to mark styles under a relevant link that is also - /// visited. - const IS_RELEVANT_LINK_VISITED = 1 << 3; - - /// A flag used to mark styles which are a pseudo-element or under one. - const IS_IN_PSEUDO_ELEMENT_SUBTREE = 1 << 4; - - /// A flag used to mark styles which have contain:style or under one. - const SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE = 1 << 5; - - /// Whether this style's `display` property depends on our parent style. - /// - /// This is important because it may affect our optimizations to avoid - /// computing the style of pseudo-elements, given whether the - /// pseudo-element is generated depends on the `display` value. - const DISPLAY_DEPENDS_ON_INHERITED_STYLE = 1 << 6; - - /// Whether this style's `content` depends on our parent style. - /// - /// Important because of the same reason. - const CONTENT_DEPENDS_ON_INHERITED_STYLE = 1 << 7; - - /// Whether the child explicitly inherits any reset property. - const INHERITS_RESET_STYLE = 1 << 8; - - /// Whether any value on our style is font-metric-dependent on our - /// primary font. - const DEPENDS_ON_SELF_FONT_METRICS = 1 << 9; - - /// Whether any value on our style is font-metric-dependent on the - /// primary font of our parent. - const DEPENDS_ON_INHERITED_FONT_METRICS = 1 << 10; - - /// Whether the style or any of the ancestors has a multicol style. - /// - /// Only used in Servo. - const CAN_BE_FRAGMENTED = 1 << 11; - - /// Whether this style is the style of the document element. - const IS_ROOT_ELEMENT_STYLE = 1 << 12; - - /// Whether this element is inside an `opacity: 0` subtree. - const IS_IN_OPACITY_ZERO_SUBTREE = 1 << 13; - - /// Whether there are author-specified rules for border-* properties - /// (except border-image-*), background-color, or background-image. - /// - /// TODO(emilio): Maybe do include border-image, see: - /// - /// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845 - const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14; - - /// Whether there are author-specified rules for `font-family`. - const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16; - - /// Whether there are author-specified rules for `font-synthesis-weight`. - const HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT = 1 << 17; - - /// Whether there are author-specified rules for `font-synthesis-style`. - const HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE = 1 << 18; - - // (There's also font-synthesis-small-caps, but we don't currently need to - // keep track of that.) - - /// Whether there are author-specified rules for `letter-spacing`. - const HAS_AUTHOR_SPECIFIED_LETTER_SPACING = 1 << 19; - - /// Whether there are author-specified rules for `word-spacing`. - const HAS_AUTHOR_SPECIFIED_WORD_SPACING = 1 << 20; - - /// Whether the style depends on viewport units. - const USES_VIEWPORT_UNITS = 1 << 21; - - /// Whether the style depends on viewport units on container queries. - /// - /// This needs to be a separate flag from `USES_VIEWPORT_UNITS` because - /// it causes us to re-match the style (rather than re-cascascading it, - /// which is enough for other uses of viewport units). - const USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES = 1 << 22; - - /// A flag used to mark styles which have `container-type` of `size` or - /// `inline-size`, or under one. - const SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE = 1 << 23; - - /// Whether the style evaluated any relative selector. - const CONSIDERED_RELATIVE_SELECTOR = 1 << 24; - - /// Whether the style evaluated the matched element to be an anchor of - /// a relative selector. - const ANCHORS_RELATIVE_SELECTOR = 1 << 25; - - /// Whether the style uses container query units, in which case the style depends on the - /// container's size and we can't reuse it across cousins (without double-checking the - /// container at least). - const USES_CONTAINER_UNITS = 1 << 26; - } -} - -impl Default for ComputedValueFlags { - #[inline] - fn default() -> Self { - Self::empty() - } -} - -impl ComputedValueFlags { - /// Flags that are unconditionally propagated to descendants. - #[inline] - fn inherited_flags() -> Self { - Self::IS_RELEVANT_LINK_VISITED | - Self::CAN_BE_FRAGMENTED | - Self::IS_IN_PSEUDO_ELEMENT_SUBTREE | - Self::HAS_TEXT_DECORATION_LINES | - Self::IS_IN_OPACITY_ZERO_SUBTREE | - Self::SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE | - Self::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE - } - - /// Flags that may be propagated to descendants. - #[inline] - fn maybe_inherited_flags() -> Self { - Self::inherited_flags() | Self::SHOULD_SUPPRESS_LINEBREAK - } - - /// Flags that are an input to the cascade. - #[inline] - fn cascade_input_flags() -> Self { - Self::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES | - Self::CONSIDERED_RELATIVE_SELECTOR | - Self::ANCHORS_RELATIVE_SELECTOR - } - - /// Returns the flags that are always propagated to descendants. - /// - /// See StyleAdjuster::set_bits and StyleBuilder. - #[inline] - pub fn inherited(self) -> Self { - self & Self::inherited_flags() - } - - /// Flags that are conditionally propagated to descendants, just to handle - /// properly style invalidation. - #[inline] - pub fn maybe_inherited(self) -> Self { - self & Self::maybe_inherited_flags() - } - - /// Flags that are an input to the cascade. - #[inline] - pub fn for_cascade_inputs(self) -> Self { - self & Self::cascade_input_flags() - } -} diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py deleted file mode 100644 index b1b800812dd..00000000000 --- a/components/style/properties/counted_unknown_properties.py +++ /dev/null @@ -1,110 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -COUNTED_UNKNOWN_PROPERTIES = [ - "-webkit-font-smoothing", - "-webkit-tap-highlight-color", - "speak", - "text-size-adjust", - "-webkit-font-feature-settings", - "-webkit-user-drag", - "orphans", - "widows", - "-webkit-user-modify", - "-webkit-margin-before", - "-webkit-margin-after", - "-webkit-margin-start", - "-webkit-column-break-inside", - "-webkit-padding-start", - "-webkit-margin-end", - "-webkit-box-reflect", - "-webkit-print-color-adjust", - "-webkit-mask-box-image", - "-webkit-line-break", - "alignment-baseline", - "-webkit-writing-mode", - "baseline-shift", - "-webkit-hyphenate-character", - "-webkit-highlight", - "background-repeat-x", - "-webkit-padding-end", - "background-repeat-y", - "-webkit-text-emphasis-color", - "-webkit-margin-top-collapse", - "-webkit-rtl-ordering", - "-webkit-padding-before", - "-webkit-text-decorations-in-effect", - "-webkit-border-vertical-spacing", - "-webkit-locale", - "-webkit-padding-after", - "-webkit-border-horizontal-spacing", - "color-rendering", - "-webkit-column-break-before", - "-webkit-transform-origin-x", - "-webkit-transform-origin-y", - "-webkit-text-emphasis-position", - "buffered-rendering", - "-webkit-text-orientation", - "-webkit-text-combine", - "-webkit-text-emphasis-style", - "-webkit-text-emphasis", - "-webkit-mask-box-image-width", - "-webkit-mask-box-image-source", - "-webkit-mask-box-image-outset", - "-webkit-mask-box-image-slice", - "-webkit-mask-box-image-repeat", - "-webkit-margin-after-collapse", - "-webkit-border-before-color", - "-webkit-border-before-width", - "-webkit-perspective-origin-x", - "-webkit-perspective-origin-y", - "-webkit-margin-before-collapse", - "-webkit-border-before-style", - "-webkit-margin-bottom-collapse", - "-webkit-ruby-position", - "-webkit-column-break-after", - "-webkit-margin-collapse", - "-webkit-border-before", - "-webkit-border-end", - "-webkit-border-after", - "-webkit-border-start", - "-webkit-min-logical-width", - "-webkit-logical-height", - "-webkit-transform-origin-z", - "-webkit-font-size-delta", - "-webkit-logical-width", - "-webkit-max-logical-width", - "-webkit-min-logical-height", - "-webkit-max-logical-height", - "-webkit-border-end-color", - "-webkit-border-end-width", - "-webkit-border-start-color", - "-webkit-border-start-width", - "-webkit-border-after-color", - "-webkit-border-after-width", - "-webkit-border-end-style", - "-webkit-border-after-style", - "-webkit-border-start-style", - "-webkit-mask-repeat-x", - "-webkit-mask-repeat-y", - "user-zoom", - "min-zoom", - "-webkit-box-decoration-break", - "orientation", - "max-zoom", - "-webkit-app-region", - "-webkit-column-rule", - "-webkit-column-span", - "-webkit-column-gap", - "-webkit-shape-outside", - "-webkit-column-rule-width", - "-webkit-column-count", - "-webkit-opacity", - "-webkit-column-width", - "-webkit-shape-image-threshold", - "-webkit-column-rule-style", - "-webkit-columns", - "-webkit-column-rule-color", - "-webkit-shape-margin", -] diff --git a/components/style/properties/data.py b/components/style/properties/data.py deleted file mode 100644 index 03e5b4aa0a6..00000000000 --- a/components/style/properties/data.py +++ /dev/null @@ -1,912 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at https://mozilla.org/MPL/2.0/. - -import re -from counted_unknown_properties import COUNTED_UNKNOWN_PROPERTIES - -PHYSICAL_SIDES = ["top", "right", "bottom", "left"] -LOGICAL_SIDES = ["block-start", "block-end", "inline-start", "inline-end"] -PHYSICAL_SIZES = ["width", "height"] -LOGICAL_SIZES = ["block-size", "inline-size"] -PHYSICAL_CORNERS = ["top-left", "top-right", "bottom-right", "bottom-left"] -LOGICAL_CORNERS = ["start-start", "start-end", "end-start", "end-end"] -PHYSICAL_AXES = ["x", "y"] -LOGICAL_AXES = ["inline", "block"] - -# bool is True when logical -ALL_SIDES = [(side, False) for side in PHYSICAL_SIDES] + [ - (side, True) for side in LOGICAL_SIDES -] -ALL_SIZES = [(size, False) for size in PHYSICAL_SIZES] + [ - (size, True) for size in LOGICAL_SIZES -] -ALL_CORNERS = [(corner, False) for corner in PHYSICAL_CORNERS] + [ - (corner, True) for corner in LOGICAL_CORNERS -] -ALL_AXES = [(axis, False) for axis in PHYSICAL_AXES] + [ - (axis, True) for axis in LOGICAL_AXES -] - -SYSTEM_FONT_LONGHANDS = """font_family font_size font_style - font_stretch font_weight""".split() - -# Bitfield values for all rule types which can have property declarations. -STYLE_RULE = 1 << 0 -PAGE_RULE = 1 << 1 -KEYFRAME_RULE = 1 << 2 - -ALL_RULES = STYLE_RULE | PAGE_RULE | KEYFRAME_RULE -DEFAULT_RULES = STYLE_RULE | KEYFRAME_RULE -DEFAULT_RULES_AND_PAGE = DEFAULT_RULES | PAGE_RULE -DEFAULT_RULES_EXCEPT_KEYFRAME = STYLE_RULE - -# Rule name to value dict -RULE_VALUES = { - "Style": STYLE_RULE, - "Page": PAGE_RULE, - "Keyframe": KEYFRAME_RULE, -} - - -def rule_values_from_arg(that): - if isinstance(that, int): - return that - mask = 0 - for rule in that.split(): - mask |= RULE_VALUES[rule] - return mask - - -def maybe_moz_logical_alias(engine, side, prop): - if engine == "gecko" and side[1]: - axis, dir = side[0].split("-") - if axis == "inline": - return prop % dir - return None - - -def to_rust_ident(name): - name = name.replace("-", "_") - if name in ["static", "super", "box", "move"]: # Rust keywords - name += "_" - return name - - -def to_snake_case(ident): - return re.sub("([A-Z]+)", lambda m: "_" + m.group(1).lower(), ident).strip("_") - - -def to_camel_case(ident): - return re.sub( - "(^|_|-)([a-z0-9])", lambda m: m.group(2).upper(), ident.strip("_").strip("-") - ) - - -def to_camel_case_lower(ident): - camel = to_camel_case(ident) - return camel[0].lower() + camel[1:] - - -# https://drafts.csswg.org/cssom/#css-property-to-idl-attribute -def to_idl_name(ident): - return re.sub("-([a-z])", lambda m: m.group(1).upper(), ident) - - -def parse_aliases(value): - aliases = {} - for pair in value.split(): - [a, v] = pair.split("=") - aliases[a] = v - return aliases - - -class Keyword(object): - def __init__( - self, - name, - values, - gecko_constant_prefix=None, - gecko_enum_prefix=None, - custom_consts=None, - extra_gecko_values=None, - extra_servo_values=None, - gecko_aliases=None, - servo_aliases=None, - gecko_strip_moz_prefix=None, - gecko_inexhaustive=None, - ): - self.name = name - self.values = values.split() - if gecko_constant_prefix and gecko_enum_prefix: - raise TypeError( - "Only one of gecko_constant_prefix and gecko_enum_prefix " - "can be specified" - ) - self.gecko_constant_prefix = ( - gecko_constant_prefix or "NS_STYLE_" + self.name.upper().replace("-", "_") - ) - self.gecko_enum_prefix = gecko_enum_prefix - self.extra_gecko_values = (extra_gecko_values or "").split() - self.extra_servo_values = (extra_servo_values or "").split() - self.gecko_aliases = parse_aliases(gecko_aliases or "") - self.servo_aliases = parse_aliases(servo_aliases or "") - self.consts_map = {} if custom_consts is None else custom_consts - self.gecko_strip_moz_prefix = ( - True if gecko_strip_moz_prefix is None else gecko_strip_moz_prefix - ) - self.gecko_inexhaustive = gecko_inexhaustive or (gecko_enum_prefix is None) - - def values_for(self, engine): - if engine == "gecko": - return self.values + self.extra_gecko_values - elif engine == "servo": - return self.values + self.extra_servo_values - else: - raise Exception("Bad engine: " + engine) - - def aliases_for(self, engine): - if engine == "gecko": - return self.gecko_aliases - elif engine == "servo": - return self.servo_aliases - else: - raise Exception("Bad engine: " + engine) - - def gecko_constant(self, value): - moz_stripped = ( - value.replace("-moz-", "") - if self.gecko_strip_moz_prefix - else value.replace("-moz-", "moz-") - ) - mapped = self.consts_map.get(value) - if self.gecko_enum_prefix: - parts = moz_stripped.replace("-", "_").split("_") - parts = mapped if mapped else [p.title() for p in parts] - return self.gecko_enum_prefix + "::" + "".join(parts) - else: - suffix = mapped if mapped else moz_stripped.replace("-", "_") - return self.gecko_constant_prefix + "_" + suffix.upper() - - def needs_cast(self): - return self.gecko_enum_prefix is None - - def maybe_cast(self, type_str): - return "as " + type_str if self.needs_cast() else "" - - def casted_constant_name(self, value, cast_type): - if cast_type is None: - raise TypeError("We should specify the cast_type.") - - if self.gecko_enum_prefix is None: - return cast_type.upper() + "_" + self.gecko_constant(value) - else: - return ( - cast_type.upper() - + "_" - + self.gecko_constant(value).upper().replace("::", "_") - ) - - -def arg_to_bool(arg): - if isinstance(arg, bool): - return arg - assert arg in ["True", "False"], "Unexpected value for boolean argument: " + repr( - arg - ) - return arg == "True" - - -def parse_property_aliases(alias_list): - result = [] - if alias_list: - for alias in alias_list.split(): - (name, _, pref) = alias.partition(":") - result.append((name, pref)) - return result - - -def to_phys(name, logical, physical): - return name.replace(logical, physical).replace("inset-", "") - - -class Property(object): - def __init__( - self, - name, - spec, - servo_pref, - gecko_pref, - enabled_in, - rule_types_allowed, - aliases, - extra_prefixes, - flags, - ): - self.name = name - if not spec: - raise TypeError("Spec should be specified for " + name) - self.spec = spec - self.ident = to_rust_ident(name) - self.camel_case = to_camel_case(self.ident) - self.servo_pref = servo_pref - self.gecko_pref = gecko_pref - self.rule_types_allowed = rule_values_from_arg(rule_types_allowed) - # For enabled_in, the setup is as follows: - # It needs to be one of the four values: ["", "ua", "chrome", "content"] - # * "chrome" implies "ua", and implies that they're explicitly - # enabled. - # * "" implies the property will never be parsed. - # * "content" implies the property is accessible unconditionally, - # modulo a pref, set via servo_pref / gecko_pref. - assert enabled_in in ("", "ua", "chrome", "content") - self.enabled_in = enabled_in - self.aliases = parse_property_aliases(aliases) - self.extra_prefixes = parse_property_aliases(extra_prefixes) - self.flags = flags.split() if flags else [] - - def rule_types_allowed_names(self): - for name in RULE_VALUES: - if self.rule_types_allowed & RULE_VALUES[name] != 0: - yield name - - def experimental(self, engine): - if engine == "gecko": - return bool(self.gecko_pref) - elif engine == "servo": - return bool(self.servo_pref) - else: - raise Exception("Bad engine: " + engine) - - def explicitly_enabled_in_ua_sheets(self): - return self.enabled_in in ("ua", "chrome") - - def explicitly_enabled_in_chrome(self): - return self.enabled_in == "chrome" - - def enabled_in_content(self): - return self.enabled_in == "content" - - def nscsspropertyid(self): - return "nsCSSPropertyID::eCSSProperty_" + self.ident - - -class Longhand(Property): - def __init__( - self, - style_struct, - name, - spec=None, - animation_value_type=None, - keyword=None, - predefined_type=None, - servo_pref=None, - gecko_pref=None, - enabled_in="content", - need_index=False, - gecko_ffi_name=None, - has_effect_on_gecko_scrollbars=None, - rule_types_allowed=DEFAULT_RULES, - cast_type="u8", - logical=False, - logical_group=None, - aliases=None, - extra_prefixes=None, - boxed=False, - flags=None, - allow_quirks="No", - ignored_when_colors_disabled=False, - simple_vector_bindings=False, - vector=False, - servo_restyle_damage="repaint", - ): - Property.__init__( - self, - name=name, - spec=spec, - servo_pref=servo_pref, - gecko_pref=gecko_pref, - enabled_in=enabled_in, - rule_types_allowed=rule_types_allowed, - aliases=aliases, - extra_prefixes=extra_prefixes, - flags=flags, - ) - - self.keyword = keyword - self.predefined_type = predefined_type - self.style_struct = style_struct - self.has_effect_on_gecko_scrollbars = has_effect_on_gecko_scrollbars - assert ( - has_effect_on_gecko_scrollbars in [None, False, True] - and not style_struct.inherited - or (gecko_pref is None and enabled_in != "") - == (has_effect_on_gecko_scrollbars is None) - ), ( - "Property " - + name - + ": has_effect_on_gecko_scrollbars must be " - + "specified, and must have a value of True or False, iff a " - + "property is inherited and is behind a Gecko pref or internal" - ) - self.need_index = need_index - self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case - self.cast_type = cast_type - self.logical = arg_to_bool(logical) - self.logical_group = logical_group - if self.logical: - assert logical_group, "Property " + name + " must have a logical group" - - self.boxed = arg_to_bool(boxed) - self.allow_quirks = allow_quirks - self.ignored_when_colors_disabled = ignored_when_colors_disabled - self.is_vector = vector - self.simple_vector_bindings = simple_vector_bindings - - # This is done like this since just a plain bool argument seemed like - # really random. - if animation_value_type is None: - raise TypeError( - "animation_value_type should be specified for (" + name + ")" - ) - self.animation_value_type = animation_value_type - - self.animatable = animation_value_type != "none" - self.transitionable = ( - animation_value_type != "none" and animation_value_type != "discrete" - ) - self.is_animatable_with_computed_value = ( - animation_value_type == "ComputedValue" - or animation_value_type == "discrete" - ) - - # See compute_damage for the various values this can take - self.servo_restyle_damage = servo_restyle_damage - - @staticmethod - def type(): - return "longhand" - - # For a given logical property return all the physical property names - # corresponding to it. - def all_physical_mapped_properties(self, data): - if not self.logical: - return [] - - candidates = [ - s for s in LOGICAL_SIDES + LOGICAL_SIZES + LOGICAL_CORNERS if s in self.name - ] + [s for s in LOGICAL_AXES if self.name.endswith(s)] - assert len(candidates) == 1 - logical_side = candidates[0] - - physical = ( - PHYSICAL_SIDES - if logical_side in LOGICAL_SIDES - else PHYSICAL_SIZES - if logical_side in LOGICAL_SIZES - else PHYSICAL_AXES - if logical_side in LOGICAL_AXES - else LOGICAL_CORNERS - ) - return [ - data.longhands_by_name[to_phys(self.name, logical_side, physical_side)] - for physical_side in physical - ] - - def may_be_disabled_in(self, shorthand, engine): - if engine == "gecko": - return self.gecko_pref and self.gecko_pref != shorthand.gecko_pref - elif engine == "servo": - return ( - self.servo_pref - and self.servo_pref != shorthand.servo_pref - ) - else: - raise Exception("Bad engine: " + engine) - - def base_type(self): - if self.predefined_type and not self.is_vector: - return "crate::values::specified::{}".format(self.predefined_type) - return "longhands::{}::SpecifiedValue".format(self.ident) - - def specified_type(self): - if self.predefined_type and not self.is_vector: - ty = "crate::values::specified::{}".format(self.predefined_type) - else: - ty = "longhands::{}::SpecifiedValue".format(self.ident) - if self.boxed: - ty = "Box<{}>".format(ty) - return ty - - def specified_is_copy(self): - if self.is_vector or self.boxed: - return False - if self.predefined_type: - return self.predefined_type in { - "AlignContent", - "AlignItems", - "AlignSelf", - "Appearance", - "AspectRatio", - "BaselineSource", - "BreakBetween", - "BreakWithin", - "BackgroundRepeat", - "BorderImageRepeat", - "BorderStyle", - "table::CaptionSide", - "Clear", - "ColumnCount", - "Contain", - "ContentVisibility", - "ContainerType", - "Display", - "FillRule", - "Float", - "FontLanguageOverride", - "FontSizeAdjust", - "FontStretch", - "FontStyle", - "FontSynthesis", - "FontVariantEastAsian", - "FontVariantLigatures", - "FontVariantNumeric", - "FontWeight", - "GreaterThanOrEqualToOneNumber", - "GridAutoFlow", - "ImageRendering", - "InitialLetter", - "Integer", - "JustifyContent", - "JustifyItems", - "JustifySelf", - "LineBreak", - "LineClamp", - "MasonryAutoFlow", - "BoolInteger", - "text::MozControlCharacterVisibility", - "MathDepth", - "MozScriptMinSize", - "MozScriptSizeMultiplier", - "TextDecorationSkipInk", - "NonNegativeNumber", - "OffsetRotate", - "Opacity", - "OutlineStyle", - "Overflow", - "OverflowAnchor", - "OverflowClipBox", - "OverflowWrap", - "OverscrollBehavior", - "PageOrientation", - "Percentage", - "PrintColorAdjust", - "ForcedColorAdjust", - "Resize", - "RubyPosition", - "SVGOpacity", - "SVGPaintOrder", - "ScrollbarGutter", - "ScrollSnapAlign", - "ScrollSnapAxis", - "ScrollSnapStop", - "ScrollSnapStrictness", - "ScrollSnapType", - "TextAlign", - "TextAlignLast", - "TextDecorationLine", - "TextEmphasisPosition", - "TextJustify", - "TextTransform", - "TextUnderlinePosition", - "TouchAction", - "TransformStyle", - "UserSelect", - "WordBreak", - "XSpan", - "XTextScale", - "ZIndex", - } - if self.name == "overflow-y": - return True - return bool(self.keyword) - - def animated_type(self): - assert self.animatable - computed = "<{} as ToComputedValue>::ComputedValue".format(self.base_type()) - if self.is_animatable_with_computed_value: - return computed - return "<{} as ToAnimatedValue>::AnimatedValue".format(computed) - - -class Shorthand(Property): - def __init__( - self, - name, - sub_properties, - spec=None, - servo_pref=None, - gecko_pref=None, - enabled_in="content", - rule_types_allowed=DEFAULT_RULES, - aliases=None, - extra_prefixes=None, - flags=None, - ): - Property.__init__( - self, - name=name, - spec=spec, - servo_pref=servo_pref, - gecko_pref=gecko_pref, - enabled_in=enabled_in, - rule_types_allowed=rule_types_allowed, - aliases=aliases, - extra_prefixes=extra_prefixes, - flags=flags, - ) - self.sub_properties = sub_properties - - def get_animatable(self): - for sub in self.sub_properties: - if sub.animatable: - return True - return False - - def get_transitionable(self): - transitionable = False - for sub in self.sub_properties: - if sub.transitionable: - transitionable = True - break - return transitionable - - animatable = property(get_animatable) - transitionable = property(get_transitionable) - - @staticmethod - def type(): - return "shorthand" - - -class Alias(object): - def __init__(self, name, original, gecko_pref): - self.name = name - self.ident = to_rust_ident(name) - self.camel_case = to_camel_case(self.ident) - self.original = original - self.enabled_in = original.enabled_in - self.animatable = original.animatable - self.servo_pref = original.servo_pref - self.gecko_pref = gecko_pref - self.transitionable = original.transitionable - self.rule_types_allowed = original.rule_types_allowed - self.flags = original.flags - - @staticmethod - def type(): - return "alias" - - def rule_types_allowed_names(self): - for name in RULE_VALUES: - if self.rule_types_allowed & RULE_VALUES[name] != 0: - yield name - - def experimental(self, engine): - if engine == "gecko": - return bool(self.gecko_pref) - elif engine == "servo": - return bool(self.servo_pref) - else: - raise Exception("Bad engine: " + engine) - - def explicitly_enabled_in_ua_sheets(self): - return self.enabled_in in ["ua", "chrome"] - - def explicitly_enabled_in_chrome(self): - return self.enabled_in == "chrome" - - def enabled_in_content(self): - return self.enabled_in == "content" - - def nscsspropertyid(self): - return "nsCSSPropertyID::eCSSPropertyAlias_%s" % self.ident - - -class Method(object): - def __init__(self, name, return_type=None, arg_types=None, is_mut=False): - self.name = name - self.return_type = return_type - self.arg_types = arg_types or [] - self.is_mut = is_mut - - def arg_list(self): - args = ["_: " + x for x in self.arg_types] - args = ["&mut self" if self.is_mut else "&self"] + args - return ", ".join(args) - - def signature(self): - sig = "fn %s(%s)" % (self.name, self.arg_list()) - if self.return_type: - sig = sig + " -> " + self.return_type - return sig - - def declare(self): - return self.signature() + ";" - - def stub(self): - return self.signature() + "{ unimplemented!() }" - - -class StyleStruct(object): - def __init__(self, name, inherited, gecko_name=None, additional_methods=None): - self.gecko_struct_name = "Gecko" + name - self.name = name - self.name_lower = to_snake_case(name) - self.ident = to_rust_ident(self.name_lower) - self.longhands = [] - self.inherited = inherited - self.gecko_name = gecko_name or name - self.gecko_ffi_name = "nsStyle" + self.gecko_name - self.additional_methods = additional_methods or [] - - -class PropertiesData(object): - def __init__(self, engine): - self.engine = engine - self.style_structs = [] - self.current_style_struct = None - self.longhands = [] - self.longhands_by_name = {} - self.longhands_by_logical_group = {} - self.longhand_aliases = [] - self.shorthands = [] - self.shorthands_by_name = {} - self.shorthand_aliases = [] - self.counted_unknown_properties = [ - CountedUnknownProperty(p) for p in COUNTED_UNKNOWN_PROPERTIES - ] - - def new_style_struct(self, *args, **kwargs): - style_struct = StyleStruct(*args, **kwargs) - self.style_structs.append(style_struct) - self.current_style_struct = style_struct - - def active_style_structs(self): - return [s for s in self.style_structs if s.additional_methods or s.longhands] - - def add_prefixed_aliases(self, property): - # FIXME Servo's DOM architecture doesn't support vendor-prefixed properties. - # See servo/servo#14941. - if self.engine == "gecko": - for (prefix, pref) in property.extra_prefixes: - property.aliases.append(("-%s-%s" % (prefix, property.name), pref)) - - def declare_longhand(self, name, engines=None, **kwargs): - engines = engines.split() - if self.engine not in engines: - return - - longhand = Longhand(self.current_style_struct, name, **kwargs) - self.add_prefixed_aliases(longhand) - longhand.aliases = [Alias(xp[0], longhand, xp[1]) for xp in longhand.aliases] - self.longhand_aliases += longhand.aliases - self.current_style_struct.longhands.append(longhand) - self.longhands.append(longhand) - self.longhands_by_name[name] = longhand - if longhand.logical_group: - self.longhands_by_logical_group.setdefault( - longhand.logical_group, [] - ).append(longhand) - - return longhand - - def declare_shorthand(self, name, sub_properties, engines, *args, **kwargs): - engines = engines.split() - if self.engine not in engines: - return - - sub_properties = [self.longhands_by_name[s] for s in sub_properties] - shorthand = Shorthand(name, sub_properties, *args, **kwargs) - self.add_prefixed_aliases(shorthand) - shorthand.aliases = [Alias(xp[0], shorthand, xp[1]) for xp in shorthand.aliases] - self.shorthand_aliases += shorthand.aliases - self.shorthands.append(shorthand) - self.shorthands_by_name[name] = shorthand - return shorthand - - def shorthands_except_all(self): - return [s for s in self.shorthands if s.name != "all"] - - def all_aliases(self): - return self.longhand_aliases + self.shorthand_aliases - - -def _add_logical_props(data, props): - groups = set() - for prop in props: - if prop not in data.longhands_by_name: - assert data.engine == "servo" - continue - prop = data.longhands_by_name[prop] - if prop.logical_group: - groups.add(prop.logical_group) - for group in groups: - for prop in data.longhands_by_logical_group[group]: - props.add(prop.name) - - -# These are probably Gecko bugs and should be supported per spec. -def _remove_common_first_line_and_first_letter_properties(props, engine): - if engine == "gecko": - props.remove("tab-size") - props.remove("hyphens") - props.remove("line-break") - props.remove("text-align-last") - props.remove("text-emphasis-position") - props.remove("text-emphasis-style") - props.remove("text-emphasis-color") - - props.remove("overflow-wrap") - props.remove("text-align") - props.remove("text-justify") - props.remove("white-space") - props.remove("word-break") - props.remove("text-indent") - - -class PropertyRestrictions: - @staticmethod - def logical_group(data, group): - return [p.name for p in data.longhands_by_logical_group[group]] - - @staticmethod - def shorthand(data, shorthand): - if shorthand not in data.shorthands_by_name: - return [] - return [p.name for p in data.shorthands_by_name[shorthand].sub_properties] - - @staticmethod - def spec(data, spec_path): - return [p.name for p in data.longhands if spec_path in p.spec] - - # https://drafts.csswg.org/css-pseudo/#first-letter-styling - @staticmethod - def first_letter(data): - props = set( - [ - "color", - "opacity", - "float", - "initial-letter", - # Kinda like css-fonts? - "-moz-osx-font-smoothing", - # Kinda like css-text? - "-webkit-text-stroke-width", - "-webkit-text-fill-color", - "-webkit-text-stroke-color", - "vertical-align", - # Will become shorthand of vertical-align (Bug 1830771) - "baseline-source", - "line-height", - # Kinda like css-backgrounds? - "background-blend-mode", - ] - + PropertyRestrictions.shorthand(data, "padding") - + PropertyRestrictions.shorthand(data, "margin") - + PropertyRestrictions.spec(data, "css-fonts") - + PropertyRestrictions.spec(data, "css-backgrounds") - + PropertyRestrictions.spec(data, "css-text") - + PropertyRestrictions.spec(data, "css-shapes") - + PropertyRestrictions.spec(data, "css-text-decor") - ) - - _add_logical_props(data, props) - - _remove_common_first_line_and_first_letter_properties(props, data.engine) - return props - - # https://drafts.csswg.org/css-pseudo/#first-line-styling - @staticmethod - def first_line(data): - props = set( - [ - # Per spec. - "color", - "opacity", - # Kinda like css-fonts? - "-moz-osx-font-smoothing", - # Kinda like css-text? - "-webkit-text-stroke-width", - "-webkit-text-fill-color", - "-webkit-text-stroke-color", - "vertical-align", - # Will become shorthand of vertical-align (Bug 1830771) - "baseline-source", - "line-height", - # Kinda like css-backgrounds? - "background-blend-mode", - ] - + PropertyRestrictions.spec(data, "css-fonts") - + PropertyRestrictions.spec(data, "css-backgrounds") - + PropertyRestrictions.spec(data, "css-text") - + PropertyRestrictions.spec(data, "css-text-decor") - ) - - # These are probably Gecko bugs and should be supported per spec. - for prop in PropertyRestrictions.shorthand(data, "border"): - props.remove(prop) - for prop in PropertyRestrictions.shorthand(data, "border-radius"): - props.remove(prop) - props.remove("box-shadow") - - _remove_common_first_line_and_first_letter_properties(props, data.engine) - return props - - # https://drafts.csswg.org/css-pseudo/#placeholder - # - # The spec says that placeholder and first-line have the same restrictions, - # but that's not true in Gecko and we also allow a handful other properties - # for ::placeholder. - @staticmethod - def placeholder(data): - props = PropertyRestrictions.first_line(data) - props.add("opacity") - props.add("white-space") - props.add("text-overflow") - props.add("text-align") - props.add("text-justify") - return props - - # https://drafts.csswg.org/css-pseudo/#marker-pseudo - @staticmethod - def marker(data): - return set( - [ - "white-space", - "color", - "text-combine-upright", - "text-transform", - "unicode-bidi", - "direction", - "content", - "line-height", - "-moz-osx-font-smoothing", - ] - + PropertyRestrictions.spec(data, "css-fonts") - + PropertyRestrictions.spec(data, "css-animations") - + PropertyRestrictions.spec(data, "css-transitions") - ) - - # https://www.w3.org/TR/webvtt1/#the-cue-pseudo-element - @staticmethod - def cue(data): - return set( - [ - "color", - "opacity", - "visibility", - "text-shadow", - "white-space", - "text-combine-upright", - "ruby-position", - # XXX Should these really apply to cue? - "-moz-osx-font-smoothing", - # FIXME(emilio): background-blend-mode should be part of the - # background shorthand, and get reset, per - # https://drafts.fxtf.org/compositing/#background-blend-mode - "background-blend-mode", - ] - + PropertyRestrictions.shorthand(data, "text-decoration") - + PropertyRestrictions.shorthand(data, "background") - + PropertyRestrictions.shorthand(data, "outline") - + PropertyRestrictions.shorthand(data, "font") - + PropertyRestrictions.shorthand(data, "font-synthesis") - ) - - -class CountedUnknownProperty: - def __init__(self, name): - self.name = name - self.ident = to_rust_ident(name) - self.camel_case = to_camel_case(self.ident) diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs deleted file mode 100644 index 90845af311a..00000000000 --- a/components/style/properties/declaration_block.rs +++ /dev/null @@ -1,1613 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A property declaration block. - -#![deny(missing_docs)] - -use super::generated::{ - shorthands, AllShorthand, ComputedValues, LogicalGroupSet, LonghandIdSet, - NonCustomPropertyIdSet, PropertyDeclaration, PropertyDeclarationId, PropertyId, ShorthandId, - SourcePropertyDeclaration, SourcePropertyDeclarationDrain, SubpropertiesVec, -}; -use crate::applicable_declarations::CascadePriority; -use crate::context::QuirksMode; -use crate::custom_properties::{self, CustomPropertiesBuilder}; -use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; -use crate::media_queries::Device; -use crate::parser::ParserContext; -use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; -use crate::rule_tree::CascadeLevel; -use crate::selector_map::PrecomputedHashSet; -use crate::selector_parser::SelectorImpl; -use crate::shared_lock::Locked; -use crate::str::{CssString, CssStringWriter}; -use crate::stylesheets::{layer_rule::LayerOrder, CssRuleType, Origin, UrlExtraData}; -use crate::values::computed::Context; -use cssparser::{ - parse_important, AtRuleParser, CowRcStr, DeclarationParser, Delimiter, ParseErrorKind, Parser, - ParserInput, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, -}; -use itertools::Itertools; -use selectors::SelectorList; -use servo_arc::Arc; -use smallbitvec::{self, SmallBitVec}; -use smallvec::SmallVec; -use std::fmt::{self, Write}; -use std::iter::{DoubleEndedIterator, Zip}; -use std::slice::Iter; -use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss}; -use thin_vec::ThinVec; - -/// A set of property declarations including animations and transitions. -#[derive(Default)] -pub struct AnimationDeclarations { - /// Declarations for animations. - pub animations: Option<Arc<Locked<PropertyDeclarationBlock>>>, - /// Declarations for transitions. - pub transitions: Option<Arc<Locked<PropertyDeclarationBlock>>>, -} - -impl AnimationDeclarations { - /// Whether or not this `AnimationDeclarations` is empty. - pub fn is_empty(&self) -> bool { - self.animations.is_none() && self.transitions.is_none() - } -} - -/// An enum describes how a declaration should update -/// the declaration block. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum DeclarationUpdate { - /// The given declaration doesn't update anything. - None, - /// The given declaration is new, and should be append directly. - Append, - /// The given declaration can be updated in-place at the given position. - UpdateInPlace { pos: usize }, - /// The given declaration cannot be updated in-place, and an existing - /// one needs to be removed at the given position. - AppendAndRemove { pos: usize }, -} - -/// A struct describes how a declaration block should be updated by -/// a `SourcePropertyDeclaration`. -#[derive(Default)] -pub struct SourcePropertyDeclarationUpdate { - updates: SubpropertiesVec<DeclarationUpdate>, - new_count: usize, - any_removal: bool, -} - -/// A declaration [importance][importance]. -/// -/// [importance]: https://drafts.csswg.org/css-cascade/#importance -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -pub enum Importance { - /// Indicates a declaration without `!important`. - Normal, - - /// Indicates a declaration with `!important`. - Important, -} - -impl Default for Importance { - fn default() -> Self { - Self::Normal - } -} - -impl Importance { - /// Return whether this is an important declaration. - pub fn important(self) -> bool { - match self { - Self::Normal => false, - Self::Important => true, - } - } -} - -#[derive(Clone, ToShmem, Default, MallocSizeOf)] -struct PropertyDeclarationIdSet { - longhands: LonghandIdSet, - custom: PrecomputedHashSet<custom_properties::Name>, -} - -impl PropertyDeclarationIdSet { - fn insert(&mut self, id: PropertyDeclarationId) -> bool { - match id { - PropertyDeclarationId::Longhand(id) => { - if self.longhands.contains(id) { - return false; - } - self.longhands.insert(id); - return true; - }, - PropertyDeclarationId::Custom(name) => self.custom.insert(name.clone()), - } - } - - fn contains(&self, id: PropertyDeclarationId) -> bool { - match id { - PropertyDeclarationId::Longhand(id) => self.longhands.contains(id), - PropertyDeclarationId::Custom(name) => self.custom.contains(name), - } - } - - fn remove(&mut self, id: PropertyDeclarationId) { - match id { - PropertyDeclarationId::Longhand(id) => self.longhands.remove(id), - PropertyDeclarationId::Custom(name) => { - self.custom.remove(name); - }, - } - } - - fn clear(&mut self) { - self.longhands.clear(); - self.custom.clear(); - } -} - -/// Overridden declarations are skipped. -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, ToShmem, Default)] -pub struct PropertyDeclarationBlock { - /// The group of declarations, along with their importance. - /// - /// Only deduplicated declarations appear here. - declarations: ThinVec<PropertyDeclaration>, - - /// The "important" flag for each declaration in `declarations`. - declarations_importance: SmallBitVec, - - /// The set of properties that are present in the block. - property_ids: PropertyDeclarationIdSet, -} - -/// Iterator over `(PropertyDeclaration, Importance)` pairs. -pub struct DeclarationImportanceIterator<'a> { - iter: Zip<Iter<'a, PropertyDeclaration>, smallbitvec::Iter<'a>>, -} - -impl<'a> Default for DeclarationImportanceIterator<'a> { - fn default() -> Self { - Self { - iter: [].iter().zip(smallbitvec::Iter::default()), - } - } -} - -impl<'a> DeclarationImportanceIterator<'a> { - /// Constructor. - fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self { - DeclarationImportanceIterator { - iter: declarations.iter().zip(important.iter()), - } - } -} - -impl<'a> Iterator for DeclarationImportanceIterator<'a> { - type Item = (&'a PropertyDeclaration, Importance); - - #[inline] - fn next(&mut self) -> Option<Self::Item> { - self.iter.next().map(|(decl, important)| { - ( - decl, - if important { - Importance::Important - } else { - Importance::Normal - }, - ) - }) - } - - #[inline] - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl<'a> DoubleEndedIterator for DeclarationImportanceIterator<'a> { - #[inline(always)] - fn next_back(&mut self) -> Option<Self::Item> { - self.iter.next_back().map(|(decl, important)| { - ( - decl, - if important { - Importance::Important - } else { - Importance::Normal - }, - ) - }) - } -} - -/// Iterator for AnimationValue to be generated from PropertyDeclarationBlock. -pub struct AnimationValueIterator<'a, 'cx, 'cx_a: 'cx> { - iter: DeclarationImportanceIterator<'a>, - context: &'cx mut Context<'cx_a>, - default_values: &'a ComputedValues, - /// Custom properties in a keyframe if exists. - extra_custom_properties: Option<&'a Arc<crate::custom_properties::CustomPropertiesMap>>, -} - -impl<'a, 'cx, 'cx_a: 'cx> AnimationValueIterator<'a, 'cx, 'cx_a> { - fn new( - declarations: &'a PropertyDeclarationBlock, - context: &'cx mut Context<'cx_a>, - default_values: &'a ComputedValues, - extra_custom_properties: Option<&'a Arc<crate::custom_properties::CustomPropertiesMap>>, - ) -> AnimationValueIterator<'a, 'cx, 'cx_a> { - AnimationValueIterator { - iter: declarations.declaration_importance_iter(), - context, - default_values, - extra_custom_properties, - } - } -} - -impl<'a, 'cx, 'cx_a: 'cx> Iterator for AnimationValueIterator<'a, 'cx, 'cx_a> { - type Item = AnimationValue; - #[inline] - fn next(&mut self) -> Option<Self::Item> { - loop { - let (decl, importance) = self.iter.next()?; - - if importance.important() { - continue; - } - - let animation = AnimationValue::from_declaration( - decl, - &mut self.context, - self.extra_custom_properties, - self.default_values, - ); - - if let Some(anim) = animation { - return Some(anim); - } - } - } -} - -impl fmt::Debug for PropertyDeclarationBlock { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.declarations.fmt(f) - } -} - -impl PropertyDeclarationBlock { - /// Returns the number of declarations in the block. - #[inline] - pub fn len(&self) -> usize { - self.declarations.len() - } - - /// Returns whether the block is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.declarations.is_empty() - } - - /// Create an empty block - #[inline] - pub fn new() -> Self { - PropertyDeclarationBlock { - declarations: ThinVec::new(), - declarations_importance: SmallBitVec::new(), - property_ids: PropertyDeclarationIdSet::default(), - } - } - - /// Create a block with a single declaration - pub fn with_one(declaration: PropertyDeclaration, importance: Importance) -> Self { - let mut property_ids = PropertyDeclarationIdSet::default(); - property_ids.insert(declaration.id()); - let mut declarations = ThinVec::with_capacity(1); - declarations.push(declaration); - PropertyDeclarationBlock { - declarations, - declarations_importance: SmallBitVec::from_elem(1, importance.important()), - property_ids, - } - } - - /// The declarations in this block - #[inline] - pub fn declarations(&self) -> &[PropertyDeclaration] { - &self.declarations - } - - /// The `important` flags for declarations in this block - #[inline] - pub fn declarations_importance(&self) -> &SmallBitVec { - &self.declarations_importance - } - - /// Iterate over `(PropertyDeclaration, Importance)` pairs - #[inline] - pub fn declaration_importance_iter(&self) -> DeclarationImportanceIterator { - DeclarationImportanceIterator::new(&self.declarations, &self.declarations_importance) - } - - /// Iterate over `PropertyDeclaration` for Importance::Normal - #[inline] - pub fn normal_declaration_iter<'a>( - &'a self, - ) -> impl DoubleEndedIterator<Item = &'a PropertyDeclaration> { - self.declaration_importance_iter() - .filter(|(_, importance)| !importance.important()) - .map(|(declaration, _)| declaration) - } - - /// Return an iterator of (AnimatableLonghand, AnimationValue). - #[inline] - pub fn to_animation_value_iter<'a, 'cx, 'cx_a: 'cx>( - &'a self, - context: &'cx mut Context<'cx_a>, - default_values: &'a ComputedValues, - extra_custom_properties: Option<&'a Arc<crate::custom_properties::CustomPropertiesMap>>, - ) -> AnimationValueIterator<'a, 'cx, 'cx_a> { - AnimationValueIterator::new(self, context, default_values, extra_custom_properties) - } - - /// Returns whether this block contains any declaration with `!important`. - /// - /// This is based on the `declarations_importance` bit-vector, - /// which should be maintained whenever `declarations` is changed. - #[inline] - pub fn any_important(&self) -> bool { - !self.declarations_importance.all_false() - } - - /// Returns whether this block contains any declaration without `!important`. - /// - /// This is based on the `declarations_importance` bit-vector, - /// which should be maintained whenever `declarations` is changed. - #[inline] - pub fn any_normal(&self) -> bool { - !self.declarations_importance.all_true() - } - - /// Returns a `LonghandIdSet` representing the properties that are changed in - /// this block. - #[inline] - pub fn longhands(&self) -> &LonghandIdSet { - &self.property_ids.longhands - } - - /// Returns whether this block contains a declaration of a given property id. - #[inline] - pub fn contains(&self, id: PropertyDeclarationId) -> bool { - self.property_ids.contains(id) - } - - /// Returns whether this block contains any reset longhand. - #[inline] - pub fn contains_any_reset(&self) -> bool { - self.property_ids.longhands.contains_any_reset() - } - - /// Get a declaration for a given property. - /// - /// NOTE: This is linear time in the case of custom properties or in the - /// case the longhand is actually in the declaration block. - #[inline] - pub fn get( - &self, - property: PropertyDeclarationId, - ) -> Option<(&PropertyDeclaration, Importance)> { - if !self.contains(property) { - return None; - } - self.declaration_importance_iter() - .find(|(declaration, _)| declaration.id() == property) - } - - /// Tries to serialize a given shorthand from the declarations in this - /// block. - pub fn shorthand_to_css( - &self, - shorthand: ShorthandId, - dest: &mut CssStringWriter, - ) -> fmt::Result { - // Step 1.2.1 of - // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue - let mut list = SmallVec::<[&_; 10]>::new(); - let mut important_count = 0; - - // Step 1.2.2 - for longhand in shorthand.longhands() { - // Step 1.2.2.1 - let declaration = self.get(PropertyDeclarationId::Longhand(longhand)); - - // Step 1.2.2.2 & 1.2.2.3 - match declaration { - Some((declaration, importance)) => { - list.push(declaration); - if importance.important() { - important_count += 1; - } - }, - None => return Ok(()), - } - } - - // If there is one or more longhand with important, and one or more - // without important, we don't serialize it as a shorthand. - if important_count > 0 && important_count != list.len() { - return Ok(()); - } - - // Step 1.2.3 - // We don't print !important when serializing individual properties, - // so we treat this as a normal-importance property - match shorthand.get_shorthand_appendable_value(&list) { - Some(appendable_value) => append_declaration_value(dest, appendable_value), - None => return Ok(()), - } - } - - /// Find the value of the given property in this block and serialize it - /// - /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue> - pub fn property_value_to_css( - &self, - property: &PropertyId, - dest: &mut CssStringWriter, - ) -> fmt::Result { - // Step 1.1: done when parsing a string to PropertyId - - // Step 1.2 - let longhand_or_custom = match property.as_shorthand() { - Ok(shorthand) => return self.shorthand_to_css(shorthand, dest), - Err(longhand_or_custom) => longhand_or_custom, - }; - - if let Some((value, _importance)) = self.get(longhand_or_custom) { - // Step 2 - value.to_css(dest) - } else { - // Step 3 - Ok(()) - } - } - - /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertypriority> - pub fn property_priority(&self, property: &PropertyId) -> Importance { - // Step 1: done when parsing a string to PropertyId - - // Step 2 - match property.as_shorthand() { - Ok(shorthand) => { - // Step 2.1 & 2.2 & 2.3 - if shorthand.longhands().all(|l| { - self.get(PropertyDeclarationId::Longhand(l)) - .map_or(false, |(_, importance)| importance.important()) - }) { - Importance::Important - } else { - Importance::Normal - } - }, - Err(longhand_or_custom) => { - // Step 3 - self.get(longhand_or_custom) - .map_or(Importance::Normal, |(_, importance)| importance) - }, - } - } - - /// Adds or overrides the declaration for a given property in this block. - /// - /// See the documentation of `push` to see what impact `source` has when the - /// property is already there. - pub fn extend( - &mut self, - mut drain: SourcePropertyDeclarationDrain, - importance: Importance, - ) -> bool { - let all_shorthand_len = match drain.all_shorthand { - AllShorthand::NotSet => 0, - AllShorthand::CSSWideKeyword(_) | AllShorthand::WithVariables(_) => { - shorthands::ALL_SHORTHAND_MAX_LEN - }, - }; - let push_calls_count = drain.declarations.len() + all_shorthand_len; - - // With deduplication the actual length increase may be less than this. - self.declarations.reserve(push_calls_count); - - let mut changed = false; - for decl in &mut drain.declarations { - changed |= self.push(decl, importance); - } - drain - .all_shorthand - .declarations() - .fold(changed, |changed, decl| { - changed | self.push(decl, importance) - }) - } - - /// Adds or overrides the declaration for a given property in this block. - /// - /// Returns whether the declaration has changed. - /// - /// This is only used for parsing and internal use. - pub fn push(&mut self, declaration: PropertyDeclaration, importance: Importance) -> bool { - let id = declaration.id(); - if !self.property_ids.insert(id) { - let mut index_to_remove = None; - for (i, slot) in self.declarations.iter_mut().enumerate() { - if slot.id() != id { - continue; - } - - let important = self.declarations_importance[i]; - - // For declarations from parsing, non-important declarations - // shouldn't override existing important one. - if important && !importance.important() { - return false; - } - - index_to_remove = Some(i); - break; - } - - if let Some(index) = index_to_remove { - self.declarations.remove(index); - self.declarations_importance.remove(index); - self.declarations.push(declaration); - self.declarations_importance.push(importance.important()); - return true; - } - } - - self.declarations.push(declaration); - self.declarations_importance.push(importance.important()); - true - } - - /// Prepares updating this declaration block with the given - /// `SourcePropertyDeclaration` and importance, and returns whether - /// there is something to update. - pub fn prepare_for_update( - &self, - source_declarations: &SourcePropertyDeclaration, - importance: Importance, - updates: &mut SourcePropertyDeclarationUpdate, - ) -> bool { - debug_assert!(updates.updates.is_empty()); - // Check whether we are updating for an all shorthand change. - if !matches!(source_declarations.all_shorthand, AllShorthand::NotSet) { - debug_assert!(source_declarations.declarations.is_empty()); - return source_declarations - .all_shorthand - .declarations() - .any(|decl| { - !self.contains(decl.id()) || - self.declarations - .iter() - .enumerate() - .find(|&(_, ref d)| d.id() == decl.id()) - .map_or(true, |(i, d)| { - let important = self.declarations_importance[i]; - *d != decl || important != importance.important() - }) - }); - } - // Fill `updates` with update information. - let mut any_update = false; - let new_count = &mut updates.new_count; - let any_removal = &mut updates.any_removal; - let updates = &mut updates.updates; - updates.extend( - source_declarations - .declarations - .iter() - .map(|declaration| { - if !self.contains(declaration.id()) { - return DeclarationUpdate::Append; - } - let longhand_id = declaration.id().as_longhand(); - if let Some(longhand_id) = longhand_id { - if let Some(logical_group) = longhand_id.logical_group() { - let mut needs_append = false; - for (pos, decl) in self.declarations.iter().enumerate().rev() { - let id = match decl.id().as_longhand() { - Some(id) => id, - None => continue, - }; - if id == longhand_id { - if needs_append { - return DeclarationUpdate::AppendAndRemove { pos }; - } - let important = self.declarations_importance[pos]; - if decl == declaration && important == importance.important() { - return DeclarationUpdate::None; - } - return DeclarationUpdate::UpdateInPlace { pos }; - } - if !needs_append && - id.logical_group() == Some(logical_group) && - id.is_logical() != longhand_id.is_logical() - { - needs_append = true; - } - } - unreachable!("Longhand should be found in loop above"); - } - } - self.declarations - .iter() - .enumerate() - .find(|&(_, ref decl)| decl.id() == declaration.id()) - .map_or(DeclarationUpdate::Append, |(pos, decl)| { - let important = self.declarations_importance[pos]; - if decl == declaration && important == importance.important() { - DeclarationUpdate::None - } else { - DeclarationUpdate::UpdateInPlace { pos } - } - }) - }) - .inspect(|update| { - if matches!(update, DeclarationUpdate::None) { - return; - } - any_update = true; - match update { - DeclarationUpdate::Append => { - *new_count += 1; - }, - DeclarationUpdate::AppendAndRemove { .. } => { - *any_removal = true; - }, - _ => {}, - } - }), - ); - any_update - } - - /// Update this declaration block with the given data. - pub fn update( - &mut self, - drain: SourcePropertyDeclarationDrain, - importance: Importance, - updates: &mut SourcePropertyDeclarationUpdate, - ) { - let important = importance.important(); - if !matches!(drain.all_shorthand, AllShorthand::NotSet) { - debug_assert!(updates.updates.is_empty()); - for decl in drain.all_shorthand.declarations() { - let id = decl.id(); - if self.property_ids.insert(id) { - self.declarations.push(decl); - self.declarations_importance.push(important); - } else { - let (idx, slot) = self - .declarations - .iter_mut() - .enumerate() - .find(|&(_, ref d)| d.id() == decl.id()) - .unwrap(); - *slot = decl; - self.declarations_importance.set(idx, important); - } - } - return; - } - - self.declarations.reserve(updates.new_count); - if updates.any_removal { - // Prepare for removal and fixing update positions. - struct UpdateOrRemoval<'a> { - item: &'a mut DeclarationUpdate, - pos: usize, - remove: bool, - } - let mut updates_and_removals: SubpropertiesVec<UpdateOrRemoval> = updates - .updates - .iter_mut() - .filter_map(|item| { - let (pos, remove) = match *item { - DeclarationUpdate::UpdateInPlace { pos } => (pos, false), - DeclarationUpdate::AppendAndRemove { pos } => (pos, true), - _ => return None, - }; - Some(UpdateOrRemoval { item, pos, remove }) - }) - .collect(); - // Execute removals. It's important to do it in reverse index order, - // so that removing doesn't invalidate following positions. - updates_and_removals.sort_unstable_by_key(|update| update.pos); - updates_and_removals - .iter() - .rev() - .filter(|update| update.remove) - .for_each(|update| { - self.declarations.remove(update.pos); - self.declarations_importance.remove(update.pos); - }); - // Fixup pos field for updates. - let mut removed_count = 0; - for update in updates_and_removals.iter_mut() { - if update.remove { - removed_count += 1; - continue; - } - debug_assert_eq!( - *update.item, - DeclarationUpdate::UpdateInPlace { pos: update.pos } - ); - *update.item = DeclarationUpdate::UpdateInPlace { - pos: update.pos - removed_count, - }; - } - } - // Execute updates and appends. - for (decl, update) in drain.declarations.zip_eq(updates.updates.iter()) { - match *update { - DeclarationUpdate::None => {}, - DeclarationUpdate::Append | DeclarationUpdate::AppendAndRemove { .. } => { - self.property_ids.insert(decl.id()); - self.declarations.push(decl); - self.declarations_importance.push(important); - }, - DeclarationUpdate::UpdateInPlace { pos } => { - self.declarations[pos] = decl; - self.declarations_importance.set(pos, important); - }, - } - } - updates.updates.clear(); - } - - /// Returns the first declaration that would be removed by removing - /// `property`. - #[inline] - pub fn first_declaration_to_remove(&self, property: &PropertyId) -> Option<usize> { - if let Err(longhand_or_custom) = property.as_shorthand() { - if !self.contains(longhand_or_custom) { - return None; - } - } - - self.declarations - .iter() - .position(|declaration| declaration.id().is_or_is_longhand_of(property)) - } - - /// Removes a given declaration at a given index. - #[inline] - fn remove_declaration_at(&mut self, i: usize) { - self.property_ids.remove(self.declarations[i].id()); - self.declarations_importance.remove(i); - self.declarations.remove(i); - } - - /// Clears all the declarations from this block. - #[inline] - pub fn clear(&mut self) { - self.declarations_importance.clear(); - self.declarations.clear(); - self.property_ids.clear(); - } - - /// <https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-removeproperty> - /// - /// `first_declaration` needs to be the result of - /// `first_declaration_to_remove`. - #[inline] - pub fn remove_property(&mut self, property: &PropertyId, first_declaration: usize) { - debug_assert_eq!( - Some(first_declaration), - self.first_declaration_to_remove(property) - ); - debug_assert!(self.declarations[first_declaration] - .id() - .is_or_is_longhand_of(property)); - - self.remove_declaration_at(first_declaration); - - let shorthand = match property.as_shorthand() { - Ok(s) => s, - Err(_longhand_or_custom) => return, - }; - - let mut i = first_declaration; - let mut len = self.len(); - while i < len { - if !self.declarations[i].id().is_longhand_of(shorthand) { - i += 1; - continue; - } - - self.remove_declaration_at(i); - len -= 1; - } - } - - /// Take a declaration block known to contain a single property and serialize it. - pub fn single_value_to_css( - &self, - property: &PropertyId, - dest: &mut CssStringWriter, - computed_values: Option<&ComputedValues>, - custom_properties_block: Option<&PropertyDeclarationBlock>, - device: &Device, - ) -> fmt::Result { - if let Ok(shorthand) = property.as_shorthand() { - return self.shorthand_to_css(shorthand, dest); - } - - // FIXME(emilio): Should this assert, or assert that the declaration is - // the property we expect? - let declaration = match self.declarations.get(0) { - Some(d) => d, - None => return Err(fmt::Error), - }; - - let custom_properties = if let Some(cv) = computed_values { - // If there are extra custom properties for this declaration block, - // factor them in too. - if let Some(block) = custom_properties_block { - // FIXME(emilio): This is not super-efficient here, and all this - // feels like a hack anyway... - block.cascade_custom_properties(cv.custom_properties(), device) - } else { - cv.custom_properties().cloned() - } - } else { - None - }; - - match (declaration, computed_values) { - // If we have a longhand declaration with variables, those variables - // will be stored as unparsed values. - // - // As a temporary measure to produce sensible results in Gecko's - // getKeyframes() implementation for CSS animations, if - // |computed_values| is supplied, we use it to expand such variable - // declarations. This will be fixed properly in Gecko bug 1391537. - (&PropertyDeclaration::WithVariables(ref declaration), Some(ref computed_values)) => { - declaration - .value - .substitute_variables( - declaration.id, - computed_values.writing_mode, - custom_properties.as_ref(), - QuirksMode::NoQuirks, - device, - &mut Default::default(), - ) - .to_css(dest) - }, - (ref d, _) => d.to_css(dest), - } - } - - /// Convert AnimationValueMap to PropertyDeclarationBlock. - pub fn from_animation_value_map(animation_value_map: &AnimationValueMap) -> Self { - let len = animation_value_map.len(); - let mut declarations = ThinVec::with_capacity(len); - let mut property_ids = PropertyDeclarationIdSet::default(); - - for (property, animation_value) in animation_value_map.iter() { - property_ids.longhands.insert(*property); - declarations.push(animation_value.uncompute()); - } - - PropertyDeclarationBlock { - declarations, - property_ids, - declarations_importance: SmallBitVec::from_elem(len, false), - } - } - - /// Returns true if the declaration block has a CSSWideKeyword for the given - /// property. - pub fn has_css_wide_keyword(&self, property: &PropertyId) -> bool { - if let Err(longhand_or_custom) = property.as_shorthand() { - if !self.property_ids.contains(longhand_or_custom) { - return false; - } - } - self.declarations.iter().any(|decl| { - decl.id().is_or_is_longhand_of(property) && decl.get_css_wide_keyword().is_some() - }) - } - - /// Returns a custom properties map which is the result of cascading custom - /// properties in this declaration block along with context's custom - /// properties. - pub fn cascade_custom_properties_with_context( - &self, - context: &Context, - ) -> Option<Arc<crate::custom_properties::CustomPropertiesMap>> { - self.cascade_custom_properties(context.style().custom_properties(), context.device()) - } - - /// Returns a custom properties map which is the result of cascading custom - /// properties in this declaration block along with the given custom - /// properties. - fn cascade_custom_properties( - &self, - inherited_custom_properties: Option<&Arc<crate::custom_properties::CustomPropertiesMap>>, - device: &Device, - ) -> Option<Arc<crate::custom_properties::CustomPropertiesMap>> { - let mut builder = CustomPropertiesBuilder::new(inherited_custom_properties, device); - - for declaration in self.normal_declaration_iter() { - if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade( - declaration, - CascadePriority::new( - CascadeLevel::same_tree_author_normal(), - LayerOrder::root(), - ), - ); - } - } - - builder.build() - } - - /// Like the method on ToCss, but without the type parameter to avoid - /// accidentally monomorphizing this large function multiple times for - /// different writers. - /// - /// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block - pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { - let mut is_first_serialization = true; // trailing serializations should have a prepended space - - // Step 1 -> dest = result list - - // Step 2 - // - // NOTE(emilio): We reuse this set for both longhands and shorthands - // with subtly different meaning. For longhands, only longhands that - // have actually been serialized (either by themselves, or as part of a - // shorthand) appear here. For shorthands, all the shorthands that we've - // attempted to serialize appear here. - let mut already_serialized = NonCustomPropertyIdSet::new(); - - // Step 3 - 'declaration_loop: for (declaration, importance) in self.declaration_importance_iter() { - // Step 3.1 - let property = declaration.id(); - let longhand_id = match property { - PropertyDeclarationId::Longhand(id) => id, - PropertyDeclarationId::Custom(..) => { - // Given the invariants that there are no duplicate - // properties in a declaration block, and that custom - // properties can't be part of a shorthand, we can just care - // about them here. - append_serialization( - dest, - &property, - AppendableValue::Declaration(declaration), - importance, - &mut is_first_serialization, - )?; - continue; - }, - }; - - // Step 3.2 - if already_serialized.contains(longhand_id.into()) { - continue; - } - - // Steps 3.3 & 3.4 - for shorthand in longhand_id.shorthands() { - // We already attempted to serialize this shorthand before. - if already_serialized.contains(shorthand.into()) { - continue; - } - already_serialized.insert(shorthand.into()); - - if shorthand.is_legacy_shorthand() { - continue; - } - - // Step 3.3.1: - // Let longhands be an array consisting of all CSS - // declarations in declaration block’s declarations that - // that are not in already serialized and have a property - // name that maps to one of the shorthand properties in - // shorthands. - let longhands = { - // TODO(emilio): This could just index in an array if we - // remove pref-controlled longhands. - let mut ids = LonghandIdSet::new(); - for longhand in shorthand.longhands() { - ids.insert(longhand); - } - ids - }; - - // Step 3.4.2 - // If all properties that map to shorthand are not present - // in longhands, continue with the steps labeled shorthand - // loop. - if !self.property_ids.longhands.contains_all(&longhands) { - continue; - } - - // Step 3.4.3: - // Let current longhands be an empty array. - let mut current_longhands = SmallVec::<[&_; 10]>::new(); - let mut logical_groups = LogicalGroupSet::new(); - let mut saw_one = false; - let mut logical_mismatch = false; - let mut seen = LonghandIdSet::new(); - let mut important_count = 0; - - // Step 3.4.4: - // Append all CSS declarations in longhands that have a - // property name that maps to shorthand to current longhands. - for (declaration, importance) in self.declaration_importance_iter() { - let longhand = match declaration.id() { - PropertyDeclarationId::Longhand(id) => id, - PropertyDeclarationId::Custom(..) => continue, - }; - - if longhands.contains(longhand) { - saw_one = true; - if importance.important() { - important_count += 1; - } - current_longhands.push(declaration); - if shorthand != ShorthandId::All { - // All is special because it contains both physical - // and logical longhands. - if let Some(g) = longhand.logical_group() { - logical_groups.insert(g); - } - seen.insert(longhand); - if seen == longhands { - break; - } - } - } else if saw_one { - if let Some(g) = longhand.logical_group() { - if logical_groups.contains(g) { - logical_mismatch = true; - break; - } - } - } - } - - // 3.4.5: - // If there is one or more CSS declarations in current - // longhands have their important flag set and one or more - // with it unset, continue with the steps labeled shorthand - // loop. - let is_important = important_count > 0; - if is_important && important_count != current_longhands.len() { - continue; - } - - // 3.4.6: - // If there’s any declaration in declaration block in between - // the first and the last longhand in current longhands which - // belongs to the same logical property group, but has a - // different mapping logic as any of the longhands in current - // longhands, and is not in current longhands, continue with - // the steps labeled shorthand loop. - if logical_mismatch { - continue; - } - - let importance = if is_important { - Importance::Important - } else { - Importance::Normal - }; - - // 3.4.7: - // Let value be the result of invoking serialize a CSS value - // of current longhands. - let appendable_value = - match shorthand.get_shorthand_appendable_value(¤t_longhands) { - None => continue, - Some(appendable_value) => appendable_value, - }; - - // We avoid re-serializing if we're already an - // AppendableValue::Css. - let mut v = CssString::new(); - let value = match appendable_value { - AppendableValue::Css(css) => { - debug_assert!(!css.is_empty()); - appendable_value - }, - other => { - append_declaration_value(&mut v, other)?; - - // 3.4.8: - // If value is the empty string, continue with the - // steps labeled shorthand loop. - if v.is_empty() { - continue; - } - - AppendableValue::Css({ - // Safety: serialization only generates valid utf-8. - #[cfg(feature = "gecko")] - unsafe { - v.as_str_unchecked() - } - #[cfg(feature = "servo")] - &v - }) - }, - }; - - // 3.4.9: - // Let serialized declaration be the result of invoking - // serialize a CSS declaration with property name shorthand, - // value value, and the important flag set if the CSS - // declarations in current longhands have their important - // flag set. - // - // 3.4.10: - // Append serialized declaration to list. - append_serialization( - dest, - &shorthand, - value, - importance, - &mut is_first_serialization, - )?; - - // 3.4.11: - // Append the property names of all items of current - // longhands to already serialized. - for current_longhand in ¤t_longhands { - let longhand_id = match current_longhand.id() { - PropertyDeclarationId::Longhand(id) => id, - PropertyDeclarationId::Custom(..) => unreachable!(), - }; - - // Substep 9 - already_serialized.insert(longhand_id.into()); - } - - // 3.4.12: - // Continue with the steps labeled declaration loop. - continue 'declaration_loop; - } - - // Steps 3.5, 3.6 & 3.7: - // Let value be the result of invoking serialize a CSS value of - // declaration. - // - // Let serialized declaration be the result of invoking - // serialize a CSS declaration with property name property, - // value value, and the important flag set if declaration has - // its important flag set. - // - // Append serialized declaration to list. - append_serialization( - dest, - &property, - AppendableValue::Declaration(declaration), - importance, - &mut is_first_serialization, - )?; - - // Step 3.8: - // Append property to already serialized. - already_serialized.insert(longhand_id.into()); - } - - // Step 4 - Ok(()) - } -} - -/// A convenient enum to represent different kinds of stuff that can represent a -/// _value_ in the serialization of a property declaration. -pub enum AppendableValue<'a, 'b: 'a> { - /// A given declaration, of which we'll serialize just the value. - Declaration(&'a PropertyDeclaration), - /// A set of declarations for a given shorthand. - /// - /// FIXME: This needs more docs, where are the shorthands expanded? We print - /// the property name before-hand, don't we? - DeclarationsForShorthand(ShorthandId, &'a [&'b PropertyDeclaration]), - /// A raw CSS string, coming for example from a property with CSS variables, - /// or when storing a serialized shorthand value before appending directly. - Css(&'a str), -} - -/// Potentially appends whitespace after the first (property: value;) pair. -fn handle_first_serialization<W>(dest: &mut W, is_first_serialization: &mut bool) -> fmt::Result -where - W: Write, -{ - if !*is_first_serialization { - dest.write_char(' ') - } else { - *is_first_serialization = false; - Ok(()) - } -} - -/// Append a given kind of appendable value to a serialization. -pub fn append_declaration_value<'a, 'b: 'a>( - dest: &mut CssStringWriter, - appendable_value: AppendableValue<'a, 'b>, -) -> fmt::Result { - match appendable_value { - AppendableValue::Css(css) => dest.write_str(css), - AppendableValue::Declaration(decl) => decl.to_css(dest), - AppendableValue::DeclarationsForShorthand(shorthand, decls) => { - shorthand.longhands_to_css(decls, dest) - }, - } -} - -/// Append a given property and value pair to a serialization. -pub fn append_serialization<'a, 'b: 'a, N>( - dest: &mut CssStringWriter, - property_name: &N, - appendable_value: AppendableValue<'a, 'b>, - importance: Importance, - is_first_serialization: &mut bool, -) -> fmt::Result -where - N: ToCss, -{ - handle_first_serialization(dest, is_first_serialization)?; - - property_name.to_css(&mut CssWriter::new(dest))?; - dest.write_str(": ")?; - - append_declaration_value(dest, appendable_value)?; - - if importance.important() { - dest.write_str(" !important")?; - } - - dest.write_char(';') -} - -/// A helper to parse the style attribute of an element, in order for this to be -/// shared between Servo and Gecko. -/// -/// Inline because we call this cross-crate. -#[inline] -pub fn parse_style_attribute( - input: &str, - url_data: &UrlExtraData, - error_reporter: Option<&dyn ParseErrorReporter>, - quirks_mode: QuirksMode, - rule_type: CssRuleType, -) -> PropertyDeclarationBlock { - let context = ParserContext::new( - Origin::Author, - url_data, - Some(rule_type), - ParsingMode::DEFAULT, - quirks_mode, - /* namespaces = */ Default::default(), - error_reporter, - None, - ); - - let mut input = ParserInput::new(input); - parse_property_declaration_list(&context, &mut Parser::new(&mut input), None) -} - -/// Parse a given property declaration. Can result in multiple -/// `PropertyDeclaration`s when expanding a shorthand, for example. -/// -/// This does not attempt to parse !important at all. -#[inline] -pub fn parse_one_declaration_into( - declarations: &mut SourcePropertyDeclaration, - id: PropertyId, - input: &str, - origin: Origin, - url_data: &UrlExtraData, - error_reporter: Option<&dyn ParseErrorReporter>, - parsing_mode: ParsingMode, - quirks_mode: QuirksMode, - rule_type: CssRuleType, -) -> Result<(), ()> { - let context = ParserContext::new( - origin, - url_data, - Some(rule_type), - parsing_mode, - quirks_mode, - /* namespaces = */ Default::default(), - error_reporter, - None, - ); - - let property_id_for_error_reporting = if context.error_reporting_enabled() { - Some(id.clone()) - } else { - None - }; - - let mut input = ParserInput::new(input); - let mut parser = Parser::new(&mut input); - let start_position = parser.position(); - parser - .parse_entirely(|parser| { - PropertyDeclaration::parse_into(declarations, id, &context, parser) - }) - .map_err(|err| { - if context.error_reporting_enabled() { - report_one_css_error( - &context, - None, - None, - err, - parser.slice_from(start_position), - property_id_for_error_reporting, - ) - } - }) -} - -/// A struct to parse property declarations. -struct PropertyDeclarationParser<'a, 'b: 'a, 'i> { - context: &'a ParserContext<'b>, - state: &'a mut DeclarationParserState<'i>, -} - -/// The state needed to parse a declaration block. -/// -/// It stores declarations in output_block. -#[derive(Default)] -pub struct DeclarationParserState<'i> { - /// The output block where results are stored. - output_block: PropertyDeclarationBlock, - /// Declarations from the last declaration parsed. (note that a shorthand might expand to - /// multiple declarations). - declarations: SourcePropertyDeclaration, - /// The importance from the last declaration parsed. - importance: Importance, - /// A list of errors that have happened so far. Not all of them might be reported. - errors: SmallParseErrorVec<'i>, - /// The last parsed property id, if any. - last_parsed_property_id: Option<PropertyId>, -} - -impl<'i> DeclarationParserState<'i> { - /// Returns whether any parsed declarations have been parsed so far. - pub fn has_parsed_declarations(&self) -> bool { - !self.output_block.is_empty() - } - - /// Takes the parsed declarations. - pub fn take_declarations(&mut self) -> PropertyDeclarationBlock { - std::mem::take(&mut self.output_block) - } - - /// Parse a single declaration value. - pub fn parse_value<'t>( - &mut self, - context: &ParserContext, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - let id = match PropertyId::parse(&name, context) { - Ok(id) => id, - Err(..) => { - return Err(input.new_custom_error(StyleParseErrorKind::UnknownProperty(name))); - }, - }; - if context.error_reporting_enabled() { - self.last_parsed_property_id = Some(id.clone()); - } - input.parse_until_before(Delimiter::Bang, |input| { - PropertyDeclaration::parse_into(&mut self.declarations, id, context, input) - })?; - self.importance = match input.try_parse(parse_important) { - Ok(()) => Importance::Important, - Err(_) => Importance::Normal, - }; - // In case there is still unparsed text in the declaration, we should roll back. - input.expect_exhausted()?; - self.output_block - .extend(self.declarations.drain(), self.importance); - // We've successfully parsed a declaration, so forget about - // `last_parsed_property_id`. It'd be wrong to associate any - // following error with this property. - self.last_parsed_property_id = None; - Ok(()) - } - - /// Reports any CSS errors that have ocurred if needed. - #[inline] - pub fn report_errors_if_needed( - &mut self, - context: &ParserContext, - selectors: Option<&SelectorList<SelectorImpl>>, - ) { - if self.errors.is_empty() { - return; - } - self.do_report_css_errors(context, selectors); - } - - #[cold] - fn do_report_css_errors( - &mut self, - context: &ParserContext, - selectors: Option<&SelectorList<SelectorImpl>>, - ) { - for (error, slice, property) in self.errors.drain(..) { - report_one_css_error( - context, - Some(&self.output_block), - selectors, - error, - slice, - property, - ) - } - } - - /// Resets the declaration parser state, and reports the error if needed. - #[inline] - pub fn did_error(&mut self, context: &ParserContext, error: ParseError<'i>, slice: &'i str) { - self.declarations.clear(); - if !context.error_reporting_enabled() { - return; - } - let property = self.last_parsed_property_id.take(); - self.errors.push((error, slice, property)); - } -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -/// Default methods reject all rules. -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -/// Based on NonMozillaVendorIdentifier from Gecko's CSS parser. -fn is_non_mozilla_vendor_identifier(name: &str) -> bool { - (name.starts_with("-") && !name.starts_with("-moz-")) || name.starts_with("_") -} - -impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyDeclarationParser<'a, 'b, 'i> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - self.state.parse_value(self.context, name, input) - } -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for PropertyDeclarationParser<'a, 'b, 'i> -{ - fn parse_declarations(&self) -> bool { - true - } - // TODO(emilio): Nesting. - fn parse_qualified(&self) -> bool { - false - } -} - -type SmallParseErrorVec<'i> = SmallVec<[(ParseError<'i>, &'i str, Option<PropertyId>); 2]>; - -fn alias_of_known_property(name: &str) -> Option<PropertyId> { - let mut prefixed = String::with_capacity(name.len() + 5); - prefixed.push_str("-moz-"); - prefixed.push_str(name); - PropertyId::parse_enabled_for_all_content(&prefixed).ok() -} - -#[cold] -fn report_one_css_error<'i>( - context: &ParserContext, - block: Option<&PropertyDeclarationBlock>, - selectors: Option<&SelectorList<SelectorImpl>>, - mut error: ParseError<'i>, - slice: &str, - property: Option<PropertyId>, -) { - debug_assert!(context.error_reporting_enabled()); - - fn all_properties_in_block(block: &PropertyDeclarationBlock, property: &PropertyId) -> bool { - match property.as_shorthand() { - Ok(id) => id - .longhands() - .all(|longhand| block.contains(PropertyDeclarationId::Longhand(longhand))), - Err(longhand_or_custom) => block.contains(longhand_or_custom), - } - } - - if let ParseErrorKind::Custom(StyleParseErrorKind::UnknownProperty(ref name)) = error.kind { - if is_non_mozilla_vendor_identifier(name) { - // If the unrecognized property looks like a vendor-specific property, - // silently ignore it instead of polluting the error output. - return; - } - if let Some(alias) = alias_of_known_property(name) { - // This is an unknown property, but its -moz-* version is known. - // We don't want to report error if the -moz-* version is already - // specified. - if let Some(block) = block { - if all_properties_in_block(block, &alias) { - return; - } - } - } - } - - if let Some(ref property) = property { - if let Some(block) = block { - if all_properties_in_block(block, property) { - return; - } - } - error = match *property { - PropertyId::Custom(ref c) => { - StyleParseErrorKind::new_invalid(format!("--{}", c), error) - }, - _ => StyleParseErrorKind::new_invalid(property.non_custom_id().unwrap().name(), error), - }; - } - - let location = error.location; - let error = ContextualParseError::UnsupportedPropertyDeclaration(slice, error, selectors); - context.log_css_error(location, error); -} - -/// Parse a list of property declarations and return a property declaration -/// block. -pub fn parse_property_declaration_list( - context: &ParserContext, - input: &mut Parser, - selectors: Option<&SelectorList<SelectorImpl>>, -) -> PropertyDeclarationBlock { - let mut state = DeclarationParserState::default(); - let mut parser = PropertyDeclarationParser { - context, - state: &mut state, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - match declaration { - Ok(()) => {}, - Err((error, slice)) => iter.parser.state.did_error(context, error, slice), - } - } - parser.state.report_errors_if_needed(context, selectors); - state.output_block -} diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs deleted file mode 100644 index 4fe290980d8..00000000000 --- a/components/style/properties/gecko.mako.rs +++ /dev/null @@ -1,1971 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -// `data` comes from components/style/properties.mako.rs; see build.rs for more details. - -<%! - from data import to_camel_case, to_camel_case_lower - from data import Keyword -%> -<%namespace name="helpers" file="/helpers.mako.rs" /> - -use crate::Atom; -use app_units::Au; -use crate::computed_value_flags::*; -use crate::custom_properties::CustomPropertiesMap; -use crate::gecko_bindings::bindings; -% for style_struct in data.style_structs: -use crate::gecko_bindings::bindings::Gecko_Construct_Default_${style_struct.gecko_ffi_name}; -use crate::gecko_bindings::bindings::Gecko_CopyConstruct_${style_struct.gecko_ffi_name}; -use crate::gecko_bindings::bindings::Gecko_Destroy_${style_struct.gecko_ffi_name}; -% endfor -use crate::gecko_bindings::bindings::Gecko_CopyCounterStyle; -use crate::gecko_bindings::bindings::Gecko_EnsureImageLayersLength; -use crate::gecko_bindings::bindings::Gecko_nsStyleFont_SetLang; -use crate::gecko_bindings::bindings::Gecko_nsStyleFont_CopyLangFrom; -use crate::gecko_bindings::structs; -use crate::gecko_bindings::structs::nsCSSPropertyID; -use crate::gecko_bindings::structs::mozilla::PseudoStyleType; -use crate::gecko::data::PerDocumentStyleData; -use crate::logical_geometry::WritingMode; -use crate::media_queries::Device; -use crate::properties::longhands; -use crate::rule_tree::StrongRuleNode; -use crate::selector_parser::PseudoElement; -use servo_arc::{Arc, UniqueArc}; -use std::mem::{forget, MaybeUninit, ManuallyDrop}; -use std::{cmp, ops, ptr}; -use crate::values::{self, CustomIdent, KeyframesName}; -use crate::values::computed::{BorderStyle, Percentage, Time, TransitionProperty}; -use crate::values::computed::font::FontSize; -use crate::values::generics::column::ColumnCount; - - -pub mod style_structs { - % for style_struct in data.style_structs: - pub use super::${style_struct.gecko_struct_name} as ${style_struct.name}; - - unsafe impl Send for ${style_struct.name} {} - unsafe impl Sync for ${style_struct.name} {} - % endfor -} - -/// FIXME(emilio): This is completely duplicated with the other properties code. -pub type ComputedValuesInner = structs::ServoComputedData; - -#[repr(C)] -pub struct ComputedValues(structs::mozilla::ComputedStyle); - -impl ComputedValues { - #[inline] - pub (crate) fn as_gecko_computed_style(&self) -> &structs::ComputedStyle { - &self.0 - } - - pub fn new( - pseudo: Option<<&PseudoElement>, - custom_properties: Option<Arc<CustomPropertiesMap>>, - writing_mode: WritingMode, - flags: ComputedValueFlags, - rules: Option<StrongRuleNode>, - visited_style: Option<Arc<ComputedValues>>, - % for style_struct in data.style_structs: - ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, - % endfor - ) -> Arc<Self> { - ComputedValuesInner::new( - custom_properties, - writing_mode, - flags, - rules, - visited_style, - % for style_struct in data.style_structs: - ${style_struct.ident}, - % endfor - ).to_outer(pseudo) - } - - pub fn default_values(doc: &structs::Document) -> Arc<Self> { - ComputedValuesInner::new( - /* custom_properties = */ None, - /* writing_mode = */ WritingMode::empty(), // FIXME(bz): This seems dubious - ComputedValueFlags::empty(), - /* rules = */ None, - /* visited_style = */ None, - % for style_struct in data.style_structs: - style_structs::${style_struct.name}::default(doc), - % endfor - ).to_outer(None) - } - - /// Converts the computed values to an Arc<> from a reference. - pub fn to_arc(&self) -> Arc<Self> { - // SAFETY: We're guaranteed to be allocated as an Arc<> since the - // functions above are the only ones that create ComputedValues - // instances in Gecko (and that must be the case since ComputedValues' - // member is private). - unsafe { Arc::from_raw_addrefed(self) } - } - - #[inline] - pub fn is_pseudo_style(&self) -> bool { - self.0.mPseudoType != PseudoStyleType::NotPseudo - } - - #[inline] - pub fn pseudo(&self) -> Option<PseudoElement> { - if !self.is_pseudo_style() { - return None; - } - PseudoElement::from_pseudo_type(self.0.mPseudoType) - } - - #[inline] - pub fn is_first_line_style(&self) -> bool { - self.pseudo() == Some(PseudoElement::FirstLine) - } - - /// Returns true if the display property is changed from 'none' to others. - pub fn is_display_property_changed_from_none( - &self, - old_values: Option<<&ComputedValues> - ) -> bool { - use crate::properties::longhands::display::computed_value::T as Display; - - old_values.map_or(false, |old| { - let old_display_style = old.get_box().clone_display(); - let new_display_style = self.get_box().clone_display(); - old_display_style == Display::None && - new_display_style != Display::None - }) - } - -} - -impl Drop for ComputedValues { - fn drop(&mut self) { - // XXX this still relies on the destructor of ComputedValuesInner to run on the rust side, - // that's pretty wild. - unsafe { - bindings::Gecko_ComputedStyle_Destroy(&mut self.0); - } - } -} - -unsafe impl Sync for ComputedValues {} -unsafe impl Send for ComputedValues {} - -impl Clone for ComputedValues { - fn clone(&self) -> Self { - unreachable!() - } -} - -impl Clone for ComputedValuesInner { - fn clone(&self) -> Self { - ComputedValuesInner { - % for style_struct in data.style_structs: - ${style_struct.gecko_name}: Arc::into_raw(unsafe { Arc::from_raw_addrefed(self.${style_struct.name_lower}_ptr()) }) as *const _, - % endfor - custom_properties: self.custom_properties.clone(), - writing_mode: self.writing_mode.clone(), - flags: self.flags.clone(), - rules: self.rules.clone(), - visited_style: if self.visited_style.is_null() { - ptr::null() - } else { - Arc::into_raw(unsafe { Arc::from_raw_addrefed(self.visited_style_ptr()) }) as *const _ - }, - } - } -} - - -impl Drop for ComputedValuesInner { - fn drop(&mut self) { - % for style_struct in data.style_structs: - let _ = unsafe { Arc::from_raw(self.${style_struct.name_lower}_ptr()) }; - % endfor - if !self.visited_style.is_null() { - let _ = unsafe { Arc::from_raw(self.visited_style_ptr()) }; - } - } -} - -impl ComputedValuesInner { - pub fn new( - custom_properties: Option<Arc<CustomPropertiesMap>>, - writing_mode: WritingMode, - flags: ComputedValueFlags, - rules: Option<StrongRuleNode>, - visited_style: Option<Arc<ComputedValues>>, - % for style_struct in data.style_structs: - ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, - % endfor - ) -> Self { - Self { - custom_properties, - writing_mode, - rules, - visited_style: visited_style.map_or(ptr::null(), |p| Arc::into_raw(p)) as *const _, - flags, - % for style_struct in data.style_structs: - ${style_struct.gecko_name}: Arc::into_raw(${style_struct.ident}) as *const _, - % endfor - } - } - - fn to_outer(self, pseudo: Option<<&PseudoElement>) -> Arc<ComputedValues> { - let pseudo_ty = match pseudo { - Some(p) => p.pseudo_type(), - None => structs::PseudoStyleType::NotPseudo, - }; - unsafe { - let mut arc = UniqueArc::<ComputedValues>::new_uninit(); - bindings::Gecko_ComputedStyle_Init( - arc.as_mut_ptr() as *mut _, - &self, - pseudo_ty, - ); - // We're simulating move semantics by having C++ do a memcpy and - // then forgetting it on this end. - forget(self); - UniqueArc::assume_init(arc).shareable() - } - } -} - -impl ops::Deref for ComputedValues { - type Target = ComputedValuesInner; - #[inline] - fn deref(&self) -> &ComputedValuesInner { - &self.0.mSource - } -} - -impl ops::DerefMut for ComputedValues { - #[inline] - fn deref_mut(&mut self) -> &mut ComputedValuesInner { - &mut self.0.mSource - } -} - -impl ComputedValuesInner { - /// Returns true if the value of the `content` property would make a - /// pseudo-element not rendered. - #[inline] - pub fn ineffective_content_property(&self) -> bool { - self.get_counters().ineffective_content_property() - } - - #[inline] - fn visited_style_ptr(&self) -> *const ComputedValues { - self.visited_style as *const _ - } - - /// Returns the visited style, if any. - pub fn visited_style(&self) -> Option<<&ComputedValues> { - unsafe { self.visited_style_ptr().as_ref() } - } - - % for style_struct in data.style_structs: - #[inline] - fn ${style_struct.name_lower}_ptr(&self) -> *const style_structs::${style_struct.name} { - // This is sound because the wrapper we create is repr(transparent). - self.${style_struct.gecko_name} as *const _ - } - - #[inline] - pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> { - unsafe { Arc::from_raw_addrefed(self.${style_struct.name_lower}_ptr()) } - } - #[inline] - pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { - unsafe { &*self.${style_struct.name_lower}_ptr() } - } - - #[inline] - pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { - unsafe { - let mut arc = Arc::from_raw(self.${style_struct.name_lower}_ptr()); - let ptr = Arc::make_mut(&mut arc) as *mut _; - // Sound for the same reason _ptr() is sound. - self.${style_struct.gecko_name} = Arc::into_raw(arc) as *const _; - &mut *ptr - } - } - % endfor -} - -<%def name="impl_simple_setter(ident, gecko_ffi_name)"> - #[allow(non_snake_case)] - pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - ${set_gecko_property(gecko_ffi_name, "From::from(v)")} - } -</%def> - -<%def name="impl_simple_clone(ident, gecko_ffi_name)"> - #[allow(non_snake_case)] - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - From::from(self.${gecko_ffi_name}.clone()) - } -</%def> - -<%def name="impl_simple_copy(ident, gecko_ffi_name, *kwargs)"> - #[allow(non_snake_case)] - pub fn copy_${ident}_from(&mut self, other: &Self) { - self.${gecko_ffi_name} = other.${gecko_ffi_name}.clone(); - } - - #[allow(non_snake_case)] - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } -</%def> - -<%! -def get_gecko_property(ffi_name, self_param = "self"): - return "%s.%s" % (self_param, ffi_name) - -def set_gecko_property(ffi_name, expr): - return "self.%s = %s;" % (ffi_name, expr) -%> - -<%def name="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type='u8')"> - #[allow(non_snake_case)] - pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - use crate::properties::longhands::${ident}::computed_value::T as Keyword; - // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts - let result = match v { - % for value in keyword.values_for('gecko'): - Keyword::${to_camel_case(value)} => - structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)}, - % endfor - }; - ${set_gecko_property(gecko_ffi_name, "result")} - } -</%def> - -<%def name="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type='u8')"> - #[allow(non_snake_case)] - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - use crate::properties::longhands::${ident}::computed_value::T as Keyword; - // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts - - // Some constant macros in the gecko are defined as negative integer(e.g. font-stretch). - // And they are convert to signed integer in Rust bindings. We need to cast then - // as signed type when we have both signed/unsigned integer in order to use them - // as match's arms. - // Also, to use same implementation here we use casted constant if we have only singed values. - % if keyword.gecko_enum_prefix is None: - % for value in keyword.values_for('gecko'): - const ${keyword.casted_constant_name(value, cast_type)} : ${cast_type} = - structs::${keyword.gecko_constant(value)} as ${cast_type}; - % endfor - - match ${get_gecko_property(gecko_ffi_name)} as ${cast_type} { - % for value in keyword.values_for('gecko'): - ${keyword.casted_constant_name(value, cast_type)} => Keyword::${to_camel_case(value)}, - % endfor - % if keyword.gecko_inexhaustive: - _ => panic!("Found unexpected value in style struct for ${ident} property"), - % endif - } - % else: - match ${get_gecko_property(gecko_ffi_name)} { - % for value in keyword.values_for('gecko'): - structs::${keyword.gecko_constant(value)} => Keyword::${to_camel_case(value)}, - % endfor - % if keyword.gecko_inexhaustive: - _ => panic!("Found unexpected value in style struct for ${ident} property"), - % endif - } - % endif - } -</%def> - -<%def name="impl_keyword(ident, gecko_ffi_name, keyword, cast_type='u8', **kwargs)"> -<%call expr="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type, **kwargs)"></%call> -<%call expr="impl_simple_copy(ident, gecko_ffi_name, **kwargs)"></%call> -<%call expr="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type)"></%call> -</%def> - -<%def name="impl_simple(ident, gecko_ffi_name)"> -<%call expr="impl_simple_setter(ident, gecko_ffi_name)"></%call> -<%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call> -<%call expr="impl_simple_clone(ident, gecko_ffi_name)"></%call> -</%def> - -<%def name="impl_border_width(ident, gecko_ffi_name, inherit_from)"> - #[allow(non_snake_case)] - pub fn set_${ident}(&mut self, v: Au) { - let value = v.0; - self.${inherit_from} = value; - self.${gecko_ffi_name} = value; - } - - #[allow(non_snake_case)] - pub fn copy_${ident}_from(&mut self, other: &Self) { - self.${inherit_from} = other.${inherit_from}; - // NOTE: This is needed to easily handle the `unset` and `initial` - // keywords, which are implemented calling this function. - // - // In practice, this means that we may have an incorrect value here, but - // we'll adjust that properly in the style fixup phase. - // - // FIXME(emilio): We could clean this up a bit special-casing the reset_ - // function below. - self.${gecko_ffi_name} = other.${inherit_from}; - } - - #[allow(non_snake_case)] - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } - - #[allow(non_snake_case)] - pub fn clone_${ident}(&self) -> Au { - Au(self.${gecko_ffi_name}) - } -</%def> - -<%def name="impl_split_style_coord(ident, gecko_ffi_name, index)"> - #[allow(non_snake_case)] - pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - self.${gecko_ffi_name}.${index} = v; - } - #[allow(non_snake_case)] - pub fn copy_${ident}_from(&mut self, other: &Self) { - self.${gecko_ffi_name}.${index} = - other.${gecko_ffi_name}.${index}.clone(); - } - #[allow(non_snake_case)] - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } - - #[allow(non_snake_case)] - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - self.${gecko_ffi_name}.${index}.clone() - } -</%def> - -<%def name="copy_sides_style_coord(ident)"> - <% gecko_ffi_name = "m" + to_camel_case(ident) %> - #[allow(non_snake_case)] - pub fn copy_${ident}_from(&mut self, other: &Self) { - % for side in SIDES: - self.${gecko_ffi_name}.data_at_mut(${side.index}) - .copy_from(&other.${gecko_ffi_name}.data_at(${side.index})); - % endfor - ${ caller.body() } - } - - #[allow(non_snake_case)] - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } -</%def> - -<%def name="impl_corner_style_coord(ident, gecko_ffi_name, corner)"> - #[allow(non_snake_case)] - pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - self.${gecko_ffi_name}.${corner} = v; - } - #[allow(non_snake_case)] - pub fn copy_${ident}_from(&mut self, other: &Self) { - self.${gecko_ffi_name}.${corner} = - other.${gecko_ffi_name}.${corner}.clone(); - } - #[allow(non_snake_case)] - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } - #[allow(non_snake_case)] - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - self.${gecko_ffi_name}.${corner}.clone() - } -</%def> - -<%def name="impl_logical(name, **kwargs)"> - ${helpers.logical_setter(name)} -</%def> - -<%def name="impl_style_struct(style_struct)"> -/// A wrapper for ${style_struct.gecko_ffi_name}, to be able to manually construct / destruct / -/// clone it. -#[repr(transparent)] -pub struct ${style_struct.gecko_struct_name}(ManuallyDrop<structs::${style_struct.gecko_ffi_name}>); - -impl ops::Deref for ${style_struct.gecko_struct_name} { - type Target = structs::${style_struct.gecko_ffi_name}; - #[inline] - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl ops::DerefMut for ${style_struct.gecko_struct_name} { - #[inline] - fn deref_mut(&mut self) -> &mut <Self as ops::Deref>::Target { - &mut self.0 - } -} - -impl ${style_struct.gecko_struct_name} { - #[allow(dead_code, unused_variables)] - pub fn default(document: &structs::Document) -> Arc<Self> { - unsafe { - let mut result = UniqueArc::<Self>::new_uninit(); - // FIXME(bug 1595895): Zero the memory to keep valgrind happy, but - // these looks like Valgrind false-positives at a quick glance. - ptr::write_bytes::<Self>(result.as_mut_ptr(), 0, 1); - Gecko_Construct_Default_${style_struct.gecko_ffi_name}( - result.as_mut_ptr() as *mut _, - document, - ); - UniqueArc::assume_init(result).shareable() - } - } -} - -impl Drop for ${style_struct.gecko_struct_name} { - fn drop(&mut self) { - unsafe { - Gecko_Destroy_${style_struct.gecko_ffi_name}(&mut **self); - } - } -} -impl Clone for ${style_struct.gecko_struct_name} { - fn clone(&self) -> Self { - unsafe { - let mut result = MaybeUninit::<Self>::uninit(); - // FIXME(bug 1595895): Zero the memory to keep valgrind happy, but - // these looks like Valgrind false-positives at a quick glance. - ptr::write_bytes::<Self>(result.as_mut_ptr(), 0, 1); - Gecko_CopyConstruct_${style_struct.gecko_ffi_name}(result.as_mut_ptr() as *mut _, &**self); - result.assume_init() - } - } -} -</%def> - -<%def name="impl_simple_type_with_conversion(ident, gecko_ffi_name)"> - #[allow(non_snake_case)] - pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - self.${gecko_ffi_name} = From::from(v) - } - - <% impl_simple_copy(ident, gecko_ffi_name) %> - - #[allow(non_snake_case)] - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - From::from(self.${gecko_ffi_name}) - } -</%def> - -<%def name="impl_font_settings(ident, gecko_type, tag_type, value_type, gecko_value_type)"> - <% - gecko_ffi_name = to_camel_case_lower(ident) - %> - - pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - let iter = v.0.iter().map(|other| structs::${gecko_type} { - mTag: other.tag.0, - mValue: other.value as ${gecko_value_type}, - }); - self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter); - } - - pub fn copy_${ident}_from(&mut self, other: &Self) { - let iter = other.mFont.${gecko_ffi_name}.iter().map(|s| *s); - self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter); - } - - pub fn reset_${ident}(&mut self, other: &Self) { - self.copy_${ident}_from(other) - } - - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - use crate::values::generics::font::{FontSettings, FontTag, ${tag_type}}; - - FontSettings( - self.mFont.${gecko_ffi_name}.iter().map(|gecko_font_setting| { - ${tag_type} { - tag: FontTag(gecko_font_setting.mTag), - value: gecko_font_setting.mValue as ${value_type}, - } - }).collect::<Vec<_>>().into_boxed_slice() - ) - } -</%def> - -<%def name="impl_trait(style_struct_name, skip_longhands='')"> -<% - style_struct = next(x for x in data.style_structs if x.name == style_struct_name) - longhands = [x for x in style_struct.longhands - if not (skip_longhands == "*" or x.name in skip_longhands.split())] - - def longhand_method(longhand): - args = dict(ident=longhand.ident, gecko_ffi_name=longhand.gecko_ffi_name) - - # get the method and pass additional keyword or type-specific arguments - if longhand.logical: - method = impl_logical - args.update(name=longhand.name) - elif longhand.keyword: - method = impl_keyword - args.update(keyword=longhand.keyword) - if "font" in longhand.ident: - args.update(cast_type=longhand.cast_type) - else: - method = impl_simple - - method(**args) -%> -impl ${style_struct.gecko_struct_name} { - /* - * Manually-Implemented Methods. - */ - ${caller.body().strip()} - - /* - * Auto-Generated Methods. - */ - <% - for longhand in longhands: - longhand_method(longhand) - %> -} -</%def> - -<%! -class Side(object): - def __init__(self, name, index): - self.name = name - self.ident = name.lower() - self.index = index - -SIDES = [Side("Top", 0), Side("Right", 1), Side("Bottom", 2), Side("Left", 3)] -CORNERS = ["top_left", "top_right", "bottom_right", "bottom_left"] -%> - -#[allow(dead_code)] -fn static_assert() { - // Note: using the above technique with an enum hits a rust bug when |structs| is in a different crate. - % for side in SIDES: - { const DETAIL: u32 = [0][(structs::Side::eSide${side.name} as usize != ${side.index}) as usize]; let _ = DETAIL; } - % endfor -} - - -<% skip_border_longhands = " ".join(["border-{0}-{1}".format(x.ident, y) - for x in SIDES - for y in ["style", "width"]] + - ["border-{0}-radius".format(x.replace("_", "-")) - for x in CORNERS]) %> - -<%self:impl_trait style_struct_name="Border" - skip_longhands="${skip_border_longhands} border-image-repeat"> - % for side in SIDES: - pub fn set_border_${side.ident}_style(&mut self, v: BorderStyle) { - self.mBorderStyle[${side.index}] = v; - - // This is needed because the initial mComputedBorder value is set to - // zero. - // - // In order to compute stuff, we start from the initial struct, and keep - // going down the tree applying properties. - // - // That means, effectively, that when we set border-style to something - // non-hidden, we should use the initial border instead. - // - // Servo stores the initial border-width in the initial struct, and then - // adjusts as needed in the fixup phase. This means that the initial - // struct is technically not valid without fixups, and that you lose - // pretty much any sharing of the initial struct, which is kind of - // unfortunate. - // - // Gecko has two fields for this, one that stores the "specified" - // border, and other that stores the actual computed one. That means - // that when we set border-style, border-width may change and we need to - // sync back to the specified one. This is what this function does. - // - // Note that this doesn't impose any dependency in the order of - // computation of the properties. This is only relevant if border-style - // is specified, but border-width isn't. If border-width is specified at - // some point, the two mBorder and mComputedBorder fields would be the - // same already. - // - // Once we're here, we know that we'll run style fixups, so it's fine to - // just copy the specified border here, we'll adjust it if it's - // incorrect later. - self.mComputedBorder.${side.ident} = self.mBorder.${side.ident}; - } - - pub fn copy_border_${side.ident}_style_from(&mut self, other: &Self) { - self.set_border_${side.ident}_style(other.mBorderStyle[${side.index}]); - } - - pub fn reset_border_${side.ident}_style(&mut self, other: &Self) { - self.copy_border_${side.ident}_style_from(other); - } - - #[inline] - pub fn clone_border_${side.ident}_style(&self) -> BorderStyle { - self.mBorderStyle[${side.index}] - } - - ${impl_border_width("border_%s_width" % side.ident, "mComputedBorder.%s" % side.ident, "mBorder.%s" % side.ident)} - - pub fn border_${side.ident}_has_nonzero_width(&self) -> bool { - self.mComputedBorder.${side.ident} != 0 - } - % endfor - - % for corner in CORNERS: - <% impl_corner_style_coord("border_%s_radius" % corner, - "mBorderRadius", - corner) %> - % endfor - - <% - border_image_repeat_keywords = ["Stretch", "Repeat", "Round", "Space"] - %> - - pub fn set_border_image_repeat(&mut self, v: longhands::border_image_repeat::computed_value::T) { - use crate::values::specified::border::BorderImageRepeatKeyword; - use crate::gecko_bindings::structs::StyleBorderImageRepeat; - - % for i, side in enumerate(["H", "V"]): - self.mBorderImageRepeat${side} = match v.${i} { - % for keyword in border_image_repeat_keywords: - BorderImageRepeatKeyword::${keyword} => StyleBorderImageRepeat::${keyword}, - % endfor - }; - % endfor - } - - pub fn copy_border_image_repeat_from(&mut self, other: &Self) { - self.mBorderImageRepeatH = other.mBorderImageRepeatH; - self.mBorderImageRepeatV = other.mBorderImageRepeatV; - } - - pub fn reset_border_image_repeat(&mut self, other: &Self) { - self.copy_border_image_repeat_from(other) - } - - pub fn clone_border_image_repeat(&self) -> longhands::border_image_repeat::computed_value::T { - use crate::values::specified::border::BorderImageRepeatKeyword; - use crate::gecko_bindings::structs::StyleBorderImageRepeat; - - % for side in ["H", "V"]: - let servo_${side.lower()} = match self.mBorderImageRepeat${side} { - % for keyword in border_image_repeat_keywords: - StyleBorderImageRepeat::${keyword} => BorderImageRepeatKeyword::${keyword}, - % endfor - }; - % endfor - longhands::border_image_repeat::computed_value::T(servo_h, servo_v) - } -</%self:impl_trait> - -<% skip_scroll_margin_longhands = " ".join(["scroll-margin-%s" % x.ident for x in SIDES]) %> -<% skip_margin_longhands = " ".join(["margin-%s" % x.ident for x in SIDES]) %> -<%self:impl_trait style_struct_name="Margin" - skip_longhands="${skip_margin_longhands} - ${skip_scroll_margin_longhands}"> - % for side in SIDES: - <% impl_split_style_coord("margin_%s" % side.ident, - "mMargin", - side.index) %> - <% impl_split_style_coord("scroll_margin_%s" % side.ident, - "mScrollMargin", - side.index) %> - % endfor -</%self:impl_trait> - -<% skip_scroll_padding_longhands = " ".join(["scroll-padding-%s" % x.ident for x in SIDES]) %> -<% skip_padding_longhands = " ".join(["padding-%s" % x.ident for x in SIDES]) %> -<%self:impl_trait style_struct_name="Padding" - skip_longhands="${skip_padding_longhands} - ${skip_scroll_padding_longhands}"> - - % for side in SIDES: - <% impl_split_style_coord("padding_%s" % side.ident, - "mPadding", - side.index) %> - <% impl_split_style_coord("scroll_padding_%s" % side.ident, "mScrollPadding", side.index) %> - % endfor -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Page"> -</%self:impl_trait> - -<% skip_position_longhands = " ".join(x.ident for x in SIDES) %> -<%self:impl_trait style_struct_name="Position" - skip_longhands="${skip_position_longhands} - masonry-auto-flow"> - % for side in SIDES: - <% impl_split_style_coord(side.ident, "mOffset", side.index) %> - % endfor - pub fn set_computed_justify_items(&mut self, v: values::specified::JustifyItems) { - debug_assert_ne!(v.0, crate::values::specified::align::AlignFlags::LEGACY); - self.mJustifyItems.computed = v; - } - - ${impl_simple_type_with_conversion("masonry_auto_flow", "mMasonryAutoFlow")} -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Outline" - skip_longhands="outline-style outline-width"> - - pub fn set_outline_style(&mut self, v: longhands::outline_style::computed_value::T) { - self.mOutlineStyle = v; - // NB: This is needed to correctly handling the initial value of - // outline-width when outline-style changes, see the - // update_border_${side.ident} comment for more details. - self.mActualOutlineWidth = self.mOutlineWidth; - } - - pub fn copy_outline_style_from(&mut self, other: &Self) { - self.set_outline_style(other.mOutlineStyle); - } - - pub fn reset_outline_style(&mut self, other: &Self) { - self.copy_outline_style_from(other) - } - - pub fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T { - self.mOutlineStyle.clone() - } - - ${impl_border_width("outline_width", "mActualOutlineWidth", "mOutlineWidth")} - - pub fn outline_has_nonzero_width(&self) -> bool { - self.mActualOutlineWidth != 0 - } -</%self:impl_trait> - -<% skip_font_longhands = """font-family font-size font-size-adjust font-weight - font-style font-stretch -x-lang - font-variant-alternates font-variant-east-asian - font-variant-ligatures font-variant-numeric - font-language-override font-feature-settings - font-variation-settings -moz-min-font-size-ratio""" %> -<%self:impl_trait style_struct_name="Font" - skip_longhands="${skip_font_longhands}"> - - // Negative numbers are invalid at parse time, but <integer> is still an - // i32. - <% impl_font_settings("font_feature_settings", "gfxFontFeature", "FeatureTagValue", "i32", "u32") %> - <% impl_font_settings("font_variation_settings", "gfxFontVariation", "VariationValue", "f32", "f32") %> - - pub fn unzoom_fonts(&mut self, device: &Device) { - use crate::values::generics::NonNegative; - self.mSize = NonNegative(device.unzoom_text(self.mSize.0)); - self.mScriptUnconstrainedSize = NonNegative(device.unzoom_text(self.mScriptUnconstrainedSize.0)); - self.mFont.size = NonNegative(device.unzoom_text(self.mFont.size.0)); - } - - pub fn copy_font_size_from(&mut self, other: &Self) { - self.mScriptUnconstrainedSize = other.mScriptUnconstrainedSize; - - self.mSize = other.mScriptUnconstrainedSize; - // NOTE: Intentionally not copying from mFont.size. The cascade process - // recomputes the used size as needed. - self.mFont.size = other.mSize; - self.mFontSizeKeyword = other.mFontSizeKeyword; - - // TODO(emilio): Should we really copy over these two? - self.mFontSizeFactor = other.mFontSizeFactor; - self.mFontSizeOffset = other.mFontSizeOffset; - } - - pub fn reset_font_size(&mut self, other: &Self) { - self.copy_font_size_from(other) - } - - pub fn set_font_size(&mut self, v: FontSize) { - let computed_size = v.computed_size; - self.mScriptUnconstrainedSize = computed_size; - - // These two may be changed from Cascade::fixup_font_stuff. - self.mSize = computed_size; - // NOTE: Intentionally not copying from used_size. The cascade process - // recomputes the used size as needed. - self.mFont.size = computed_size; - - self.mFontSizeKeyword = v.keyword_info.kw; - self.mFontSizeFactor = v.keyword_info.factor; - self.mFontSizeOffset = v.keyword_info.offset; - } - - pub fn clone_font_size(&self) -> FontSize { - use crate::values::specified::font::KeywordInfo; - - FontSize { - computed_size: self.mSize, - used_size: self.mFont.size, - keyword_info: KeywordInfo { - kw: self.mFontSizeKeyword, - factor: self.mFontSizeFactor, - offset: self.mFontSizeOffset, - } - } - } - - ${impl_simple('font_weight', 'mFont.weight')} - ${impl_simple('font_stretch', 'mFont.stretch')} - ${impl_simple('font_style', 'mFont.style')} - - ${impl_simple("font_variant_alternates", "mFont.variantAlternates")} - - ${impl_simple("font_size_adjust", "mFont.sizeAdjust")} - - ${impl_simple("font_family", "mFont.family")} - - #[allow(non_snake_case)] - pub fn set__x_lang(&mut self, v: longhands::_x_lang::computed_value::T) { - let ptr = v.0.as_ptr(); - forget(v); - unsafe { - Gecko_nsStyleFont_SetLang(&mut **self, ptr); - } - } - - #[allow(non_snake_case)] - pub fn copy__x_lang_from(&mut self, other: &Self) { - unsafe { - Gecko_nsStyleFont_CopyLangFrom(&mut **self, &**other); - } - } - - #[allow(non_snake_case)] - pub fn reset__x_lang(&mut self, other: &Self) { - self.copy__x_lang_from(other) - } - - #[allow(non_snake_case)] - pub fn clone__x_lang(&self) -> longhands::_x_lang::computed_value::T { - longhands::_x_lang::computed_value::T(unsafe { - Atom::from_raw(self.mLanguage.mRawPtr) - }) - } - - - ${impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride")} - ${impl_simple_type_with_conversion("font_variant_ligatures", "mFont.variantLigatures")} - ${impl_simple_type_with_conversion("font_variant_east_asian", "mFont.variantEastAsian")} - ${impl_simple_type_with_conversion("font_variant_numeric", "mFont.variantNumeric")} - - #[allow(non_snake_case)] - pub fn clone__moz_min_font_size_ratio( - &self, - ) -> longhands::_moz_min_font_size_ratio::computed_value::T { - Percentage(self.mMinFontSizeRatio as f32 / 100.) - } - - #[allow(non_snake_case)] - pub fn set__moz_min_font_size_ratio(&mut self, v: longhands::_moz_min_font_size_ratio::computed_value::T) { - let scaled = v.0 * 100.; - let percentage = if scaled > 255. { - 255. - } else if scaled < 0. { - 0. - } else { - scaled - }; - - self.mMinFontSizeRatio = percentage as u8; - } - - ${impl_simple_copy('_moz_min_font_size_ratio', 'mMinFontSizeRatio')} -</%self:impl_trait> - -<%def name="impl_coordinated_property_copy(type, ident, gecko_ffi_name)"> - #[allow(non_snake_case)] - pub fn copy_${type}_${ident}_from(&mut self, other: &Self) { - self.m${to_camel_case(type)}s.ensure_len(other.m${to_camel_case(type)}s.len()); - - let count = other.m${to_camel_case(type)}${gecko_ffi_name}Count; - self.m${to_camel_case(type)}${gecko_ffi_name}Count = count; - - let iter = self.m${to_camel_case(type)}s.iter_mut().take(count as usize).zip( - other.m${to_camel_case(type)}s.iter() - ); - - for (ours, others) in iter { - ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone(); - } - } - #[allow(non_snake_case)] - pub fn reset_${type}_${ident}(&mut self, other: &Self) { - self.copy_${type}_${ident}_from(other) - } -</%def> - -<%def name="impl_coordinated_property_count(type, ident, gecko_ffi_name)"> - #[allow(non_snake_case)] - pub fn ${type}_${ident}_count(&self) -> usize { - self.m${to_camel_case(type)}${gecko_ffi_name}Count as usize - } -</%def> - -<%def name="impl_coordinated_property(type, ident, gecko_ffi_name)"> - #[allow(non_snake_case)] - pub fn set_${type}_${ident}<I>(&mut self, v: I) - where - I: IntoIterator<Item = longhands::${type}_${ident}::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator + Clone - { - let v = v.into_iter(); - debug_assert_ne!(v.len(), 0); - let input_len = v.len(); - self.m${to_camel_case(type)}s.ensure_len(input_len); - - self.m${to_camel_case(type)}${gecko_ffi_name}Count = input_len as u32; - for (gecko, servo) in self.m${to_camel_case(type)}s.iter_mut().take(input_len as usize).zip(v) { - gecko.m${gecko_ffi_name} = servo; - } - } - #[allow(non_snake_case)] - pub fn ${type}_${ident}_at(&self, index: usize) - -> longhands::${type}_${ident}::computed_value::SingleComputedValue { - self.m${to_camel_case(type)}s[index % self.${type}_${ident}_count()].m${gecko_ffi_name}.clone() - } - ${impl_coordinated_property_copy(type, ident, gecko_ffi_name)} - ${impl_coordinated_property_count(type, ident, gecko_ffi_name)} -</%def> - -<%def name="impl_animation_keyword(ident, gecko_ffi_name, keyword, cast_type='u8')"> - #[allow(non_snake_case)] - pub fn set_animation_${ident}<I>(&mut self, v: I) - where - I: IntoIterator<Item = longhands::animation_${ident}::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator + Clone - { - use crate::properties::longhands::animation_${ident}::single_value::computed_value::T as Keyword; - - let v = v.into_iter(); - - debug_assert_ne!(v.len(), 0); - let input_len = v.len(); - self.mAnimations.ensure_len(input_len); - - self.mAnimation${gecko_ffi_name}Count = input_len as u32; - - for (gecko, servo) in self.mAnimations.iter_mut().take(input_len as usize).zip(v) { - let result = match servo { - % for value in keyword.values_for("gecko"): - Keyword::${to_camel_case(value)} => - structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast(cast_type)}, - % endfor - }; - gecko.m${gecko_ffi_name} = result; - } - } - #[allow(non_snake_case)] - pub fn animation_${ident}_at(&self, index: usize) - -> longhands::animation_${ident}::computed_value::SingleComputedValue { - use crate::properties::longhands::animation_${ident}::single_value::computed_value::T as Keyword; - match self.mAnimations[index].m${gecko_ffi_name} ${keyword.maybe_cast("u32")} { - % for value in keyword.values_for("gecko"): - structs::${keyword.gecko_constant(value)} => Keyword::${to_camel_case(value)}, - % endfor - % if keyword.gecko_inexhaustive: - _ => panic!("Found unexpected value for animation-${ident}"), - % endif - } - } - ${impl_coordinated_property_copy('animation', ident, gecko_ffi_name)} - ${impl_coordinated_property_count('animation', ident, gecko_ffi_name)} -</%def> - -<% skip_box_longhands= """display contain""" %> -<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}"> - #[inline] - pub fn set_display(&mut self, v: longhands::display::computed_value::T) { - self.mDisplay = v; - self.mOriginalDisplay = v; - } - - #[inline] - pub fn copy_display_from(&mut self, other: &Self) { - self.set_display(other.mDisplay); - } - - #[inline] - pub fn reset_display(&mut self, other: &Self) { - self.copy_display_from(other) - } - - #[inline] - pub fn set_adjusted_display( - &mut self, - v: longhands::display::computed_value::T, - _is_item_or_root: bool - ) { - self.mDisplay = v; - } - - #[inline] - pub fn clone_display(&self) -> longhands::display::computed_value::T { - self.mDisplay - } - - #[inline] - pub fn set_contain(&mut self, v: longhands::contain::computed_value::T) { - self.mContain = v; - self.mEffectiveContainment = v; - } - - #[inline] - pub fn copy_contain_from(&mut self, other: &Self) { - self.set_contain(other.mContain); - } - - #[inline] - pub fn reset_contain(&mut self, other: &Self) { - self.copy_contain_from(other) - } - - #[inline] - pub fn clone_contain(&self) -> longhands::contain::computed_value::T { - self.mContain - } - - #[inline] - pub fn set_effective_containment( - &mut self, - v: longhands::contain::computed_value::T - ) { - self.mEffectiveContainment = v; - } - - #[inline] - pub fn clone_effective_containment(&self) -> longhands::contain::computed_value::T { - self.mEffectiveContainment - } -</%self:impl_trait> - -<%def name="simple_image_array_property(name, shorthand, field_name)"> - <% - image_layers_field = "mImage" if shorthand == "background" else "mMask" - copy_simple_image_array_property(name, shorthand, image_layers_field, field_name) - %> - - pub fn set_${shorthand}_${name}<I>(&mut self, v: I) - where I: IntoIterator<Item=longhands::${shorthand}_${name}::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator - { - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - let v = v.into_iter(); - - unsafe { - Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, v.len(), - LayerType::${shorthand.title()}); - } - - self.${image_layers_field}.${field_name}Count = v.len() as u32; - for (servo, geckolayer) in v.zip(self.${image_layers_field}.mLayers.iter_mut()) { - geckolayer.${field_name} = { - ${caller.body()} - }; - } - } -</%def> - -<%def name="copy_simple_image_array_property(name, shorthand, layers_field_name, field_name)"> - pub fn copy_${shorthand}_${name}_from(&mut self, other: &Self) { - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - - let count = other.${layers_field_name}.${field_name}Count; - unsafe { - Gecko_EnsureImageLayersLength(&mut self.${layers_field_name}, - count as usize, - LayerType::${shorthand.title()}); - } - // FIXME(emilio): This may be bogus in the same way as bug 1426246. - for (layer, other) in self.${layers_field_name}.mLayers.iter_mut() - .zip(other.${layers_field_name}.mLayers.iter()) - .take(count as usize) { - layer.${field_name} = other.${field_name}.clone(); - } - self.${layers_field_name}.${field_name}Count = count; - } - - pub fn reset_${shorthand}_${name}(&mut self, other: &Self) { - self.copy_${shorthand}_${name}_from(other) - } -</%def> - -<%def name="impl_simple_image_array_property(name, shorthand, layer_field_name, field_name, struct_name)"> - <% - ident = "%s_%s" % (shorthand, name) - style_struct = next(x for x in data.style_structs if x.name == struct_name) - longhand = next(x for x in style_struct.longhands if x.ident == ident) - keyword = longhand.keyword - %> - - <% copy_simple_image_array_property(name, shorthand, layer_field_name, field_name) %> - - pub fn set_${ident}<I>(&mut self, v: I) - where - I: IntoIterator<Item=longhands::${ident}::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator, - { - use crate::properties::longhands::${ident}::single_value::computed_value::T as Keyword; - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - - let v = v.into_iter(); - - unsafe { - Gecko_EnsureImageLayersLength(&mut self.${layer_field_name}, v.len(), - LayerType::${shorthand.title()}); - } - - self.${layer_field_name}.${field_name}Count = v.len() as u32; - for (servo, geckolayer) in v.zip(self.${layer_field_name}.mLayers.iter_mut()) { - geckolayer.${field_name} = { - match servo { - % for value in keyword.values_for("gecko"): - Keyword::${to_camel_case(value)} => - structs::${keyword.gecko_constant(value)} ${keyword.maybe_cast('u8')}, - % endfor - } - }; - } - } - - pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - use crate::properties::longhands::${ident}::single_value::computed_value::T as Keyword; - - % if keyword.needs_cast(): - % for value in keyword.values_for('gecko'): - const ${keyword.casted_constant_name(value, "u8")} : u8 = - structs::${keyword.gecko_constant(value)} as u8; - % endfor - % endif - - longhands::${ident}::computed_value::List( - self.${layer_field_name}.mLayers.iter() - .take(self.${layer_field_name}.${field_name}Count as usize) - .map(|ref layer| { - match layer.${field_name} { - % for value in longhand.keyword.values_for("gecko"): - % if keyword.needs_cast(): - ${keyword.casted_constant_name(value, "u8")} - % else: - structs::${keyword.gecko_constant(value)} - % endif - => Keyword::${to_camel_case(value)}, - % endfor - % if keyword.gecko_inexhaustive: - _ => panic!("Found unexpected value in style struct for ${ident} property"), - % endif - } - }).collect() - ) - } -</%def> - -<%def name="impl_common_image_layer_properties(shorthand)"> - <% - if shorthand == "background": - image_layers_field = "mImage" - struct_name = "Background" - else: - image_layers_field = "mMask" - struct_name = "SVG" - %> - - <%self:simple_image_array_property name="repeat" shorthand="${shorthand}" field_name="mRepeat"> - use crate::values::specified::background::BackgroundRepeatKeyword; - use crate::gecko_bindings::structs::nsStyleImageLayers_Repeat; - use crate::gecko_bindings::structs::StyleImageLayerRepeat; - - fn to_ns(repeat: BackgroundRepeatKeyword) -> StyleImageLayerRepeat { - match repeat { - BackgroundRepeatKeyword::Repeat => StyleImageLayerRepeat::Repeat, - BackgroundRepeatKeyword::Space => StyleImageLayerRepeat::Space, - BackgroundRepeatKeyword::Round => StyleImageLayerRepeat::Round, - BackgroundRepeatKeyword::NoRepeat => StyleImageLayerRepeat::NoRepeat, - } - } - - let repeat_x = to_ns(servo.0); - let repeat_y = to_ns(servo.1); - nsStyleImageLayers_Repeat { - mXRepeat: repeat_x, - mYRepeat: repeat_y, - } - </%self:simple_image_array_property> - - pub fn clone_${shorthand}_repeat(&self) -> longhands::${shorthand}_repeat::computed_value::T { - use crate::properties::longhands::${shorthand}_repeat::single_value::computed_value::T; - use crate::values::specified::background::BackgroundRepeatKeyword; - use crate::gecko_bindings::structs::StyleImageLayerRepeat; - - fn to_servo(repeat: StyleImageLayerRepeat) -> BackgroundRepeatKeyword { - match repeat { - StyleImageLayerRepeat::Repeat => BackgroundRepeatKeyword::Repeat, - StyleImageLayerRepeat::Space => BackgroundRepeatKeyword::Space, - StyleImageLayerRepeat::Round => BackgroundRepeatKeyword::Round, - StyleImageLayerRepeat::NoRepeat => BackgroundRepeatKeyword::NoRepeat, - _ => panic!("Found unexpected value in style struct for ${shorthand}_repeat property"), - } - } - - longhands::${shorthand}_repeat::computed_value::List( - self.${image_layers_field}.mLayers.iter() - .take(self.${image_layers_field}.mRepeatCount as usize) - .map(|ref layer| { - T(to_servo(layer.mRepeat.mXRepeat), to_servo(layer.mRepeat.mYRepeat)) - }).collect() - ) - } - - <% impl_simple_image_array_property("clip", shorthand, image_layers_field, "mClip", struct_name) %> - <% impl_simple_image_array_property("origin", shorthand, image_layers_field, "mOrigin", struct_name) %> - - % for (orientation, keyword) in [("x", "horizontal"), ("y", "vertical")]: - pub fn copy_${shorthand}_position_${orientation}_from(&mut self, other: &Self) { - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - - let count = other.${image_layers_field}.mPosition${orientation.upper()}Count; - - unsafe { - Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, - count as usize, - LayerType::${shorthand.capitalize()}); - } - - for (layer, other) in self.${image_layers_field}.mLayers.iter_mut() - .zip(other.${image_layers_field}.mLayers.iter()) - .take(count as usize) { - layer.mPosition.${keyword} = other.mPosition.${keyword}.clone(); - } - self.${image_layers_field}.mPosition${orientation.upper()}Count = count; - } - - pub fn reset_${shorthand}_position_${orientation}(&mut self, other: &Self) { - self.copy_${shorthand}_position_${orientation}_from(other) - } - - pub fn clone_${shorthand}_position_${orientation}(&self) - -> longhands::${shorthand}_position_${orientation}::computed_value::T { - longhands::${shorthand}_position_${orientation}::computed_value::List( - self.${image_layers_field}.mLayers.iter() - .take(self.${image_layers_field}.mPosition${orientation.upper()}Count as usize) - .map(|position| position.mPosition.${keyword}.clone()) - .collect() - ) - } - - pub fn set_${shorthand}_position_${orientation[0]}<I>(&mut self, - v: I) - where I: IntoIterator<Item = longhands::${shorthand}_position_${orientation[0]} - ::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator - { - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - - let v = v.into_iter(); - - unsafe { - Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, v.len(), - LayerType::${shorthand.capitalize()}); - } - - self.${image_layers_field}.mPosition${orientation[0].upper()}Count = v.len() as u32; - for (servo, geckolayer) in v.zip(self.${image_layers_field} - .mLayers.iter_mut()) { - geckolayer.mPosition.${keyword} = servo; - } - } - % endfor - - <%self:simple_image_array_property name="size" shorthand="${shorthand}" field_name="mSize"> - servo - </%self:simple_image_array_property> - - pub fn clone_${shorthand}_size(&self) -> longhands::${shorthand}_size::computed_value::T { - longhands::${shorthand}_size::computed_value::List( - self.${image_layers_field}.mLayers.iter().map(|layer| layer.mSize.clone()).collect() - ) - } - - pub fn copy_${shorthand}_image_from(&mut self, other: &Self) { - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - unsafe { - let count = other.${image_layers_field}.mImageCount; - Gecko_EnsureImageLayersLength(&mut self.${image_layers_field}, - count as usize, - LayerType::${shorthand.capitalize()}); - - for (layer, other) in self.${image_layers_field}.mLayers.iter_mut() - .zip(other.${image_layers_field}.mLayers.iter()) - .take(count as usize) { - layer.mImage = other.mImage.clone(); - } - self.${image_layers_field}.mImageCount = count; - } - } - - pub fn reset_${shorthand}_image(&mut self, other: &Self) { - self.copy_${shorthand}_image_from(other) - } - - #[allow(unused_variables)] - pub fn set_${shorthand}_image<I>(&mut self, images: I) - where I: IntoIterator<Item = longhands::${shorthand}_image::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator - { - use crate::gecko_bindings::structs::nsStyleImageLayers_LayerType as LayerType; - - let images = images.into_iter(); - - unsafe { - Gecko_EnsureImageLayersLength( - &mut self.${image_layers_field}, - images.len(), - LayerType::${shorthand.title()}, - ); - } - - self.${image_layers_field}.mImageCount = images.len() as u32; - for (image, geckoimage) in images.zip(self.${image_layers_field} - .mLayers.iter_mut()) { - geckoimage.mImage = image; - } - } - - pub fn clone_${shorthand}_image(&self) -> longhands::${shorthand}_image::computed_value::T { - longhands::${shorthand}_image::computed_value::List( - self.${image_layers_field}.mLayers.iter() - .take(self.${image_layers_field}.mImageCount as usize) - .map(|layer| layer.mImage.clone()) - .collect() - ) - } - - <% - fill_fields = "mRepeat mClip mOrigin mPositionX mPositionY mImage mSize" - if shorthand == "background": - fill_fields += " mAttachment mBlendMode" - else: - # mSourceURI uses mImageCount - fill_fields += " mMaskMode mComposite" - %> - pub fn fill_arrays(&mut self) { - use crate::gecko_bindings::bindings::Gecko_FillAllImageLayers; - use std::cmp; - let mut max_len = 1; - % for member in fill_fields.split(): - max_len = cmp::max(max_len, self.${image_layers_field}.${member}Count); - % endfor - unsafe { - // While we could do this manually, we'd need to also manually - // run all the copy constructors, so we just delegate to gecko - Gecko_FillAllImageLayers(&mut self.${image_layers_field}, max_len); - } - } -</%def> - -// TODO: Gecko accepts lists in most background-related properties. We just use -// the first element (which is the common case), but at some point we want to -// add support for parsing these lists in servo and pushing to nsTArray's. -<% skip_background_longhands = """background-repeat - background-image background-clip - background-origin background-attachment - background-size background-position - background-blend-mode - background-position-x - background-position-y""" %> -<%self:impl_trait style_struct_name="Background" - skip_longhands="${skip_background_longhands}"> - - <% impl_common_image_layer_properties("background") %> - <% impl_simple_image_array_property("attachment", "background", "mImage", "mAttachment", "Background") %> - <% impl_simple_image_array_property("blend_mode", "background", "mImage", "mBlendMode", "Background") %> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="List" skip_longhands="list-style-type"> - pub fn set_list_style_type(&mut self, v: longhands::list_style_type::computed_value::T) { - use nsstring::{nsACString, nsCStr}; - use self::longhands::list_style_type::computed_value::T; - match v { - T::None => unsafe { - bindings::Gecko_SetCounterStyleToNone(&mut self.mCounterStyle) - } - T::CounterStyle(s) => s.to_gecko_value(&mut self.mCounterStyle), - T::String(s) => unsafe { - bindings::Gecko_SetCounterStyleToString( - &mut self.mCounterStyle, - &nsCStr::from(&s) as &nsACString, - ) - } - } - } - - pub fn copy_list_style_type_from(&mut self, other: &Self) { - unsafe { - Gecko_CopyCounterStyle(&mut self.mCounterStyle, &other.mCounterStyle); - } - } - - pub fn reset_list_style_type(&mut self, other: &Self) { - self.copy_list_style_type_from(other) - } - - pub fn clone_list_style_type(&self) -> longhands::list_style_type::computed_value::T { - use self::longhands::list_style_type::computed_value::T; - use crate::values::Either; - use crate::values::generics::CounterStyle; - use crate::gecko_bindings::bindings; - - let name = unsafe { - bindings::Gecko_CounterStyle_GetName(&self.mCounterStyle) - }; - if !name.is_null() { - let name = unsafe { Atom::from_raw(name) }; - if name == atom!("none") { - return T::None; - } - } - let result = CounterStyle::from_gecko_value(&self.mCounterStyle); - match result { - Either::First(counter_style) => T::CounterStyle(counter_style), - Either::Second(string) => T::String(string), - } - } -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Table"> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Effects"> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="InheritedBox"> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="InheritedTable" - skip_longhands="border-spacing"> - - pub fn set_border_spacing(&mut self, v: longhands::border_spacing::computed_value::T) { - self.mBorderSpacingCol = v.horizontal().0; - self.mBorderSpacingRow = v.vertical().0; - } - - pub fn copy_border_spacing_from(&mut self, other: &Self) { - self.mBorderSpacingCol = other.mBorderSpacingCol; - self.mBorderSpacingRow = other.mBorderSpacingRow; - } - - pub fn reset_border_spacing(&mut self, other: &Self) { - self.copy_border_spacing_from(other) - } - - pub fn clone_border_spacing(&self) -> longhands::border_spacing::computed_value::T { - longhands::border_spacing::computed_value::T::new( - Au(self.mBorderSpacingCol).into(), - Au(self.mBorderSpacingRow).into() - ) - } -</%self:impl_trait> - - -<%self:impl_trait style_struct_name="InheritedText"> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Text" skip_longhands="initial-letter"> - pub fn set_initial_letter(&mut self, v: longhands::initial_letter::computed_value::T) { - use crate::values::generics::text::InitialLetter; - match v { - InitialLetter::Normal => { - self.mInitialLetterSize = 0.; - self.mInitialLetterSink = 0; - }, - InitialLetter::Specified(size, sink) => { - self.mInitialLetterSize = size; - if let Some(sink) = sink { - self.mInitialLetterSink = sink; - } else { - self.mInitialLetterSink = size.floor() as i32; - } - } - } - } - - pub fn copy_initial_letter_from(&mut self, other: &Self) { - self.mInitialLetterSize = other.mInitialLetterSize; - self.mInitialLetterSink = other.mInitialLetterSink; - } - - pub fn reset_initial_letter(&mut self, other: &Self) { - self.copy_initial_letter_from(other) - } - - pub fn clone_initial_letter(&self) -> longhands::initial_letter::computed_value::T { - use crate::values::generics::text::InitialLetter; - - if self.mInitialLetterSize == 0. && self.mInitialLetterSink == 0 { - InitialLetter::Normal - } else if self.mInitialLetterSize.floor() as i32 == self.mInitialLetterSink { - InitialLetter::Specified(self.mInitialLetterSize, None) - } else { - InitialLetter::Specified(self.mInitialLetterSize, Some(self.mInitialLetterSink)) - } - } -</%self:impl_trait> - -<% skip_svg_longhands = """ -mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask-position-y mask-size mask-image -""" -%> -<%self:impl_trait style_struct_name="SVG" - skip_longhands="${skip_svg_longhands}"> - <% impl_common_image_layer_properties("mask") %> - <% impl_simple_image_array_property("mode", "mask", "mMask", "mMaskMode", "SVG") %> - <% impl_simple_image_array_property("composite", "mask", "mMask", "mComposite", "SVG") %> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="InheritedSVG"> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="InheritedUI"> -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Column" - skip_longhands="column-count column-rule-width column-rule-style"> - - #[allow(unused_unsafe)] - pub fn set_column_count(&mut self, v: longhands::column_count::computed_value::T) { - use crate::gecko_bindings::structs::{nsStyleColumn_kColumnCountAuto, nsStyleColumn_kMaxColumnCount}; - - self.mColumnCount = match v { - ColumnCount::Integer(integer) => { - cmp::min(integer.0 as u32, unsafe { nsStyleColumn_kMaxColumnCount }) - }, - ColumnCount::Auto => nsStyleColumn_kColumnCountAuto - }; - } - - ${impl_simple_copy('column_count', 'mColumnCount')} - - pub fn clone_column_count(&self) -> longhands::column_count::computed_value::T { - use crate::gecko_bindings::structs::{nsStyleColumn_kColumnCountAuto, nsStyleColumn_kMaxColumnCount}; - if self.mColumnCount != nsStyleColumn_kColumnCountAuto { - debug_assert!(self.mColumnCount >= 1 && - self.mColumnCount <= nsStyleColumn_kMaxColumnCount); - ColumnCount::Integer((self.mColumnCount as i32).into()) - } else { - ColumnCount::Auto - } - } - - pub fn set_column_rule_style(&mut self, v: longhands::column_rule_style::computed_value::T) { - self.mColumnRuleStyle = v; - // NB: This is needed to correctly handling the initial value of - // column-rule-width when colun-rule-style changes, see the - // update_border_${side.ident} comment for more details. - self.mActualColumnRuleWidth = self.mColumnRuleWidth; - } - - pub fn copy_column_rule_style_from(&mut self, other: &Self) { - self.set_column_rule_style(other.mColumnRuleStyle); - } - - pub fn reset_column_rule_style(&mut self, other: &Self) { - self.copy_column_rule_style_from(other) - } - - pub fn clone_column_rule_style(&self) -> longhands::column_rule_style::computed_value::T { - self.mColumnRuleStyle.clone() - } - - ${impl_border_width("column_rule_width", "mActualColumnRuleWidth", "mColumnRuleWidth")} - - pub fn column_rule_has_nonzero_width(&self) -> bool { - self.mActualColumnRuleWidth != 0 - } -</%self:impl_trait> - -<%self:impl_trait style_struct_name="Counters"> - pub fn ineffective_content_property(&self) -> bool { - !self.mContent.is_items() - } -</%self:impl_trait> - -<% skip_ui_longhands = """animation-name animation-delay animation-duration - animation-direction animation-fill-mode - animation-play-state animation-iteration-count - animation-timing-function animation-composition animation-timeline - transition-duration transition-delay - transition-timing-function transition-property - scroll-timeline-name scroll-timeline-axis - view-timeline-name view-timeline-axis view-timeline-inset""" %> - -<%self:impl_trait style_struct_name="UI" skip_longhands="${skip_ui_longhands}"> - ${impl_coordinated_property('transition', 'delay', 'Delay')} - ${impl_coordinated_property('transition', 'duration', 'Duration')} - ${impl_coordinated_property('transition', 'timing_function', 'TimingFunction')} - - pub fn transition_combined_duration_at(&self, index: usize) -> Time { - // https://drafts.csswg.org/css-transitions/#transition-combined-duration - Time::from_seconds( - self.transition_duration_at(index).seconds().max(0.0) + - self.transition_delay_at(index).seconds() - ) - } - - pub fn set_transition_property<I>(&mut self, v: I) - where - I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator - { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; - - let v = v.into_iter(); - - if v.len() != 0 { - self.mTransitions.ensure_len(v.len()); - self.mTransitionPropertyCount = v.len() as u32; - for (servo, gecko) in v.zip(self.mTransitions.iter_mut()) { - unsafe { gecko.mUnknownProperty.clear() }; - - match servo { - TransitionProperty::Unsupported(ident) => { - gecko.mProperty = eCSSProperty_UNKNOWN; - gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed(); - }, - TransitionProperty::Custom(name) => { - gecko.mProperty = eCSSPropertyExtra_variable; - gecko.mUnknownProperty.mRawPtr = name.into_addrefed(); - } - _ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(), - } - } - } else { - // In gecko |none| is represented by eCSSPropertyExtra_no_properties. - self.mTransitionPropertyCount = 1; - self.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties; - } - } - - /// Returns whether there are any transitions specified. - pub fn specifies_transitions(&self) -> bool { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties; - if self.mTransitionPropertyCount == 1 && - self.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties && - self.transition_combined_duration_at(0).seconds() <= 0.0f32 { - return false; - } - - self.mTransitionPropertyCount > 0 - } - - pub fn transition_property_at(&self, index: usize) - -> longhands::transition_property::computed_value::SingleComputedValue { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; - - let property = self.mTransitions[index].mProperty; - if property == eCSSProperty_UNKNOWN { - let atom = self.mTransitions[index].mUnknownProperty.mRawPtr; - debug_assert!(!atom.is_null()); - TransitionProperty::Unsupported(CustomIdent(unsafe{ - Atom::from_raw(atom) - })) - } else if property == eCSSPropertyExtra_variable { - let atom = self.mTransitions[index].mUnknownProperty.mRawPtr; - debug_assert!(!atom.is_null()); - TransitionProperty::Custom(unsafe{ - Atom::from_raw(atom) - }) - } else if property == eCSSPropertyExtra_no_properties { - // Actually, we don't expect TransitionProperty::Unsupported also - // represents "none", but if the caller wants to convert it, it is - // fine. Please use it carefully. - // - // FIXME(emilio): This is a hack, is this reachable? - TransitionProperty::Unsupported(CustomIdent(atom!("none"))) - } else { - property.into() - } - } - - pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID { - self.mTransitions[index].mProperty - } - - pub fn copy_transition_property_from(&mut self, other: &Self) { - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable; - use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN; - self.mTransitions.ensure_len(other.mTransitions.len()); - - let count = other.mTransitionPropertyCount; - self.mTransitionPropertyCount = count; - - for (index, transition) in self.mTransitions.iter_mut().enumerate().take(count as usize) { - transition.mProperty = other.mTransitions[index].mProperty; - unsafe { transition.mUnknownProperty.clear() }; - if transition.mProperty == eCSSProperty_UNKNOWN || - transition.mProperty == eCSSPropertyExtra_variable { - let atom = other.mTransitions[index].mUnknownProperty.mRawPtr; - debug_assert!(!atom.is_null()); - transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed(); - } - } - } - - pub fn reset_transition_property(&mut self, other: &Self) { - self.copy_transition_property_from(other) - } - - ${impl_coordinated_property_count('transition', 'property', 'Property')} - - pub fn animations_equals(&self, other: &Self) -> bool { - return self.mAnimationNameCount == other.mAnimationNameCount - && self.mAnimationDelayCount == other.mAnimationDelayCount - && self.mAnimationDirectionCount == other.mAnimationDirectionCount - && self.mAnimationDurationCount == other.mAnimationDurationCount - && self.mAnimationFillModeCount == other.mAnimationFillModeCount - && self.mAnimationIterationCountCount == other.mAnimationIterationCountCount - && self.mAnimationPlayStateCount == other.mAnimationPlayStateCount - && self.mAnimationTimingFunctionCount == other.mAnimationTimingFunctionCount - && self.mAnimationCompositionCount == other.mAnimationCompositionCount - && self.mAnimationTimelineCount == other.mAnimationTimelineCount - && unsafe { bindings::Gecko_StyleAnimationsEquals(&self.mAnimations, &other.mAnimations) } - } - - pub fn set_animation_name<I>(&mut self, v: I) - where - I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>, - I::IntoIter: ExactSizeIterator - { - let v = v.into_iter(); - debug_assert_ne!(v.len(), 0); - self.mAnimations.ensure_len(v.len()); - - self.mAnimationNameCount = v.len() as u32; - for (servo, gecko) in v.zip(self.mAnimations.iter_mut()) { - let atom = servo.0.as_atom().clone(); - unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); } - } - } - pub fn animation_name_at(&self, index: usize) - -> longhands::animation_name::computed_value::SingleComputedValue { - use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName; - - let atom = self.mAnimations[index].mName.mRawPtr; - AnimationName(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) })) - } - pub fn copy_animation_name_from(&mut self, other: &Self) { - self.mAnimationNameCount = other.mAnimationNameCount; - unsafe { bindings::Gecko_CopyAnimationNames(&mut self.mAnimations, &other.mAnimations); } - } - - pub fn reset_animation_name(&mut self, other: &Self) { - self.copy_animation_name_from(other) - } - - ${impl_coordinated_property_count('animation', 'name', 'Name')} - ${impl_coordinated_property('animation', 'delay', 'Delay')} - ${impl_coordinated_property('animation', 'duration', 'Duration')} - ${impl_animation_keyword('direction', 'Direction', - data.longhands_by_name["animation-direction"].keyword)} - ${impl_animation_keyword('fill_mode', 'FillMode', - data.longhands_by_name["animation-fill-mode"].keyword)} - ${impl_animation_keyword('play_state', 'PlayState', - data.longhands_by_name["animation-play-state"].keyword)} - ${impl_animation_keyword('composition', 'Composition', - data.longhands_by_name["animation-composition"].keyword)} - ${impl_coordinated_property('animation', 'iteration_count', 'IterationCount')} - ${impl_coordinated_property('animation', 'timeline', 'Timeline')} - ${impl_coordinated_property('animation', 'timing_function', 'TimingFunction')} - - ${impl_coordinated_property('scroll_timeline', 'name', 'Name')} - ${impl_coordinated_property('scroll_timeline', 'axis', 'Axis')} - - pub fn scroll_timelines_equals(&self, other: &Self) -> bool { - self.mScrollTimelineNameCount == other.mScrollTimelineNameCount - && self.mScrollTimelineAxisCount == other.mScrollTimelineAxisCount - && unsafe { - bindings::Gecko_StyleScrollTimelinesEquals( - &self.mScrollTimelines, - &other.mScrollTimelines, - ) - } - } - - ${impl_coordinated_property('view_timeline', 'name', 'Name')} - ${impl_coordinated_property('view_timeline', 'axis', 'Axis')} - ${impl_coordinated_property('view_timeline', 'inset', 'Inset')} - - pub fn view_timelines_equals(&self, other: &Self) -> bool { - self.mViewTimelineNameCount == other.mViewTimelineNameCount - && self.mViewTimelineAxisCount == other.mViewTimelineAxisCount - && self.mViewTimelineInsetCount == other.mViewTimelineInsetCount - && unsafe { - bindings::Gecko_StyleViewTimelinesEquals( - &self.mViewTimelines, - &other.mViewTimelines, - ) - } - } -</%self:impl_trait> - -<%self:impl_trait style_struct_name="XUL"> -</%self:impl_trait> - -% for style_struct in data.style_structs: -${impl_style_struct(style_struct)} -% endfor - -/// Assert that the initial values set in Gecko style struct constructors -/// match the values returned by `get_initial_value()` for each longhand. -#[cfg(feature = "gecko")] -#[inline] -pub fn assert_initial_values_match(data: &PerDocumentStyleData) { - if cfg!(debug_assertions) { - let data = data.borrow(); - let cv = data.stylist.device().default_computed_values(); - <% - # Skip properties with initial values that change at computed - # value time, or whose initial value depends on the document - # / other prefs. - SKIPPED = [ - "border-top-width", - "border-bottom-width", - "border-left-width", - "border-right-width", - "column-rule-width", - "font-family", - "font-size", - "outline-width", - "color", - ] - TO_TEST = [p for p in data.longhands if p.enabled_in != "" and not p.logical and not p.name in SKIPPED] - %> - % for property in TO_TEST: - assert_eq!( - cv.clone_${property.ident}(), - longhands::${property.ident}::get_initial_value(), - concat!( - "initial value in Gecko style struct for ", - stringify!(${property.ident}), - " must match longhands::", - stringify!(${property.ident}), - "::get_initial_value()" - ) - ); - % endfor - } -} diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs deleted file mode 100644 index 5e1eea5072f..00000000000 --- a/components/style/properties/helpers.mako.rs +++ /dev/null @@ -1,1023 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%! - from data import Keyword, to_rust_ident, to_phys, to_camel_case, SYSTEM_FONT_LONGHANDS - from data import (LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, - PHYSICAL_SIDES, LOGICAL_SIZES, LOGICAL_AXES) -%> - -<%def name="predefined_type(name, type, initial_value, parse_method='parse', - vector=False, initial_specified_value=None, - allow_quirks='No', allow_empty=False, **kwargs)"> - <%def name="predefined_type_inner(name, type, initial_value, parse_method)"> - #[allow(unused_imports)] - use app_units::Au; - #[allow(unused_imports)] - use crate::values::specified::AllowQuirks; - #[allow(unused_imports)] - use crate::Zero; - #[allow(unused_imports)] - use smallvec::SmallVec; - pub use crate::values::specified::${type} as SpecifiedValue; - pub mod computed_value { - pub use crate::values::computed::${type} as T; - } - % if initial_value: - #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} } - % endif - % if initial_specified_value: - #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} } - % endif - #[allow(unused_variables)] - #[inline] - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<SpecifiedValue, ParseError<'i>> { - % if allow_quirks != "No": - specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::${allow_quirks}) - % elif parse_method != "parse": - specified::${type}::${parse_method}(context, input) - % else: - <specified::${type} as crate::parser::Parse>::parse(context, input) - % endif - } - </%def> - % if vector: - <%call - expr="vector_longhand(name, predefined_type=type, allow_empty=allow_empty or not initial_value, **kwargs)" - > - ${predefined_type_inner(name, type, initial_value, parse_method)} - % if caller: - ${caller.body()} - % endif - </%call> - % else: - <%call expr="longhand(name, predefined_type=type, **kwargs)"> - ${predefined_type_inner(name, type, initial_value, parse_method)} - % if caller: - ${caller.body()} - % endif - </%call> - % endif -</%def> - -// FIXME (Manishearth): Add computed_value_as_specified argument -// and handle the empty case correctly -<%doc> - To be used in cases where we have a grammar like "<thing> [ , <thing> ]*". - - Setting allow_empty to False allows for cases where the vector - is empty. The grammar for these is usually "none | <thing> [ , <thing> ]*". - We assume that the default/initial value is an empty vector for these. - `initial_value` need not be defined for these. -</%doc> - -// The setup here is roughly: -// -// * UnderlyingList is the list that is stored in the computed value. This may -// be a shared ArcSlice if the property is inherited. -// * UnderlyingOwnedList is the list that is used for animation. -// * Specified values always use OwnedSlice, since it's more compact. -// * computed_value::List is just a convenient alias that you can use for the -// computed value list, since this is in the computed_value module. -// -// If simple_vector_bindings is true, then we don't use the complex iterator -// machinery and set_foo_from, and just compute the value like any other -// longhand. -<%def name="vector_longhand(name, animation_value_type=None, - vector_animation_type=None, allow_empty=False, - simple_vector_bindings=False, - separator='Comma', - **kwargs)"> - <%call expr="longhand(name, animation_value_type=animation_value_type, vector=True, - simple_vector_bindings=simple_vector_bindings, **kwargs)"> - #[allow(unused_imports)] - use smallvec::SmallVec; - - pub mod single_value { - #[allow(unused_imports)] - use cssparser::{Parser, BasicParseError}; - #[allow(unused_imports)] - use crate::parser::{Parse, ParserContext}; - #[allow(unused_imports)] - use crate::properties::ShorthandId; - #[allow(unused_imports)] - use selectors::parser::SelectorParseErrorKind; - #[allow(unused_imports)] - use style_traits::{ParseError, StyleParseErrorKind}; - #[allow(unused_imports)] - use crate::values::computed::{Context, ToComputedValue}; - #[allow(unused_imports)] - use crate::values::{computed, specified}; - ${caller.body()} - } - - /// The definition of the computed value for ${name}. - pub mod computed_value { - #[allow(unused_imports)] - use crate::values::animated::ToAnimatedValue; - #[allow(unused_imports)] - use crate::values::resolved::ToResolvedValue; - pub use super::single_value::computed_value as single_value; - pub use self::single_value::T as SingleComputedValue; - % if not allow_empty or allow_empty == "NotInitial": - use smallvec::SmallVec; - % endif - use crate::values::computed::ComputedVecIter; - - <% - is_shared_list = allow_empty and allow_empty != "NotInitial" and \ - data.longhands_by_name[name].style_struct.inherited - %> - - // FIXME(emilio): Add an OwnedNonEmptySlice type, and figure out - // something for transition-name, which is the only remaining user - // of NotInitial. - pub type UnderlyingList<T> = - % if allow_empty and allow_empty != "NotInitial": - % if data.longhands_by_name[name].style_struct.inherited: - crate::ArcSlice<T>; - % else: - crate::OwnedSlice<T>; - % endif - % else: - SmallVec<[T; 1]>; - % endif - - pub type UnderlyingOwnedList<T> = - % if allow_empty and allow_empty != "NotInitial": - crate::OwnedSlice<T>; - % else: - SmallVec<[T; 1]>; - % endif - - - /// The generic type defining the animated and resolved values for - /// this property. - /// - /// Making this type generic allows the compiler to figure out the - /// animated value for us, instead of having to implement it - /// manually for every type we care about. - #[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedValue, - ToResolvedValue, - ToCss, - )] - % if separator == "Comma": - #[css(comma)] - % endif - pub struct OwnedList<T>( - % if not allow_empty: - #[css(iterable)] - % else: - #[css(if_empty = "none", iterable)] - % endif - pub UnderlyingOwnedList<T>, - ); - - /// The computed value for this property. - % if not is_shared_list: - pub type ComputedList = OwnedList<single_value::T>; - pub use self::OwnedList as List; - % else: - pub use self::ComputedList as List; - - #[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - ToCss, - )] - % if separator == "Comma": - #[css(comma)] - % endif - pub struct ComputedList( - % if not allow_empty: - #[css(iterable)] - % else: - #[css(if_empty = "none", iterable)] - % endif - % if is_shared_list: - #[ignore_malloc_size_of = "Arc"] - % endif - pub UnderlyingList<single_value::T>, - ); - - type ResolvedList = OwnedList<<single_value::T as ToResolvedValue>::ResolvedValue>; - impl ToResolvedValue for ComputedList { - type ResolvedValue = ResolvedList; - - fn to_resolved_value(self, context: &crate::values::resolved::Context) -> Self::ResolvedValue { - OwnedList( - self.0 - .iter() - .cloned() - .map(|v| v.to_resolved_value(context)) - .collect() - ) - } - - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - % if not is_shared_list: - use std::iter::FromIterator; - % endif - let iter = - resolved.0.into_iter().map(ToResolvedValue::from_resolved_value); - ComputedList(UnderlyingList::from_iter(iter)) - } - } - % endif - - % if simple_vector_bindings: - impl From<ComputedList> for UnderlyingList<single_value::T> { - #[inline] - fn from(l: ComputedList) -> Self { - l.0 - } - } - impl From<UnderlyingList<single_value::T>> for ComputedList { - #[inline] - fn from(l: UnderlyingList<single_value::T>) -> Self { - List(l) - } - } - % endif - - % if vector_animation_type: - % if not animation_value_type: - Sorry, this is stupid but needed for now. - % endif - - use crate::values::animated::{Animate, ToAnimatedZero, Procedure, lists}; - use crate::values::distance::{SquaredDistance, ComputeSquaredDistance}; - - // FIXME(emilio): For some reason rust thinks that this alias is - // unused, even though it's clearly used below? - #[allow(unused)] - type AnimatedList = OwnedList<<single_value::T as ToAnimatedValue>::AnimatedValue>; - - % if is_shared_list: - impl ToAnimatedValue for ComputedList { - type AnimatedValue = AnimatedList; - - fn to_animated_value(self) -> Self::AnimatedValue { - OwnedList( - self.0.iter().map(|v| v.clone().to_animated_value()).collect() - ) - } - - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - let iter = - animated.0.into_iter().map(ToAnimatedValue::from_animated_value); - ComputedList(UnderlyingList::from_iter(iter)) - } - } - % endif - - impl ToAnimatedZero for AnimatedList { - fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) } - } - - impl Animate for AnimatedList { - fn animate( - &self, - other: &Self, - procedure: Procedure, - ) -> Result<Self, ()> { - Ok(OwnedList( - lists::${vector_animation_type}::animate(&self.0, &other.0, procedure)? - )) - } - } - impl ComputeSquaredDistance for AnimatedList { - fn compute_squared_distance( - &self, - other: &Self, - ) -> Result<SquaredDistance, ()> { - lists::${vector_animation_type}::squared_distance(&self.0, &other.0) - } - } - % endif - - /// The computed value, effectively a list of single values. - pub use self::ComputedList as T; - - pub type Iter<'a, 'cx, 'cx_a> = ComputedVecIter<'a, 'cx, 'cx_a, super::single_value::SpecifiedValue>; - } - - /// The specified value of ${name}. - #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] - % if separator == "Comma": - #[css(comma)] - % endif - pub struct SpecifiedValue( - % if not allow_empty: - #[css(iterable)] - % else: - #[css(if_empty = "none", iterable)] - % endif - pub crate::OwnedSlice<single_value::SpecifiedValue>, - ); - - pub fn get_initial_value() -> computed_value::T { - % if allow_empty and allow_empty != "NotInitial": - computed_value::List(Default::default()) - % else: - let mut v = SmallVec::new(); - v.push(single_value::get_initial_value()); - computed_value::List(v) - % endif - } - - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<SpecifiedValue, ParseError<'i>> { - use style_traits::Separator; - - % if allow_empty: - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(SpecifiedValue(Default::default())) - } - % endif - - let v = style_traits::${separator}::parse(input, |parser| { - single_value::parse(context, parser) - })?; - Ok(SpecifiedValue(v.into())) - } - - pub use self::single_value::SpecifiedValue as SingleSpecifiedValue; - - % if not simple_vector_bindings and engine == "gecko": - impl SpecifiedValue { - fn compute_iter<'a, 'cx, 'cx_a>( - &'a self, - context: &'cx Context<'cx_a>, - ) -> computed_value::Iter<'a, 'cx, 'cx_a> { - computed_value::Iter::new(context, &self.0) - } - } - % endif - - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_value::T { - % if not is_shared_list: - use std::iter::FromIterator; - % endif - computed_value::List(computed_value::UnderlyingList::from_iter( - self.0.iter().map(|i| i.to_computed_value(context)) - )) - } - - #[inline] - fn from_computed_value(computed: &computed_value::T) -> Self { - let iter = computed.0.iter().map(ToComputedValue::from_computed_value); - SpecifiedValue(iter.collect()) - } - } - </%call> -</%def> -<%def name="longhand(*args, **kwargs)"> - <% - property = data.declare_longhand(*args, **kwargs) - if property is None: - return "" - %> - /// ${property.spec} - pub mod ${property.ident} { - #[allow(unused_imports)] - use cssparser::{Parser, BasicParseError, Token}; - #[allow(unused_imports)] - use crate::parser::{Parse, ParserContext}; - #[allow(unused_imports)] - use crate::properties::{UnparsedValue, ShorthandId}; - #[allow(unused_imports)] - use crate::error_reporting::ParseErrorReporter; - #[allow(unused_imports)] - use crate::properties::longhands; - #[allow(unused_imports)] - use crate::properties::{LonghandId, LonghandIdSet}; - #[allow(unused_imports)] - use crate::properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration}; - #[allow(unused_imports)] - use crate::properties::style_structs; - #[allow(unused_imports)] - use selectors::parser::SelectorParseErrorKind; - #[allow(unused_imports)] - use servo_arc::Arc; - #[allow(unused_imports)] - use style_traits::{ParseError, StyleParseErrorKind}; - #[allow(unused_imports)] - use crate::values::computed::{Context, ToComputedValue}; - #[allow(unused_imports)] - use crate::values::{computed, generics, specified}; - #[allow(unused_imports)] - use crate::Atom; - ${caller.body()} - #[allow(unused_variables)] - pub fn cascade_property( - declaration: &PropertyDeclaration, - context: &mut computed::Context, - ) { - context.for_non_inherited_property = ${"false" if property.style_struct.inherited else "true"}; - let specified_value = match *declaration { - PropertyDeclaration::${property.camel_case}(ref value) => value, - PropertyDeclaration::CSSWideKeyword(ref declaration) => { - debug_assert_eq!(declaration.id, LonghandId::${property.camel_case}); - match declaration.keyword { - % if not property.style_struct.inherited: - CSSWideKeyword::Unset | - % endif - CSSWideKeyword::Initial => { - % if not property.style_struct.inherited: - debug_assert!(false, "Should be handled in apply_properties"); - % else: - context.builder.reset_${property.ident}(); - % endif - }, - % if property.style_struct.inherited: - CSSWideKeyword::Unset | - % endif - CSSWideKeyword::Inherit => { - % if property.style_struct.inherited: - debug_assert!(false, "Should be handled in apply_properties"); - % else: - context.rule_cache_conditions.borrow_mut().set_uncacheable(); - context.builder.inherit_${property.ident}(); - % endif - } - CSSWideKeyword::RevertLayer | - CSSWideKeyword::Revert => unreachable!("Should never get here"), - } - return; - } - PropertyDeclaration::WithVariables(..) => { - panic!("variables should already have been substituted") - } - _ => panic!("entered the wrong cascade_property() implementation"), - }; - - % if property.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko": - if let Some(sf) = specified_value.get_system() { - longhands::system_font::resolve_system_font(sf, context); - } - % endif - - % if not property.style_struct.inherited and property.logical: - context.rule_cache_conditions.borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - % endif - - % if property.is_vector and not property.simple_vector_bindings and engine == "gecko": - // In the case of a vector property we want to pass down an - // iterator so that this can be computed without allocation. - // - // However, computing requires a context, but the style struct - // being mutated is on the context. We temporarily remove it, - // mutate it, and then put it back. Vector longhands cannot - // touch their own style struct whilst computing, else this will - // panic. - let mut s = - context.builder.take_${data.current_style_struct.name_lower}(); - { - let iter = specified_value.compute_iter(context); - s.set_${property.ident}(iter); - } - context.builder.put_${data.current_style_struct.name_lower}(s); - % else: - % if property.boxed: - let computed = (**specified_value).to_computed_value(context); - % else: - let computed = specified_value.to_computed_value(context); - % endif - context.builder.set_${property.ident}(computed) - % endif - } - - pub fn parse_declared<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<PropertyDeclaration, ParseError<'i>> { - % if property.allow_quirks != "No": - parse_quirky(context, input, specified::AllowQuirks::${property.allow_quirks}) - % else: - parse(context, input) - % endif - % if property.boxed: - .map(Box::new) - % endif - .map(PropertyDeclaration::${property.camel_case}) - } - } -</%def> - -<%def name="gecko_keyword_conversion(keyword, values=None, type='SpecifiedValue', cast_to=None)"> - <% - if not values: - values = keyword.values_for(engine) - maybe_cast = "as %s" % cast_to if cast_to else "" - const_type = cast_to if cast_to else "u32" - %> - #[cfg(feature = "gecko")] - impl ${type} { - /// Obtain a specified value from a Gecko keyword value - /// - /// Intended for use with presentation attributes, not style structs - pub fn from_gecko_keyword(kw: u32) -> Self { - use crate::gecko_bindings::structs; - % for value in values: - // We can't match on enum values if we're matching on a u32 - const ${to_rust_ident(value).upper()}: ${const_type} - = structs::${keyword.gecko_constant(value)} as ${const_type}; - % endfor - match kw ${maybe_cast} { - % for value in values: - ${to_rust_ident(value).upper()} => ${type}::${to_camel_case(value)}, - % endfor - _ => panic!("Found unexpected value in style struct for ${keyword.name} property"), - } - } - } -</%def> - -<%def name="gecko_bitflags_conversion(bit_map, gecko_bit_prefix, type, kw_type='u8')"> - #[cfg(feature = "gecko")] - impl ${type} { - /// Obtain a specified value from a Gecko keyword value - /// - /// Intended for use with presentation attributes, not style structs - pub fn from_gecko_keyword(kw: ${kw_type}) -> Self { - % for gecko_bit in bit_map.values(): - use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; - % endfor - - let mut bits = ${type}::empty(); - % for servo_bit, gecko_bit in bit_map.items(): - if kw & (${gecko_bit_prefix}${gecko_bit} as ${kw_type}) != 0 { - bits |= ${servo_bit}; - } - % endfor - bits - } - - pub fn to_gecko_keyword(self) -> ${kw_type} { - % for gecko_bit in bit_map.values(): - use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; - % endfor - - let mut bits: ${kw_type} = 0; - // FIXME: if we ensure that the Servo bitflags storage is the same - // as Gecko's one, we can just copy it. - % for servo_bit, gecko_bit in bit_map.items(): - if self.contains(${servo_bit}) { - bits |= ${gecko_bit_prefix}${gecko_bit} as ${kw_type}; - } - % endfor - bits - } - } -</%def> - -<%def name="single_keyword(name, values, vector=False, - needs_conversion=False, **kwargs)"> - <% - keyword_kwargs = {a: kwargs.pop(a, None) for a in [ - 'gecko_constant_prefix', - 'gecko_enum_prefix', - 'extra_gecko_values', - 'extra_servo_values', - 'gecko_aliases', - 'servo_aliases', - 'custom_consts', - 'gecko_inexhaustive', - 'gecko_strip_moz_prefix', - ]} - %> - - <%def name="inner_body(keyword, needs_conversion=False)"> - pub use self::computed_value::T as SpecifiedValue; - pub mod computed_value { - #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] - #[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] - pub enum T { - % for variant in keyword.values_for(engine): - <% - aliases = [] - for alias, v in keyword.aliases_for(engine).items(): - if variant == v: - aliases.append(alias) - %> - % if aliases: - #[parse(aliases = "${','.join(sorted(aliases))}")] - % endif - ${to_camel_case(variant)}, - % endfor - } - } - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T::${to_camel_case(values.split()[0])} - } - #[inline] - pub fn get_initial_specified_value() -> SpecifiedValue { - SpecifiedValue::${to_camel_case(values.split()[0])} - } - #[inline] - pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) - -> Result<SpecifiedValue, ParseError<'i>> { - SpecifiedValue::parse(input) - } - - % if needs_conversion: - <% - conversion_values = keyword.values_for(engine) + list(keyword.aliases_for(engine).keys()) - %> - ${gecko_keyword_conversion(keyword, values=conversion_values)} - % endif - </%def> - % if vector: - <%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> - ${inner_body(Keyword(name, values, **keyword_kwargs))} - % if caller: - ${caller.body()} - % endif - </%call> - % else: - <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> - ${inner_body(Keyword(name, values, **keyword_kwargs), - needs_conversion=needs_conversion)} - % if caller: - ${caller.body()} - % endif - </%call> - % endif -</%def> - -<%def name="shorthand(name, sub_properties, derive_serialize=False, - derive_value_info=True, **kwargs)"> -<% - shorthand = data.declare_shorthand(name, sub_properties.split(), **kwargs) - # mako doesn't accept non-string value in parameters with <% %> form, so - # we have to workaround it this way. - if not isinstance(derive_value_info, bool): - derive_value_info = eval(derive_value_info) -%> - % if shorthand: - /// ${shorthand.spec} - pub mod ${shorthand.ident} { - use cssparser::Parser; - use crate::parser::ParserContext; - use crate::properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed, longhands}; - #[allow(unused_imports)] - use selectors::parser::SelectorParseErrorKind; - #[allow(unused_imports)] - use std::fmt::{self, Write}; - #[allow(unused_imports)] - use style_traits::{ParseError, StyleParseErrorKind}; - #[allow(unused_imports)] - use style_traits::{CssWriter, KeywordsCollectFn, SpecifiedValueInfo, ToCss}; - - % if derive_value_info: - #[derive(SpecifiedValueInfo)] - % endif - pub struct Longhands { - % for sub_property in shorthand.sub_properties: - pub ${sub_property.ident}: - % if sub_property.boxed: - Box< - % endif - longhands::${sub_property.ident}::SpecifiedValue - % if sub_property.boxed: - > - % endif - , - % endfor - } - - /// Represents a serializable set of all of the longhand properties that - /// correspond to a shorthand. - % if derive_serialize: - #[derive(ToCss)] - % endif - pub struct LonghandsToSerialize<'a> { - % for sub_property in shorthand.sub_properties: - pub ${sub_property.ident}: - % if sub_property.may_be_disabled_in(shorthand, engine): - Option< - % endif - &'a longhands::${sub_property.ident}::SpecifiedValue, - % if sub_property.may_be_disabled_in(shorthand, engine): - >, - % endif - % endfor - } - - impl<'a> LonghandsToSerialize<'a> { - /// Tries to get a serializable set of longhands given a set of - /// property declarations. - pub fn from_iter(iter: impl Iterator<Item = &'a PropertyDeclaration>) -> Result<Self, ()> { - // Define all of the expected variables that correspond to the shorthand - % for sub_property in shorthand.sub_properties: - let mut ${sub_property.ident} = - None::< &'a longhands::${sub_property.ident}::SpecifiedValue>; - % endfor - - // Attempt to assign the incoming declarations to the expected variables - for declaration in iter { - match *declaration { - % for sub_property in shorthand.sub_properties: - PropertyDeclaration::${sub_property.camel_case}(ref value) => { - ${sub_property.ident} = Some(value) - }, - % endfor - _ => {} - }; - } - - // If any of the expected variables are missing, return an error - match ( - % for sub_property in shorthand.sub_properties: - ${sub_property.ident}, - % endfor - ) { - - ( - % for sub_property in shorthand.sub_properties: - % if sub_property.may_be_disabled_in(shorthand, engine): - ${sub_property.ident}, - % else: - Some(${sub_property.ident}), - % endif - % endfor - ) => - Ok(LonghandsToSerialize { - % for sub_property in shorthand.sub_properties: - ${sub_property.ident}, - % endfor - }), - _ => Err(()) - } - } - } - - /// Parse the given shorthand and fill the result into the - /// `declarations` vector. - pub fn parse_into<'i, 't>( - declarations: &mut SourcePropertyDeclaration, - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - #[allow(unused_imports)] - use crate::properties::{NonCustomPropertyId, LonghandId}; - input.parse_entirely(|input| parse_value(context, input)).map(|longhands| { - % for sub_property in shorthand.sub_properties: - % if sub_property.may_be_disabled_in(shorthand, engine): - if NonCustomPropertyId::from(LonghandId::${sub_property.camel_case}) - .allowed_in_ignoring_rule_type(context) { - % endif - declarations.push(PropertyDeclaration::${sub_property.camel_case}( - longhands.${sub_property.ident} - )); - % if sub_property.may_be_disabled_in(shorthand, engine): - } - % endif - % endfor - }) - } - - /// Try to serialize a given shorthand to a string. - pub fn to_css(declarations: &[&PropertyDeclaration], dest: &mut crate::str::CssStringWriter) -> fmt::Result { - match LonghandsToSerialize::from_iter(declarations.iter().cloned()) { - Ok(longhands) => longhands.to_css(&mut CssWriter::new(dest)), - Err(_) => Ok(()) - } - } - - ${caller.body()} - } - % endif -</%def> - -// A shorthand of kind `<property-1> <property-2>?` where both properties have -// the same type. -<%def name="two_properties_shorthand( - name, - first_property, - second_property, - parser_function='crate::parser::Parse::parse', - **kwargs -)"> -<%call expr="self.shorthand(name, sub_properties=' '.join([first_property, second_property]), **kwargs)"> - #[allow(unused_imports)] - use crate::parser::Parse; - #[allow(unused_imports)] - use crate::values::specified; - - fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let parse_one = |c: &ParserContext, input: &mut Parser<'i, 't>| -> Result< - crate::properties::longhands::${to_rust_ident(first_property)}::SpecifiedValue, - ParseError<'i> - > { - ${parser_function}(c, input) - }; - - let first = parse_one(context, input)?; - let second = - input.try_parse(|input| parse_one(context, input)).unwrap_or_else(|_| first.clone()); - Ok(expanded! { - ${to_rust_ident(first_property)}: first, - ${to_rust_ident(second_property)}: second, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let first = &self.${to_rust_ident(first_property)}; - let second = &self.${to_rust_ident(second_property)}; - - first.to_css(dest)?; - if first != second { - dest.write_char(' ')?; - second.to_css(dest)?; - } - Ok(()) - } - } -</%call> -</%def> - -<%def name="four_sides_shorthand(name, sub_property_pattern, - parser_function='crate::parser::Parse::parse', - allow_quirks='No', **kwargs)"> - <% sub_properties=' '.join(sub_property_pattern % side for side in PHYSICAL_SIDES) %> - <%call expr="self.shorthand(name, sub_properties=sub_properties, **kwargs)"> - #[allow(unused_imports)] - use crate::parser::Parse; - use crate::values::generics::rect::Rect; - #[allow(unused_imports)] - use crate::values::specified; - - fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let rect = Rect::parse_with(context, input, |c, i| -> Result< - crate::properties::longhands::${to_rust_ident(sub_property_pattern % "top")}::SpecifiedValue, - ParseError<'i> - > { - % if allow_quirks != "No": - ${parser_function}_quirky(c, i, specified::AllowQuirks::${allow_quirks}) - % else: - ${parser_function}(c, i) - % endif - })?; - Ok(expanded! { - % for index, side in enumerate(["top", "right", "bottom", "left"]): - ${to_rust_ident(sub_property_pattern % side)}: rect.${index}, - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let rect = Rect::new( - % for side in ["top", "right", "bottom", "left"]: - &self.${to_rust_ident(sub_property_pattern % side)}, - % endfor - ); - rect.to_css(dest) - } - } - </%call> -</%def> - -<%def name="logical_setter_helper(name)"> - <% - side = None - size = None - corner = None - axis = None - maybe_side = [s for s in LOGICAL_SIDES if s in name] - maybe_size = [s for s in LOGICAL_SIZES if s in name] - maybe_corner = [s for s in LOGICAL_CORNERS if s in name] - maybe_axis = [s for s in LOGICAL_AXES if name.endswith(s)] - if len(maybe_side) == 1: - side = maybe_side[0] - elif len(maybe_size) == 1: - size = maybe_size[0] - elif len(maybe_corner) == 1: - corner = maybe_corner[0] - elif len(maybe_axis) == 1: - axis = maybe_axis[0] - def phys_ident(side, phy_side): - return to_rust_ident(to_phys(name, side, phy_side)) - %> - % if side is not None: - use crate::logical_geometry::PhysicalSide; - match wm.${to_rust_ident(side)}_physical_side() { - % for phy_side in PHYSICAL_SIDES: - PhysicalSide::${phy_side.title()} => { - ${caller.inner(physical_ident=phys_ident(side, phy_side))} - } - % endfor - } - % elif corner is not None: - use crate::logical_geometry::PhysicalCorner; - match wm.${to_rust_ident(corner)}_physical_corner() { - % for phy_corner in PHYSICAL_CORNERS: - PhysicalCorner::${to_camel_case(phy_corner)} => { - ${caller.inner(physical_ident=phys_ident(corner, phy_corner))} - } - % endfor - } - % elif size is not None: - <% - # (horizontal, vertical) - physical_size = ("height", "width") - if size == "inline-size": - physical_size = ("width", "height") - %> - if wm.is_vertical() { - ${caller.inner(physical_ident=phys_ident(size, physical_size[1]))} - } else { - ${caller.inner(physical_ident=phys_ident(size, physical_size[0]))} - } - % elif axis is not None: - <% - if axis == "inline": - me, other = "x", "y" - else: - assert(axis == "block") - me, other = "y", "x" - %> - if wm.is_vertical() { - ${caller.inner(physical_ident=phys_ident(axis, other))} - } else { - ${caller.inner(physical_ident=phys_ident(axis, me))} - } - % else: - <% raise Exception("Don't know what to do with logical property %s" % name) %> - % endif -</%def> - -<%def name="logical_setter(name)"> - /// Set the appropriate physical property for ${name} given a writing mode. - pub fn set_${to_rust_ident(name)}(&mut self, - v: longhands::${to_rust_ident(name)}::computed_value::T, - wm: WritingMode) { - <%self:logical_setter_helper name="${name}"> - <%def name="inner(physical_ident)"> - self.set_${physical_ident}(v) - </%def> - </%self:logical_setter_helper> - } - - /// Copy the appropriate physical property from another struct for ${name} - /// given a writing mode. - pub fn copy_${to_rust_ident(name)}_from(&mut self, - other: &Self, - wm: WritingMode) { - <%self:logical_setter_helper name="${name}"> - <%def name="inner(physical_ident)"> - self.copy_${physical_ident}_from(other) - </%def> - </%self:logical_setter_helper> - } - - /// Copy the appropriate physical property from another struct for ${name} - /// given a writing mode. - pub fn reset_${to_rust_ident(name)}(&mut self, - other: &Self, - wm: WritingMode) { - self.copy_${to_rust_ident(name)}_from(other, wm) - } - - /// Get the computed value for the appropriate physical property for - /// ${name} given a writing mode. - pub fn clone_${to_rust_ident(name)}(&self, wm: WritingMode) - -> longhands::${to_rust_ident(name)}::computed_value::T { - <%self:logical_setter_helper name="${name}"> - <%def name="inner(physical_ident)"> - self.clone_${physical_ident}() - </%def> - </%self:logical_setter_helper> - } -</%def> diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs deleted file mode 100644 index d1e28a5f97b..00000000000 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ /dev/null @@ -1,716 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% - from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case - from itertools import groupby -%> - -#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID; -use crate::properties::{CSSWideKeyword, PropertyDeclaration, NonCustomPropertyIterator}; -use crate::properties::longhands; -use crate::properties::longhands::visibility::computed_value::T as Visibility; -use crate::properties::LonghandId; -use servo_arc::Arc; -use std::ptr; -use std::mem; -use fxhash::FxHashMap; -use super::ComputedValues; -use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; -use crate::values::animated::effects::AnimatedFilter; -#[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty; -use crate::values::computed::{ClipRect, Context}; -use crate::values::computed::ToComputedValue; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::effects::Filter; -use void::{self, Void}; - -/// Convert nsCSSPropertyID to TransitionProperty -#[cfg(feature = "gecko")] -#[allow(non_upper_case_globals)] -impl From<nsCSSPropertyID> for TransitionProperty { - fn from(property: nsCSSPropertyID) -> TransitionProperty { - use crate::properties::ShorthandId; - match property { - % for prop in data.longhands: - ${prop.nscsspropertyid()} => { - TransitionProperty::Longhand(LonghandId::${prop.camel_case}) - } - % endfor - % for prop in data.shorthands_except_all(): - ${prop.nscsspropertyid()} => { - TransitionProperty::Shorthand(ShorthandId::${prop.camel_case}) - } - % endfor - nsCSSPropertyID::eCSSPropertyExtra_all_properties => { - TransitionProperty::Shorthand(ShorthandId::All) - } - _ => { - panic!("non-convertible nsCSSPropertyID") - } - } - } -} - -/// A collection of AnimationValue that were composed on an element. -/// This HashMap stores the values that are the last AnimationValue to be -/// composed for each TransitionProperty. -pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>; - -/// An enum to represent a single computed value belonging to an animated -/// property in order to be interpolated with another one. When interpolating, -/// both values need to belong to the same property. -/// -/// FIXME: We need to add a path for custom properties, but that's trivial after -/// this (is a similar path to that of PropertyDeclaration). -#[derive(Debug, MallocSizeOf)] -#[repr(u16)] -pub enum AnimationValue { - % for prop in data.longhands: - /// `${prop.name}` - % if prop.animatable and not prop.logical: - ${prop.camel_case}(${prop.animated_type()}), - % else: - ${prop.camel_case}(Void), - % endif - % endfor -} - -<% - animated = [] - unanimated = [] - animated_with_logical = [] - for prop in data.longhands: - if prop.animatable: - animated_with_logical.append(prop) - if prop.animatable and not prop.logical: - animated.append(prop) - else: - unanimated.append(prop) -%> - -#[repr(C)] -struct AnimationValueVariantRepr<T> { - tag: u16, - value: T -} - -impl Clone for AnimationValue { - #[inline] - fn clone(&self) -> Self { - use self::AnimationValue::*; - - <% - [copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())] - %> - - let self_tag = unsafe { *(self as *const _ as *const u16) }; - if self_tag <= LonghandId::${copy[-1].camel_case} as u16 { - #[derive(Clone, Copy)] - #[repr(u16)] - enum CopyVariants { - % for prop in copy: - _${prop.camel_case}(${prop.animated_type()}), - % endfor - } - - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut CopyVariants, - *(self as *const _ as *const CopyVariants), - ); - return out.assume_init(); - } - } - - match *self { - % for ty, props in groupby(others, key=lambda x: x.animated_type()): - <% props = list(props) %> - ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => { - % if len(props) == 1: - ${props[0].camel_case}(value.clone()) - % else: - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>, - AnimationValueVariantRepr { - tag: *(self as *const _ as *const u16), - value: value.clone(), - }, - ); - out.assume_init() - } - % endif - } - % endfor - _ => unsafe { debug_unreachable!() } - } - } -} - -impl PartialEq for AnimationValue { - #[inline] - fn eq(&self, other: &Self) -> bool { - use self::AnimationValue::*; - - unsafe { - let this_tag = *(self as *const _ as *const u16); - let other_tag = *(other as *const _ as *const u16); - if this_tag != other_tag { - return false; - } - - match *self { - % for ty, props in groupby(animated, key=lambda x: x.animated_type()): - ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { - let other_repr = - &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); - *this == other_repr.value - } - % endfor - ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { - void::unreachable(void) - } - } - } - } -} - -impl AnimationValue { - /// Returns the longhand id this animated value corresponds to. - #[inline] - pub fn id(&self) -> LonghandId { - let id = unsafe { *(self as *const _ as *const LonghandId) }; - debug_assert_eq!(id, match *self { - % for prop in data.longhands: - % if prop.animatable and not prop.logical: - AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case}, - % else: - AnimationValue::${prop.camel_case}(void) => void::unreachable(void), - % endif - % endfor - }); - id - } - - /// "Uncompute" this animation value in order to be used inside the CSS - /// cascade. - pub fn uncompute(&self) -> PropertyDeclaration { - use crate::properties::longhands; - use self::AnimationValue::*; - - use super::PropertyDeclarationVariantRepr; - - match *self { - <% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %> - % for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc): - <% props = list(props) %> - ${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => { - % if not computed: - let ref value = ToAnimatedValue::from_animated_value(value.clone()); - % endif - let value = ${ty}::from_computed_value(&value); - % if boxed: - let value = Box::new(value); - % endif - % if len(props) == 1: - PropertyDeclaration::${props[0].camel_case}(value) - % else: - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>, - PropertyDeclarationVariantRepr { - tag: *(self as *const _ as *const u16), - value, - }, - ); - out.assume_init() - } - % endif - } - % endfor - ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { - void::unreachable(void) - } - } - } - - /// Construct an AnimationValue from a property declaration. - pub fn from_declaration( - decl: &PropertyDeclaration, - context: &mut Context, - extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>, - initial: &ComputedValues, - ) -> Option<Self> { - use super::PropertyDeclarationVariantRepr; - - <% - keyfunc = lambda x: ( - x.specified_type(), - x.animated_type(), - x.boxed, - not x.is_animatable_with_computed_value, - x.style_struct.inherited, - x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko", - ) - %> - - let animatable = match *decl { - % for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc): - ${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => { - let decl_repr = unsafe { - &*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>) - }; - let longhand_id = unsafe { - *(&decl_repr.tag as *const u16 as *const LonghandId) - }; - context.for_non_inherited_property = ${"false" if inherit else "true"}; - % if system: - if let Some(sf) = value.get_system() { - longhands::system_font::resolve_system_font(sf, context) - } - % endif - % if boxed: - let value = (**value).to_computed_value(context); - % else: - let value = value.to_computed_value(context); - % endif - % if to_animated: - let value = value.to_animated_value(); - % endif - - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>, - AnimationValueVariantRepr { - tag: longhand_id.to_physical(context.builder.writing_mode) as u16, - value, - }, - ); - out.assume_init() - } - } - % endfor - PropertyDeclaration::CSSWideKeyword(ref declaration) => { - match declaration.id { - // We put all the animatable properties first in the hopes - // that it might increase match locality. - % for prop in data.longhands: - % if prop.animatable: - LonghandId::${prop.camel_case} => { - // FIXME(emilio, bug 1533327): I think revert (and - // revert-layer) handling is not fine here, but what to - // do instead? - // - // Seems we'd need the computed value as if it was - // revert, somehow. Treating it as `unset` seems fine - // for now... - let style_struct = match declaration.keyword { - % if not prop.style_struct.inherited: - CSSWideKeyword::Revert | - CSSWideKeyword::RevertLayer | - CSSWideKeyword::Unset | - % endif - CSSWideKeyword::Initial => { - initial.get_${prop.style_struct.name_lower}() - }, - % if prop.style_struct.inherited: - CSSWideKeyword::Revert | - CSSWideKeyword::RevertLayer | - CSSWideKeyword::Unset | - % endif - CSSWideKeyword::Inherit => { - context.builder - .get_parent_${prop.style_struct.name_lower}() - }, - }; - let computed = style_struct - % if prop.logical: - .clone_${prop.ident}(context.builder.writing_mode); - % else: - .clone_${prop.ident}(); - % endif - - % if not prop.is_animatable_with_computed_value: - let computed = computed.to_animated_value(); - % endif - - % if prop.logical: - let wm = context.builder.writing_mode; - <%helpers:logical_setter_helper name="${prop.name}"> - <%def name="inner(physical_ident)"> - AnimationValue::${to_camel_case(physical_ident)}(computed) - </%def> - </%helpers:logical_setter_helper> - % else: - AnimationValue::${prop.camel_case}(computed) - % endif - }, - % endif - % endfor - % for prop in data.longhands: - % if not prop.animatable: - LonghandId::${prop.camel_case} => return None, - % endif - % endfor - } - }, - PropertyDeclaration::WithVariables(ref declaration) => { - let mut cache = Default::default(); - let substituted = { - let custom_properties = - extra_custom_properties.or_else(|| context.style().custom_properties()); - - declaration.value.substitute_variables( - declaration.id, - context.builder.writing_mode, - custom_properties, - context.quirks_mode, - context.device(), - &mut cache, - ) - }; - return AnimationValue::from_declaration( - &substituted, - context, - extra_custom_properties, - initial, - ) - }, - _ => return None // non animatable properties will get included because of shorthands. ignore. - }; - Some(animatable) - } - - /// Get an AnimationValue for an AnimatableLonghand from a given computed values. - pub fn from_computed_values( - property: LonghandId, - style: &ComputedValues, - ) -> Option<Self> { - let property = property.to_physical(style.writing_mode); - Some(match property { - % for prop in data.longhands: - % if prop.animatable and not prop.logical: - LonghandId::${prop.camel_case} => { - let computed = style.clone_${prop.ident}(); - AnimationValue::${prop.camel_case}( - % if prop.is_animatable_with_computed_value: - computed - % else: - computed.to_animated_value() - % endif - ) - } - % endif - % endfor - _ => return None, - }) - } - - /// Update `style` with the value of this `AnimationValue`. - /// - /// SERVO ONLY: This doesn't properly handle things like updating 'em' units - /// when animated font-size. - #[cfg(feature = "servo")] - pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) { - match self { - % for prop in data.longhands: - % if prop.animatable and not prop.logical: - AnimationValue::${prop.camel_case}(ref value) => { - % if not prop.is_animatable_with_computed_value: - let value: longhands::${prop.ident}::computed_value::T = - ToAnimatedValue::from_animated_value(value.clone()); - style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value); - % else: - style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value.clone()); - % endif - } - % else: - AnimationValue::${prop.camel_case}(..) => unreachable!(), - % endif - % endfor - } - } - - /// As above, but a stub for Gecko. - #[cfg(feature = "gecko")] - pub fn set_in_style_for_servo(&self, _: &mut ComputedValues) { - } -} - -fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> { - if let Procedure::Interpolate { progress } = procedure { - Ok(if progress < 0.5 { this.clone() } else { other.clone() }) - } else { - Err(()) - } -} - -impl Animate for AnimationValue { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(unsafe { - use self::AnimationValue::*; - - let this_tag = *(self as *const _ as *const u16); - let other_tag = *(other as *const _ as *const u16); - if this_tag != other_tag { - panic!("Unexpected AnimationValue::animate call"); - } - - match *self { - <% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %> - % for (ty, discrete), props in groupby(animated, key=keyfunc): - ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { - let other_repr = - &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); - % if discrete: - let value = animate_discrete(this, &other_repr.value, procedure)?; - % else: - let value = this.animate(&other_repr.value, procedure)?; - % endif - - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>, - AnimationValueVariantRepr { - tag: this_tag, - value, - }, - ); - out.assume_init() - } - % endfor - ${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => { - void::unreachable(void) - } - } - }) - } -} - -<% - nondiscrete = [] - for prop in animated: - if prop.animation_value_type != "discrete": - nondiscrete.append(prop) -%> - -impl ComputeSquaredDistance for AnimationValue { - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - unsafe { - use self::AnimationValue::*; - - let this_tag = *(self as *const _ as *const u16); - let other_tag = *(other as *const _ as *const u16); - if this_tag != other_tag { - panic!("Unexpected AnimationValue::compute_squared_distance call"); - } - - match *self { - % for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()): - ${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => { - let other_repr = - &*(other as *const _ as *const AnimationValueVariantRepr<${ty}>); - - this.compute_squared_distance(&other_repr.value) - } - % endfor - _ => Err(()), - } - } - } -} - -impl ToAnimatedZero for AnimationValue { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - match *self { - % for prop in data.longhands: - % if prop.animatable and not prop.logical and prop.animation_value_type != "discrete": - AnimationValue::${prop.camel_case}(ref base) => { - Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?)) - }, - % endif - % endfor - _ => Err(()), - } - } -} - -/// <https://drafts.csswg.org/web-animations-1/#animating-visibility> -impl Animate for Visibility { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match procedure { - Procedure::Interpolate { .. } => { - let (this_weight, other_weight) = procedure.weights(); - match (*self, *other) { - (Visibility::Visible, _) => { - Ok(if this_weight > 0.0 { *self } else { *other }) - }, - (_, Visibility::Visible) => { - Ok(if other_weight > 0.0 { *other } else { *self }) - }, - _ => Err(()), - } - }, - _ => Err(()), - } - } -} - -impl ComputeSquaredDistance for Visibility { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. })) - } -} - -impl ToAnimatedZero for Visibility { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} - -/// <https://drafts.csswg.org/css-transitions/#animtype-rect> -impl Animate for ClipRect { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - use crate::values::computed::LengthOrAuto; - let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| { - let result = this.animate(other, procedure)?; - if let Procedure::Interpolate { .. } = procedure { - return Ok(result); - } - if result.is_auto() { - // FIXME(emilio): Why? A couple SMIL tests fail without this, - // but it seems extremely fishy. - return Err(()); - } - Ok(result) - }; - - Ok(ClipRect { - top: animate_component(&self.top, &other.top)?, - right: animate_component(&self.right, &other.right)?, - bottom: animate_component(&self.bottom, &other.bottom)?, - left: animate_component(&self.left, &other.left)?, - }) - } -} - -<% - FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale', - 'HueRotate', 'Invert', 'Opacity', 'Saturate', - 'Sepia' ] -%> - -/// <https://drafts.fxtf.org/filters/#animation-of-filters> -impl Animate for AnimatedFilter { - fn animate( - &self, - other: &Self, - procedure: Procedure, - ) -> Result<Self, ()> { - use crate::values::animated::animate_multiplicative_factor; - match (self, other) { - % for func in ['Blur', 'DropShadow', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: - (&Filter::${func}(ref this), &Filter::${func}(ref other)) => { - Ok(Filter::${func}(this.animate(other, procedure)?)) - }, - % endfor - % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']: - (&Filter::${func}(this), &Filter::${func}(other)) => { - Ok(Filter::${func}(animate_multiplicative_factor(this, other, procedure)?)) - }, - % endfor - _ => Err(()), - } - } -} - -/// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation> -impl ToAnimatedZero for AnimatedFilter { - fn to_animated_zero(&self) -> Result<Self, ()> { - match *self { - % for func in ['Blur', 'DropShadow', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: - Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)), - % endfor - % for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']: - Filter::${func}(_) => Ok(Filter::${func}(1.)), - % endfor - _ => Err(()), - } - } -} - -/// An iterator over all the properties that transition on a given style. -pub struct TransitionPropertyIterator<'a> { - style: &'a ComputedValues, - index_range: core::ops::Range<usize>, - longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>, -} - -impl<'a> TransitionPropertyIterator<'a> { - /// Create a `TransitionPropertyIterator` for the given style. - pub fn from_style(style: &'a ComputedValues) -> Self { - Self { - style, - index_range: 0..style.get_ui().transition_property_count(), - longhand_iterator: None, - } - } -} - -/// A single iteration of the TransitionPropertyIterator. -pub struct TransitionPropertyIteration { - /// The id of the longhand for this property. - pub longhand_id: LonghandId, - - /// The index of this property in the list of transition properties for this - /// iterator's style. - pub index: usize, -} - -impl<'a> Iterator for TransitionPropertyIterator<'a> { - type Item = TransitionPropertyIteration; - - fn next(&mut self) -> Option<Self::Item> { - use crate::values::computed::TransitionProperty; - loop { - if let Some(ref mut longhand_iterator) = self.longhand_iterator { - if let Some(longhand_id) = longhand_iterator.next() { - return Some(TransitionPropertyIteration { - longhand_id, - index: self.index_range.start - 1, - }); - } - self.longhand_iterator = None; - } - - let index = self.index_range.next()?; - match self.style.get_ui().transition_property_at(index) { - TransitionProperty::Longhand(longhand_id) => { - return Some(TransitionPropertyIteration { - longhand_id, - index, - }) - } - // In the other cases, we set up our state so that we are ready to - // compute the next value of the iterator and then loop (equivalent - // to calling self.next()). - TransitionProperty::Shorthand(ref shorthand_id) => - self.longhand_iterator = Some(shorthand_id.longhands()), - TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {} - } - } - } -} diff --git a/components/style/properties/longhands/background.mako.rs b/components/style/properties/longhands/background.mako.rs deleted file mode 100644 index ebc8f91fc13..00000000000 --- a/components/style/properties/longhands/background.mako.rs +++ /dev/null @@ -1,116 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("Background", inherited=False) %> - -${helpers.predefined_type( - "background-color", - "Color", - "computed::Color::transparent()", - engines="gecko servo", - initial_specified_value="SpecifiedValue::transparent()", - spec="https://drafts.csswg.org/css-backgrounds/#background-color", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - allow_quirks="Yes", - flags="CAN_ANIMATE_ON_COMPOSITOR", -)} - -${helpers.predefined_type( - "background-image", - "Image", - engines="gecko servo", - initial_value="computed::Image::None", - initial_specified_value="specified::Image::None", - spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", - vector="True", - animation_value_type="discrete", - ignored_when_colors_disabled="True", -)} - -% for (axis, direction, initial) in [("x", "Horizontal", "left"), ("y", "Vertical", "top")]: - ${helpers.predefined_type( - "background-position-" + axis, - "position::" + direction + "Position", - "computed::LengthPercentage::zero_percent()", - engines="gecko servo", - initial_specified_value="SpecifiedValue::initial_specified_value()", - spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-" + axis, - animation_value_type="ComputedValue", - vector=True, - vector_animation_type="repeatable_list", - )} -% endfor - -${helpers.predefined_type( - "background-repeat", - "BackgroundRepeat", - "computed::BackgroundRepeat::repeat()", - engines="gecko servo", - initial_specified_value="specified::BackgroundRepeat::repeat()", - animation_value_type="discrete", - vector=True, - spec="https://drafts.csswg.org/css-backgrounds/#the-background-repeat", -)} - -${helpers.single_keyword( - "background-attachment", - "scroll fixed" + (" local" if engine == "gecko" else ""), - engines="gecko servo", - vector=True, - gecko_enum_prefix="StyleImageLayerAttachment", - spec="https://drafts.csswg.org/css-backgrounds/#the-background-attachment", - animation_value_type="discrete", -)} - -${helpers.single_keyword( - "background-clip", - "border-box padding-box content-box", - engines="gecko servo", - extra_gecko_values="text", - vector=True, extra_prefixes="webkit", - gecko_enum_prefix="StyleGeometryBox", - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-backgrounds/#the-background-clip", - animation_value_type="discrete", -)} - -${helpers.single_keyword( - "background-origin", - "padding-box border-box content-box", - engines="gecko servo", - vector=True, extra_prefixes="webkit", - gecko_enum_prefix="StyleGeometryBox", - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-backgrounds/#the-background-origin", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "background-size", - "BackgroundSize", - engines="gecko servo", - initial_value="computed::BackgroundSize::auto()", - initial_specified_value="specified::BackgroundSize::auto()", - spec="https://drafts.csswg.org/css-backgrounds/#the-background-size", - vector=True, - vector_animation_type="repeatable_list", - animation_value_type="BackgroundSizeList", - extra_prefixes="webkit")} - -// https://drafts.fxtf.org/compositing/#background-blend-mode -${helpers.single_keyword( - "background-blend-mode", - """normal multiply screen overlay darken lighten color-dodge - color-burn hard-light soft-light difference exclusion hue - saturation color luminosity""", - gecko_enum_prefix="StyleBlend", - vector=True, - engines="gecko", - animation_value_type="discrete", - gecko_inexhaustive=True, - spec="https://drafts.fxtf.org/compositing/#background-blend-mode", -)} diff --git a/components/style/properties/longhands/border.mako.rs b/components/style/properties/longhands/border.mako.rs deleted file mode 100644 index 5c615a6aafe..00000000000 --- a/components/style/properties/longhands/border.mako.rs +++ /dev/null @@ -1,159 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Keyword, Method, ALL_CORNERS, PHYSICAL_SIDES, ALL_SIDES, maybe_moz_logical_alias %> - -<% data.new_style_struct("Border", inherited=False, - additional_methods=[Method("border_" + side + "_has_nonzero_width", - "bool") for side in ["top", "right", "bottom", "left"]]) %> -<% - def maybe_logical_spec(side, kind): - if side[1]: # if it is logical - return "https://drafts.csswg.org/css-logical-props/#propdef-border-%s-%s" % (side[0], kind) - else: - return "https://drafts.csswg.org/css-backgrounds/#border-%s-%s" % (side[0], kind) -%> -% for side in ALL_SIDES: - <% - side_name = side[0] - is_logical = side[1] - %> - ${helpers.predefined_type( - "border-%s-color" % side_name, "Color", - "computed_value::T::currentcolor()", - engines="gecko servo", - aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-color"), - spec=maybe_logical_spec(side, "color"), - animation_value_type="AnimatedColor", - logical=is_logical, - logical_group="border-color", - allow_quirks="No" if is_logical else "Yes", - ignored_when_colors_disabled=True, - )} - - ${helpers.predefined_type( - "border-%s-style" % side_name, "BorderStyle", - "specified::BorderStyle::None", - engines="gecko servo", - aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-style"), - spec=maybe_logical_spec(side, "style"), - animation_value_type="discrete" if not is_logical else "none", - logical=is_logical, - logical_group="border-style", - )} - - ${helpers.predefined_type( - "border-%s-width" % side_name, - "BorderSideWidth", - "app_units::Au::from_px(3)", - engines="gecko servo", - aliases=maybe_moz_logical_alias(engine, side, "-moz-border-%s-width"), - spec=maybe_logical_spec(side, "width"), - animation_value_type="NonNegativeLength", - logical=is_logical, - logical_group="border-width", - allow_quirks="No" if is_logical else "Yes", - servo_restyle_damage="reflow rebuild_and_reflow_inline" - )} -% endfor - -% for corner in ALL_CORNERS: - <% - corner_name = corner[0] - is_logical = corner[1] - if is_logical: - prefixes = None - else: - prefixes = "webkit" - %> - ${helpers.predefined_type( - "border-%s-radius" % corner_name, - "BorderCornerRadius", - "computed::BorderCornerRadius::zero()", - "parse", - engines="gecko servo", - extra_prefixes=prefixes, - spec=maybe_logical_spec(corner, "radius"), - boxed=True, - animation_value_type="BorderCornerRadius", - logical_group="border-radius", - logical=is_logical, - )} -% endfor - -${helpers.single_keyword( - "box-decoration-break", - "slice clone", - engines="gecko", - gecko_enum_prefix="StyleBoxDecorationBreak", - spec="https://drafts.csswg.org/css-break/#propdef-box-decoration-break", - animation_value_type="discrete", -)} - -${helpers.single_keyword( - "-moz-float-edge", - "content-box margin-box", - engines="gecko", - gecko_ffi_name="mFloatEdge", - gecko_enum_prefix="StyleFloatEdge", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-float-edge)", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "border-image-source", - "Image", - engines="gecko servo", - initial_value="computed::Image::None", - initial_specified_value="specified::Image::None", - spec="https://drafts.csswg.org/css-backgrounds/#the-background-image", - vector=False, - animation_value_type="discrete", - boxed=engine == "servo", - ignored_when_colors_disabled=True -)} - -${helpers.predefined_type( - "border-image-outset", - "NonNegativeLengthOrNumberRect", - engines="gecko servo", - initial_value="generics::rect::Rect::all(computed::NonNegativeLengthOrNumber::zero())", - initial_specified_value="generics::rect::Rect::all(specified::NonNegativeLengthOrNumber::zero())", - spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset", - animation_value_type="NonNegativeLengthOrNumberRect", - boxed=True, -)} - -${helpers.predefined_type( - "border-image-repeat", - "BorderImageRepeat", - "computed::BorderImageRepeat::stretch()", - engines="gecko servo", - initial_specified_value="specified::BorderImageRepeat::stretch()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat", -)} - -${helpers.predefined_type( - "border-image-width", - "BorderImageWidth", - engines="gecko servo", - initial_value="computed::BorderImageWidth::all(computed::BorderImageSideWidth::one())", - initial_specified_value="specified::BorderImageWidth::all(specified::BorderImageSideWidth::one())", - spec="https://drafts.csswg.org/css-backgrounds/#border-image-width", - animation_value_type="BorderImageWidth", - boxed=True, -)} - -${helpers.predefined_type( - "border-image-slice", - "BorderImageSlice", - engines="gecko servo", - initial_value="computed::BorderImageSlice::hundred_percent()", - initial_specified_value="specified::BorderImageSlice::hundred_percent()", - spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice", - animation_value_type="BorderImageSlice", - boxed=True, -)} diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs deleted file mode 100644 index 57375f5faa8..00000000000 --- a/components/style/properties/longhands/box.mako.rs +++ /dev/null @@ -1,591 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%> - -<% data.new_style_struct("Box", - inherited=False, - gecko_name="Display") %> - -${helpers.predefined_type( - "display", - "Display", - "computed::Display::inline()", - engines="gecko servo", - initial_specified_value="specified::Display::inline()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-display/#propdef-display", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "-moz-top-layer", - "none top", - engines="gecko", - gecko_enum_prefix="StyleTopLayer", - gecko_ffi_name="mTopLayer", - animation_value_type="none", - enabled_in="ua", - spec="Internal (not web-exposed)", -)} - -// An internal-only property for elements in a top layer -// https://fullscreen.spec.whatwg.org/#top-layer -${helpers.single_keyword( - "-servo-top-layer", - "none top", - engines="servo", - animation_value_type="none", - enabled_in="ua", - spec="Internal (not web-exposed)", -)} - -<%helpers:single_keyword - name="position" - values="static absolute relative fixed sticky" - engines="gecko servo" - animation_value_type="discrete" - gecko_enum_prefix="StylePositionProperty" - spec="https://drafts.csswg.org/css-position/#position-property" - servo_restyle_damage="rebuild_and_reflow" -> -impl computed_value::T { - pub fn is_absolutely_positioned(self) -> bool { - matches!(self, Self::Absolute | Self::Fixed) - } - pub fn is_relative(self) -> bool { - self == Self::Relative - } -} -</%helpers:single_keyword> - -${helpers.predefined_type( - "float", - "Float", - "computed::Float::None", - engines="gecko servo", - initial_specified_value="specified::Float::None", - spec="https://drafts.csswg.org/css-box/#propdef-float", - animation_value_type="discrete", - servo_restyle_damage="rebuild_and_reflow", - gecko_ffi_name="mFloat", -)} - -${helpers.predefined_type( - "clear", - "Clear", - "computed::Clear::None", - engines="gecko servo", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css2/#propdef-clear", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "vertical-align", - "VerticalAlign", - "computed::VerticalAlign::baseline()", - engines="gecko servo", - animation_value_type="ComputedValue", - spec="https://www.w3.org/TR/CSS2/visudet.html#propdef-vertical-align", - servo_restyle_damage = "reflow", -)} - -${helpers.predefined_type( - "baseline-source", - "BaselineSource", - "computed::BaselineSource::Auto", - engines="gecko servo-2013", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-inline-3/#baseline-source", - servo_restyle_damage = "reflow", -)} - -// CSS 2.1, Section 11 - Visual effects - -${helpers.single_keyword( - "-servo-overflow-clip-box", - "padding-box content-box", - engines="servo", - servo_pref="layout.legacy_layout", - animation_value_type="none", - enabled_in="ua", - spec="Internal, not web-exposed, \ - may be standardized in the future (https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)", -)} - - -% for direction in ["inline", "block"]: - ${helpers.predefined_type( - "overflow-clip-box-" + direction, - "OverflowClipBox", - "computed::OverflowClipBox::PaddingBox", - engines="gecko", - enabled_in="ua", - gecko_pref="layout.css.overflow-clip-box.enabled", - animation_value_type="discrete", - spec="Internal, may be standardized in the future: \ - https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box", - )} -% endfor - -% for (axis, logical) in ALL_AXES: - <% full_name = "overflow-{}".format(axis) %> - ${helpers.predefined_type( - full_name, - "Overflow", - "computed::Overflow::Visible", - engines="gecko servo", - logical_group="overflow", - logical=logical, - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-overflow-3/#propdef-{}".format(full_name), - servo_restyle_damage = "reflow", - gecko_pref="layout.css.overflow-logical.enabled" if logical else None, - )} -% endfor - -${helpers.predefined_type( - "overflow-anchor", - "OverflowAnchor", - "computed::OverflowAnchor::Auto", - engines="gecko", - initial_specified_value="specified::OverflowAnchor::Auto", - gecko_pref="layout.css.scroll-anchoring.enabled", - spec="https://drafts.csswg.org/css-scroll-anchoring/#exclusion-api", - animation_value_type="discrete", -)} - -<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %> - -${helpers.predefined_type( - "transform", - "Transform", - "generics::transform::Transform::none()", - engines="gecko servo", - extra_prefixes=transform_extra_prefixes, - animation_value_type="ComputedValue", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.csswg.org/css-transforms/#propdef-transform", - servo_restyle_damage="reflow_out_of_flow", -)} - -${helpers.predefined_type( - "rotate", - "Rotate", - "generics::transform::Rotate::None", - engines="gecko servo", - animation_value_type="ComputedValue", - boxed=True, - flags="CAN_ANIMATE_ON_COMPOSITOR", - gecko_pref="layout.css.individual-transform.enabled", - spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", - servo_restyle_damage = "reflow_out_of_flow", -)} - -${helpers.predefined_type( - "scale", - "Scale", - "generics::transform::Scale::None", - engines="gecko servo", - animation_value_type="ComputedValue", - boxed=True, - flags="CAN_ANIMATE_ON_COMPOSITOR", - gecko_pref="layout.css.individual-transform.enabled", - spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", - servo_restyle_damage = "reflow_out_of_flow", -)} - -${helpers.predefined_type( - "translate", - "Translate", - "generics::transform::Translate::None", - engines="gecko servo", - animation_value_type="ComputedValue", - boxed=True, - flags="CAN_ANIMATE_ON_COMPOSITOR", - gecko_pref="layout.css.individual-transform.enabled", - spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", - servo_restyle_damage="reflow_out_of_flow", -)} - -// Motion Path Module Level 1 -${helpers.predefined_type( - "offset-path", - "OffsetPath", - "computed::OffsetPath::none()", - engines="gecko", - animation_value_type="ComputedValue", - gecko_pref="layout.css.motion-path.enabled", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.fxtf.org/motion-1/#offset-path-property", - servo_restyle_damage="reflow_out_of_flow" -)} - -// Motion Path Module Level 1 -${helpers.predefined_type( - "offset-distance", - "LengthPercentage", - "computed::LengthPercentage::zero()", - engines="gecko", - animation_value_type="ComputedValue", - gecko_pref="layout.css.motion-path.enabled", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.fxtf.org/motion-1/#offset-distance-property", - servo_restyle_damage="reflow_out_of_flow" -)} - -// Motion Path Module Level 1 -${helpers.predefined_type( - "offset-rotate", - "OffsetRotate", - "computed::OffsetRotate::auto()", - engines="gecko", - animation_value_type="ComputedValue", - gecko_pref="layout.css.motion-path.enabled", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.fxtf.org/motion-1/#offset-rotate-property", - servo_restyle_damage="reflow_out_of_flow" -)} - -// Motion Path Module Level 1 -${helpers.predefined_type( - "offset-anchor", - "PositionOrAuto", - "computed::PositionOrAuto::auto()", - engines="gecko", - animation_value_type="ComputedValue", - gecko_pref="layout.css.motion-path.enabled", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.fxtf.org/motion-1/#offset-anchor-property", - servo_restyle_damage="reflow_out_of_flow", - boxed=True -)} - -// Motion Path Module Level 1 -${helpers.predefined_type( - "offset-position", - "OffsetPosition", - "computed::OffsetPosition::auto()", - engines="gecko", - animation_value_type="ComputedValue", - gecko_pref="layout.css.motion-path-offset-position.enabled", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.fxtf.org/motion-1/#offset-position-property", - servo_restyle_damage="reflow_out_of_flow", - boxed=True -)} - -// CSSOM View Module -// https://www.w3.org/TR/cssom-view-1/ -${helpers.single_keyword( - "scroll-behavior", - "auto smooth", - engines="gecko", - spec="https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior", - animation_value_type="discrete", - gecko_enum_prefix="StyleScrollBehavior", -)} - -${helpers.predefined_type( - "scroll-snap-align", - "ScrollSnapAlign", - "computed::ScrollSnapAlign::none()", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-align", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "scroll-snap-type", - "ScrollSnapType", - "computed::ScrollSnapType::none()", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "scroll-snap-stop", - "ScrollSnapStop", - "computed::ScrollSnapStop::Normal", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop", - animation_value_type="discrete", -)} - -% for (axis, logical) in ALL_AXES: - ${helpers.predefined_type( - "overscroll-behavior-" + axis, - "OverscrollBehavior", - "computed::OverscrollBehavior::Auto", - engines="gecko", - logical_group="overscroll-behavior", - logical=logical, - gecko_pref="layout.css.overscroll-behavior.enabled", - spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", - animation_value_type="discrete", - )} -% endfor - -// Compositing and Blending Level 1 -// http://www.w3.org/TR/compositing-1/ -${helpers.single_keyword( - "isolation", - "auto isolate", - engines="gecko", - spec="https://drafts.fxtf.org/compositing/#isolation", - gecko_enum_prefix="StyleIsolation", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "break-after", - "BreakBetween", - "computed::BreakBetween::Auto", - engines="gecko", - spec="https://drafts.csswg.org/css-break/#propdef-break-after", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "break-before", - "BreakBetween", - "computed::BreakBetween::Auto", - engines="gecko", - spec="https://drafts.csswg.org/css-break/#propdef-break-before", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "break-inside", - "BreakWithin", - "computed::BreakWithin::Auto", - engines="gecko", - spec="https://drafts.csswg.org/css-break/#propdef-break-inside", - animation_value_type="discrete", -)} - -// CSS Basic User Interface Module Level 3 -// http://dev.w3.org/csswg/css-ui -${helpers.predefined_type( - "resize", - "Resize", - "computed::Resize::None", - engines="gecko", - animation_value_type="discrete", - gecko_ffi_name="mResize", - spec="https://drafts.csswg.org/css-ui/#propdef-resize", -)} - -${helpers.predefined_type( - "perspective", - "Perspective", - "computed::Perspective::none()", - engines="gecko servo", - gecko_ffi_name="mChildPerspective", - spec="https://drafts.csswg.org/css-transforms/#perspective", - extra_prefixes=transform_extra_prefixes, - animation_value_type="AnimatedPerspective", - servo_restyle_damage = "reflow_out_of_flow", -)} - -${helpers.predefined_type( - "perspective-origin", - "Position", - "computed::position::Position::center()", - engines="gecko servo", - boxed=True, - extra_prefixes=transform_extra_prefixes, - spec="https://drafts.csswg.org/css-transforms-2/#perspective-origin-property", - animation_value_type="ComputedValue", - servo_restyle_damage="reflow_out_of_flow" -)} - -${helpers.single_keyword( - "backface-visibility", - "visible hidden", - engines="gecko servo", - gecko_enum_prefix="StyleBackfaceVisibility", - spec="https://drafts.csswg.org/css-transforms/#backface-visibility-property", - extra_prefixes=transform_extra_prefixes, - animation_value_type="discrete", -)} - -${helpers.single_keyword( - "transform-box", - "border-box fill-box view-box", - engines="gecko", - gecko_enum_prefix="StyleGeometryBox", - spec="https://drafts.csswg.org/css-transforms/#transform-box", - gecko_inexhaustive="True", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "transform-style", - "TransformStyle", - "computed::TransformStyle::Flat", - engines="gecko servo", - spec="https://drafts.csswg.org/css-transforms-2/#transform-style-property", - extra_prefixes=transform_extra_prefixes, - animation_value_type="discrete", - servo_restyle_damage = "reflow_out_of_flow", -)} - -${helpers.predefined_type( - "transform-origin", - "TransformOrigin", - "computed::TransformOrigin::initial_value()", - engines="gecko servo", - animation_value_type="ComputedValue", - extra_prefixes=transform_extra_prefixes, - gecko_ffi_name="mTransformOrigin", - boxed=True, - spec="https://drafts.csswg.org/css-transforms/#transform-origin-property", - servo_restyle_damage="reflow_out_of_flow", -)} - -${helpers.predefined_type( - "contain", - "Contain", - "specified::Contain::empty()", - engines="gecko", - animation_value_type="none", - spec="https://drafts.csswg.org/css-contain/#contain-property", -)} - -${helpers.predefined_type( - "content-visibility", - "ContentVisibility", - "computed::ContentVisibility::Visible", - engines="gecko", - spec="https://drafts.csswg.org/css-contain/#content-visibility", - gecko_pref="layout.css.content-visibility.enabled", - animation_value_type="none", -)} - -${helpers.predefined_type( - "container-type", - "ContainerType", - "computed::ContainerType::Normal", - engines="gecko servo", - animation_value_type="none", - enabled_in="ua", - gecko_pref="layout.css.container-queries.enabled", - servo_pref="layout.container-queries.enabled", - spec="https://drafts.csswg.org/css-contain-3/#container-type", -)} - -${helpers.predefined_type( - "container-name", - "ContainerName", - "computed::ContainerName::none()", - engines="gecko servo", - animation_value_type="none", - enabled_in="ua", - gecko_pref="layout.css.container-queries.enabled", - servo_pref="layout.container-queries.enabled", - spec="https://drafts.csswg.org/css-contain-3/#container-name", -)} - -${helpers.predefined_type( - "appearance", - "Appearance", - "computed::Appearance::None", - engines="gecko", - aliases="-moz-appearance -webkit-appearance", - spec="https://drafts.csswg.org/css-ui-4/#propdef-appearance", - animation_value_type="discrete", - gecko_ffi_name="mAppearance", -)} - -// The inherent widget type of an element, selected by specifying -// `appearance: auto`. -${helpers.predefined_type( - "-moz-default-appearance", - "Appearance", - "computed::Appearance::None", - engines="gecko", - animation_value_type="none", - spec="Internal (not web-exposed)", - enabled_in="chrome", - gecko_ffi_name="mDefaultAppearance", -)} - -${helpers.single_keyword( - "-moz-orient", - "inline block horizontal vertical", - engines="gecko", - gecko_ffi_name="mOrient", - gecko_enum_prefix="StyleOrient", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-orient)", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "will-change", - "WillChange", - "computed::WillChange::auto()", - engines="gecko", - animation_value_type="none", - spec="https://drafts.csswg.org/css-will-change/#will-change", -)} - -// The spec issue for the parse_method: https://github.com/w3c/csswg-drafts/issues/4102. -${helpers.predefined_type( - "shape-image-threshold", - "Opacity", - "0.0", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-shapes/#shape-image-threshold-property", -)} - -${helpers.predefined_type( - "shape-margin", - "NonNegativeLengthPercentage", - "computed::NonNegativeLengthPercentage::zero()", - engines="gecko", - animation_value_type="NonNegativeLengthPercentage", - spec="https://drafts.csswg.org/css-shapes/#shape-margin-property", -)} - -${helpers.predefined_type( - "shape-outside", - "basic_shape::ShapeOutside", - "generics::basic_shape::ShapeOutside::None", - engines="gecko", - animation_value_type="basic_shape::ShapeOutside", - spec="https://drafts.csswg.org/css-shapes/#shape-outside-property", -)} - -${helpers.predefined_type( - "touch-action", - "TouchAction", - "computed::TouchAction::auto()", - engines="gecko", - animation_value_type="discrete", - spec="https://compat.spec.whatwg.org/#touch-action", -)} - -${helpers.predefined_type( - "-webkit-line-clamp", - "LineClamp", - "computed::LineClamp::none()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-overflow-3/#line-clamp", -)} - -${helpers.predefined_type( - "scrollbar-gutter", - "ScrollbarGutter", - "computed::ScrollbarGutter::AUTO", - engines="gecko", - gecko_pref="layout.css.scrollbar-gutter.enabled", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property", -)} diff --git a/components/style/properties/longhands/column.mako.rs b/components/style/properties/longhands/column.mako.rs deleted file mode 100644 index 6ad4c400c22..00000000000 --- a/components/style/properties/longhands/column.mako.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("Column", inherited=False) %> - -${helpers.predefined_type( - "column-width", - "length::NonNegativeLengthOrAuto", - "computed::length::NonNegativeLengthOrAuto::auto()", - engines="gecko servo", - initial_specified_value="specified::length::NonNegativeLengthOrAuto::auto()", - animation_value_type="NonNegativeLengthOrAuto", - servo_pref="layout.columns.enabled", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-width", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "column-count", - "ColumnCount", - "computed::ColumnCount::auto()", - engines="gecko servo", - initial_specified_value="specified::ColumnCount::auto()", - servo_pref="layout.columns.enabled", - animation_value_type="AnimatedColumnCount", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-count", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "column-fill", - "balance auto", - engines="gecko", - animation_value_type="discrete", - gecko_enum_prefix="StyleColumnFill", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-fill", -)} - -${helpers.predefined_type( - "column-rule-width", - "BorderSideWidth", - "app_units::Au::from_px(3)", - engines="gecko", - initial_specified_value="specified::BorderSideWidth::medium()", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-width", - animation_value_type="NonNegativeLength", -)} - -// https://drafts.csswg.org/css-multicol-1/#crc -${helpers.predefined_type( - "column-rule-color", - "Color", - "computed_value::T::currentcolor()", - engines="gecko", - initial_specified_value="specified::Color::currentcolor()", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-color", -)} - -${helpers.single_keyword( - "column-span", - "none all", - engines="gecko servo", - servo_pref="layout.columns.enabled", - animation_value_type="discrete", - gecko_enum_prefix="StyleColumnSpan", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-span", -)} - -${helpers.predefined_type( - "column-rule-style", - "BorderStyle", - "computed::BorderStyle::None", - engines="gecko", - initial_specified_value="specified::BorderStyle::None", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-style", -)} diff --git a/components/style/properties/longhands/counters.mako.rs b/components/style/properties/longhands/counters.mako.rs deleted file mode 100644 index 8cea66bfe8b..00000000000 --- a/components/style/properties/longhands/counters.mako.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %> - -${helpers.predefined_type( - "content", - "Content", - "computed::Content::normal()", - engines="gecko servo", - initial_specified_value="specified::Content::normal()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-content/#propdef-content", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "counter-increment", - "CounterIncrement", - engines="gecko servo", - servo_pref="layout.legacy_layout", - initial_value="Default::default()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-lists/#propdef-counter-increment", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "counter-reset", - "CounterReset", - engines="gecko servo", - servo_pref="layout.legacy_layout", - initial_value="Default::default()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-reset", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "counter-set", - "CounterSet", - engines="gecko", - initial_value="Default::default()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-lists-3/#propdef-counter-set", - servo_restyle_damage="rebuild_and_reflow", -)} diff --git a/components/style/properties/longhands/effects.mako.rs b/components/style/properties/longhands/effects.mako.rs deleted file mode 100644 index 263189fdbf0..00000000000 --- a/components/style/properties/longhands/effects.mako.rs +++ /dev/null @@ -1,86 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -// Box-shadow, etc. -<% data.new_style_struct("Effects", inherited=False) %> - -${helpers.predefined_type( - "opacity", - "Opacity", - "1.0", - engines="gecko servo", - animation_value_type="ComputedValue", - flags="CAN_ANIMATE_ON_COMPOSITOR", - spec="https://drafts.csswg.org/css-color/#transparency", - servo_restyle_damage = "reflow_out_of_flow", -)} - -${helpers.predefined_type( - "box-shadow", - "BoxShadow", - None, - engines="gecko servo", - servo_pref="layout.legacy_layout", - vector=True, - simple_vector_bindings=True, - animation_value_type="AnimatedBoxShadowList", - vector_animation_type="with_zero", - extra_prefixes="webkit", - ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-backgrounds/#box-shadow", -)} - -${helpers.predefined_type( - "clip", - "ClipRectOrAuto", - "computed::ClipRectOrAuto::auto()", - engines="gecko servo", - animation_value_type="ComputedValue", - boxed=True, - allow_quirks="Yes", - spec="https://drafts.fxtf.org/css-masking/#clip-property", -)} - -${helpers.predefined_type( - "filter", - "Filter", - None, - engines="gecko servo", - vector=True, - simple_vector_bindings=True, - gecko_ffi_name="mFilters", - separator="Space", - animation_value_type="AnimatedFilterList", - vector_animation_type="with_zero", - extra_prefixes="webkit", - spec="https://drafts.fxtf.org/filters/#propdef-filter", -)} - -${helpers.predefined_type( - "backdrop-filter", - "Filter", - None, - engines="gecko", - vector=True, - simple_vector_bindings=True, - gecko_ffi_name="mBackdropFilters", - separator="Space", - animation_value_type="AnimatedFilterList", - vector_animation_type="with_zero", - gecko_pref="layout.css.backdrop-filter.enabled", - spec="https://drafts.fxtf.org/filter-effects-2/#propdef-backdrop-filter", -)} - -${helpers.single_keyword( - "mix-blend-mode", - """normal multiply screen overlay darken lighten color-dodge - color-burn hard-light soft-light difference exclusion hue - saturation color luminosity""" + ("plus-lighter" if engine == 'gecko' else ""), - engines="gecko servo", - gecko_enum_prefix="StyleBlend", - animation_value_type="discrete", - spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode", -)} diff --git a/components/style/properties/longhands/font.mako.rs b/components/style/properties/longhands/font.mako.rs deleted file mode 100644 index 91eb872592b..00000000000 --- a/components/style/properties/longhands/font.mako.rs +++ /dev/null @@ -1,488 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method, to_camel_case, to_rust_ident, to_camel_case_lower, SYSTEM_FONT_LONGHANDS %> - -<% data.new_style_struct("Font", inherited=True) %> - -${helpers.predefined_type( - "font-family", - "FontFamily", - engines="gecko servo", - initial_value="computed::FontFamily::serif()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-family", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "font-style", - "FontStyle", - engines="gecko servo", - initial_value="computed::FontStyle::normal()", - initial_specified_value="specified::FontStyle::normal()", - animation_value_type="FontStyle", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-style", - servo_restyle_damage="rebuild_and_reflow", -)} - -<% font_variant_caps_custom_consts= { "small-caps": "SMALLCAPS", - "all-small-caps": "ALLSMALL", - "petite-caps": "PETITECAPS", - "all-petite-caps": "ALLPETITE", - "titling-caps": "TITLING" } %> - -${helpers.single_keyword( - "font-variant-caps", - "normal small-caps", - engines="gecko servo", - extra_gecko_values="all-small-caps petite-caps all-petite-caps unicase titling-caps", - gecko_constant_prefix="NS_FONT_VARIANT_CAPS", - gecko_ffi_name="mFont.variantCaps", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-caps", - custom_consts=font_variant_caps_custom_consts, - animation_value_type="discrete", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "font-weight", - "FontWeight", - engines="gecko servo", - initial_value="computed::FontWeight::normal()", - initial_specified_value="specified::FontWeight::normal()", - animation_value_type="Number", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-weight", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "font-size", - "FontSize", - engines="gecko servo", - initial_value="computed::FontSize::medium()", - initial_specified_value="specified::FontSize::medium()", - animation_value_type="NonNegativeLength", - allow_quirks="Yes", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-size", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "font-size-adjust", - "FontSizeAdjust", - engines="gecko", - initial_value="computed::FontSizeAdjust::None", - initial_specified_value="specified::FontSizeAdjust::None", - animation_value_type="FontSizeAdjust", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-size-adjust", -)} - -${helpers.predefined_type( - "font-synthesis-weight", - "FontSynthesis", - engines="gecko", - initial_value="computed::FontSynthesis::Auto", - initial_specified_value="specified::FontSynthesis::Auto", - gecko_ffi_name="mFont.synthesisWeight", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-weight", -)} - -${helpers.predefined_type( - "font-synthesis-style", - "FontSynthesis", - engines="gecko", - initial_value="computed::FontSynthesis::Auto", - initial_specified_value="specified::FontSynthesis::Auto", - gecko_ffi_name="mFont.synthesisStyle", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-style", -)} - -${helpers.predefined_type( - "font-synthesis-small-caps", - "FontSynthesis", - engines="gecko", - initial_value="computed::FontSynthesis::Auto", - initial_specified_value="specified::FontSynthesis::Auto", - gecko_ffi_name="mFont.synthesisSmallCaps", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts-4/#font-synthesis-small-caps", -)} - -${helpers.predefined_type( - "font-stretch", - "FontStretch", - engines="gecko servo", - initial_value="computed::FontStretch::hundred()", - initial_specified_value="specified::FontStretch::normal()", - animation_value_type="Percentage", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-stretch", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "font-kerning", - "auto none normal", - engines="gecko", - gecko_ffi_name="mFont.kerning", - gecko_constant_prefix="NS_FONT_KERNING", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-kerning", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "font-variant-alternates", - "FontVariantAlternates", - engines="gecko", - initial_value="computed::FontVariantAlternates::default()", - initial_specified_value="specified::FontVariantAlternates::default()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-alternates", -)} - -${helpers.predefined_type( - "font-variant-east-asian", - "FontVariantEastAsian", - engines="gecko", - initial_value="computed::FontVariantEastAsian::empty()", - initial_specified_value="specified::FontVariantEastAsian::empty()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian", -)} - -${helpers.single_keyword( - "font-variant-emoji", - "normal text emoji unicode", - engines="gecko", - gecko_pref="layout.css.font-variant-emoji.enabled", - has_effect_on_gecko_scrollbars=False, - gecko_enum_prefix="StyleFontVariantEmoji", - gecko_ffi_name="mFont.variantEmoji", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-emoji", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "font-variant-ligatures", - "FontVariantLigatures", - engines="gecko", - initial_value="computed::FontVariantLigatures::empty()", - initial_specified_value="specified::FontVariantLigatures::empty()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures", -)} - -${helpers.predefined_type( - "font-variant-numeric", - "FontVariantNumeric", - engines="gecko", - initial_value="computed::FontVariantNumeric::empty()", - initial_specified_value="specified::FontVariantNumeric::empty()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric", -)} - -${helpers.single_keyword( - "font-variant-position", - "normal sub super", - engines="gecko", - gecko_ffi_name="mFont.variantPosition", - gecko_constant_prefix="NS_FONT_VARIANT_POSITION", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-position", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "font-feature-settings", - "FontFeatureSettings", - engines="gecko", - initial_value="computed::FontFeatureSettings::normal()", - initial_specified_value="specified::FontFeatureSettings::normal()", - extra_prefixes="moz:layout.css.prefixes.font-features", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-fonts/#propdef-font-feature-settings", -)} - -${helpers.predefined_type( - "font-variation-settings", - "FontVariationSettings", - engines="gecko", - gecko_pref="layout.css.font-variations.enabled", - has_effect_on_gecko_scrollbars=False, - initial_value="computed::FontVariationSettings::normal()", - initial_specified_value="specified::FontVariationSettings::normal()", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-fonts-4/#propdef-font-variation-settings" -)} - -${helpers.predefined_type( - "font-language-override", - "FontLanguageOverride", - engines="gecko", - initial_value="computed::FontLanguageOverride::normal()", - initial_specified_value="specified::FontLanguageOverride::normal()", - animation_value_type="discrete", - extra_prefixes="moz:layout.css.prefixes.font-features", - spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override", -)} - -${helpers.single_keyword( - "font-optical-sizing", - "auto none", - engines="gecko", - gecko_pref="layout.css.font-variations.enabled", - has_effect_on_gecko_scrollbars=False, - gecko_ffi_name="mFont.opticalSizing", - gecko_constant_prefix="NS_FONT_OPTICAL_SIZING", - animation_value_type="discrete", - spec="https://www.w3.org/TR/css-fonts-4/#font-optical-sizing-def", -)} - -${helpers.predefined_type( - "font-palette", - "FontPalette", - engines="gecko", - initial_value="computed::FontPalette::normal()", - initial_specified_value="specified::FontPalette::normal()", - animation_value_type="discrete", - gecko_pref="layout.css.font-palette.enabled", - has_effect_on_gecko_scrollbars=False, - spec="https://drafts.csswg.org/css-fonts/#font-palette-prop", -)} - -${helpers.predefined_type( - "-x-lang", - "XLang", - engines="gecko", - initial_value="computed::XLang::get_initial_value()", - animation_value_type="none", - enabled_in="", - has_effect_on_gecko_scrollbars=False, - spec="Internal (not web-exposed)", -)} - -${helpers.predefined_type( - "-moz-script-size-multiplier", - "MozScriptSizeMultiplier", - engines="gecko", - initial_value="computed::MozScriptSizeMultiplier::get_initial_value()", - animation_value_type="none", - gecko_ffi_name="mScriptSizeMultiplier", - enabled_in="", - has_effect_on_gecko_scrollbars=False, - spec="Internal (not web-exposed)", -)} - -${helpers.predefined_type( - "math-depth", - "MathDepth", - "0", - engines="gecko", - gecko_pref="layout.css.math-depth.enabled", - has_effect_on_gecko_scrollbars=False, - animation_value_type="none", - enabled_in="ua", - spec="https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property", -)} - -${helpers.single_keyword( - "math-style", - "normal compact", - engines="gecko", - gecko_enum_prefix="StyleMathStyle", - gecko_pref="layout.css.math-style.enabled", - spec="https://mathml-refresh.github.io/mathml-core/#the-math-style-property", - has_effect_on_gecko_scrollbars=False, - animation_value_type="none", - enabled_in="ua", - needs_conversion=True, -)} - -${helpers.single_keyword( - "-moz-math-variant", - """none normal bold italic bold-italic script bold-script - fraktur double-struck bold-fraktur sans-serif - bold-sans-serif sans-serif-italic sans-serif-bold-italic - monospace initial tailed looped stretched""", - engines="gecko", - gecko_enum_prefix="StyleMathVariant", - gecko_ffi_name="mMathVariant", - spec="Internal (not web-exposed)", - animation_value_type="none", - enabled_in="", - has_effect_on_gecko_scrollbars=False, - needs_conversion=True, -)} - -${helpers.predefined_type( - "-moz-script-min-size", - "MozScriptMinSize", - "specified::MozScriptMinSize::get_initial_value()", - engines="gecko", - animation_value_type="none", - enabled_in="", - has_effect_on_gecko_scrollbars=False, - gecko_ffi_name="mScriptMinSize", - spec="Internal (not web-exposed)", -)} - -${helpers.predefined_type( - "-x-text-scale", - "XTextScale", - "computed::XTextScale::All", - engines="gecko", - animation_value_type="none", - enabled_in="", - has_effect_on_gecko_scrollbars=False, - spec="Internal (not web-exposed)", -)} - -% if engine == "gecko": -pub mod system_font { - //! We deal with system fonts here - //! - //! System fonts can only be set as a group via the font shorthand. - //! They resolve at compute time (not parse time -- this lets the - //! browser respond to changes to the OS font settings). - //! - //! While Gecko handles these as a separate property and keyword - //! values on each property indicating that the font should be picked - //! from the -x-system-font property, we avoid this. Instead, - //! each font longhand has a special SystemFont variant which contains - //! the specified system font. When the cascade function (in helpers) - //! detects that a value has a system font, it will resolve it, and - //! cache it on the ComputedValues. After this, it can be just fetched - //! whenever a font longhand on the same element needs the system font. - //! - //! When a longhand property is holding a SystemFont, it's serialized - //! to an empty string as if its value comes from a shorthand with - //! variable reference. We may want to improve this behavior at some - //! point. See also https://github.com/w3c/csswg-drafts/issues/1586. - - use crate::properties::longhands; - use std::hash::{Hash, Hasher}; - use crate::values::computed::{ToComputedValue, Context}; - use crate::values::specified::font::SystemFont; - // ComputedValues are compared at times - // so we need these impls. We don't want to - // add Eq to Number (which contains a float) - // so instead we have an eq impl which skips the - // cached values - impl PartialEq for ComputedSystemFont { - fn eq(&self, other: &Self) -> bool { - self.system_font == other.system_font - } - } - impl Eq for ComputedSystemFont {} - - impl Hash for ComputedSystemFont { - fn hash<H: Hasher>(&self, hasher: &mut H) { - self.system_font.hash(hasher) - } - } - - impl ToComputedValue for SystemFont { - type ComputedValue = ComputedSystemFont; - - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - use crate::gecko_bindings::bindings; - use crate::gecko_bindings::structs::nsFont; - use crate::values::computed::font::FontSize; - use crate::values::specified::font::KeywordInfo; - use crate::values::generics::NonNegative; - use std::mem; - - let mut system = mem::MaybeUninit::<nsFont>::uninit(); - let system = unsafe { - bindings::Gecko_nsFont_InitSystem( - system.as_mut_ptr(), - *self, - &**cx.style().get_font(), - cx.device().document() - ); - &mut *system.as_mut_ptr() - }; - let size = NonNegative(cx.maybe_zoom_text(system.size.0)); - let ret = ComputedSystemFont { - font_family: system.family.clone(), - font_size: FontSize { - computed_size: size, - used_size: size, - keyword_info: KeywordInfo::none() - }, - font_weight: system.weight, - font_stretch: system.stretch, - font_style: system.style, - system_font: *self, - }; - unsafe { bindings::Gecko_nsFont_Destroy(system); } - ret - } - - fn from_computed_value(_: &ComputedSystemFont) -> Self { - unreachable!() - } - } - - #[inline] - /// Compute and cache a system font - /// - /// Must be called before attempting to compute a system font - /// specified value - pub fn resolve_system_font(system: SystemFont, context: &mut Context) { - // Checking if context.cached_system_font.is_none() isn't enough, - // if animating from one system font to another the cached system font - // may change - if Some(system) != context.cached_system_font.as_ref().map(|x| x.system_font) { - let computed = system.to_computed_value(context); - context.cached_system_font = Some(computed); - } - } - - #[derive(Clone, Debug)] - pub struct ComputedSystemFont { - % for name in SYSTEM_FONT_LONGHANDS: - pub ${name}: longhands::${name}::computed_value::T, - % endfor - pub system_font: SystemFont, - } - -} -% endif - -${helpers.single_keyword( - "-moz-osx-font-smoothing", - "auto grayscale", - engines="gecko", - gecko_constant_prefix="NS_FONT_SMOOTHING", - gecko_ffi_name="mFont.smoothing", - gecko_pref="layout.css.osx-font-smoothing.enabled", - has_effect_on_gecko_scrollbars=False, - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/font-smooth)", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "-moz-font-smoothing-background-color", - "color::MozFontSmoothingBackgroundColor", - "computed::color::MozFontSmoothingBackgroundColor::transparent()", - engines="gecko", - animation_value_type="none", - gecko_ffi_name="mFont.fontSmoothingBackgroundColor", - enabled_in="chrome", - spec="None (Nonstandard internal property)", -)} - -${helpers.predefined_type( - "-moz-min-font-size-ratio", - "Percentage", - "computed::Percentage::hundred()", - engines="gecko", - animation_value_type="none", - enabled_in="ua", - spec="Nonstandard (Internal-only)", -)} diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs deleted file mode 100644 index 947c66e9eed..00000000000 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ /dev/null @@ -1,96 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %> - -// TODO: collapse. Well, do tables first. -${helpers.single_keyword( - "visibility", - "visible hidden collapse", - engines="gecko servo", - gecko_ffi_name="mVisible", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-box/#propdef-visibility", - gecko_enum_prefix="StyleVisibility", -)} - -// CSS Writing Modes Level 3 -// https://drafts.csswg.org/css-writing-modes-3 -${helpers.single_keyword( - "writing-mode", - "horizontal-tb vertical-rl vertical-lr", - engines="gecko servo", - extra_gecko_values="sideways-rl sideways-lr", - gecko_aliases="lr=horizontal-tb lr-tb=horizontal-tb \ - rl=horizontal-tb rl-tb=horizontal-tb \ - tb=vertical-rl tb-rl=vertical-rl", - servo_pref="layout.writing-mode.enabled", - animation_value_type="none", - spec="https://drafts.csswg.org/css-writing-modes/#propdef-writing-mode", - gecko_enum_prefix="StyleWritingModeProperty", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "direction", - "ltr rtl", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="none", - spec="https://drafts.csswg.org/css-writing-modes/#propdef-direction", - gecko_enum_prefix="StyleDirection", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "-moz-box-collapse", - "flex legacy", - engines="gecko", - gecko_enum_prefix="StyleMozBoxCollapse", - animation_value_type="none", - enabled_in="chrome", - spec="None (internal)", -)} - -${helpers.single_keyword( - "text-orientation", - "mixed upright sideways", - engines="gecko", - gecko_aliases="sideways-right=sideways", - gecko_enum_prefix="StyleTextOrientation", - animation_value_type="none", - spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation", -)} - -${helpers.predefined_type( - "print-color-adjust", - "PrintColorAdjust", - "computed::PrintColorAdjust::Economy", - engines="gecko", - aliases="color-adjust", - spec="https://drafts.csswg.org/css-color-adjust/#print-color-adjust", - animation_value_type="discrete", -)} - -// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` -// And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337) -${helpers.predefined_type( - "image-rendering", - "ImageRendering", - "computed::ImageRendering::Auto", - engines="gecko servo", - spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", - animation_value_type="discrete", -)} - -${helpers.single_keyword( - "image-orientation", - "from-image none", - engines="gecko", - gecko_enum_prefix="StyleImageOrientation", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-images/#propdef-image-orientation", -)} diff --git a/components/style/properties/longhands/inherited_svg.mako.rs b/components/style/properties/longhands/inherited_svg.mako.rs deleted file mode 100644 index b9d55c01b20..00000000000 --- a/components/style/properties/longhands/inherited_svg.mako.rs +++ /dev/null @@ -1,217 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -// SVG 1.1 (Second Edition) -// https://www.w3.org/TR/SVG/ -<% data.new_style_struct("InheritedSVG", inherited=True, gecko_name="SVG") %> - -// Section 10 - Text - -${helpers.single_keyword( - "dominant-baseline", - """auto ideographic alphabetic hanging mathematical central middle - text-after-edge text-before-edge""", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/css-inline-3/#propdef-dominant-baseline", - gecko_enum_prefix="StyleDominantBaseline", -)} - -${helpers.single_keyword( - "text-anchor", - "start middle end", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG/text.html#TextAnchorProperty", - gecko_enum_prefix="StyleTextAnchor", -)} - -// Section 11 - Painting: Filling, Stroking and Marker Symbols -${helpers.single_keyword( - "color-interpolation", - "srgb auto linearrgb", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperty", - gecko_enum_prefix="StyleColorInterpolation", -)} - -${helpers.single_keyword( - "color-interpolation-filters", - "linearrgb auto srgb", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationFiltersProperty", - gecko_enum_prefix="StyleColorInterpolation", -)} - -${helpers.predefined_type( - "fill", - "SVGPaint", - "crate::values::computed::SVGPaint::black()", - engines="gecko", - animation_value_type="IntermediateSVGPaint", - boxed=True, - spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingFillPaint", -)} - -${helpers.predefined_type( - "fill-opacity", - "SVGOpacity", - "Default::default()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/painting.html#FillOpacity", -)} - -${helpers.predefined_type( - "fill-rule", - "FillRule", - "Default::default()", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#FillRuleProperty", -)} - -${helpers.single_keyword( - "shape-rendering", - "auto optimizespeed crispedges geometricprecision", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#ShapeRenderingProperty", - gecko_enum_prefix = "StyleShapeRendering", -)} - -${helpers.predefined_type( - "stroke", - "SVGPaint", - "Default::default()", - engines="gecko", - animation_value_type="IntermediateSVGPaint", - boxed=True, - spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint", -)} - -${helpers.predefined_type( - "stroke-width", - "SVGWidth", - "computed::SVGWidth::one()", - engines="gecko", - animation_value_type="crate::values::computed::SVGWidth", - spec="https://www.w3.org/TR/SVG2/painting.html#StrokeWidth", -)} - -${helpers.single_keyword( - "stroke-linecap", - "butt round square", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty", - gecko_enum_prefix = "StyleStrokeLinecap", -)} - -${helpers.single_keyword( - "stroke-linejoin", - "miter round bevel", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty", - gecko_enum_prefix = "StyleStrokeLinejoin", -)} - -${helpers.predefined_type( - "stroke-miterlimit", - "NonNegativeNumber", - "From::from(4.0)", - engines="gecko", - animation_value_type="crate::values::computed::NonNegativeNumber", - spec="https://www.w3.org/TR/SVG2/painting.html#StrokeMiterlimitProperty", -)} - -${helpers.predefined_type( - "stroke-opacity", - "SVGOpacity", - "Default::default()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/painting.html#StrokeOpacity", -)} - -${helpers.predefined_type( - "stroke-dasharray", - "SVGStrokeDashArray", - "Default::default()", - engines="gecko", - animation_value_type="crate::values::computed::SVGStrokeDashArray", - spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing", -)} - -${helpers.predefined_type( - "stroke-dashoffset", - "SVGLength", - "computed::SVGLength::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing", -)} - -// Section 14 - Clipping, Masking and Compositing -${helpers.predefined_type( - "clip-rule", - "FillRule", - "Default::default()", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty", -)} - -${helpers.predefined_type( - "marker-start", - "url::UrlOrNone", - "computed::url::UrlOrNone::none()", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties", -)} - -${helpers.predefined_type( - "marker-mid", - "url::UrlOrNone", - "computed::url::UrlOrNone::none()", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties", -)} - -${helpers.predefined_type( - "marker-end", - "url::UrlOrNone", - "computed::url::UrlOrNone::none()", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties", -)} - -${helpers.predefined_type( - "paint-order", - "SVGPaintOrder", - "computed::SVGPaintOrder::normal()", - engines="gecko", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG2/painting.html#PaintOrder", -)} - -${helpers.predefined_type( - "-moz-context-properties", - "MozContextProperties", - "computed::MozContextProperties::default()", - engines="gecko", - enabled_in="chrome", - gecko_pref="svg.context-properties.content.enabled", - has_effect_on_gecko_scrollbars=False, - animation_value_type="none", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)", -)} diff --git a/components/style/properties/longhands/inherited_table.mako.rs b/components/style/properties/longhands/inherited_table.mako.rs deleted file mode 100644 index a720c33f47e..00000000000 --- a/components/style/properties/longhands/inherited_table.mako.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("InheritedTable", inherited=True, gecko_name="TableBorder") %> - -${helpers.single_keyword( - "border-collapse", - "separate collapse", - engines="gecko servo", - servo_pref="layout.legacy_layout", - gecko_enum_prefix="StyleBorderCollapse", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-tables/#propdef-border-collapse", - servo_restyle_damage = "reflow", -)} - -${helpers.single_keyword( - "empty-cells", - "show hide", - engines="gecko servo", - servo_pref="layout.legacy_layout", - gecko_enum_prefix="StyleEmptyCells", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-tables/#propdef-empty-cells", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "caption-side", - "table::CaptionSide", - "computed::table::CaptionSide::Top", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-tables/#propdef-caption-side", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "border-spacing", - "BorderSpacing", - "computed::BorderSpacing::zero()", - engines="gecko servo", - animation_value_type="BorderSpacing", - boxed=True, - spec="https://drafts.csswg.org/css-tables/#propdef-border-spacing", - servo_restyle_damage="reflow", -)} diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs deleted file mode 100644 index 455812a8df0..00000000000 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ /dev/null @@ -1,407 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Keyword %> -<% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %> - -${helpers.predefined_type( - "color", - "ColorPropertyValue", - "crate::color::AbsoluteColor::black()", - engines="gecko servo", - animation_value_type="AbsoluteColor", - ignored_when_colors_disabled="True", - spec="https://drafts.csswg.org/css-color/#color", -)} - -${helpers.predefined_type( - "line-height", - "LineHeight", - "computed::LineHeight::normal()", - engines="gecko servo", - animation_value_type="LineHeight", - spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height", - servo_restyle_damage="reflow" -)} - -// CSS Text Module Level 3 - -${helpers.predefined_type( - "text-transform", - "TextTransform", - "computed::TextTransform::none()", - engines="gecko servo", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#propdef-text-transform", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "hyphens", - "manual none auto", - engines="gecko", - gecko_enum_prefix="StyleHyphens", - animation_value_type="discrete", - extra_prefixes="moz", - spec="https://drafts.csswg.org/css-text/#propdef-hyphens", -)} - -// TODO: Support <percentage> -${helpers.single_keyword( - "-moz-text-size-adjust", - "auto none", - engines="gecko", - gecko_enum_prefix="StyleTextSizeAdjust", - gecko_ffi_name="mTextSizeAdjust", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-size-adjust/#adjustment-control", - aliases="-webkit-text-size-adjust", -)} - -${helpers.predefined_type( - "text-indent", - "LengthPercentage", - "computed::LengthPercentage::zero()", - engines="gecko servo", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-text/#propdef-text-indent", - allow_quirks="Yes", - servo_restyle_damage = "reflow", -)} - -// Also known as "word-wrap" (which is more popular because of IE), but this is -// the preferred name per CSS-TEXT 6.2. -${helpers.predefined_type( - "overflow-wrap", - "OverflowWrap", - "computed::OverflowWrap::Normal", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#propdef-overflow-wrap", - aliases="word-wrap", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "word-break", - "WordBreak", - "computed::WordBreak::Normal", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#propdef-word-break", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "text-justify", - "TextJustify", - "computed::TextJustify::Auto", - engines="gecko servo", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#propdef-text-justify", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "text-align-last", - "TextAlignLast", - "computed::text::TextAlignLast::Auto", - engines="gecko servo", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#propdef-text-align-last", -)} - -// TODO make this a shorthand and implement text-align-last/text-align-all -${helpers.predefined_type( - "text-align", - "TextAlign", - "computed::TextAlign::Start", - engines="gecko servo", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#propdef-text-align", - servo_restyle_damage = "reflow", -)} - -${helpers.predefined_type( - "letter-spacing", - "LetterSpacing", - "computed::LetterSpacing::normal()", - engines="gecko servo", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-text/#propdef-letter-spacing", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "word-spacing", - "WordSpacing", - "computed::WordSpacing::zero()", - engines="gecko servo", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-text/#propdef-word-spacing", - servo_restyle_damage="rebuild_and_reflow", -)} - -<%helpers:single_keyword - name="white-space" - values="normal pre nowrap pre-wrap pre-line" - engines="gecko servo", - extra_gecko_values="break-spaces -moz-pre-space" - gecko_enum_prefix="StyleWhiteSpace" - needs_conversion="True" - animation_value_type="discrete" - spec="https://drafts.csswg.org/css-text/#propdef-white-space" - servo_restyle_damage="rebuild_and_reflow" -> - % if engine == "servo": - impl SpecifiedValue { - pub fn allow_wrap(&self) -> bool { - match *self { - SpecifiedValue::Nowrap | - SpecifiedValue::Pre => false, - SpecifiedValue::Normal | - SpecifiedValue::PreWrap | - SpecifiedValue::PreLine => true, - } - } - - pub fn preserve_newlines(&self) -> bool { - match *self { - SpecifiedValue::Normal | - SpecifiedValue::Nowrap => false, - SpecifiedValue::Pre | - SpecifiedValue::PreWrap | - SpecifiedValue::PreLine => true, - } - } - - pub fn preserve_spaces(&self) -> bool { - match *self { - SpecifiedValue::Normal | - SpecifiedValue::Nowrap | - SpecifiedValue::PreLine => false, - SpecifiedValue::Pre | - SpecifiedValue::PreWrap => true, - } - } - } - % endif -</%helpers:single_keyword> - -${helpers.predefined_type( - "text-shadow", - "SimpleShadow", - None, - engines="gecko servo", - servo_pref="layout.legacy_layout", - vector=True, - vector_animation_type="with_zero", - animation_value_type="AnimatedTextShadowList", - ignored_when_colors_disabled=True, - simple_vector_bindings=True, - spec="https://drafts.csswg.org/css-text-decor-3/#text-shadow-property", -)} - -${helpers.predefined_type( - "text-emphasis-style", - "TextEmphasisStyle", - "computed::TextEmphasisStyle::None", - engines="gecko", - initial_specified_value="SpecifiedValue::None", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style", -)} - -${helpers.predefined_type( - "text-emphasis-position", - "TextEmphasisPosition", - "computed::TextEmphasisPosition::OVER", - engines="gecko", - initial_specified_value="specified::TextEmphasisPosition::OVER", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-position", -)} - -${helpers.predefined_type( - "text-emphasis-color", - "Color", - "computed_value::T::currentcolor()", - engines="gecko", - initial_specified_value="specified::Color::currentcolor()", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-color", -)} - -${helpers.predefined_type( - "tab-size", - "NonNegativeLengthOrNumber", - "generics::length::LengthOrNumber::Number(From::from(8.0))", - engines="gecko", - animation_value_type="LengthOrNumber", - spec="https://drafts.csswg.org/css-text-3/#tab-size-property", - aliases="-moz-tab-size", -)} - -${helpers.predefined_type( - "line-break", - "LineBreak", - "computed::LineBreak::Auto", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-3/#line-break-property", -)} - -// CSS Compatibility -// https://compat.spec.whatwg.org -${helpers.predefined_type( - "-webkit-text-fill-color", - "Color", - "computed_value::T::currentcolor()", - engines="gecko", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - spec="https://compat.spec.whatwg.org/#the-webkit-text-fill-color", -)} - -${helpers.predefined_type( - "-webkit-text-stroke-color", - "Color", - "computed_value::T::currentcolor()", - initial_specified_value="specified::Color::currentcolor()", - engines="gecko", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-color", -)} - -${helpers.predefined_type( - "-webkit-text-stroke-width", - "LineWidth", - "app_units::Au(0)", - engines="gecko", - initial_specified_value="specified::LineWidth::zero()", - spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke-width", - animation_value_type="discrete", -)} - -// CSS Ruby Layout Module Level 1 -// https://drafts.csswg.org/css-ruby/ -${helpers.single_keyword( - "ruby-align", - "space-around start center space-between", - engines="gecko", - animation_value_type="discrete", - gecko_enum_prefix="StyleRubyAlign", - spec="https://drafts.csswg.org/css-ruby/#ruby-align-property", -)} - -${helpers.predefined_type( - "ruby-position", - "RubyPosition", - "computed::RubyPosition::AlternateOver", - engines="gecko", - spec="https://drafts.csswg.org/css-ruby/#ruby-position-property", - animation_value_type="discrete", -)} - -// CSS Writing Modes Module Level 3 -// https://drafts.csswg.org/css-writing-modes-3/ - -${helpers.single_keyword( - "text-combine-upright", - "none all", - engines="gecko", - gecko_enum_prefix="StyleTextCombineUpright", - animation_value_type="none", - spec="https://drafts.csswg.org/css-writing-modes-3/#text-combine-upright", -)} - -// SVG 1.1: Section 11 - Painting: Filling, Stroking and Marker Symbols -${helpers.single_keyword( - "text-rendering", - "auto optimizespeed optimizelegibility geometricprecision", - engines="gecko servo", - gecko_enum_prefix="StyleTextRendering", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVG11/painting.html#TextRenderingProperty", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "-moz-control-character-visibility", - "text::MozControlCharacterVisibility", - "Default::default()", - engines="gecko", - enabled_in="chrome", - gecko_pref="layout.css.moz-control-character-visibility.enabled", - has_effect_on_gecko_scrollbars=False, - animation_value_type="none", - spec="Nonstandard" -)} - -// text underline offset -${helpers.predefined_type( - "text-underline-offset", - "LengthPercentageOrAuto", - "computed::LengthPercentageOrAuto::auto()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-text-decor-4/#underline-offset", -)} - -// text underline position -${helpers.predefined_type( - "text-underline-position", - "TextUnderlinePosition", - "computed::TextUnderlinePosition::AUTO", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-decor-3/#text-underline-position-property", -)} - -// text decoration skip ink -${helpers.predefined_type( - "text-decoration-skip-ink", - "TextDecorationSkipInk", - "computed::TextDecorationSkipInk::Auto", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property", -)} - -// hyphenation character -${helpers.predefined_type( - "hyphenate-character", - "HyphenateCharacter", - "computed::HyphenateCharacter::Auto", - engines="gecko", - gecko_pref="layout.css.hyphenate-character.enabled", - has_effect_on_gecko_scrollbars=False, - animation_value_type="discrete", - spec="https://www.w3.org/TR/css-text-4/#hyphenate-character", -)} - -${helpers.predefined_type( - "forced-color-adjust", - "ForcedColorAdjust", - "computed::ForcedColorAdjust::Auto", - engines="gecko", - gecko_pref="layout.css.forced-color-adjust.enabled", - has_effect_on_gecko_scrollbars=False, - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-color-adjust-1/#forced-color-adjust-prop", -)} - -${helpers.single_keyword( - "-webkit-text-security", - "none circle disc square", - engines="gecko", - gecko_enum_prefix="StyleTextSecurity", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text/#MISSING", -)} diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs deleted file mode 100644 index 9c1218118e0..00000000000 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ /dev/null @@ -1,118 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("InheritedUI", inherited=True, gecko_name="UI") %> - -${helpers.predefined_type( - "cursor", - "Cursor", - "computed::Cursor::auto()", - engines="gecko servo", - initial_specified_value="specified::Cursor::auto()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-ui/#cursor", -)} - -// NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact) -// is nonstandard, slated for CSS4-UI. -// TODO(pcwalton): SVG-only values. -${helpers.single_keyword( - "pointer-events", - "auto none", - engines="gecko servo", - animation_value_type="discrete", - extra_gecko_values="visiblepainted visiblefill visiblestroke visible painted fill stroke all", - spec="https://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty", - gecko_enum_prefix="StylePointerEvents", -)} - -${helpers.single_keyword( - "-moz-inert", - "none inert", - engines="gecko", - gecko_ffi_name="mInert", - gecko_enum_prefix="StyleInert", - animation_value_type="discrete", - enabled_in="ua", - spec="Nonstandard (https://html.spec.whatwg.org/multipage/#inert-subtrees)", -)} - -${helpers.single_keyword( - "-moz-user-input", - "auto none", - engines="gecko", - gecko_ffi_name="mUserInput", - gecko_enum_prefix="StyleUserInput", - animation_value_type="discrete", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-input)", -)} - -${helpers.single_keyword( - "-moz-user-modify", - "read-only read-write write-only", - engines="gecko", - gecko_ffi_name="mUserModify", - gecko_enum_prefix="StyleUserModify", - needs_conversion=True, - animation_value_type="discrete", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-modify)", -)} - -${helpers.single_keyword( - "-moz-user-focus", - "none ignore normal select-after select-before select-menu select-same select-all", - engines="gecko", - gecko_ffi_name="mUserFocus", - gecko_enum_prefix="StyleUserFocus", - animation_value_type="discrete", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-user-focus)", -)} - -${helpers.predefined_type( - "caret-color", - "color::CaretColor", - "generics::color::CaretColor::auto()", - engines="gecko", - spec="https://drafts.csswg.org/css-ui/#caret-color", - animation_value_type="CaretColor", - ignored_when_colors_disabled=True, -)} - -${helpers.predefined_type( - "accent-color", - "ColorOrAuto", - "generics::color::ColorOrAuto::Auto", - engines="gecko", - spec="https://drafts.csswg.org/css-ui-4/#widget-accent", - gecko_pref="layout.css.accent-color.enabled", - animation_value_type="ColorOrAuto", - ignored_when_colors_disabled=True, - has_effect_on_gecko_scrollbars=False, -)} - -${helpers.predefined_type( - "color-scheme", - "ColorScheme", - "specified::color::ColorScheme::normal()", - engines="gecko", - spec="https://drafts.csswg.org/css-color-adjust/#color-scheme-prop", - gecko_pref="layout.css.color-scheme.enabled", - animation_value_type="discrete", - has_effect_on_gecko_scrollbars=False, - ignored_when_colors_disabled=True, - enabled_in="chrome", -)} - -${helpers.predefined_type( - "scrollbar-color", - "ui::ScrollbarColor", - "Default::default()", - engines="gecko", - spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-color", - animation_value_type="ScrollbarColor", - boxed=True, - ignored_when_colors_disabled=True, -)} diff --git a/components/style/properties/longhands/list.mako.rs b/components/style/properties/longhands/list.mako.rs deleted file mode 100644 index f3c84295afc..00000000000 --- a/components/style/properties/longhands/list.mako.rs +++ /dev/null @@ -1,76 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("List", inherited=True) %> - -${helpers.single_keyword( - "list-style-position", - "outside inside", - engines="gecko servo", - servo_pref="layout.legacy_layout", - gecko_enum_prefix="StyleListStylePosition", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-lists/#propdef-list-style-position", - servo_restyle_damage="rebuild_and_reflow", -)} - -// TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1: -// -// decimal-leading-zero, armenian, upper-armenian, lower-armenian, georgian, lower-roman, -// upper-roman -// -// [1]: http://dev.w3.org/csswg/css-counter-styles/ -% if engine == "servo": - ${helpers.single_keyword( - "list-style-type", - """disc none circle square disclosure-open disclosure-closed - decimal lower-alpha upper-alpha arabic-indic bengali cambodian cjk-decimal devanagari - gujarati gurmukhi kannada khmer lao malayalam mongolian myanmar oriya persian telugu - thai tibetan cjk-earthly-branch cjk-heavenly-stem lower-greek hiragana hiragana-iroha - katakana katakana-iroha - """, - engines="servo", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", - servo_restyle_damage="rebuild_and_reflow", - )} -% endif -% if engine == "gecko": - ${helpers.predefined_type( - "list-style-type", - "ListStyleType", - "computed::ListStyleType::disc()", - engines="gecko", - initial_specified_value="specified::ListStyleType::disc()", - animation_value_type="discrete", - boxed=True, - spec="https://drafts.csswg.org/css-lists/#propdef-list-style-type", - servo_restyle_damage="rebuild_and_reflow", - )} -% endif - -${helpers.predefined_type( - "list-style-image", - "Image", - engines="gecko servo", - initial_value="computed::Image::None", - initial_specified_value="specified::Image::None", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-lists/#propdef-list-style-image", - boxed=engine == "servo", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "quotes", - "Quotes", - "computed::Quotes::get_initial_value()", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-content/#propdef-quotes", - servo_restyle_damage="rebuild_and_reflow", -)} diff --git a/components/style/properties/longhands/margin.mako.rs b/components/style/properties/longhands/margin.mako.rs deleted file mode 100644 index 251ee85f265..00000000000 --- a/components/style/properties/longhands/margin.mako.rs +++ /dev/null @@ -1,52 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import ALL_SIDES, DEFAULT_RULES_AND_PAGE, maybe_moz_logical_alias %> -<% data.new_style_struct("Margin", inherited=False) %> - -% for side in ALL_SIDES: - <% - spec = "https://drafts.csswg.org/css-box/#propdef-margin-%s" % side[0] - if side[1]: - spec = "https://drafts.csswg.org/css-logical-props/#propdef-margin-%s" % side[1] - %> - ${helpers.predefined_type( - "margin-%s" % side[0], - "LengthPercentageOrAuto", - "computed::LengthPercentageOrAuto::zero()", - engines="gecko servo", - aliases=maybe_moz_logical_alias(engine, side, "-moz-margin-%s"), - allow_quirks="No" if side[1] else "Yes", - animation_value_type="ComputedValue", - logical=side[1], - logical_group="margin", - spec=spec, - rule_types_allowed=DEFAULT_RULES_AND_PAGE, - servo_restyle_damage="reflow" - )} -% endfor - -${helpers.predefined_type( - "overflow-clip-margin", - "Length", - "computed::Length::zero()", - parse_method="parse_non_negative", - engines="gecko", - spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin", - animation_value_type="ComputedValue", -)} - -% for side in ALL_SIDES: - ${helpers.predefined_type( - "scroll-margin-%s" % side[0], - "Length", - "computed::Length::zero()", - engines="gecko", - logical=side[1], - logical_group="scroll-margin", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin-%s" % side[0], - animation_value_type="ComputedValue", - )} -% endfor diff --git a/components/style/properties/longhands/outline.mako.rs b/components/style/properties/longhands/outline.mako.rs deleted file mode 100644 index 714cbc81ecc..00000000000 --- a/components/style/properties/longhands/outline.mako.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> - -<% data.new_style_struct("Outline", - inherited=False, - additional_methods=[Method("outline_has_nonzero_width", "bool")]) %> - -// TODO(pcwalton): `invert` -${helpers.predefined_type( - "outline-color", - "Color", - "computed_value::T::currentcolor()", - engines="gecko servo", - initial_specified_value="specified::Color::currentcolor()", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-ui/#propdef-outline-color", -)} - -${helpers.predefined_type( - "outline-style", - "OutlineStyle", - "computed::OutlineStyle::none()", - engines="gecko servo", - initial_specified_value="specified::OutlineStyle::none()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-ui/#propdef-outline-style", -)} - -${helpers.predefined_type( - "outline-width", - "BorderSideWidth", - "app_units::Au::from_px(3)", - engines="gecko servo", - initial_specified_value="specified::BorderSideWidth::medium()", - animation_value_type="NonNegativeLength", - spec="https://drafts.csswg.org/css-ui/#propdef-outline-width", -)} - -${helpers.predefined_type( - "outline-offset", - "Length", - "crate::values::computed::Length::new(0.)", - engines="gecko servo", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-ui/#propdef-outline-offset", -)} diff --git a/components/style/properties/longhands/padding.mako.rs b/components/style/properties/longhands/padding.mako.rs deleted file mode 100644 index 5b7b95cc3b3..00000000000 --- a/components/style/properties/longhands/padding.mako.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import ALL_SIDES, maybe_moz_logical_alias %> -<% data.new_style_struct("Padding", inherited=False) %> - -% for side in ALL_SIDES: - <% - spec = "https://drafts.csswg.org/css-box/#propdef-padding-%s" % side[0] - if side[1]: - spec = "https://drafts.csswg.org/css-logical-props/#propdef-padding-%s" % side[1] - %> - ${helpers.predefined_type( - "padding-%s" % side[0], - "NonNegativeLengthPercentage", - "computed::NonNegativeLengthPercentage::zero()", - engines="gecko servo", - aliases=maybe_moz_logical_alias(engine, side, "-moz-padding-%s"), - animation_value_type="NonNegativeLengthPercentage", - logical=side[1], - logical_group="padding", - spec=spec, - allow_quirks="No" if side[1] else "Yes", - servo_restyle_damage="reflow rebuild_and_reflow_inline" - )} -% endfor - -% for side in ALL_SIDES: - ${helpers.predefined_type( - "scroll-padding-%s" % side[0], - "NonNegativeLengthPercentageOrAuto", - "computed::NonNegativeLengthPercentageOrAuto::auto()", - engines="gecko", - logical=side[1], - logical_group="scroll-padding", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-%s" % side[0], - animation_value_type="NonNegativeLengthPercentageOrAuto", - )} -% endfor diff --git a/components/style/properties/longhands/page.mako.rs b/components/style/properties/longhands/page.mako.rs deleted file mode 100644 index b857ce0ca39..00000000000 --- a/components/style/properties/longhands/page.mako.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import PAGE_RULE %> - -<% data.new_style_struct("Page", inherited=False) %> - -${helpers.predefined_type( - "size", - "PageSize", - "computed::PageSize::auto()", - engines="gecko", - gecko_pref="layout.css.page-size.enabled", - initial_specified_value="specified::PageSize::auto()", - spec="https://drafts.csswg.org/css-page-3/#page-size-prop", - boxed=True, - animation_value_type="none", - rule_types_allowed=PAGE_RULE, -)} - -${helpers.predefined_type( - "page", - "PageName", - "computed::PageName::auto()", - engines="gecko", - spec="https://drafts.csswg.org/css-page-3/#using-named-pages", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "page-orientation", - "PageOrientation", - "computed::PageOrientation::Upright", - engines="gecko", - gecko_pref="layout.css.page-orientation.enabled", - initial_specified_value="specified::PageOrientation::Upright", - spec="https://drafts.csswg.org/css-page-3/#page-orientation-prop", - animation_value_type="none", - rule_types_allowed=PAGE_RULE, -)} diff --git a/components/style/properties/longhands/position.mako.rs b/components/style/properties/longhands/position.mako.rs deleted file mode 100644 index f7241f70d21..00000000000 --- a/components/style/properties/longhands/position.mako.rs +++ /dev/null @@ -1,478 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%! from data import to_rust_ident %> -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import ALL_SIZES, PHYSICAL_SIDES, LOGICAL_SIDES %> - -<% data.new_style_struct("Position", inherited=False) %> - -// "top" / "left" / "bottom" / "right" -% for side in PHYSICAL_SIDES: - ${helpers.predefined_type( - side, - "LengthPercentageOrAuto", - "computed::LengthPercentageOrAuto::auto()", - engines="gecko servo", - spec="https://www.w3.org/TR/CSS2/visuren.html#propdef-%s" % side, - animation_value_type="ComputedValue", - allow_quirks="Yes", - servo_restyle_damage="reflow_out_of_flow", - logical_group="inset", - )} -% endfor -// inset-* logical properties, map to "top" / "left" / "bottom" / "right" -% for side in LOGICAL_SIDES: - ${helpers.predefined_type( - "inset-%s" % side, - "LengthPercentageOrAuto", - "computed::LengthPercentageOrAuto::auto()", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical-props/#propdef-inset-%s" % side, - animation_value_type="ComputedValue", - logical=True, - logical_group="inset", - )} -% endfor - -#[cfg(feature = "gecko")] -macro_rules! impl_align_conversions { - ($name: path) => { - impl From<u8> for $name { - fn from(bits: u8) -> $name { - $name(crate::values::specified::align::AlignFlags::from_bits(bits) - .expect("bits contain valid flag")) - } - } - - impl From<$name> for u8 { - fn from(v: $name) -> u8 { - v.0.bits() - } - } - }; -} - -${helpers.predefined_type( - "z-index", - "ZIndex", - "computed::ZIndex::auto()", - engines="gecko servo", - spec="https://www.w3.org/TR/CSS2/visuren.html#z-index", - animation_value_type="ComputedValue", -)} - -// CSS Flexible Box Layout Module Level 1 -// http://www.w3.org/TR/css3-flexbox/ - -// Flex container properties -${helpers.single_keyword( - "flex-direction", - "row row-reverse column column-reverse", - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - spec="https://drafts.csswg.org/css-flexbox/#flex-direction-property", - extra_prefixes="webkit", - animation_value_type="discrete", - servo_restyle_damage = "reflow", - gecko_enum_prefix = "StyleFlexDirection", -)} - -${helpers.single_keyword( - "flex-wrap", - "nowrap wrap wrap-reverse", - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property", - extra_prefixes="webkit", - animation_value_type="discrete", - servo_restyle_damage = "reflow", - gecko_enum_prefix = "StyleFlexWrap", -)} - -% if engine in "servo": - // FIXME: Update Servo to support the same Syntax as Gecko. - ${helpers.single_keyword( - "justify-content", - "flex-start stretch flex-end center space-between space-around", - engines="servo", - servo_pref="layout.flexbox.enabled", - extra_prefixes="webkit", - spec="https://drafts.csswg.org/css-align/#propdef-justify-content", - animation_value_type="discrete", - servo_restyle_damage = "reflow", - )} -% endif -% if engine == "gecko": - ${helpers.predefined_type( - "justify-content", - "JustifyContent", - "specified::JustifyContent(specified::ContentDistribution::normal())", - engines="gecko", - spec="https://drafts.csswg.org/css-align/#propdef-justify-content", - extra_prefixes="webkit", - animation_value_type="discrete", - servo_restyle_damage="reflow", - )} - - ${helpers.predefined_type( - "justify-tracks", - "JustifyTracks", - "specified::JustifyTracks::default()", - engines="gecko", - gecko_pref="layout.css.grid-template-masonry-value.enabled", - animation_value_type="discrete", - servo_restyle_damage="reflow", - spec="https://github.com/w3c/csswg-drafts/issues/4650", - )} -% endif - -% if engine == "servo": - // FIXME: Update Servo to support the same Syntax as Gecko. - ${helpers.single_keyword( - "align-content", - "stretch flex-start flex-end center space-between space-around", - engines="servo", - servo_pref="layout.flexbox.enabled", - extra_prefixes="webkit", - spec="https://drafts.csswg.org/css-align/#propdef-align-content", - animation_value_type="discrete", - servo_restyle_damage="reflow", - )} - - ${helpers.single_keyword( - "align-items", - "stretch flex-start flex-end center baseline", - engines="servo", - servo_pref="layout.flexbox.enabled", - extra_prefixes="webkit", - spec="https://drafts.csswg.org/css-flexbox/#align-items-property", - animation_value_type="discrete", - servo_restyle_damage="reflow", - )} -% endif -% if engine == "gecko": - ${helpers.predefined_type( - "align-content", - "AlignContent", - "specified::AlignContent(specified::ContentDistribution::normal())", - engines="gecko", - spec="https://drafts.csswg.org/css-align/#propdef-align-content", - extra_prefixes="webkit", - animation_value_type="discrete", - servo_restyle_damage="reflow", - )} - - ${helpers.predefined_type( - "align-tracks", - "AlignTracks", - "specified::AlignTracks::default()", - engines="gecko", - gecko_pref="layout.css.grid-template-masonry-value.enabled", - animation_value_type="discrete", - servo_restyle_damage="reflow", - spec="https://github.com/w3c/csswg-drafts/issues/4650", - )} - - ${helpers.predefined_type( - "align-items", - "AlignItems", - "specified::AlignItems::normal()", - engines="gecko", - spec="https://drafts.csswg.org/css-align/#propdef-align-items", - extra_prefixes="webkit", - animation_value_type="discrete", - servo_restyle_damage="reflow", - )} - - #[cfg(feature = "gecko")] - impl_align_conversions!(crate::values::specified::align::AlignItems); - - ${helpers.predefined_type( - "justify-items", - "JustifyItems", - "computed::JustifyItems::legacy()", - engines="gecko", - spec="https://drafts.csswg.org/css-align/#propdef-justify-items", - animation_value_type="discrete", - )} - - #[cfg(feature = "gecko")] - impl_align_conversions!(crate::values::specified::align::JustifyItems); -% endif - -// Flex item properties -${helpers.predefined_type( - "flex-grow", - "NonNegativeNumber", - "From::from(0.0)", - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property", - extra_prefixes="webkit", - animation_value_type="NonNegativeNumber", - servo_restyle_damage="reflow", -)} - -${helpers.predefined_type( - "flex-shrink", - "NonNegativeNumber", - "From::from(1.0)", - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property", - extra_prefixes="webkit", - animation_value_type="NonNegativeNumber", - servo_restyle_damage = "reflow", -)} - -// https://drafts.csswg.org/css-align/#align-self-property -% if engine == "servo": - // FIXME: Update Servo to support the same syntax as Gecko. - ${helpers.single_keyword( - "align-self", - "auto stretch flex-start flex-end center baseline", - engines="servo", - servo_pref="layout.flexbox.enabled", - extra_prefixes="webkit", - spec="https://drafts.csswg.org/css-flexbox/#propdef-align-self", - animation_value_type="discrete", - servo_restyle_damage = "reflow", - )} -% endif -% if engine == "gecko": - ${helpers.predefined_type( - "align-self", - "AlignSelf", - "specified::AlignSelf(specified::SelfAlignment::auto())", - engines="gecko", - spec="https://drafts.csswg.org/css-align/#align-self-property", - extra_prefixes="webkit", - animation_value_type="discrete", - )} - - ${helpers.predefined_type( - "justify-self", - "JustifySelf", - "specified::JustifySelf(specified::SelfAlignment::auto())", - engines="gecko", - spec="https://drafts.csswg.org/css-align/#justify-self-property", - animation_value_type="discrete", - )} - - #[cfg(feature = "gecko")] - impl_align_conversions!(crate::values::specified::align::SelfAlignment); -% endif - -// https://drafts.csswg.org/css-flexbox/#propdef-order -${helpers.predefined_type( - "order", - "Integer", - "0", - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - extra_prefixes="webkit", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-flexbox/#order-property", - servo_restyle_damage="reflow", -)} - -${helpers.predefined_type( - "flex-basis", - "FlexBasis", - "computed::FlexBasis::auto()", - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - spec="https://drafts.csswg.org/css-flexbox/#flex-basis-property", - extra_prefixes="webkit", - animation_value_type="FlexBasis", - servo_restyle_damage="reflow", - boxed=True, -)} - -% for (size, logical) in ALL_SIZES: - <% - spec = "https://drafts.csswg.org/css-box/#propdef-%s" - if logical: - spec = "https://drafts.csswg.org/css-logical-props/#propdef-%s" - %> - // width, height, block-size, inline-size - ${helpers.predefined_type( - size, - "Size", - "computed::Size::auto()", - engines="gecko servo", - logical=logical, - logical_group="size", - allow_quirks="No" if logical else "Yes", - spec=spec % size, - animation_value_type="Size", - servo_restyle_damage="reflow", - )} - // min-width, min-height, min-block-size, min-inline-size - ${helpers.predefined_type( - "min-%s" % size, - "Size", - "computed::Size::auto()", - engines="gecko servo", - logical=logical, - logical_group="min-size", - allow_quirks="No" if logical else "Yes", - spec=spec % size, - animation_value_type="Size", - servo_restyle_damage="reflow", - )} - ${helpers.predefined_type( - "max-%s" % size, - "MaxSize", - "computed::MaxSize::none()", - engines="gecko servo", - logical=logical, - logical_group="max-size", - allow_quirks="No" if logical else "Yes", - spec=spec % size, - animation_value_type="MaxSize", - servo_restyle_damage="reflow", - )} -% endfor - -${helpers.single_keyword( - "box-sizing", - "content-box border-box", - engines="gecko servo", - extra_prefixes="moz:layout.css.prefixes.box-sizing webkit", - spec="https://drafts.csswg.org/css-ui/#propdef-box-sizing", - gecko_enum_prefix="StyleBoxSizing", - custom_consts={ "content-box": "Content", "border-box": "Border" }, - animation_value_type="discrete", - servo_restyle_damage = "reflow", -)} - -${helpers.single_keyword( - "object-fit", - "fill contain cover none scale-down", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-images/#propdef-object-fit", - gecko_enum_prefix = "StyleObjectFit", -)} - -${helpers.predefined_type( - "object-position", - "Position", - "computed::Position::center()", - engines="gecko", - boxed=True, - spec="https://drafts.csswg.org/css-images-3/#the-object-position", - animation_value_type="ComputedValue", -)} - -% for kind in ["row", "column"]: - % for range in ["start", "end"]: - ${helpers.predefined_type( - "grid-%s-%s" % (kind, range), - "GridLine", - "Default::default()", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-%s-%s" % (kind, range), - )} - % endfor - - ${helpers.predefined_type( - "grid-auto-%ss" % kind, - "ImplicitGridTracks", - "Default::default()", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-%ss" % kind, - )} - - ${helpers.predefined_type( - "grid-template-%ss" % kind, - "GridTemplateComponent", - "specified::GenericGridTemplateComponent::None", - engines="gecko", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-%ss" % kind, - animation_value_type="ComputedValue", - )} - -% endfor - -${helpers.predefined_type( - "masonry-auto-flow", - "MasonryAutoFlow", - "computed::MasonryAutoFlow::initial()", - engines="gecko", - gecko_pref="layout.css.grid-template-masonry-value.enabled", - animation_value_type="discrete", - spec="https://github.com/w3c/csswg-drafts/issues/4650", -)} - -${helpers.predefined_type( - "grid-auto-flow", - "GridAutoFlow", - "computed::GridAutoFlow::ROW", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow", -)} - -${helpers.predefined_type( - "grid-template-areas", - "GridTemplateAreas", - "computed::GridTemplateAreas::none()", - engines="gecko", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas", -)} - -${helpers.predefined_type( - "column-gap", - "length::NonNegativeLengthPercentageOrNormal", - "computed::length::NonNegativeLengthPercentageOrNormal::normal()", - engines="gecko servo", - aliases="grid-column-gap" if engine == "gecko" else "", - servo_pref="layout.columns.enabled", - spec="https://drafts.csswg.org/css-align-3/#propdef-column-gap", - animation_value_type="NonNegativeLengthPercentageOrNormal", - servo_restyle_damage="reflow", -)} - -// no need for -moz- prefixed alias for this property -${helpers.predefined_type( - "row-gap", - "length::NonNegativeLengthPercentageOrNormal", - "computed::length::NonNegativeLengthPercentageOrNormal::normal()", - engines="gecko", - aliases="grid-row-gap", - spec="https://drafts.csswg.org/css-align-3/#propdef-row-gap", - animation_value_type="NonNegativeLengthPercentageOrNormal", - servo_restyle_damage="reflow", -)} - -${helpers.predefined_type( - "aspect-ratio", - "AspectRatio", - "computed::AspectRatio::auto()", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-sizing-4/#aspect-ratio", - servo_restyle_damage="reflow", -)} - -% for (size, logical) in ALL_SIZES: - ${helpers.predefined_type( - "contain-intrinsic-" + size, - "ContainIntrinsicSize", - "computed::ContainIntrinsicSize::None", - engines="gecko", - logical_group="contain-intrinsic-size", - logical=logical, - gecko_pref="layout.css.contain-intrinsic-size.enabled", - spec="https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override", - animation_value_type="NonNegativeLength", - )} -% endfor diff --git a/components/style/properties/longhands/svg.mako.rs b/components/style/properties/longhands/svg.mako.rs deleted file mode 100644 index d579473a023..00000000000 --- a/components/style/properties/longhands/svg.mako.rs +++ /dev/null @@ -1,258 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("SVG", inherited=False, gecko_name="SVGReset") %> - -${helpers.single_keyword( - "vector-effect", - "none non-scaling-stroke", - engines="gecko", - gecko_enum_prefix="StyleVectorEffect", - animation_value_type="discrete", - spec="https://www.w3.org/TR/SVGTiny12/painting.html#VectorEffectProperty", -)} - -// Section 13 - Gradients and Patterns - -${helpers.predefined_type( - "stop-color", - "Color", - "computed::Color::black()", - engines="gecko", - animation_value_type="AnimatedRGBA", - spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty", -)} - -${helpers.predefined_type( - "stop-opacity", - "Opacity", - "1.0", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/pservers.html#StopOpacityProperty", -)} - -// Section 15 - Filter Effects - -${helpers.predefined_type( - "flood-color", - "Color", - "computed::Color::black()", - engines="gecko", - animation_value_type="AnimatedColor", - spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty", -)} - -${helpers.predefined_type( - "flood-opacity", - "Opacity", - "1.0", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://drafts.fxtf.org/filter-effects/#FloodOpacityProperty", -)} - -${helpers.predefined_type( - "lighting-color", - "Color", - "computed::Color::white()", - engines="gecko", - animation_value_type="AnimatedColor", - spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty", -)} - -// CSS Masking Module Level 1 -// https://drafts.fxtf.org/css-masking -${helpers.single_keyword( - "mask-type", - "luminance alpha", - engines="gecko", - gecko_enum_prefix="StyleMaskType", - animation_value_type="discrete", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-type", -)} - -${helpers.predefined_type( - "clip-path", - "basic_shape::ClipPath", - "generics::basic_shape::ClipPath::None", - engines="gecko", - extra_prefixes="webkit", - animation_value_type="basic_shape::ClipPath", - spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path", -)} - -${helpers.single_keyword( - "mask-mode", - "match-source alpha luminance", - engines="gecko", - gecko_enum_prefix="StyleMaskMode", - vector=True, - animation_value_type="discrete", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-mode", -)} - -${helpers.predefined_type( - "mask-repeat", - "BackgroundRepeat", - "computed::BackgroundRepeat::repeat()", - engines="gecko", - initial_specified_value="specified::BackgroundRepeat::repeat()", - extra_prefixes="webkit", - animation_value_type="discrete", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-repeat", - vector=True, -)} - -% for (axis, direction) in [("x", "Horizontal"), ("y", "Vertical")]: - ${helpers.predefined_type( - "mask-position-" + axis, - "position::" + direction + "Position", - "computed::LengthPercentage::zero_percent()", - engines="gecko", - extra_prefixes="webkit", - initial_specified_value="specified::PositionComponent::Center", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-position", - animation_value_type="ComputedValue", - vector_animation_type="repeatable_list", - vector=True, - )} -% endfor - -${helpers.single_keyword( - "mask-clip", - "border-box content-box padding-box", - engines="gecko", - extra_gecko_values="fill-box stroke-box view-box no-clip", - vector=True, - extra_prefixes="webkit", - gecko_enum_prefix="StyleGeometryBox", - gecko_inexhaustive=True, - animation_value_type="discrete", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-clip", -)} - -${helpers.single_keyword( - "mask-origin", - "border-box content-box padding-box", - engines="gecko", - extra_gecko_values="fill-box stroke-box view-box", - vector=True, - extra_prefixes="webkit", - gecko_enum_prefix="StyleGeometryBox", - gecko_inexhaustive=True, - animation_value_type="discrete", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-origin", -)} - -${helpers.predefined_type( - "mask-size", - "background::BackgroundSize", - "computed::BackgroundSize::auto()", - engines="gecko", - initial_specified_value="specified::BackgroundSize::auto()", - extra_prefixes="webkit", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-size", - animation_value_type="MaskSizeList", - vector=True, - vector_animation_type="repeatable_list", -)} - -${helpers.single_keyword( - "mask-composite", - "add subtract intersect exclude", - engines="gecko", - gecko_enum_prefix="StyleMaskComposite", - vector=True, - extra_prefixes="webkit", - animation_value_type="discrete", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-composite", -)} - -${helpers.predefined_type( - "mask-image", - "Image", - engines="gecko", - initial_value="computed::Image::None", - initial_specified_value="specified::Image::None", - parse_method="parse_with_cors_anonymous", - spec="https://drafts.fxtf.org/css-masking/#propdef-mask-image", - vector=True, - extra_prefixes="webkit", - animation_value_type="discrete", -)} - -${helpers.predefined_type( - "x", - "LengthPercentage", - "computed::LengthPercentage::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/geometry.html#X", -)} - -${helpers.predefined_type( - "y", - "LengthPercentage", - "computed::LengthPercentage::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/geometry.html#Y", -)} - -${helpers.predefined_type( - "cx", - "LengthPercentage", - "computed::LengthPercentage::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/geometry.html#CX", -)} - -${helpers.predefined_type( - "cy", - "LengthPercentage", - "computed::LengthPercentage::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/geometry.html#CY", -)} - -${helpers.predefined_type( - "rx", - "NonNegativeLengthPercentageOrAuto", - "computed::NonNegativeLengthPercentageOrAuto::auto()", - engines="gecko", - animation_value_type="LengthPercentageOrAuto", - spec="https://svgwg.org/svg2-draft/geometry.html#RX", -)} - -${helpers.predefined_type( - "ry", - "NonNegativeLengthPercentageOrAuto", - "computed::NonNegativeLengthPercentageOrAuto::auto()", - engines="gecko", - animation_value_type="LengthPercentageOrAuto", - spec="https://svgwg.org/svg2-draft/geometry.html#RY", -)} - -${helpers.predefined_type( - "r", - "NonNegativeLengthPercentage", - "computed::NonNegativeLengthPercentage::zero()", - engines="gecko", - animation_value_type="LengthPercentage", - spec="https://svgwg.org/svg2-draft/geometry.html#R", -)} - -${helpers.predefined_type( - "d", - "DProperty", - "specified::DProperty::none()", - engines="gecko", - animation_value_type="ComputedValue", - spec="https://svgwg.org/svg2-draft/paths.html#TheDProperty", -)} diff --git a/components/style/properties/longhands/table.mako.rs b/components/style/properties/longhands/table.mako.rs deleted file mode 100644 index 73fdd51ffbf..00000000000 --- a/components/style/properties/longhands/table.mako.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<% data.new_style_struct("Table", inherited=False) %> - -${helpers.single_keyword( - "table-layout", - "auto fixed", - engines="gecko servo", - servo_pref="layout.legacy_layout", - gecko_ffi_name="mLayoutStrategy", - animation_value_type="discrete", - gecko_enum_prefix="StyleTableLayout", - spec="https://drafts.csswg.org/css-tables/#propdef-table-layout", - servo_restyle_damage="reflow", -)} - -${helpers.predefined_type( - "-x-span", - "Integer", - "1", - engines="gecko", - spec="Internal-only (for `<col span>` pres attr)", - animation_value_type="none", - enabled_in="", -)} diff --git a/components/style/properties/longhands/text.mako.rs b/components/style/properties/longhands/text.mako.rs deleted file mode 100644 index 30471821b9b..00000000000 --- a/components/style/properties/longhands/text.mako.rs +++ /dev/null @@ -1,83 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> - -<% data.new_style_struct("Text", inherited=False, gecko_name="TextReset") %> - -${helpers.predefined_type( - "text-overflow", - "TextOverflow", - "computed::TextOverflow::get_initial_value()", - engines="gecko servo", - servo_pref="layout.legacy_layout", - animation_value_type="discrete", - boxed=True, - spec="https://drafts.csswg.org/css-ui/#propdef-text-overflow", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "unicode-bidi", - "normal embed isolate bidi-override isolate-override plaintext", - engines="gecko servo", - servo_pref="layout.legacy_layout", - gecko_enum_prefix="StyleUnicodeBidi", - animation_value_type="none", - spec="https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.predefined_type( - "text-decoration-line", - "TextDecorationLine", - "specified::TextDecorationLine::none()", - engines="gecko servo", - initial_specified_value="specified::TextDecorationLine::none()", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-line", - servo_restyle_damage="rebuild_and_reflow", -)} - -${helpers.single_keyword( - "text-decoration-style", - "solid double dotted dashed wavy -moz-none", - engines="gecko servo", - gecko_enum_prefix="StyleTextDecorationStyle", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style", -)} - -${helpers.predefined_type( - "text-decoration-color", - "Color", - "computed_value::T::currentcolor()", - engines="gecko servo", - initial_specified_value="specified::Color::currentcolor()", - animation_value_type="AnimatedColor", - ignored_when_colors_disabled=True, - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color", -)} - -${helpers.predefined_type( - "initial-letter", - "InitialLetter", - "computed::InitialLetter::normal()", - engines="gecko", - initial_specified_value="specified::InitialLetter::normal()", - animation_value_type="discrete", - gecko_pref="layout.css.initial-letter.enabled", - spec="https://drafts.csswg.org/css-inline/#sizing-drop-initials", -)} - -${helpers.predefined_type( - "text-decoration-thickness", - "TextDecorationLength", - "generics::text::GenericTextDecorationLength::Auto", - engines="gecko", - initial_specified_value="generics::text::GenericTextDecorationLength::Auto", - animation_value_type="ComputedValue", - spec="https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property" -)} diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs deleted file mode 100644 index 80724d6e5d3..00000000000 --- a/components/style/properties/longhands/ui.mako.rs +++ /dev/null @@ -1,397 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %> - -// CSS Basic User Interface Module Level 1 -// https://drafts.csswg.org/css-ui-3/ -<% data.new_style_struct("UI", inherited=False, gecko_name="UIReset") %> - -// TODO spec says that UAs should not support this -// we should probably remove from gecko (https://bugzilla.mozilla.org/show_bug.cgi?id=1328331) -${helpers.single_keyword( - "ime-mode", - "auto normal active disabled inactive", - engines="gecko", - gecko_enum_prefix="StyleImeMode", - gecko_ffi_name="mIMEMode", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-ui/#input-method-editor", -)} - -${helpers.single_keyword( - "scrollbar-width", - "auto thin none", - engines="gecko", - gecko_enum_prefix="StyleScrollbarWidth", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-scrollbars-1/#scrollbar-width" -)} - -${helpers.predefined_type( - "user-select", - "UserSelect", - "computed::UserSelect::Auto", - engines="gecko", - extra_prefixes="moz webkit", - animation_value_type="discrete", - spec="https://drafts.csswg.org/css-ui-4/#propdef-user-select", -)} - -// TODO(emilio): This probably should be hidden from content. -${helpers.single_keyword( - "-moz-window-dragging", - "default drag no-drag", - engines="gecko", - gecko_ffi_name="mWindowDragging", - gecko_enum_prefix="StyleWindowDragging", - animation_value_type="discrete", - spec="None (Nonstandard Firefox-only property)", -)} - -// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this -// property. -${helpers.single_keyword( - "-moz-window-shadow", - "default none", - engines="gecko", - gecko_ffi_name="mWindowShadow", - gecko_enum_prefix="StyleWindowShadow", - gecko_inexhaustive=True, - animation_value_type="discrete", - enabled_in="chrome", - spec="None (Nonstandard internal property)", -)} - -${helpers.predefined_type( - "-moz-window-opacity", - "Opacity", - "1.0", - engines="gecko", - gecko_ffi_name="mWindowOpacity", - animation_value_type="ComputedValue", - spec="None (Nonstandard internal property)", - enabled_in="chrome", -)} - -${helpers.predefined_type( - "-moz-window-transform", - "Transform", - "generics::transform::Transform::none()", - engines="gecko", - animation_value_type="ComputedValue", - spec="None (Nonstandard internal property)", - enabled_in="chrome", -)} - -${helpers.predefined_type( - "-moz-window-transform-origin", - "TransformOrigin", - "computed::TransformOrigin::initial_value()", - engines="gecko", - animation_value_type="ComputedValue", - gecko_ffi_name="mWindowTransformOrigin", - boxed=True, - spec="None (Nonstandard internal property)", - enabled_in="chrome", -)} - -${helpers.predefined_type( - "-moz-window-input-region-margin", - "Length", - "computed::Length::zero()", - engines="gecko", - animation_value_type="ComputedValue", - spec="None (Nonstandard internal property)", - enabled_in="chrome", -)} - -// Hack to allow chrome to hide stuff only visually (without hiding it from -// a11y). -${helpers.predefined_type( - "-moz-subtree-hidden-only-visually", - "BoolInteger", - "computed::BoolInteger::zero()", - engines="gecko", - animation_value_type="discrete", - spec="None (Nonstandard internal property)", - enabled_in="chrome", -)} - -// TODO(emilio): Probably also should be hidden from content. -${helpers.predefined_type( - "-moz-force-broken-image-icon", - "BoolInteger", - "computed::BoolInteger::zero()", - engines="gecko", - animation_value_type="discrete", - spec="None (Nonstandard Firefox-only property)", -)} - -<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %> - -${helpers.predefined_type( - "transition-duration", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - parse_method="parse_non_negative", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", -)} - -${helpers.predefined_type( - "transition-timing-function", - "TimingFunction", - "computed::TimingFunction::ease()", - engines="gecko servo", - initial_specified_value="specified::TimingFunction::ease()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function", -)} - -${helpers.predefined_type( - "transition-property", - "TransitionProperty", - "computed::TransitionProperty::all()", - engines="gecko servo", - initial_specified_value="specified::TransitionProperty::all()", - vector=True, - allow_empty="NotInitial", - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property", -)} - -${helpers.predefined_type( - "transition-delay", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=transition_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay", -)} - -<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %> - -${helpers.predefined_type( - "animation-name", - "AnimationName", - "computed::AnimationName::none()", - engines="gecko servo", - initial_specified_value="specified::AnimationName::none()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-name", -)} - -${helpers.predefined_type( - "animation-duration", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - parse_method="parse_non_negative", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration", -)} - -// animation-timing-function is the exception to the rule for allowed_in_keyframe_block: -// https://drafts.csswg.org/css-animations/#keyframes -${helpers.predefined_type( - "animation-timing-function", - "TimingFunction", - "computed::TimingFunction::ease()", - engines="gecko servo", - initial_specified_value="specified::TimingFunction::ease()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function", -)} - -${helpers.predefined_type( - "animation-iteration-count", - "AnimationIterationCount", - "computed::AnimationIterationCount::one()", - engines="gecko servo", - initial_specified_value="specified::AnimationIterationCount::one()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count", -)} - -<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %> -${helpers.single_keyword( - "animation-direction", - "normal reverse alternate alternate-reverse", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="PlaybackDirection", - custom_consts=animation_direction_custom_consts, - extra_prefixes=animation_extra_prefixes, - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-play-state", - "running paused", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - extra_prefixes=animation_extra_prefixes, - gecko_enum_prefix="StyleAnimationPlayState", - spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-fill-mode", - "none forwards backwards both", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="FillMode", - extra_prefixes=animation_extra_prefixes, - gecko_inexhaustive=True, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.single_keyword( - "animation-composition", - "replace add accumulate", - engines="gecko servo", - need_index=True, - animation_value_type="none", - vector=True, - gecko_enum_prefix="CompositeOperation", - gecko_inexhaustive=True, - gecko_pref="layout.css.animation-composition.enabled", - servo_pref="layout.unimplemented", - spec="https://drafts.csswg.org/css-animations-2/#animation-composition", -)} - -${helpers.predefined_type( - "animation-delay", - "Time", - "computed::Time::zero()", - engines="gecko servo", - initial_specified_value="specified::Time::zero()", - vector=True, - need_index=True, - animation_value_type="none", - extra_prefixes=animation_extra_prefixes, - spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "animation-timeline", - "AnimationTimeline", - "computed::AnimationTimeline::auto()", - engines="gecko servo", - servo_pref="layout.unimplemented", - initial_specified_value="specified::AnimationTimeline::auto()", - vector=True, - need_index=True, - animation_value_type="none", - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "scroll-timeline-name", - "ScrollTimelineName", - "computed::ScrollTimelineName::none()", - vector=True, - need_index=True, - engines="gecko", - animation_value_type="none", - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-name", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "scroll-timeline-axis", - "ScrollAxis", - "computed::ScrollAxis::default()", - vector=True, - need_index=True, - engines="gecko", - animation_value_type="none", - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "view-timeline-name", - "ScrollTimelineName", - "computed::ScrollTimelineName::none()", - vector=True, - need_index=True, - engines="gecko", - animation_value_type="none", - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-name", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "view-timeline-axis", - "ScrollAxis", - "computed::ScrollAxis::default()", - vector=True, - need_index=True, - engines="gecko", - animation_value_type="none", - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} - -${helpers.predefined_type( - "view-timeline-inset", - "ViewTimelineInset", - "computed::ViewTimelineInset::default()", - vector=True, - need_index=True, - engines="gecko", - animation_value_type="none", - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis", - rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, -)} diff --git a/components/style/properties/longhands/xul.mako.rs b/components/style/properties/longhands/xul.mako.rs deleted file mode 100644 index a939b018920..00000000000 --- a/components/style/properties/longhands/xul.mako.rs +++ /dev/null @@ -1,79 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import Method %> - -// Non-standard properties that Gecko uses for XUL elements. -<% data.new_style_struct("XUL", inherited=False) %> - -${helpers.single_keyword( - "-moz-box-align", - "stretch start center baseline end", - engines="gecko", - gecko_ffi_name="mBoxAlign", - gecko_enum_prefix="StyleBoxAlign", - animation_value_type="discrete", - aliases="-webkit-box-align", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-align)", -)} - -${helpers.single_keyword( - "-moz-box-direction", - "normal reverse", - engines="gecko", - gecko_ffi_name="mBoxDirection", - gecko_enum_prefix="StyleBoxDirection", - animation_value_type="discrete", - aliases="-webkit-box-direction", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-direction)", -)} - -${helpers.predefined_type( - "-moz-box-flex", - "NonNegativeNumber", - "From::from(0.)", - engines="gecko", - gecko_ffi_name="mBoxFlex", - animation_value_type="NonNegativeNumber", - aliases="-webkit-box-flex", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-flex)", -)} - -${helpers.single_keyword( - "-moz-box-orient", - "horizontal vertical", - engines="gecko", - gecko_ffi_name="mBoxOrient", - gecko_aliases="inline-axis=horizontal block-axis=vertical", - gecko_enum_prefix="StyleBoxOrient", - animation_value_type="discrete", - aliases="-webkit-box-orient", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-orient)", -)} - -${helpers.single_keyword( - "-moz-box-pack", - "start center end justify", - engines="gecko", - gecko_ffi_name="mBoxPack", - gecko_enum_prefix="StyleBoxPack", - animation_value_type="discrete", - aliases="-webkit-box-pack", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/box-pack)", -)} - -// NOTE(heycam): Odd that the initial value is 1 yet 0 is a valid value. There -// are uses of `-moz-box-ordinal-group: 0` in the tree, too. -${helpers.predefined_type( - "-moz-box-ordinal-group", - "Integer", - "1", - engines="gecko", - parse_method="parse_non_negative", - aliases="-webkit-box-ordinal-group", - gecko_ffi_name="mBoxOrdinal", - animation_value_type="discrete", - spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-box-ordinal-group)", -)} diff --git a/components/style/properties/mod.rs b/components/style/properties/mod.rs deleted file mode 100644 index 9eb567ca6f6..00000000000 --- a/components/style/properties/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! Supported CSS properties and the cascade. - -pub mod cascade; -pub mod declaration_block; - -/// The CSS properties supported by the style system. -/// Generated from the properties.mako.rs template by build.rs -#[macro_use] -#[allow(unsafe_code)] -#[deny(missing_docs)] -pub mod generated { - include!(concat!(env!("OUT_DIR"), "/properties.rs")); - - #[cfg(feature = "gecko")] - #[allow(unsafe_code, missing_docs)] - pub mod gecko { - include!(concat!(env!("OUT_DIR"), "/gecko_properties.rs")); - } -} - -pub use self::cascade::*; -pub use self::declaration_block::*; -pub use self::generated::*; diff --git a/components/style/properties/properties.html.mako b/components/style/properties/properties.html.mako deleted file mode 100644 index 5c515935175..00000000000 --- a/components/style/properties/properties.html.mako +++ /dev/null @@ -1,31 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Supported CSS properties in Servo</title> - <link rel="stylesheet" type="text/css" href="../normalize.css"> - <link rel="stylesheet" type="text/css" href="../rustdoc.css"> - <link rel="stylesheet" type="text/css" href="../light.css"> -</head> -<body class="rustdoc"> - <section id='main' class="content mod"> - <h1 class='fqn'><span class='in-band'>CSS properties currently supported in Servo</span></h1> - % for kind, props in sorted(properties.items()): - <h2>${kind.capitalize()}</h2> - <table> - <tr> - <th>Name</th> - <th>Pref</th> - </tr> - % for name, data in sorted(props.items()): - <tr> - <td><code>${name}</code></td> - <td><code>${data['pref'] or ''}</code></td> - </tr> - % endfor - </table> - % endfor - </section> -</body> -</html> diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs deleted file mode 100644 index 4c33f9d7458..00000000000 --- a/components/style/properties/properties.mako.rs +++ /dev/null @@ -1,4277 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -// This file is a Mako template: http://www.makotemplates.org/ - -// Please note that valid Rust syntax may be mangled by the Mako parser. -// For example, Vec<&Foo> will be mangled as Vec&Foo>. To work around these issues, the code -// can be escaped. In the above example, Vec<<&Foo> or Vec< &Foo> achieves the desired result of Vec<&Foo>. - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -use app_units::Au; -use 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, 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 fxhash::FxHashMap; -use crate::media_queries::Device; -use crate::parser::ParserContext; -use crate::selector_parser::PseudoElement; -use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode}; -use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; -use to_shmem::impl_trivial_to_shmem; -use crate::stylesheets::{CssRuleType, CssRuleTypes, Origin, UrlExtraData}; -use crate::use_counters::UseCounters; -use crate::values::generics::text::LineHeight; -use crate::values::{computed, resolved, serialize_atom_name}; -use crate::values::specified::font::SystemFont; -use crate::rule_tree::StrongRuleNode; -use crate::str::{CssString, CssStringWriter}; -use std::cell::Cell; -use super::declaration_block::AppendableValue; - -<%! - from collections import defaultdict - from data import Method, PropertyRestrictions, Keyword, to_rust_ident, \ - to_camel_case, RULE_VALUES, SYSTEM_FONT_LONGHANDS - import os.path -%> - -/// Conversion with fewer impls than From/Into -pub trait MaybeBoxed<Out> { - /// Convert - fn maybe_boxed(self) -> Out; -} - -impl<T> MaybeBoxed<T> for T { - #[inline] - fn maybe_boxed(self) -> T { self } -} - -impl<T> MaybeBoxed<Box<T>> for T { - #[inline] - fn maybe_boxed(self) -> Box<T> { Box::new(self) } -} - -macro_rules! expanded { - ( $( $name: ident: $value: expr ),+ ) => { - expanded!( $( $name: $value, )+ ) - }; - ( $( $name: ident: $value: expr, )+ ) => { - Longhands { - $( - $name: MaybeBoxed::maybe_boxed($value), - )+ - } - } -} - -/// A module with all the code for longhand properties. -#[allow(missing_docs)] -pub mod longhands { - % for style_struct in data.style_structs: - include!("${repr(os.path.join(OUT_DIR, 'longhands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); - % endfor - 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; - - % for style_struct in data.style_structs: - include!("${repr(os.path.join(OUT_DIR, 'shorthands/{}.rs'.format(style_struct.name_lower)))[1:-1]}"); - % endfor - - // We didn't define the 'all' shorthand using the regular helpers:shorthand - // mechanism, since it causes some very large types to be generated. - // - // Also, make sure logical properties appear before its physical - // counter-parts, in order to prevent bugs like: - // - // https://bugzilla.mozilla.org/show_bug.cgi?id=1410028 - // - // FIXME(emilio): Adopt the resolution from: - // - // https://github.com/w3c/csswg-drafts/issues/1898 - // - // when there is one, whatever that is. - <% - logical_longhands = [] - other_longhands = [] - - for p in data.longhands: - if p.name in ['direction', 'unicode-bidi']: - continue; - if not p.enabled_in_content() and not p.experimental(engine): - continue; - if "Style" not in p.rule_types_allowed_names(): - continue; - if p.logical: - logical_longhands.append(p.name) - else: - other_longhands.append(p.name) - - data.declare_shorthand( - "all", - logical_longhands + other_longhands, - engines="gecko servo", - 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_variants = [ - { - "name": "CSSWideKeyword", - "type": "WideKeywordDeclaration", - "doc": "A CSS-wide keyword.", - "copy": False, - }, - { - "name": "WithVariables", - "type": "VariableDeclaration", - "doc": "An unparsed declaration.", - "copy": False, - }, - { - "name": "Custom", - "type": "CustomDeclaration", - "doc": "A custom property declaration.", - "copy": False, - }, - ] - for v in extra_variants: - variants.append(v) - groups[v["type"]] = [v] -%> - -/// Servo's representation for a property declaration. -#[derive(ToShmem)] -#[repr(u16)] -pub enum PropertyDeclaration { - % for variant in variants: - /// ${variant["doc"]} - ${variant["name"]}(${variant["type"]}), - % endfor -} - -// There's one of these for each parsed declaration so it better be small. -size_of_test!(PropertyDeclaration, 32); - -#[repr(C)] -struct PropertyDeclarationVariantRepr<T> { - tag: u16, - value: T -} - -impl Clone for PropertyDeclaration { - #[inline] - fn clone(&self) -> Self { - use self::PropertyDeclaration::*; - - <% - [copy, others] = [list(g) for _, g in groupby(variants, key=lambda x: not x["copy"])] - %> - - let self_tag = unsafe { - (*(self as *const _ as *const PropertyDeclarationVariantRepr<()>)).tag - }; - if self_tag <= LonghandId::${copy[-1]["name"]} as u16 { - #[derive(Clone, Copy)] - #[repr(u16)] - enum CopyVariants { - % for v in copy: - _${v["name"]}(${v["type"]}), - % endfor - } - - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut CopyVariants, - *(self as *const _ as *const CopyVariants), - ); - return out.assume_init(); - } - } - - // This function ensures that all properties not handled above - // do not have a specified value implements Copy. If you hit - // compile error here, you may want to add the type name into - // Longhand.specified_is_copy in data.py. - fn _static_assert_others_are_not_copy() { - struct Helper<T>(T); - trait AssertCopy { fn assert() {} } - trait AssertNotCopy { fn assert() {} } - impl<T: Copy> AssertCopy for Helper<T> {} - % for ty in sorted(set(x["type"] for x in others)): - impl AssertNotCopy for Helper<${ty}> {} - Helper::<${ty}>::assert(); - % endfor - } - - match *self { - ${" |\n".join("{}(..)".format(v["name"]) for v in copy)} => { - unsafe { debug_unreachable!() } - } - % for ty, vs in groupby(others, key=lambda x: x["type"]): - <% - vs = list(vs) - %> - % if len(vs) == 1: - ${vs[0]["name"]}(ref value) => { - ${vs[0]["name"]}(value.clone()) - } - % else: - ${" |\n".join("{}(ref value)".format(v["name"]) for v in vs)} => { - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${ty}>, - PropertyDeclarationVariantRepr { - tag: *(self as *const _ as *const u16), - value: value.clone(), - }, - ); - out.assume_init() - } - } - % endif - % endfor - } - } -} - -impl PartialEq for PropertyDeclaration { - #[inline] - fn eq(&self, other: &Self) -> bool { - use self::PropertyDeclaration::*; - - unsafe { - let this_repr = - &*(self as *const _ as *const PropertyDeclarationVariantRepr<()>); - let other_repr = - &*(other as *const _ as *const PropertyDeclarationVariantRepr<()>); - if this_repr.tag != other_repr.tag { - return false; - } - match *self { - % for ty, vs in groupby(variants, key=lambda x: x["type"]): - ${" |\n".join("{}(ref this)".format(v["name"]) for v in vs)} => { - let other_repr = - &*(other as *const _ as *const PropertyDeclarationVariantRepr<${ty}>); - *this == other_repr.value - } - % endfor - } - } - } -} - -impl MallocSizeOf for PropertyDeclaration { - #[inline] - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - use self::PropertyDeclaration::*; - - match *self { - % for ty, vs in groupby(variants, key=lambda x: x["type"]): - ${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => { - value.size_of(ops) - } - % endfor - } - } -} - - -impl PropertyDeclaration { - /// Returns whether this is a variant of the Longhand(Value) type, rather - /// than one of the special variants in extra_variants. - fn is_longhand_value(&self) -> bool { - match *self { - % for v in extra_variants: - PropertyDeclaration::${v["name"]}(..) => false, - % endfor - _ => true, - } - } - - /// Like the method on ToCss, but without the type parameter to avoid - /// accidentally monomorphizing this large function multiple times for - /// different writers. - pub fn to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { - use self::PropertyDeclaration::*; - - let mut dest = CssWriter::new(dest); - match *self { - % for ty, vs in groupby(variants, key=lambda x: x["type"]): - ${" | ".join("{}(ref value)".format(v["name"]) for v in vs)} => { - value.to_css(&mut dest) - } - % endfor - } - } - - /// Returns the color value of a given property, for high-contrast-mode - /// tweaks. - pub(super) fn color_value(&self) -> Option<<&crate::values::specified::Color> { - ${static_longhand_id_set("COLOR_PROPERTIES", lambda p: p.predefined_type == "Color")} - <% - # sanity check - assert data.longhands_by_name["background-color"].predefined_type == "Color" - - color_specified_type = data.longhands_by_name["background-color"].specified_type() - %> - let id = self.id().as_longhand()?; - if !COLOR_PROPERTIES.contains(id) || !self.is_longhand_value() { - return None; - } - let repr = self as *const _ as *const PropertyDeclarationVariantRepr<${color_specified_type}>; - Some(unsafe { &(*repr).value }) - } -} - -/// A module with all the code related to animated properties. -/// -/// This needs to be "included" by mako at least after all longhand modules, -/// given they populate the global data. -pub mod animated_properties { - <%include file="/helpers/animated_properties.mako.rs" /> -} - -/// A 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 - } - - /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. - #[cfg(feature = "gecko")] - #[inline] - pub 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(): - <% - pref = getattr(property, "servo_pref") - %> - % if pref: - Some("${pref}"), - % else: - None, - % endif - % endfor - ]; - let pref = match PREF_NAME[self.0] { - None => return true, - Some(pref) => pref, - }; - - style_config::get_bool(pref) - % endif - }; - - if ALWAYS_ENABLED.contains(self) { - return true - } - - if EXPERIMENTAL.contains(self) && passes_pref_check() { - return true - } - - false - } - - /// Returns whether a given rule allows a given property. - #[inline] - pub fn allowed_in_rule(self, rule_types: CssRuleTypes) -> bool { - debug_assert!( - rule_types.contains(CssRuleType::Keyframe) || - rule_types.contains(CssRuleType::Page) || - rule_types.contains(CssRuleType::Style), - "Declarations are only expected inside a keyframe, page, or style rule." - ); - - static MAP: [u32; NON_CUSTOM_PROPERTY_ID_COUNT] = [ - % for property in data.longhands + data.shorthands + data.all_aliases(): - % for name in RULE_VALUES: - % if property.rule_types_allowed & RULE_VALUES[name] != 0: - CssRuleType::${name}.bit() | - % endif - % endfor - 0, - % endfor - ]; - MAP[self.0] & rule_types.bits() != 0 - } - - fn allowed_in(self, context: &ParserContext) -> bool { - if !self.allowed_in_rule(context.rule_types()) { - 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, Default)] -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)] -#[repr(u8)] -pub enum LogicalGroup { - % for i, group in enumerate(logical_groups.keys()): - /// ${group} - ${to_camel_case(group)} = ${i}, - % endfor -} - - -/// A set of logical groups. -#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)] -pub struct LogicalGroupSet { - storage: [u32; (${len(logical_groups)} - 1 + 32) / 32] -} - -impl LogicalGroupSet { - /// Creates an empty `NonCustomPropertyIdSet`. - pub fn new() -> Self { - Self { - storage: Default::default(), - } - } - - /// Return whether the given group is in the set - #[inline] - pub fn contains(&self, g: LogicalGroup) -> bool { - let bit = g as usize; - (self.storage[bit / 32] & (1 << (bit % 32))) != 0 - } - - /// Insert a group the set. - #[inline] - pub fn insert(&mut self, g: LogicalGroup) { - let bit = g as usize; - self.storage[bit / 32] |= 1 << (bit % 32); - } -} - -/// 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); - } - } - } -} - -<% - -CASCADE_GROUPS = { - # The writing-mode group has the most priority of all property groups, as - # sizes like font-size can depend on it. - "writing_mode": [ - "writing-mode", - "direction", - "text-orientation", - ], - # The fonts and colors group has the second priority, as all other lengths - # and colors depend on them. - # - # There are some interdependencies between these, but we fix them up in - # Cascade::fixup_font_stuff. - "fonts_and_color": [ - # Needed to properly compute the zoomed font-size. - "-x-text-scale", - # Needed to do font-size computation in a language-dependent way. - "-x-lang", - # Needed for ruby to respect language-dependent min-font-size - # preferences properly, see bug 1165538. - "-moz-min-font-size-ratio", - # font-size depends on math-depth's computed value. - "math-depth", - # Needed to compute the first available font and its used size, - # in order to compute font-relative units correctly. - "font-size", - "font-size-adjust", - "font-weight", - "font-stretch", - "font-style", - "font-family", - # color-scheme affects how system colors resolve. - "color-scheme", - "forced-color-adjust", - ], -} -def in_late_group(p): - return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"] - -def is_visited_dependent(p): - return p.name in [ - "column-rule-color", - "text-emphasis-color", - "-webkit-text-fill-color", - "-webkit-text-stroke-color", - "text-decoration-color", - "fill", - "stroke", - "caret-color", - "background-color", - "border-top-color", - "border-right-color", - "border-bottom-color", - "border-left-color", - "border-block-start-color", - "border-inline-end-color", - "border-block-end-color", - "border-inline-start-color", - "outline-color", - "color", - ] - -%> - -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 transitionable() -> &'static Self { - ${static_longhand_id_set("TRANSITIONABLE", lambda p: p.transitionable)} - &TRANSITIONABLE - } - - #[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] - fn ignored_when_colors_disabled() -> &'static Self { - ${static_longhand_id_set( - "IGNORED_WHEN_COLORS_DISABLED", - lambda p: p.ignored_when_colors_disabled - )} - &IGNORED_WHEN_COLORS_DISABLED - } - - /// Only a few properties are allowed to depend on the visited state of - /// links. When cascading visited styles, we can save time by only - /// processing these properties. - pub(super) fn visited_dependent() -> &'static Self { - ${static_longhand_id_set( - "VISITED_DEPENDENT", - lambda p: is_visited_dependent(p) - )} - debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT)); - &VISITED_DEPENDENT - } - - #[inline] - pub(super) fn writing_mode_group() -> &'static Self { - ${static_longhand_id_set( - "WRITING_MODE_GROUP", - lambda p: p.name in CASCADE_GROUPS["writing_mode"] - )} - &WRITING_MODE_GROUP - } - - #[inline] - pub(super) fn fonts_and_color_group() -> &'static Self { - ${static_longhand_id_set( - "FONTS_AND_COLOR_GROUP", - lambda p: p.name in CASCADE_GROUPS["fonts_and_color"] - )} - &FONTS_AND_COLOR_GROUP - } - - #[inline] - pub(super) fn late_group_only_inherited() -> &'static Self { - ${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))} - &LATE_GROUP_ONLY_INHERITED - } - - #[inline] - pub(super) fn late_group() -> &'static Self { - ${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))} - &LATE_GROUP - } - - /// Returns the set of properties that are declared as having no effect on - /// Gecko <scrollbar> elements or their descendant scrollbar parts. - #[cfg(debug_assertions)] - #[cfg(feature = "gecko")] - #[inline] - pub fn has_no_effect_on_gecko_scrollbars() -> &'static Self { - // data.py asserts that has_no_effect_on_gecko_scrollbars is True or - // False for properties that are inherited and Gecko pref controlled, - // and is None for all other properties. - ${static_longhand_id_set( - "HAS_NO_EFFECT_ON_SCROLLBARS", - lambda p: p.has_effect_on_gecko_scrollbars is False - )} - &HAS_NO_EFFECT_ON_SCROLLBARS - } - - /// Returns the set of border properties for the purpose of disabling native - /// appearance. - #[inline] - pub fn border_background_properties() -> &'static Self { - ${static_longhand_id_set( - "BORDER_BACKGROUND_PROPERTIES", - lambda p: (p.logical_group and p.logical_group.startswith("border")) or \ - p.name in ["background-color", "background-image"] - )} - &BORDER_BACKGROUND_PROPERTIES - } - - /// 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, - /// The `revert-layer` keyword. - RevertLayer, -} - -impl CSSWideKeyword { - fn to_str(&self) -> &'static str { - match *self { - CSSWideKeyword::Initial => "initial", - CSSWideKeyword::Inherit => "inherit", - CSSWideKeyword::Unset => "unset", - CSSWideKeyword::Revert => "revert", - CSSWideKeyword::RevertLayer => "revert-layer", - } - } -} - -impl CSSWideKeyword { - /// Parses a CSS wide keyword from a CSS identifier. - pub fn from_ident(ident: &str) -> Result<Self, ()> { - Ok(match_ignore_ascii_case! { ident, - "initial" => CSSWideKeyword::Initial, - "inherit" => CSSWideKeyword::Inherit, - "unset" => CSSWideKeyword::Unset, - "revert" => CSSWideKeyword::Revert, - "revert-layer" => CSSWideKeyword::RevertLayer, - _ => return Err(()), - }) - } - - fn parse(input: &mut Parser) -> Result<Self, ()> { - let keyword = { - let ident = input.expect_ident().map_err(|_| ())?; - Self::from_ident(ident)? - }; - input.expect_exhausted().map_err(|_| ())?; - Ok(keyword) - } -} - -bitflags! { - /// A set of flags for properties. - pub struct PropertyFlags: u16 { - /// This longhand property applies to ::first-letter. - const APPLIES_TO_FIRST_LETTER = 1 << 1; - /// This longhand property applies to ::first-line. - const APPLIES_TO_FIRST_LINE = 1 << 2; - /// This longhand property applies to ::placeholder. - const APPLIES_TO_PLACEHOLDER = 1 << 3; - /// This longhand property applies to ::cue. - const APPLIES_TO_CUE = 1 << 4; - /// This longhand property applies to ::marker. - const APPLIES_TO_MARKER = 1 << 5; - /// This property is a legacy shorthand. - /// - /// https://drafts.csswg.org/css-cascade/#legacy-shorthand - const IS_LEGACY_SHORTHAND = 1 << 6; - - /* The following flags are currently not used in Rust code, they - * only need to be listed in corresponding properties so that - * they can be checked in the C++ side via ServoCSSPropList.h. */ - /// This property can be animated on the compositor. - const CAN_ANIMATE_ON_COMPOSITOR = 0; - /// This shorthand property is accessible from getComputedStyle. - const SHORTHAND_IN_GETCS = 0; - } -} - -/// 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) - } - - /// Returns an iterator over all the shorthands that include this longhand. - pub fn shorthands(&self) -> NonCustomPropertyIterator<ShorthandId> { - // first generate longhand to shorthands lookup map - // - // NOTE(emilio): This currently doesn't exclude the "all" shorthand. It - // could potentially do so, which would speed up serialization - // algorithms and what not, I guess. - <% - from functools import cmp_to_key - longhand_to_shorthand_map = {} - num_sub_properties = {} - for shorthand in data.shorthands: - num_sub_properties[shorthand.camel_case] = len(shorthand.sub_properties) - for sub_property in shorthand.sub_properties: - if sub_property.ident not in longhand_to_shorthand_map: - longhand_to_shorthand_map[sub_property.ident] = [] - - longhand_to_shorthand_map[sub_property.ident].append(shorthand.camel_case) - - def cmp(a, b): - return (a > b) - (a < b) - - def preferred_order(x, y): - # Since we want properties in order from most subproperties to least, - # reverse the arguments to cmp from the expected order. - result = cmp(num_sub_properties.get(y, 0), num_sub_properties.get(x, 0)) - if result: - return result - # Fall back to lexicographic comparison. - return cmp(x, y) - - # Sort the lists of shorthand properties according to preferred order: - # https://drafts.csswg.org/cssom/#concept-shorthands-preferred-order - for shorthand_list in longhand_to_shorthand_map.values(): - shorthand_list.sort(key=cmp_to_key(preferred_order)) - %> - - // based on lookup results for each longhand, create result arrays - % 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(), - } - } - - fn parse_value<'i, 't>( - &self, - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<PropertyDeclaration, ParseError<'i>> { - type ParsePropertyFn = for<'i, 't> fn( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<PropertyDeclaration, ParseError<'i>>; - static PARSE_PROPERTY: [ParsePropertyFn; ${len(data.longhands)}] = [ - % for property in data.longhands: - longhands::${property.ident}::parse_declared, - % endfor - ]; - (PARSE_PROPERTY[*self as usize])(context, input) - } - - /// 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) - } - - /// Returns whether this property is transitionable. - #[inline] - pub fn is_transitionable(self) -> bool { - LonghandIdSet::transitionable().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]) - } - - /// Returns true if the property is one that is ignored when document - /// colors are disabled. - #[inline] - pub fn ignored_when_document_colors_disabled(self) -> bool { - LonghandIdSet::ignored_when_colors_disabled().contains(self) - } -} - -/// 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( - &self, - declarations: &[&PropertyDeclaration], - dest: &mut CssStringWriter, - ) -> fmt::Result { - type LonghandsToCssFn = for<'a, 'b> fn(&'a [&'b PropertyDeclaration], &mut CssStringWriter) -> fmt::Result; - fn all_to_css(_: &[&PropertyDeclaration], _: &mut CssStringWriter) -> fmt::Result { - // No need to try to serialize the declarations as the 'all' - // shorthand, since it only accepts CSS-wide keywords (and variable - // references), which will be handled in - // get_shorthand_appendable_value. - Ok(()) - } - - static LONGHANDS_TO_CSS: [LonghandsToCssFn; ${len(data.shorthands)}] = [ - % for shorthand in data.shorthands: - % if shorthand.ident == "all": - all_to_css, - % else: - shorthands::${shorthand.ident}::to_css, - % endif - % endfor - ]; - - LONGHANDS_TO_CSS[*self as usize](declarations, dest) - } - - /// Finds and returns an appendable value for the given declarations. - /// - /// Returns the optional appendable value. - pub fn get_shorthand_appendable_value<'a, 'b: 'a>( - self, - declarations: &'a [&'b PropertyDeclaration], - ) -> Option<AppendableValue<'a, 'b>> { - let first_declaration = declarations.get(0)?; - let rest = || declarations.iter().skip(1); - - // https://drafts.csswg.org/css-variables/#variables-in-shorthands - if let Some(css) = first_declaration.with_variables_from_shorthand(self) { - if rest().all(|d| d.with_variables_from_shorthand(self) == Some(css)) { - return Some(AppendableValue::Css(css)); - } - return None; - } - - // Check whether they are all the same CSS-wide keyword. - if let Some(keyword) = first_declaration.get_css_wide_keyword() { - if rest().all(|d| d.get_css_wide_keyword() == Some(keyword)) { - return Some(AppendableValue::Css(keyword.to_str())) - } - return None; - } - - if self == ShorthandId::All { - // 'all' only supports variables and CSS wide keywords. - return None; - } - - // Check whether all declarations can be serialized as part of shorthand. - if declarations.iter().all(|d| d.may_serialize_as_part_of_shorthand()) { - return Some(AppendableValue::DeclarationsForShorthand(self, declarations)); - } - - None - } - - /// Returns 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>> { - type ParseIntoFn = for<'i, 't> fn( - declarations: &mut SourcePropertyDeclaration, - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>>; - - fn parse_all<'i, 't>( - _: &mut SourcePropertyDeclaration, - _: &ParserContext, - input: &mut Parser<'i, 't> - ) -> Result<(), ParseError<'i>> { - // 'all' accepts no value other than CSS-wide keywords - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - - static PARSE_INTO: [ParseIntoFn; ${len(data.shorthands)}] = [ - % for shorthand in data.shorthands: - % if shorthand.ident == "all": - parse_all, - % else: - shorthands::${shorthand.ident}::parse_into, - % endif - % endfor - ]; - - (PARSE_INTO[*self as usize])(declarations, context, input) - } -} - -/// 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(()) - } -} - -/// A simple cache for properties that come from a shorthand and have variable -/// references. -/// -/// This cache works because of the fact that you can't have competing values -/// for a given longhand coming from the same shorthand (but note that this is -/// why the shorthand needs to be part of the cache key). -pub type ShorthandsWithPropertyReferencesCache = - FxHashMap<(ShorthandId, LonghandId), PropertyDeclaration>; - -impl UnparsedValue { - pub(super) fn substitute_variables<'cache>( - &self, - longhand_id: LonghandId, - writing_mode: WritingMode, - custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>, - quirks_mode: QuirksMode, - device: &Device, - shorthand_cache: &'cache mut ShorthandsWithPropertyReferencesCache, - ) -> Cow<'cache, PropertyDeclaration> { - let invalid_at_computed_value_time = || { - let keyword = if longhand_id.inherited() { - CSSWideKeyword::Inherit - } else { - CSSWideKeyword::Initial - }; - Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)) - }; - - if let Some(shorthand_id) = self.from_shorthand { - let key = (shorthand_id, longhand_id); - if shorthand_cache.contains_key(&key) { - // FIXME: This double lookup should be avoidable, but rustc - // doesn't like that, see: - // - // https://github.com/rust-lang/rust/issues/82146 - return Cow::Borrowed(&shorthand_cache[&key]); - } - } - - let css = match 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, - /* namespaces = */ Default::default(), - None, - None, - ); - - let mut input = ParserInput::new(&css); - let mut input = Parser::new(&mut input); - input.skip_whitespace(); - - if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) { - return Cow::Owned(PropertyDeclaration::css_wide_keyword(longhand_id, keyword)); - } - - let shorthand = match self.from_shorthand { - None => { - return match input.parse_entirely(|input| longhand_id.parse_value(&context, input)) { - Ok(decl) => Cow::Owned(decl), - Err(..) => invalid_at_computed_value_time(), - } - }, - Some(shorthand) => shorthand, - }; - - let mut decls = SourcePropertyDeclaration::default(); - // parse_into takes care of doing `parse_entirely` for us. - if shorthand.parse_into(&mut decls, &context, &mut input).is_err() { - return invalid_at_computed_value_time(); - } - - for declaration in decls.declarations.drain(..) { - let longhand = declaration.id().as_longhand().unwrap(); - if longhand.is_logical() { - shorthand_cache.insert((shorthand, longhand.to_physical(writing_mode)), declaration.clone()); - } - shorthand_cache.insert((shorthand, longhand), declaration); - } - - let key = (shorthand, longhand_id); - match shorthand_cache.get(&key) { - Some(decl) => Cow::Borrowed(decl), - None => { - // FIXME: We should always have the key here but it seems - // sometimes we don't, see bug 1696409. - #[cfg(feature = "gecko")] - { - if structs::GECKO_IS_NIGHTLY { - panic!("Expected {:?} to be in the cache but it was not!", key); - } - } - 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.aliases: - "${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, - /// The CSS-wide keyword. - pub keyword: CSSWideKeyword, -} - -/// An unparsed declaration that contains `var()` functions. -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, PartialEq, ToCss, ToShmem)] -pub struct VariableDeclaration { - /// The id of the property this declaration represents. - #[css(skip)] - pub id: LonghandId, - /// The unparsed value of the variable. - #[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")] - pub value: Arc<UnparsedValue>, -} - -/// A custom property declaration value is either an unparsed value or a CSS -/// wide-keyword. -#[derive(Clone, PartialEq, ToCss, ToShmem)] -pub enum CustomDeclarationValue { - /// A value. - Value(Arc<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(); - input.skip_whitespace(); - - let start = input.state(); - match id { - PropertyId::Custom(property_name) => { - let value = match input.try_parse(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.try_parse(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, - url_data: context.url_data.clone(), - from_shorthand: None, - }), - })) - }) - }).map(|declaration| { - declarations.push(declaration) - })?; - } - PropertyId::ShorthandAlias(id, _) | - PropertyId::Shorthand(id) => { - if let Ok(keyword) = input.try_parse(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, - 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(()) - } -} - -const SUB_PROPERTIES_ARRAY_CAP: usize = - ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ - if data.shorthands_except_all() else 0}; - -/// An ArrayVec of subproperties, contains space for the longest shorthand except all. -pub type SubpropertiesVec<T> = ArrayVec<T, SUB_PROPERTIES_ARRAY_CAP>; - -/// A stack-allocated vector of `PropertyDeclaration` -/// large enough to parse one CSS `key: value` declaration. -/// (Shorthands expand to multiple `PropertyDeclaration`s.) -#[derive(Default)] -pub struct SourcePropertyDeclaration { - /// The storage for the actual declarations (except for all). - pub declarations: SubpropertiesVec<PropertyDeclaration>, - /// Stored separately to keep SubpropertiesVec smaller. - pub all_shorthand: AllShorthand, -} - -// This is huge, but we allocate it on the stack and then never move it, -// we only pass `&mut SourcePropertyDeclaration` references around. -size_of_test!(SourcePropertyDeclaration, 568); - -impl SourcePropertyDeclaration { - /// Create one with a single PropertyDeclaration. - #[inline] - pub fn with_one(decl: PropertyDeclaration) -> Self { - let mut result = Self::default(); - result.declarations.push(decl); - result - } - - /// Similar to Vec::drain: leaves this empty when the return value is dropped. - pub fn drain(&mut self) -> SourcePropertyDeclarationDrain { - SourcePropertyDeclarationDrain { - declarations: self.declarations.drain(..), - all_shorthand: mem::replace(&mut self.all_shorthand, AllShorthand::NotSet), - } - } - - /// Reset to initial state - pub fn clear(&mut self) { - self.declarations.clear(); - self.all_shorthand = AllShorthand::NotSet; - } - - 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> { - /// A drain over the non-all declarations. - pub declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>, - /// The all shorthand that was set. - pub all_shorthand: AllShorthand, -} - -/// A parsed all-shorthand value. -pub enum AllShorthand { - /// Not present. - NotSet, - /// A CSS-wide keyword. - CSSWideKeyword(CSSWideKeyword), - /// An all shorthand with var() references that we can't resolve right now. - WithVariables(Arc<UnparsedValue>) -} - -impl Default for AllShorthand { - fn default() -> Self { - Self::NotSet - } -} - -impl AllShorthand { - /// Iterates property declarations from the given all shorthand value. - #[inline] - pub fn declarations(&self) -> AllShorthandDeclarationIterator { - AllShorthandDeclarationIterator { - all_shorthand: self, - longhands: ShorthandId::All.longhands(), - } - } -} - -/// An iterator over the all shorthand's shorthand declarations. -pub struct AllShorthandDeclarationIterator<'a> { - all_shorthand: &'a AllShorthand, - longhands: NonCustomPropertyIterator<LonghandId>, -} - -impl<'a> Iterator for AllShorthandDeclarationIterator<'a> { - type Item = PropertyDeclaration; - - #[inline] - fn next(&mut self) -> Option<Self::Item> { - match *self.all_shorthand { - AllShorthand::NotSet => None, - AllShorthand::CSSWideKeyword(ref keyword) => { - Some(PropertyDeclaration::css_wide_keyword(self.longhands.next()?, *keyword)) - } - AllShorthand::WithVariables(ref unparsed) => { - Some(PropertyDeclaration::WithVariables( - VariableDeclaration { - id: self.longhands.next()?, - value: unparsed.clone() - } - )) - } - } - } -} - -#[cfg(feature = "gecko")] -pub use super::gecko::style_structs; - -/// The module where all the style structs are defined. -#[cfg(feature = "servo")] -pub mod style_structs { - use fxhash::FxHasher; - use super::longhands; - use std::hash::{Hash, Hasher}; - use crate::logical_geometry::WritingMode; - use crate::media_queries::Device; - use crate::values::computed::NonNegativeLength; - - % for style_struct in data.active_style_structs(): - % if style_struct.name == "Font": - #[derive(Clone, Debug, MallocSizeOf)] - #[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] - % else: - #[derive(Clone, Debug, MallocSizeOf, PartialEq)] - % endif - /// The ${style_struct.name} style struct. - pub struct ${style_struct.name} { - % for longhand in style_struct.longhands: - % if not longhand.logical: - /// The ${longhand.name} computed value. - pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T, - % endif - % endfor - % if style_struct.name == "InheritedText": - /// The "used" text-decorations that apply to this box. - /// - /// FIXME(emilio): This is technically a box-tree concept, and - /// would be nice to move away from style. - pub text_decorations_in_effect: crate::values::computed::text::TextDecorationsInEffect, - % endif - % if style_struct.name == "Font": - /// The font hash, used for font caching. - pub hash: u64, - % endif - % if style_struct.name == "Box": - /// The display value specified by the CSS stylesheets (without any style adjustments), - /// which is needed for hypothetical layout boxes. - pub original_display: longhands::display::computed_value::T, - % endif - } - % if style_struct.name == "Font": - impl PartialEq for Font { - fn eq(&self, other: &Font) -> bool { - self.hash == other.hash - % for longhand in style_struct.longhands: - && self.${longhand.ident} == other.${longhand.ident} - % endfor - } - } - % endif - - impl ${style_struct.name} { - % for longhand in style_struct.longhands: - % if 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 == "UI": - /// Returns whether there is any animation specified with - /// animation-name other than `none`. - pub fn specifies_animations(&self) -> bool { - self.animation_name_iter().any(|name| !name.is_none()) - } - - /// Returns whether there are any transitions specified. - #[cfg(feature = "servo")] - pub fn specifies_transitions(&self) -> bool { - (0..self.transition_property_count()).any(|index| { - let combined_duration = - self.transition_duration_mod(index).seconds().max(0.) + - self.transition_delay_mod(index).seconds(); - combined_duration > 0. - }) - } - - /// Returns whether there is any named progress timeline specified with - /// scroll-timeline-name other than `none`. - #[cfg(feature = "gecko")] - pub fn specifies_scroll_timelines(&self) -> bool { - self.scroll_timeline_name_iter().any(|name| !name.is_none()) - } - - /// Returns whether there is any named progress timeline specified with - /// view-timeline-name other than `none`. - #[cfg(feature = "gecko")] - pub fn specifies_view_timelines(&self) -> bool { - self.view_timeline_name_iter().any(|name| !name.is_none()) - } - - /// Returns true if animation properties are equal between styles, but without - /// considering keyframe data and animation-timeline. - #[cfg(feature = "servo")] - pub fn animations_equals(&self, other: &Self) -> bool { - self.animation_name_iter().eq(other.animation_name_iter()) && - self.animation_delay_iter().eq(other.animation_delay_iter()) && - self.animation_direction_iter().eq(other.animation_direction_iter()) && - self.animation_duration_iter().eq(other.animation_duration_iter()) && - self.animation_fill_mode_iter().eq(other.animation_fill_mode_iter()) && - self.animation_iteration_count_iter().eq(other.animation_iteration_count_iter()) && - self.animation_play_state_iter().eq(other.animation_play_state_iter()) && - self.animation_timing_function_iter().eq(other.animation_timing_function_iter()) - } - - % elif style_struct.name == "Column": - /// Whether this is a multicol style. - #[cfg(feature = "servo")] - pub fn is_multicol(&self) -> bool { - !self.column_width.is_auto() || !self.column_count.is_auto() - } - % endif - } - - % for longhand in style_struct.longhands: - % if longhand.need_index: - /// An iterator over the values of the ${longhand.name} properties. - pub struct ${longhand.camel_case}Iter<'a> { - style_struct: &'a style_structs::${style_struct.name}, - current: usize, - max: usize, - } - - impl<'a> Iterator for ${longhand.camel_case}Iter<'a> { - type Item = longhands::${longhand.ident}::computed_value::SingleComputedValue; - - fn next(&mut self) -> Option<Self::Item> { - self.current += 1; - if self.current <= self.max { - Some(self.style_struct.${longhand.ident}_at(self.current - 1)) - } else { - None - } - } - } - % endif - % endfor -% endfor - - -#[cfg(feature = "gecko")] -pub use super::gecko::{ComputedValues, ComputedValuesInner}; - -#[cfg(feature = "servo")] -#[cfg_attr(feature = "servo", derive(Clone, Debug))] -/// Actual data of ComputedValues, to match up with Gecko -pub struct ComputedValuesInner { - % for style_struct in data.active_style_structs(): - ${style_struct.ident}: Arc<style_structs::${style_struct.name}>, - % endfor - custom_properties: 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.clone_display().is_contents() - } - - /// Gets a reference to the rule node. Panic if no rule node exists. - pub fn rules(&self) -> &StrongRuleNode { - self.rules.as_ref().unwrap() - } - - /// Returns the visited rules, if applicable. - pub fn visited_rules(&self) -> Option<<&StrongRuleNode> { - self.visited_style().and_then(|s| s.rules.as_ref()) - } - - /// Gets a reference to the custom properties map (if one exists). - pub fn custom_properties(&self) -> Option<<&Arc<crate::custom_properties::CustomPropertiesMap>> { - self.custom_properties.as_ref() - } - - /// Returns whether we have the same custom properties as another style. - /// - /// This should effectively be just: - /// - /// self.custom_properties() == other.custom_properties() - /// - /// But that's not really the case because IndexMap equality doesn't - /// consider ordering, which we have to account for. Also, for the same - /// reason, IndexMap equality comparisons are slower than needed. - /// - /// See https://github.com/bluss/indexmap/issues/153 - pub fn custom_properties_equal(&self, other: &Self) -> bool { - match (self.custom_properties(), other.custom_properties()) { - (Some(l), Some(r)) => { - l.len() == r.len() && l.iter().zip(r.iter()).all(|((k1, v1), (k2, v2))| k1 == k2 && v1 == v2) - }, - (None, None) => true, - _ => false, - } - } - -% 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 (resolved or computed) value of the given longhand as a string in `dest`. - /// - /// TODO(emilio): We should move all the special resolution from - /// nsComputedDOMStyle to ToResolvedValue instead. - pub fn computed_or_resolved_value( - &self, - property_id: LonghandId, - context: Option<<&resolved::Context>, - dest: &mut CssStringWriter, - ) -> fmt::Result { - use crate::values::resolved::ToResolvedValue; - let mut dest = CssWriter::new(dest); - match property_id { - % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): - <% props = list(props) %> - ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { - let value = match property_id { - % for prop in props: - LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), - % endfor - _ => unsafe { debug_unreachable!() }, - }; - if let Some(c) = context { - value.to_resolved_value(c).to_css(&mut dest) - } else { - value.to_css(&mut dest) - } - } - % endfor - } - } - - /// Returns the given longhand's resolved value as a property declaration. - pub fn computed_or_resolved_declaration( - &self, - property_id: LonghandId, - context: Option<<&resolved::Context>, - ) -> PropertyDeclaration { - use crate::values::resolved::ToResolvedValue; - use crate::values::computed::ToComputedValue; - match property_id { - % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): - <% props = list(props) %> - ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { - let mut computed_value = match property_id { - % for prop in props: - LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), - % endfor - _ => unsafe { debug_unreachable!() }, - }; - if let Some(c) = context { - let resolved = computed_value.to_resolved_value(c); - computed_value = ToResolvedValue::from_resolved_value(resolved); - } - let specified = ToComputedValue::from_computed_value(&computed_value); - % if props[0].boxed: - let specified = Box::new(specified); - % endif - % if len(props) == 1: - PropertyDeclaration::${props[0].camel_case}(specified) - % else: - unsafe { - let mut out = mem::MaybeUninit::uninit(); - ptr::write( - out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified_type}>, - PropertyDeclarationVariantRepr { - tag: property_id as u16, - value: specified, - }, - ); - out.assume_init() - } - % endif - } - % endfor - } - } - - /// Resolves the currentColor keyword. - /// - /// Any color value from computed values (except for the 'color' property - /// itself) should go through this method. - /// - /// Usage example: - /// let top_color = - /// style.resolve_color(style.get_border().clone_border_top_color()); - #[inline] - pub fn resolve_color(&self, color: computed::Color) -> crate::color::AbsoluteColor { - let current_color = self.get_inherited_text().clone_color(); - color.resolve_to_absolute(¤t_color) - } - - /// Returns which longhand properties have different values in the two - /// ComputedValues. - #[cfg(feature = "gecko_debug")] - pub fn differing_properties(&self, other: &ComputedValues) -> LonghandIdSet { - let mut set = LonghandIdSet::new(); - % for prop in data.longhands: - if 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 } - - /// Converts the computed values to an Arc<> from a reference. - pub fn to_arc(&self) -> Arc<Self> { - // SAFETY: We're guaranteed to be allocated as an Arc<> since the - // functions above are the only ones that create ComputedValues - // instances in Servo (and that must be the case since ComputedValues' - // member is private). - unsafe { Arc::from_raw_addrefed(self) } - } - - /// Serializes the computed value of this property as a string. - pub fn computed_value_to_string(&self, property: PropertyDeclarationId) -> String { - match property { - PropertyDeclarationId::Longhand(id) => { - let context = resolved::Context { - style: self, - }; - let mut s = String::new(); - self.computed_or_resolved_value( - id, - Some(&context), - &mut s - ).unwrap(); - s - } - PropertyDeclarationId::Custom(name) => { - 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 { - /// Returns the visited style, if any. - pub fn visited_style(&self) -> Option<<&ComputedValues> { - self.visited_style.as_deref() - } - - % for style_struct in data.active_style_structs(): - /// Clone the ${style_struct.name} struct. - #[inline] - pub fn clone_${style_struct.name_lower}(&self) -> Arc<style_structs::${style_struct.name}> { - self.${style_struct.ident}.clone() - } - - /// Get a immutable reference to the ${style_struct.name} struct. - #[inline] - pub fn get_${style_struct.name_lower}(&self) -> &style_structs::${style_struct.name} { - &self.${style_struct.ident} - } - - /// Get a mutable reference to the ${style_struct.name} struct. - #[inline] - pub fn mutate_${style_struct.name_lower}(&mut self) -> &mut style_structs::${style_struct.name} { - Arc::make_mut(&mut self.${style_struct.ident}) - } - % endfor - - /// Gets a reference to the rule node. Panic if no rule node exists. - pub fn rules(&self) -> &StrongRuleNode { - self.rules.as_ref().unwrap() - } - - #[inline] - /// Returns whether the "content" property for the given style is completely - /// ineffective, and would yield an empty `::before` or `::after` - /// pseudo-element. - pub fn ineffective_content_property(&self) -> bool { - use crate::values::generics::counters::Content; - match self.get_counters().content { - Content::Normal | Content::None => true, - Content::Items(ref items) => items.is_empty(), - } - } - - /// Whether the current style or any of its ancestors is multicolumn. - #[inline] - pub fn can_be_fragmented(&self) -> bool { - self.flags.contains(ComputedValueFlags::CAN_BE_FRAGMENTED) - } - - /// Whether the current style is multicolumn. - #[inline] - pub fn is_multicol(&self) -> bool { - self.get_column().is_multicol() - } - - /// Get the logical computed inline size. - #[inline] - pub fn content_inline_size(&self) -> &computed::Size { - let position_style = self.get_position(); - if self.writing_mode.is_vertical() { - &position_style.height - } else { - &position_style.width - } - } - - /// Get the logical computed block size. - #[inline] - pub fn content_block_size(&self) -> &computed::Size { - let position_style = self.get_position(); - if self.writing_mode.is_vertical() { &position_style.width } else { &position_style.height } - } - - /// Get the logical computed min inline size. - #[inline] - pub fn min_inline_size(&self) -> &computed::Size { - let position_style = self.get_position(); - if self.writing_mode.is_vertical() { &position_style.min_height } else { &position_style.min_width } - } - - /// Get the logical computed min block size. - #[inline] - pub fn min_block_size(&self) -> &computed::Size { - let position_style = self.get_position(); - if self.writing_mode.is_vertical() { &position_style.min_width } else { &position_style.min_height } - } - - /// Get the logical computed max inline size. - #[inline] - pub fn max_inline_size(&self) -> &computed::MaxSize { - let position_style = self.get_position(); - if self.writing_mode.is_vertical() { &position_style.max_height } else { &position_style.max_width } - } - - /// Get the logical computed max block size. - #[inline] - pub fn max_block_size(&self) -> &computed::MaxSize { - let position_style = self.get_position(); - if self.writing_mode.is_vertical() { &position_style.max_width } else { &position_style.max_height } - } - - /// Get the logical computed padding for this writing mode. - #[inline] - pub fn logical_padding(&self) -> LogicalMargin<<&computed::LengthPercentage> { - let padding_style = self.get_padding(); - LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( - &padding_style.padding_top.0, - &padding_style.padding_right.0, - &padding_style.padding_bottom.0, - &padding_style.padding_left.0, - )) - } - - /// Get the logical border width - #[inline] - pub fn border_width_for_writing_mode(&self, writing_mode: WritingMode) -> LogicalMargin<Au> { - let border_style = self.get_border(); - LogicalMargin::from_physical(writing_mode, SideOffsets2D::new( - Au::from(border_style.border_top_width), - Au::from(border_style.border_right_width), - Au::from(border_style.border_bottom_width), - Au::from(border_style.border_left_width), - )) - } - - /// Gets the logical computed border widths for this style. - #[inline] - pub fn logical_border_width(&self) -> LogicalMargin<Au> { - self.border_width_for_writing_mode(self.writing_mode) - } - - /// Gets the logical computed margin from this style. - #[inline] - pub fn logical_margin(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { - let margin_style = self.get_margin(); - LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( - &margin_style.margin_top, - &margin_style.margin_right, - &margin_style.margin_bottom, - &margin_style.margin_left, - )) - } - - /// Gets the logical position from this style. - #[inline] - pub fn logical_position(&self) -> LogicalMargin<<&computed::LengthPercentageOrAuto> { - // FIXME(SimonSapin): should be the writing mode of the containing block, maybe? - let position_style = self.get_position(); - LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new( - &position_style.top, - &position_style.right, - &position_style.bottom, - &position_style.left, - )) - } - - /// Return true if the effects force the transform style to be Flat - pub fn overrides_transform_style(&self) -> bool { - use crate::computed_values::mix_blend_mode::T as MixBlendMode; - - let effects = self.get_effects(); - // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. - effects.opacity < 1.0 || - !effects.filter.0.is_empty() || - !effects.clip.is_auto() || - effects.mix_blend_mode != MixBlendMode::Normal - } - - /// <https://drafts.csswg.org/css-transforms/#grouping-property-values> - pub fn get_used_transform_style(&self) -> computed_values::transform_style::T { - use crate::computed_values::transform_style::T as TransformStyle; - - let box_ = self.get_box(); - - if self.overrides_transform_style() { - TransformStyle::Flat - } else { - // Return the computed value if not overridden by the above exceptions - box_.transform_style - } - } - - /// Whether given this transform value, the compositor would require a - /// layer. - pub fn transform_requires_layer(&self) -> bool { - use crate::values::generics::transform::TransformOperation; - // Check if the transform matrix is 2D or 3D - for transform in &*self.get_box().transform.0 { - match *transform { - TransformOperation::Perspective(..) => { - return true; - } - TransformOperation::Matrix3D(m) => { - // See http://dev.w3.org/csswg/css-transforms/#2d-matrix - if m.m31 != 0.0 || m.m32 != 0.0 || - m.m13 != 0.0 || m.m23 != 0.0 || - m.m43 != 0.0 || m.m14 != 0.0 || - m.m24 != 0.0 || m.m34 != 0.0 || - m.m33 != 1.0 || m.m44 != 1.0 { - return true; - } - } - TransformOperation::Translate3D(_, _, z) | - TransformOperation::TranslateZ(z) => { - if z.px() != 0. { - return true; - } - } - _ => {} - } - } - - // Neither perspective nor transform present - false - } -} - -/// A reference to a style struct of the parent, or our own style struct. -pub enum StyleStructRef<'a, T: 'static> { - /// A borrowed struct from the parent, for example, for inheriting style. - Borrowed(&'a T), - /// An owned struct, that we've already mutated. - Owned(UniqueArc<T>), - /// Temporarily vacated, will panic if accessed - Vacated, -} - -impl<'a, T: 'a> StyleStructRef<'a, T> -where - T: Clone, -{ - /// Ensure a mutable reference of this value exists, either cloning the - /// borrowed value, or returning the owned one. - pub fn mutate(&mut self) -> &mut T { - if let StyleStructRef::Borrowed(v) = *self { - *self = StyleStructRef::Owned(UniqueArc::new(v.clone())); - } - - match *self { - StyleStructRef::Owned(ref mut v) => v, - StyleStructRef::Borrowed(..) => unreachable!(), - StyleStructRef::Vacated => panic!("Accessed vacated style struct") - } - } - - /// Whether this is pointer-equal to the struct we're going to copy the - /// value from. - /// - /// This is used to avoid allocations when people write stuff like `font: - /// inherit` or such `all: initial`. - #[inline] - pub fn ptr_eq(&self, struct_to_copy_from: &T) -> bool { - match *self { - StyleStructRef::Owned(..) => false, - StyleStructRef::Borrowed(s) => { - s as *const T == struct_to_copy_from as *const T - } - StyleStructRef::Vacated => panic!("Accessed vacated style struct") - } - } - - /// Extract a unique Arc from this struct, vacating it. - /// - /// The vacated state is a transient one, please put the Arc back - /// when done via `put()`. This function is to be used to separate - /// the struct being mutated from the computed context - pub fn take(&mut self) -> UniqueArc<T> { - use std::mem::replace; - let inner = replace(self, StyleStructRef::Vacated); - - match inner { - StyleStructRef::Owned(arc) => arc, - StyleStructRef::Borrowed(s) => UniqueArc::new(s.clone()), - StyleStructRef::Vacated => panic!("Accessed vacated style struct"), - } - } - - /// Replace vacated ref with an arc - pub fn put(&mut self, arc: UniqueArc<T>) { - debug_assert!(matches!(*self, StyleStructRef::Vacated)); - *self = StyleStructRef::Owned(arc); - } - - /// Get a mutable reference to the owned struct, or `None` if the struct - /// hasn't been mutated. - pub fn get_if_mutated(&mut self) -> Option<<&mut T> { - match *self { - StyleStructRef::Owned(ref mut v) => Some(v), - StyleStructRef::Borrowed(..) => None, - StyleStructRef::Vacated => panic!("Accessed vacated style struct") - } - } - - /// Returns an `Arc` to the internal struct, constructing one if - /// appropriate. - pub fn build(self) -> Arc<T> { - match self { - StyleStructRef::Owned(v) => v.shareable(), - // SAFETY: We know all style structs are arc-allocated. - StyleStructRef::Borrowed(v) => unsafe { Arc::from_raw_addrefed(v) }, - StyleStructRef::Vacated => panic!("Accessed vacated style struct") - } - } -} - -impl<'a, T: 'a> ops::Deref for StyleStructRef<'a, T> { - type Target = T; - - fn deref(&self) -> &T { - match *self { - StyleStructRef::Owned(ref v) => &**v, - StyleStructRef::Borrowed(v) => v, - StyleStructRef::Vacated => panic!("Accessed vacated style struct") - } - } -} - -/// A type used to compute a struct with minimal overhead. -/// -/// This allows holding references to the parent/default computed values without -/// actually cloning them, until we either build the style, or mutate the -/// inherited value. -pub struct StyleBuilder<'a> { - /// The device we're using to compute style. - /// - /// This provides access to viewport unit ratios, etc. - pub device: &'a Device, - - /// The 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. - pub visited_style: Option<Arc<ComputedValues>>, - % for style_struct in data.active_style_structs(): - ${style_struct.ident}: StyleStructRef<'a, style_structs::${style_struct.name}>, - % endfor -} - -impl<'a> StyleBuilder<'a> { - /// Trivially construct a `StyleBuilder`. - pub(super) 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.get_${style_struct.name_lower}()), - % else: - ${style_struct.ident}: StyleStructRef::Borrowed(reset_style.get_${style_struct.name_lower}()), - % endif - % endfor - } - } - - /// NOTE(emilio): This is done so we can compute relative units with respect - /// to the parent style, but all the early properties / writing-mode / etc - /// are already set to the right ones on the kid. - /// - /// Do _not_ actually call this to construct a style, this should mostly be - /// used for animations. - pub fn for_animation( - device: &'a Device, - 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.get_${style_struct.name_lower}() - ), - % endfor - } - } - - /// Copy the reset properties from `style`. - pub fn copy_reset_from(&mut self, style: &'a ComputedValues) { - % for style_struct in data.active_style_structs(): - % if not style_struct.inherited: - self.${style_struct.ident} = - StyleStructRef::Borrowed(style.get_${style_struct.name_lower}()); - % endif - % endfor - } - - % for property in data.longhands: - % if not property.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::CONTENT_DEPENDS_ON_INHERITED_STYLE); - % endif - - % if property.ident == "display": - self.add_flags(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE); - % endif - - if self.${property.style_struct.ident}.ptr_eq(inherited_struct) { - return; - } - - self.${property.style_struct.ident}.mutate() - .copy_${property.ident}_from( - inherited_struct, - % 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 == "servo": - /// Set the `${property.ident}` to the computed value `value`. - #[allow(non_snake_case)] - pub fn set_${property.ident}( - &mut self, - value: longhands::${property.ident}::computed_value::T - ) { - % if not property.style_struct.inherited: - self.modified_reset = true; - % endif - - self.${property.style_struct.ident}.mutate() - .set_${property.ident}( - value, - % if property.logical: - self.writing_mode, - % endif - ); - } - % endif - % 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.get_${style_struct.name_lower}()); - } - % endfor - <% del style_struct %> - - /// Returns whether this computed style represents a floated object. - pub fn is_floating(&self) -> bool { - self.get_box().clone_float().is_floating() - } - - /// Returns whether this computed style represents an absolutely-positioned - /// object. - pub fn is_absolutely_positioned(&self) -> bool { - self.get_box().clone_position().is_absolutely_positioned() - } - - /// Whether this style has a top-layer style. - #[cfg(feature = "servo")] - pub fn in_top_layer(&self) -> bool { - matches!(self.get_box().clone__servo_top_layer(), - longhands::_servo_top_layer::computed_value::T::Top) - } - - /// Whether this style has a top-layer style. - #[cfg(feature = "gecko")] - pub fn in_top_layer(&self) -> bool { - matches!(self.get_box().clone__moz_top_layer(), - longhands::_moz_top_layer::computed_value::T::Top) - } - - /// Clears the "have any reset structs been modified" flag. - pub fn clear_modified_reset(&mut self) { - self.modified_reset = false; - } - - /// Returns whether we have mutated any reset structs since the the last - /// time `clear_modified_reset` was called. - pub fn modified_reset(&self) -> bool { - self.modified_reset - } - - /// Return the current flags. - #[inline] - pub fn flags(&self) -> ComputedValueFlags { - self.flags.get() - } - - /// Add a flag to the current builder. - #[inline] - pub fn add_flags(&self, flag: ComputedValueFlags) { - let flags = self.flags() | flag; - self.flags.set(flags); - } - - /// Removes a flag to the current builder. - #[inline] - pub fn remove_flags(&self, flag: ComputedValueFlags) { - let flags = self.flags() & !flag; - self.flags.set(flags); - } - - /// Turns this `StyleBuilder` into a proper `ComputedValues` instance. - pub fn build(self) -> Arc<ComputedValues> { - ComputedValues::new( - self.pseudo, - self.custom_properties, - self.writing_mode, - self.flags.get(), - self.rules, - self.visited_style, - % for style_struct in data.active_style_structs(): - self.${style_struct.ident}.build(), - % endfor - ) - } - - /// Get the custom properties map if necessary. - pub fn custom_properties(&self) -> 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 : Arc<ComputedValues> = Arc::new(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(Au(0)); - } - % endfor -} - -/// An identifier for a given alias property. -#[derive(Clone, Copy, Eq, PartialEq, MallocSizeOf)] -#[repr(u16)] -pub enum AliasId { - % for i, property in enumerate(data.all_aliases()): - /// ${property.name} - ${property.camel_case} = ${i}, - % endfor -} - -#[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.aliases: - % if '-' in prop.name: - [${prop.ident.capitalize()}, Set${prop.ident.capitalize()}, - PropertyId::${kind}(${kind}Id::${property.camel_case})], - % endif - [${prop.camel_case}, Set${prop.camel_case}, - PropertyId::${kind}(${kind}Id::${property.camel_case})], - % endfor - % endif - % endfor - % endfor - } - } -} - -/// Call the given macro with tokens like this for each longhand properties: -/// -/// ``` -/// { snake_case_ident } -/// ``` -/// -/// … 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} } - % endfor - } - } -} - -// Large pages generate tens of thousands of ComputedValues. -size_of_test!(ComputedValues, 192); -// FFI relies on this. -size_of_test!(Option<Arc<ComputedValues>>, 8); - -// There are two reasons for this test to fail: -// -// * Your changes made a specified value type for a given property go -// over the threshold. In that case, you should try to shrink it again -// or, if not possible, mark the property as boxed in the property -// definition. -// -// * Your changes made a specified value type smaller, so that it no -// longer needs to be boxed. In this case you just need to remove -// boxed=True from the property definition. Nice job! -#[cfg(target_pointer_width = "64")] -#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952 -const BOX_THRESHOLD: usize = 24; -% for longhand in data.longhands: -#[cfg(target_pointer_width = "64")] -% if longhand.boxed: -const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() > BOX_THRESHOLD); -% else: -const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() <= BOX_THRESHOLD); -% endif -% endfor - -% if engine == "servo": -% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]: - macro_rules! restyle_damage_${effect_name} { - ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ]) => ({ - restyle_damage_${effect_name}!($old, $new, $damage, [$($effect),*], false) - }); - ($old: ident, $new: ident, $damage: ident, [ $($effect:expr),* ], $extra:expr) => ({ - if - % for style_struct in data.active_style_structs(): - % for longhand in style_struct.longhands: - % if effect_name in longhand.servo_restyle_damage.split() and not longhand.logical: - $old.get_${style_struct.name_lower}().${longhand.ident} != - $new.get_${style_struct.name_lower}().${longhand.ident} || - % endif - % endfor - % endfor - - $extra || false { - $damage.insert($($effect)|*); - true - } else { - false - } - }); - } -% endfor -% endif diff --git a/components/style/properties/shorthands/background.mako.rs b/components/style/properties/shorthands/background.mako.rs deleted file mode 100644 index 5fee5cb2b03..00000000000 --- a/components/style/properties/shorthands/background.mako.rs +++ /dev/null @@ -1,289 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -// TODO: other background-* properties -<%helpers:shorthand name="background" - engines="gecko servo" - sub_properties="background-color background-position-x background-position-y background-repeat - background-attachment background-image background-size background-origin - background-clip" - spec="https://drafts.csswg.org/css-backgrounds/#the-background"> - use crate::properties::longhands::{background_position_x, background_position_y, background_repeat}; - use crate::properties::longhands::{background_attachment, background_color, background_image, background_size, background_origin}; - use crate::properties::longhands::background_clip; - use crate::properties::longhands::background_clip::single_value::computed_value::T as Clip; - use crate::properties::longhands::background_origin::single_value::computed_value::T as Origin; - use crate::values::specified::{AllowQuirks, Color, Position, PositionComponent}; - use crate::parser::Parse; - - // FIXME(emilio): Should be the same type! - impl From<background_origin::single_value::SpecifiedValue> for background_clip::single_value::SpecifiedValue { - fn from(origin: background_origin::single_value::SpecifiedValue) -> - background_clip::single_value::SpecifiedValue { - match origin { - background_origin::single_value::SpecifiedValue::ContentBox => - background_clip::single_value::SpecifiedValue::ContentBox, - background_origin::single_value::SpecifiedValue::PaddingBox => - background_clip::single_value::SpecifiedValue::PaddingBox, - background_origin::single_value::SpecifiedValue::BorderBox => - background_clip::single_value::SpecifiedValue::BorderBox, - } - } - } - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut background_color = None; - - % for name in "image position_x position_y repeat size attachment origin clip".split(): - // Vec grows from 0 to 4 by default on first push(). So allocate with - // capacity 1, so in the common case of only one item we don't way - // overallocate, then shrink. Note that we always push at least one - // item if parsing succeeds. - let mut background_${name} = Vec::with_capacity(1); - % endfor - input.parse_comma_separated(|input| { - // background-color can only be in the last element, so if it - // is parsed anywhere before, the value is invalid. - if background_color.is_some() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - % for name in "image position repeat size attachment origin clip".split(): - let mut ${name} = None; - % endfor - loop { - if background_color.is_none() { - if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) { - background_color = Some(value); - continue - } - } - if position.is_none() { - if let Ok(value) = input.try_parse(|input| { - Position::parse_three_value_quirky(context, input, AllowQuirks::No) - }) { - position = Some(value); - - // Parse background size, if applicable. - size = input.try_parse(|input| { - input.expect_delim('/')?; - background_size::single_value::parse(context, input) - }).ok(); - - continue - } - } - % for name in "image repeat attachment origin clip".split(): - if ${name}.is_none() { - if let Ok(value) = input.try_parse(|input| background_${name}::single_value - ::parse(context, input)) { - ${name} = Some(value); - continue - } - } - % endfor - break - } - if clip.is_none() { - if let Some(origin) = origin { - clip = Some(background_clip::single_value::SpecifiedValue::from(origin)); - } - } - let mut any = false; - % for name in "image position repeat size attachment origin clip".split(): - any = any || ${name}.is_some(); - % endfor - any = any || background_color.is_some(); - if any { - if let Some(position) = position { - background_position_x.push(position.horizontal); - background_position_y.push(position.vertical); - } else { - background_position_x.push(PositionComponent::zero()); - background_position_y.push(PositionComponent::zero()); - } - % for name in "image repeat size attachment origin clip".split(): - if let Some(bg_${name}) = ${name} { - background_${name}.push(bg_${name}); - } else { - background_${name}.push(background_${name}::single_value - ::get_initial_specified_value()); - } - % endfor - Ok(()) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - })?; - - Ok(expanded! { - background_color: background_color.unwrap_or(Color::transparent()), - % for name in "image position_x position_y repeat size attachment origin clip".split(): - background_${name}: background_${name}::SpecifiedValue(background_${name}.into()), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let len = self.background_image.0.len(); - // There should be at least one declared value - if len == 0 { - return Ok(()); - } - - // If a value list length is differs then we don't do a shorthand serialization. - // The exceptions to this is color which appears once only and is serialized - // with the last item. - % for name in "image position_x position_y size repeat origin clip attachment".split(): - if len != self.background_${name}.0.len() { - return Ok(()); - } - % endfor - - for i in 0..len { - % for name in "image position_x position_y repeat size attachment origin clip".split(): - let ${name} = &self.background_${name}.0[i]; - % endfor - - if i != 0 { - dest.write_str(", ")?; - } - - let mut wrote_value = false; - - if i == len - 1 { - if *self.background_color != background_color::get_initial_specified_value() { - self.background_color.to_css(dest)?; - wrote_value = true; - } - } - - if *image != background_image::single_value::get_initial_specified_value() { - if wrote_value { - dest.write_char(' ')?; - } - image.to_css(dest)?; - wrote_value = true; - } - - // Size is only valid after a position so when there is a - // non-initial size we must also serialize position - if *position_x != PositionComponent::zero() || - *position_y != PositionComponent::zero() || - *size != background_size::single_value::get_initial_specified_value() - { - if wrote_value { - dest.write_char(' ')?; - } - - Position { - horizontal: position_x.clone(), - vertical: position_y.clone() - }.to_css(dest)?; - - wrote_value = true; - - if *size != background_size::single_value::get_initial_specified_value() { - dest.write_str(" / ")?; - size.to_css(dest)?; - } - } - - % for name in "repeat attachment".split(): - if *${name} != background_${name}::single_value::get_initial_specified_value() { - if wrote_value { - dest.write_char(' ')?; - } - ${name}.to_css(dest)?; - wrote_value = true; - } - % endfor - - if *origin != Origin::PaddingBox || *clip != Clip::BorderBox { - if wrote_value { - dest.write_char(' ')?; - } - origin.to_css(dest)?; - if *clip != From::from(*origin) { - dest.write_char(' ')?; - clip.to_css(dest)?; - } - - wrote_value = true; - } - - if !wrote_value { - image.to_css(dest)?; - } - } - - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="background-position" - engines="gecko servo" - flags="SHORTHAND_IN_GETCS" - sub_properties="background-position-x background-position-y" - spec="https://drafts.csswg.org/css-backgrounds-4/#the-background-position"> - use crate::properties::longhands::{background_position_x, background_position_y}; - use crate::values::specified::AllowQuirks; - use crate::values::specified::position::Position; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - // Vec grows from 0 to 4 by default on first push(). So allocate with - // capacity 1, so in the common case of only one item we don't way - // overallocate, then shrink. Note that we always push at least one - // item if parsing succeeds. - let mut position_x = Vec::with_capacity(1); - let mut position_y = Vec::with_capacity(1); - let mut any = false; - - input.parse_comma_separated(|input| { - let value = Position::parse_three_value_quirky(context, input, AllowQuirks::Yes)?; - position_x.push(value.horizontal); - position_y.push(value.vertical); - any = true; - Ok(()) - })?; - if !any { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(expanded! { - background_position_x: background_position_x::SpecifiedValue(position_x.into()), - background_position_y: background_position_y::SpecifiedValue(position_y.into()), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let len = self.background_position_x.0.len(); - if len == 0 || len != self.background_position_y.0.len() { - return Ok(()); - } - for i in 0..len { - Position { - horizontal: self.background_position_x.0[i].clone(), - vertical: self.background_position_y.0[i].clone() - }.to_css(dest)?; - - if i < len - 1 { - dest.write_str(", ")?; - } - } - Ok(()) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/border.mako.rs b/components/style/properties/shorthands/border.mako.rs deleted file mode 100644 index 2eeb691e242..00000000000 --- a/components/style/properties/shorthands/border.mako.rs +++ /dev/null @@ -1,492 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import to_rust_ident, ALL_SIDES, PHYSICAL_SIDES, maybe_moz_logical_alias %> - -${helpers.four_sides_shorthand( - "border-color", - "border-%s-color", - "specified::Color::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-backgrounds/#border-color", - allow_quirks="Yes", -)} - -${helpers.four_sides_shorthand( - "border-style", - "border-%s-style", - engines="gecko servo", - spec="https://drafts.csswg.org/css-backgrounds/#border-style", -)} - -<%helpers:shorthand - name="border-width" - engines="gecko servo" - sub_properties="${ - ' '.join('border-%s-width' % side - for side in PHYSICAL_SIDES)}" - spec="https://drafts.csswg.org/css-backgrounds/#border-width"> - use crate::values::generics::rect::Rect; - use crate::values::specified::{AllowQuirks, BorderSideWidth}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let rect = Rect::parse_with(context, input, |_, i| { - BorderSideWidth::parse_quirky(context, i, AllowQuirks::Yes) - })?; - Ok(expanded! { - border_top_width: rect.0, - border_right_width: rect.1, - border_bottom_width: rect.2, - border_left_width: rect.3, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - % for side in PHYSICAL_SIDES: - let ${side} = &self.border_${side}_width; - % endfor - Rect::new(top, right, bottom, left).to_css(dest) - } - } -</%helpers:shorthand> - - -pub fn parse_border<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<(specified::Color, specified::BorderStyle, specified::BorderSideWidth), ParseError<'i>> { - use crate::values::specified::{Color, BorderStyle, BorderSideWidth}; - let _unused = context; - let mut color = None; - let mut style = None; - let mut width = None; - let mut any = false; - loop { - if width.is_none() { - if let Ok(value) = input.try_parse(|i| BorderSideWidth::parse(context, i)) { - width = Some(value); - any = true; - } - } - if style.is_none() { - if let Ok(value) = input.try_parse(BorderStyle::parse) { - style = Some(value); - any = true; - continue - } - } - if color.is_none() { - if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) { - color = Some(value); - any = true; - continue - } - } - break - } - if !any { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - Ok((color.unwrap_or(Color::CurrentColor), style.unwrap_or(BorderStyle::None), width.unwrap_or(BorderSideWidth::medium()))) -} - -% for side, logical in ALL_SIDES: - <% - spec = "https://drafts.csswg.org/css-backgrounds/#border-%s" % side - if logical: - spec = "https://drafts.csswg.org/css-logical-props/#propdef-border-%s" % side - %> - <%helpers:shorthand - name="border-${side}" - engines="gecko servo" - sub_properties="${' '.join( - 'border-%s-%s' % (side, prop) - for prop in ['width', 'style', 'color'] - )}" - aliases="${maybe_moz_logical_alias(engine, (side, logical), '-moz-border-%s')}" - spec="${spec}"> - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let (color, style, width) = super::parse_border(context, input)?; - Ok(expanded! { - border_${to_rust_ident(side)}_color: color, - border_${to_rust_ident(side)}_style: style, - border_${to_rust_ident(side)}_width: width - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - crate::values::specified::border::serialize_directional_border( - dest, - self.border_${to_rust_ident(side)}_width, - self.border_${to_rust_ident(side)}_style, - self.border_${to_rust_ident(side)}_color - ) - } - } - - </%helpers:shorthand> -% endfor - -<%helpers:shorthand name="border" - engines="gecko servo" - sub_properties="${' '.join('border-%s-%s' % (side, prop) - for side in PHYSICAL_SIDES for prop in ['width', 'style', 'color'] - )} - ${' '.join('border-image-%s' % name - for name in ['outset', 'repeat', 'slice', 'source', 'width'])}" - derive_value_info="False" - spec="https://drafts.csswg.org/css-backgrounds/#border"> - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice}; - use crate::properties::longhands::{border_image_source, border_image_width}; - - let (color, style, width) = super::parse_border(context, input)?; - Ok(expanded! { - % for side in PHYSICAL_SIDES: - border_${side}_color: color.clone(), - border_${side}_style: style, - border_${side}_width: width.clone(), - % endfor - - // The ‘border’ shorthand resets ‘border-image’ to its initial value. - // See https://drafts.csswg.org/css-backgrounds-3/#the-border-shorthands - % for name in "outset repeat slice source width".split(): - border_image_${name}: border_image_${name}::get_initial_specified_value(), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - use crate::properties::longhands; - - // If any of the border-image longhands differ from their initial specified values we should not - // invoke serialize_directional_border(), so there is no point in continuing on to compute all_equal. - % for name in "outset repeat slice source width".split(): - if *self.border_image_${name} != longhands::border_image_${name}::get_initial_specified_value() { - return Ok(()); - } - % endfor - - let all_equal = { - % for side in PHYSICAL_SIDES: - let border_${side}_width = self.border_${side}_width; - let border_${side}_style = self.border_${side}_style; - let border_${side}_color = self.border_${side}_color; - % endfor - - border_top_width == border_right_width && - border_right_width == border_bottom_width && - border_bottom_width == border_left_width && - - border_top_style == border_right_style && - border_right_style == border_bottom_style && - border_bottom_style == border_left_style && - - border_top_color == border_right_color && - border_right_color == border_bottom_color && - border_bottom_color == border_left_color - }; - - // If all longhands are all present, then all sides should be the same, - // so we can just one set of color/style/width - if !all_equal { - return Ok(()) - } - crate::values::specified::border::serialize_directional_border( - dest, - self.border_${side}_width, - self.border_${side}_style, - self.border_${side}_color - ) - } - } - - // Just use the same as border-left. The border shorthand can't accept - // any value that the sub-shorthand couldn't. - <% - border_left = "<crate::properties::shorthands::border_left::Longhands as SpecifiedValueInfo>" - %> - impl SpecifiedValueInfo for Longhands { - const SUPPORTED_TYPES: u8 = ${border_left}::SUPPORTED_TYPES; - fn collect_completion_keywords(f: KeywordsCollectFn) { - ${border_left}::collect_completion_keywords(f); - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="border-radius" - engines="gecko servo" - sub_properties="${' '.join( - 'border-%s-radius' % (corner) - for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left'] - )}" - extra_prefixes="webkit" - spec="https://drafts.csswg.org/css-backgrounds/#border-radius" -> - use crate::values::generics::rect::Rect; - use crate::values::generics::border::BorderCornerRadius; - use crate::values::specified::border::BorderRadius; - use crate::parser::Parse; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let radii = BorderRadius::parse(context, input)?; - Ok(expanded! { - border_top_left_radius: radii.top_left, - border_top_right_radius: radii.top_right, - border_bottom_right_radius: radii.bottom_right, - border_bottom_left_radius: radii.bottom_left, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let LonghandsToSerialize { - border_top_left_radius: &BorderCornerRadius(ref tl), - border_top_right_radius: &BorderCornerRadius(ref tr), - border_bottom_right_radius: &BorderCornerRadius(ref br), - border_bottom_left_radius: &BorderCornerRadius(ref bl), - } = *self; - - - let widths = Rect::new(tl.width(), tr.width(), br.width(), bl.width()); - let heights = Rect::new(tl.height(), tr.height(), br.height(), bl.height()); - - BorderRadius::serialize_rects(widths, heights, dest) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="border-image" - engines="gecko servo" - servo_pref="layout.legacy_layout", - sub_properties="border-image-outset - border-image-repeat border-image-slice border-image-source border-image-width" - extra_prefixes="moz:layout.css.prefixes.border-image webkit" - spec="https://drafts.csswg.org/css-backgrounds-3/#border-image" -> - use crate::properties::longhands::{border_image_outset, border_image_repeat, border_image_slice}; - use crate::properties::longhands::{border_image_source, border_image_width}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - % for name in "outset repeat slice source width".split(): - let mut ${name} = border_image_${name}::get_initial_specified_value(); - % endfor - let mut any = false; - let mut parsed_slice = false; - let mut parsed_source = false; - let mut parsed_repeat = false; - loop { - if !parsed_slice { - if let Ok(value) = input.try_parse(|input| border_image_slice::parse(context, input)) { - parsed_slice = true; - any = true; - slice = value; - // Parse border image width and outset, if applicable. - let maybe_width_outset: Result<_, ParseError> = input.try_parse(|input| { - input.expect_delim('/')?; - - // Parse border image width, if applicable. - let w = input.try_parse(|input| border_image_width::parse(context, input)).ok(); - - // Parse border image outset if applicable. - let o = input.try_parse(|input| { - input.expect_delim('/')?; - border_image_outset::parse(context, input) - }).ok(); - if w.is_none() && o.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - Ok((w, o)) - }); - if let Ok((w, o)) = maybe_width_outset { - if let Some(w) = w { - width = w; - } - if let Some(o) = o { - outset = o; - } - } - continue; - } - } - % for name in "source repeat".split(): - if !parsed_${name} { - if let Ok(value) = input.try_parse(|input| border_image_${name}::parse(context, input)) { - ${name} = value; - parsed_${name} = true; - any = true; - continue - } - } - % endfor - break - } - if !any { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - Ok(expanded! { - % for name in "outset repeat slice source width".split(): - border_image_${name}: ${name}, - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let mut has_any = false; - % for name in "source slice outset width repeat".split(): - let has_${name} = *self.border_image_${name} != border_image_${name}::get_initial_specified_value(); - has_any = has_any || has_${name}; - % endfor - if has_source || !has_any { - self.border_image_source.to_css(dest)?; - if !has_any { - return Ok(()); - } - } - let needs_slice = has_slice || has_width || has_outset; - if needs_slice { - if has_source { - dest.write_char(' ')?; - } - self.border_image_slice.to_css(dest)?; - if has_width || has_outset { - dest.write_str(" /")?; - if has_width { - dest.write_char(' ')?; - self.border_image_width.to_css(dest)?; - } - if has_outset { - dest.write_str(" / ")?; - self.border_image_outset.to_css(dest)?; - } - } - } - if has_repeat { - if has_source || needs_slice { - dest.write_char(' ')?; - } - self.border_image_repeat.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -% for axis in ["block", "inline"]: - % for prop in ["width", "style", "color"]: - <% - spec = "https://drafts.csswg.org/css-logical/#propdef-border-%s-%s" % (axis, prop) - %> - <%helpers:shorthand - engines="gecko servo" - name="border-${axis}-${prop}" - sub_properties="${' '.join( - 'border-%s-%s-%s' % (axis, side, prop) - for side in ['start', 'end'] - )}" - spec="${spec}"> - - use crate::properties::longhands::border_${axis}_start_${prop}; - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let start_value = border_${axis}_start_${prop}::parse(context, input)?; - let end_value = - input.try_parse(|input| border_${axis}_start_${prop}::parse(context, input)) - .unwrap_or_else(|_| start_value.clone()); - - Ok(expanded! { - border_${axis}_start_${prop}: start_value, - border_${axis}_end_${prop}: end_value, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.border_${axis}_start_${prop}.to_css(dest)?; - - if self.border_${axis}_end_${prop} != self.border_${axis}_start_${prop} { - dest.write_char(' ')?; - self.border_${axis}_end_${prop}.to_css(dest)?; - } - - Ok(()) - } - } - </%helpers:shorthand> - % endfor -% endfor - -% for axis in ["block", "inline"]: - <% - spec = "https://drafts.csswg.org/css-logical/#propdef-border-%s" % (axis) - %> - <%helpers:shorthand - name="border-${axis}" - engines="gecko servo" - sub_properties="${' '.join( - 'border-%s-%s-width' % (axis, side) - for side in ['start', 'end'] - )} ${' '.join( - 'border-%s-%s-style' % (axis, side) - for side in ['start', 'end'] - )} ${' '.join( - 'border-%s-%s-color' % (axis, side) - for side in ['start', 'end'] - )}" - spec="${spec}"> - - use crate::properties::shorthands::border_${axis}_start; - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let start_value = border_${axis}_start::parse_value(context, input)?; - Ok(expanded! { - border_${axis}_start_width: start_value.border_${axis}_start_width.clone(), - border_${axis}_end_width: start_value.border_${axis}_start_width, - border_${axis}_start_style: start_value.border_${axis}_start_style.clone(), - border_${axis}_end_style: start_value.border_${axis}_start_style, - border_${axis}_start_color: start_value.border_${axis}_start_color.clone(), - border_${axis}_end_color: start_value.border_${axis}_start_color, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - crate::values::specified::border::serialize_directional_border( - dest, - self.border_${axis}_start_width, - self.border_${axis}_start_style, - self.border_${axis}_start_color - ) - } - } - </%helpers:shorthand> -% endfor diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs deleted file mode 100644 index b9885492cd8..00000000000 --- a/components/style/properties/shorthands/box.mako.rs +++ /dev/null @@ -1,311 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -${helpers.two_properties_shorthand( - "overflow", - "overflow-x", - "overflow-y", - engines="gecko servo", - flags="SHORTHAND_IN_GETCS", - spec="https://drafts.csswg.org/css-overflow/#propdef-overflow", -)} - -${helpers.two_properties_shorthand( - "overflow-clip-box", - "overflow-clip-box-block", - "overflow-clip-box-inline", - engines="gecko", - enabled_in="ua", - gecko_pref="layout.css.overflow-clip-box.enabled", - spec="Internal, may be standardized in the future " - "(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)", -)} - -${helpers.two_properties_shorthand( - "overscroll-behavior", - "overscroll-behavior-x", - "overscroll-behavior-y", - engines="gecko", - gecko_pref="layout.css.overscroll-behavior.enabled", - spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties", -)} - -<%helpers:shorthand - engines="gecko" - name="container" - sub_properties="container-name container-type" - gecko_pref="layout.css.container-queries.enabled" - enabled_in="ua" - spec="https://drafts.csswg.org/css-contain-3/#container-shorthand" -> - use crate::values::specified::box_::{ContainerName, ContainerType}; - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::parser::Parse; - // See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't - // match the spec. - let container_name = ContainerName::parse(context, input)?; - let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() { - ContainerType::parse(input)? - } else { - ContainerType::Normal - }; - Ok(expanded! { - container_name: container_name, - container_type: container_type, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.container_name.to_css(dest)?; - if !self.container_type.is_normal() { - dest.write_str(" / ")?; - self.container_type.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - engines="gecko" - name="page-break-before" - flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" - sub_properties="break-before" - spec="https://drafts.csswg.org/css-break-3/#page-break-properties" -> - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::values::specified::box_::BreakBetween; - Ok(expanded! { - break_before: BreakBetween::parse_legacy(context, input)?, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.break_before.to_css_legacy(dest) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - engines="gecko" - name="page-break-after" - flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" - sub_properties="break-after" - spec="https://drafts.csswg.org/css-break-3/#page-break-properties" -> - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::values::specified::box_::BreakBetween; - Ok(expanded! { - break_after: BreakBetween::parse_legacy(context, input)?, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.break_after.to_css_legacy(dest) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - engines="gecko" - name="page-break-inside" - flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" - sub_properties="break-inside" - spec="https://drafts.csswg.org/css-break-3/#page-break-properties" -> - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::values::specified::box_::BreakWithin; - Ok(expanded! { - break_inside: BreakWithin::parse_legacy(context, input)?, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.break_inside.to_css_legacy(dest) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="offset" - engines="gecko" - sub_properties="offset-path offset-distance offset-rotate offset-anchor - offset-position" - gecko_pref="layout.css.motion-path.enabled", - spec="https://drafts.fxtf.org/motion-1/#offset-shorthand"> - use crate::parser::Parse; - use crate::values::specified::motion::{OffsetPath, OffsetPosition, OffsetRotate}; - use crate::values::specified::{LengthPercentage, PositionOrAuto}; - use crate::Zero; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let offset_position = - if static_prefs::pref!("layout.css.motion-path-offset-position.enabled") { - input.try_parse(|i| OffsetPosition::parse(context, i)).ok() - } else { - None - }; - - let offset_path = input.try_parse(|i| OffsetPath::parse(context, i)).ok(); - - // Must have one of [offset-position, offset-path]. - // FIXME: The syntax is out-of-date after the update of the spec. - // https://github.com/w3c/fxtf-drafts/issues/515 - if offset_position.is_none() && offset_path.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - let mut offset_distance = None; - let mut offset_rotate = None; - // offset-distance and offset-rotate are grouped with offset-path. - if offset_path.is_some() { - loop { - if offset_distance.is_none() { - if let Ok(value) = input.try_parse(|i| LengthPercentage::parse(context, i)) { - offset_distance = Some(value); - } - } - - if offset_rotate.is_none() { - if let Ok(value) = input.try_parse(|i| OffsetRotate::parse(context, i)) { - offset_rotate = Some(value); - continue; - } - } - break; - } - } - - let offset_anchor = input.try_parse(|i| { - i.expect_delim('/')?; - PositionOrAuto::parse(context, i) - }).ok(); - - Ok(expanded! { - offset_position: offset_position.unwrap_or(OffsetPosition::auto()), - offset_path: offset_path.unwrap_or(OffsetPath::none()), - offset_distance: offset_distance.unwrap_or(LengthPercentage::zero()), - offset_rotate: offset_rotate.unwrap_or(OffsetRotate::auto()), - offset_anchor: offset_anchor.unwrap_or(PositionOrAuto::auto()), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if let Some(offset_position) = self.offset_position { - // The basic concept is: we must serialize offset-position or offset-path group. - // offset-path group means "offset-path offset-distance offset-rotate". - let must_serialize_path = *self.offset_path != OffsetPath::None - || (!self.offset_distance.is_zero() || !self.offset_rotate.is_auto()); - let position_is_default = matches!(offset_position, OffsetPosition::Auto); - if !position_is_default || !must_serialize_path { - offset_position.to_css(dest)?; - } - - if must_serialize_path { - if !position_is_default { - dest.write_char(' ')?; - } - self.offset_path.to_css(dest)?; - } - } else { - // If the pref is off, we always show offset-path. - self.offset_path.to_css(dest)?; - } - - if !self.offset_distance.is_zero() { - dest.write_char(' ')?; - self.offset_distance.to_css(dest)?; - } - - if !self.offset_rotate.is_auto() { - dest.write_char(' ')?; - self.offset_rotate.to_css(dest)?; - } - - if *self.offset_anchor != PositionOrAuto::auto() { - dest.write_str(" / ")?; - self.offset_anchor.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="zoom" engines="gecko" - sub_properties="transform transform-origin" - gecko_pref="layout.css.zoom-transform-hack.enabled" - flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" - spec="Not a standard, only a compat hack"> - use crate::parser::Parse; - use crate::values::specified::{Number, NumberOrPercentage, TransformOrigin}; - use crate::values::generics::transform::{Transform, TransformOperation}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let zoom = match input.try_parse(|input| NumberOrPercentage::parse(context, input)) { - Ok(number_or_percent) => number_or_percent.to_number(), - Err(..) => { - input.expect_ident_matching("normal")?; - Number::new(1.0) - }, - }; - - // Make sure that the initial value matches the values for the - // longhands, just for general sanity. `zoom: 1` and `zoom: 0` are - // ignored, see [1][2]. They are just hack for the "has layout" mode on - // IE. - // - // [1]: https://bugs.webkit.org/show_bug.cgi?id=18467 - // [2]: https://bugzilla.mozilla.org/show_bug.cgi?id=1593009 - Ok(if zoom.get() == 1.0 || zoom.get() == 0.0 { - expanded! { - transform: Transform::none(), - transform_origin: TransformOrigin::initial_value(), - } - } else { - expanded! { - transform: Transform(vec![TransformOperation::Scale(zoom, zoom)].into()), - transform_origin: TransformOrigin::zero_zero(), - } - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if self.transform.0.is_empty() && *self.transform_origin == TransformOrigin::initial_value() { - return 1.0f32.to_css(dest); - } - if *self.transform_origin != TransformOrigin::zero_zero() { - return Ok(()) - } - match &*self.transform.0 { - [TransformOperation::Scale(x, y)] if x == y => x.to_css(dest), - _ => Ok(()), - } - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/column.mako.rs b/components/style/properties/shorthands/column.mako.rs deleted file mode 100644 index 4cf9a8d7866..00000000000 --- a/components/style/properties/shorthands/column.mako.rs +++ /dev/null @@ -1,115 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand name="columns" - engines="gecko servo" - sub_properties="column-width column-count" - servo_pref="layout.columns.enabled" - spec="https://drafts.csswg.org/css-multicol/#propdef-columns"> - use crate::properties::longhands::{column_count, column_width}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut column_count = None; - let mut column_width = None; - let mut autos = 0; - - loop { - if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() { - // Leave the options to None, 'auto' is the initial value. - autos += 1; - continue - } - - if column_count.is_none() { - if let Ok(value) = input.try_parse(|input| column_count::parse(context, input)) { - column_count = Some(value); - continue - } - } - - if column_width.is_none() { - if let Ok(value) = input.try_parse(|input| column_width::parse(context, input)) { - column_width = Some(value); - continue - } - } - - break - } - - let values = autos + column_count.iter().len() + column_width.iter().len(); - if values == 0 || values > 2 { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(expanded! { - column_count: unwrap_or_initial!(column_count), - column_width: unwrap_or_initial!(column_width), - }) - } - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if self.column_width.is_auto() { - return self.column_count.to_css(dest) - } - self.column_width.to_css(dest)?; - if !self.column_count.is_auto() { - dest.write_char(' ')?; - self.column_count.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="column-rule" - engines="gecko" - sub_properties="column-rule-width column-rule-style column-rule-color" - derive_serialize="True" - spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule" -> - use crate::properties::longhands::{column_rule_width, column_rule_style}; - use crate::properties::longhands::column_rule_color; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - % for name in "width style color".split(): - let mut column_rule_${name} = None; - % endfor - let mut any = false; - - loop { - % for name in "width style color".split(): - if column_rule_${name}.is_none() { - if let Ok(value) = input.try_parse(|input| - column_rule_${name}::parse(context, input)) { - column_rule_${name} = Some(value); - any = true; - continue - } - } - % endfor - - break - } - if any { - Ok(expanded! { - column_rule_width: unwrap_or_initial!(column_rule_width), - column_rule_style: unwrap_or_initial!(column_rule_style), - column_rule_color: unwrap_or_initial!(column_rule_color), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/font.mako.rs b/components/style/properties/shorthands/font.mako.rs deleted file mode 100644 index ec682048e75..00000000000 --- a/components/style/properties/shorthands/font.mako.rs +++ /dev/null @@ -1,547 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import SYSTEM_FONT_LONGHANDS %> - -<%helpers:shorthand - name="font" - engines="gecko servo" - sub_properties=" - font-style - font-variant-caps - font-weight - font-stretch - font-size - line-height - font-family - ${'font-size-adjust' if engine == 'gecko' else ''} - ${'font-kerning' if engine == 'gecko' else ''} - ${'font-optical-sizing' if engine == 'gecko' else ''} - ${'font-variant-alternates' if engine == 'gecko' else ''} - ${'font-variant-east-asian' if engine == 'gecko' else ''} - ${'font-variant-emoji' if engine == 'gecko' else ''} - ${'font-variant-ligatures' if engine == 'gecko' else ''} - ${'font-variant-numeric' if engine == 'gecko' else ''} - ${'font-variant-position' if engine == 'gecko' else ''} - ${'font-language-override' if engine == 'gecko' else ''} - ${'font-feature-settings' if engine == 'gecko' else ''} - ${'font-variation-settings' if engine == 'gecko' else ''} - " - derive_value_info="False" - spec="https://drafts.csswg.org/css-fonts-3/#propdef-font" -> - use crate::computed_values::font_variant_caps::T::SmallCaps; - use crate::parser::Parse; - use crate::properties::longhands::{font_family, font_style, font_weight, font_stretch}; - #[cfg(feature = "gecko")] - use crate::properties::longhands::font_size; - use crate::properties::longhands::font_variant_caps; - use crate::values::specified::text::LineHeight; - use crate::values::specified::{FontSize, FontWeight}; - use crate::values::specified::font::{FontStretch, FontStretchKeyword}; - #[cfg(feature = "gecko")] - use crate::values::specified::font::SystemFont; - - <% - gecko_sub_properties = "kerning language_override size_adjust \ - variant_alternates variant_east_asian \ - variant_emoji variant_ligatures \ - variant_numeric variant_position \ - feature_settings variation_settings \ - optical_sizing".split() - %> - % if engine == "gecko": - % for prop in gecko_sub_properties: - use crate::properties::longhands::font_${prop}; - % endfor - % endif - use self::font_family::SpecifiedValue as FontFamily; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut nb_normals = 0; - let mut style = None; - let mut variant_caps = None; - let mut weight = None; - let mut stretch = None; - let size; - % if engine == "gecko": - if let Ok(sys) = input.try_parse(|i| SystemFont::parse(context, i)) { - return Ok(expanded! { - % for name in SYSTEM_FONT_LONGHANDS: - ${name}: ${name}::SpecifiedValue::system_font(sys), - % endfor - line_height: LineHeight::normal(), - % for name in gecko_sub_properties + ["variant_caps"]: - font_${name}: font_${name}::get_initial_specified_value(), - % endfor - }) - } - % endif - loop { - // Special-case 'normal' because it is valid in each of - // font-style, font-weight, font-variant and font-stretch. - // Leaves the values to None, 'normal' is the initial value for each of them. - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - nb_normals += 1; - continue; - } - if style.is_none() { - if let Ok(value) = input.try_parse(|input| font_style::parse(context, input)) { - style = Some(value); - continue - } - } - if weight.is_none() { - if let Ok(value) = input.try_parse(|input| font_weight::parse(context, input)) { - weight = Some(value); - continue - } - } - if variant_caps.is_none() { - // The only variant-caps value allowed is small-caps (from CSS2); the added values - // defined by CSS Fonts 3 and later are not accepted. - // https://www.w3.org/TR/css-fonts-4/#font-prop - if input.try_parse(|input| input.expect_ident_matching("small-caps")).is_ok() { - variant_caps = Some(SmallCaps); - continue - } - } - if stretch.is_none() { - if let Ok(value) = input.try_parse(FontStretchKeyword::parse) { - stretch = Some(FontStretch::Keyword(value)); - continue - } - } - size = Some(FontSize::parse(context, input)?); - break - } - - let size = match size { - Some(s) => s, - None => { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - }; - - let line_height = if input.try_parse(|input| input.expect_delim('/')).is_ok() { - Some(LineHeight::parse(context, input)?) - } else { - None - }; - - #[inline] - fn count<T>(opt: &Option<T>) -> u8 { - if opt.is_some() { 1 } else { 0 } - } - - if (count(&style) + count(&weight) + count(&variant_caps) + count(&stretch) + nb_normals) > 4 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - let family = FontFamily::parse(context, input)?; - Ok(expanded! { - % for name in "style weight stretch variant_caps".split(): - font_${name}: unwrap_or_initial!(font_${name}, ${name}), - % endfor - font_size: size, - line_height: line_height.unwrap_or(LineHeight::normal()), - font_family: family, - % if engine == "gecko": - % for name in gecko_sub_properties: - font_${name}: font_${name}::get_initial_specified_value(), - % endfor - % endif - }) - } - - % if engine == "gecko": - enum CheckSystemResult { - AllSystem(SystemFont), - SomeSystem, - None - } - % endif - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - % if engine == "gecko": - match self.check_system() { - CheckSystemResult::AllSystem(sys) => return sys.to_css(dest), - CheckSystemResult::SomeSystem => return Ok(()), - CheckSystemResult::None => {} - } - % endif - - % if engine == "gecko": - if let Some(v) = self.font_optical_sizing { - if v != &font_optical_sizing::get_initial_specified_value() { - return Ok(()); - } - } - if let Some(v) = self.font_variation_settings { - if v != &font_variation_settings::get_initial_specified_value() { - return Ok(()); - } - } - if let Some(v) = self.font_variant_emoji { - if v != &font_variant_emoji::get_initial_specified_value() { - return Ok(()); - } - } - - % for name in gecko_sub_properties: - % if name != "optical_sizing" and name != "variation_settings" and name != "variant_emoji": - if self.font_${name} != &font_${name}::get_initial_specified_value() { - return Ok(()); - } - % endif - % endfor - % endif - - // Only font-stretch keywords are allowed as part as the font - // shorthand. - let font_stretch = match *self.font_stretch { - FontStretch::Keyword(kw) => kw, - FontStretch::Stretch(percentage) => { - match FontStretchKeyword::from_percentage(percentage.0.get()) { - Some(kw) => kw, - None => return Ok(()), - } - } - FontStretch::System(..) => return Ok(()), - }; - - // The only variant-caps value allowed in the shorthand is small-caps (from CSS2); - // the added values defined by CSS Fonts 3 and later are not supported. - // https://www.w3.org/TR/css-fonts-4/#font-prop - if self.font_variant_caps != &font_variant_caps::get_initial_specified_value() && - *self.font_variant_caps != SmallCaps { - return Ok(()); - } - - % for name in "style variant_caps".split(): - if self.font_${name} != &font_${name}::get_initial_specified_value() { - self.font_${name}.to_css(dest)?; - dest.write_char(' ')?; - } - % endfor - - // The initial specified font-weight value of 'normal' computes as a number (400), - // not to the keyword, so we need to check for that as well in order to properly - // serialize the computed style. - if self.font_weight != &FontWeight::normal() && - self.font_weight != &FontWeight::from_gecko_keyword(400) { - self.font_weight.to_css(dest)?; - dest.write_char(' ')?; - } - - if font_stretch != FontStretchKeyword::Normal { - font_stretch.to_css(dest)?; - dest.write_char(' ')?; - } - - self.font_size.to_css(dest)?; - - if *self.line_height != LineHeight::normal() { - dest.write_str(" / ")?; - self.line_height.to_css(dest)?; - } - - dest.write_char(' ')?; - self.font_family.to_css(dest)?; - - Ok(()) - } - } - - impl<'a> LonghandsToSerialize<'a> { - % if engine == "gecko": - /// Check if some or all members are system fonts - fn check_system(&self) -> CheckSystemResult { - let mut sys = None; - let mut all = true; - - % for prop in SYSTEM_FONT_LONGHANDS: - % if prop == "font_optical_sizing" or prop == "font_variation_settings": - if let Some(value) = self.${prop} { - % else: - { - let value = self.${prop}; - % endif - match value.get_system() { - Some(s) => { - debug_assert!(sys.is_none() || s == sys.unwrap()); - sys = Some(s); - } - None => { - all = false; - } - } - } - % endfor - if self.line_height != &LineHeight::normal() { - all = false - } - if all { - CheckSystemResult::AllSystem(sys.unwrap()) - } else if sys.is_some() { - CheckSystemResult::SomeSystem - } else { - CheckSystemResult::None - } - } - % endif - } - - <% - subprops_for_value_info = ["font_style", "font_weight", "font_stretch", - "font_variant_caps", "font_size", "font_family"] - subprops_for_value_info = [ - "<longhands::{}::SpecifiedValue as SpecifiedValueInfo>".format(p) - for p in subprops_for_value_info - ] - %> - impl SpecifiedValueInfo for Longhands { - const SUPPORTED_TYPES: u8 = 0 - % for p in subprops_for_value_info: - | ${p}::SUPPORTED_TYPES - % endfor - ; - - fn collect_completion_keywords(f: KeywordsCollectFn) { - % for p in subprops_for_value_info: - ${p}::collect_completion_keywords(f); - % endfor - % if engine == "gecko": - <SystemFont as SpecifiedValueInfo>::collect_completion_keywords(f); - % endif - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="font-variant" - engines="gecko servo" - servo_pref="layout.legacy_layout", - flags="SHORTHAND_IN_GETCS" - sub_properties="font-variant-caps - ${'font-variant-alternates' if engine == 'gecko' else ''} - ${'font-variant-east-asian' if engine == 'gecko' else ''} - ${'font-variant-emoji' if engine == 'gecko' else ''} - ${'font-variant-ligatures' if engine == 'gecko' else ''} - ${'font-variant-numeric' if engine == 'gecko' else ''} - ${'font-variant-position' if engine == 'gecko' else ''}" - spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> -% if engine == 'gecko': - <% sub_properties = "ligatures caps alternates numeric east_asian position emoji".split() %> -% else: - <% sub_properties = ["caps"] %> -% endif - -% for prop in sub_properties: - use crate::properties::longhands::font_variant_${prop}; -% endfor - #[allow(unused_imports)] - use crate::values::specified::FontVariantLigatures; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - % for prop in sub_properties: - let mut ${prop} = None; - % endfor - - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() { - // Leave the values to None, 'normal' is the initial value for all the sub properties. - } else if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - // The 'none' value sets 'font-variant-ligatures' to 'none' and resets all other sub properties - // to their initial value. - % if engine == "gecko": - ligatures = Some(FontVariantLigatures::NONE); - % endif - } else { - let mut has_custom_value: bool = false; - loop { - if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() || - input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - % for prop in sub_properties: - if ${prop}.is_none() { - if let Ok(value) = input.try_parse(|i| font_variant_${prop}::parse(context, i)) { - has_custom_value = true; - ${prop} = Some(value); - continue - } - } - % endfor - - break - } - - if !has_custom_value { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } - - Ok(expanded! { - % for prop in sub_properties: - font_variant_${prop}: unwrap_or_initial!(font_variant_${prop}, ${prop}), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - #[allow(unused_assignments)] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - - let has_none_ligatures = - % if engine == "gecko": - self.font_variant_ligatures == &FontVariantLigatures::NONE; - % else: - false; - % endif - - const TOTAL_SUBPROPS: usize = ${len(sub_properties)}; - let mut nb_normals = 0; - % for prop in sub_properties: - % if prop == "emoji": - if let Some(value) = self.font_variant_${prop} { - % else: - { - let value = self.font_variant_${prop}; - % endif - if value == &font_variant_${prop}::get_initial_specified_value() { - nb_normals += 1; - } - } - % if prop == "emoji": - else { - // The property was disabled, so we count it as 'normal' for the purpose - // of deciding how the shorthand can be serialized. - nb_normals += 1; - } - % endif - % endfor - - - if nb_normals > 0 && nb_normals == TOTAL_SUBPROPS { - dest.write_str("normal")?; - } else if has_none_ligatures { - if nb_normals == TOTAL_SUBPROPS - 1 { - // Serialize to 'none' if 'font-variant-ligatures' is set to 'none' and all other - // font feature properties are reset to their initial value. - dest.write_str("none")?; - } else { - return Ok(()) - } - } else { - let mut has_any = false; - % for prop in sub_properties: - % if prop == "emoji": - if let Some(value) = self.font_variant_${prop} { - % else: - { - let value = self.font_variant_${prop}; - % endif - if value != &font_variant_${prop}::get_initial_specified_value() { - if has_any { - dest.write_char(' ')?; - } - has_any = true; - value.to_css(dest)?; - } - } - % endfor - } - - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="font-synthesis" - engines="gecko" - flags="SHORTHAND_IN_GETCS" - sub_properties="font-synthesis-weight font-synthesis-style font-synthesis-small-caps" - derive_value_info="False" - spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-variant"> - <% sub_properties = ["weight", "style", "small_caps"] %> - - use crate::values::specified::FontSynthesis; - - pub fn parse_value<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - % for prop in sub_properties: - let mut ${prop} = FontSynthesis::None; - % endfor - - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - // Leave all the individual values as None - } else { - let mut has_custom_value = false; - while !input.is_exhausted() { - try_match_ident_ignore_ascii_case! { input, - % for prop in sub_properties: - "${prop.replace('_', '-')}" if ${prop} == FontSynthesis::None => { - has_custom_value = true; - ${prop} = FontSynthesis::Auto; - continue; - }, - % endfor - } - } - if !has_custom_value { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - - Ok(expanded! { - % for prop in sub_properties: - font_synthesis_${prop}: ${prop}, - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let mut has_any = false; - - % for prop in sub_properties: - if self.font_synthesis_${prop} == &FontSynthesis::Auto { - if has_any { - dest.write_char(' ')?; - } - has_any = true; - dest.write_str("${prop.replace('_', '-')}")?; - } - % endfor - - if !has_any { - dest.write_str("none")?; - } - - Ok(()) - } - } - - // The shorthand takes the sub-property names of the longhands, and not the - // 'auto' keyword like they do, so we can't automatically derive this. - impl SpecifiedValueInfo for Longhands { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&[ - "none", - % for prop in sub_properties: - "${prop.replace('_', '-')}", - % endfor - ]); - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/inherited_svg.mako.rs b/components/style/properties/shorthands/inherited_svg.mako.rs deleted file mode 100644 index 899fc6a4643..00000000000 --- a/components/style/properties/shorthands/inherited_svg.mako.rs +++ /dev/null @@ -1,38 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand - name="marker" - engines="gecko" - sub_properties="marker-start marker-end marker-mid" - spec="https://www.w3.org/TR/SVG2/painting.html#MarkerShorthand" -> - use crate::values::specified::url::UrlOrNone; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::parser::Parse; - let url = UrlOrNone::parse(context, input)?; - - Ok(expanded! { - marker_start: url.clone(), - marker_mid: url.clone(), - marker_end: url, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if self.marker_start == self.marker_mid && self.marker_mid == self.marker_end { - self.marker_start.to_css(dest) - } else { - Ok(()) - } - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/inherited_text.mako.rs b/components/style/properties/shorthands/inherited_text.mako.rs deleted file mode 100644 index 9eb278da05c..00000000000 --- a/components/style/properties/shorthands/inherited_text.mako.rs +++ /dev/null @@ -1,91 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand - name="text-emphasis" - engines="gecko" - sub_properties="text-emphasis-style text-emphasis-color" - derive_serialize="True" - spec="https://drafts.csswg.org/css-text-decor-3/#text-emphasis-property" -> - use crate::properties::longhands::{text_emphasis_color, text_emphasis_style}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut color = None; - let mut style = None; - - loop { - if color.is_none() { - if let Ok(value) = input.try_parse(|input| text_emphasis_color::parse(context, input)) { - color = Some(value); - continue - } - } - if style.is_none() { - if let Ok(value) = input.try_parse(|input| text_emphasis_style::parse(context, input)) { - style = Some(value); - continue - } - } - break - } - if color.is_some() || style.is_some() { - Ok(expanded! { - text_emphasis_color: unwrap_or_initial!(text_emphasis_color, color), - text_emphasis_style: unwrap_or_initial!(text_emphasis_style, style), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -</%helpers:shorthand> - -// CSS Compatibility -// https://compat.spec.whatwg.org/ -<%helpers:shorthand name="-webkit-text-stroke" - engines="gecko" - sub_properties="-webkit-text-stroke-width - -webkit-text-stroke-color" - derive_serialize="True" - spec="https://compat.spec.whatwg.org/#the-webkit-text-stroke"> - use crate::properties::longhands::{_webkit_text_stroke_color, _webkit_text_stroke_width}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut color = None; - let mut width = None; - loop { - if color.is_none() { - if let Ok(value) = input.try_parse(|input| _webkit_text_stroke_color::parse(context, input)) { - color = Some(value); - continue - } - } - - if width.is_none() { - if let Ok(value) = input.try_parse(|input| _webkit_text_stroke_width::parse(context, input)) { - width = Some(value); - continue - } - } - break - } - - if color.is_some() || width.is_some() { - Ok(expanded! { - _webkit_text_stroke_color: unwrap_or_initial!(_webkit_text_stroke_color, color), - _webkit_text_stroke_width: unwrap_or_initial!(_webkit_text_stroke_width, width), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/list.mako.rs b/components/style/properties/shorthands/list.mako.rs deleted file mode 100644 index 183c5ab5daa..00000000000 --- a/components/style/properties/shorthands/list.mako.rs +++ /dev/null @@ -1,152 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand name="list-style" - engines="gecko servo" - sub_properties="list-style-position list-style-image list-style-type" - spec="https://drafts.csswg.org/css-lists/#propdef-list-style"> - use crate::properties::longhands::{list_style_image, list_style_position, list_style_type}; - use crate::values::specified::Image; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - // `none` is ambiguous until we've finished parsing the shorthands, so we count the number - // of times we see it. - let mut nones = 0u8; - let (mut image, mut position, mut list_style_type, mut any) = (None, None, None, false); - loop { - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - nones = nones + 1; - if nones > 2 { - return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("none".into()))) - } - any = true; - continue - } - - if image.is_none() { - if let Ok(value) = input.try_parse(|input| list_style_image::parse(context, input)) { - image = Some(value); - any = true; - continue - } - } - - if position.is_none() { - if let Ok(value) = input.try_parse(|input| list_style_position::parse(context, input)) { - position = Some(value); - any = true; - continue - } - } - - // list-style-type must be checked the last, because it accepts - // arbitrary identifier for custom counter style, and thus may - // affect values of list-style-position. - if list_style_type.is_none() { - if let Ok(value) = input.try_parse(|input| list_style_type::parse(context, input)) { - list_style_type = Some(value); - any = true; - continue - } - } - break - } - - let position = unwrap_or_initial!(list_style_position, position); - - // If there are two `none`s, then we can't have a type or image; if there is one `none`, - // then we can't have both a type *and* an image; if there is no `none` then we're fine as - // long as we parsed something. - use self::list_style_type::SpecifiedValue as ListStyleType; - match (any, nones, list_style_type, image) { - (true, 2, None, None) => { - Ok(expanded! { - list_style_position: position, - list_style_image: Image::None, - list_style_type: ListStyleType::None, - }) - } - (true, 1, None, Some(image)) => { - Ok(expanded! { - list_style_position: position, - list_style_image: image, - list_style_type: ListStyleType::None, - }) - } - (true, 1, Some(list_style_type), None) => { - Ok(expanded! { - list_style_position: position, - list_style_image: Image::None, - list_style_type: list_style_type, - }) - } - (true, 1, None, None) => { - Ok(expanded! { - list_style_position: position, - list_style_image: Image::None, - list_style_type: ListStyleType::None, - }) - } - (true, 0, list_style_type, image) => { - Ok(expanded! { - list_style_position: position, - list_style_image: unwrap_or_initial!(list_style_image, image), - list_style_type: unwrap_or_initial!(list_style_type), - }) - } - _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - use longhands::list_style_position::SpecifiedValue as ListStylePosition; - use longhands::list_style_type::SpecifiedValue as ListStyleType; - use longhands::list_style_image::SpecifiedValue as ListStyleImage; - let mut have_one_non_initial_value = false; - #[cfg(feature = "gecko")] - let position_is_initial = self.list_style_position == &ListStylePosition::Outside; - #[cfg(feature = "servo")] - let position_is_initial = matches!(self.list_style_position, None | Some(&ListStylePosition::Outside)); - if !position_is_initial { - self.list_style_position.to_css(dest)?; - have_one_non_initial_value = true; - } - if self.list_style_image != &ListStyleImage::None { - if have_one_non_initial_value { - dest.write_char(' ')?; - } - self.list_style_image.to_css(dest)?; - have_one_non_initial_value = true; - } - #[cfg(feature = "gecko")] - let type_is_initial = self.list_style_type == &ListStyleType::disc(); - #[cfg(feature = "servo")] - let type_is_initial = self.list_style_type == &ListStyleType::Disc; - if !type_is_initial { - if have_one_non_initial_value { - dest.write_char(' ')?; - } - self.list_style_type.to_css(dest)?; - have_one_non_initial_value = true; - } - if !have_one_non_initial_value { - #[cfg(feature = "gecko")] - self.list_style_position.to_css(dest)?; - #[cfg(feature = "servo")] - if let Some(position) = self.list_style_position { - position.to_css(dest)?; - } else { - self.list_style_type.to_css(dest)?; - } - } - Ok(()) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/margin.mako.rs b/components/style/properties/shorthands/margin.mako.rs deleted file mode 100644 index ba994316a21..00000000000 --- a/components/style/properties/shorthands/margin.mako.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> -<% from data import DEFAULT_RULES_AND_PAGE %> - -${helpers.four_sides_shorthand( - "margin", - "margin-%s", - "specified::LengthPercentageOrAuto::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-box/#propdef-margin", - rule_types_allowed=DEFAULT_RULES_AND_PAGE, - allow_quirks="Yes", -)} - -${helpers.two_properties_shorthand( - "margin-block", - "margin-block-start", - "margin-block-end", - "specified::LengthPercentageOrAuto::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-margin-block" -)} - -${helpers.two_properties_shorthand( - "margin-inline", - "margin-inline-start", - "margin-inline-end", - "specified::LengthPercentageOrAuto::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-margin-inline" -)} - -${helpers.four_sides_shorthand( - "scroll-margin", - "scroll-margin-%s", - "specified::Length::parse", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin", -)} - -${helpers.two_properties_shorthand( - "scroll-margin-block", - "scroll-margin-block-start", - "scroll-margin-block-end", - "specified::Length::parse", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin-block", -)} - -${helpers.two_properties_shorthand( - "scroll-margin-inline", - "scroll-margin-inline-start", - "scroll-margin-inline-end", - "specified::Length::parse", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-margin-inline", -)} diff --git a/components/style/properties/shorthands/outline.mako.rs b/components/style/properties/shorthands/outline.mako.rs deleted file mode 100644 index ff77e1175e2..00000000000 --- a/components/style/properties/shorthands/outline.mako.rs +++ /dev/null @@ -1,80 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand name="outline" - engines="gecko servo" - sub_properties="outline-color outline-style outline-width" - spec="https://drafts.csswg.org/css-ui/#propdef-outline"> - use crate::properties::longhands::{outline_color, outline_width, outline_style}; - use crate::values::specified; - use crate::parser::Parse; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let _unused = context; - let mut color = None; - let mut style = None; - let mut width = None; - let mut any = false; - loop { - if color.is_none() { - if let Ok(value) = input.try_parse(|i| specified::Color::parse(context, i)) { - color = Some(value); - any = true; - continue - } - } - if style.is_none() { - if let Ok(value) = input.try_parse(|input| outline_style::parse(context, input)) { - style = Some(value); - any = true; - continue - } - } - if width.is_none() { - if let Ok(value) = input.try_parse(|input| outline_width::parse(context, input)) { - width = Some(value); - any = true; - continue - } - } - break - } - if any { - Ok(expanded! { - outline_color: unwrap_or_initial!(outline_color, color), - outline_style: unwrap_or_initial!(outline_style, style), - outline_width: unwrap_or_initial!(outline_width, width), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let mut wrote_value = false; - - % for name in "color style width".split(): - if *self.outline_${name} != outline_${name}::get_initial_specified_value() { - if wrote_value { - dest.write_char(' ')?; - } - self.outline_${name}.to_css(dest)?; - wrote_value = true; - } - % endfor - - if !wrote_value { - self.outline_style.to_css(dest)?; - } - - Ok(()) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/padding.mako.rs b/components/style/properties/shorthands/padding.mako.rs deleted file mode 100644 index dad0193708d..00000000000 --- a/components/style/properties/shorthands/padding.mako.rs +++ /dev/null @@ -1,58 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -${helpers.four_sides_shorthand( - "padding", - "padding-%s", - "specified::NonNegativeLengthPercentage::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-box-3/#propdef-padding", - allow_quirks="Yes", -)} - -${helpers.two_properties_shorthand( - "padding-block", - "padding-block-start", - "padding-block-end", - "specified::NonNegativeLengthPercentage::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-padding-block" -)} - -${helpers.two_properties_shorthand( - "padding-inline", - "padding-inline-start", - "padding-inline-end", - "specified::NonNegativeLengthPercentage::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-padding-inline" -)} - -${helpers.four_sides_shorthand( - "scroll-padding", - "scroll-padding-%s", - "specified::NonNegativeLengthPercentageOrAuto::parse", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding" -)} - -${helpers.two_properties_shorthand( - "scroll-padding-block", - "scroll-padding-block-start", - "scroll-padding-block-end", - "specified::NonNegativeLengthPercentageOrAuto::parse", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-block" -)} - -${helpers.two_properties_shorthand( - "scroll-padding-inline", - "scroll-padding-inline-start", - "scroll-padding-inline-end", - "specified::NonNegativeLengthPercentageOrAuto::parse", - engines="gecko", - spec="https://drafts.csswg.org/css-scroll-snap-1/#propdef-scroll-padding-inline" -)} diff --git a/components/style/properties/shorthands/position.mako.rs b/components/style/properties/shorthands/position.mako.rs deleted file mode 100644 index 6d2b47d345f..00000000000 --- a/components/style/properties/shorthands/position.mako.rs +++ /dev/null @@ -1,893 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand name="flex-flow" - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - sub_properties="flex-direction flex-wrap" - extra_prefixes="webkit" - spec="https://drafts.csswg.org/css-flexbox/#flex-flow-property"> - use crate::properties::longhands::{flex_direction, flex_wrap}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut direction = None; - let mut wrap = None; - loop { - if direction.is_none() { - if let Ok(value) = input.try_parse(|input| flex_direction::parse(context, input)) { - direction = Some(value); - continue - } - } - if wrap.is_none() { - if let Ok(value) = input.try_parse(|input| flex_wrap::parse(context, input)) { - wrap = Some(value); - continue - } - } - break - } - - if direction.is_none() && wrap.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - Ok(expanded! { - flex_direction: unwrap_or_initial!(flex_direction, direction), - flex_wrap: unwrap_or_initial!(flex_wrap, wrap), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if *self.flex_direction == flex_direction::get_initial_specified_value() && - *self.flex_wrap != flex_wrap::get_initial_specified_value() { - return self.flex_wrap.to_css(dest) - } - self.flex_direction.to_css(dest)?; - if *self.flex_wrap != flex_wrap::get_initial_specified_value() { - dest.write_char(' ')?; - self.flex_wrap.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="flex" - engines="gecko servo", - servo_pref="layout.flexbox.enabled", - sub_properties="flex-grow flex-shrink flex-basis" - extra_prefixes="webkit" - derive_serialize="True" - spec="https://drafts.csswg.org/css-flexbox/#flex-property"> - use crate::parser::Parse; - use crate::values::specified::NonNegativeNumber; - use crate::properties::longhands::flex_basis::SpecifiedValue as FlexBasis; - - fn parse_flexibility<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<(NonNegativeNumber, Option<NonNegativeNumber>),ParseError<'i>> { - let grow = NonNegativeNumber::parse(context, input)?; - let shrink = input.try_parse(|i| NonNegativeNumber::parse(context, i)).ok(); - Ok((grow, shrink)) - } - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut grow = None; - let mut shrink = None; - let mut basis = None; - - if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { - return Ok(expanded! { - flex_grow: NonNegativeNumber::new(0.0), - flex_shrink: NonNegativeNumber::new(0.0), - flex_basis: FlexBasis::auto(), - }) - } - loop { - if grow.is_none() { - if let Ok((flex_grow, flex_shrink)) = input.try_parse(|i| parse_flexibility(context, i)) { - grow = Some(flex_grow); - shrink = flex_shrink; - continue - } - } - if basis.is_none() { - if let Ok(value) = input.try_parse(|input| FlexBasis::parse(context, input)) { - basis = Some(value); - continue - } - } - break - } - - if grow.is_none() && basis.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - Ok(expanded! { - flex_grow: grow.unwrap_or(NonNegativeNumber::new(1.0)), - flex_shrink: shrink.unwrap_or(NonNegativeNumber::new(1.0)), - // Per spec, this should be SpecifiedValue::zero(), but all - // browsers currently agree on using `0%`. This is a spec - // change which hasn't been adopted by browsers: - // https://github.com/w3c/csswg-drafts/commit/2c446befdf0f686217905bdd7c92409f6bd3921b - flex_basis: basis.unwrap_or(FlexBasis::zero_percent()), - }) - } -</%helpers:shorthand> - -<%helpers:shorthand - name="gap" - engines="gecko" - aliases="grid-gap" - sub_properties="row-gap column-gap" - spec="https://drafts.csswg.org/css-align-3/#gap-shorthand" -> - use crate::properties::longhands::{row_gap, column_gap}; - - pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) - -> Result<Longhands, ParseError<'i>> { - let r_gap = row_gap::parse(context, input)?; - let c_gap = input.try_parse(|input| column_gap::parse(context, input)).unwrap_or(r_gap.clone()); - - Ok(expanded! { - row_gap: r_gap, - column_gap: c_gap, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if self.row_gap == self.column_gap { - self.row_gap.to_css(dest) - } else { - self.row_gap.to_css(dest)?; - dest.write_char(' ')?; - self.column_gap.to_css(dest) - } - } - } - -</%helpers:shorthand> - -% for kind in ["row", "column"]: -<%helpers:shorthand - name="grid-${kind}" - sub_properties="grid-${kind}-start grid-${kind}-end" - engines="gecko", - spec="https://drafts.csswg.org/css-grid/#propdef-grid-${kind}" -> - use crate::values::specified::GridLine; - use crate::parser::Parse; - use crate::Zero; - - // NOTE: Since both the shorthands have the same code, we should (re-)use code from one to implement - // the other. This might not be a big deal for now, but we should consider looking into this in the future - // to limit the amount of code generated. - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let start = input.try_parse(|i| GridLine::parse(context, i))?; - let end = if input.try_parse(|i| i.expect_delim('/')).is_ok() { - GridLine::parse(context, input)? - } else { - let mut line = GridLine::auto(); - if start.line_num.is_zero() && !start.is_span { - line.ident = start.ident.clone(); // ident from start value should be taken - } - - line - }; - - Ok(expanded! { - grid_${kind}_start: start, - grid_${kind}_end: end, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - // Return the shortest possible serialization of the `grid-${kind}-[start/end]` values. - // This function exploits the opportunities to omit the end value per this spec text: - // - // https://drafts.csswg.org/css-grid/#propdef-grid-column - // "When the second value is omitted, if the first value is a <custom-ident>, - // the grid-row-end/grid-column-end longhand is also set to that <custom-ident>; - // otherwise, it is set to auto." - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.grid_${kind}_start.to_css(dest)?; - if self.grid_${kind}_start.can_omit(self.grid_${kind}_end) { - return Ok(()); // the end value is redundant - } - dest.write_str(" / ")?; - self.grid_${kind}_end.to_css(dest) - } - } -</%helpers:shorthand> -% endfor - -<%helpers:shorthand - name="grid-area" - engines="gecko" - sub_properties="grid-row-start grid-row-end grid-column-start grid-column-end" - spec="https://drafts.csswg.org/css-grid/#propdef-grid-area" -> - use crate::values::specified::GridLine; - use crate::parser::Parse; - use crate::Zero; - - // The code is the same as `grid-{row,column}` except that this can have four values at most. - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - fn line_with_ident_from(other: &GridLine) -> GridLine { - let mut this = GridLine::auto(); - if other.line_num.is_zero() && !other.is_span { - this.ident = other.ident.clone(); - } - - this - } - - let row_start = input.try_parse(|i| GridLine::parse(context, i))?; - let (column_start, row_end, column_end) = if input.try_parse(|i| i.expect_delim('/')).is_ok() { - let column_start = GridLine::parse(context, input)?; - let (row_end, column_end) = if input.try_parse(|i| i.expect_delim('/')).is_ok() { - let row_end = GridLine::parse(context, input)?; - let column_end = if input.try_parse(|i| i.expect_delim('/')).is_ok() { - GridLine::parse(context, input)? - } else { // grid-column-end has not been given - line_with_ident_from(&column_start) - }; - - (row_end, column_end) - } else { // grid-row-start and grid-column-start has been given - let row_end = line_with_ident_from(&row_start); - let column_end = line_with_ident_from(&column_start); - (row_end, column_end) - }; - - (column_start, row_end, column_end) - } else { // only grid-row-start is given - let line = line_with_ident_from(&row_start); - (line.clone(), line.clone(), line) - }; - - Ok(expanded! { - grid_row_start: row_start, - grid_row_end: row_end, - grid_column_start: column_start, - grid_column_end: column_end, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - // Return the shortest possible serialization of the `grid-[column/row]-[start/end]` values. - // This function exploits the opportunities to omit trailing values per this spec text: - // - // https://drafts.csswg.org/css-grid/#propdef-grid-area - // "If four <grid-line> values are specified, grid-row-start is set to the first value, - // grid-column-start is set to the second value, grid-row-end is set to the third value, - // and grid-column-end is set to the fourth value. - // - // When grid-column-end is omitted, if grid-column-start is a <custom-ident>, - // grid-column-end is set to that <custom-ident>; otherwise, it is set to auto. - // - // When grid-row-end is omitted, if grid-row-start is a <custom-ident>, grid-row-end is - // set to that <custom-ident>; otherwise, it is set to auto. - // - // When grid-column-start is omitted, if grid-row-start is a <custom-ident>, all four - // longhands are set to that value. Otherwise, it is set to auto." - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.grid_row_start.to_css(dest)?; - let mut trailing_values = 3; - if self.grid_column_start.can_omit(self.grid_column_end) { - trailing_values -= 1; - if self.grid_row_start.can_omit(self.grid_row_end) { - trailing_values -= 1; - if self.grid_row_start.can_omit(self.grid_column_start) { - trailing_values -= 1; - } - } - } - let values = [&self.grid_column_start, &self.grid_row_end, &self.grid_column_end]; - for value in values.iter().take(trailing_values) { - dest.write_str(" / ")?; - value.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="grid-template" - engines="gecko" - sub_properties="grid-template-rows grid-template-columns grid-template-areas" - spec="https://drafts.csswg.org/css-grid/#propdef-grid-template" -> - use crate::parser::Parse; - use servo_arc::Arc; - use crate::values::generics::grid::{TrackSize, TrackList}; - use crate::values::generics::grid::{TrackListValue, concat_serialize_idents}; - use crate::values::specified::{GridTemplateComponent, GenericGridTemplateComponent}; - use crate::values::specified::grid::parse_line_names; - use crate::values::specified::position::{GridTemplateAreas, TemplateAreas, TemplateAreasArc}; - - /// Parsing for `<grid-template>` shorthand (also used by `grid` shorthand). - pub fn parse_grid_template<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<(GridTemplateComponent, GridTemplateComponent, GridTemplateAreas), ParseError<'i>> { - // Other shorthand sub properties also parse the `none` keyword and this shorthand - // should know after this keyword there is nothing to parse. Otherwise it gets - // confused and rejects the sub properties that contains `none`. - <% keywords = { - "none": "GenericGridTemplateComponent::None", - } - %> - % for keyword, rust_type in keywords.items(): - if let Ok(x) = input.try_parse(|i| { - if i.try_parse(|i| i.expect_ident_matching("${keyword}")).is_ok() { - if !i.is_exhausted() { - return Err(()); - } - return Ok((${rust_type}, ${rust_type}, GridTemplateAreas::None)); - } - Err(()) - }) { - return Ok(x); - } - % endfor - - let first_line_names = input.try_parse(parse_line_names).unwrap_or_default(); - if let Ok(string) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned().into())) { - let mut strings = vec![]; - let mut values = vec![]; - let mut line_names = vec![]; - line_names.push(first_line_names); - strings.push(string); - loop { - let size = input.try_parse(|i| TrackSize::parse(context, i)).unwrap_or_default(); - values.push(TrackListValue::TrackSize(size)); - let mut names = input.try_parse(parse_line_names).unwrap_or_default(); - let more_names = input.try_parse(parse_line_names); - - match input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned().into())) { - Ok(string) => { - strings.push(string); - if let Ok(v) = more_names { - // We got `[names] [more_names] "string"` - merge the two name lists. - let mut names_vec = names.into_vec(); - names_vec.extend(v.into_iter()); - names = names_vec.into(); - } - line_names.push(names); - }, - Err(e) => { - if more_names.is_ok() { - // We've parsed `"string" [names] [more_names]` but then failed to parse another `"string"`. - // The grammar doesn't allow two trailing `<line-names>` so this is an invalid value. - return Err(e.into()); - } - // only the named area determines whether we should bail out - line_names.push(names); - break - }, - }; - } - - if line_names.len() == values.len() { - // should be one longer than track sizes - line_names.push(Default::default()); - } - - let template_areas = TemplateAreas::from_vec(strings) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?; - let template_rows = TrackList { - values: values.into(), - line_names: line_names.into(), - auto_repeat_index: std::usize::MAX, - }; - - let template_cols = if input.try_parse(|i| i.expect_delim('/')).is_ok() { - let value = GridTemplateComponent::parse_without_none(context, input)?; - if let GenericGridTemplateComponent::TrackList(ref list) = value { - if !list.is_explicit() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } - - value - } else { - GridTemplateComponent::default() - }; - - Ok(( - GenericGridTemplateComponent::TrackList(Box::new(template_rows)), - template_cols, - GridTemplateAreas::Areas(TemplateAreasArc(Arc::new(template_areas))) - )) - } else { - let mut template_rows = GridTemplateComponent::parse(context, input)?; - if let GenericGridTemplateComponent::TrackList(ref mut list) = template_rows { - // Fist line names are parsed already and it shouldn't be parsed again. - // If line names are not empty, that means given property value is not acceptable - if list.line_names[0].is_empty() { - list.line_names[0] = first_line_names; // won't panic - } else { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - - input.expect_delim('/')?; - Ok((template_rows, GridTemplateComponent::parse(context, input)?, GridTemplateAreas::None)) - } - } - - #[inline] - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let (rows, columns, areas) = parse_grid_template(context, input)?; - Ok(expanded! { - grid_template_rows: rows, - grid_template_columns: columns, - grid_template_areas: areas, - }) - } - - /// Serialization for `<grid-template>` shorthand (also used by `grid` shorthand). - pub fn serialize_grid_template<W>( - template_rows: &GridTemplateComponent, - template_columns: &GridTemplateComponent, - template_areas: &GridTemplateAreas, - dest: &mut CssWriter<W>, - ) -> fmt::Result - where - W: Write { - match *template_areas { - GridTemplateAreas::None => { - if template_rows.is_initial() && template_columns.is_initial() { - return GridTemplateComponent::default().to_css(dest); - } - template_rows.to_css(dest)?; - dest.write_str(" / ")?; - template_columns.to_css(dest) - }, - GridTemplateAreas::Areas(ref areas) => { - // The length of template-area and template-rows values should be equal. - if areas.0.strings.len() != template_rows.track_list_len() { - return Ok(()); - } - - let track_list = match *template_rows { - GenericGridTemplateComponent::TrackList(ref list) => { - // We should fail if there is a `repeat` function. - // `grid` and `grid-template` shorthands doesn't accept - // that. Only longhand accepts. - if !list.is_explicit() { - return Ok(()); - } - list - }, - // Others template components shouldn't exist with normal shorthand values. - // But if we need to serialize a group of longhand sub-properties for - // the shorthand, we should be able to return empty string instead of crashing. - _ => return Ok(()), - }; - - // We need to check some values that longhand accepts but shorthands don't. - match *template_columns { - // We should fail if there is a `repeat` function. `grid` and - // `grid-template` shorthands doesn't accept that. Only longhand accepts that. - GenericGridTemplateComponent::TrackList(ref list) => { - if !list.is_explicit() { - return Ok(()); - } - }, - // Also the shorthands don't accept subgrids unlike longhand. - // We should fail without an error here. - GenericGridTemplateComponent::Subgrid(_) => { - return Ok(()); - }, - _ => {}, - } - - let mut names_iter = track_list.line_names.iter(); - for (((i, string), names), value) in areas.0.strings.iter().enumerate() - .zip(&mut names_iter) - .zip(track_list.values.iter()) { - if i > 0 { - dest.write_char(' ')?; - } - - if !names.is_empty() { - concat_serialize_idents("[", "] ", names, " ", dest)?; - } - - string.to_css(dest)?; - - // If the track size is the initial value then it's redundant here. - if !value.is_initial() { - dest.write_char(' ')?; - value.to_css(dest)?; - } - } - - if let Some(names) = names_iter.next() { - concat_serialize_idents(" [", "]", names, " ", dest)?; - } - - if let GenericGridTemplateComponent::TrackList(ref list) = *template_columns { - dest.write_str(" / ")?; - list.to_css(dest)?; - } - - Ok(()) - }, - } - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - serialize_grid_template( - self.grid_template_rows, - self.grid_template_columns, - self.grid_template_areas, - dest - ) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="grid" - engines="gecko" - sub_properties="grid-template-rows grid-template-columns grid-template-areas - grid-auto-rows grid-auto-columns grid-auto-flow" - spec="https://drafts.csswg.org/css-grid/#propdef-grid" -> - use crate::parser::Parse; - use crate::properties::longhands::{grid_auto_columns, grid_auto_rows, grid_auto_flow}; - use crate::values::generics::grid::GridTemplateComponent; - use crate::values::specified::{GenericGridTemplateComponent, ImplicitGridTracks}; - use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas}; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let mut temp_rows = GridTemplateComponent::default(); - let mut temp_cols = GridTemplateComponent::default(); - let mut temp_areas = GridTemplateAreas::None; - let mut auto_rows = ImplicitGridTracks::default(); - let mut auto_cols = ImplicitGridTracks::default(); - let mut flow = grid_auto_flow::get_initial_value(); - - fn parse_auto_flow<'i, 't>( - input: &mut Parser<'i, 't>, - is_row: bool, - ) -> Result<GridAutoFlow, ParseError<'i>> { - let mut track = None; - let mut dense = GridAutoFlow::empty(); - - for _ in 0..2 { - if input.try_parse(|i| i.expect_ident_matching("auto-flow")).is_ok() { - track = if is_row { - Some(GridAutoFlow::ROW) - } else { - Some(GridAutoFlow::COLUMN) - }; - } else if input.try_parse(|i| i.expect_ident_matching("dense")).is_ok() { - dense = GridAutoFlow::DENSE - } else { - break - } - } - - if track.is_some() { - Ok(track.unwrap() | dense) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } - - if let Ok((rows, cols, areas)) = input.try_parse(|i| super::grid_template::parse_grid_template(context, i)) { - temp_rows = rows; - temp_cols = cols; - temp_areas = areas; - } else if let Ok(rows) = input.try_parse(|i| GridTemplateComponent::parse(context, i)) { - temp_rows = rows; - input.expect_delim('/')?; - flow = parse_auto_flow(input, false)?; - auto_cols = input.try_parse(|i| grid_auto_columns::parse(context, i)).unwrap_or_default(); - } else { - flow = parse_auto_flow(input, true)?; - auto_rows = input.try_parse(|i| grid_auto_rows::parse(context, i)).unwrap_or_default(); - input.expect_delim('/')?; - temp_cols = GridTemplateComponent::parse(context, input)?; - } - - Ok(expanded! { - grid_template_rows: temp_rows, - grid_template_columns: temp_cols, - grid_template_areas: temp_areas, - grid_auto_rows: auto_rows, - grid_auto_columns: auto_cols, - grid_auto_flow: flow, - }) - } - - impl<'a> LonghandsToSerialize<'a> { - /// Returns true if other sub properties except template-{rows,columns} are initial. - fn is_grid_template(&self) -> bool { - self.grid_auto_rows.is_initial() && - self.grid_auto_columns.is_initial() && - *self.grid_auto_flow == grid_auto_flow::get_initial_value() - } - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - if self.is_grid_template() { - return super::grid_template::serialize_grid_template( - self.grid_template_rows, - self.grid_template_columns, - self.grid_template_areas, - dest - ); - } - - if *self.grid_template_areas != GridTemplateAreas::None { - // No other syntax can set the template areas, so fail to - // serialize. - return Ok(()); - } - - if self.grid_auto_flow.contains(GridAutoFlow::COLUMN) { - // It should fail to serialize if other branch of the if condition's values are set. - if !self.grid_auto_rows.is_initial() || - !self.grid_template_columns.is_initial() { - return Ok(()); - } - - // It should fail to serialize if template-rows value is not Explicit. - if let GenericGridTemplateComponent::TrackList(ref list) = *self.grid_template_rows { - if !list.is_explicit() { - return Ok(()); - } - } - - self.grid_template_rows.to_css(dest)?; - dest.write_str(" / auto-flow")?; - if self.grid_auto_flow.contains(GridAutoFlow::DENSE) { - dest.write_str(" dense")?; - } - - if !self.grid_auto_columns.is_initial() { - dest.write_char(' ')?; - self.grid_auto_columns.to_css(dest)?; - } - - return Ok(()); - } - - // It should fail to serialize if other branch of the if condition's values are set. - if !self.grid_auto_columns.is_initial() || - !self.grid_template_rows.is_initial() { - return Ok(()); - } - - // It should fail to serialize if template-column value is not Explicit. - if let GenericGridTemplateComponent::TrackList(ref list) = *self.grid_template_columns { - if !list.is_explicit() { - return Ok(()); - } - } - - dest.write_str("auto-flow")?; - if self.grid_auto_flow.contains(GridAutoFlow::DENSE) { - dest.write_str(" dense")?; - } - - if !self.grid_auto_rows.is_initial() { - dest.write_char(' ')?; - self.grid_auto_rows.to_css(dest)?; - } - - dest.write_str(" / ")?; - self.grid_template_columns.to_css(dest)?; - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="place-content" - engines="gecko" - sub_properties="align-content justify-content" - spec="https://drafts.csswg.org/css-align/#propdef-place-content" -> - use crate::values::specified::align::{AlignContent, JustifyContent, ContentDistribution, AxisDirection}; - - pub fn parse_value<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let align_content = - ContentDistribution::parse(input, AxisDirection::Block)?; - - let justify_content = input.try_parse(|input| { - ContentDistribution::parse(input, AxisDirection::Inline) - }); - - let justify_content = match justify_content { - Ok(v) => v, - Err(..) => { - // https://drafts.csswg.org/css-align-3/#place-content: - // - // The second value is assigned to justify-content; if - // omitted, it is copied from the first value, unless that - // value is a <baseline-position> in which case it is - // defaulted to start. - // - if !align_content.is_baseline_position() { - align_content - } else { - ContentDistribution::start() - } - } - }; - - Ok(expanded! { - align_content: AlignContent(align_content), - justify_content: JustifyContent(justify_content), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.align_content.to_css(dest)?; - if self.align_content.0 != self.justify_content.0 { - dest.write_char(' ')?; - self.justify_content.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="place-self" - engines="gecko" - sub_properties="align-self justify-self" - spec="https://drafts.csswg.org/css-align/#place-self-property" -> - use crate::values::specified::align::{AlignSelf, JustifySelf, SelfAlignment, AxisDirection}; - - pub fn parse_value<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let align = SelfAlignment::parse(input, AxisDirection::Block)?; - let justify = input.try_parse(|input| SelfAlignment::parse(input, AxisDirection::Inline)); - - let justify = match justify { - Ok(v) => v, - Err(..) => { - debug_assert!(align.is_valid_on_both_axes()); - align - } - }; - - Ok(expanded! { - align_self: AlignSelf(align), - justify_self: JustifySelf(justify), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.align_self.to_css(dest)?; - if self.align_self.0 != self.justify_self.0 { - dest.write_char(' ')?; - self.justify_self.to_css(dest)?; - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - name="place-items" - engines="gecko" - sub_properties="align-items justify-items" - spec="https://drafts.csswg.org/css-align/#place-items-property" -> - use crate::values::specified::align::{AlignItems, JustifyItems}; - use crate::parser::Parse; - - impl From<AlignItems> for JustifyItems { - fn from(align: AlignItems) -> JustifyItems { - JustifyItems(align.0) - } - } - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - let align = AlignItems::parse(context, input)?; - let justify = - input.try_parse(|input| JustifyItems::parse(context, input)) - .unwrap_or_else(|_| JustifyItems::from(align)); - - Ok(expanded! { - align_items: align, - justify_items: justify, - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - self.align_items.to_css(dest)?; - if self.align_items.0 != self.justify_items.0 { - dest.write_char(' ')?; - self.justify_items.to_css(dest)?; - } - - Ok(()) - } - } -</%helpers:shorthand> - -// See https://github.com/w3c/csswg-drafts/issues/3525 for the quirks stuff. -${helpers.four_sides_shorthand( - "inset", - "%s", - "specified::LengthPercentageOrAuto::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-inset", - allow_quirks="No", -)} - -${helpers.two_properties_shorthand( - "inset-block", - "inset-block-start", - "inset-block-end", - "specified::LengthPercentageOrAuto::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-inset-block" -)} - -${helpers.two_properties_shorthand( - "inset-inline", - "inset-inline-start", - "inset-inline-end", - "specified::LengthPercentageOrAuto::parse", - engines="gecko servo", - spec="https://drafts.csswg.org/css-logical/#propdef-inset-inline" -)} - -${helpers.two_properties_shorthand( - "contain-intrinsic-size", - "contain-intrinsic-width", - "contain-intrinsic-height", - engines="gecko", - gecko_pref="layout.css.contain-intrinsic-size.enabled", - spec="https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override", -)} diff --git a/components/style/properties/shorthands/svg.mako.rs b/components/style/properties/shorthands/svg.mako.rs deleted file mode 100644 index 97cce9ad04d..00000000000 --- a/components/style/properties/shorthands/svg.mako.rs +++ /dev/null @@ -1,258 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand name="mask" engines="gecko" extra_prefixes="webkit" - flags="SHORTHAND_IN_GETCS" - sub_properties="mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x - mask-position-y mask-size mask-image" - spec="https://drafts.fxtf.org/css-masking/#propdef-mask"> - use crate::properties::longhands::{mask_mode, mask_repeat, mask_clip, mask_origin, mask_composite, mask_position_x, - mask_position_y}; - use crate::properties::longhands::{mask_size, mask_image}; - use crate::values::specified::{Position, PositionComponent}; - use crate::parser::Parse; - - // FIXME(emilio): These two mask types should be the same! - impl From<mask_origin::single_value::SpecifiedValue> for mask_clip::single_value::SpecifiedValue { - fn from(origin: mask_origin::single_value::SpecifiedValue) -> mask_clip::single_value::SpecifiedValue { - match origin { - mask_origin::single_value::SpecifiedValue::ContentBox => - mask_clip::single_value::SpecifiedValue::ContentBox, - mask_origin::single_value::SpecifiedValue::PaddingBox => - mask_clip::single_value::SpecifiedValue::PaddingBox , - mask_origin::single_value::SpecifiedValue::BorderBox => - mask_clip::single_value::SpecifiedValue::BorderBox, - % if engine == "gecko": - mask_origin::single_value::SpecifiedValue::FillBox => - mask_clip::single_value::SpecifiedValue::FillBox , - mask_origin::single_value::SpecifiedValue::StrokeBox => - mask_clip::single_value::SpecifiedValue::StrokeBox, - mask_origin::single_value::SpecifiedValue::ViewBox=> - mask_clip::single_value::SpecifiedValue::ViewBox, - % endif - } - } - } - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - % for name in "image mode position_x position_y size repeat origin clip composite".split(): - // Vec grows from 0 to 4 by default on first push(). So allocate with - // capacity 1, so in the common case of only one item we don't way - // overallocate, then shrink. Note that we always push at least one - // item if parsing succeeds. - let mut mask_${name} = Vec::with_capacity(1); - % endfor - - input.parse_comma_separated(|input| { - % for name in "image mode position size repeat origin clip composite".split(): - let mut ${name} = None; - % endfor - loop { - if image.is_none() { - if let Ok(value) = input.try_parse(|input| mask_image::single_value - ::parse(context, input)) { - image = Some(value); - continue - } - } - if position.is_none() { - if let Ok(value) = input.try_parse(|input| Position::parse(context, input)) { - position = Some(value); - - // Parse mask size, if applicable. - size = input.try_parse(|input| { - input.expect_delim('/')?; - mask_size::single_value::parse(context, input) - }).ok(); - - continue - } - } - % for name in "repeat origin clip composite mode".split(): - if ${name}.is_none() { - if let Ok(value) = input.try_parse(|input| mask_${name}::single_value - ::parse(context, input)) { - ${name} = Some(value); - continue - } - } - % endfor - break - } - if clip.is_none() { - if let Some(origin) = origin { - clip = Some(mask_clip::single_value::SpecifiedValue::from(origin)); - } - } - let mut any = false; - % for name in "image mode position size repeat origin clip composite".split(): - any = any || ${name}.is_some(); - % endfor - if any { - if let Some(position) = position { - mask_position_x.push(position.horizontal); - mask_position_y.push(position.vertical); - } else { - mask_position_x.push(PositionComponent::zero()); - mask_position_y.push(PositionComponent::zero()); - } - % for name in "image mode size repeat origin clip composite".split(): - if let Some(m_${name}) = ${name} { - mask_${name}.push(m_${name}); - } else { - mask_${name}.push(mask_${name}::single_value - ::get_initial_specified_value()); - } - % endfor - Ok(()) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - })?; - - Ok(expanded! { - % for name in "image mode position_x position_y size repeat origin clip composite".split(): - mask_${name}: mask_${name}::SpecifiedValue(mask_${name}.into()), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - use crate::properties::longhands::mask_origin::single_value::computed_value::T as Origin; - use crate::properties::longhands::mask_clip::single_value::computed_value::T as Clip; - - let len = self.mask_image.0.len(); - if len == 0 { - return Ok(()); - } - % for name in "mode position_x position_y size repeat origin clip composite".split(): - if self.mask_${name}.0.len() != len { - return Ok(()); - } - % endfor - - for i in 0..len { - if i > 0 { - dest.write_str(", ")?; - } - - % for name in "image mode position_x position_y size repeat origin clip composite".split(): - let ${name} = &self.mask_${name}.0[i]; - % endfor - - image.to_css(dest)?; - - if *mode != mask_mode::single_value::get_initial_specified_value() { - dest.write_char(' ')?; - mode.to_css(dest)?; - } - - if *position_x != PositionComponent::zero() || - *position_y != PositionComponent::zero() || - *size != mask_size::single_value::get_initial_specified_value() - { - dest.write_char(' ')?; - Position { - horizontal: position_x.clone(), - vertical: position_y.clone() - }.to_css(dest)?; - - if *size != mask_size::single_value::get_initial_specified_value() { - dest.write_str(" / ")?; - size.to_css(dest)?; - } - } - - if *repeat != mask_repeat::single_value::get_initial_specified_value() { - dest.write_char(' ')?; - repeat.to_css(dest)?; - } - - if *origin != Origin::BorderBox || *clip != Clip::BorderBox { - dest.write_char(' ')?; - origin.to_css(dest)?; - if *clip != From::from(*origin) { - dest.write_char(' ')?; - clip.to_css(dest)?; - } - } - - if *composite != mask_composite::single_value::get_initial_specified_value() { - dest.write_char(' ')?; - composite.to_css(dest)?; - } - } - - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="mask-position" engines="gecko" extra_prefixes="webkit" - flags="SHORTHAND_IN_GETCS" - sub_properties="mask-position-x mask-position-y" - spec="https://drafts.csswg.org/css-masks-4/#the-mask-position"> - use crate::properties::longhands::{mask_position_x,mask_position_y}; - use crate::values::specified::position::Position; - use crate::parser::Parse; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - // Vec grows from 0 to 4 by default on first push(). So allocate with - // capacity 1, so in the common case of only one item we don't way - // overallocate, then shrink. Note that we always push at least one - // item if parsing succeeds. - let mut position_x = Vec::with_capacity(1); - let mut position_y = Vec::with_capacity(1); - let mut any = false; - - input.parse_comma_separated(|input| { - let value = Position::parse(context, input)?; - position_x.push(value.horizontal); - position_y.push(value.vertical); - any = true; - Ok(()) - })?; - - if !any { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - - Ok(expanded! { - mask_position_x: mask_position_x::SpecifiedValue(position_x.into()), - mask_position_y: mask_position_y::SpecifiedValue(position_y.into()), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let len = self.mask_position_x.0.len(); - if len == 0 || self.mask_position_y.0.len() != len { - return Ok(()); - } - - for i in 0..len { - Position { - horizontal: self.mask_position_x.0[i].clone(), - vertical: self.mask_position_y.0[i].clone() - }.to_css(dest)?; - - if i < len - 1 { - dest.write_str(", ")?; - } - } - - Ok(()) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/text.mako.rs b/components/style/properties/shorthands/text.mako.rs deleted file mode 100644 index 34d3cd5c5db..00000000000 --- a/components/style/properties/shorthands/text.mako.rs +++ /dev/null @@ -1,120 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -<%helpers:shorthand name="text-decoration" - engines="gecko servo" - flags="SHORTHAND_IN_GETCS" - sub_properties="text-decoration-line - ${' text-decoration-style text-decoration-color text-decoration-thickness' if engine == 'gecko' else ''}" - spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration"> - % if engine == "gecko": - use crate::values::specified; - use crate::properties::longhands::{text_decoration_style, text_decoration_color, text_decoration_thickness}; - % endif - use crate::properties::longhands::text_decoration_line; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - % if engine == "gecko": - let (mut line, mut style, mut color, mut thickness, mut any) = (None, None, None, None, false); - % else: - let (mut line, mut any) = (None, false); - % endif - - loop { - macro_rules! parse_component { - ($value:ident, $module:ident) => ( - if $value.is_none() { - if let Ok(value) = input.try_parse(|input| $module::parse(context, input)) { - $value = Some(value); - any = true; - continue; - } - } - ) - } - - parse_component!(line, text_decoration_line); - - % if engine == "gecko": - parse_component!(style, text_decoration_style); - parse_component!(color, text_decoration_color); - parse_component!(thickness, text_decoration_thickness); - % endif - - break; - } - - if !any { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(expanded! { - text_decoration_line: unwrap_or_initial!(text_decoration_line, line), - - % if engine == "gecko": - text_decoration_style: unwrap_or_initial!(text_decoration_style, style), - text_decoration_color: unwrap_or_initial!(text_decoration_color, color), - text_decoration_thickness: unwrap_or_initial!(text_decoration_thickness, thickness), - % endif - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - #[allow(unused)] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - use crate::values::specified::TextDecorationLine; - - let (is_solid_style, is_current_color, is_auto_thickness) = - ( - % if engine == "gecko": - *self.text_decoration_style == text_decoration_style::SpecifiedValue::Solid, - *self.text_decoration_color == specified::Color::CurrentColor, - self.text_decoration_thickness.is_auto() - % else: - true, true, true - % endif - ); - - let mut has_value = false; - let is_none = *self.text_decoration_line == TextDecorationLine::none(); - if (is_solid_style && is_current_color && is_auto_thickness) || !is_none { - self.text_decoration_line.to_css(dest)?; - has_value = true; - } - - % if engine == "gecko": - if !is_auto_thickness { - if has_value { - dest.write_char(' ')?; - } - self.text_decoration_thickness.to_css(dest)?; - has_value = true; - } - - if !is_solid_style { - if has_value { - dest.write_char(' ')?; - } - self.text_decoration_style.to_css(dest)?; - has_value = true; - } - - if !is_current_color { - if has_value { - dest.write_char(' ')?; - } - self.text_decoration_color.to_css(dest)?; - has_value = true; - } - % endif - - Ok(()) - } - } -</%helpers:shorthand> diff --git a/components/style/properties/shorthands/ui.mako.rs b/components/style/properties/shorthands/ui.mako.rs deleted file mode 100644 index 8fefb89a837..00000000000 --- a/components/style/properties/shorthands/ui.mako.rs +++ /dev/null @@ -1,427 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -<%namespace name="helpers" file="/helpers.mako.rs" /> - -macro_rules! try_parse_one { - ($context: expr, $input: expr, $var: ident, $prop_module: ident) => { - if $var.is_none() { - if let Ok(value) = $input.try_parse(|i| { - $prop_module::single_value::parse($context, i) - }) { - $var = Some(value); - continue; - } - } - }; -} - -<%helpers:shorthand name="transition" - engines="gecko servo" - extra_prefixes="moz:layout.css.prefixes.transitions webkit" - sub_properties="transition-property transition-duration - transition-timing-function - transition-delay" - spec="https://drafts.csswg.org/css-transitions/#propdef-transition"> - use crate::parser::Parse; - % for prop in "delay duration property timing_function".split(): - use crate::properties::longhands::transition_${prop}; - % endfor - use crate::values::specified::TransitionProperty; - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - struct SingleTransition { - % for prop in "duration timing_function delay".split(): - transition_${prop}: transition_${prop}::SingleSpecifiedValue, - % endfor - // Unlike other properties, transition-property uses an Option<> to - // represent 'none' as `None`. - transition_property: Option<TransitionProperty>, - } - - fn parse_one_transition<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<SingleTransition,ParseError<'i>> { - % for prop in "property duration timing_function delay".split(): - let mut ${prop} = None; - % endfor - - let mut parsed = 0; - loop { - parsed += 1; - - try_parse_one!(context, input, duration, transition_duration); - try_parse_one!(context, input, timing_function, transition_timing_function); - try_parse_one!(context, input, delay, transition_delay); - // Must check 'transition-property' after 'transition-timing-function' since - // 'transition-property' accepts any keyword. - if property.is_none() { - if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) { - property = Some(Some(value)); - continue; - } - - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - // 'none' is not a valid value for <single-transition-property>, - // so it's not acceptable in the function above. - property = Some(None); - continue; - } - } - - parsed -= 1; - break - } - - if parsed != 0 { - Ok(SingleTransition { - % for prop in "duration timing_function delay".split(): - transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value - ::get_initial_specified_value), - % endfor - transition_property: property.unwrap_or( - Some(transition_property::single_value::get_initial_specified_value())), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } - - % for prop in "property duration timing_function delay".split(): - let mut ${prop}s = Vec::new(); - % endfor - - let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?; - let multiple_items = results.len() >= 2; - for result in results { - if let Some(value) = result.transition_property { - propertys.push(value); - } else if multiple_items { - // If there is more than one item, and any of transitions has 'none', - // then it's invalid. Othersize, leave propertys to be empty (which - // means "transition-property: none"); - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - % for prop in "duration timing_function delay".split(): - ${prop}s.push(result.transition_${prop}); - % endfor - } - - Ok(expanded! { - % for prop in "property duration timing_function delay".split(): - transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let property_len = self.transition_property.0.len(); - - // There are two cases that we can do shorthand serialization: - // * when all value lists have the same length, or - // * when transition-property is none, and other value lists have exactly one item. - if property_len == 0 { - % for name in "duration delay timing_function".split(): - if self.transition_${name}.0.len() != 1 { - return Ok(()); - } - % endfor - } else { - % for name in "duration delay timing_function".split(): - if self.transition_${name}.0.len() != property_len { - return Ok(()); - } - % endfor - } - - // Representative length. - let len = self.transition_duration.0.len(); - - for i in 0..len { - if i != 0 { - dest.write_str(", ")?; - } - if property_len == 0 { - dest.write_str("none")?; - } else { - self.transition_property.0[i].to_css(dest)?; - } - % for name in "duration timing_function delay".split(): - dest.write_char(' ')?; - self.transition_${name}.0[i].to_css(dest)?; - % endfor - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand name="animation" - engines="gecko servo" - extra_prefixes="moz:layout.css.prefixes.animations webkit" - sub_properties="animation-name animation-duration - animation-timing-function animation-delay - animation-iteration-count animation-direction - animation-fill-mode animation-play-state animation-timeline" - rule_types_allowed="Style" - spec="https://drafts.csswg.org/css-animations/#propdef-animation"> - <% - props = "name timeline duration timing_function delay iteration_count \ - direction fill_mode play_state".split() - %> - % for prop in props: - use crate::properties::longhands::animation_${prop}; - % endfor - - pub fn parse_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Longhands, ParseError<'i>> { - struct SingleAnimation { - % for prop in props: - animation_${prop}: animation_${prop}::SingleSpecifiedValue, - % endfor - } - - fn parse_one_animation<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<SingleAnimation, ParseError<'i>> { - % for prop in props: - let mut ${prop} = None; - % endfor - - let mut parsed = 0; - // NB: Name must be the last one here so that keywords valid for other - // longhands are not interpreted as names. - // - // Also, duration must be before delay, see - // https://drafts.csswg.org/css-animations/#typedef-single-animation - loop { - parsed += 1; - try_parse_one!(context, input, duration, animation_duration); - try_parse_one!(context, input, timing_function, animation_timing_function); - try_parse_one!(context, input, delay, animation_delay); - try_parse_one!(context, input, iteration_count, animation_iteration_count); - try_parse_one!(context, input, direction, animation_direction); - try_parse_one!(context, input, fill_mode, animation_fill_mode); - try_parse_one!(context, input, play_state, animation_play_state); - try_parse_one!(context, input, name, animation_name); - if static_prefs::pref!("layout.css.scroll-driven-animations.enabled") { - try_parse_one!(context, input, timeline, animation_timeline); - } - - parsed -= 1; - break - } - - // If nothing is parsed, this is an invalid entry. - if parsed == 0 { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(SingleAnimation { - % for prop in props: - animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value - ::get_initial_specified_value), - % endfor - }) - } - } - - % for prop in props: - let mut ${prop}s = vec![]; - % endfor - - let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?; - for result in results.into_iter() { - % for prop in props: - ${prop}s.push(result.animation_${prop}); - % endfor - } - - Ok(expanded! { - % for prop in props: - animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()), - % endfor - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - let len = self.animation_name.0.len(); - // There should be at least one declared value - if len == 0 { - return Ok(()); - } - - // If any value list length is differs then we don't do a shorthand serialization - // either. - % for name in props[2:]: - if len != self.animation_${name}.0.len() { - return Ok(()) - } - % endfor - - // If the preference of animation-timeline is disabled, `self.animation_timeline` is - // None. - if self.animation_timeline.map_or(false, |v| len != v.0.len()) { - return Ok(()); - } - - for i in 0..len { - if i != 0 { - dest.write_str(", ")?; - } - - % for name in props[2:]: - self.animation_${name}.0[i].to_css(dest)?; - dest.write_char(' ')?; - % endfor - - self.animation_name.0[i].to_css(dest)?; - - // Based on the spec, the default values of other properties must be output in at - // least the cases necessary to distinguish an animation-name. The serialization - // order of animation-timeline is always later than animation-name, so it's fine - // to not serialize it if it is the default value. It's still possible to - // distinguish them (because we always serialize animation-name). - // https://drafts.csswg.org/css-animations-1/#animation - // https://drafts.csswg.org/css-animations-2/#typedef-single-animation - // - // Note: it's also fine to always serialize this. However, it seems Blink - // doesn't serialize default animation-timeline now, so we follow the same rule. - if let Some(ref timeline) = self.animation_timeline { - if !timeline.0[i].is_auto() { - dest.write_char(' ')?; - timeline.0[i].to_css(dest)?; - } - } - } - Ok(()) - } - } -</%helpers:shorthand> - -<%helpers:shorthand - engines="gecko" - name="scroll-timeline" - sub_properties="scroll-timeline-name scroll-timeline-axis" - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-shorthand" -> - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::properties::longhands::{scroll_timeline_axis, scroll_timeline_name}; - - let mut names = Vec::with_capacity(1); - let mut axes = Vec::with_capacity(1); - input.parse_comma_separated(|input| { - let name = scroll_timeline_name::single_value::parse(context, input)?; - let axis = input.try_parse(|i| scroll_timeline_axis::single_value::parse(context, i)); - - names.push(name); - axes.push(axis.unwrap_or_default()); - - Ok(()) - })?; - - Ok(expanded! { - scroll_timeline_name: scroll_timeline_name::SpecifiedValue(names.into()), - scroll_timeline_axis: scroll_timeline_axis::SpecifiedValue(axes.into()), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - // If any value list length is differs then we don't do a shorthand serialization - // either. - let len = self.scroll_timeline_name.0.len(); - if len != self.scroll_timeline_axis.0.len() { - return Ok(()); - } - - for i in 0..len { - if i != 0 { - dest.write_str(", ")?; - } - - self.scroll_timeline_name.0[i].to_css(dest)?; - - if self.scroll_timeline_axis.0[i] != Default::default() { - dest.write_char(' ')?; - self.scroll_timeline_axis.0[i].to_css(dest)?; - } - - } - Ok(()) - } - } -</%helpers:shorthand> - -// Note: view-timeline shorthand doesn't take view-timeline-inset into account. -<%helpers:shorthand - engines="gecko" - name="view-timeline" - sub_properties="view-timeline-name view-timeline-axis" - gecko_pref="layout.css.scroll-driven-animations.enabled", - spec="https://drafts.csswg.org/scroll-animations-1/#view-timeline-shorthand" -> - pub fn parse_value<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Longhands, ParseError<'i>> { - use crate::properties::longhands::{view_timeline_axis, view_timeline_name}; - - let mut names = Vec::with_capacity(1); - let mut axes = Vec::with_capacity(1); - input.parse_comma_separated(|input| { - let name = view_timeline_name::single_value::parse(context, input)?; - let axis = input.try_parse(|i| view_timeline_axis::single_value::parse(context, i)); - - names.push(name); - axes.push(axis.unwrap_or_default()); - - Ok(()) - })?; - - Ok(expanded! { - view_timeline_name: view_timeline_name::SpecifiedValue(names.into()), - view_timeline_axis: view_timeline_axis::SpecifiedValue(axes.into()), - }) - } - - impl<'a> ToCss for LonghandsToSerialize<'a> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { - // If any value list length is differs then we don't do a shorthand serialization - // either. - let len = self.view_timeline_name.0.len(); - if len != self.view_timeline_axis.0.len() { - return Ok(()); - } - - for i in 0..len { - if i != 0 { - dest.write_str(", ")?; - } - - self.view_timeline_name.0[i].to_css(dest)?; - - if self.view_timeline_axis.0[i] != Default::default() { - dest.write_char(' ')?; - self.view_timeline_axis.0[i].to_css(dest)?; - } - - } - Ok(()) - } - } -</%helpers:shorthand> diff --git a/components/style/properties_and_values/mod.rs b/components/style/properties_and_values/mod.rs deleted file mode 100644 index 00f7ac98f75..00000000000 --- a/components/style/properties_and_values/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -/* 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/. */ - -//! Properties and Values -//! -//! https://drafts.css-houdini.org/css-properties-values-api-1/ - -pub mod rule; -pub mod syntax; diff --git a/components/style/properties_and_values/rule.rs b/components/style/properties_and_values/rule.rs deleted file mode 100644 index 916f7e93560..00000000000 --- a/components/style/properties_and_values/rule.rs +++ /dev/null @@ -1,245 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The [`@property`] at-rule. -//! -//! https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule - -use crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue}; -use crate::error_reporting::ContextualParseError; -use crate::parser::{Parse, ParserContext}; -use crate::properties_and_values::syntax::Descriptor as SyntaxDescriptor; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::values::serialize_atom_name; -use cssparser::{ - AtRuleParser, CowRcStr, DeclarationParser, ParseErrorKind, Parser, QualifiedRuleParser, - RuleBodyItemParser, RuleBodyParser, SourceLocation, -}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use selectors::parser::SelectorParseErrorKind; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; -use to_shmem::{SharedMemoryBuilder, ToShmem}; - -/// Parse the block inside a `@property` rule. -/// -/// Valid `@property` rules result in a registered custom property, as if `registerProperty()` had -/// been called with equivalent parameters. -pub fn parse_property_block( - context: &ParserContext, - input: &mut Parser, - name: PropertyRuleName, - location: SourceLocation, -) -> PropertyRuleData { - let mut rule = PropertyRuleData::empty(name, location); - let mut parser = PropertyRuleParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if !context.error_reporting_enabled() { - continue; - } - if let Err((error, slice)) = declaration { - let location = error.location; - let error = if matches!( - error.kind, - ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_)) - ) { - ContextualParseError::UnsupportedValue(slice, error) - } else { - ContextualParseError::UnsupportedPropertyDescriptor(slice, error) - }; - context.log_css_error(location, error); - } - } - rule -} - -struct PropertyRuleParser<'a, 'b: 'a> { - context: &'a ParserContext<'b>, - rule: &'a mut PropertyRuleData, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyRuleParser<'a, 'b> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyRuleParser<'a, 'b> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for PropertyRuleParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - false - } - fn parse_declarations(&self) -> bool { - true - } -} - -macro_rules! property_descriptors { - ( - $( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )* - ) => { - /// Data inside a `@property` rule. - /// - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule> - #[derive(Clone, Debug, PartialEq)] - pub struct PropertyRuleData { - /// The custom property name. - pub name: PropertyRuleName, - - $( - #[$doc] - pub $ident: Option<$ty>, - )* - - /// Line and column of the @property rule source code. - pub source_location: SourceLocation, - } - - impl PropertyRuleData { - /// Create an empty property rule - pub fn empty(name: PropertyRuleName, source_location: SourceLocation) -> Self { - PropertyRuleData { - name, - $( - $ident: None, - )* - source_location, - } - } - - /// Serialization of declarations in PropertyRuleData - pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result { - $( - if let Some(ref value) = self.$ident { - dest.write_str(concat!($name, ": "))?; - value.to_css(&mut CssWriter::new(dest))?; - dest.write_str("; ")?; - } - )* - Ok(()) - } - } - - impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyRuleParser<'a, 'b> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - match_ignore_ascii_case! { &*name, - $( - $name => { - // DeclarationParser also calls parse_entirely so we’d normally not need - // to, but in this case we do because we set the value as a side effect - // rather than returning it. - let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; - self.rule.$ident = Some(value) - }, - )* - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - } - Ok(()) - } - } - } -} - -property_descriptors! { - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor> - "syntax" syntax: SyntaxDescriptor, - - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor> - "inherits" inherits: Inherits, - - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor> - "initial-value" initial_value: InitialValue, -} - -impl PropertyRuleData { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, _guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - self.name.0.size_of(ops) + - self.syntax.size_of(ops) + - self.inherits.size_of(ops) + - if let Some(ref initial_value) = self.initial_value { - initial_value.size_of(ops) - } else { - 0 - } - } -} - -impl ToCssWithGuard for PropertyRuleData { - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#serialize-a-csspropertyrule> - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@property ")?; - self.name.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" { ")?; - self.decl_to_css(dest)?; - dest.write_char('}') - } -} - -impl ToShmem for PropertyRuleData { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - Err(String::from( - "ToShmem failed for PropertyRule: cannot handle @property rules", - )) - } -} - -/// A custom property name wrapper that includes the `--` prefix in its serialization -#[derive(Clone, Debug, PartialEq)] -pub struct PropertyRuleName(pub Arc<CustomPropertyName>); - -impl ToCss for PropertyRuleName { - fn to_css<W: Write>(&self, dest: &mut CssWriter<W>) -> fmt::Result { - dest.write_str("--")?; - serialize_atom_name(&self.0, dest) - } -} - -/// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor> -#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss)] -pub enum Inherits { - /// `true` value for the `inherits` descriptor - True, - /// `false` value for the `inherits` descriptor - False, -} - -/// Specifies the initial value of the custom property registration represented by the @property -/// rule, controlling the property’s initial value. -/// -/// The SpecifiedValue is wrapped in an Arc to avoid copying when using it. -pub type InitialValue = Arc<SpecifiedValue>; - -impl Parse for InitialValue { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.skip_whitespace(); - SpecifiedValue::parse(input) - } -} diff --git a/components/style/properties_and_values/syntax/ascii.rs b/components/style/properties_and_values/syntax/ascii.rs deleted file mode 100644 index e1a1b08535b..00000000000 --- a/components/style/properties_and_values/syntax/ascii.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* 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/. */ - -/// Trims ASCII whitespace characters from a slice, and returns the trimmed input. -pub fn trim_ascii_whitespace(input: &str) -> &str { - if input.is_empty() { - return input; - } - - let mut start = 0; - { - let mut iter = input.as_bytes().iter(); - loop { - let byte = match iter.next() { - Some(b) => b, - None => return "", - }; - - if !byte.is_ascii_whitespace() { - break; - } - start += 1; - } - } - - let mut end = input.len(); - assert!(start < end); - { - let mut iter = input.as_bytes()[start..].iter().rev(); - loop { - let byte = match iter.next() { - Some(b) => b, - None => { - debug_assert!(false, "We should have caught this in the loop above!"); - return ""; - }, - }; - - if !byte.is_ascii_whitespace() { - break; - } - end -= 1; - } - } - - &input[start..end] -} - -#[test] -fn trim_ascii_whitespace_test() { - fn test(i: &str, o: &str) { - assert_eq!(trim_ascii_whitespace(i), o) - } - - test("", ""); - test(" ", ""); - test(" a b c ", "a b c"); - test(" \t \t \ta b c \t \t \t \t", "a b c"); -} diff --git a/components/style/properties_and_values/syntax/data_type.rs b/components/style/properties_and_values/syntax/data_type.rs deleted file mode 100644 index db5c1478db6..00000000000 --- a/components/style/properties_and_values/syntax/data_type.rs +++ /dev/null @@ -1,91 +0,0 @@ -/* 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/. */ - -use super::{Component, ComponentName, Multiplier}; - -/// <https://drafts.css-houdini.org/css-properties-values-api-1/#supported-names> -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)] -pub enum DataType { - /// Any valid `<length>` value - Length, - /// `<number>` values - Number, - /// Any valid <percentage> value - Percentage, - /// Any valid `<length>` or `<percentage>` value, any valid `<calc()>` expression combining - /// `<length>` and `<percentage>` components. - LengthPercentage, - /// Any valid `<color>` value - Color, - /// Any valid `<image>` value - Image, - /// Any valid `<url>` value - Url, - /// Any valid `<integer>` value - Integer, - /// Any valid `<angle>` value - Angle, - /// Any valid `<time>` value - Time, - /// Any valid `<resolution>` value - Resolution, - /// Any valid `<transform-function>` value - TransformFunction, - /// A list of valid `<transform-function>` values. Note that "<transform-list>" is a pre-multiplied - /// data type name equivalent to "<transform-function>+" - TransformList, - /// Any valid `<custom-ident>` value - CustomIdent, -} - -impl DataType { - pub fn unpremultiply(&self) -> Option<Component> { - match *self { - DataType::TransformList => Some(Component { - name: ComponentName::DataType(DataType::TransformFunction), - multiplier: Some(Multiplier::Space), - }), - _ => None, - } - } - - pub fn from_str(ty: &str) -> Option<Self> { - Some(match ty.as_bytes() { - b"length" => DataType::Length, - b"number" => DataType::Number, - b"percentage" => DataType::Percentage, - b"length-percentage" => DataType::LengthPercentage, - b"color" => DataType::Color, - b"image" => DataType::Image, - b"url" => DataType::Url, - b"integer" => DataType::Integer, - b"angle" => DataType::Angle, - b"time" => DataType::Time, - b"resolution" => DataType::Resolution, - b"transform-function" => DataType::TransformFunction, - b"custom-ident" => DataType::CustomIdent, - b"transform-list" => DataType::TransformList, - _ => return None, - }) - } - - pub fn to_str(&self) -> &str { - match self { - DataType::Length => "length", - DataType::Number => "number", - DataType::Percentage => "percentage", - DataType::LengthPercentage => "length-percentage", - DataType::Color => "color", - DataType::Image => "image", - DataType::Url => "url", - DataType::Integer => "integer", - DataType::Angle => "angle", - DataType::Time => "time", - DataType::Resolution => "resolution", - DataType::TransformFunction => "transform-function", - DataType::CustomIdent => "custom-ident", - DataType::TransformList => "transform-list", - } - } -} diff --git a/components/style/properties_and_values/syntax/mod.rs b/components/style/properties_and_values/syntax/mod.rs deleted file mode 100644 index 96f3c0ff4f3..00000000000 --- a/components/style/properties_and_values/syntax/mod.rs +++ /dev/null @@ -1,328 +0,0 @@ -/* 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/. */ - -//! Used for parsing and serializing the [`@property`] syntax string. -//! -//! <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax> - -use std::fmt::{self, Debug}; -use std::{borrow::Cow, fmt::Write}; - -use crate::parser::{Parse, ParserContext}; -use crate::values::CustomIdent; -use cssparser::{Parser as CSSParser, ParserInput as CSSParserInput}; -use style_traits::{ - CssWriter, ParseError as StyleParseError, PropertySyntaxParseError as ParseError, - StyleParseErrorKind, ToCss, -}; - -use self::data_type::DataType; - -mod ascii; -mod data_type; - -/// <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax> -#[derive(Debug, Clone, MallocSizeOf)] -pub struct Descriptor { - components: Box<[Component]>, - css: String, -} - -impl Descriptor { - /// Returns the universal syntax definition with the given CSS representation. - fn universal(css: &str) -> Self { - Self { - components: Default::default(), - css: String::from(css), - } - } - - /// Returns the specified syntax string. - pub fn as_str(&self) -> &str { - &self.css - } -} - -impl PartialEq for Descriptor { - fn eq(&self, other: &Self) -> bool { - self.components == other.components - } -} - -impl Parse for Descriptor { - /// Parse a syntax descriptor. - fn parse<'i, 't>( - _context: &ParserContext, - parser: &mut CSSParser<'i, 't>, - ) -> Result<Self, StyleParseError<'i>> { - // 1. Strip leading and trailing ASCII whitespace from string. - let input = parser.expect_string()?; - match parse_descriptor(input) { - Ok(syntax) => Ok(syntax), - Err(err) => Err(parser.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))), - } - } -} - -impl ToCss for Descriptor { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.css.to_css(dest) - } -} - -/// <https://drafts.css-houdini.org/css-properties-values-api-1/#multipliers> -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)] -pub enum Multiplier { - /// Indicates a space-separated list. - Space, - /// Indicates a comma-separated list. - Comma, -} - -/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component> -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub struct Component { - name: ComponentName, - multiplier: Option<Multiplier>, -} - -impl Component { - /// Returns the component's name. - #[inline] - pub fn name(&self) -> &ComponentName { - &self.name - } - - /// Returns the component's multiplier, if one exists. - #[inline] - pub fn multiplier(&self) -> Option<Multiplier> { - self.multiplier - } - - /// If the component is premultiplied, return the un-premultiplied component. - #[inline] - pub fn unpremultiplied(&self) -> Cow<Self> { - match self.name.unpremultiply() { - Some(component) => { - debug_assert!( - self.multiplier.is_none(), - "Shouldn't have parsed a multiplier for a pre-multiplied data type name", - ); - Cow::Owned(component) - }, - None => Cow::Borrowed(self), - } - } -} - -/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name> -#[derive(Clone, Debug, MallocSizeOf, PartialEq)] -pub enum ComponentName { - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#data-type-name> - DataType(DataType), - /// <https://drafts.csswg.org/css-values-4/#custom-idents> - Ident(CustomIdent), -} - -impl ComponentName { - fn unpremultiply(&self) -> Option<Component> { - match *self { - ComponentName::DataType(ref t) => t.unpremultiply(), - ComponentName::Ident(..) => None, - } - } - - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name> - fn is_pre_multiplied(&self) -> bool { - self.unpremultiply().is_some() - } -} - -/// Parse a syntax descriptor. -#[inline] -fn parse_descriptor(css: &str) -> Result<Descriptor, ParseError> { - // 1. Strip leading and trailing ASCII whitespace from string. - let input = ascii::trim_ascii_whitespace(css); - - // 2. If string's length is 0, return failure. - if input.is_empty() { - return Err(ParseError::EmptyInput); - } - - // 3. If string's length is 1, and the only code point in string is U+002A - // ASTERISK (*), return the universal syntax descriptor. - if input.len() == 1 && input.as_bytes()[0] == b'*' { - return Ok(Descriptor::universal(css)); - } - - // 4. Let stream be an input stream created from the code points of string, - // preprocessed as specified in [css-syntax-3]. Let descriptor be an - // initially empty list of syntax components. - // - // NOTE(emilio): Instead of preprocessing we cheat and treat new-lines and - // nulls in the parser specially. - let mut components = vec![]; - { - let mut parser = Parser::new(input, &mut components); - // 5. Repeatedly consume the next input code point from stream. - parser.parse()?; - } - Ok(Descriptor { - components: components.into_boxed_slice(), - css: String::from(css), - }) -} - -struct Parser<'a> { - input: &'a str, - position: usize, - output: &'a mut Vec<Component>, -} - -/// <https://drafts.csswg.org/css-syntax-3/#whitespace> -fn is_whitespace(byte: u8) -> bool { - match byte { - b'\t' | b'\n' | b'\r' | b' ' => true, - _ => false, - } -} - -/// <https://drafts.csswg.org/css-syntax-3/#letter> -fn is_letter(byte: u8) -> bool { - match byte { - b'A'..=b'Z' | b'a'..=b'z' => true, - _ => false, - } -} - -/// <https://drafts.csswg.org/css-syntax-3/#non-ascii-code-point> -fn is_non_ascii(byte: u8) -> bool { - byte >= 0x80 -} - -/// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point> -fn is_name_start(byte: u8) -> bool { - is_letter(byte) || is_non_ascii(byte) || byte == b'_' -} - -impl<'a> Parser<'a> { - fn new(input: &'a str, output: &'a mut Vec<Component>) -> Self { - Self { - input, - position: 0, - output, - } - } - - fn peek(&self) -> Option<u8> { - self.input.as_bytes().get(self.position).cloned() - } - - fn parse(&mut self) -> Result<(), ParseError> { - // 5. Repeatedly consume the next input code point from stream: - loop { - let component = self.parse_component()?; - self.output.push(component); - self.skip_whitespace(); - - let byte = match self.peek() { - None => return Ok(()), - Some(b) => b, - }; - - if byte != b'|' { - return Err(ParseError::ExpectedPipeBetweenComponents); - } - - self.position += 1; - } - } - - fn skip_whitespace(&mut self) { - loop { - match self.peek() { - Some(c) if is_whitespace(c) => self.position += 1, - _ => return, - } - } - } - - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name> - fn parse_data_type_name(&mut self) -> Result<DataType, ParseError> { - let start = self.position; - loop { - let byte = match self.peek() { - Some(b) => b, - None => return Err(ParseError::UnclosedDataTypeName), - }; - if byte != b'>' { - self.position += 1; - continue; - } - let ty = match DataType::from_str(&self.input[start..self.position]) { - Some(ty) => ty, - None => return Err(ParseError::UnknownDataTypeName), - }; - self.position += 1; - return Ok(ty); - } - } - - fn parse_name(&mut self) -> Result<ComponentName, ParseError> { - let b = match self.peek() { - Some(b) => b, - None => return Err(ParseError::UnexpectedEOF), - }; - - if b == b'<' { - self.position += 1; - return Ok(ComponentName::DataType(self.parse_data_type_name()?)); - } - - if b != b'\\' && !is_name_start(b) { - return Err(ParseError::InvalidNameStart); - } - - let input = &self.input[self.position..]; - let mut input = CSSParserInput::new(input); - let mut input = CSSParser::new(&mut input); - let location = input.current_source_location(); - let name = input - .expect_ident() - .ok() - .and_then(|name| CustomIdent::from_ident(location, name, &[]).ok()); - let name = match name { - Some(name) => name, - None => return Err(ParseError::InvalidName), - }; - self.position += input.position().byte_index(); - return Ok(ComponentName::Ident(name)); - } - - fn parse_multiplier(&mut self) -> Option<Multiplier> { - let multiplier = match self.peek()? { - b'+' => Multiplier::Space, - b'#' => Multiplier::Comma, - _ => return None, - }; - self.position += 1; - Some(multiplier) - } - - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-component> - fn parse_component(&mut self) -> Result<Component, ParseError> { - // Consume as much whitespace as possible from stream. - self.skip_whitespace(); - let name = self.parse_name()?; - let multiplier = if name.is_pre_multiplied() { - None - } else { - self.parse_multiplier() - }; - Ok(Component { name, multiplier }) - } -} diff --git a/components/style/queries/condition.rs b/components/style/queries/condition.rs deleted file mode 100644 index e17e6abd2e7..00000000000 --- a/components/style/queries/condition.rs +++ /dev/null @@ -1,366 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A query condition: -//! -//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition -//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition - -use super::{FeatureFlags, FeatureType, QueryFeatureExpression}; -use crate::values::computed; -use crate::{error_reporting::ContextualParseError, parser::ParserContext}; -use cssparser::{Parser, Token}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// A binary `and` or `or` operator. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -#[allow(missing_docs)] -pub enum Operator { - And, - Or, -} - -/// Whether to allow an `or` condition or not during parsing. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] -enum AllowOr { - Yes, - No, -} - -/// https://en.wikipedia.org/wiki/Three-valued_logic#Kleene_and_Priest_logics -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] -pub enum KleeneValue { - /// False - False = 0, - /// True - True = 1, - /// Either true or false, but we’re not sure which yet. - Unknown, -} - -impl From<bool> for KleeneValue { - fn from(b: bool) -> Self { - if b { - Self::True - } else { - Self::False - } - } -} - -impl KleeneValue { - /// Turns this Kleene value to a bool, taking the unknown value as an - /// argument. - pub fn to_bool(self, unknown: bool) -> bool { - match self { - Self::True => true, - Self::False => false, - Self::Unknown => unknown, - } - } -} - -impl std::ops::Not for KleeneValue { - type Output = Self; - - fn not(self) -> Self { - match self { - Self::True => Self::False, - Self::False => Self::True, - Self::Unknown => Self::Unknown, - } - } -} - -// Implements the logical and operation. -impl std::ops::BitAnd for KleeneValue { - type Output = Self; - - fn bitand(self, other: Self) -> Self { - if self == Self::False || other == Self::False { - return Self::False; - } - if self == Self::Unknown || other == Self::Unknown { - return Self::Unknown; - } - Self::True - } -} - -// Implements the logical or operation. -impl std::ops::BitOr for KleeneValue { - type Output = Self; - - fn bitor(self, other: Self) -> Self { - if self == Self::True || other == Self::True { - return Self::True; - } - if self == Self::Unknown || other == Self::Unknown { - return Self::Unknown; - } - Self::False - } -} - -impl std::ops::BitOrAssign for KleeneValue { - fn bitor_assign(&mut self, other: Self) { - *self = *self | other; - } -} - -impl std::ops::BitAndAssign for KleeneValue { - fn bitand_assign(&mut self, other: Self) { - *self = *self & other; - } -} - -/// Represents a condition. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum QueryCondition { - /// A simple feature expression, implicitly parenthesized. - Feature(QueryFeatureExpression), - /// A negation of a condition. - Not(Box<QueryCondition>), - /// A set of joint operations. - Operation(Box<[QueryCondition]>, Operator), - /// A condition wrapped in parenthesis. - InParens(Box<QueryCondition>), - /// [ <function-token> <any-value>? ) ] | [ ( <any-value>? ) ] - GeneralEnclosed(String), -} - -impl ToCss for QueryCondition { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - match *self { - // NOTE(emilio): QueryFeatureExpression already includes the - // parenthesis. - QueryCondition::Feature(ref f) => f.to_css(dest), - QueryCondition::Not(ref c) => { - dest.write_str("not ")?; - c.to_css(dest) - }, - QueryCondition::InParens(ref c) => { - dest.write_char('(')?; - c.to_css(dest)?; - dest.write_char(')') - }, - QueryCondition::Operation(ref list, op) => { - let mut iter = list.iter(); - iter.next().unwrap().to_css(dest)?; - for item in iter { - dest.write_char(' ')?; - op.to_css(dest)?; - dest.write_char(' ')?; - item.to_css(dest)?; - } - Ok(()) - }, - QueryCondition::GeneralEnclosed(ref s) => dest.write_str(&s), - } - } -} - -/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value> -fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { - input.expect_no_error_token().map_err(Into::into) -} - -impl QueryCondition { - /// Parse a single condition. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, feature_type, AllowOr::Yes) - } - - fn visit<F>(&self, visitor: &mut F) - where - F: FnMut(&Self), - { - visitor(self); - match *self { - Self::Feature(..) => {}, - Self::GeneralEnclosed(..) => {}, - Self::Not(ref cond) => cond.visit(visitor), - Self::Operation(ref conds, _op) => { - for cond in conds.iter() { - cond.visit(visitor); - } - }, - Self::InParens(ref cond) => cond.visit(visitor), - } - } - - /// Returns the union of all flags in the expression. This is useful for - /// container queries. - pub fn cumulative_flags(&self) -> FeatureFlags { - let mut result = FeatureFlags::empty(); - self.visit(&mut |condition| { - if let Self::Feature(ref f) = condition { - result.insert(f.feature_flags()) - } - }); - result - } - - /// Parse a single condition, disallowing `or` expressions. - /// - /// To be used from the legacy query syntax. - pub fn parse_disallow_or<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, feature_type, AllowOr::No) - } - - /// https://drafts.csswg.org/mediaqueries-5/#typedef-media-condition or - /// https://drafts.csswg.org/mediaqueries-5/#typedef-media-condition-without-or - /// (depending on `allow_or`). - fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - allow_or: AllowOr, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() { - let inner_condition = Self::parse_in_parens(context, input, feature_type)?; - return Ok(QueryCondition::Not(Box::new(inner_condition))); - } - - let first_condition = Self::parse_in_parens(context, input, feature_type)?; - let operator = match input.try_parse(Operator::parse) { - Ok(op) => op, - Err(..) => return Ok(first_condition), - }; - - if allow_or == AllowOr::No && operator == Operator::Or { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - let mut conditions = vec![]; - conditions.push(first_condition); - conditions.push(Self::parse_in_parens(context, input, feature_type)?); - - let delim = match operator { - Operator::And => "and", - Operator::Or => "or", - }; - - loop { - if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() { - return Ok(QueryCondition::Operation( - conditions.into_boxed_slice(), - operator, - )); - } - - conditions.push(Self::parse_in_parens(context, input, feature_type)?); - } - } - - fn parse_in_parenthesis_block<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - // Base case. Make sure to preserve this error as it's more generally - // relevant. - let feature_error = match input.try_parse(|input| { - QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type) - }) { - Ok(expr) => return Ok(Self::Feature(expr)), - Err(e) => e, - }; - if let Ok(inner) = Self::parse(context, input, feature_type) { - return Ok(Self::InParens(Box::new(inner))); - } - Err(feature_error) - } - - /// Parse a condition in parentheses, or `<general-enclosed>`. - /// - /// https://drafts.csswg.org/mediaqueries/#typedef-media-in-parens - pub fn parse_in_parens<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - input.skip_whitespace(); - let start = input.position(); - let start_location = input.current_source_location(); - match *input.next()? { - Token::ParenthesisBlock => { - let nested = input.try_parse(|input| { - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input, feature_type) - }) - }); - match nested { - Ok(nested) => return Ok(nested), - Err(e) => { - // We're about to swallow the error in a `<general-enclosed>` - // condition, so report it while we can. - let loc = e.location; - let error = - ContextualParseError::InvalidMediaRule(input.slice_from(start), e); - context.log_css_error(loc, error); - }, - } - }, - Token::Function(..) => { - // TODO: handle `style()` queries, etc. - }, - ref t => return Err(start_location.new_unexpected_token_error(t.clone())), - } - input.parse_nested_block(consume_any_value)?; - Ok(Self::GeneralEnclosed(input.slice_from(start).to_owned())) - } - - /// Whether this condition matches the device and quirks mode. - /// https://drafts.csswg.org/mediaqueries/#evaluating - /// https://drafts.csswg.org/mediaqueries/#typedef-general-enclosed - /// Kleene 3-valued logic is adopted here due to the introduction of - /// <general-enclosed>. - pub fn matches(&self, context: &computed::Context) -> KleeneValue { - match *self { - QueryCondition::Feature(ref f) => f.matches(context), - QueryCondition::GeneralEnclosed(_) => KleeneValue::Unknown, - QueryCondition::InParens(ref c) => c.matches(context), - QueryCondition::Not(ref c) => !c.matches(context), - QueryCondition::Operation(ref conditions, op) => { - debug_assert!(!conditions.is_empty(), "We never create an empty op"); - match op { - Operator::And => { - let mut result = KleeneValue::True; - for c in conditions.iter() { - result &= c.matches(context); - if result == KleeneValue::False { - break; - } - } - result - }, - Operator::Or => { - let mut result = KleeneValue::False; - for c in conditions.iter() { - result |= c.matches(context); - if result == KleeneValue::True { - break; - } - } - result - }, - } - }, - } - } -} diff --git a/components/style/queries/feature.rs b/components/style/queries/feature.rs deleted file mode 100644 index e8fd62284bd..00000000000 --- a/components/style/queries/feature.rs +++ /dev/null @@ -1,195 +0,0 @@ -/* 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/. */ - -//! Query features. - -use super::condition::KleeneValue; -use crate::parser::ParserContext; -use crate::values::computed::{self, CSSPixelLength, Ratio, Resolution}; -use crate::Atom; -use cssparser::Parser; -use std::fmt; -use style_traits::ParseError; - -/// A generic discriminant for an enum value. -pub type KeywordDiscriminant = u8; - -type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T; - -/// Serializes a given discriminant. -/// -/// FIXME(emilio): we could prevent this allocation if the ToCss code would -/// generate a method for keywords to get the static string or something. -pub type KeywordSerializer = fn(KeywordDiscriminant) -> String; - -/// Parses a given identifier. -pub type KeywordParser = for<'a, 'i, 't> fn( - context: &'a ParserContext, - input: &'a mut Parser<'i, 't>, -) -> Result<KeywordDiscriminant, ParseError<'i>>; - -/// An evaluator for a given feature. -/// -/// This determines the kind of values that get parsed, too. -#[allow(missing_docs)] -pub enum Evaluator { - Length(QueryFeatureGetter<CSSPixelLength>), - OptionalLength(QueryFeatureGetter<Option<CSSPixelLength>>), - Integer(QueryFeatureGetter<i32>), - Float(QueryFeatureGetter<f32>), - BoolInteger(QueryFeatureGetter<bool>), - /// A non-negative number ratio, such as the one from device-pixel-ratio. - NumberRatio(QueryFeatureGetter<Ratio>), - OptionalNumberRatio(QueryFeatureGetter<Option<Ratio>>), - /// A resolution. - Resolution(QueryFeatureGetter<Resolution>), - /// A keyword value. - Enumerated { - /// The parser to get a discriminant given a string. - parser: KeywordParser, - /// The serializer to get a string from a discriminant. - /// - /// This is guaranteed to be called with a keyword that `parser` has - /// produced. - serializer: KeywordSerializer, - /// The evaluator itself. This is guaranteed to be called with a - /// keyword that `parser` has produced. - evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> KleeneValue, - }, -} - -/// A simple helper macro to create a keyword evaluator. -/// -/// This assumes that keyword feature expressions don't accept ranges, and -/// asserts if that's not true. As of today there's nothing like that (does that -/// even make sense?). -macro_rules! keyword_evaluator { - ($actual_evaluator:ident, $keyword_type:ty) => {{ - fn __parse<'i, 't>( - context: &$crate::parser::ParserContext, - input: &mut $crate::cssparser::Parser<'i, 't>, - ) -> Result<$crate::queries::feature::KeywordDiscriminant, ::style_traits::ParseError<'i>> - { - let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?; - Ok(kw as $crate::queries::feature::KeywordDiscriminant) - } - - fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String { - // This unwrap is ok because the only discriminants that get - // back to us is the ones that `parse` produces. - let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap(); - <$keyword_type as ::style_traits::ToCss>::to_css_string(&value) - } - - fn __evaluate( - context: &$crate::values::computed::Context, - value: Option<$crate::queries::feature::KeywordDiscriminant>, - ) -> $crate::queries::condition::KleeneValue { - // This unwrap is ok because the only discriminants that get - // back to us is the ones that `parse` produces. - let value: Option<$keyword_type> = - value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap()); - $crate::queries::condition::KleeneValue::from($actual_evaluator(context, value)) - } - - $crate::queries::feature::Evaluator::Enumerated { - parser: __parse, - serializer: __serialize, - evaluator: __evaluate, - } - }}; -} - -bitflags! { - /// Different flags or toggles that change how a expression is parsed or - /// evaluated. - #[derive(ToShmem)] - pub struct FeatureFlags : u8 { - /// The feature should only be parsed in chrome and ua sheets. - const CHROME_AND_UA_ONLY = 1 << 0; - /// The feature requires a -webkit- prefix. - const WEBKIT_PREFIX = 1 << 1; - /// The feature requires the inline-axis containment. - const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2; - /// The feature requires the block-axis containment. - const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3; - /// The feature requires containment in the physical width axis. - const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4; - /// The feature requires containment in the physical height axis. - const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5; - /// The feature evaluation depends on the viewport size. - const VIEWPORT_DEPENDENT = 1 << 6; - } -} - -impl FeatureFlags { - /// Returns parsing requirement flags. - pub fn parsing_requirements(self) -> Self { - self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX) - } - - /// Returns all the container axis flags. - pub fn all_container_axes() -> Self { - Self::CONTAINER_REQUIRES_INLINE_AXIS | - Self::CONTAINER_REQUIRES_BLOCK_AXIS | - Self::CONTAINER_REQUIRES_WIDTH_AXIS | - Self::CONTAINER_REQUIRES_HEIGHT_AXIS - } - - /// Returns our subset of container axis flags. - pub fn container_axes(self) -> Self { - self.intersection(Self::all_container_axes()) - } -} - -/// Whether a feature allows ranges or not. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[allow(missing_docs)] -pub enum AllowsRanges { - Yes, - No, -} - -/// A description of a feature. -pub struct QueryFeatureDescription { - /// The feature name, in ascii lowercase. - pub name: Atom, - /// Whether min- / max- prefixes are allowed or not. - pub allows_ranges: AllowsRanges, - /// The evaluator, which we also use to determine which kind of value to - /// parse. - pub evaluator: Evaluator, - /// Different feature-specific flags. - pub flags: FeatureFlags, -} - -impl QueryFeatureDescription { - /// Whether this feature allows ranges. - #[inline] - pub fn allows_ranges(&self) -> bool { - self.allows_ranges == AllowsRanges::Yes - } -} - -/// A simple helper to construct a `QueryFeatureDescription`. -macro_rules! feature { - ($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => { - $crate::queries::feature::QueryFeatureDescription { - name: $name, - allows_ranges: $allows_ranges, - evaluator: $evaluator, - flags: $flags, - } - }; -} - -impl fmt::Debug for QueryFeatureDescription { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("QueryFeatureDescription") - .field("name", &self.name) - .field("allows_ranges", &self.allows_ranges) - .field("flags", &self.flags) - .finish() - } -} diff --git a/components/style/queries/feature_expression.rs b/components/style/queries/feature_expression.rs deleted file mode 100644 index a3f067c923a..00000000000 --- a/components/style/queries/feature_expression.rs +++ /dev/null @@ -1,754 +0,0 @@ -/* 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/. */ - -//! Parsing for query feature expressions, like `(foo: bar)` or -//! `(width >= 400px)`. - -use super::feature::{Evaluator, QueryFeatureDescription}; -use super::feature::{FeatureFlags, KeywordDiscriminant}; -use crate::parser::{Parse, ParserContext}; -use crate::queries::condition::KleeneValue; -use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; -use crate::values::computed::{self, Ratio, ToComputedValue}; -use crate::values::specified::{Integer, Length, Number, Resolution}; -use crate::values::CSSFloat; -use crate::{Atom, Zero}; -use cssparser::{Parser, Token}; -use std::cmp::{Ordering, PartialOrd}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// Whether we're parsing a media or container query feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum FeatureType { - /// We're parsing a media feature. - Media, - /// We're parsing a container feature. - Container, -} - -impl FeatureType { - fn features(&self) -> &'static [QueryFeatureDescription] { - #[cfg(feature = "gecko")] - let media_features = &crate::gecko::media_features::MEDIA_FEATURES; - #[cfg(feature = "servo")] - let media_features = &*crate::servo::media_queries::MEDIA_FEATURES; - - use crate::stylesheets::container_rule::CONTAINER_FEATURES; - - match *self { - FeatureType::Media => media_features, - FeatureType::Container => &CONTAINER_FEATURES, - } - } - - fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> { - self.features() - .iter() - .enumerate() - .find(|(_, f)| f.name == *name) - } -} - -/// The kind of matching that should be performed on a feature value. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -enum LegacyRange { - /// At least the specified value. - Min, - /// At most the specified value. - Max, -} - -/// The operator that was specified in this feature. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -enum Operator { - /// = - Equal, - /// > - GreaterThan, - /// >= - GreaterThanEqual, - /// < - LessThan, - /// <= - LessThanEqual, -} - -impl ToCss for Operator { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - dest.write_str(match *self { - Self::Equal => "=", - Self::LessThan => "<", - Self::LessThanEqual => "<=", - Self::GreaterThan => ">", - Self::GreaterThanEqual => ">=", - }) - } -} - -impl Operator { - fn is_compatible_with(self, right_op: Self) -> bool { - // Some operators are not compatible with each other in multi-range - // context. - match self { - Self::Equal => false, - Self::GreaterThan | Self::GreaterThanEqual => { - matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual) - }, - Self::LessThan | Self::LessThanEqual => { - matches!(right_op, Self::LessThan | Self::LessThanEqual) - }, - } - } - - fn evaluate(&self, cmp: Ordering) -> bool { - match *self { - Self::Equal => cmp == Ordering::Equal, - Self::GreaterThan => cmp == Ordering::Greater, - Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater, - Self::LessThan => cmp == Ordering::Less, - Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less, - } - } - - fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let operator = match *input.next()? { - Token::Delim('=') => return Ok(Operator::Equal), - Token::Delim('>') => Operator::GreaterThan, - Token::Delim('<') => Operator::LessThan, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }; - - // https://drafts.csswg.org/mediaqueries-4/#mq-syntax: - // - // No whitespace is allowed between the “<” or “>” - // <delim-token>s and the following “=” <delim-token>, if it’s - // present. - // - // TODO(emilio): Maybe we should ignore comments as well? - // https://github.com/w3c/csswg-drafts/issues/6248 - let parsed_equal = input - .try_parse(|i| { - let t = i.next_including_whitespace().map_err(|_| ())?; - if !matches!(t, Token::Delim('=')) { - return Err(()); - } - Ok(()) - }) - .is_ok(); - - if !parsed_equal { - return Ok(operator); - } - - Ok(match operator { - Operator::GreaterThan => Operator::GreaterThanEqual, - Operator::LessThan => Operator::LessThanEqual, - _ => unreachable!(), - }) - } -} - -#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)] -enum QueryFeatureExpressionKind { - /// Just the media feature name. - Empty, - - /// A single value. - Single(QueryExpressionValue), - - /// Legacy range syntax (min-*: value) or so. - LegacyRange(LegacyRange, QueryExpressionValue), - - /// Modern range context syntax: - /// https://drafts.csswg.org/mediaqueries-5/#mq-range-context - Range { - left: Option<(Operator, QueryExpressionValue)>, - right: Option<(Operator, QueryExpressionValue)>, - }, -} - -impl QueryFeatureExpressionKind { - /// Evaluate a given range given an optional query value and a value from - /// the browser. - fn evaluate<T>( - &self, - context_value: T, - mut compute: impl FnMut(&QueryExpressionValue) -> T, - ) -> bool - where - T: PartialOrd + Zero, - { - match *self { - Self::Empty => return !context_value.is_zero(), - Self::Single(ref value) => { - let value = compute(value); - let cmp = match context_value.partial_cmp(&value) { - Some(c) => c, - None => return false, - }; - cmp == Ordering::Equal - }, - Self::LegacyRange(ref range, ref value) => { - let value = compute(value); - let cmp = match context_value.partial_cmp(&value) { - Some(c) => c, - None => return false, - }; - cmp == Ordering::Equal || - match range { - LegacyRange::Min => cmp == Ordering::Greater, - LegacyRange::Max => cmp == Ordering::Less, - } - }, - Self::Range { - ref left, - ref right, - } => { - debug_assert!(left.is_some() || right.is_some()); - if let Some((ref op, ref value)) = left { - let value = compute(value); - let cmp = match value.partial_cmp(&context_value) { - Some(c) => c, - None => return false, - }; - if !op.evaluate(cmp) { - return false; - } - } - if let Some((ref op, ref value)) = right { - let value = compute(value); - let cmp = match context_value.partial_cmp(&value) { - Some(c) => c, - None => return false, - }; - if !op.evaluate(cmp) { - return false; - } - } - true - }, - } - } - - /// Non-ranged features only need to compare to one value at most. - fn non_ranged_value(&self) -> Option<&QueryExpressionValue> { - match *self { - Self::Empty => None, - Self::Single(ref v) => Some(v), - Self::LegacyRange(..) | Self::Range { .. } => { - debug_assert!(false, "Unexpected ranged value in non-ranged feature!"); - None - }, - } - } -} - -/// A feature expression contains a reference to the feature, the value the -/// query contained, and the range to evaluate. -#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)] -pub struct QueryFeatureExpression { - feature_type: FeatureType, - feature_index: usize, - kind: QueryFeatureExpressionKind, -} - -impl ToCss for QueryFeatureExpression { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - dest.write_char('(')?; - - match self.kind { - QueryFeatureExpressionKind::Empty => self.write_name(dest)?, - QueryFeatureExpressionKind::Single(ref v) | - QueryFeatureExpressionKind::LegacyRange(_, ref v) => { - self.write_name(dest)?; - dest.write_str(": ")?; - v.to_css(dest, self)?; - }, - QueryFeatureExpressionKind::Range { - ref left, - ref right, - } => { - if let Some((ref op, ref val)) = left { - val.to_css(dest, self)?; - dest.write_char(' ')?; - op.to_css(dest)?; - dest.write_char(' ')?; - } - self.write_name(dest)?; - if let Some((ref op, ref val)) = right { - dest.write_char(' ')?; - op.to_css(dest)?; - dest.write_char(' ')?; - val.to_css(dest, self)?; - } - }, - } - dest.write_char(')') - } -} - -fn consume_operation_or_colon<'i>( - input: &mut Parser<'i, '_>, -) -> Result<Option<Operator>, ParseError<'i>> { - if input.try_parse(|input| input.expect_colon()).is_ok() { - return Ok(None); - } - Operator::parse(input).map(|op| Some(op)) -} - -#[allow(unused_variables)] -fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool { - #[cfg(feature = "gecko")] - { - if *feature == atom!("forced-colors") { - // forced-colors is always enabled in the ua and chrome. On - // the web it is hidden behind a preference, which is defaulted - // to 'true' as of bug 1659511. - return !context.in_ua_or_chrome_sheet() && - !static_prefs::pref!("layout.css.forced-colors.enabled"); - } - // prefers-contrast is always enabled in the ua and chrome. On - // the web it is hidden behind a preference. - if *feature == atom!("prefers-contrast") { - return !context.in_ua_or_chrome_sheet() && - !static_prefs::pref!("layout.css.prefers-contrast.enabled"); - } - - // prefers-reduced-transparency is always enabled in the ua and chrome. On - // the web it is hidden behind a preference (see Bug 1822176). - if *feature == atom!("prefers-reduced-transparency") { - return !context.in_ua_or_chrome_sheet() && - !static_prefs::pref!("layout.css.prefers-reduced-transparency.enabled"); - } - - // inverted-colors is always enabled in the ua and chrome. On - // the web it is hidden behind a preferenc. - if *feature == atom!("inverted-colors") { - return !context.in_ua_or_chrome_sheet() && - !static_prefs::pref!("layout.css.inverted-colors.enabled"); - } - } - false -} - -impl QueryFeatureExpression { - fn new( - feature_type: FeatureType, - feature_index: usize, - kind: QueryFeatureExpressionKind, - ) -> Self { - debug_assert!(feature_index < feature_type.features().len()); - Self { - feature_type, - feature_index, - kind, - } - } - - fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - let feature = self.feature(); - if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) { - dest.write_str("-webkit-")?; - } - - if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind { - match range { - LegacyRange::Min => dest.write_str("min-")?, - LegacyRange::Max => dest.write_str("max-")?, - } - } - - // NB: CssStringWriter not needed, feature names are under control. - write!(dest, "{}", feature.name)?; - - Ok(()) - } - - fn feature(&self) -> &'static QueryFeatureDescription { - &self.feature_type.features()[self.feature_index] - } - - /// Returns the feature flags for our feature. - pub fn feature_flags(&self) -> FeatureFlags { - self.feature().flags - } - - /// Parse a feature expression of the form: - /// - /// ``` - /// (media-feature: media-value) - /// ``` - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input, feature_type) - }) - } - - fn parse_feature_name<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> { - let mut flags = FeatureFlags::empty(); - let location = input.current_source_location(); - let ident = input.expect_ident()?; - - if context.in_ua_or_chrome_sheet() { - flags.insert(FeatureFlags::CHROME_AND_UA_ONLY); - } - - let mut feature_name = &**ident; - if starts_with_ignore_ascii_case(feature_name, "-webkit-") { - feature_name = &feature_name[8..]; - flags.insert(FeatureFlags::WEBKIT_PREFIX); - } - - let range = if starts_with_ignore_ascii_case(feature_name, "min-") { - feature_name = &feature_name[4..]; - Some(LegacyRange::Min) - } else if starts_with_ignore_ascii_case(feature_name, "max-") { - feature_name = &feature_name[4..]; - Some(LegacyRange::Max) - } else { - None - }; - - let atom = Atom::from(string_as_ascii_lowercase(feature_name)); - let (feature_index, feature) = match feature_type.find_feature(&atom) { - Some((i, f)) => (i, f), - None => { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )) - }, - }; - - if disabled_by_pref(&feature.name, context) || - !flags.contains(feature.flags.parsing_requirements()) || - (range.is_some() && !feature.allows_ranges()) - { - return Err(location.new_custom_error( - StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()), - )); - } - - Ok((feature_index, range)) - } - - /// Parses the following range syntax: - /// - /// (feature-value <operator> feature-name) - /// (feature-value <operator> feature-name <operator> feature-value) - fn parse_multi_range_syntax<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - let start = input.state(); - - // To parse the values, we first need to find the feature name. We rely - // on feature values for ranged features not being able to be top-level - // <ident>s, which holds. - let feature_index = loop { - // NOTE: parse_feature_name advances the input. - if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) { - if range.is_some() { - // Ranged names are not allowed here. - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - break index; - } - if input.is_exhausted() { - return Err(start - .source_location() - .new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }; - - input.reset(&start); - - let feature = &feature_type.features()[feature_index]; - let left_val = QueryExpressionValue::parse(feature, context, input)?; - let left_op = Operator::parse(input)?; - - { - let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?; - debug_assert_eq!( - parsed_index, feature_index, - "How did we find a different feature?" - ); - } - - let right_op = input.try_parse(Operator::parse).ok(); - let right = match right_op { - Some(op) => { - if !left_op.is_compatible_with(op) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Some((op, QueryExpressionValue::parse(feature, context, input)?)) - }, - None => None, - }; - Ok(Self::new( - feature_type, - feature_index, - QueryFeatureExpressionKind::Range { - left: Some((left_op, left_val)), - right, - }, - )) - } - - /// Parse a feature expression where we've already consumed the parenthesis. - pub fn parse_in_parenthesis_block<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - let (feature_index, range) = - match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) { - Ok(v) => v, - Err(e) => { - if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) { - return Ok(expr); - } - return Err(e); - }, - }; - let operator = input.try_parse(consume_operation_or_colon); - let operator = match operator { - Err(..) => { - // If there's no colon, this is a query of the form - // '(<feature>)', that is, there's no value specified. - // - // Gecko doesn't allow ranged expressions without a - // value, so just reject them here too. - if range.is_some() { - return Err( - input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue) - ); - } - - return Ok(Self::new( - feature_type, - feature_index, - QueryFeatureExpressionKind::Empty, - )); - }, - Ok(operator) => operator, - }; - - let feature = &feature_type.features()[feature_index]; - - let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| { - err.location - .new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue) - })?; - - let kind = match range { - Some(range) => { - if operator.is_some() { - return Err( - input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator) - ); - } - QueryFeatureExpressionKind::LegacyRange(range, value) - }, - None => match operator { - Some(operator) => { - if !feature.allows_ranges() { - return Err(input - .new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)); - } - QueryFeatureExpressionKind::Range { - left: None, - right: Some((operator, value)), - } - }, - None => QueryFeatureExpressionKind::Single(value), - }, - }; - - Ok(Self::new(feature_type, feature_index, kind)) - } - - /// Returns whether this query evaluates to true for the given device. - pub fn matches(&self, context: &computed::Context) -> KleeneValue { - macro_rules! expect { - ($variant:ident, $v:expr) => { - match *$v { - QueryExpressionValue::$variant(ref v) => v, - _ => unreachable!("Unexpected QueryExpressionValue"), - } - }; - } - - KleeneValue::from(match self.feature().evaluator { - Evaluator::Length(eval) => { - let v = eval(context); - self.kind - .evaluate(v, |v| expect!(Length, v).to_computed_value(context)) - }, - Evaluator::OptionalLength(eval) => { - let v = match eval(context) { - Some(v) => v, - None => return KleeneValue::Unknown, - }; - self.kind - .evaluate(v, |v| expect!(Length, v).to_computed_value(context)) - }, - Evaluator::Integer(eval) => { - let v = eval(context); - self.kind.evaluate(v, |v| *expect!(Integer, v)) - }, - Evaluator::Float(eval) => { - let v = eval(context); - self.kind.evaluate(v, |v| *expect!(Float, v)) - }, - Evaluator::NumberRatio(eval) => { - let ratio = eval(context); - // A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value() - // to convert it if necessary. - // FIXME: we may need to update here once - // https://github.com/w3c/csswg-drafts/issues/4954 got resolved. - self.kind - .evaluate(ratio, |v| expect!(NumberRatio, v).used_value()) - }, - Evaluator::OptionalNumberRatio(eval) => { - let ratio = match eval(context) { - Some(v) => v, - None => return KleeneValue::Unknown, - }; - // See above for subtleties here. - self.kind - .evaluate(ratio, |v| expect!(NumberRatio, v).used_value()) - }, - Evaluator::Resolution(eval) => { - let v = eval(context).dppx(); - self.kind.evaluate(v, |v| { - expect!(Resolution, v).to_computed_value(context).dppx() - }) - }, - Evaluator::Enumerated { evaluator, .. } => { - let computed = self - .kind - .non_ranged_value() - .map(|v| *expect!(Enumerated, v)); - return evaluator(context, computed); - }, - Evaluator::BoolInteger(eval) => { - let computed = self - .kind - .non_ranged_value() - .map(|v| *expect!(BoolInteger, v)); - let boolean = eval(context); - computed.map_or(boolean, |v| v == boolean) - }, - }) - } -} - -/// A value found or expected in a expression. -/// -/// FIXME(emilio): How should calc() serialize in the Number / Integer / -/// BoolInteger / NumberRatio case, as computed or as specified value? -/// -/// If the first, this would need to store the relevant values. -/// -/// See: https://github.com/w3c/csswg-drafts/issues/1968 -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum QueryExpressionValue { - /// A length. - Length(Length), - /// An integer. - Integer(i32), - /// A floating point value. - Float(CSSFloat), - /// A boolean value, specified as an integer (i.e., either 0 or 1). - BoolInteger(bool), - /// A single non-negative number or two non-negative numbers separated by '/', - /// with optional whitespace on either side of the '/'. - NumberRatio(Ratio), - /// A resolution. - Resolution(Resolution), - /// An enumerated value, defined by the variant keyword table in the - /// feature's `mData` member. - Enumerated(KeywordDiscriminant), -} - -impl QueryExpressionValue { - fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result - where - W: fmt::Write, - { - match *self { - QueryExpressionValue::Length(ref l) => l.to_css(dest), - QueryExpressionValue::Integer(v) => v.to_css(dest), - QueryExpressionValue::Float(v) => v.to_css(dest), - QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }), - QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest), - QueryExpressionValue::Resolution(ref r) => r.to_css(dest), - QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator { - Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)), - _ => unreachable!(), - }, - } - } - - fn parse<'i, 't>( - for_feature: &QueryFeatureDescription, - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<QueryExpressionValue, ParseError<'i>> { - Ok(match for_feature.evaluator { - Evaluator::OptionalLength(..) | Evaluator::Length(..) => { - let length = Length::parse(context, input)?; - QueryExpressionValue::Length(length) - }, - Evaluator::Integer(..) => { - let integer = Integer::parse(context, input)?; - QueryExpressionValue::Integer(integer.value()) - }, - Evaluator::BoolInteger(..) => { - let integer = Integer::parse_non_negative(context, input)?; - let value = integer.value(); - if value > 1 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - QueryExpressionValue::BoolInteger(value == 1) - }, - Evaluator::Float(..) => { - let number = Number::parse(context, input)?; - QueryExpressionValue::Float(number.get()) - }, - Evaluator::OptionalNumberRatio(..) | Evaluator::NumberRatio(..) => { - use crate::values::specified::Ratio as SpecifiedRatio; - let ratio = SpecifiedRatio::parse(context, input)?; - QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get())) - }, - Evaluator::Resolution(..) => { - QueryExpressionValue::Resolution(Resolution::parse(context, input)?) - }, - Evaluator::Enumerated { parser, .. } => { - QueryExpressionValue::Enumerated(parser(context, input)?) - }, - }) - } -} diff --git a/components/style/queries/mod.rs b/components/style/queries/mod.rs deleted file mode 100644 index ec11ab3721e..00000000000 --- a/components/style/queries/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -/* 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/. */ - -//! Code shared between [media queries][mq] and [container queries][cq]. -//! -//! [mq]: https://drafts.csswg.org/mediaqueries/ -//! [cq]: https://drafts.csswg.org/css-contain-3/#container-rule - -pub mod condition; - -#[macro_use] -pub mod feature; -pub mod feature_expression; -pub mod values; - -pub use self::condition::QueryCondition; -pub use self::feature::FeatureFlags; -pub use self::feature_expression::{FeatureType, QueryFeatureExpression}; diff --git a/components/style/queries/values.rs b/components/style/queries/values.rs deleted file mode 100644 index f4934408c4d..00000000000 --- a/components/style/queries/values.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* 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/. */ - -//! Common feature values between media and container features. - -use app_units::Au; -use euclid::default::Size2D; - -/// The orientation media / container feature. -/// https://drafts.csswg.org/mediaqueries-5/#orientation -/// https://drafts.csswg.org/css-contain-3/#orientation -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum Orientation { - Portrait, - Landscape, -} - -impl Orientation { - /// A helper to evaluate a orientation query given a generic size getter. - pub fn eval(size: Size2D<Au>, value: Option<Self>) -> bool { - let query_orientation = match value { - Some(v) => v, - None => return true, - }; - - // Per spec, square viewports should be 'portrait' - let is_landscape = size.width > size.height; - match query_orientation { - Self::Landscape => is_landscape, - Self::Portrait => !is_landscape, - } - } -} diff --git a/components/style/rule_cache.rs b/components/style/rule_cache.rs deleted file mode 100644 index 604a92b724a..00000000000 --- a/components/style/rule_cache.rs +++ /dev/null @@ -1,187 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A cache from rule node to computed values, in order to cache reset -//! properties. - -use crate::logical_geometry::WritingMode; -use crate::properties::{ComputedValues, StyleBuilder}; -use crate::rule_tree::StrongRuleNode; -use crate::selector_parser::PseudoElement; -use crate::shared_lock::StylesheetGuards; -use crate::values::computed::NonNegativeLength; -use fxhash::FxHashMap; -use servo_arc::Arc; -use smallvec::SmallVec; - -/// The conditions for caching and matching a style in the rule cache. -#[derive(Clone, Debug, Default)] -pub struct RuleCacheConditions { - uncacheable: bool, - font_size: Option<NonNegativeLength>, - writing_mode: Option<WritingMode>, -} - -impl RuleCacheConditions { - /// Sets the style as depending in the font-size value. - pub fn set_font_size_dependency(&mut self, font_size: NonNegativeLength) { - debug_assert!(self.font_size.map_or(true, |f| f == font_size)); - self.font_size = Some(font_size); - } - - /// Sets the style as uncacheable. - pub fn set_uncacheable(&mut self) { - self.uncacheable = true; - } - - /// Sets the style as depending in the writing-mode value `writing_mode`. - pub fn set_writing_mode_dependency(&mut self, writing_mode: WritingMode) { - debug_assert!(self.writing_mode.map_or(true, |wm| wm == writing_mode)); - self.writing_mode = Some(writing_mode); - } - - /// Returns whether the current style's reset properties are cacheable. - fn cacheable(&self) -> bool { - !self.uncacheable - } - - /// Returns whether `style` matches the conditions. - fn matches(&self, style: &StyleBuilder) -> bool { - if self.uncacheable { - return false; - } - - if let Some(fs) = self.font_size { - if style.get_font().clone_font_size().computed_size != fs { - return false; - } - } - - if let Some(wm) = self.writing_mode { - if style.writing_mode != wm { - return false; - } - } - - true - } -} - -/// A TLS cache from rules matched to computed values. -pub struct RuleCache { - // FIXME(emilio): Consider using LRUCache or something like that? - map: FxHashMap<StrongRuleNode, SmallVec<[(RuleCacheConditions, Arc<ComputedValues>); 1]>>, -} - -impl RuleCache { - /// Creates an empty `RuleCache`. - pub fn new() -> Self { - Self { - map: FxHashMap::default(), - } - } - - /// Walk the rule tree and return a rule node for using as the key - /// for rule cache. - /// - /// It currently skips a rule node when it is neither from a style - /// rule, nor containing any declaration of reset property. We don't - /// skip style rule so that we don't need to walk a long way in the - /// worst case. Skipping declarations rule nodes should be enough - /// to address common cases that rule cache would fail to share - /// when using the rule node directly, like preshint, style attrs, - /// and animations. - fn get_rule_node_for_cache<'r>( - guards: &StylesheetGuards, - mut rule_node: Option<&'r StrongRuleNode>, - ) -> Option<&'r StrongRuleNode> { - while let Some(node) = rule_node { - match node.style_source() { - Some(s) => match s.as_declarations() { - Some(decls) => { - let cascade_level = node.cascade_level(); - let decls = decls.read_with(cascade_level.guard(guards)); - if decls.contains_any_reset() { - break; - } - }, - None => break, - }, - None => {}, - } - rule_node = node.parent(); - } - rule_node - } - - /// Finds a node in the properties matched cache. - /// - /// This needs to receive a `StyleBuilder` with the `early` properties - /// already applied. - pub fn find( - &self, - guards: &StylesheetGuards, - builder_with_early_props: &StyleBuilder, - ) -> Option<&ComputedValues> { - // A pseudo-element with property restrictions can result in different - // computed values if it's also used for a non-pseudo. - if builder_with_early_props - .pseudo - .and_then(|p| p.property_restriction()) - .is_some() - { - return None; - } - - let rules = builder_with_early_props.rules.as_ref(); - let rules = Self::get_rule_node_for_cache(guards, rules)?; - let cached_values = self.map.get(rules)?; - - for &(ref conditions, ref values) in cached_values.iter() { - if conditions.matches(builder_with_early_props) { - debug!("Using cached reset style with conditions {:?}", conditions); - return Some(&**values); - } - } - None - } - - /// Inserts a node into the rules cache if possible. - /// - /// Returns whether the style was inserted into the cache. - pub fn insert_if_possible( - &mut self, - guards: &StylesheetGuards, - style: &Arc<ComputedValues>, - pseudo: Option<&PseudoElement>, - conditions: &RuleCacheConditions, - ) -> bool { - if !conditions.cacheable() { - return false; - } - - // A pseudo-element with property restrictions can result in different - // computed values if it's also used for a non-pseudo. - if pseudo.and_then(|p| p.property_restriction()).is_some() { - return false; - } - - let rules = style.rules.as_ref(); - let rules = match Self::get_rule_node_for_cache(guards, rules) { - Some(r) => r.clone(), - None => return false, - }; - - debug!( - "Inserting cached reset style with conditions {:?}", - conditions - ); - self.map - .entry(rules) - .or_insert_with(SmallVec::new) - .push((conditions.clone(), style.clone())); - - true - } -} diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs deleted file mode 100644 index 058d6823179..00000000000 --- a/components/style/rule_collector.rs +++ /dev/null @@ -1,505 +0,0 @@ -/* 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/. */ - -//! Collects a series of applicable rules for a given element. - -use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; -use crate::dom::{TElement, TNode, TShadowRoot}; -use crate::properties::{AnimationDeclarations, PropertyDeclarationBlock}; -use crate::rule_tree::{CascadeLevel, ShadowCascadeOrder}; -use crate::selector_map::SelectorMap; -use crate::selector_parser::PseudoElement; -use crate::shared_lock::Locked; -use crate::stylesheets::{layer_rule::LayerOrder, Origin}; -use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; -use selectors::matching::{MatchingContext, MatchingMode}; -use servo_arc::ArcBorrow; -use smallvec::SmallVec; - -/// This is a bit of a hack so <svg:use> matches the rules of the enclosing -/// tree. -/// -/// This function returns the containing shadow host ignoring <svg:use> shadow -/// trees, since those match the enclosing tree's rules. -/// -/// Only a handful of places need to really care about this. This is not a -/// problem for invalidation and that kind of stuff because they still don't -/// match rules based on elements outside of the shadow tree, and because the -/// <svg:use> subtrees are immutable and recreated each time the source tree -/// changes. -/// -/// We historically allow cross-document <svg:use> to have these rules applied, -/// but I think that's not great. Gecko is the only engine supporting that. -/// -/// See https://github.com/w3c/svgwg/issues/504 for the relevant spec -/// discussion. -#[inline] -pub fn containing_shadow_ignoring_svg_use<E: TElement>( - element: E, -) -> Option<<E::ConcreteNode as TNode>::ConcreteShadowRoot> { - let mut shadow = element.containing_shadow()?; - loop { - let host = shadow.host(); - let host_is_svg_use_element = - host.is_svg_element() && host.local_name() == &**local_name!("use"); - if !host_is_svg_use_element { - return Some(shadow); - } - debug_assert!( - shadow.style_data().is_none(), - "We allow no stylesheets in <svg:use> subtrees" - ); - shadow = host.containing_shadow()?; - } -} - -/// An object that we use with all the intermediate state needed for the -/// cascade. -/// -/// This is done basically to be able to organize the cascade in smaller -/// functions, and be able to reason about it easily. -pub struct RuleCollector<'a, 'b: 'a, E> -where - E: TElement, -{ - element: E, - rule_hash_target: E, - stylist: &'a Stylist, - pseudo_element: Option<&'a PseudoElement>, - style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, - smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, - animation_declarations: AnimationDeclarations, - rule_inclusion: RuleInclusion, - rules: &'a mut ApplicableDeclarationList, - context: &'a mut MatchingContext<'b, E::Impl>, - matches_user_and_content_rules: bool, - matches_document_author_rules: bool, - in_sort_scope: bool, -} - -impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E> -where - E: TElement, -{ - /// Trivially construct a new collector. - pub fn new( - stylist: &'a Stylist, - element: E, - pseudo_element: Option<&'a PseudoElement>, - style_attribute: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, - smil_override: Option<ArcBorrow<'a, Locked<PropertyDeclarationBlock>>>, - animation_declarations: AnimationDeclarations, - rule_inclusion: RuleInclusion, - rules: &'a mut ApplicableDeclarationList, - context: &'a mut MatchingContext<'b, E::Impl>, - ) -> Self { - // When we're matching with matching_mode = - // `ForStatelessPseudoeElement`, the "target" for the rule hash is the - // element itself, since it's what's generating the pseudo-element. - let rule_hash_target = match context.matching_mode() { - MatchingMode::ForStatelessPseudoElement => element, - MatchingMode::Normal => element.rule_hash_target(), - }; - - let matches_user_and_content_rules = rule_hash_target.matches_user_and_content_rules(); - - // Gecko definitely has pseudo-elements with style attributes, like - // ::-moz-color-swatch. - debug_assert!( - cfg!(feature = "gecko") || style_attribute.is_none() || pseudo_element.is_none(), - "Style attributes do not apply to pseudo-elements" - ); - debug_assert!(pseudo_element.map_or(true, |p| !p.is_precomputed())); - - Self { - element, - rule_hash_target, - stylist, - pseudo_element, - style_attribute, - smil_override, - animation_declarations, - rule_inclusion, - context, - rules, - matches_user_and_content_rules, - matches_document_author_rules: matches_user_and_content_rules, - in_sort_scope: false, - } - } - - /// Sets up the state necessary to collect rules from a given DOM tree - /// (either the document tree, or a shadow tree). - /// - /// All rules in the same tree need to be matched together, and this - /// function takes care of sorting them by specificity and source order. - #[inline] - fn in_tree(&mut self, host: Option<E>, f: impl FnOnce(&mut Self)) { - debug_assert!(!self.in_sort_scope, "Nested sorting makes no sense"); - let start = self.rules.len(); - self.in_sort_scope = true; - let old_host = self.context.current_host.take(); - self.context.current_host = host.map(|e| e.opaque()); - f(self); - if start != self.rules.len() { - self.rules[start..].sort_unstable_by_key(|block| { - (block.layer_order(), block.specificity, block.source_order()) - }); - } - self.context.current_host = old_host; - self.in_sort_scope = false; - } - - #[inline] - fn in_shadow_tree(&mut self, host: E, f: impl FnOnce(&mut Self)) { - self.in_tree(Some(host), f); - } - - fn collect_stylist_rules(&mut self, origin: Origin) { - let cascade_level = match origin { - Origin::UserAgent => CascadeLevel::UANormal, - Origin::User => CascadeLevel::UserNormal, - Origin::Author => CascadeLevel::same_tree_author_normal(), - }; - - let cascade_data = self.stylist.cascade_data().borrow_for_origin(origin); - let map = match cascade_data.normal_rules(self.pseudo_element) { - Some(m) => m, - None => return, - }; - - self.in_tree(None, |collector| { - collector.collect_rules_in_map(map, cascade_level, cascade_data); - }); - } - - fn collect_user_agent_rules(&mut self) { - self.collect_stylist_rules(Origin::UserAgent); - } - - fn collect_user_rules(&mut self) { - if !self.matches_user_and_content_rules { - return; - } - - self.collect_stylist_rules(Origin::User); - } - - /// Presentational hints. - /// - /// These go before author rules, but after user rules, see: - /// https://drafts.csswg.org/css-cascade/#preshint - fn collect_presentational_hints(&mut self) { - if self.pseudo_element.is_some() { - return; - } - - let length_before_preshints = self.rules.len(); - self.element - .synthesize_presentational_hints_for_legacy_attributes( - self.context.visited_handling(), - self.rules, - ); - if cfg!(debug_assertions) { - if self.rules.len() != length_before_preshints { - for declaration in &self.rules[length_before_preshints..] { - assert_eq!(declaration.level(), CascadeLevel::PresHints); - } - } - } - } - - #[inline] - fn collect_rules_in_list( - &mut self, - part_rules: &[Rule], - cascade_level: CascadeLevel, - cascade_data: &CascadeData, - ) { - debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); - SelectorMap::get_matching_rules( - self.element, - part_rules, - &mut self.rules, - &mut self.context, - cascade_level, - cascade_data, - &self.stylist, - ); - } - - #[inline] - fn collect_rules_in_map( - &mut self, - map: &SelectorMap<Rule>, - cascade_level: CascadeLevel, - cascade_data: &CascadeData, - ) { - debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); - map.get_all_matching_rules( - self.element, - self.rule_hash_target, - &mut self.rules, - &mut self.context, - cascade_level, - cascade_data, - &self.stylist, - ); - } - - /// Collects the rules for the ::slotted pseudo-element and the :host - /// pseudo-class. - fn collect_host_and_slotted_rules(&mut self) { - let mut slots = SmallVec::<[_; 3]>::new(); - let mut current = self.rule_hash_target.assigned_slot(); - let mut shadow_cascade_order = ShadowCascadeOrder::for_outermost_shadow_tree(); - - while let Some(slot) = current { - debug_assert!( - self.matches_user_and_content_rules, - "We should not slot NAC anywhere" - ); - slots.push(slot); - current = slot.assigned_slot(); - shadow_cascade_order.dec(); - } - - self.collect_host_rules(shadow_cascade_order); - - // Match slotted rules in reverse order, so that the outer slotted rules - // come before the inner rules (and thus have less priority). - for slot in slots.iter().rev() { - shadow_cascade_order.inc(); - - let shadow = slot.containing_shadow().unwrap(); - let data = match shadow.style_data() { - Some(d) => d, - None => continue, - }; - let slotted_rules = match data.slotted_rules(self.pseudo_element) { - Some(r) => r, - None => continue, - }; - - self.in_shadow_tree(shadow.host(), |collector| { - let cascade_level = CascadeLevel::AuthorNormal { - shadow_cascade_order, - }; - collector.collect_rules_in_map(slotted_rules, cascade_level, data); - }); - } - } - - fn collect_rules_from_containing_shadow_tree(&mut self) { - if !self.matches_user_and_content_rules { - return; - } - - let containing_shadow = containing_shadow_ignoring_svg_use(self.rule_hash_target); - let containing_shadow = match containing_shadow { - Some(s) => s, - None => return, - }; - - self.matches_document_author_rules = false; - - let cascade_data = match containing_shadow.style_data() { - Some(c) => c, - None => return, - }; - - let cascade_level = CascadeLevel::same_tree_author_normal(); - self.in_shadow_tree(containing_shadow.host(), |collector| { - if let Some(map) = cascade_data.normal_rules(collector.pseudo_element) { - collector.collect_rules_in_map(map, cascade_level, cascade_data); - } - - // Collect rules from :host::part() and such - let hash_target = collector.rule_hash_target; - if !hash_target.has_part_attr() { - return; - } - - let part_rules = match cascade_data.part_rules(collector.pseudo_element) { - Some(p) => p, - None => return, - }; - - hash_target.each_part(|part| { - if let Some(part_rules) = part_rules.get(&part.0) { - collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); - } - }); - }); - } - - /// Collects the rules for the :host pseudo-class. - fn collect_host_rules(&mut self, shadow_cascade_order: ShadowCascadeOrder) { - let shadow = match self.rule_hash_target.shadow_root() { - Some(s) => s, - None => return, - }; - - let style_data = match shadow.style_data() { - Some(d) => d, - None => return, - }; - - let host_rules = match style_data.host_rules(self.pseudo_element) { - Some(rules) => rules, - None => return, - }; - - let rule_hash_target = self.rule_hash_target; - self.in_shadow_tree(rule_hash_target, |collector| { - let cascade_level = CascadeLevel::AuthorNormal { - shadow_cascade_order, - }; - collector.collect_rules_in_map(host_rules, cascade_level, style_data); - }); - } - - fn collect_document_author_rules(&mut self) { - if !self.matches_document_author_rules { - return; - } - - self.collect_stylist_rules(Origin::Author); - } - - fn collect_part_rules_from_outer_trees(&mut self) { - if !self.rule_hash_target.has_part_attr() { - return; - } - - let mut inner_shadow = match self.rule_hash_target.containing_shadow() { - Some(s) => s, - None => return, - }; - - let mut shadow_cascade_order = ShadowCascadeOrder::for_innermost_containing_tree(); - - let mut parts = SmallVec::<[_; 3]>::new(); - self.rule_hash_target.each_part(|p| parts.push(p.clone())); - - loop { - if parts.is_empty() { - return; - } - - let inner_shadow_host = inner_shadow.host(); - let outer_shadow = inner_shadow_host.containing_shadow(); - let cascade_data = match outer_shadow { - Some(shadow) => shadow.style_data(), - None => Some( - self.stylist - .cascade_data() - .borrow_for_origin(Origin::Author), - ), - }; - - if let Some(cascade_data) = cascade_data { - if let Some(part_rules) = cascade_data.part_rules(self.pseudo_element) { - let containing_host = outer_shadow.map(|s| s.host()); - let cascade_level = CascadeLevel::AuthorNormal { - shadow_cascade_order, - }; - self.in_tree(containing_host, |collector| { - for p in &parts { - if let Some(part_rules) = part_rules.get(&p.0) { - collector.collect_rules_in_list( - part_rules, - cascade_level, - cascade_data, - ); - } - } - }); - shadow_cascade_order.inc(); - } - } - - inner_shadow = match outer_shadow { - Some(s) => s, - None => break, // Nowhere to export to. - }; - - let mut new_parts = SmallVec::new(); - for part in &parts { - inner_shadow_host.each_exported_part(part, |exported_part| { - new_parts.push(exported_part.clone()); - }); - } - parts = new_parts; - } - } - - fn collect_style_attribute(&mut self) { - if let Some(sa) = self.style_attribute { - self.rules - .push(ApplicableDeclarationBlock::from_declarations( - sa.clone_arc(), - CascadeLevel::same_tree_author_normal(), - LayerOrder::style_attribute(), - )); - } - } - - fn collect_animation_rules(&mut self) { - if let Some(so) = self.smil_override { - self.rules - .push(ApplicableDeclarationBlock::from_declarations( - so.clone_arc(), - CascadeLevel::SMILOverride, - LayerOrder::root(), - )); - } - - // The animations sheet (CSS animations, script-generated - // animations, and CSS transitions that are no longer tied to CSS - // markup). - if let Some(anim) = self.animation_declarations.animations.take() { - self.rules - .push(ApplicableDeclarationBlock::from_declarations( - anim, - CascadeLevel::Animations, - LayerOrder::root(), - )); - } - - // The transitions sheet (CSS transitions that are tied to CSS - // markup). - if let Some(anim) = self.animation_declarations.transitions.take() { - self.rules - .push(ApplicableDeclarationBlock::from_declarations( - anim, - CascadeLevel::Transitions, - LayerOrder::root(), - )); - } - } - - /// Collects all the rules, leaving the result in `self.rules`. - /// - /// Note that `!important` rules are handled during rule tree insertion. - pub fn collect_all(mut self) { - self.collect_user_agent_rules(); - self.collect_user_rules(); - if self.rule_inclusion == RuleInclusion::DefaultOnly { - return; - } - self.collect_presentational_hints(); - // FIXME(emilio): Should the author styles enabled stuff avoid the - // presentational hints from getting pushed? See bug 1505770. - if self.stylist.author_styles_enabled() == AuthorStylesEnabled::No { - return; - } - self.collect_host_and_slotted_rules(); - self.collect_rules_from_containing_shadow_tree(); - self.collect_document_author_rules(); - self.collect_style_attribute(); - self.collect_part_rules_from_outer_trees(); - self.collect_animation_rules(); - } -} diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs deleted file mode 100644 index 85584a0e224..00000000000 --- a/components/style/rule_tree/core.rs +++ /dev/null @@ -1,772 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] - -use crate::applicable_declarations::CascadePriority; -use crate::shared_lock::StylesheetGuards; -use crate::stylesheets::layer_rule::LayerOrder; -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; -use parking_lot::RwLock; -use smallvec::SmallVec; -use std::fmt; -use std::hash; -use std::io::Write; -use std::mem; -use std::ptr; -use std::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering}; - -use super::map::{Entry, Map}; -use super::unsafe_box::UnsafeBox; -use super::{CascadeLevel, StyleSource}; - -/// The rule tree, the structure servo uses to preserve the results of selector -/// matching. -/// -/// This is organized as a tree of rules. When a node matches a set of rules, -/// they're inserted in order in the tree, starting with the less specific one. -/// -/// When a rule is inserted in the tree, other elements may share the path up to -/// a given rule. If that's the case, we don't duplicate child nodes, but share -/// them. -/// -/// When the rule node refcount drops to zero, it doesn't get freed. It gets -/// instead put into a free list, and it is potentially GC'd after a while. -/// -/// That way, a rule node that represents a likely-to-match-again rule (like a -/// :hover rule) can be reused if we haven't GC'd it yet. -#[derive(Debug)] -pub struct RuleTree { - root: StrongRuleNode, -} - -impl Drop for RuleTree { - fn drop(&mut self) { - unsafe { self.swap_free_list_and_gc(ptr::null_mut()) } - } -} - -impl MallocSizeOf for RuleTree { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - let mut stack = SmallVec::<[_; 32]>::new(); - stack.push(self.root.clone()); - - while let Some(node) = stack.pop() { - n += unsafe { ops.malloc_size_of(&*node.p) }; - let children = node.p.children.read(); - children.shallow_size_of(ops); - for c in &*children { - stack.push(unsafe { c.upgrade() }); - } - } - - n - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -struct ChildKey(CascadePriority, ptr::NonNull<()>); -unsafe impl Send for ChildKey {} -unsafe impl Sync for ChildKey {} - -impl RuleTree { - /// Construct a new rule tree. - pub fn new() -> Self { - RuleTree { - root: StrongRuleNode::new(Box::new(RuleNode::root())), - } - } - - /// Get the root rule node. - pub fn root(&self) -> &StrongRuleNode { - &self.root - } - - /// This can only be called when no other threads is accessing this tree. - pub fn gc(&self) { - unsafe { self.swap_free_list_and_gc(RuleNode::DANGLING_PTR) } - } - - /// This can only be called when no other threads is accessing this tree. - pub fn maybe_gc(&self) { - #[cfg(debug_assertions)] - self.maybe_dump_stats(); - - if self.root.p.approximate_free_count.load(Ordering::Relaxed) > RULE_TREE_GC_INTERVAL { - self.gc(); - } - } - - #[cfg(debug_assertions)] - fn maybe_dump_stats(&self) { - use itertools::Itertools; - use std::cell::Cell; - use std::time::{Duration, Instant}; - - if !log_enabled!(log::Level::Trace) { - return; - } - - const RULE_TREE_STATS_INTERVAL: Duration = Duration::from_secs(2); - - thread_local! { - pub static LAST_STATS: Cell<Instant> = Cell::new(Instant::now()); - }; - - let should_dump = LAST_STATS.with(|s| { - let now = Instant::now(); - if now.duration_since(s.get()) < RULE_TREE_STATS_INTERVAL { - return false; - } - s.set(now); - true - }); - - if !should_dump { - return; - } - - let mut children_count = fxhash::FxHashMap::default(); - - let mut stack = SmallVec::<[_; 32]>::new(); - stack.push(self.root.clone()); - while let Some(node) = stack.pop() { - let children = node.p.children.read(); - *children_count.entry(children.len()).or_insert(0) += 1; - for c in &*children { - stack.push(unsafe { c.upgrade() }); - } - } - - trace!("Rule tree stats:"); - let counts = children_count.keys().sorted(); - for count in counts { - trace!(" {} - {}", count, children_count[count]); - } - } - - /// Steals the free list and drops its contents. - unsafe fn swap_free_list_and_gc(&self, ptr: *mut RuleNode) { - let root = &self.root.p; - - debug_assert!(!root.next_free.load(Ordering::Relaxed).is_null()); - - // Reset the approximate free count to zero, as we are going to steal - // the free list. - root.approximate_free_count.store(0, Ordering::Relaxed); - - // Steal the free list head. Memory loads on nodes while iterating it - // must observe any prior changes that occured so this requires - // acquire ordering, but there are no writes that need to be kept - // before this swap so there is no need for release. - let mut head = root.next_free.swap(ptr, Ordering::Acquire); - - while head != RuleNode::DANGLING_PTR { - debug_assert!(!head.is_null()); - - let mut node = UnsafeBox::from_raw(head); - - // The root node cannot go on the free list. - debug_assert!(node.root.is_some()); - - // The refcount of nodes on the free list never goes below 1. - debug_assert!(node.refcount.load(Ordering::Relaxed) > 0); - - // No one else is currently writing to that field. Get the address - // of the next node in the free list and replace it with null, - // other threads will now consider that this node is not on the - // free list. - head = node.next_free.swap(ptr::null_mut(), Ordering::Relaxed); - - // This release write synchronises with the acquire fence in - // `WeakRuleNode::upgrade`, making sure that if `upgrade` observes - // decrements the refcount to 0, it will also observe the - // `node.next_free` swap to null above. - if node.refcount.fetch_sub(1, Ordering::Release) == 1 { - // And given it observed the null swap above, it will need - // `pretend_to_be_on_free_list` to finish its job, writing - // `RuleNode::DANGLING_PTR` in `node.next_free`. - RuleNode::pretend_to_be_on_free_list(&node); - - // Drop this node now that we just observed its refcount going - // down to zero. - RuleNode::drop_without_free_list(&mut node); - } - } - } -} - -/// The number of RuleNodes added to the free list before we will consider -/// doing a GC when calling maybe_gc(). (The value is copied from Gecko, -/// where it likely did not result from a rigorous performance analysis.) -const RULE_TREE_GC_INTERVAL: usize = 300; - -/// A node in the rule tree. -struct RuleNode { - /// The root node. Only the root has no root pointer, for obvious reasons. - root: Option<WeakRuleNode>, - - /// The parent rule node. Only the root has no parent. - parent: Option<StrongRuleNode>, - - /// The actual style source, either coming from a selector in a StyleRule, - /// or a raw property declaration block (like the style attribute). - /// - /// None for the root node. - source: Option<StyleSource>, - - /// The cascade level + layer order this rule is positioned at. - cascade_priority: CascadePriority, - - /// The refcount of this node. - /// - /// Starts at one. Incremented in `StrongRuleNode::clone` and - /// `WeakRuleNode::upgrade`. Decremented in `StrongRuleNode::drop` - /// and `RuleTree::swap_free_list_and_gc`. - /// - /// If a non-root node's refcount reaches zero, it is incremented back to at - /// least one in `RuleNode::pretend_to_be_on_free_list` until the caller who - /// observed it dropping to zero had a chance to try to remove it from its - /// parent's children list. - /// - /// The refcount should never be decremented to zero if the value in - /// `next_free` is not null. - refcount: AtomicUsize, - - /// Only used for the root, stores the number of free rule nodes that are - /// around. - approximate_free_count: AtomicUsize, - - /// The children of a given rule node. Children remove themselves from here - /// when they go away. - children: RwLock<Map<ChildKey, WeakRuleNode>>, - - /// This field has two different meanings depending on whether this is the - /// root node or not. - /// - /// If it is the root, it represents the head of the free list. It may be - /// null, which means the free list is gone because the tree was dropped, - /// and it may be `RuleNode::DANGLING_PTR`, which means the free list is - /// empty. - /// - /// If it is not the root node, this field is either null if the node is - /// not on the free list, `RuleNode::DANGLING_PTR` if it is the last item - /// on the free list or the node is pretending to be on the free list, or - /// any valid non-null pointer representing the next item on the free list - /// after this one. - /// - /// See `RuleNode::push_on_free_list`, `swap_free_list_and_gc`, and - /// `WeakRuleNode::upgrade`. - /// - /// Two threads should never attempt to put the same node on the free list - /// both at the same time. - next_free: AtomicPtr<RuleNode>, -} - -// On Gecko builds, hook into the leak checking machinery. -#[cfg(feature = "gecko_refcount_logging")] -mod gecko_leak_checking { - use super::RuleNode; - use std::mem::size_of; - use std::os::raw::{c_char, c_void}; - - extern "C" { - fn NS_LogCtor(aPtr: *mut c_void, aTypeName: *const c_char, aSize: u32); - fn NS_LogDtor(aPtr: *mut c_void, aTypeName: *const c_char, aSize: u32); - } - static NAME: &'static [u8] = b"RuleNode\0"; - - /// Logs the creation of a heap-allocated object to Gecko's leak-checking machinery. - pub(super) fn log_ctor(ptr: *const RuleNode) { - let s = NAME as *const [u8] as *const u8 as *const c_char; - unsafe { - NS_LogCtor(ptr as *mut c_void, s, size_of::<RuleNode>() as u32); - } - } - - /// Logs the destruction of a heap-allocated object to Gecko's leak-checking machinery. - pub(super) fn log_dtor(ptr: *const RuleNode) { - let s = NAME as *const [u8] as *const u8 as *const c_char; - unsafe { - NS_LogDtor(ptr as *mut c_void, s, size_of::<RuleNode>() as u32); - } - } -} - -#[inline(always)] -fn log_new(_ptr: *const RuleNode) { - #[cfg(feature = "gecko_refcount_logging")] - gecko_leak_checking::log_ctor(_ptr); -} - -#[inline(always)] -fn log_drop(_ptr: *const RuleNode) { - #[cfg(feature = "gecko_refcount_logging")] - gecko_leak_checking::log_dtor(_ptr); -} - -impl RuleNode { - const DANGLING_PTR: *mut Self = ptr::NonNull::dangling().as_ptr(); - - unsafe fn new( - root: WeakRuleNode, - parent: StrongRuleNode, - source: StyleSource, - cascade_priority: CascadePriority, - ) -> Self { - debug_assert!(root.p.parent.is_none()); - RuleNode { - root: Some(root), - parent: Some(parent), - source: Some(source), - cascade_priority, - refcount: AtomicUsize::new(1), - children: Default::default(), - approximate_free_count: AtomicUsize::new(0), - next_free: AtomicPtr::new(ptr::null_mut()), - } - } - - fn root() -> Self { - RuleNode { - root: None, - parent: None, - source: None, - cascade_priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), - refcount: AtomicUsize::new(1), - approximate_free_count: AtomicUsize::new(0), - children: Default::default(), - next_free: AtomicPtr::new(RuleNode::DANGLING_PTR), - } - } - - fn key(&self) -> ChildKey { - ChildKey( - self.cascade_priority, - self.source - .as_ref() - .expect("Called key() on the root node") - .key(), - ) - } - - /// Drops a node without ever putting it on the free list. - /// - /// Note that the node may not be dropped if we observe that its refcount - /// isn't zero anymore when we write-lock its parent's children map to - /// remove it. - /// - /// This loops over parents of dropped nodes if their own refcount reaches - /// zero to avoid recursion when dropping deep hierarchies of nodes. - /// - /// For non-root nodes, this should always be preceded by a call of - /// `RuleNode::pretend_to_be_on_free_list`. - unsafe fn drop_without_free_list(this: &mut UnsafeBox<Self>) { - // We clone the box and shadow the original one to be able to loop - // over its ancestors if they also need to be dropped. - let mut this = UnsafeBox::clone(this); - loop { - // If the node has a parent, we need to remove it from its parent's - // children list. - if let Some(parent) = this.parent.as_ref() { - debug_assert!(!this.next_free.load(Ordering::Relaxed).is_null()); - - // We lock the parent's children list, which means no other - // thread will have any more opportunity to resurrect the node - // anymore. - let mut children = parent.p.children.write(); - - this.next_free.store(ptr::null_mut(), Ordering::Relaxed); - - // We decrement the counter to remove the "pretend to be - // on the free list" reference. - let old_refcount = this.refcount.fetch_sub(1, Ordering::Release); - debug_assert!(old_refcount != 0); - if old_refcount != 1 { - // Other threads resurrected this node and those references - // are still alive, we have nothing to do anymore. - return; - } - - // We finally remove the node from its parent's children list, - // there are now no other references to it and it cannot - // be resurrected anymore even after we unlock the list. - debug!( - "Remove from child list: {:?}, parent: {:?}", - this.as_mut_ptr(), - this.parent.as_ref().map(|p| p.p.as_mut_ptr()) - ); - let weak = children.remove(&this.key(), |node| node.p.key()).unwrap(); - assert_eq!(weak.p.as_mut_ptr(), this.as_mut_ptr()); - } else { - debug_assert_eq!(this.next_free.load(Ordering::Relaxed), ptr::null_mut()); - debug_assert_eq!(this.refcount.load(Ordering::Relaxed), 0); - } - - // We are going to drop this node for good this time, as per the - // usual refcounting protocol we need an acquire fence here before - // we run the destructor. - // - // See https://github.com/rust-lang/rust/pull/41714#issuecomment-298996916 - // for why it doesn't matter whether this is a load or a fence. - atomic::fence(Ordering::Acquire); - - // Remove the parent reference from the child to avoid - // recursively dropping it and putting it on the free list. - let parent = UnsafeBox::deref_mut(&mut this).parent.take(); - - // We now drop the actual box and its contents, no one should - // access the current value in `this` anymore. - log_drop(&*this); - UnsafeBox::drop(&mut this); - - if let Some(parent) = parent { - // We will attempt to drop the node's parent without the free - // list, so we clone the inner unsafe box and forget the - // original parent to avoid running its `StrongRuleNode` - // destructor which would attempt to use the free list if it - // still exists. - this = UnsafeBox::clone(&parent.p); - mem::forget(parent); - if this.refcount.fetch_sub(1, Ordering::Release) == 1 { - debug_assert_eq!(this.next_free.load(Ordering::Relaxed), ptr::null_mut()); - if this.root.is_some() { - RuleNode::pretend_to_be_on_free_list(&this); - } - // Parent also reached refcount zero, we loop to drop it. - continue; - } - } - - return; - } - } - - /// Pushes this node on the tree's free list. Returns false if the free list - /// is gone. Should only be called after we decremented a node's refcount - /// to zero and pretended to be on the free list. - unsafe fn push_on_free_list(this: &UnsafeBox<Self>) -> bool { - let root = &this.root.as_ref().unwrap().p; - - debug_assert!(this.refcount.load(Ordering::Relaxed) > 0); - debug_assert_eq!(this.next_free.load(Ordering::Relaxed), Self::DANGLING_PTR); - - // Increment the approximate free count by one. - root.approximate_free_count.fetch_add(1, Ordering::Relaxed); - - // If the compare-exchange operation fails in the loop, we will retry - // with the new head value, so this can be a relaxed load. - let mut head = root.next_free.load(Ordering::Relaxed); - - while !head.is_null() { - // Two threads can never attempt to push the same node on the free - // list both at the same time, so whoever else pushed a node on the - // free list cannot have done so with this node. - debug_assert_ne!(head, this.as_mut_ptr()); - - // Store the current head of the free list in this node. - this.next_free.store(head, Ordering::Relaxed); - - // Any thread acquiring the free list must observe the previous - // next_free changes that occured, hence the release ordering - // on success. - match root.next_free.compare_exchange_weak( - head, - this.as_mut_ptr(), - Ordering::Release, - Ordering::Relaxed, - ) { - Ok(_) => { - // This node is now on the free list, caller should not use - // the node anymore. - return true; - }, - Err(new_head) => head = new_head, - } - } - - // Tree was dropped and free list has been destroyed. We did not push - // this node on the free list but we still pretend to be on the free - // list to be ready to call `drop_without_free_list`. - false - } - - /// Makes the node pretend to be on the free list. This will increment the - /// refcount by 1 and store `Self::DANGLING_PTR` in `next_free`. This - /// method should only be called after caller decremented the refcount to - /// zero, with the null pointer stored in `next_free`. - unsafe fn pretend_to_be_on_free_list(this: &UnsafeBox<Self>) { - debug_assert_eq!(this.next_free.load(Ordering::Relaxed), ptr::null_mut()); - this.refcount.fetch_add(1, Ordering::Relaxed); - this.next_free.store(Self::DANGLING_PTR, Ordering::Release); - } - - fn as_mut_ptr(&self) -> *mut RuleNode { - self as *const RuleNode as *mut RuleNode - } -} - -pub(crate) struct WeakRuleNode { - p: UnsafeBox<RuleNode>, -} - -/// A strong reference to a rule node. -pub struct StrongRuleNode { - p: UnsafeBox<RuleNode>, -} - -#[cfg(feature = "servo")] -malloc_size_of_is_0!(StrongRuleNode); - -impl StrongRuleNode { - fn new(n: Box<RuleNode>) -> Self { - debug_assert_eq!(n.parent.is_none(), !n.source.is_some()); - - log_new(&*n); - - debug!("Creating rule node: {:p}", &*n); - - Self { - p: UnsafeBox::from_box(n), - } - } - - unsafe fn from_unsafe_box(p: UnsafeBox<RuleNode>) -> Self { - Self { p } - } - - unsafe fn downgrade(&self) -> WeakRuleNode { - WeakRuleNode { - p: UnsafeBox::clone(&self.p), - } - } - - /// Get the parent rule node of this rule node. - pub fn parent(&self) -> Option<&StrongRuleNode> { - self.p.parent.as_ref() - } - - pub(super) fn ensure_child( - &self, - root: &StrongRuleNode, - source: StyleSource, - cascade_priority: CascadePriority, - ) -> StrongRuleNode { - use parking_lot::RwLockUpgradableReadGuard; - - debug_assert!( - self.p.cascade_priority <= cascade_priority, - "Should be ordered (instead {:?} > {:?}), from {:?} and {:?}", - self.p.cascade_priority, - cascade_priority, - self.p.source, - source, - ); - - let key = ChildKey(cascade_priority, source.key()); - let children = self.p.children.upgradable_read(); - if let Some(child) = children.get(&key, |node| node.p.key()) { - // Sound to call because we read-locked the parent's children. - return unsafe { child.upgrade() }; - } - let mut children = RwLockUpgradableReadGuard::upgrade(children); - match children.entry(key, |node| node.p.key()) { - Entry::Occupied(child) => { - // Sound to call because we write-locked the parent's children. - unsafe { child.upgrade() } - }, - Entry::Vacant(entry) => unsafe { - let node = StrongRuleNode::new(Box::new(RuleNode::new( - root.downgrade(), - self.clone(), - source, - cascade_priority, - ))); - // Sound to call because we still own a strong reference to - // this node, through the `node` variable itself that we are - // going to return to the caller. - entry.insert(node.downgrade()); - node - }, - } - } - - /// Get the style source corresponding to this rule node. May return `None` - /// if it's the root node, which means that the node hasn't matched any - /// rules. - pub fn style_source(&self) -> Option<&StyleSource> { - self.p.source.as_ref() - } - - /// The cascade priority. - #[inline] - pub fn cascade_priority(&self) -> CascadePriority { - self.p.cascade_priority - } - - /// The cascade level. - #[inline] - pub fn cascade_level(&self) -> CascadeLevel { - self.cascade_priority().cascade_level() - } - - /// The importance. - #[inline] - pub fn importance(&self) -> crate::properties::Importance { - self.cascade_level().importance() - } - - /// Returns whether this node has any child, only intended for testing - /// purposes. - pub unsafe fn has_children_for_testing(&self) -> bool { - !self.p.children.read().is_empty() - } - - pub(super) fn dump<W: Write>(&self, guards: &StylesheetGuards, writer: &mut W, indent: usize) { - const INDENT_INCREMENT: usize = 4; - - for _ in 0..indent { - let _ = write!(writer, " "); - } - - let _ = writeln!( - writer, - " - {:p} (ref: {:?}, parent: {:?})", - &*self.p, - self.p.refcount.load(Ordering::Relaxed), - self.parent().map(|p| &*p.p as *const RuleNode) - ); - - for _ in 0..indent { - let _ = write!(writer, " "); - } - - if let Some(source) = self.style_source() { - source.dump(self.cascade_level().guard(guards), writer); - } else { - if indent != 0 { - warn!("How has this happened?"); - } - let _ = write!(writer, "(root)"); - } - - let _ = write!(writer, "\n"); - for child in &*self.p.children.read() { - unsafe { - child - .upgrade() - .dump(guards, writer, indent + INDENT_INCREMENT); - } - } - } -} - -impl Clone for StrongRuleNode { - fn clone(&self) -> Self { - debug!( - "{:p}: {:?}+", - &*self.p, - self.p.refcount.load(Ordering::Relaxed) - ); - debug_assert!(self.p.refcount.load(Ordering::Relaxed) > 0); - self.p.refcount.fetch_add(1, Ordering::Relaxed); - unsafe { StrongRuleNode::from_unsafe_box(UnsafeBox::clone(&self.p)) } - } -} - -impl Drop for StrongRuleNode { - #[cfg_attr(feature = "servo", allow(unused_mut))] - fn drop(&mut self) { - let node = &*self.p; - debug!("{:p}: {:?}-", node, node.refcount.load(Ordering::Relaxed)); - debug!( - "Dropping node: {:p}, root: {:?}, parent: {:?}", - node, - node.root.as_ref().map(|r| &*r.p as *const RuleNode), - node.parent.as_ref().map(|p| &*p.p as *const RuleNode) - ); - - let should_drop = { - debug_assert!(node.refcount.load(Ordering::Relaxed) > 0); - node.refcount.fetch_sub(1, Ordering::Release) == 1 - }; - - if !should_drop { - // The refcount didn't even drop zero yet, there is nothing for us - // to do anymore. - return; - } - - unsafe { - if node.root.is_some() { - // This is a non-root node and we just observed the refcount - // dropping to zero, we need to pretend to be on the free list - // to unstuck any thread who tried to resurrect this node first - // through `WeakRuleNode::upgrade`. - RuleNode::pretend_to_be_on_free_list(&self.p); - - // Attempt to push the node on the free list. This may fail - // if the free list is gone. - if RuleNode::push_on_free_list(&self.p) { - return; - } - } - - // Either this was the last reference of the root node, or the - // tree rule is gone and there is no free list anymore. Drop the - // node. - RuleNode::drop_without_free_list(&mut self.p); - } - } -} - -impl WeakRuleNode { - /// Upgrades this weak node reference, returning a strong one. - /// - /// Must be called with items stored in a node's children list. The children - /// list must at least be read-locked when this is called. - unsafe fn upgrade(&self) -> StrongRuleNode { - debug!("Upgrading weak node: {:p}", &*self.p); - - if self.p.refcount.fetch_add(1, Ordering::Relaxed) == 0 { - // We observed a refcount of 0, we need to wait for this node to - // be put on the free list. Resetting the `next_free` pointer to - // null is only done in `RuleNode::drop_without_free_list`, just - // before a release refcount decrement, so this acquire fence here - // makes sure that we observed the write to null before we loop - // until there is a non-null value. - atomic::fence(Ordering::Acquire); - while self.p.next_free.load(Ordering::Relaxed).is_null() {} - } - StrongRuleNode::from_unsafe_box(UnsafeBox::clone(&self.p)) - } -} - -impl fmt::Debug for StrongRuleNode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - (&*self.p as *const RuleNode).fmt(f) - } -} - -impl Eq for StrongRuleNode {} -impl PartialEq for StrongRuleNode { - fn eq(&self, other: &Self) -> bool { - &*self.p as *const RuleNode == &*other.p - } -} - -impl hash::Hash for StrongRuleNode { - fn hash<H>(&self, state: &mut H) - where - H: hash::Hasher, - { - (&*self.p as *const RuleNode).hash(state) - } -} - -// Large pages generate thousands of RuleNode objects. -size_of_test!(RuleNode, 80); -// StrongRuleNode should be pointer-sized even inside an option. -size_of_test!(Option<StrongRuleNode>, 8); diff --git a/components/style/rule_tree/level.rs b/components/style/rule_tree/level.rs deleted file mode 100644 index b8cbe55ed9c..00000000000 --- a/components/style/rule_tree/level.rs +++ /dev/null @@ -1,249 +0,0 @@ -/* 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/. */ - -#![forbid(unsafe_code)] - -use crate::properties::Importance; -use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards}; -use crate::stylesheets::Origin; - -/// The cascade level these rules are relevant at, as per[1][2][3]. -/// -/// Presentational hints for SVG and HTML are in the "author-level -/// zero-specificity" level, that is, right after user rules, and before author -/// rules. -/// -/// The order of variants declared here is significant, and must be in -/// _ascending_ order of precedence. -/// -/// See also [4] for the Shadow DOM bits. We rely on the invariant that rules -/// from outside the tree the element is in can't affect the element. -/// -/// The opposite is not true (i.e., :host and ::slotted) from an "inner" shadow -/// tree may affect an element connected to the document or an "outer" shadow -/// tree. -/// -/// [1]: https://drafts.csswg.org/css-cascade/#cascade-origin -/// [2]: https://drafts.csswg.org/css-cascade/#preshint -/// [3]: https://html.spec.whatwg.org/multipage/#presentational-hints -/// [4]: https://drafts.csswg.org/css-scoping/#shadow-cascading -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] -pub enum CascadeLevel { - /// Normal User-Agent rules. - UANormal, - /// User normal rules. - UserNormal, - /// Presentational hints. - PresHints, - /// Shadow DOM styles from author styles. - AuthorNormal { - /// The order in the shadow tree hierarchy. This number is relative to - /// the tree of the element, and thus the only invariants that need to - /// be preserved is: - /// - /// * Zero is the same tree as the element that matched the rule. This - /// is important so that we can optimize style attribute insertions. - /// - /// * The levels are ordered in accordance with - /// https://drafts.csswg.org/css-scoping/#shadow-cascading - shadow_cascade_order: ShadowCascadeOrder, - }, - /// SVG SMIL animations. - SMILOverride, - /// CSS animations and script-generated animations. - Animations, - /// Author-supplied important rules. - AuthorImportant { - /// The order in the shadow tree hierarchy, inverted, so that PartialOrd - /// does the right thing. - shadow_cascade_order: ShadowCascadeOrder, - }, - /// User important rules. - UserImportant, - /// User-agent important rules. - UAImportant, - /// Transitions - Transitions, -} - -impl CascadeLevel { - /// Convert this level from "unimportant" to "important". - pub fn important(&self) -> Self { - match *self { - Self::UANormal => Self::UAImportant, - Self::UserNormal => Self::UserImportant, - Self::AuthorNormal { - shadow_cascade_order, - } => Self::AuthorImportant { - shadow_cascade_order: -shadow_cascade_order, - }, - Self::PresHints | - Self::SMILOverride | - Self::Animations | - Self::AuthorImportant { .. } | - Self::UserImportant | - Self::UAImportant | - Self::Transitions => *self, - } - } - - /// Convert this level from "important" to "non-important". - pub fn unimportant(&self) -> Self { - match *self { - Self::UAImportant => Self::UANormal, - Self::UserImportant => Self::UserNormal, - Self::AuthorImportant { - shadow_cascade_order, - } => Self::AuthorNormal { - shadow_cascade_order: -shadow_cascade_order, - }, - Self::PresHints | - Self::SMILOverride | - Self::Animations | - Self::AuthorNormal { .. } | - Self::UserNormal | - Self::UANormal | - Self::Transitions => *self, - } - } - - /// Select a lock guard for this level - pub fn guard<'a>(&self, guards: &'a StylesheetGuards<'a>) -> &'a SharedRwLockReadGuard<'a> { - match *self { - Self::UANormal | Self::UserNormal | Self::UserImportant | Self::UAImportant => { - guards.ua_or_user - }, - _ => guards.author, - } - } - - /// Returns the cascade level for author important declarations from the - /// same tree as the element. - #[inline] - pub fn same_tree_author_important() -> Self { - Self::AuthorImportant { - shadow_cascade_order: ShadowCascadeOrder::for_same_tree(), - } - } - - /// Returns the cascade level for author normal declarations from the same - /// tree as the element. - #[inline] - pub fn same_tree_author_normal() -> Self { - Self::AuthorNormal { - shadow_cascade_order: ShadowCascadeOrder::for_same_tree(), - } - } - - /// Returns whether this cascade level represents important rules of some - /// sort. - #[inline] - pub fn is_important(&self) -> bool { - match *self { - Self::AuthorImportant { .. } | Self::UserImportant | Self::UAImportant => true, - _ => false, - } - } - - /// Returns the importance relevant for this rule. Pretty similar to - /// `is_important`. - #[inline] - pub fn importance(&self) -> Importance { - if self.is_important() { - Importance::Important - } else { - Importance::Normal - } - } - - /// Returns the cascade origin of the rule. - #[inline] - pub fn origin(&self) -> Origin { - match *self { - Self::UAImportant | Self::UANormal => Origin::UserAgent, - Self::UserImportant | Self::UserNormal => Origin::User, - Self::PresHints | - Self::AuthorNormal { .. } | - Self::AuthorImportant { .. } | - Self::SMILOverride | - Self::Animations | - Self::Transitions => Origin::Author, - } - } - - /// Returns whether this cascade level represents an animation rules. - #[inline] - pub fn is_animation(&self) -> bool { - match *self { - Self::SMILOverride | Self::Animations | Self::Transitions => true, - _ => false, - } - } -} - -/// A counter to track how many shadow root rules deep we are. This is used to -/// handle: -/// -/// https://drafts.csswg.org/css-scoping/#shadow-cascading -/// -/// See the static functions for the meaning of different values. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] -pub struct ShadowCascadeOrder(i8); - -impl ShadowCascadeOrder { - /// We keep a maximum of 3 bits of order as a limit so that we can pack - /// CascadeLevel in one byte by using half of it for the order, if that ends - /// up being necessary. - const MAX: i8 = 0b111; - const MIN: i8 = -Self::MAX; - - /// A level for the outermost shadow tree (the shadow tree we own, and the - /// ones from the slots we're slotted in). - #[inline] - pub fn for_outermost_shadow_tree() -> Self { - Self(-1) - } - - /// A level for the element's tree. - #[inline] - fn for_same_tree() -> Self { - Self(0) - } - - /// A level for the innermost containing tree (the one closest to the - /// element). - #[inline] - pub fn for_innermost_containing_tree() -> Self { - Self(1) - } - - /// Decrement the level, moving inwards. We should only move inwards if - /// we're traversing slots. - #[inline] - pub fn dec(&mut self) { - debug_assert!(self.0 < 0); - if self.0 != Self::MIN { - self.0 -= 1; - } - } - - /// The level, moving inwards. We should only move inwards if we're - /// traversing slots. - #[inline] - pub fn inc(&mut self) { - debug_assert_ne!(self.0, -1); - if self.0 != Self::MAX { - self.0 += 1; - } - } -} - -impl std::ops::Neg for ShadowCascadeOrder { - type Output = Self; - #[inline] - fn neg(self) -> Self { - Self(self.0.neg()) - } -} diff --git a/components/style/rule_tree/map.rs b/components/style/rule_tree/map.rs deleted file mode 100644 index 33c470e9c12..00000000000 --- a/components/style/rule_tree/map.rs +++ /dev/null @@ -1,201 +0,0 @@ -/* 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/. */ - -#![forbid(unsafe_code)] - -use fxhash::FxHashMap; -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps}; -use std::collections::hash_map; -use std::hash::Hash; -use std::mem; - -pub(super) struct Map<K, V> { - inner: MapInner<K, V>, -} - -enum MapInner<K, V> { - Empty, - One(V), - Map(Box<FxHashMap<K, V>>), -} - -pub(super) struct MapIter<'a, K, V> { - inner: MapIterInner<'a, K, V>, -} - -enum MapIterInner<'a, K, V> { - One(std::option::IntoIter<&'a V>), - Map(std::collections::hash_map::Values<'a, K, V>), -} - -pub(super) enum Entry<'a, K, V> { - Occupied(&'a mut V), - Vacant(VacantEntry<'a, K, V>), -} - -pub(super) struct VacantEntry<'a, K, V> { - inner: VacantEntryInner<'a, K, V>, -} - -enum VacantEntryInner<'a, K, V> { - One(&'a mut MapInner<K, V>), - Map(hash_map::VacantEntry<'a, K, V>), -} - -impl<K, V> Default for Map<K, V> { - fn default() -> Self { - Map { - inner: MapInner::Empty, - } - } -} - -impl<'a, K, V> IntoIterator for &'a Map<K, V> { - type Item = &'a V; - type IntoIter = MapIter<'a, K, V>; - - fn into_iter(self) -> Self::IntoIter { - MapIter { - inner: match &self.inner { - MapInner::Empty => MapIterInner::One(None.into_iter()), - MapInner::One(one) => MapIterInner::One(Some(one).into_iter()), - MapInner::Map(map) => MapIterInner::Map(map.values()), - }, - } - } -} - -impl<'a, K, V> Iterator for MapIter<'a, K, V> { - type Item = &'a V; - - fn next(&mut self) -> Option<Self::Item> { - match &mut self.inner { - MapIterInner::One(one_iter) => one_iter.next(), - MapIterInner::Map(map_iter) => map_iter.next(), - } - } -} - -impl<K, V> Map<K, V> -where - K: Eq + Hash, -{ - pub(super) fn is_empty(&self) -> bool { - match &self.inner { - MapInner::Empty => true, - MapInner::One(_) => false, - MapInner::Map(map) => map.is_empty(), - } - } - - #[cfg(debug_assertions)] - pub(super) fn len(&self) -> usize { - match &self.inner { - MapInner::Empty => 0, - MapInner::One(_) => 1, - MapInner::Map(map) => map.len(), - } - } - - pub(super) fn get(&self, key: &K, key_from_value: impl FnOnce(&V) -> K) -> Option<&V> { - match &self.inner { - MapInner::One(one) if *key == key_from_value(one) => Some(one), - MapInner::Map(map) => map.get(key), - MapInner::Empty | MapInner::One(_) => None, - } - } - - pub(super) fn entry( - &mut self, - key: K, - key_from_value: impl FnOnce(&V) -> K, - ) -> Entry<'_, K, V> { - match self.inner { - ref mut inner @ MapInner::Empty => Entry::Vacant(VacantEntry { - inner: VacantEntryInner::One(inner), - }), - MapInner::One(_) => { - let one = match mem::replace(&mut self.inner, MapInner::Empty) { - MapInner::One(one) => one, - _ => unreachable!(), - }; - // If this panics, the child `one` will be lost. - let one_key = key_from_value(&one); - // Same for the equality test. - if key == one_key { - self.inner = MapInner::One(one); - let one = match &mut self.inner { - MapInner::One(one) => one, - _ => unreachable!(), - }; - return Entry::Occupied(one); - } - self.inner = MapInner::Map(Box::new(FxHashMap::with_capacity_and_hasher( - 2, - Default::default(), - ))); - let map = match &mut self.inner { - MapInner::Map(map) => map, - _ => unreachable!(), - }; - map.insert(one_key, one); - match map.entry(key) { - hash_map::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { - inner: VacantEntryInner::Map(entry), - }), - _ => unreachable!(), - } - }, - MapInner::Map(ref mut map) => match map.entry(key) { - hash_map::Entry::Occupied(entry) => Entry::Occupied(entry.into_mut()), - hash_map::Entry::Vacant(entry) => Entry::Vacant(VacantEntry { - inner: VacantEntryInner::Map(entry), - }), - }, - } - } - - pub(super) fn remove(&mut self, key: &K, key_from_value: impl FnOnce(&V) -> K) -> Option<V> { - match &mut self.inner { - MapInner::One(one) if *key == key_from_value(one) => { - match mem::replace(&mut self.inner, MapInner::Empty) { - MapInner::One(one) => Some(one), - _ => unreachable!(), - } - }, - MapInner::Map(map) => map.remove(key), - MapInner::Empty | MapInner::One(_) => None, - } - } -} - -impl<'a, K, V> VacantEntry<'a, K, V> { - pub(super) fn insert(self, value: V) -> &'a mut V { - match self.inner { - VacantEntryInner::One(map) => { - *map = MapInner::One(value); - match map { - MapInner::One(one) => one, - _ => unreachable!(), - } - }, - VacantEntryInner::Map(entry) => entry.insert(value), - } - } -} - -impl<K, V> MallocShallowSizeOf for Map<K, V> -where - K: Eq + Hash, -{ - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match &self.inner { - MapInner::Map(m) => { - // We want to account for both the box and the hashmap. - m.shallow_size_of(ops) + (**m).shallow_size_of(ops) - }, - MapInner::One(_) | MapInner::Empty => 0, - } - } -} diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs deleted file mode 100644 index 01510e62070..00000000000 --- a/components/style/rule_tree/mod.rs +++ /dev/null @@ -1,426 +0,0 @@ -/* 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/. */ - -#![deny(unsafe_code)] - -//! The rule tree. - -use crate::applicable_declarations::{ApplicableDeclarationList, CascadePriority}; -use crate::properties::{LonghandIdSet, PropertyDeclarationBlock}; -use crate::shared_lock::{Locked, StylesheetGuards}; -use crate::stylesheets::layer_rule::LayerOrder; -use servo_arc::{Arc, ArcBorrow}; -use smallvec::SmallVec; -use std::io::{self, Write}; - -mod core; -mod level; -mod map; -mod source; -mod unsafe_box; - -pub use self::core::{RuleTree, StrongRuleNode}; -pub use self::level::{CascadeLevel, ShadowCascadeOrder}; -pub use self::source::StyleSource; - -impl RuleTree { - fn dump<W: Write>(&self, guards: &StylesheetGuards, writer: &mut W) { - let _ = writeln!(writer, " + RuleTree"); - self.root().dump(guards, writer, 0); - } - - /// Dump the rule tree to stdout. - pub fn dump_stdout(&self, guards: &StylesheetGuards) { - let mut stdout = io::stdout(); - self.dump(guards, &mut stdout); - } - - /// Inserts the given rules, that must be in proper order by specifity, and - /// returns the corresponding rule node representing the last inserted one. - /// - /// !important rules are detected and inserted into the appropriate position - /// in the rule tree. This allows selector matching to ignore importance, - /// while still maintaining the appropriate cascade order in the rule tree. - pub fn insert_ordered_rules_with_important<'a, I>( - &self, - iter: I, - guards: &StylesheetGuards, - ) -> StrongRuleNode - where - I: Iterator<Item = (StyleSource, CascadePriority)>, - { - use self::CascadeLevel::*; - let mut current = self.root().clone(); - - let mut found_important = false; - - let mut important_author = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); - let mut important_user = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); - let mut important_ua = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); - let mut transition = None; - - for (source, priority) in iter { - let level = priority.cascade_level(); - debug_assert!(!level.is_important(), "Important levels handled internally"); - - let any_important = { - let pdb = source.read(level.guard(guards)); - pdb.any_important() - }; - - if any_important { - found_important = true; - match level { - AuthorNormal { .. } => { - important_author.push((source.clone(), priority.important())) - }, - UANormal => important_ua.push((source.clone(), priority.important())), - UserNormal => important_user.push((source.clone(), priority.important())), - _ => {}, - }; - } - - // We don't optimize out empty rules, even though we could. - // - // Inspector relies on every rule being inserted in the normal level - // at least once, in order to return the rules with the correct - // specificity order. - // - // TODO(emilio): If we want to apply these optimizations without - // breaking inspector's expectations, we'd need to run - // selector-matching again at the inspector's request. That may or - // may not be a better trade-off. - if matches!(level, Transitions) && found_important { - // There can be at most one transition, and it will come at - // the end of the iterator. Stash it and apply it after - // !important rules. - debug_assert!(transition.is_none()); - transition = Some(source); - } else { - current = current.ensure_child(self.root(), source, priority); - } - } - - // Early-return in the common case of no !important declarations. - if !found_important { - return current; - } - - // Insert important declarations, in order of increasing importance, - // followed by any transition rule. - // - // Important rules are sorted differently from unimportant ones by - // shadow order and cascade order. - if !important_author.is_empty() && - important_author.first().unwrap().1 != important_author.last().unwrap().1 - { - // We only need to sort if the important rules come from - // different trees, but we need this sort to be stable. - // - // FIXME(emilio): This could maybe be smarter, probably by chunking - // the important rules while inserting, and iterating the outer - // chunks in reverse order. - // - // That is, if we have rules with levels like: -1 -1 -1 0 0 0 1 1 1, - // we're really only sorting the chunks, while keeping elements - // inside the same chunk already sorted. Seems like we could try to - // keep a SmallVec-of-SmallVecs with the chunks and just iterate the - // outer in reverse. - important_author.sort_by_key(|&(_, priority)| priority); - } - - for (source, priority) in important_author.drain(..) { - current = current.ensure_child(self.root(), source, priority); - } - - for (source, priority) in important_user.drain(..) { - current = current.ensure_child(self.root(), source, priority); - } - - for (source, priority) in important_ua.drain(..) { - current = current.ensure_child(self.root(), source, priority); - } - - if let Some(source) = transition { - current = current.ensure_child( - self.root(), - source, - CascadePriority::new(Transitions, LayerOrder::root()), - ); - } - - current - } - - /// Given a list of applicable declarations, insert the rules and return the - /// corresponding rule node. - pub fn compute_rule_node( - &self, - applicable_declarations: &mut ApplicableDeclarationList, - guards: &StylesheetGuards, - ) -> StrongRuleNode { - self.insert_ordered_rules_with_important( - applicable_declarations.drain(..).map(|d| d.for_rule_tree()), - guards, - ) - } - - /// Insert the given rules, that must be in proper order by specifity, and - /// return the corresponding rule node representing the last inserted one. - pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode - where - I: Iterator<Item = (StyleSource, CascadePriority)>, - { - self.insert_ordered_rules_from(self.root().clone(), iter) - } - - fn insert_ordered_rules_from<'a, I>(&self, from: StrongRuleNode, iter: I) -> StrongRuleNode - where - I: Iterator<Item = (StyleSource, CascadePriority)>, - { - let mut current = from; - for (source, priority) in iter { - current = current.ensure_child(self.root(), source, priority); - } - current - } - - /// Replaces a rule in a given level (if present) for another rule. - /// - /// Returns the resulting node that represents the new path, or None if - /// the old path is still valid. - pub fn update_rule_at_level( - &self, - level: CascadeLevel, - layer_order: LayerOrder, - pdb: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, - path: &StrongRuleNode, - guards: &StylesheetGuards, - important_rules_changed: &mut bool, - ) -> Option<StrongRuleNode> { - // TODO(emilio): Being smarter with lifetimes we could avoid a bit of - // the refcount churn. - let mut current = path.clone(); - *important_rules_changed = false; - - // First walk up until the first less-or-equally specific rule. - let mut children = SmallVec::<[_; 10]>::new(); - while current.cascade_priority().cascade_level() > level { - children.push(( - current.style_source().unwrap().clone(), - current.cascade_priority(), - )); - current = current.parent().unwrap().clone(); - } - - // Then remove the one at the level we want to replace, if any. - // - // NOTE: Here we assume that only one rule can be at the level we're - // replacing. - // - // This is certainly true for HTML style attribute rules, animations and - // transitions, but could not be so for SMIL animations, which we'd need - // to special-case (isn't hard, it's just about removing the `if` and - // special cases, and replacing them for a `while` loop, avoiding the - // optimizations). - if current.cascade_priority().cascade_level() == level { - *important_rules_changed |= level.is_important(); - - let current_decls = current.style_source().unwrap().as_declarations(); - - // If the only rule at the level we're replacing is exactly the - // same as `pdb`, we're done, and `path` is still valid. - if let (Some(ref pdb), Some(ref current_decls)) = (pdb, current_decls) { - // If the only rule at the level we're replacing is exactly the - // same as `pdb`, we're done, and `path` is still valid. - // - // TODO(emilio): Another potential optimization is the one where - // we can just replace the rule at that level for `pdb`, and - // then we don't need to re-create the children, and `path` is - // also equally valid. This is less likely, and would require an - // in-place mutation of the source, which is, at best, fiddly, - // so let's skip it for now. - let is_here_already = ArcBorrow::ptr_eq(pdb, current_decls); - if is_here_already { - debug!("Picking the fast path in rule replacement"); - return None; - } - } - - if current_decls.is_some() { - current = current.parent().unwrap().clone(); - } - } - - // Insert the rule if it's relevant at this level in the cascade. - // - // These optimizations are likely to be important, because the levels - // where replacements apply (style and animations) tend to trigger - // pretty bad styling cases already. - if let Some(pdb) = pdb { - if level.is_important() { - if pdb.read_with(level.guard(guards)).any_important() { - current = current.ensure_child( - self.root(), - StyleSource::from_declarations(pdb.clone_arc()), - CascadePriority::new(level, layer_order), - ); - *important_rules_changed = true; - } - } else { - if pdb.read_with(level.guard(guards)).any_normal() { - current = current.ensure_child( - self.root(), - StyleSource::from_declarations(pdb.clone_arc()), - CascadePriority::new(level, layer_order), - ); - } - } - } - - // Now the rule is in the relevant place, push the children as - // necessary. - let rule = self.insert_ordered_rules_from(current, children.drain(..).rev()); - Some(rule) - } - - /// Returns new rule nodes without Transitions level rule. - pub fn remove_transition_rule_if_applicable(&self, path: &StrongRuleNode) -> StrongRuleNode { - // Return a clone if there is no transition level. - if path.cascade_level() != CascadeLevel::Transitions { - return path.clone(); - } - - path.parent().unwrap().clone() - } - - /// Returns new rule node without rules from declarative animations. - pub fn remove_animation_rules(&self, path: &StrongRuleNode) -> StrongRuleNode { - // Return a clone if there are no animation rules. - if !path.has_animation_or_transition_rules() { - return path.clone(); - } - - let iter = path - .self_and_ancestors() - .take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride); - let mut last = path; - let mut children = SmallVec::<[_; 10]>::new(); - for node in iter { - if !node.cascade_level().is_animation() { - children.push(( - node.style_source().unwrap().clone(), - node.cascade_priority(), - )); - } - last = node; - } - - let rule = self - .insert_ordered_rules_from(last.parent().unwrap().clone(), children.drain(..).rev()); - rule - } - - /// Returns new rule node by adding animation rules at transition level. - /// The additional rules must be appropriate for the transition - /// level of the cascade, which is the highest level of the cascade. - /// (This is the case for one current caller, the cover rule used - /// for CSS transitions.) - pub fn add_animation_rules_at_transition_level( - &self, - path: &StrongRuleNode, - pdb: Arc<Locked<PropertyDeclarationBlock>>, - guards: &StylesheetGuards, - ) -> StrongRuleNode { - let mut dummy = false; - self.update_rule_at_level( - CascadeLevel::Transitions, - LayerOrder::root(), - Some(pdb.borrow_arc()), - path, - guards, - &mut dummy, - ) - .expect("Should return a valid rule node") - } -} - -impl StrongRuleNode { - /// Get an iterator for this rule node and its ancestors. - pub fn self_and_ancestors(&self) -> SelfAndAncestors { - SelfAndAncestors { - current: Some(self), - } - } - - /// Returns true if there is either animation or transition level rule. - pub fn has_animation_or_transition_rules(&self) -> bool { - self.self_and_ancestors() - .take_while(|node| node.cascade_level() >= CascadeLevel::SMILOverride) - .any(|node| node.cascade_level().is_animation()) - } - - /// Get a set of properties whose CascadeLevel are higher than Animations - /// but not equal to Transitions. - /// - /// If there are any custom properties, we set the boolean value of the - /// returned tuple to true. - pub fn get_properties_overriding_animations( - &self, - guards: &StylesheetGuards, - ) -> (LonghandIdSet, bool) { - use crate::properties::PropertyDeclarationId; - - // We want to iterate over cascade levels that override the animations - // level, i.e. !important levels and the transitions level. - // - // However, we actually want to skip the transitions level because - // although it is higher in the cascade than animations, when both - // transitions and animations are present for a given element and - // property, transitions are suppressed so that they don't actually - // override animations. - let iter = self - .self_and_ancestors() - .skip_while(|node| node.cascade_level() == CascadeLevel::Transitions) - .take_while(|node| node.cascade_level() > CascadeLevel::Animations); - let mut result = (LonghandIdSet::new(), false); - for node in iter { - let style = node.style_source().unwrap(); - for (decl, important) in style - .read(node.cascade_level().guard(guards)) - .declaration_importance_iter() - { - // Although we are only iterating over cascade levels that - // override animations, in a given property declaration block we - // can have a mixture of !important and non-!important - // declarations but only the !important declarations actually - // override animations. - if important.important() { - match decl.id() { - PropertyDeclarationId::Longhand(id) => result.0.insert(id), - PropertyDeclarationId::Custom(_) => result.1 = true, - } - } - } - } - result - } -} - -/// An iterator over a rule node and its ancestors. -#[derive(Clone)] -pub struct SelfAndAncestors<'a> { - current: Option<&'a StrongRuleNode>, -} - -impl<'a> Iterator for SelfAndAncestors<'a> { - type Item = &'a StrongRuleNode; - - fn next(&mut self) -> Option<Self::Item> { - self.current.map(|node| { - self.current = node.parent(); - node - }) - } -} diff --git a/components/style/rule_tree/source.rs b/components/style/rule_tree/source.rs deleted file mode 100644 index 76443692d74..00000000000 --- a/components/style/rule_tree/source.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* 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/. */ - -#![forbid(unsafe_code)] - -use crate::properties::PropertyDeclarationBlock; -use crate::shared_lock::{Locked, SharedRwLockReadGuard}; -use crate::stylesheets::StyleRule; -use servo_arc::{Arc, ArcBorrow, ArcUnion, ArcUnionBorrow}; -use std::io::Write; -use std::ptr; - -/// A style source for the rule node. It can either be a CSS style rule or a -/// declaration block. -/// -/// Note that, even though the declaration block from inside the style rule -/// could be enough to implement the rule tree, keeping the whole rule provides -/// more debuggability, and also the ability of show those selectors to -/// devtools. -#[derive(Clone, Debug)] -pub struct StyleSource(ArcUnion<Locked<StyleRule>, Locked<PropertyDeclarationBlock>>); - -impl PartialEq for StyleSource { - fn eq(&self, other: &Self) -> bool { - ArcUnion::ptr_eq(&self.0, &other.0) - } -} - -impl StyleSource { - /// Creates a StyleSource from a StyleRule. - pub fn from_rule(rule: Arc<Locked<StyleRule>>) -> Self { - StyleSource(ArcUnion::from_first(rule)) - } - - #[inline] - pub(super) fn key(&self) -> ptr::NonNull<()> { - self.0.ptr() - } - - /// Creates a StyleSource from a PropertyDeclarationBlock. - pub fn from_declarations(decls: Arc<Locked<PropertyDeclarationBlock>>) -> Self { - StyleSource(ArcUnion::from_second(decls)) - } - - pub(super) fn dump<W: Write>(&self, guard: &SharedRwLockReadGuard, writer: &mut W) { - if let Some(ref rule) = self.0.as_first() { - let rule = rule.read_with(guard); - let _ = write!(writer, "{:?}", rule.selectors); - } - - let _ = write!(writer, " -> {:?}", self.read(guard).declarations()); - } - - /// Read the style source guard, and obtain thus read access to the - /// underlying property declaration block. - #[inline] - pub fn read<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a PropertyDeclarationBlock { - let block: &Locked<PropertyDeclarationBlock> = match self.0.borrow() { - ArcUnionBorrow::First(ref rule) => &rule.get().read_with(guard).block, - ArcUnionBorrow::Second(ref block) => block.get(), - }; - block.read_with(guard) - } - - /// Returns the style rule if applicable, otherwise None. - pub fn as_rule(&self) -> Option<ArcBorrow<Locked<StyleRule>>> { - self.0.as_first() - } - - /// Returns the declaration block if applicable, otherwise None. - pub fn as_declarations(&self) -> Option<ArcBorrow<Locked<PropertyDeclarationBlock>>> { - self.0.as_second() - } -} diff --git a/components/style/rule_tree/unsafe_box.rs b/components/style/rule_tree/unsafe_box.rs deleted file mode 100644 index eaa441d7b25..00000000000 --- a/components/style/rule_tree/unsafe_box.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] - -use std::mem::ManuallyDrop; -use std::ops::Deref; -use std::ptr; - -/// An unsafe box, derefs to `T`. -pub(super) struct UnsafeBox<T> { - inner: ManuallyDrop<Box<T>>, -} - -impl<T> UnsafeBox<T> { - /// Creates a new unsafe box. - pub(super) fn from_box(value: Box<T>) -> Self { - Self { - inner: ManuallyDrop::new(value), - } - } - - /// Creates a new box from a pointer. - /// - /// # Safety - /// - /// The input should point to a valid `T`. - pub(super) unsafe fn from_raw(ptr: *mut T) -> Self { - Self { - inner: ManuallyDrop::new(Box::from_raw(ptr)), - } - } - - /// Creates a new unsafe box from an existing one. - /// - /// # Safety - /// - /// There is no refcounting or whatever else in an unsafe box, so this - /// operation can lead to double frees. - pub(super) unsafe fn clone(this: &Self) -> Self { - Self { - inner: ptr::read(&this.inner), - } - } - - /// Returns a mutable reference to the inner value of this unsafe box. - /// - /// # Safety - /// - /// Given `Self::clone`, nothing prevents anyone from creating - /// multiple mutable references to the inner value, which is completely UB. - pub(crate) unsafe fn deref_mut(this: &mut Self) -> &mut T { - &mut this.inner - } - - /// Drops the inner value of this unsafe box. - /// - /// # Safety - /// - /// Given this doesn't consume the unsafe box itself, this has the same - /// safety caveats as `ManuallyDrop::drop`. - pub(super) unsafe fn drop(this: &mut Self) { - ManuallyDrop::drop(&mut this.inner) - } -} - -impl<T> Deref for UnsafeBox<T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} diff --git a/components/style/rustfmt.toml b/components/style/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/style/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/style/scoped_tls.rs b/components/style/scoped_tls.rs deleted file mode 100644 index 672cc275e65..00000000000 --- a/components/style/scoped_tls.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* 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/. */ - -//! Stack-scoped thread-local storage for rayon thread pools. - -#![allow(unsafe_code)] -#![deny(missing_docs)] - -use crate::global_style_data::STYLO_MAX_THREADS; -use rayon; -use std::cell::{Ref, RefCell, RefMut}; -use std::ops::DerefMut; - -/// A scoped TLS set, that is alive during the `'scope` lifetime. -/// -/// We use this on Servo to construct thread-local contexts, but clear them once -/// we're done with restyling. -/// -/// Note that the cleanup is done on the thread that owns the scoped TLS, thus -/// the Send bound. -pub struct ScopedTLS<'scope, T: Send> { - pool: &'scope rayon::ThreadPool, - slots: [RefCell<Option<T>>; STYLO_MAX_THREADS], -} - -/// The scoped TLS is `Sync` because no more than one worker thread can access a -/// given slot. -unsafe impl<'scope, T: Send> Sync for ScopedTLS<'scope, T> {} - -impl<'scope, T: Send> ScopedTLS<'scope, T> { - /// Create a new scoped TLS that will last as long as this rayon threadpool - /// reference. - pub fn new(pool: &'scope rayon::ThreadPool) -> Self { - debug_assert!(pool.current_num_threads() <= STYLO_MAX_THREADS); - ScopedTLS { - pool, - slots: Default::default(), - } - } - - /// Return an immutable reference to the `Option<T>` that this thread owns. - pub fn borrow(&self) -> Ref<Option<T>> { - let idx = self.pool.current_thread_index().unwrap(); - self.slots[idx].borrow() - } - - /// Return a mutable reference to the `Option<T>` that this thread owns. - pub fn borrow_mut(&self) -> RefMut<Option<T>> { - let idx = self.pool.current_thread_index().unwrap(); - self.slots[idx].borrow_mut() - } - - /// Ensure that the current data this thread owns is initialized, or - /// initialize it using `f`. We want ensure() to be fast and inline, and we - /// want to inline the memmove that initializes the Option<T>. But we don't - /// want to inline space for the entire large T struct in our stack frame. - /// That's why we hand `f` a mutable borrow to write to instead of just - /// having it return a T. - #[inline(always)] - pub fn ensure<F: FnOnce(&mut Option<T>)>(&self, f: F) -> RefMut<T> { - let mut opt = self.borrow_mut(); - if opt.is_none() { - f(opt.deref_mut()); - } - - RefMut::map(opt, |x| x.as_mut().unwrap()) - } - - /// Returns the slots. Safe because if we have a mut reference the tls can't be referenced by - /// any other thread. - pub fn slots(&mut self) -> &mut [RefCell<Option<T>>] { - &mut self.slots - } -} diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs deleted file mode 100644 index fe0b6238621..00000000000 --- a/components/style/selector_map.rs +++ /dev/null @@ -1,868 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A data structure to efficiently index structs containing selectors by local -//! name, ids and hash. - -use crate::applicable_declarations::ApplicableDeclarationList; -use crate::context::QuirksMode; -use crate::dom::TElement; -use crate::rule_tree::CascadeLevel; -use crate::selector_parser::SelectorImpl; -use crate::stylist::{CascadeData, ContainerConditionId, Rule, Stylist}; -use crate::AllocErr; -use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; -use precomputed_hash::PrecomputedHash; -use selectors::matching::{matches_selector, MatchingContext}; -use selectors::parser::{Combinator, Component, SelectorIter}; -use smallvec::SmallVec; -use std::collections::hash_map; -use std::collections::{HashMap, HashSet}; -use std::hash::{BuildHasherDefault, Hash, Hasher}; -use style_traits::dom::ElementState; - -/// A hasher implementation that doesn't hash anything, because it expects its -/// input to be a suitable u32 hash. -pub struct PrecomputedHasher { - hash: Option<u32>, -} - -impl Default for PrecomputedHasher { - fn default() -> Self { - Self { hash: None } - } -} - -/// This is a set of pseudo-classes that are both relatively-rare (they don't -/// affect most elements by default) and likely or known to have global rules -/// (in e.g., the UA sheets). -/// -/// We can avoid selector-matching those global rules for all elements without -/// these pseudo-class states. -const RARE_PSEUDO_CLASS_STATES: ElementState = ElementState::from_bits_truncate( - ElementState::FULLSCREEN.bits() | - ElementState::VISITED_OR_UNVISITED.bits() | - ElementState::URLTARGET.bits() | - ElementState::INERT.bits() | - ElementState::FOCUS.bits() | - ElementState::FOCUSRING.bits() | - ElementState::TOPMOST_MODAL.bits(), -); - -/// A simple alias for a hashmap using PrecomputedHasher. -pub type PrecomputedHashMap<K, V> = HashMap<K, V, BuildHasherDefault<PrecomputedHasher>>; - -/// A simple alias for a hashset using PrecomputedHasher. -pub type PrecomputedHashSet<K> = HashSet<K, BuildHasherDefault<PrecomputedHasher>>; - -impl Hasher for PrecomputedHasher { - #[inline] - fn write(&mut self, _: &[u8]) { - unreachable!( - "Called into PrecomputedHasher with something that isn't \ - a u32" - ) - } - - #[inline] - fn write_u32(&mut self, i: u32) { - debug_assert!(self.hash.is_none()); - self.hash = Some(i); - } - - #[inline] - fn finish(&self) -> u64 { - self.hash.expect("PrecomputedHasher wasn't fed?") as u64 - } -} - -/// A trait to abstract over a given selector map entry. -pub trait SelectorMapEntry: Sized + Clone { - /// Gets the selector we should use to index in the selector map. - fn selector(&self) -> SelectorIter<SelectorImpl>; -} - -/// Map element data to selector-providing objects for which the last simple -/// selector starts with them. -/// -/// e.g., -/// "p > img" would go into the set of selectors corresponding to the -/// element "img" -/// "a .foo .bar.baz" would go into the set of selectors corresponding to -/// the class "bar" -/// -/// Because we match selectors right-to-left (i.e., moving up the tree -/// from an element), we need to compare the last simple selector in the -/// selector with the element. -/// -/// So, if an element has ID "id1" and classes "foo" and "bar", then all -/// the rules it matches will have their last simple selector starting -/// either with "#id1" or with ".foo" or with ".bar". -/// -/// Hence, the union of the rules keyed on each of element's classes, ID, -/// element name, etc. will contain the Selectors that actually match that -/// element. -/// -/// We use a 1-entry SmallVec to avoid a separate heap allocation in the case -/// where we only have one entry, which is quite common. See measurements in: -/// * https://bugzilla.mozilla.org/show_bug.cgi?id=1363789#c5 -/// * https://bugzilla.mozilla.org/show_bug.cgi?id=681755 -/// -/// TODO: Tune the initial capacity of the HashMap -#[derive(Clone, Debug, MallocSizeOf)] -pub struct SelectorMap<T: 'static> { - /// Rules that have `:root` selectors. - pub root: SmallVec<[T; 1]>, - /// A hash from an ID to rules which contain that ID selector. - pub id_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>, - /// A hash from a class name to rules which contain that class selector. - pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>, - /// A hash from local name to rules which contain that local name selector. - pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>, - /// A hash from attributes to rules which contain that attribute selector. - pub attribute_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>, - /// A hash from namespace to rules which contain that namespace selector. - pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>, - /// Rules for pseudo-states that are rare but have global selectors. - pub rare_pseudo_classes: SmallVec<[T; 1]>, - /// All other rules. - pub other: SmallVec<[T; 1]>, - /// Whether we should bucket by attribute names. - bucket_attributes: bool, - /// The number of entries in this map. - pub count: usize, -} - -impl<T: 'static> Default for SelectorMap<T> { - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl<T> SelectorMap<T> { - /// Trivially constructs an empty `SelectorMap`. - pub fn new() -> Self { - SelectorMap { - root: SmallVec::new(), - id_hash: MaybeCaseInsensitiveHashMap::new(), - class_hash: MaybeCaseInsensitiveHashMap::new(), - attribute_hash: HashMap::default(), - local_name_hash: HashMap::default(), - namespace_hash: HashMap::default(), - rare_pseudo_classes: SmallVec::new(), - other: SmallVec::new(), - bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"), - count: 0, - } - } - - /// Trivially constructs an empty `SelectorMap`, with attribute bucketing - /// explicitly disabled. - pub fn new_without_attribute_bucketing() -> Self { - let mut ret = Self::new(); - ret.bucket_attributes = false; - ret - } - - /// Shrink the capacity of the map if needed. - pub fn shrink_if_needed(&mut self) { - self.id_hash.shrink_if_needed(); - self.class_hash.shrink_if_needed(); - self.attribute_hash.shrink_if_needed(); - self.local_name_hash.shrink_if_needed(); - self.namespace_hash.shrink_if_needed(); - } - - /// Clears the hashmap retaining storage. - pub fn clear(&mut self) { - self.root.clear(); - self.id_hash.clear(); - self.class_hash.clear(); - self.attribute_hash.clear(); - self.local_name_hash.clear(); - self.namespace_hash.clear(); - self.rare_pseudo_classes.clear(); - self.other.clear(); - self.count = 0; - } - - /// Returns whether there are any entries in the map. - pub fn is_empty(&self) -> bool { - self.count == 0 - } - - /// Returns the number of entries. - pub fn len(&self) -> usize { - self.count - } -} - -impl SelectorMap<Rule> { - /// Append to `rule_list` all Rules in `self` that match element. - /// - /// Extract matching rules as per element's ID, classes, tag name, etc.. - /// Sort the Rules at the end to maintain cascading order. - pub fn get_all_matching_rules<E>( - &self, - element: E, - rule_hash_target: E, - matching_rules_list: &mut ApplicableDeclarationList, - matching_context: &mut MatchingContext<E::Impl>, - cascade_level: CascadeLevel, - cascade_data: &CascadeData, - stylist: &Stylist, - ) where - E: TElement, - { - if self.is_empty() { - return; - } - - let quirks_mode = matching_context.quirks_mode(); - - if rule_hash_target.is_root() { - SelectorMap::get_matching_rules( - element, - &self.root, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ); - } - - if let Some(id) = rule_hash_target.id() { - if let Some(rules) = self.id_hash.get(id, quirks_mode) { - SelectorMap::get_matching_rules( - element, - rules, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ) - } - } - - rule_hash_target.each_class(|class| { - if let Some(rules) = self.class_hash.get(&class, quirks_mode) { - SelectorMap::get_matching_rules( - element, - rules, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ) - } - }); - - if self.bucket_attributes { - rule_hash_target.each_attr_name(|name| { - if let Some(rules) = self.attribute_hash.get(name) { - SelectorMap::get_matching_rules( - element, - rules, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ) - } - }); - } - - if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) { - SelectorMap::get_matching_rules( - element, - rules, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ) - } - - if rule_hash_target - .state() - .intersects(RARE_PSEUDO_CLASS_STATES) - { - SelectorMap::get_matching_rules( - element, - &self.rare_pseudo_classes, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ); - } - - if let Some(rules) = self.namespace_hash.get(rule_hash_target.namespace()) { - SelectorMap::get_matching_rules( - element, - rules, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ) - } - - SelectorMap::get_matching_rules( - element, - &self.other, - matching_rules_list, - matching_context, - cascade_level, - cascade_data, - stylist, - ); - } - - /// Adds rules in `rules` that match `element` to the `matching_rules` list. - pub(crate) fn get_matching_rules<E>( - element: E, - rules: &[Rule], - matching_rules: &mut ApplicableDeclarationList, - matching_context: &mut MatchingContext<E::Impl>, - cascade_level: CascadeLevel, - cascade_data: &CascadeData, - stylist: &Stylist, - ) where - E: TElement, - { - for rule in rules { - if !matches_selector( - &rule.selector, - 0, - Some(&rule.hashes), - &element, - matching_context, - ) { - continue; - } - - if rule.container_condition_id != ContainerConditionId::none() { - if !cascade_data.container_condition_matches( - rule.container_condition_id, - stylist, - element, - matching_context, - ) { - continue; - } - } - - matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); - } - } -} - -impl<T: SelectorMapEntry> SelectorMap<T> { - /// Inserts an entry into the correct bucket(s). - pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) -> Result<(), AllocErr> { - self.count += 1; - - // NOTE(emilio): It'd be nice for this to be a separate function, but - // then the compiler can't reason about the lifetime dependency between - // `entry` and `bucket`, and would force us to clone the rule in the - // common path. - macro_rules! insert_into_bucket { - ($entry:ident, $bucket:expr) => {{ - let vec = match $bucket { - Bucket::Root => &mut self.root, - Bucket::ID(id) => self - .id_hash - .try_entry(id.clone(), quirks_mode)? - .or_default(), - Bucket::Class(class) => self - .class_hash - .try_entry(class.clone(), quirks_mode)? - .or_default(), - Bucket::Attribute { name, lower_name } | - Bucket::LocalName { name, lower_name } => { - // If the local name in the selector isn't lowercase, - // insert it into the rule hash twice. This means that, - // during lookup, we can always find the rules based on - // the local name of the element, regardless of whether - // it's an html element in an html document (in which - // case we match against lower_name) or not (in which - // case we match against name). - // - // In the case of a non-html-element-in-html-document - // with a lowercase localname and a non-lowercase - // selector, the rulehash lookup may produce superfluous - // selectors, but the subsequent selector matching work - // will filter them out. - let is_attribute = matches!($bucket, Bucket::Attribute { .. }); - let hash = if is_attribute { - &mut self.attribute_hash - } else { - &mut self.local_name_hash - }; - if name != lower_name { - hash.try_reserve(1)?; - let vec = hash.entry(lower_name.clone()).or_default(); - vec.try_reserve(1)?; - vec.push($entry.clone()); - } - hash.try_reserve(1)?; - hash.entry(name.clone()).or_default() - }, - Bucket::Namespace(url) => { - self.namespace_hash.try_reserve(1)?; - self.namespace_hash.entry(url.clone()).or_default() - }, - Bucket::RarePseudoClasses => &mut self.rare_pseudo_classes, - Bucket::Universal => &mut self.other, - }; - vec.try_reserve(1)?; - vec.push($entry); - }}; - } - - let bucket = { - let mut disjoint_buckets = SmallVec::new(); - let bucket = find_bucket( - entry.selector(), - &mut disjoint_buckets, - self.bucket_attributes, - ); - - // See if inserting this selector in multiple entries in the - // selector map would be worth it. Consider a case like: - // - // .foo:where(div, #bar) - // - // There, `bucket` would be `Class(foo)`, and disjoint_buckets would - // be `[LocalName { div }, ID(bar)]`. - // - // Here we choose to insert the selector in the `.foo` bucket in - // such a case, as it's likely more worth it than inserting it in - // both `div` and `#bar`. - // - // This is specially true if there's any universal selector in the - // `disjoint_selectors` set, at which point we'd just be doing - // wasted work. - if !disjoint_buckets.is_empty() && - disjoint_buckets - .iter() - .all(|b| b.more_specific_than(&bucket)) - { - for bucket in &disjoint_buckets { - let entry = entry.clone(); - insert_into_bucket!(entry, *bucket); - } - return Ok(()); - } - bucket - }; - - insert_into_bucket!(entry, bucket); - Ok(()) - } - - /// Looks up entries by id, class, local name, namespace, and other (in - /// order). - /// - /// Each entry is passed to the callback, which returns true to continue - /// iterating entries, or false to terminate the lookup. - /// - /// Returns false if the callback ever returns false. - /// - /// FIXME(bholley) This overlaps with SelectorMap<Rule>::get_all_matching_rules, - /// but that function is extremely hot and I'd rather not rearrange it. - pub fn lookup<'a, E, F>(&'a self, element: E, quirks_mode: QuirksMode, f: F) -> bool - where - E: TElement, - F: FnMut(&'a T) -> bool, - { - self.lookup_with_state(element, element.state(), quirks_mode, f) - } - - #[inline] - fn lookup_with_state<'a, E, F>( - &'a self, - element: E, - element_state: ElementState, - quirks_mode: QuirksMode, - mut f: F, - ) -> bool - where - E: TElement, - F: FnMut(&'a T) -> bool, - { - if element.is_root() { - for entry in self.root.iter() { - if !f(&entry) { - return false; - } - } - } - - if let Some(id) = element.id() { - if let Some(v) = self.id_hash.get(id, quirks_mode) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - } - - let mut done = false; - element.each_class(|class| { - if done { - return; - } - if let Some(v) = self.class_hash.get(class, quirks_mode) { - for entry in v.iter() { - if !f(&entry) { - done = true; - return; - } - } - } - }); - - if done { - return false; - } - - if self.bucket_attributes { - element.each_attr_name(|name| { - if done { - return; - } - if let Some(v) = self.attribute_hash.get(name) { - for entry in v.iter() { - if !f(&entry) { - done = true; - return; - } - } - } - }); - - if done { - return false; - } - } - - if let Some(v) = self.local_name_hash.get(element.local_name()) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - - if let Some(v) = self.namespace_hash.get(element.namespace()) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - - if element_state.intersects(RARE_PSEUDO_CLASS_STATES) { - for entry in self.rare_pseudo_classes.iter() { - if !f(&entry) { - return false; - } - } - } - - for entry in self.other.iter() { - if !f(&entry) { - return false; - } - } - - true - } - - /// Performs a normal lookup, and also looks up entries for the passed-in - /// id and classes. - /// - /// Each entry is passed to the callback, which returns true to continue - /// iterating entries, or false to terminate the lookup. - /// - /// Returns false if the callback ever returns false. - #[inline] - pub fn lookup_with_additional<'a, E, F>( - &'a self, - element: E, - quirks_mode: QuirksMode, - additional_id: Option<&WeakAtom>, - additional_classes: &[Atom], - additional_states: ElementState, - mut f: F, - ) -> bool - where - E: TElement, - F: FnMut(&'a T) -> bool, - { - // Do the normal lookup. - if !self.lookup_with_state( - element, - element.state() | additional_states, - quirks_mode, - |entry| f(entry), - ) { - return false; - } - - // Check the additional id. - if let Some(id) = additional_id { - if let Some(v) = self.id_hash.get(id, quirks_mode) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - } - - // Check the additional classes. - for class in additional_classes { - if let Some(v) = self.class_hash.get(class, quirks_mode) { - for entry in v.iter() { - if !f(&entry) { - return false; - } - } - } - } - - true - } -} - -enum Bucket<'a> { - Universal, - Namespace(&'a Namespace), - RarePseudoClasses, - LocalName { - name: &'a LocalName, - lower_name: &'a LocalName, - }, - Attribute { - name: &'a LocalName, - lower_name: &'a LocalName, - }, - Class(&'a Atom), - ID(&'a Atom), - Root, -} - -impl<'a> Bucket<'a> { - /// root > id > class > local name > namespace > pseudo-classes > universal. - #[inline] - fn specificity(&self) -> usize { - match *self { - Bucket::Universal => 0, - Bucket::Namespace(..) => 1, - Bucket::RarePseudoClasses => 2, - Bucket::LocalName { .. } => 3, - Bucket::Attribute { .. } => 4, - Bucket::Class(..) => 5, - Bucket::ID(..) => 6, - Bucket::Root => 7, - } - } - - #[inline] - fn more_or_equally_specific_than(&self, other: &Self) -> bool { - self.specificity() >= other.specificity() - } - - #[inline] - fn more_specific_than(&self, other: &Self) -> bool { - self.specificity() > other.specificity() - } -} - -type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>; - -fn specific_bucket_for<'a>( - component: &'a Component<SelectorImpl>, - disjoint_buckets: &mut DisjointBuckets<'a>, - bucket_attributes: bool, -) -> Bucket<'a> { - match *component { - Component::Root => Bucket::Root, - Component::ID(ref id) => Bucket::ID(id), - Component::Class(ref class) => Bucket::Class(class), - Component::AttributeInNoNamespace { ref local_name, .. } if bucket_attributes => { - Bucket::Attribute { - name: local_name, - lower_name: local_name, - } - }, - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } if bucket_attributes => Bucket::Attribute { - name: local_name, - lower_name: local_name_lower, - }, - Component::AttributeOther(ref selector) if bucket_attributes => Bucket::Attribute { - name: &selector.local_name, - lower_name: &selector.local_name_lower, - }, - Component::LocalName(ref selector) => Bucket::LocalName { - name: &selector.name, - lower_name: &selector.lower_name, - }, - Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { - Bucket::Namespace(url) - }, - // ::slotted(..) isn't a normal pseudo-element, so we can insert it on - // the rule hash normally without much problem. For example, in a - // selector like: - // - // div::slotted(span)::before - // - // It looks like: - // - // [ - // LocalName(div), - // Combinator(SlotAssignment), - // Slotted(span), - // Combinator::PseudoElement, - // PseudoElement(::before), - // ] - // - // So inserting `span` in the rule hash makes sense since we want to - // match the slotted <span>. - Component::Slotted(ref selector) => { - find_bucket(selector.iter(), disjoint_buckets, bucket_attributes) - }, - Component::Host(Some(ref selector)) => { - find_bucket(selector.iter(), disjoint_buckets, bucket_attributes) - }, - Component::Is(ref list) | Component::Where(ref list) => { - if list.len() == 1 { - find_bucket(list[0].iter(), disjoint_buckets, bucket_attributes) - } else { - for selector in &**list { - let bucket = find_bucket(selector.iter(), disjoint_buckets, bucket_attributes); - disjoint_buckets.push(bucket); - } - Bucket::Universal - } - }, - Component::NonTSPseudoClass(ref pseudo_class) - if pseudo_class - .state_flag() - .intersects(RARE_PSEUDO_CLASS_STATES) => - { - Bucket::RarePseudoClasses - }, - _ => Bucket::Universal, - } -} - -/// Searches a compound selector from left to right, and returns the appropriate -/// bucket for it. -/// -/// It also populates disjoint_buckets with dependencies from nested selectors -/// with any semantics like :is() and :where(). -#[inline(always)] -fn find_bucket<'a>( - mut iter: SelectorIter<'a, SelectorImpl>, - disjoint_buckets: &mut DisjointBuckets<'a>, - bucket_attributes: bool, -) -> Bucket<'a> { - let mut current_bucket = Bucket::Universal; - - loop { - for ss in &mut iter { - let new_bucket = specific_bucket_for(ss, disjoint_buckets, bucket_attributes); - // NOTE: When presented with the choice of multiple specific selectors, use the - // rightmost, on the assumption that that's less common, see bug 1829540. - if new_bucket.more_or_equally_specific_than(¤t_bucket) { - current_bucket = new_bucket; - } - } - - // Effectively, pseudo-elements are ignored, given only state - // pseudo-classes may appear before them. - if iter.next_sequence() != Some(Combinator::PseudoElement) { - break; - } - } - - current_bucket -} - -/// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct MaybeCaseInsensitiveHashMap<K: PrecomputedHash + Hash + Eq, V>(PrecomputedHashMap<K, V>); - -impl<V> Default for MaybeCaseInsensitiveHashMap<Atom, V> { - #[inline] - fn default() -> Self { - MaybeCaseInsensitiveHashMap(PrecomputedHashMap::default()) - } -} - -impl<V> MaybeCaseInsensitiveHashMap<Atom, V> { - /// Empty map - pub fn new() -> Self { - Self::default() - } - - /// Shrink the capacity of the map if needed. - pub fn shrink_if_needed(&mut self) { - self.0.shrink_if_needed() - } - - /// HashMap::try_entry - pub fn try_entry( - &mut self, - mut key: Atom, - quirks_mode: QuirksMode, - ) -> Result<hash_map::Entry<Atom, V>, AllocErr> { - if quirks_mode == QuirksMode::Quirks { - key = key.to_ascii_lowercase() - } - self.0.try_reserve(1)?; - Ok(self.0.entry(key)) - } - - /// HashMap::is_empty - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// HashMap::iter - pub fn iter(&self) -> hash_map::Iter<Atom, V> { - self.0.iter() - } - - /// HashMap::clear - pub fn clear(&mut self) { - self.0.clear() - } - - /// HashMap::get - pub fn get(&self, key: &WeakAtom, quirks_mode: QuirksMode) -> Option<&V> { - if quirks_mode == QuirksMode::Quirks { - self.0.get(&key.to_ascii_lowercase()) - } else { - self.0.get(key) - } - } -} diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs deleted file mode 100644 index baf9ce83e61..00000000000 --- a/components/style/selector_parser.rs +++ /dev/null @@ -1,240 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The pseudo-classes and pseudo-elements supported by the style system. - -#![deny(missing_docs)] - -use crate::stylesheets::{Namespaces, Origin, UrlExtraData}; -use crate::values::serialize_atom_identifier; -use crate::Atom; -use cssparser::{Parser as CssParser, ParserInput}; -use selectors::parser::SelectorList; -use std::fmt::{self, Debug, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; -use style_traits::dom::ElementState; - -/// A convenient alias for the type that represents an attribute value used for -/// selector parser implementation. -pub type AttrValue = <SelectorImpl as ::selectors::SelectorImpl>::AttrValue; - -#[cfg(feature = "servo")] -pub use crate::servo::selector_parser::*; - -#[cfg(feature = "gecko")] -pub use crate::gecko::selector_parser::*; - -#[cfg(feature = "servo")] -pub use crate::servo::selector_parser::ServoElementSnapshot as Snapshot; - -#[cfg(feature = "gecko")] -pub use crate::gecko::snapshot::GeckoElementSnapshot as Snapshot; - -#[cfg(feature = "servo")] -pub use crate::servo::restyle_damage::ServoRestyleDamage as RestyleDamage; - -#[cfg(feature = "gecko")] -pub use crate::gecko::restyle_damage::GeckoRestyleDamage as RestyleDamage; - -/// Servo's selector parser. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct SelectorParser<'a> { - /// The origin of the stylesheet we're parsing. - pub stylesheet_origin: Origin, - /// The namespace set of the stylesheet. - pub namespaces: &'a Namespaces, - /// The extra URL data of the stylesheet, which is used to look up - /// whether we are parsing a chrome:// URL style sheet. - pub url_data: &'a UrlExtraData, - /// Whether we're parsing selectors for `@supports` - pub for_supports_rule: bool, -} - -impl<'a> SelectorParser<'a> { - /// Parse a selector list with an author origin and without taking into - /// account namespaces. - /// - /// This is used for some DOM APIs like `querySelector`. - pub fn parse_author_origin_no_namespace<'i>( - input: &'i str, - url_data: &UrlExtraData, - ) -> Result<SelectorList<SelectorImpl>, ParseError<'i>> { - let namespaces = Namespaces::default(); - let parser = SelectorParser { - stylesheet_origin: Origin::Author, - namespaces: &namespaces, - url_data, - for_supports_rule: false, - }; - let mut input = ParserInput::new(input); - SelectorList::parse(&parser, &mut CssParser::new(&mut input)) - } - - /// Whether we're parsing selectors in a user-agent stylesheet. - pub fn in_user_agent_stylesheet(&self) -> bool { - matches!(self.stylesheet_origin, Origin::UserAgent) - } - - /// Whether we're parsing selectors in a stylesheet that has chrome - /// privilege. - pub fn chrome_rules_enabled(&self) -> bool { - self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User - } -} - -/// This enumeration determines if a pseudo-element is eagerly cascaded or not. -/// -/// If you're implementing a public selector for `Servo` that the end-user might -/// customize, then you probably need to make it eager. -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum PseudoElementCascadeType { - /// Eagerly cascaded pseudo-elements are "normal" pseudo-elements (i.e. - /// `::before` and `::after`). They inherit styles normally as another - /// selector would do, and they're computed as part of the cascade. - Eager, - /// Lazy pseudo-elements are affected by selector matching, but they're only - /// computed when needed, and not before. They're useful for general - /// pseudo-elements that are not very common. - /// - /// Note that in Servo lazy pseudo-elements are restricted to a subset of - /// selectors, so you can't use it for public pseudo-elements. This is not - /// the case with Gecko though. - Lazy, - /// Precomputed pseudo-elements skip the cascade process entirely, mostly as - /// an optimisation since they are private pseudo-elements (like - /// `::-servo-details-content`). - /// - /// This pseudo-elements are resolved on the fly using *only* global rules - /// (rules of the form `*|*`), and applying them to the parent style. - Precomputed, -} - -/// A per-pseudo map, from a given pseudo to a `T`. -#[derive(Clone, MallocSizeOf)] -pub struct PerPseudoElementMap<T> { - entries: [Option<T>; PSEUDO_COUNT], -} - -impl<T> Default for PerPseudoElementMap<T> { - fn default() -> Self { - Self { - entries: PseudoElement::pseudo_none_array(), - } - } -} - -impl<T> Debug for PerPseudoElementMap<T> -where - T: Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_char('[')?; - let mut first = true; - for entry in self.entries.iter() { - if !first { - f.write_str(", ")?; - } - first = false; - entry.fmt(f)?; - } - f.write_char(']') - } -} - -impl<T> PerPseudoElementMap<T> { - /// Get an entry in the map. - pub fn get(&self, pseudo: &PseudoElement) -> Option<&T> { - self.entries[pseudo.index()].as_ref() - } - - /// Clear this enumerated array. - pub fn clear(&mut self) { - *self = Self::default(); - } - - /// Set an entry value. - /// - /// Returns an error if the element is not a simple pseudo. - pub fn set(&mut self, pseudo: &PseudoElement, value: T) { - self.entries[pseudo.index()] = Some(value); - } - - /// Get an entry for `pseudo`, or create it with calling `f`. - pub fn get_or_insert_with<F>(&mut self, pseudo: &PseudoElement, f: F) -> &mut T - where - F: FnOnce() -> T, - { - let index = pseudo.index(); - if self.entries[index].is_none() { - self.entries[index] = Some(f()); - } - self.entries[index].as_mut().unwrap() - } - - /// Get an iterator for the entries. - pub fn iter(&self) -> std::slice::Iter<Option<T>> { - self.entries.iter() - } - - /// Get a mutable iterator for the entries. - pub fn iter_mut(&mut self) -> std::slice::IterMut<Option<T>> { - self.entries.iter_mut() - } -} - -/// Values for the :dir() pseudo class -/// -/// "ltr" and "rtl" values are normalized to lowercase. -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub struct Direction(pub Atom); - -/// Horizontal values for the :dir() pseudo class -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum HorizontalDirection { - /// :dir(ltr) - Ltr, - /// :dir(rtl) - Rtl, -} - -impl Direction { - /// Parse a direction value. - pub fn parse<'i, 't>(parser: &mut CssParser<'i, 't>) -> Result<Self, ParseError<'i>> { - let ident = parser.expect_ident()?; - Ok(Direction(match_ignore_ascii_case! { &ident, - "rtl" => atom!("rtl"), - "ltr" => atom!("ltr"), - _ => Atom::from(ident.as_ref()), - })) - } - - /// Convert this Direction into a HorizontalDirection, if applicable - pub fn as_horizontal_direction(&self) -> Option<HorizontalDirection> { - if self.0 == atom!("ltr") { - Some(HorizontalDirection::Ltr) - } else if self.0 == atom!("rtl") { - Some(HorizontalDirection::Rtl) - } else { - None - } - } - - /// Gets the element state relevant to this :dir() selector. - pub fn element_state(&self) -> ElementState { - match self.as_horizontal_direction() { - Some(HorizontalDirection::Ltr) => ElementState::LTR, - Some(HorizontalDirection::Rtl) => ElementState::RTL, - None => ElementState::empty(), - } - } -} - -impl ToCss for Direction { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs deleted file mode 100644 index 7ee7e1cfe5f..00000000000 --- a/components/style/servo/media_queries.rs +++ /dev/null @@ -1,305 +0,0 @@ -/* 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/. */ - -//! Servo's media-query device and expression representation. - -use crate::color::AbsoluteColor; -use crate::context::QuirksMode; -use crate::custom_properties::CssEnvironment; -use crate::font_metrics::FontMetrics; -use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::media_queries::MediaType; -use crate::properties::ComputedValues; -use crate::values::computed::CSSPixelLength; -use crate::values::computed::Context; -use crate::values::computed::Resolution; -use crate::values::specified::font::FONT_MEDIUM_PX; -use crate::values::specified::ViewportVariant; -use crate::values::KeyframesName; -use app_units::{Au, AU_PER_PX}; -use euclid::default::Size2D as UntypedSize2D; -use euclid::{Scale, SideOffsets2D, Size2D}; -use mime::Mime; -use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; -use style_traits::{CSSPixel, DevicePixel}; - -/// A device is a structure that represents the current media a given document -/// is displayed in. -/// -/// This is the struct against which media queries are evaluated. -#[derive(Debug, MallocSizeOf)] -pub struct Device { - /// The current media type used by de device. - media_type: MediaType, - /// The current viewport size, in CSS pixels. - viewport_size: Size2D<f32, CSSPixel>, - /// The current device pixel ratio, from CSS pixels to device pixels. - device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, - /// The current quirks mode. - #[ignore_malloc_size_of = "Pure stack type"] - quirks_mode: QuirksMode, - - /// The font size of the root element - /// This is set when computing the style of the root - /// element, and used for rem units in other elements - /// - /// When computing the style of the root element, there can't be any - /// other style being computed at the same time, given we need the style of - /// the parent to compute everything else. So it is correct to just use - /// a relaxed atomic here. - #[ignore_malloc_size_of = "Pure stack type"] - root_font_size: AtomicU32, - /// Whether any styles computed in the document relied on the root font-size - /// by using rem units. - #[ignore_malloc_size_of = "Pure stack type"] - used_root_font_size: AtomicBool, - /// Whether any styles computed in the document relied on the viewport size. - #[ignore_malloc_size_of = "Pure stack type"] - used_viewport_units: AtomicBool, - /// The CssEnvironment object responsible of getting CSS environment - /// variables. - environment: CssEnvironment, -} - -impl Device { - /// Trivially construct a new `Device`. - pub fn new( - media_type: MediaType, - quirks_mode: QuirksMode, - viewport_size: Size2D<f32, CSSPixel>, - device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>, - ) -> Device { - Device { - media_type, - viewport_size, - device_pixel_ratio, - quirks_mode, - // FIXME(bz): Seems dubious? - root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), - used_root_font_size: AtomicBool::new(false), - used_viewport_units: AtomicBool::new(false), - environment: CssEnvironment, - } - } - - /// Get the relevant environment to resolve `env()` functions. - #[inline] - pub fn environment(&self) -> &CssEnvironment { - &self.environment - } - - /// Return the default computed values for this device. - pub fn default_computed_values(&self) -> &ComputedValues { - // FIXME(bz): This isn't really right, but it's no more wrong - // than what we used to do. See - // https://github.com/servo/servo/issues/14773 for fixing it properly. - ComputedValues::initial_values() - } - - /// Get the font size of the root element (for rem) - pub fn root_font_size(&self) -> CSSPixelLength { - self.used_root_font_size.store(true, Ordering::Relaxed); - CSSPixelLength::new(f32::from_bits(self.root_font_size.load(Ordering::Relaxed))) - } - - /// Set the font size of the root element (for rem) - pub fn set_root_font_size(&self, size: CSSPixelLength) { - self.root_font_size - .store(size.px().to_bits(), Ordering::Relaxed) - } - - /// Get the quirks mode of the current device. - pub fn quirks_mode(&self) -> QuirksMode { - self.quirks_mode - } - - /// Sets the body text color for the "inherit color from body" quirk. - /// - /// <https://quirks.spec.whatwg.org/#the-tables-inherit-color-from-body-quirk> - pub fn set_body_text_color(&self, _color: AbsoluteColor) { - // Servo doesn't implement this quirk (yet) - } - - /// Whether a given animation name may be referenced from style. - pub fn animation_name_may_be_referenced(&self, _: &KeyframesName) -> bool { - // Assume it is, since we don't have any good way to prove it's not. - true - } - - /// Returns whether we ever looked up the root font size of the Device. - pub fn used_root_font_size(&self) -> bool { - self.used_root_font_size.load(Ordering::Relaxed) - } - - /// Returns the viewport size of the current device in app units, needed, - /// among other things, to resolve viewport units. - #[inline] - pub fn au_viewport_size(&self) -> UntypedSize2D<Au> { - Size2D::new( - Au::from_f32_px(self.viewport_size.width), - Au::from_f32_px(self.viewport_size.height), - ) - } - - /// Like the above, but records that we've used viewport units. - pub fn au_viewport_size_for_viewport_unit_resolution( - &self, - _: ViewportVariant, - ) -> UntypedSize2D<Au> { - self.used_viewport_units.store(true, Ordering::Relaxed); - // Servo doesn't have dynamic UA interfaces that affect the viewport, - // so we can just ignore the ViewportVariant. - self.au_viewport_size() - } - - /// Whether viewport units were used since the last device change. - pub fn used_viewport_units(&self) -> bool { - self.used_viewport_units.load(Ordering::Relaxed) - } - - /// Returns the number of app units per device pixel we're using currently. - pub fn app_units_per_device_pixel(&self) -> i32 { - (AU_PER_PX as f32 / self.device_pixel_ratio.0) as i32 - } - - /// Returns the device pixel ratio. - pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> { - self.device_pixel_ratio - } - - /// Gets the size of the scrollbar in CSS pixels. - pub fn scrollbar_inline_size(&self) -> CSSPixelLength { - // TODO: implement this. - CSSPixelLength::new(0.0) - } - - /// Queries dummy font metrics for Servo. Knows nothing about fonts and does not provide - /// any metrics. - /// TODO: Servo's font metrics provider will probably not live in this crate, so this will - /// have to be replaced with something else (perhaps a trait method on TElement) - /// when we get there - pub fn query_font_metrics( - &self, - _vertical: bool, - _font: &crate::properties::style_structs::Font, - _base_size: CSSPixelLength, - _in_media_query: bool, - _retrieve_math_scales: bool, - ) -> FontMetrics { - Default::default() - } - - /// Return the media type of the current device. - pub fn media_type(&self) -> MediaType { - self.media_type.clone() - } - - /// Returns whether document colors are enabled. - pub fn use_document_colors(&self) -> bool { - true - } - - /// Returns the default background color. - pub fn default_background_color(&self) -> AbsoluteColor { - AbsoluteColor::white() - } - - /// Returns the default foreground color. - pub fn default_color(&self) -> AbsoluteColor { - AbsoluteColor::black() - } - - /// Returns safe area insets - pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> { - SideOffsets2D::zero() - } - - /// Returns true if the given MIME type is supported - pub fn is_supported_mime_type(&self, mime_type: &str) -> bool { - match mime_type.parse::<Mime>() { - Ok(m) => { - // Keep this in sync with 'image_classifer' from - // components/net/mime_classifier.rs - m == mime::IMAGE_BMP - || m == mime::IMAGE_GIF - || m == mime::IMAGE_PNG - || m == mime::IMAGE_JPEG - || m == "image/x-icon" - || m == "image/webp" - } - _ => false, - } - } - - /// Return whether the document is a chrome document. - #[inline] - pub fn chrome_rules_enabled_for_document(&self) -> bool { - false - } -} - -/// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width(context: &Context) -> CSSPixelLength { - CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) -} - -#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] -#[repr(u8)] -enum Scan { - Progressive, - Interlace, -} - -/// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Context, _: Option<Scan>) -> bool { - // Since we doesn't support the 'tv' media type, the 'scan' feature never - // matches. - false -} - -/// https://drafts.csswg.org/mediaqueries-4/#resolution -fn eval_resolution(context: &Context) -> Resolution { - Resolution::from_dppx(context.device().device_pixel_ratio.0) -} - -/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio -fn eval_device_pixel_ratio(context: &Context) -> f32 { - eval_resolution(context).dppx() -} - -lazy_static! { - /// A list with all the media features that Servo supports. - pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 5] = [ - feature!( - atom!("width"), - AllowsRanges::Yes, - Evaluator::Length(eval_width), - FeatureFlags::empty(), - ), - feature!( - atom!("scan"), - AllowsRanges::No, - keyword_evaluator!(eval_scan, Scan), - FeatureFlags::empty(), - ), - feature!( - atom!("resolution"), - AllowsRanges::Yes, - Evaluator::Resolution(eval_resolution), - FeatureFlags::empty(), - ), - feature!( - atom!("device-pixel-ratio"), - AllowsRanges::Yes, - Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::WEBKIT_PREFIX, - ), - feature!( - atom!("-moz-device-pixel-ratio"), - AllowsRanges::Yes, - Evaluator::Float(eval_device_pixel_ratio), - FeatureFlags::empty(), - ), - ]; -} diff --git a/components/style/servo/mod.rs b/components/style/servo/mod.rs deleted file mode 100644 index 6502d287278..00000000000 --- a/components/style/servo/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -/* 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/. */ - -//! Servo-specific bits of the style system. -//! -//! These get compiled out on a Gecko build. - -pub mod media_queries; -pub mod restyle_damage; -pub mod selector_parser; -pub mod url; diff --git a/components/style/servo/restyle_damage.rs b/components/style/servo/restyle_damage.rs deleted file mode 100644 index 628a1533a68..00000000000 --- a/components/style/servo/restyle_damage.rs +++ /dev/null @@ -1,269 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The restyle damage is a hint that tells layout which kind of operations may -//! be needed in presence of incremental style changes. - -use crate::computed_values::display::T as Display; -use crate::matching::{StyleChange, StyleDifference}; -use crate::properties::ComputedValues; -use std::fmt; - -bitflags! { - /// Individual layout actions that may be necessary after restyling. - pub struct ServoRestyleDamage: u8 { - /// Repaint the node itself. - /// - /// Currently unused; need to decide how this propagates. - const REPAINT = 0x01; - - /// The stacking-context-relative position of this node or its - /// descendants has changed. - /// - /// Propagates both up and down the flow tree. - const REPOSITION = 0x02; - - /// Recompute the overflow regions (bounding box of object and all descendants). - /// - /// Propagates down the flow tree because the computation is bottom-up. - const STORE_OVERFLOW = 0x04; - - /// Recompute intrinsic inline_sizes (minimum and preferred). - /// - /// Propagates down the flow tree because the computation is. - /// bottom-up. - const BUBBLE_ISIZES = 0x08; - - /// Recompute actual inline-sizes and block-sizes, only taking - /// out-of-flow children into account. - /// - /// Propagates up the flow tree because the computation is top-down. - const REFLOW_OUT_OF_FLOW = 0x10; - - /// Recompute actual inline_sizes and block_sizes. - /// - /// Propagates up the flow tree because the computation is top-down. - const REFLOW = 0x20; - - /// Re-resolve generated content. - /// - /// Propagates up the flow tree because the computation is inorder. - const RESOLVE_GENERATED_CONTENT = 0x40; - - /// The entire flow needs to be reconstructed. - const RECONSTRUCT_FLOW = 0x80; - } -} - -malloc_size_of_is_0!(ServoRestyleDamage); - -impl ServoRestyleDamage { - /// Compute the `StyleDifference` (including the appropriate restyle damage) - /// for a given style change between `old` and `new`. - pub fn compute_style_difference(old: &ComputedValues, new: &ComputedValues) -> StyleDifference { - let damage = compute_damage(old, new); - let change = if damage.is_empty() { - StyleChange::Unchanged - } else { - // FIXME(emilio): Differentiate between reset and inherited - // properties here, and set `reset_only` appropriately so the - // optimization to skip the cascade in those cases applies. - StyleChange::Changed { reset_only: false } - }; - StyleDifference { damage, change } - } - - /// Returns a bitmask that represents a flow that needs to be rebuilt and - /// reflowed. - /// - /// FIXME(bholley): Do we ever actually need this? Shouldn't - /// RECONSTRUCT_FLOW imply everything else? - pub fn rebuild_and_reflow() -> ServoRestyleDamage { - ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::STORE_OVERFLOW | - ServoRestyleDamage::BUBBLE_ISIZES | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::REFLOW | - ServoRestyleDamage::RECONSTRUCT_FLOW - } - - /// Returns a bitmask indicating that the frame needs to be reconstructed. - pub fn reconstruct() -> ServoRestyleDamage { - ServoRestyleDamage::RECONSTRUCT_FLOW - } - - /// Supposing a flow has the given `position` property and this damage, - /// returns the damage that we should add to the *parent* of this flow. - pub fn damage_for_parent(self, child_is_absolutely_positioned: bool) -> ServoRestyleDamage { - if child_is_absolutely_positioned { - self & (ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::STORE_OVERFLOW | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) - } else { - self & (ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::STORE_OVERFLOW | - ServoRestyleDamage::REFLOW | - ServoRestyleDamage::REFLOW_OUT_OF_FLOW | - ServoRestyleDamage::RESOLVE_GENERATED_CONTENT) - } - } - - /// Supposing the *parent* of a flow with the given `position` property has - /// this damage, returns the damage that we should add to this flow. - pub fn damage_for_child( - self, - parent_is_absolutely_positioned: bool, - child_is_absolutely_positioned: bool, - ) -> ServoRestyleDamage { - match ( - parent_is_absolutely_positioned, - child_is_absolutely_positioned, - ) { - (false, true) => { - // Absolute children are out-of-flow and therefore insulated from changes. - // - // FIXME(pcwalton): Au contraire, if the containing block dimensions change! - self & (ServoRestyleDamage::REPAINT | ServoRestyleDamage::REPOSITION) - }, - (true, false) => { - // Changing the position of an absolutely-positioned block requires us to reflow - // its kids. - if self.contains(ServoRestyleDamage::REFLOW_OUT_OF_FLOW) { - self | ServoRestyleDamage::REFLOW - } else { - self - } - }, - _ => { - // TODO(pcwalton): Take floatedness into account. - self & (ServoRestyleDamage::REPAINT | - ServoRestyleDamage::REPOSITION | - ServoRestyleDamage::REFLOW) - }, - } - } -} - -impl Default for ServoRestyleDamage { - fn default() -> Self { - Self::empty() - } -} - -impl fmt::Display for ServoRestyleDamage { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let mut first_elem = true; - - let to_iter = [ - (ServoRestyleDamage::REPAINT, "Repaint"), - (ServoRestyleDamage::REPOSITION, "Reposition"), - (ServoRestyleDamage::STORE_OVERFLOW, "StoreOverflow"), - (ServoRestyleDamage::BUBBLE_ISIZES, "BubbleISizes"), - (ServoRestyleDamage::REFLOW_OUT_OF_FLOW, "ReflowOutOfFlow"), - (ServoRestyleDamage::REFLOW, "Reflow"), - ( - ServoRestyleDamage::RESOLVE_GENERATED_CONTENT, - "ResolveGeneratedContent", - ), - (ServoRestyleDamage::RECONSTRUCT_FLOW, "ReconstructFlow"), - ]; - - for &(damage, damage_str) in &to_iter { - if self.contains(damage) { - if !first_elem { - write!(f, " | ")?; - } - write!(f, "{}", damage_str)?; - first_elem = false; - } - } - - if first_elem { - write!(f, "NoDamage")?; - } - - Ok(()) - } -} - -fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> ServoRestyleDamage { - let mut damage = ServoRestyleDamage::empty(); - - // This should check every CSS property, as enumerated in the fields of - // https://doc.servo.org/style/properties/struct.ComputedValues.html - - // This uses short-circuiting boolean OR for its side effects and ignores the result. - let _ = restyle_damage_rebuild_and_reflow!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::BUBBLE_ISIZES, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW, - ServoRestyleDamage::REFLOW, - ServoRestyleDamage::RECONSTRUCT_FLOW - ], - old.get_box().original_display != new.get_box().original_display - ) || (new.get_box().display == Display::Inline && - restyle_damage_rebuild_and_reflow_inline!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::BUBBLE_ISIZES, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW, - ServoRestyleDamage::REFLOW, - ServoRestyleDamage::RECONSTRUCT_FLOW - ] - )) || - restyle_damage_reflow!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::BUBBLE_ISIZES, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW, - ServoRestyleDamage::REFLOW - ] - ) || - restyle_damage_reflow_out_of_flow!( - old, - new, - damage, - [ - ServoRestyleDamage::REPAINT, - ServoRestyleDamage::REPOSITION, - ServoRestyleDamage::STORE_OVERFLOW, - ServoRestyleDamage::REFLOW_OUT_OF_FLOW - ] - ) || - restyle_damage_repaint!(old, new, damage, [ServoRestyleDamage::REPAINT]); - - // Paint worklets may depend on custom properties, - // so if they have changed we should repaint. - if !old.custom_properties_equal(new) { - damage.insert(ServoRestyleDamage::REPAINT); - } - - // If the layer requirements of this flow have changed due to the value - // of the transform, then reflow is required to rebuild the layers. - if old.transform_requires_layer() != new.transform_requires_layer() { - damage.insert(ServoRestyleDamage::rebuild_and_reflow()); - } - - damage -} diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs deleted file mode 100644 index 0df3fe6b0ec..00000000000 --- a/components/style/servo/selector_parser.rs +++ /dev/null @@ -1,837 +0,0 @@ -/* 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/. */ - -#![deny(missing_docs)] - -//! Servo's selector parser. - -use crate::attr::{AttrIdentifier, AttrValue}; -use crate::computed_value_flags::ComputedValueFlags; -use crate::dom::{OpaqueNode, TElement, TNode}; -use crate::invalidation::element::document_state::InvalidationMatchingData; -use crate::invalidation::element::element_wrapper::ElementSnapshot; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::{ComputedValues, PropertyFlags}; -use crate::selector_parser::AttrValue as SelectorAttrValue; -use crate::selector_parser::{PseudoElementCascadeType, SelectorParser}; -use crate::values::{AtomIdent, AtomString}; -use crate::{Atom, CaseSensitivityExt, LocalName, Namespace, Prefix}; -use cssparser::{serialize_identifier, CowRcStr, Parser as CssParser, SourceLocation, ToCss}; -use style_traits::dom::{DocumentState, ElementState}; -use fxhash::FxHashMap; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::parser::SelectorParseErrorKind; -use selectors::visitor::SelectorVisitor; -use std::fmt; -use std::mem; -use std::ops::{Deref, DerefMut}; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// A pseudo-element, both public and private. -/// -/// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too. -#[derive( - Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem, -)] -#[allow(missing_docs)] -#[repr(usize)] -pub enum PseudoElement { - // Eager pseudos. Keep these first so that eager_index() works. - After = 0, - Before, - Selection, - // If/when :first-letter is added, update is_first_letter accordingly. - - // If/when :first-line is added, update is_first_line accordingly. - - // If/when ::first-letter, ::first-line, or ::placeholder are added, adjust - // our property_restriction implementation to do property filtering for - // them. Also, make sure the UA sheet has the !important rules some of the - // APPLIES_TO_PLACEHOLDER properties expect! - - // Non-eager pseudos. - DetailsSummary, - DetailsContent, - ServoAnonymousBox, - ServoAnonymousTableCell, - ServoAnonymousTableRow, - ServoLegacyText, - ServoLegacyInputText, - ServoLegacyTableWrapper, - ServoLegacyAnonymousTableWrapper, - ServoLegacyAnonymousTable, - ServoLegacyAnonymousBlock, - ServoLegacyInlineBlockWrapper, - ServoLegacyInlineAbsolute, -} - -/// The count of all pseudo-elements. -pub const PSEUDO_COUNT: usize = PseudoElement::ServoLegacyInlineAbsolute as usize + 1; - -impl ToCss for PseudoElement { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use self::PseudoElement::*; - dest.write_str(match *self { - After => "::after", - Before => "::before", - Selection => "::selection", - DetailsSummary => "::-servo-details-summary", - DetailsContent => "::-servo-details-content", - ServoAnonymousBox => "::-servo-anonymous-box", - ServoAnonymousTableCell => "::-servo-anonymous-table-cell", - ServoAnonymousTableRow => "::-servo-anonymous-table-row", - ServoLegacyText => "::-servo-legacy-text", - ServoLegacyInputText => "::-servo-legacy-input-text", - ServoLegacyTableWrapper => "::-servo-legacy-table-wrapper", - ServoLegacyAnonymousTableWrapper => "::-servo-legacy-anonymous-table-wrapper", - ServoLegacyAnonymousTable => "::-servo-legacy-anonymous-table", - ServoLegacyAnonymousBlock => "::-servo-legacy-anonymous-block", - ServoLegacyInlineBlockWrapper => "::-servo-legacy-inline-block-wrapper", - ServoLegacyInlineAbsolute => "::-servo-legacy-inline-absolute", - }) - } -} - -impl ::selectors::parser::PseudoElement for PseudoElement { - type Impl = SelectorImpl; -} - -/// The number of eager pseudo-elements. Keep this in sync with cascade_type. -pub const EAGER_PSEUDO_COUNT: usize = 3; - -impl PseudoElement { - /// Gets the canonical index of this eagerly-cascaded pseudo-element. - #[inline] - pub fn eager_index(&self) -> usize { - debug_assert!(self.is_eager()); - self.clone() as usize - } - - /// An index for this pseudo-element to be indexed in an enumerated array. - #[inline] - pub fn index(&self) -> usize { - self.clone() as usize - } - - /// An array of `None`, one per pseudo-element. - pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] { - Default::default() - } - - /// Creates a pseudo-element from an eager index. - #[inline] - pub fn from_eager_index(i: usize) -> Self { - assert!(i < EAGER_PSEUDO_COUNT); - let result: PseudoElement = unsafe { mem::transmute(i) }; - debug_assert!(result.is_eager()); - result - } - - /// Whether the current pseudo element is ::before or ::after. - #[inline] - pub fn is_before_or_after(&self) -> bool { - self.is_before() || self.is_after() - } - - /// Whether this is an unknown ::-webkit- pseudo-element. - #[inline] - pub fn is_unknown_webkit_pseudo_element(&self) -> bool { - false - } - - /// Whether this pseudo-element is the ::marker pseudo. - #[inline] - pub fn is_marker(&self) -> bool { - false - } - - /// Whether this pseudo-element is the ::selection pseudo. - #[inline] - pub fn is_selection(&self) -> bool { - *self == PseudoElement::Selection - } - - /// Whether this pseudo-element is the ::before pseudo. - #[inline] - pub fn is_before(&self) -> bool { - *self == PseudoElement::Before - } - - /// Whether this pseudo-element is the ::after pseudo. - #[inline] - pub fn is_after(&self) -> bool { - *self == PseudoElement::After - } - - /// Whether the current pseudo element is :first-letter - #[inline] - pub fn is_first_letter(&self) -> bool { - false - } - - /// Whether the current pseudo element is :first-line - #[inline] - pub fn is_first_line(&self) -> bool { - false - } - - /// Whether this pseudo-element is the ::-moz-color-swatch pseudo. - #[inline] - pub fn is_color_swatch(&self) -> bool { - false - } - - /// Whether this pseudo-element is eagerly-cascaded. - #[inline] - pub fn is_eager(&self) -> bool { - self.cascade_type() == PseudoElementCascadeType::Eager - } - - /// Whether this pseudo-element is lazily-cascaded. - #[inline] - pub fn is_lazy(&self) -> bool { - self.cascade_type() == PseudoElementCascadeType::Lazy - } - - /// Whether this pseudo-element is for an anonymous box. - pub fn is_anon_box(&self) -> bool { - self.is_precomputed() - } - - /// Whether this pseudo-element skips flex/grid container display-based - /// fixup. - #[inline] - pub fn skip_item_display_fixup(&self) -> bool { - !self.is_before_or_after() - } - - /// Whether this pseudo-element is precomputed. - #[inline] - pub fn is_precomputed(&self) -> bool { - self.cascade_type() == PseudoElementCascadeType::Precomputed - } - - /// Returns which kind of cascade type has this pseudo. - /// - /// For more info on cascade types, see docs/components/style.md - /// - /// Note: Keep this in sync with EAGER_PSEUDO_COUNT. - #[inline] - pub fn cascade_type(&self) -> PseudoElementCascadeType { - match *self { - PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => { - PseudoElementCascadeType::Eager - }, - PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy, - PseudoElement::DetailsContent | - PseudoElement::ServoAnonymousBox | - PseudoElement::ServoAnonymousTableCell | - PseudoElement::ServoAnonymousTableRow | - PseudoElement::ServoLegacyText | - PseudoElement::ServoLegacyInputText | - PseudoElement::ServoLegacyTableWrapper | - PseudoElement::ServoLegacyAnonymousTableWrapper | - PseudoElement::ServoLegacyAnonymousTable | - PseudoElement::ServoLegacyAnonymousBlock | - PseudoElement::ServoLegacyInlineBlockWrapper | - PseudoElement::ServoLegacyInlineAbsolute => PseudoElementCascadeType::Precomputed, - } - } - - /// Covert non-canonical pseudo-element to canonical one, and keep a - /// canonical one as it is. - pub fn canonical(&self) -> PseudoElement { - self.clone() - } - - /// Stub, only Gecko needs this - pub fn pseudo_info(&self) { - () - } - - /// Property flag that properties must have to apply to this pseudo-element. - #[inline] - pub fn property_restriction(&self) -> Option<PropertyFlags> { - None - } - - /// Whether this pseudo-element should actually exist if it has - /// the given styles. - pub fn should_exist(&self, style: &ComputedValues) -> bool { - let display = style.get_box().clone_display(); - if display == Display::None { - return false; - } - if self.is_before_or_after() && style.ineffective_content_property() { - return false; - } - - true - } -} - -/// The type used for storing `:lang` arguments. -pub type Lang = Box<str>; - -/// A non tree-structural pseudo-class. -/// See https://drafts.csswg.org/selectors-4/#structural-pseudos -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] -#[allow(missing_docs)] -pub enum NonTSPseudoClass { - Active, - AnyLink, - Checked, - Valid, - Invalid, - Defined, - Disabled, - Enabled, - Focus, - Fullscreen, - Hover, - Indeterminate, - Lang(Lang), - Link, - PlaceholderShown, - ReadWrite, - ReadOnly, - ServoNonZeroBorder, - Target, - Visited, -} - -impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { - type Impl = SelectorImpl; - - #[inline] - fn is_active_or_hover(&self) -> bool { - matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover) - } - - #[inline] - fn is_user_action_state(&self) -> bool { - matches!( - *self, - NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus - ) - } - - fn visit<V>(&self, _: &mut V) -> bool - where - V: SelectorVisitor<Impl = Self::Impl>, - { - true - } -} - -impl ToCss for NonTSPseudoClass { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use self::NonTSPseudoClass::*; - if let Lang(ref lang) = *self { - dest.write_str(":lang(")?; - serialize_identifier(lang, dest)?; - return dest.write_char(')'); - } - - dest.write_str(match *self { - Active => ":active", - AnyLink => ":any-link", - Checked => ":checked", - Valid => ":valid", - Invalid => ":invalid", - Defined => ":defined", - Disabled => ":disabled", - Enabled => ":enabled", - Focus => ":focus", - Fullscreen => ":fullscreen", - Hover => ":hover", - Indeterminate => ":indeterminate", - Link => ":link", - PlaceholderShown => ":placeholder-shown", - ReadWrite => ":read-write", - ReadOnly => ":read-only", - ServoNonZeroBorder => ":-servo-nonzero-border", - Target => ":target", - Visited => ":visited", - Lang(_) => unreachable!(), - }) - } -} - -impl NonTSPseudoClass { - /// Gets a given state flag for this pseudo-class. This is used to do - /// selector matching, and it's set from the DOM. - pub fn state_flag(&self) -> ElementState { - use self::NonTSPseudoClass::*; - match *self { - Active => ElementState::ACTIVE, - Focus => ElementState::FOCUS, - Fullscreen => ElementState::FULLSCREEN, - Hover => ElementState::HOVER, - Defined => ElementState::DEFINED, - Enabled => ElementState::ENABLED, - Disabled => ElementState::DISABLED, - Checked => ElementState::CHECKED, - Valid => ElementState::VALID, - Invalid => ElementState::INVALID, - Indeterminate => ElementState::INDETERMINATE, - ReadOnly | ReadWrite => ElementState::READWRITE, - PlaceholderShown => ElementState::PLACEHOLDER_SHOWN, - Target => ElementState::URLTARGET, - - AnyLink | Lang(_) | Link | Visited | ServoNonZeroBorder => ElementState::empty(), - } - } - - /// Get the document state flag associated with a pseudo-class, if any. - pub fn document_state_flag(&self) -> DocumentState { - DocumentState::empty() - } - - /// Returns true if the given pseudoclass should trigger style sharing cache revalidation. - pub fn needs_cache_revalidation(&self) -> bool { - self.state_flag().is_empty() - } -} - -/// The abstract struct we implement the selector parser implementation on top -/// of. -#[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct SelectorImpl; - -/// A set of extra data to carry along with the matching context, either for -/// selector-matching or invalidation. -#[derive(Debug, Default)] -pub struct ExtraMatchingData<'a> { - /// The invalidation data to invalidate doc-state pseudo-classes correctly. - pub invalidation_data: InvalidationMatchingData, - - /// The invalidation bits from matching container queries. These are here - /// just for convenience mostly. - pub cascade_input_flags: ComputedValueFlags, - - /// The style of the originating element in order to evaluate @container - /// size queries affecting pseudo-elements. - pub originating_element_style: Option<&'a ComputedValues>, -} - -impl ::selectors::SelectorImpl for SelectorImpl { - type PseudoElement = PseudoElement; - type NonTSPseudoClass = NonTSPseudoClass; - - type ExtraMatchingData<'a> = ExtraMatchingData<'a>; - type AttrValue = AtomString; - type Identifier = AtomIdent; - type LocalName = LocalName; - type NamespacePrefix = Prefix; - type NamespaceUrl = Namespace; - type BorrowedLocalName = html5ever::LocalName; - type BorrowedNamespaceUrl = html5ever::Namespace; -} - -impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> { - type Impl = SelectorImpl; - type Error = StyleParseErrorKind<'i>; - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<NonTSPseudoClass, ParseError<'i>> { - use self::NonTSPseudoClass::*; - let pseudo_class = match_ignore_ascii_case! { &name, - "active" => Active, - "any-link" => AnyLink, - "checked" => Checked, - "valid" => Valid, - "invalid" => Invalid, - "defined" => Defined, - "disabled" => Disabled, - "enabled" => Enabled, - "focus" => Focus, - "fullscreen" => Fullscreen, - "hover" => Hover, - "indeterminate" => Indeterminate, - "link" => Link, - "placeholder-shown" => PlaceholderShown, - "read-write" => ReadWrite, - "read-only" => ReadOnly, - "target" => Target, - "visited" => Visited, - "-servo-nonzero-border" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into()) - )) - } - ServoNonZeroBorder - }, - _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - }; - - Ok(pseudo_class) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result<NonTSPseudoClass, ParseError<'i>> { - use self::NonTSPseudoClass::*; - let pseudo_class = match_ignore_ascii_case! { &name, - "lang" => { - Lang(parser.expect_ident_or_string()?.as_ref().into()) - }, - _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - }; - - Ok(pseudo_class) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<PseudoElement, ParseError<'i>> { - use self::PseudoElement::*; - let pseudo_element = match_ignore_ascii_case! { &name, - "before" => Before, - "after" => After, - "selection" => Selection, - "-servo-details-summary" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - DetailsSummary - }, - "-servo-details-content" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - DetailsContent - }, - "-servo-anonymous-box" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoAnonymousBox - }, - "-servo-legacy-text" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyText - }, - "-servo-legacy-input-text" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyInputText - }, - "-servo-legacy-table-wrapper" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyTableWrapper - }, - "-servo-legacy-anonymous-table-wrapper" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyAnonymousTableWrapper - }, - "-servo-legacy-anonymous-table" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyAnonymousTable - }, - "-servo-anonymous-table-row" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoAnonymousTableRow - }, - "-servo-anonymous-table-cell" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoAnonymousTableCell - }, - "-servo-legacy-anonymous-block" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyAnonymousBlock - }, - "-servo-legacy-inline-block-wrapper" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyInlineBlockWrapper - }, - "-servo-legacy-inline-absolute" => { - if !self.in_user_agent_stylesheet() { - return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - } - ServoLegacyInlineAbsolute - }, - _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))) - - }; - - Ok(pseudo_element) - } - - fn default_namespace(&self) -> Option<Namespace> { - self.namespaces.default.as_ref().map(|ns| ns.clone()) - } - - fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> { - self.namespaces.prefixes.get(prefix).cloned() - } -} - -impl SelectorImpl { - /// A helper to traverse each eagerly cascaded pseudo-element, executing - /// `fun` on it. - #[inline] - pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F) - where - F: FnMut(PseudoElement), - { - for i in 0..EAGER_PSEUDO_COUNT { - fun(PseudoElement::from_eager_index(i)); - } - } -} - -/// A map from elements to snapshots for the Servo style back-end. -#[derive(Debug)] -pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>); - -impl SnapshotMap { - /// Create a new empty `SnapshotMap`. - pub fn new() -> Self { - SnapshotMap(FxHashMap::default()) - } - - /// Get a snapshot given an element. - pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> { - self.0.get(&el.as_node().opaque()) - } -} - -impl Deref for SnapshotMap { - type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SnapshotMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Servo's version of an element snapshot. -#[derive(Debug, Default, MallocSizeOf)] -pub struct ServoElementSnapshot { - /// The stored state of the element. - pub state: Option<ElementState>, - /// The set of stored attributes and its values. - pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>, - /// The set of changed attributes and its values. - pub changed_attrs: Vec<LocalName>, - /// Whether the class attribute changed or not. - pub class_changed: bool, - /// Whether the id attribute changed or not. - pub id_changed: bool, - /// Whether other attributes other than id or class changed or not. - pub other_attributes_changed: bool, -} - -impl ServoElementSnapshot { - /// Create an empty element snapshot. - pub fn new() -> Self { - Self::default() - } - - /// Returns whether the id attribute changed or not. - pub fn id_changed(&self) -> bool { - self.id_changed - } - - /// Returns whether the class attribute changed or not. - pub fn class_changed(&self) -> bool { - self.class_changed - } - - /// Returns whether other attributes other than id or class changed or not. - pub fn other_attr_changed(&self) -> bool { - self.other_attributes_changed - } - - fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { - self.attrs - .as_ref() - .unwrap() - .iter() - .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace) - .map(|&(_, ref v)| v) - } - - /// Executes the callback once for each attribute that changed. - #[inline] - pub fn each_attr_changed<F>(&self, mut callback: F) - where - F: FnMut(&LocalName), - { - for name in &self.changed_attrs { - callback(name) - } - } - - fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool - where - F: FnMut(&AttrValue) -> bool, - { - self.attrs - .as_ref() - .unwrap() - .iter() - .any(|&(ref ident, ref v)| ident.local_name == *name && f(v)) - } -} - -impl ElementSnapshot for ServoElementSnapshot { - fn state(&self) -> Option<ElementState> { - self.state.clone() - } - - fn has_attrs(&self) -> bool { - self.attrs.is_some() - } - - fn id_attr(&self) -> Option<&Atom> { - self.get_attr(&ns!(), &local_name!("id")) - .map(|v| v.as_atom()) - } - - fn is_part(&self, _name: &AtomIdent) -> bool { - false - } - - fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> { - None - } - - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - self.get_attr(&ns!(), &local_name!("class")) - .map_or(false, |v| { - v.as_tokens() - .iter() - .any(|atom| case_sensitivity.eq_atom(atom, name)) - }) - } - - fn each_class<F>(&self, mut callback: F) - where - F: FnMut(&AtomIdent), - { - if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) { - for class in v.as_tokens() { - callback(AtomIdent::cast(class)); - } - } - } - - fn lang_attr(&self) -> Option<SelectorAttrValue> { - self.get_attr(&ns!(xml), &local_name!("lang")) - .or_else(|| self.get_attr(&ns!(), &local_name!("lang"))) - .map(|v| SelectorAttrValue::from(v as &str)) - } -} - -impl ServoElementSnapshot { - /// selectors::Element::attr_matches - pub fn attr_matches( - &self, - ns: &NamespaceConstraint<&Namespace>, - local_name: &LocalName, - operation: &AttrSelectorOperation<&AtomString>, - ) -> bool { - match *ns { - NamespaceConstraint::Specific(ref ns) => self - .get_attr(ns, local_name) - .map_or(false, |value| value.eval_selector(operation)), - NamespaceConstraint::Any => { - self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation)) - }, - } - } -} - -/// Returns whether the language is matched, as defined by -/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2). -pub fn extended_filtering(tag: &str, range: &str) -> bool { - range.split(',').any(|lang_range| { - // step 1 - let mut range_subtags = lang_range.split('\x2d'); - let mut tag_subtags = tag.split('\x2d'); - - // step 2 - // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card - if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) { - if !(range_subtag.eq_ignore_ascii_case(tag_subtag) || - range_subtag.eq_ignore_ascii_case("*")) - { - return false; - } - } - - let mut current_tag_subtag = tag_subtags.next(); - - // step 3 - for range_subtag in range_subtags { - // step 3a - if range_subtag == "*" { - continue; - } - match current_tag_subtag.clone() { - Some(tag_subtag) => { - // step 3c - if range_subtag.eq_ignore_ascii_case(tag_subtag) { - current_tag_subtag = tag_subtags.next(); - continue; - } - // step 3d - if tag_subtag.len() == 1 { - return false; - } - // else step 3e - continue with loop - current_tag_subtag = tag_subtags.next(); - if current_tag_subtag.is_none() { - return false; - } - }, - // step 3b - None => { - return false; - }, - } - } - // step 4 - true - }) -} diff --git a/components/style/servo/url.rs b/components/style/servo/url.rs deleted file mode 100644 index ab1ef8435f7..00000000000 --- a/components/style/servo/url.rs +++ /dev/null @@ -1,246 +0,0 @@ -/* 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/. */ - -//! Common handling for the specified value CSS url() values. - -use crate::parser::{Parse, ParserContext}; -use crate::stylesheets::CorsMode; -use crate::values::computed::{Context, ToComputedValue}; -use cssparser::Parser; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; -use to_shmem::{SharedMemoryBuilder, ToShmem}; -use url::Url; - -/// A CSS url() value for servo. -/// -/// Servo eagerly resolves SpecifiedUrls, which it can then take advantage of -/// when computing values. In contrast, Gecko uses a different URL backend, so -/// eagerly resolving with rust-url would be duplicated work. -/// -/// However, this approach is still not necessarily optimal: See -/// <https://bugzilla.mozilla.org/show_bug.cgi?id=1347435#c6> -/// -/// TODO(emilio): This should be shrunk by making CssUrl a wrapper type of an -/// arc, and keep the serialization in that Arc. See gecko/url.rs for example. -#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, SpecifiedValueInfo)] -pub struct CssUrl { - /// The original URI. This might be optional since we may insert computed - /// values of images into the cascade directly, and we don't bother to - /// convert their serialization. - /// - /// Refcounted since cloning this should be cheap and data: uris can be - /// really large. - #[ignore_malloc_size_of = "Arc"] - original: Option<Arc<String>>, - - /// The resolved value for the url, if valid. - #[ignore_malloc_size_of = "Arc"] - resolved: Option<Arc<Url>>, -} - -impl ToShmem for CssUrl { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - unimplemented!("If servo wants to share stylesheets across processes, ToShmem for Url must be implemented"); - } -} - -impl CssUrl { - /// Try to parse a URL from a string value that is a valid CSS token for a - /// URL. - /// - /// FIXME(emilio): Should honor CorsMode. - pub fn parse_from_string(url: String, context: &ParserContext, _: CorsMode) -> Self { - let serialization = Arc::new(url); - let resolved = context.url_data.0.join(&serialization).ok().map(Arc::new); - CssUrl { - original: Some(serialization), - resolved: resolved, - } - } - - /// Returns true if the URL is definitely invalid. For Servo URLs, we can - /// use its |resolved| status. - pub fn is_invalid(&self) -> bool { - self.resolved.is_none() - } - - /// Returns true if this URL looks like a fragment. - /// See https://drafts.csswg.org/css-values/#local-urls - /// - /// Since Servo currently stores resolved URLs, this is hard to implement. We - /// either need to change servo to lazily resolve (like Gecko), or note this - /// information in the tokenizer. - pub fn is_fragment(&self) -> bool { - error!("Can't determine whether the url is a fragment."); - false - } - - /// Returns the resolved url if it was valid. - pub fn url(&self) -> Option<&Arc<Url>> { - self.resolved.as_ref() - } - - /// Return the resolved url as string, or the empty string if it's invalid. - /// - /// TODO(emilio): Should we return the original one if needed? - pub fn as_str(&self) -> &str { - match self.resolved { - Some(ref url) => url.as_str(), - None => "", - } - } - - /// Creates an already specified url value from an already resolved URL - /// for insertion in the cascade. - pub fn for_cascade(url: Arc<::url::Url>) -> Self { - CssUrl { - original: None, - resolved: Some(url), - } - } - - /// Gets a new url from a string for unit tests. - pub fn new_for_testing(url: &str) -> Self { - CssUrl { - original: Some(Arc::new(url.into())), - resolved: ::url::Url::parse(url).ok().map(Arc::new), - } - } - - /// Parses a URL request and records that the corresponding request needs to - /// be CORS-enabled. - /// - /// This is only for shape images and masks in Gecko, thus unimplemented for - /// now so somebody notices when trying to do so. - pub fn parse_with_cors_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - ) -> Result<Self, ParseError<'i>> { - let url = input.expect_url()?; - Ok(Self::parse_from_string( - url.as_ref().to_owned(), - context, - cors_mode, - )) - } -} - -impl Parse for CssUrl { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_cors_mode(context, input, CorsMode::None) - } -} - -impl PartialEq for CssUrl { - fn eq(&self, other: &Self) -> bool { - // TODO(emilio): maybe we care about equality of the specified values if - // present? Seems not. - self.resolved == other.resolved - } -} - -impl Eq for CssUrl {} - -impl ToCss for CssUrl { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let string = match self.original { - Some(ref original) => &**original, - None => match self.resolved { - Some(ref url) => url.as_str(), - // This can only happen if the url wasn't specified by the - // user *and* it's an invalid url that has been transformed - // back to specified value via the "uncompute" functionality. - None => "about:invalid", - }, - }; - - dest.write_str("url(")?; - string.to_css(dest)?; - dest.write_char(')') - } -} - -/// A specified url() value for servo. -pub type SpecifiedUrl = CssUrl; - -impl ToComputedValue for SpecifiedUrl { - type ComputedValue = ComputedUrl; - - // If we can't resolve the URL from the specified one, we fall back to the original - // but still return it as a ComputedUrl::Invalid - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - match self.resolved { - Some(ref url) => ComputedUrl::Valid(url.clone()), - None => match self.original { - Some(ref url) => ComputedUrl::Invalid(url.clone()), - None => { - unreachable!("Found specified url with neither resolved or original URI!"); - }, - }, - } - } - - fn from_computed_value(computed: &ComputedUrl) -> Self { - match *computed { - ComputedUrl::Valid(ref url) => SpecifiedUrl { - original: None, - resolved: Some(url.clone()), - }, - ComputedUrl::Invalid(ref url) => SpecifiedUrl { - original: Some(url.clone()), - resolved: None, - }, - } - } -} - -/// A specified image url() value for servo. -pub type SpecifiedImageUrl = CssUrl; - -/// The computed value of a CSS `url()`, resolved relative to the stylesheet URL. -#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] -pub enum ComputedUrl { - /// The `url()` was invalid or it wasn't specified by the user. - Invalid(#[ignore_malloc_size_of = "Arc"] Arc<String>), - /// The resolved `url()` relative to the stylesheet URL. - Valid(#[ignore_malloc_size_of = "Arc"] Arc<Url>), -} - -impl ComputedUrl { - /// Returns the resolved url if it was valid. - pub fn url(&self) -> Option<&Arc<Url>> { - match *self { - ComputedUrl::Valid(ref url) => Some(url), - _ => None, - } - } -} - -impl ToCss for ComputedUrl { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let string = match *self { - ComputedUrl::Valid(ref url) => url.as_str(), - ComputedUrl::Invalid(ref invalid_string) => invalid_string, - }; - - dest.write_str("url(")?; - string.to_css(dest)?; - dest.write_char(')') - } -} - -/// The computed value of a CSS `url()` for image. -pub type ComputedImageUrl = ComputedUrl; diff --git a/components/style/shared_lock.rs b/components/style/shared_lock.rs deleted file mode 100644 index 55708a9f7bb..00000000000 --- a/components/style/shared_lock.rs +++ /dev/null @@ -1,374 +0,0 @@ -/* 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/. */ - -//! Different objects protected by the same lock - -use crate::str::{CssString, CssStringWriter}; -use crate::stylesheets::Origin; -#[cfg(feature = "gecko")] -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -#[cfg(feature = "servo")] -use parking_lot::RwLock; -use servo_arc::Arc; -use std::cell::UnsafeCell; -use std::fmt; -#[cfg(feature = "servo")] -use std::mem; -#[cfg(feature = "gecko")] -use std::ptr; -use to_shmem::{SharedMemoryBuilder, ToShmem}; - -/// A shared read/write lock that can protect multiple objects. -/// -/// In Gecko builds, we don't need the blocking behavior, just the safety. As -/// such we implement this with an AtomicRefCell instead in Gecko builds, -/// which is ~2x as fast, and panics (rather than deadlocking) when things go -/// wrong (which is much easier to debug on CI). -/// -/// Servo needs the blocking behavior for its unsynchronized animation setup, -/// but that may not be web-compatible and may need to be changed (at which -/// point Servo could use AtomicRefCell too). -/// -/// Gecko also needs the ability to have "read only" SharedRwLocks, which are -/// used for objects stored in (read only) shared memory. Attempting to acquire -/// write access to objects protected by a read only SharedRwLock will panic. -#[derive(Clone)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct SharedRwLock { - #[cfg(feature = "servo")] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - arc: Arc<RwLock<()>>, - - #[cfg(feature = "gecko")] - cell: Option<Arc<AtomicRefCell<SomethingZeroSizedButTyped>>>, -} - -#[cfg(feature = "gecko")] -struct SomethingZeroSizedButTyped; - -impl fmt::Debug for SharedRwLock { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("SharedRwLock") - } -} - -impl SharedRwLock { - /// Create a new shared lock (servo). - #[cfg(feature = "servo")] - pub fn new() -> Self { - SharedRwLock { - arc: Arc::new(RwLock::new(())), - } - } - - /// Create a new shared lock (gecko). - #[cfg(feature = "gecko")] - pub fn new() -> Self { - SharedRwLock { - cell: Some(Arc::new(AtomicRefCell::new(SomethingZeroSizedButTyped))), - } - } - - /// Create a new global shared lock (servo). - #[cfg(feature = "servo")] - pub fn new_leaked() -> Self { - SharedRwLock { - arc: Arc::new_leaked(RwLock::new(())), - } - } - - /// Create a new global shared lock (gecko). - #[cfg(feature = "gecko")] - pub fn new_leaked() -> Self { - SharedRwLock { - cell: Some(Arc::new_leaked(AtomicRefCell::new( - SomethingZeroSizedButTyped, - ))), - } - } - - /// Create a new read-only shared lock (gecko). - #[cfg(feature = "gecko")] - pub fn read_only() -> Self { - SharedRwLock { cell: None } - } - - #[cfg(feature = "gecko")] - #[inline] - fn ptr(&self) -> *const SomethingZeroSizedButTyped { - self.cell - .as_ref() - .map(|cell| cell.as_ptr() as *const _) - .unwrap_or(ptr::null()) - } - - /// Wrap the given data to make its access protected by this lock. - pub fn wrap<T>(&self, data: T) -> Locked<T> { - Locked { - shared_lock: self.clone(), - data: UnsafeCell::new(data), - } - } - - /// Obtain the lock for reading (servo). - #[cfg(feature = "servo")] - pub fn read(&self) -> SharedRwLockReadGuard { - mem::forget(self.arc.read()); - SharedRwLockReadGuard(self) - } - - /// Obtain the lock for reading (gecko). - #[cfg(feature = "gecko")] - pub fn read(&self) -> SharedRwLockReadGuard { - SharedRwLockReadGuard(self.cell.as_ref().map(|cell| cell.borrow())) - } - - /// Obtain the lock for writing (servo). - #[cfg(feature = "servo")] - pub fn write(&self) -> SharedRwLockWriteGuard { - mem::forget(self.arc.write()); - SharedRwLockWriteGuard(self) - } - - /// Obtain the lock for writing (gecko). - #[cfg(feature = "gecko")] - pub fn write(&self) -> SharedRwLockWriteGuard { - SharedRwLockWriteGuard(self.cell.as_ref().unwrap().borrow_mut()) - } -} - -/// Proof that a shared lock was obtained for reading (servo). -#[cfg(feature = "servo")] -pub struct SharedRwLockReadGuard<'a>(&'a SharedRwLock); -/// Proof that a shared lock was obtained for reading (gecko). -#[cfg(feature = "gecko")] -pub struct SharedRwLockReadGuard<'a>(Option<AtomicRef<'a, SomethingZeroSizedButTyped>>); -#[cfg(feature = "servo")] -impl<'a> Drop for SharedRwLockReadGuard<'a> { - fn drop(&mut self) { - // Unsafe: self.lock is private to this module, only ever set after `read()`, - // and never copied or cloned (see `compile_time_assert` below). - unsafe { self.0.arc.force_unlock_read() } - } -} - -impl<'a> SharedRwLockReadGuard<'a> { - #[inline] - #[cfg(feature = "gecko")] - fn ptr(&self) -> *const SomethingZeroSizedButTyped { - self.0 - .as_ref() - .map(|r| &**r as *const _) - .unwrap_or(ptr::null()) - } -} - -/// Proof that a shared lock was obtained for writing (servo). -#[cfg(feature = "servo")] -pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock); -/// Proof that a shared lock was obtained for writing (gecko). -#[cfg(feature = "gecko")] -pub struct SharedRwLockWriteGuard<'a>(AtomicRefMut<'a, SomethingZeroSizedButTyped>); -#[cfg(feature = "servo")] -impl<'a> Drop for SharedRwLockWriteGuard<'a> { - fn drop(&mut self) { - // Unsafe: self.lock is private to this module, only ever set after `write()`, - // and never copied or cloned (see `compile_time_assert` below). - unsafe { self.0.arc.force_unlock_write() } - } -} - -/// Data protect by a shared lock. -pub struct Locked<T> { - shared_lock: SharedRwLock, - data: UnsafeCell<T>, -} - -// Unsafe: the data inside `UnsafeCell` is only accessed in `read_with` and `write_with`, -// where guards ensure synchronization. -unsafe impl<T: Send> Send for Locked<T> {} -unsafe impl<T: Send + Sync> Sync for Locked<T> {} - -impl<T: fmt::Debug> fmt::Debug for Locked<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let guard = self.shared_lock.read(); - self.read_with(&guard).fmt(f) - } -} - -impl<T> Locked<T> { - #[cfg(feature = "gecko")] - #[inline] - fn is_read_only_lock(&self) -> bool { - self.shared_lock.cell.is_none() - } - - #[cfg(feature = "servo")] - fn same_lock_as(&self, lock: &SharedRwLock) -> bool { - Arc::ptr_eq(&self.shared_lock.arc, &lock.arc) - } - - #[cfg(feature = "gecko")] - fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool { - ptr::eq(self.shared_lock.ptr(), ptr) - } - - /// Access the data for reading. - pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T { - #[cfg(feature = "gecko")] - assert!( - self.is_read_only_lock() || self.same_lock_as(guard.ptr()), - "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}", - self.shared_lock.ptr(), - guard.ptr(), - ); - #[cfg(not(feature = "gecko"))] - assert!(self.same_lock_as(&guard.0)); - - let ptr = self.data.get(); - - // Unsafe: - // - // * The guard guarantees that the lock is taken for reading, - // and we’ve checked that it’s the correct lock. - // * The returned reference borrows *both* the data and the guard, - // so that it can outlive neither. - unsafe { &*ptr } - } - - /// Access the data for reading without verifying the lock. Use with caution. - #[cfg(feature = "gecko")] - pub unsafe fn read_unchecked<'a>(&'a self) -> &'a T { - let ptr = self.data.get(); - &*ptr - } - - /// Access the data for writing. - pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T { - #[cfg(feature = "gecko")] - assert!( - !self.is_read_only_lock() && self.same_lock_as(&*guard.0), - "Locked::write_with called with a guard from a read only or unrelated SharedRwLock" - ); - #[cfg(not(feature = "gecko"))] - assert!(self.same_lock_as(&guard.0)); - - let ptr = self.data.get(); - - // Unsafe: - // - // * The guard guarantees that the lock is taken for writing, - // and we’ve checked that it’s the correct lock. - // * The returned reference borrows *both* the data and the guard, - // so that it can outlive neither. - // * We require a mutable borrow of the guard, - // so that one write guard can only be used once at a time. - unsafe { &mut *ptr } - } -} - -#[cfg(feature = "gecko")] -impl<T: ToShmem> ToShmem for Locked<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - use std::mem::ManuallyDrop; - - let guard = self.shared_lock.read(); - Ok(ManuallyDrop::new(Locked { - shared_lock: SharedRwLock::read_only(), - data: UnsafeCell::new(ManuallyDrop::into_inner( - self.read_with(&guard).to_shmem(builder)?, - )), - })) - } -} - -#[cfg(feature = "servo")] -impl<T: ToShmem> ToShmem for Locked<T> { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - panic!("ToShmem not supported in Servo currently") - } -} - -#[allow(dead_code)] -mod compile_time_assert { - use super::{SharedRwLockReadGuard, SharedRwLockWriteGuard}; - - trait Marker1 {} - impl<T: Clone> Marker1 for T {} - impl<'a> Marker1 for SharedRwLockReadGuard<'a> {} // Assert SharedRwLockReadGuard: !Clone - impl<'a> Marker1 for SharedRwLockWriteGuard<'a> {} // Assert SharedRwLockWriteGuard: !Clone - - trait Marker2 {} - impl<T: Copy> Marker2 for T {} - impl<'a> Marker2 for SharedRwLockReadGuard<'a> {} // Assert SharedRwLockReadGuard: !Copy - impl<'a> Marker2 for SharedRwLockWriteGuard<'a> {} // Assert SharedRwLockWriteGuard: !Copy -} - -/// Like ToCss, but with a lock guard given by the caller, and with the writer specified -/// concretely rather than with a parameter. -pub trait ToCssWithGuard { - /// Serialize `self` in CSS syntax, writing to `dest`, using the given lock guard. - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result; - - /// Serialize `self` in CSS syntax using the given lock guard and return a string. - /// - /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) - #[inline] - fn to_css_string(&self, guard: &SharedRwLockReadGuard) -> CssString { - let mut s = CssString::new(); - self.to_css(guard, &mut s).unwrap(); - s - } -} - -/// Parameters needed for deep clones. -#[cfg(feature = "gecko")] -pub struct DeepCloneParams { - /// The new sheet we're cloning rules into. - pub reference_sheet: *const crate::gecko_bindings::structs::StyleSheet, -} - -/// Parameters needed for deep clones. -#[cfg(feature = "servo")] -pub struct DeepCloneParams; - -/// A trait to do a deep clone of a given CSS type. Gets a lock and a read -/// guard, in order to be able to read and clone nested structures. -pub trait DeepCloneWithLock: Sized { - /// Deep clones this object. - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self; -} - -/// Guards for a document -#[derive(Clone)] -pub struct StylesheetGuards<'a> { - /// For author-origin stylesheets. - pub author: &'a SharedRwLockReadGuard<'a>, - - /// For user-agent-origin and user-origin stylesheets - pub ua_or_user: &'a SharedRwLockReadGuard<'a>, -} - -impl<'a> StylesheetGuards<'a> { - /// Get the guard for a given stylesheet origin. - pub fn for_origin(&self, origin: Origin) -> &SharedRwLockReadGuard<'a> { - match origin { - Origin::Author => &self.author, - _ => &self.ua_or_user, - } - } - - /// Same guard for all origins - pub fn same(guard: &'a SharedRwLockReadGuard<'a>) -> Self { - StylesheetGuards { - author: guard, - ua_or_user: guard, - } - } -} diff --git a/components/style/sharing/checks.rs b/components/style/sharing/checks.rs deleted file mode 100644 index a691ac5c764..00000000000 --- a/components/style/sharing/checks.rs +++ /dev/null @@ -1,182 +0,0 @@ -/* 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/. */ - -//! Different checks done during the style sharing process in order to determine -//! quickly whether it's worth to share style, and whether two different -//! elements can indeed share the same style. - -use crate::bloom::StyleBloom; -use crate::context::SharedStyleContext; -use crate::dom::TElement; -use crate::sharing::{StyleSharingCandidate, StyleSharingTarget}; -use selectors::NthIndexCache; - -/// Determines whether a target and a candidate have compatible parents for -/// sharing. -pub fn parents_allow_sharing<E>( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, -) -> bool -where - E: TElement, -{ - // If the identity of the parent style isn't equal, we can't share. We check - // this first, because the result is cached. - if target.parent_style_identity() != candidate.parent_style_identity() { - return false; - } - - // Siblings can always share. - let parent = target.inheritance_parent().unwrap(); - let candidate_parent = candidate.element.inheritance_parent().unwrap(); - if parent == candidate_parent { - return true; - } - - // Cousins are a bit more complicated. - // - // The fact that the candidate is here means that its element does not anchor - // the relative selector. However, it may have considered relative selector(s) - // to compute its style, i.e. there's a rule `<..> :has(<..>) <..> candidate`. - // In this case, evaluating style sharing requires evaluating the relative - // selector for the target anyway. - if candidate.considered_relative_selector { - return false; - } - - // If a parent element was already styled and we traversed past it without - // restyling it, that may be because our clever invalidation logic was able - // to prove that the styles of that element would remain unchanged despite - // changes to the id or class attributes. However, style sharing relies in - // the strong guarantee that all the classes and ids up the respective parent - // chains are identical. As such, if we skipped styling for one (or both) of - // the parents on this traversal, we can't share styles across cousins. - // - // This is a somewhat conservative check. We could tighten it by having the - // invalidation logic explicitly flag elements for which it ellided styling. - let parent_data = parent.borrow_data().unwrap(); - let candidate_parent_data = candidate_parent.borrow_data().unwrap(); - if !parent_data.safe_for_cousin_sharing() || !candidate_parent_data.safe_for_cousin_sharing() { - return false; - } - - true -} - -/// Whether two elements have the same same style attribute (by pointer identity). -pub fn have_same_style_attribute<E>( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, -) -> bool -where - E: TElement, -{ - match (target.style_attribute(), candidate.style_attribute()) { - (None, None) => true, - (Some(_), None) | (None, Some(_)) => false, - (Some(a), Some(b)) => &*a as *const _ == &*b as *const _, - } -} - -/// Whether two elements have the same same presentational attributes. -pub fn have_same_presentational_hints<E>( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, -) -> bool -where - E: TElement, -{ - target.pres_hints() == candidate.pres_hints() -} - -/// Whether a given element has the same class attribute as a given candidate. -/// -/// We don't try to share style across elements with different class attributes. -pub fn have_same_class<E>( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, -) -> bool -where - E: TElement, -{ - target.class_list() == candidate.class_list() -} - -/// Whether a given element has the same part attribute as a given candidate. -/// -/// We don't try to share style across elements with different part attributes. -pub fn have_same_parts<E>( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, -) -> bool -where - E: TElement, -{ - target.part_list() == candidate.part_list() -} - -/// Whether a given element and a candidate match the same set of "revalidation" -/// selectors. -/// -/// Revalidation selectors are those that depend on the DOM structure, like -/// :first-child, etc, or on attributes that we don't check off-hand (pretty -/// much every attribute selector except `id` and `class`. -#[inline] -pub fn revalidate<E>( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, - shared_context: &SharedStyleContext, - bloom: &StyleBloom<E>, - nth_index_cache: &mut NthIndexCache, -) -> bool -where - E: TElement, -{ - let stylist = &shared_context.stylist; - - let for_element = target.revalidation_match_results(stylist, bloom, nth_index_cache); - - let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache); - - // This assert "ensures", to some extent, that the two candidates have - // matched the same rulehash buckets, and as such, that the bits we're - // comparing represent the same set of selectors. - debug_assert_eq!(for_element.len(), for_candidate.len()); - - for_element == for_candidate -} - -/// Checks whether we might have rules for either of the two ids. -#[inline] -pub fn may_match_different_id_rules<E>( - shared_context: &SharedStyleContext, - element: E, - candidate: E, -) -> bool -where - E: TElement, -{ - let element_id = element.id(); - let candidate_id = candidate.id(); - - if element_id == candidate_id { - return false; - } - - let stylist = &shared_context.stylist; - - let may_have_rules_for_element = match element_id { - Some(id) => stylist.may_have_rules_for_id(id, element), - None => false, - }; - - if may_have_rules_for_element { - return true; - } - - match candidate_id { - Some(id) => stylist.may_have_rules_for_id(id, candidate), - None => false, - } -} diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs deleted file mode 100644 index 9e5b9603d77..00000000000 --- a/components/style/sharing/mod.rs +++ /dev/null @@ -1,910 +0,0 @@ -/* 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/. */ - -//! Code related to the style sharing cache, an optimization that allows similar -//! nodes to share style without having to run selector matching twice. -//! -//! The basic setup is as follows. We have an LRU cache of style sharing -//! candidates. When we try to style a target element, we first check whether -//! we can quickly determine that styles match something in this cache, and if -//! so we just use the cached style information. This check is done with a -//! StyleBloom filter set up for the target element, which may not be a correct -//! state for the cached candidate element if they're cousins instead of -//! siblings. -//! -//! The complicated part is determining that styles match. This is subject to -//! the following constraints: -//! -//! 1) The target and candidate must be inheriting the same styles. -//! 2) The target and candidate must have exactly the same rules matching them. -//! 3) The target and candidate must have exactly the same non-selector-based -//! style information (inline styles, presentation hints). -//! 4) The target and candidate must have exactly the same rules matching their -//! pseudo-elements, because an element's style data points to the style -//! data for its pseudo-elements. -//! -//! These constraints are satisfied in the following ways: -//! -//! * We check that the parents of the target and the candidate have the same -//! computed style. This addresses constraint 1. -//! -//! * We check that the target and candidate have the same inline style and -//! presentation hint declarations. This addresses constraint 3. -//! -//! * We ensure that a target matches a candidate only if they have the same -//! matching result for all selectors that target either elements or the -//! originating elements of pseudo-elements. This addresses constraint 4 -//! (because it prevents a target that has pseudo-element styles from matching -//! a candidate that has different pseudo-element styles) as well as -//! constraint 2. -//! -//! The actual checks that ensure that elements match the same rules are -//! conceptually split up into two pieces. First, we do various checks on -//! elements that make sure that the set of possible rules in all selector maps -//! in the stylist (for normal styling and for pseudo-elements) that might match -//! the two elements is the same. For example, we enforce that the target and -//! candidate must have the same localname and namespace. Second, we have a -//! selector map of "revalidation selectors" that the stylist maintains that we -//! actually match against the target and candidate and then check whether the -//! two sets of results were the same. Due to the up-front selector map checks, -//! we know that the target and candidate will be matched against the same exact -//! set of revalidation selectors, so the match result arrays can be compared -//! directly. -//! -//! It's very important that a selector be added to the set of revalidation -//! selectors any time there are two elements that could pass all the up-front -//! checks but match differently against some ComplexSelector in the selector. -//! If that happens, then they can have descendants that might themselves pass -//! the up-front checks but would have different matching results for the -//! selector in question. In this case, "descendants" includes pseudo-elements, -//! so there is a single selector map of revalidation selectors that includes -//! both selectors targeting elements and selectors targeting pseudo-element -//! originating elements. We ensure that the pseudo-element parts of all these -//! selectors are effectively stripped off, so that matching them all against -//! elements makes sense. - -use crate::applicable_declarations::ApplicableDeclarationBlock; -use crate::bloom::StyleBloom; -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{SharedStyleContext, StyleContext}; -use crate::dom::{SendElement, TElement}; -use crate::properties::ComputedValues; -use crate::rule_tree::StrongRuleNode; -use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles}; -use crate::stylist::Stylist; -use crate::values::AtomIdent; -use atomic_refcell::{AtomicRefCell, AtomicRefMut}; -use owning_ref::OwningHandle; -use selectors::matching::{NeedsSelectorFlags, VisitedHandlingMode}; -use selectors::NthIndexCache; -use servo_arc::Arc; -use smallbitvec::SmallBitVec; -use smallvec::SmallVec; -use std::marker::PhantomData; -use std::mem::{self, ManuallyDrop}; -use std::ops::Deref; -use std::ptr::NonNull; -use uluru::LRUCache; - -mod checks; - -/// The amount of nodes that the style sharing candidate cache should hold at -/// most. -/// -/// The cache size was chosen by measuring style sharing and resulting -/// performance on a few pages; sizes up to about 32 were giving good sharing -/// improvements (e.g. 3x fewer styles having to be resolved than at size 8) and -/// slight performance improvements. Sizes larger than 32 haven't really been -/// tested. -pub const SHARING_CACHE_SIZE: usize = 32; - -/// Opaque pointer type to compare ComputedValues identities. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct OpaqueComputedValues(NonNull<()>); - -unsafe impl Send for OpaqueComputedValues {} -unsafe impl Sync for OpaqueComputedValues {} - -impl OpaqueComputedValues { - fn from(cv: &ComputedValues) -> Self { - let p = - unsafe { NonNull::new_unchecked(cv as *const ComputedValues as *const () as *mut ()) }; - OpaqueComputedValues(p) - } - - fn eq(&self, cv: &ComputedValues) -> bool { - Self::from(cv) == *self - } -} - -/// Some data we want to avoid recomputing all the time while trying to share -/// style. -#[derive(Debug, Default)] -pub struct ValidationData { - /// The class list of this element. - /// - /// TODO(emilio): Maybe check whether rules for these classes apply to the - /// element? - class_list: Option<SmallVec<[AtomIdent; 5]>>, - - /// The part list of this element. - /// - /// TODO(emilio): Maybe check whether rules with these part names apply to - /// the element? - part_list: Option<SmallVec<[AtomIdent; 5]>>, - - /// The list of presentational attributes of the element. - pres_hints: Option<SmallVec<[ApplicableDeclarationBlock; 5]>>, - - /// The pointer identity of the parent ComputedValues. - parent_style_identity: Option<OpaqueComputedValues>, - - /// The cached result of matching this entry against the revalidation - /// selectors. - revalidation_match_results: Option<SmallBitVec>, -} - -impl ValidationData { - /// Move the cached data to a new instance, and return it. - pub fn take(&mut self) -> Self { - mem::replace(self, Self::default()) - } - - /// Get or compute the list of presentational attributes associated with - /// this element. - pub fn pres_hints<E>(&mut self, element: E) -> &[ApplicableDeclarationBlock] - where - E: TElement, - { - self.pres_hints.get_or_insert_with(|| { - let mut pres_hints = SmallVec::new(); - element.synthesize_presentational_hints_for_legacy_attributes( - VisitedHandlingMode::AllLinksUnvisited, - &mut pres_hints, - ); - pres_hints - }) - } - - /// Get or compute the part-list associated with this element. - pub fn part_list<E>(&mut self, element: E) -> &[AtomIdent] - where - E: TElement, - { - if !element.has_part_attr() { - return &[]; - } - self.part_list.get_or_insert_with(|| { - let mut list = SmallVec::<[_; 5]>::new(); - element.each_part(|p| list.push(p.clone())); - // See below for the reasoning. - if !list.spilled() { - list.sort_unstable_by_key(|a| a.get_hash()); - } - list - }) - } - - /// Get or compute the class-list associated with this element. - pub fn class_list<E>(&mut self, element: E) -> &[AtomIdent] - where - E: TElement, - { - self.class_list.get_or_insert_with(|| { - let mut list = SmallVec::<[_; 5]>::new(); - element.each_class(|c| list.push(c.clone())); - // Assuming there are a reasonable number of classes (we use the - // inline capacity as "reasonable number"), sort them to so that - // we don't mistakenly reject sharing candidates when one element - // has "foo bar" and the other has "bar foo". - if !list.spilled() { - list.sort_unstable_by_key(|a| a.get_hash()); - } - list - }) - } - - /// Get or compute the parent style identity. - pub fn parent_style_identity<E>(&mut self, el: E) -> OpaqueComputedValues - where - E: TElement, - { - self.parent_style_identity - .get_or_insert_with(|| { - let parent = el.inheritance_parent().unwrap(); - let values = - OpaqueComputedValues::from(parent.borrow_data().unwrap().styles.primary()); - values - }) - .clone() - } - - /// Computes the revalidation results if needed, and returns it. - /// Inline so we know at compile time what bloom_known_valid is. - #[inline] - fn revalidation_match_results<E>( - &mut self, - element: E, - stylist: &Stylist, - bloom: &StyleBloom<E>, - nth_index_cache: &mut NthIndexCache, - bloom_known_valid: bool, - needs_selector_flags: NeedsSelectorFlags, - ) -> &SmallBitVec - where - E: TElement, - { - self.revalidation_match_results.get_or_insert_with(|| { - // The bloom filter may already be set up for our element. - // If it is, use it. If not, we must be in a candidate - // (i.e. something in the cache), and the element is one - // of our cousins, not a sibling. In that case, we'll - // just do revalidation selector matching without a bloom - // filter, to avoid thrashing the filter. - let bloom_to_use = if bloom_known_valid { - debug_assert_eq!(bloom.current_parent(), element.traversal_parent()); - Some(bloom.filter()) - } else { - if bloom.current_parent() == element.traversal_parent() { - Some(bloom.filter()) - } else { - None - } - }; - stylist.match_revalidation_selectors( - element, - bloom_to_use, - nth_index_cache, - needs_selector_flags, - ) - }) - } -} - -/// Information regarding a style sharing candidate, that is, an entry in the -/// style sharing cache. -/// -/// Note that this information is stored in TLS and cleared after the traversal, -/// and once here, the style information of the element is immutable, so it's -/// safe to access. -/// -/// Important: If you change the members/layout here, You need to do the same for -/// FakeCandidate below. -#[derive(Debug)] -pub struct StyleSharingCandidate<E: TElement> { - /// The element. - element: E, - validation_data: ValidationData, - considered_relative_selector: bool, -} - -struct FakeCandidate { - _element: usize, - _validation_data: ValidationData, - _considered_relative_selector: bool, -} - -impl<E: TElement> Deref for StyleSharingCandidate<E> { - type Target = E; - - fn deref(&self) -> &Self::Target { - &self.element - } -} - -impl<E: TElement> StyleSharingCandidate<E> { - /// Get the classlist of this candidate. - fn class_list(&mut self) -> &[AtomIdent] { - self.validation_data.class_list(self.element) - } - - /// Get the part list of this candidate. - fn part_list(&mut self) -> &[AtomIdent] { - self.validation_data.part_list(self.element) - } - - /// Get the pres hints of this candidate. - fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] { - self.validation_data.pres_hints(self.element) - } - - /// Get the parent style identity. - fn parent_style_identity(&mut self) -> OpaqueComputedValues { - self.validation_data.parent_style_identity(self.element) - } - - /// Compute the bit vector of revalidation selector match results - /// for this candidate. - fn revalidation_match_results( - &mut self, - stylist: &Stylist, - bloom: &StyleBloom<E>, - nth_index_cache: &mut NthIndexCache, - ) -> &SmallBitVec { - self.validation_data.revalidation_match_results( - self.element, - stylist, - bloom, - nth_index_cache, - /* bloom_known_valid = */ false, - // The candidate must already have the right bits already, if - // needed. - NeedsSelectorFlags::No, - ) - } -} - -impl<E: TElement> PartialEq<StyleSharingCandidate<E>> for StyleSharingCandidate<E> { - fn eq(&self, other: &Self) -> bool { - self.element == other.element - } -} - -/// An element we want to test against the style sharing cache. -pub struct StyleSharingTarget<E: TElement> { - element: E, - validation_data: ValidationData, -} - -impl<E: TElement> Deref for StyleSharingTarget<E> { - type Target = E; - - fn deref(&self) -> &Self::Target { - &self.element - } -} - -impl<E: TElement> StyleSharingTarget<E> { - /// Trivially construct a new StyleSharingTarget to test against the cache. - pub fn new(element: E) -> Self { - Self { - element: element, - validation_data: ValidationData::default(), - } - } - - fn class_list(&mut self) -> &[AtomIdent] { - self.validation_data.class_list(self.element) - } - - fn part_list(&mut self) -> &[AtomIdent] { - self.validation_data.part_list(self.element) - } - - /// Get the pres hints of this candidate. - fn pres_hints(&mut self) -> &[ApplicableDeclarationBlock] { - self.validation_data.pres_hints(self.element) - } - - /// Get the parent style identity. - fn parent_style_identity(&mut self) -> OpaqueComputedValues { - self.validation_data.parent_style_identity(self.element) - } - - fn revalidation_match_results( - &mut self, - stylist: &Stylist, - bloom: &StyleBloom<E>, - nth_index_cache: &mut NthIndexCache, - ) -> &SmallBitVec { - // It's important to set the selector flags. Otherwise, if we succeed in - // sharing the style, we may not set the slow selector flags for the - // right elements (which may not necessarily be |element|), causing - // missed restyles after future DOM mutations. - // - // Gecko's test_bug534804.html exercises this. A minimal testcase is: - // <style> #e:empty + span { ... } </style> - // <span id="e"> - // <span></span> - // </span> - // <span></span> - // - // The style sharing cache will get a hit for the second span. When the - // child span is subsequently removed from the DOM, missing selector - // flags would cause us to miss the restyle on the second span. - self.validation_data.revalidation_match_results( - self.element, - stylist, - bloom, - nth_index_cache, - /* bloom_known_valid = */ true, - NeedsSelectorFlags::Yes, - ) - } - - /// Attempts to share a style with another node. - pub fn share_style_if_possible( - &mut self, - context: &mut StyleContext<E>, - ) -> Option<ResolvedElementStyles> { - let cache = &mut context.thread_local.sharing_cache; - let shared_context = &context.shared; - let bloom_filter = &context.thread_local.bloom_filter; - let nth_index_cache = &mut context.thread_local.nth_index_cache; - - if cache.dom_depth != bloom_filter.matching_depth() { - debug!( - "Can't share style, because DOM depth changed from {:?} to {:?}, element: {:?}", - cache.dom_depth, - bloom_filter.matching_depth(), - self.element - ); - return None; - } - debug_assert_eq!( - bloom_filter.current_parent(), - self.element.traversal_parent() - ); - - cache.share_style_if_possible(shared_context, bloom_filter, nth_index_cache, self) - } - - /// Gets the validation data used to match against this target, if any. - pub fn take_validation_data(&mut self) -> ValidationData { - self.validation_data.take() - } -} - -struct SharingCacheBase<Candidate> { - entries: LRUCache<Candidate, SHARING_CACHE_SIZE>, -} - -impl<Candidate> Default for SharingCacheBase<Candidate> { - fn default() -> Self { - Self { - entries: LRUCache::default(), - } - } -} - -impl<Candidate> SharingCacheBase<Candidate> { - fn clear(&mut self) { - self.entries.clear(); - } - - fn is_empty(&self) -> bool { - self.entries.len() == 0 - } -} - -impl<E: TElement> SharingCache<E> { - fn insert( - &mut self, - element: E, - considered_relative_selector: bool, - validation_data_holder: Option<&mut StyleSharingTarget<E>>, - ) { - let validation_data = match validation_data_holder { - Some(v) => v.take_validation_data(), - None => ValidationData::default(), - }; - self.entries.insert(StyleSharingCandidate { - element, - considered_relative_selector, - validation_data, - }); - } -} - -/// Style sharing caches are are large allocations, so we store them in thread-local -/// storage such that they can be reused across style traversals. Ideally, we'd just -/// stack-allocate these buffers with uninitialized memory, but right now rustc can't -/// avoid memmoving the entire cache during setup, which gets very expensive. See -/// issues like [1] and [2]. -/// -/// Given that the cache stores entries of type TElement, we transmute to usize -/// before storing in TLS. This is safe as long as we make sure to empty the cache -/// before we let it go. -/// -/// [1] https://github.com/rust-lang/rust/issues/42763 -/// [2] https://github.com/rust-lang/rust/issues/13707 -type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>; -type TypelessSharingCache = SharingCacheBase<FakeCandidate>; -type StoredSharingCache = Arc<AtomicRefCell<TypelessSharingCache>>; - -thread_local! { - // See the comment on bloom.rs about why do we leak this. - static SHARING_CACHE_KEY: ManuallyDrop<StoredSharingCache> = - ManuallyDrop::new(Arc::new_leaked(Default::default())); -} - -/// An LRU cache of the last few nodes seen, so that we can aggressively try to -/// reuse their styles. -/// -/// Note that this cache is flushed every time we steal work from the queue, so -/// storing nodes here temporarily is safe. -pub struct StyleSharingCache<E: TElement> { - /// The LRU cache, with the type cast away to allow persisting the allocation. - cache_typeless: OwningHandle<StoredSharingCache, AtomicRefMut<'static, TypelessSharingCache>>, - /// Bind this structure to the lifetime of E, since that's what we effectively store. - marker: PhantomData<SendElement<E>>, - /// The DOM depth we're currently at. This is used as an optimization to - /// clear the cache when we change depths, since we know at that point - /// nothing in the cache will match. - dom_depth: usize, -} - -impl<E: TElement> Drop for StyleSharingCache<E> { - fn drop(&mut self) { - self.clear(); - } -} - -impl<E: TElement> StyleSharingCache<E> { - #[allow(dead_code)] - fn cache(&self) -> &SharingCache<E> { - let base: &TypelessSharingCache = &*self.cache_typeless; - unsafe { mem::transmute(base) } - } - - fn cache_mut(&mut self) -> &mut SharingCache<E> { - let base: &mut TypelessSharingCache = &mut *self.cache_typeless; - unsafe { mem::transmute(base) } - } - - /// Create a new style sharing candidate cache. - - // Forced out of line to limit stack frame sizes after extra inlining from - // https://github.com/rust-lang/rust/pull/43931 - // - // See https://github.com/servo/servo/pull/18420#issuecomment-328769322 - #[inline(never)] - pub fn new() -> Self { - assert_eq!( - mem::size_of::<SharingCache<E>>(), - mem::size_of::<TypelessSharingCache>() - ); - assert_eq!( - mem::align_of::<SharingCache<E>>(), - mem::align_of::<TypelessSharingCache>() - ); - let cache_arc = SHARING_CACHE_KEY.with(|c| Arc::clone(&*c)); - let cache = - OwningHandle::new_with_fn(cache_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut()); - debug_assert!(cache.is_empty()); - - StyleSharingCache { - cache_typeless: cache, - marker: PhantomData, - dom_depth: 0, - } - } - - /// Tries to insert an element in the style sharing cache. - /// - /// Fails if we know it should never be in the cache. - /// - /// NB: We pass a source for the validation data, rather than the data itself, - /// to avoid memmoving at each function call. See rust issue #42763. - pub fn insert_if_possible( - &mut self, - element: &E, - style: &PrimaryStyle, - validation_data_holder: Option<&mut StyleSharingTarget<E>>, - dom_depth: usize, - shared_context: &SharedStyleContext, - ) { - let parent = match element.traversal_parent() { - Some(element) => element, - None => { - debug!("Failing to insert to the cache: no parent element"); - return; - }, - }; - - if !element.matches_user_and_content_rules() { - debug!("Failing to insert into the cache: no tree rules:"); - return; - } - - // We can't share style across shadow hosts right now, because they may - // match different :host rules. - // - // TODO(emilio): We could share across the ones that don't have :host - // rules or have the same. - if element.shadow_root().is_some() { - debug!("Failing to insert into the cache: Shadow Host"); - return; - } - - // If the element has running animations, we can't share style. - // - // This is distinct from the specifies_{animations,transitions} check below, - // because: - // * Animations can be triggered directly via the Web Animations API. - // * Our computed style can still be affected by animations after we no - // longer match any animation rules, since removing animations involves - // a sequential task and an additional traversal. - if element.has_animations(shared_context) { - debug!("Failing to insert to the cache: running animations"); - return; - } - - // If this element was considered for matching a relative selector, we can't - // share style, as that requires evaluating the relative selector in the - // first place. A couple notes: - // - This means that a document that contains a standalone `:has()` - // rule would basically turn style sharing off. - // - Since the flag is set on the element, we may be overly pessimistic: - // For example, given `<div class="foo"><div class="bar"></div></div>`, - // if we run into a `.foo:has(.bar) .baz` selector, we refuse any selector - // matching `.foo`, even if `:has()` may not even be used. Ideally we'd - // have something like `RelativeSelectorConsidered::RightMost`, but the - // element flag is required for invalidation, and this reduces more tracking. - if style - .style - .0 - .flags - .intersects(ComputedValueFlags::ANCHORS_RELATIVE_SELECTOR) { - debug!("Failing to insert to the cache: may anchor relative selector"); - return; - } - - // In addition to the above running animations check, we also need to - // check CSS animation and transition styles since it's possible that - // we are about to create CSS animations/transitions. - // - // These are things we don't check in the candidate match because they - // are either uncommon or expensive. - let ui_style = style.style().get_ui(); - if ui_style.specifies_transitions() { - debug!("Failing to insert to the cache: transitions"); - return; - } - - if ui_style.specifies_animations() { - debug!("Failing to insert to the cache: animations"); - return; - } - - debug!( - "Inserting into cache: {:?} with parent {:?}", - element, parent - ); - - if self.dom_depth != dom_depth { - debug!( - "Clearing cache because depth changed from {:?} to {:?}, element: {:?}", - self.dom_depth, dom_depth, element - ); - self.clear(); - self.dom_depth = dom_depth; - } - self.cache_mut().insert( - *element, - style - .style - .0 - .flags - .intersects(ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR), - validation_data_holder, - ); - } - - /// Clear the style sharing candidate cache. - pub fn clear(&mut self) { - self.cache_mut().clear(); - } - - /// Attempts to share a style with another node. - fn share_style_if_possible( - &mut self, - shared_context: &SharedStyleContext, - bloom_filter: &StyleBloom<E>, - nth_index_cache: &mut NthIndexCache, - target: &mut StyleSharingTarget<E>, - ) -> Option<ResolvedElementStyles> { - if shared_context.options.disable_style_sharing_cache { - debug!( - "{:?} Cannot share style: style sharing cache disabled", - target.element - ); - return None; - } - - if target.inheritance_parent().is_none() { - debug!( - "{:?} Cannot share style: element has no parent", - target.element - ); - return None; - } - - if !target.matches_user_and_content_rules() { - debug!("{:?} Cannot share style: content rules", target.element); - return None; - } - - self.cache_mut().entries.lookup(|candidate| { - Self::test_candidate( - target, - candidate, - &shared_context, - bloom_filter, - nth_index_cache, - shared_context, - ) - }) - } - - fn test_candidate( - target: &mut StyleSharingTarget<E>, - candidate: &mut StyleSharingCandidate<E>, - shared: &SharedStyleContext, - bloom: &StyleBloom<E>, - nth_index_cache: &mut NthIndexCache, - shared_context: &SharedStyleContext, - ) -> Option<ResolvedElementStyles> { - debug_assert!(target.matches_user_and_content_rules()); - - // Check that we have the same parent, or at least that the parents - // share styles and permit sharing across their children. The latter - // check allows us to share style between cousins if the parents - // shared style. - if !checks::parents_allow_sharing(target, candidate) { - trace!("Miss: Parent"); - return None; - } - - if target.local_name() != candidate.element.local_name() { - trace!("Miss: Local Name"); - return None; - } - - if target.namespace() != candidate.element.namespace() { - trace!("Miss: Namespace"); - return None; - } - - // We do not ignore visited state here, because Gecko needs to store - // extra bits on visited styles, so these contexts cannot be shared. - if target.element.state() != candidate.state() { - trace!("Miss: User and Author State"); - return None; - } - - if target.is_link() != candidate.element.is_link() { - trace!("Miss: Link"); - return None; - } - - // If two elements belong to different shadow trees, different rules may - // apply to them, from the respective trees. - if target.element.containing_shadow() != candidate.element.containing_shadow() { - trace!("Miss: Different containing shadow roots"); - return None; - } - - // If the elements are not assigned to the same slot they could match - // different ::slotted() rules in the slot scope. - // - // If two elements are assigned to different slots, even within the same - // shadow root, they could match different rules, due to the slot being - // assigned to yet another slot in another shadow root. - if target.element.assigned_slot() != candidate.element.assigned_slot() { - // TODO(emilio): We could have a look at whether the shadow roots - // actually have slotted rules and such. - trace!("Miss: Different assigned slots"); - return None; - } - - if target.element.shadow_root().is_some() { - trace!("Miss: Shadow host"); - return None; - } - - if target.element.has_animations(shared_context) { - trace!("Miss: Has Animations"); - return None; - } - - if target.matches_user_and_content_rules() != - candidate.element.matches_user_and_content_rules() - { - trace!("Miss: User and Author Rules"); - return None; - } - - // It's possible that there are no styles for either id. - if checks::may_match_different_id_rules(shared, target.element, candidate.element) { - trace!("Miss: ID Attr"); - return None; - } - - if !checks::have_same_style_attribute(target, candidate) { - trace!("Miss: Style Attr"); - return None; - } - - if !checks::have_same_class(target, candidate) { - trace!("Miss: Class"); - return None; - } - - if !checks::have_same_presentational_hints(target, candidate) { - trace!("Miss: Pres Hints"); - return None; - } - - if !checks::have_same_parts(target, candidate) { - trace!("Miss: Shadow parts"); - return None; - } - - if !checks::revalidate(target, candidate, shared, bloom, nth_index_cache) { - trace!("Miss: Revalidation"); - return None; - } - - debug!( - "Sharing allowed between {:?} and {:?}", - target.element, candidate.element - ); - Some(candidate.element.borrow_data().unwrap().share_styles()) - } - - /// Attempts to find an element in the cache with the given primary rule - /// node and parent. - /// - /// FIXME(emilio): re-measure this optimization, and remove if it's not very - /// useful... It's probably not worth the complexity / obscure bugs. - pub fn lookup_by_rules( - &mut self, - shared_context: &SharedStyleContext, - inherited: &ComputedValues, - rules: &StrongRuleNode, - visited_rules: Option<&StrongRuleNode>, - target: E, - ) -> Option<PrimaryStyle> { - if shared_context.options.disable_style_sharing_cache { - return None; - } - - self.cache_mut().entries.lookup(|candidate| { - debug_assert_ne!(candidate.element, target); - if !candidate.parent_style_identity().eq(inherited) { - return None; - } - let data = candidate.element.borrow_data().unwrap(); - let style = data.styles.primary(); - if style.rules.as_ref() != Some(&rules) { - return None; - } - if style.visited_rules() != visited_rules { - return None; - } - // NOTE(emilio): We only need to check name / namespace because we - // do name-dependent style adjustments, like the display: contents - // to display: none adjustment. - if target.namespace() != candidate.element.namespace() || - target.local_name() != candidate.element.local_name() - { - return None; - } - // When using container units, inherited style + rules matched aren't enough to - // determine whether the style is the same. We could actually do a full container - // lookup but for now we just check that our actual traversal parent matches. - if data - .styles - .primary() - .flags - .intersects(ComputedValueFlags::USES_CONTAINER_UNITS) && - candidate.element.traversal_parent() != target.traversal_parent() - { - return None; - } - // Rule nodes and styles are computed independent of the element's actual visitedness, - // but at the end of the cascade (in `adjust_for_visited`) we do store the - // RELEVANT_LINK_VISITED flag, so we can't share by rule node between visited and - // unvisited styles. We don't check for visitedness and just refuse to share for links - // entirely, so that visitedness doesn't affect timing. - debug_assert_eq!(target.is_link(), candidate.element.is_link(), "Linkness mismatch"); - if target.is_link() { - return None; - } - - Some(data.share_primary_style()) - }) - } -} diff --git a/components/style/str.rs b/components/style/str.rs deleted file mode 100644 index c610b5241a7..00000000000 --- a/components/style/str.rs +++ /dev/null @@ -1,189 +0,0 @@ -/* 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/. */ - -//! String utils for attributes and similar stuff. - -#![deny(missing_docs)] - -use num_traits::ToPrimitive; -use std::borrow::Cow; -use std::convert::AsRef; -use std::iter::{Filter, Peekable}; -use std::str::Split; - -/// A static slice of characters. -pub type StaticCharVec = &'static [char]; - -/// A static slice of `str`s. -pub type StaticStringVec = &'static [&'static str]; - -/// A "space character" according to: -/// -/// <https://html.spec.whatwg.org/multipage/#space-character> -pub static HTML_SPACE_CHARACTERS: StaticCharVec = - &['\u{0020}', '\u{0009}', '\u{000a}', '\u{000c}', '\u{000d}']; - -/// Whether a character is a HTML whitespace character. -#[inline] -pub fn char_is_whitespace(c: char) -> bool { - HTML_SPACE_CHARACTERS.contains(&c) -} - -/// Whether all the string is HTML whitespace. -#[inline] -pub fn is_whitespace(s: &str) -> bool { - s.chars().all(char_is_whitespace) -} - -#[inline] -fn not_empty(&split: &&str) -> bool { - !split.is_empty() -} - -/// Split a string on HTML whitespace. -#[inline] -pub fn split_html_space_chars<'a>( - s: &'a str, -) -> Filter<Split<'a, StaticCharVec>, fn(&&str) -> bool> { - s.split(HTML_SPACE_CHARACTERS) - .filter(not_empty as fn(&&str) -> bool) -} - -/// Split a string on commas. -#[inline] -pub fn split_commas<'a>(s: &'a str) -> Filter<Split<'a, char>, fn(&&str) -> bool> { - s.split(',').filter(not_empty as fn(&&str) -> bool) -} - -/// Character is ascii digit -pub fn is_ascii_digit(c: &char) -> bool { - match *c { - '0'..='9' => true, - _ => false, - } -} - -fn is_decimal_point(c: char) -> bool { - c == '.' -} - -fn is_exponent_char(c: char) -> bool { - match c { - 'e' | 'E' => true, - _ => false, - } -} - -/// Read a set of ascii digits and read them into a number. -pub fn read_numbers<I: Iterator<Item = char>>(mut iter: Peekable<I>) -> (Option<i64>, usize) { - match iter.peek() { - Some(c) if is_ascii_digit(c) => (), - _ => return (None, 0), - } - - iter.take_while(is_ascii_digit) - .map(|d| d as i64 - '0' as i64) - .fold((Some(0i64), 0), |accumulator, d| { - let digits = accumulator - .0 - .and_then(|accumulator| accumulator.checked_mul(10)) - .and_then(|accumulator| accumulator.checked_add(d)); - (digits, accumulator.1 + 1) - }) -} - -/// Read a decimal fraction. -pub fn read_fraction<I: Iterator<Item = char>>( - mut iter: Peekable<I>, - mut divisor: f64, - value: f64, -) -> (f64, usize) { - match iter.peek() { - Some(c) if is_decimal_point(*c) => (), - _ => return (value, 0), - } - iter.next(); - - iter.take_while(is_ascii_digit) - .map(|d| d as i64 - '0' as i64) - .fold((value, 1), |accumulator, d| { - divisor *= 10f64; - (accumulator.0 + d as f64 / divisor, accumulator.1 + 1) - }) -} - -/// Reads an exponent from an iterator over chars, for example `e100`. -pub fn read_exponent<I: Iterator<Item = char>>(mut iter: Peekable<I>) -> Option<i32> { - match iter.peek() { - Some(c) if is_exponent_char(*c) => (), - _ => return None, - } - iter.next(); - - match iter.peek() { - None => None, - Some(&'-') => { - iter.next(); - read_numbers(iter).0.map(|exp| -exp.to_i32().unwrap_or(0)) - }, - Some(&'+') => { - iter.next(); - read_numbers(iter).0.map(|exp| exp.to_i32().unwrap_or(0)) - }, - Some(_) => read_numbers(iter).0.map(|exp| exp.to_i32().unwrap_or(0)), - } -} - -/// Join a set of strings with a given delimiter `join`. -pub fn str_join<I, T>(strs: I, join: &str) -> String -where - I: IntoIterator<Item = T>, - T: AsRef<str>, -{ - strs.into_iter() - .enumerate() - .fold(String::new(), |mut acc, (i, s)| { - if i > 0 { - acc.push_str(join); - } - acc.push_str(s.as_ref()); - acc - }) -} - -/// Returns true if a given string has a given prefix with case-insensitive match. -pub fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool { - string.len() >= prefix.len() && - string.as_bytes()[0..prefix.len()].eq_ignore_ascii_case(prefix.as_bytes()) -} - -/// Returns an ascii lowercase version of a string, only allocating if needed. -pub fn string_as_ascii_lowercase<'a>(input: &'a str) -> Cow<'a, str> { - if input.bytes().any(|c| matches!(c, b'A'..=b'Z')) { - input.to_ascii_lowercase().into() - } else { - // Already ascii lowercase. - Cow::Borrowed(input) - } -} - -/// To avoid accidentally instantiating multiple monomorphizations of large -/// serialization routines, we define explicit concrete types and require -/// them in those routines. This avoids accidental mixing of String and -/// nsACString arguments in Gecko, which would cause code size to blow up. -#[cfg(feature = "gecko")] -pub type CssStringWriter = ::nsstring::nsACString; - -/// String type that coerces to CssStringWriter, used when serialization code -/// needs to allocate a temporary string. -#[cfg(feature = "gecko")] -pub type CssString = ::nsstring::nsCString; - -/// String. The comments for the Gecko types explain the need for this abstraction. -#[cfg(feature = "servo")] -pub type CssStringWriter = String; - -/// String. The comments for the Gecko types explain the need for this abstraction. -#[cfg(feature = "servo")] -pub type CssString = String; diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs deleted file mode 100644 index a353577ec44..00000000000 --- a/components/style/style_adjuster.rs +++ /dev/null @@ -1,1013 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A struct to encapsulate all the style fixups and flags propagations -//! a computed style needs in order for it to adhere to the CSS spec. - -use crate::computed_value_flags::ComputedValueFlags; -use crate::dom::TElement; -#[cfg(feature = "gecko")] -use crate::properties::longhands::contain::computed_value::T as Contain; -#[cfg(feature = "gecko")] -use crate::properties::longhands::container_type::computed_value::T as ContainerType; -#[cfg(feature = "gecko")] -use crate::properties::longhands::content_visibility::computed_value::T as ContentVisibility; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::longhands::float::computed_value::T as Float; -use crate::properties::longhands::position::computed_value::T as Position; -use crate::properties::{self, ComputedValues, StyleBuilder}; - -/// A struct that implements all the adjustment methods. -/// -/// NOTE(emilio): If new adjustments are introduced that depend on reset -/// properties of the parent, you may need tweaking the -/// `ChildCascadeRequirement` code in `matching.rs`. -/// -/// NOTE(emilio): Also, if new adjustments are introduced that break the -/// following invariant: -/// -/// Given same tag name, namespace, rules and parent style, two elements would -/// end up with exactly the same style. -/// -/// Then you need to adjust the lookup_by_rules conditions in the sharing cache. -pub struct StyleAdjuster<'a, 'b: 'a> { - style: &'a mut StyleBuilder<'b>, -} - -#[cfg(feature = "gecko")] -fn is_topmost_svg_svg_element<E>(e: E) -> bool -where - E: TElement, -{ - debug_assert!(e.is_svg_element()); - if e.local_name() != &*atom!("svg") { - return false; - } - - let parent = match e.traversal_parent() { - Some(n) => n, - None => return true, - }; - - if !parent.is_svg_element() { - return true; - } - - parent.local_name() == &*atom!("foreignObject") -} - -// https://drafts.csswg.org/css-display/#unbox -#[cfg(feature = "gecko")] -fn is_effective_display_none_for_display_contents<E>(element: E) -> bool -where - E: TElement, -{ - use crate::Atom; - - const SPECIAL_HTML_ELEMENTS: [Atom; 16] = [ - atom!("br"), - atom!("wbr"), - atom!("meter"), - atom!("progress"), - atom!("canvas"), - atom!("embed"), - atom!("object"), - atom!("audio"), - atom!("iframe"), - atom!("img"), - atom!("video"), - atom!("frame"), - atom!("frameset"), - atom!("input"), - atom!("textarea"), - atom!("select"), - ]; - - // https://drafts.csswg.org/css-display/#unbox-svg - // - // There's a note about "Unknown elements", but there's not a good way to - // know what that means, or to get that information from here, and no other - // UA implements this either. - const SPECIAL_SVG_ELEMENTS: [Atom; 6] = [ - atom!("svg"), - atom!("a"), - atom!("g"), - atom!("use"), - atom!("tspan"), - atom!("textPath"), - ]; - - // https://drafts.csswg.org/css-display/#unbox-html - if element.is_html_element() { - let local_name = element.local_name(); - return SPECIAL_HTML_ELEMENTS - .iter() - .any(|name| &**name == local_name); - } - - // https://drafts.csswg.org/css-display/#unbox-svg - if element.is_svg_element() { - if is_topmost_svg_svg_element(element) { - return true; - } - let local_name = element.local_name(); - return !SPECIAL_SVG_ELEMENTS - .iter() - .any(|name| &**name == local_name); - } - - // https://drafts.csswg.org/css-display/#unbox-mathml - // - // We always treat XUL as display: none. We don't use display: - // contents in XUL anyway, so should be fine to be consistent with - // MathML unless there's a use case for it. - if element.is_mathml_element() || element.is_xul_element() { - return true; - } - - false -} - -impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { - /// Trivially constructs a new StyleAdjuster. - #[inline] - pub fn new(style: &'a mut StyleBuilder<'b>) -> Self { - StyleAdjuster { style } - } - - /// <https://fullscreen.spec.whatwg.org/#new-stacking-layer> - /// - /// Any position value other than 'absolute' and 'fixed' are - /// computed to 'absolute' if the element is in a top layer. - /// - fn adjust_for_top_layer(&mut self) { - if !self.style.in_top_layer() { - return; - } - if !self.style.is_absolutely_positioned() { - self.style.mutate_box().set_position(Position::Absolute); - } - if self.style.get_box().clone_display().is_contents() { - self.style.mutate_box().set_display(Display::Block); - } - } - - /// -webkit-box with line-clamp and vertical orientation gets turned into - /// flow-root at computed-value time. - /// - /// This makes the element not be a flex container, with all that it - /// implies, but it should be safe. It matches blink, see - /// https://bugzilla.mozilla.org/show_bug.cgi?id=1786147#c10 - #[cfg(feature = "gecko")] - fn adjust_for_webkit_line_clamp(&mut self) { - use crate::properties::longhands::_moz_box_orient::computed_value::T as BoxOrient; - use crate::values::specified::box_::{DisplayInside, DisplayOutside}; - let box_style = self.style.get_box(); - if box_style.clone__webkit_line_clamp().is_none() { - return; - } - let disp = box_style.clone_display(); - if disp.inside() != DisplayInside::WebkitBox { - return; - } - if self.style.get_xul().clone__moz_box_orient() != BoxOrient::Vertical { - return; - } - let new_display = if disp.outside() == DisplayOutside::Block { - Display::FlowRoot - } else { - debug_assert_eq!(disp.outside(), DisplayOutside::Inline); - Display::InlineBlock - }; - self.style - .mutate_box() - .set_adjusted_display(new_display, false); - } - - /// CSS 2.1 section 9.7: - /// - /// If 'position' has the value 'absolute' or 'fixed', [...] the computed - /// value of 'float' is 'none'. - /// - fn adjust_for_position(&mut self) { - if self.style.is_absolutely_positioned() && self.style.is_floating() { - self.style.mutate_box().set_float(Float::None); - } - } - - /// Whether we should skip any item-based display property blockification on - /// this element. - fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool - where - E: TElement, - { - if let Some(pseudo) = self.style.pseudo { - return pseudo.skip_item_display_fixup(); - } - - element.map_or(false, |e| e.skip_item_display_fixup()) - } - - /// Apply the blockification rules based on the table in CSS 2.2 section 9.7. - /// <https://drafts.csswg.org/css2/visuren.html#dis-pos-flo> - /// A ::marker pseudo-element with 'list-style-position:outside' needs to - /// have its 'display' blockified, unless the ::marker is for an inline - /// list-item (for which 'list-style-position:outside' behaves as 'inside'). - /// https://drafts.csswg.org/css-lists-3/#list-style-position-property - fn blockify_if_necessary<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>) - where - E: TElement, - { - let mut blockify = false; - macro_rules! blockify_if { - ($if_what:expr) => { - if !blockify { - blockify = $if_what; - } - }; - } - - blockify_if!(self.style.is_root_element); - if !self.skip_item_display_fixup(element) { - let parent_display = layout_parent_style.get_box().clone_display(); - blockify_if!(parent_display.is_item_container()); - } - - let is_item_or_root = blockify; - - blockify_if!(self.style.is_floating()); - blockify_if!(self.style.is_absolutely_positioned()); - - if !blockify { - return; - } - - let display = self.style.get_box().clone_display(); - let blockified_display = display.equivalent_block_display(self.style.is_root_element); - if display != blockified_display { - self.style - .mutate_box() - .set_adjusted_display(blockified_display, is_item_or_root); - } - } - - /// Compute a few common flags for both text and element's style. - fn set_bits(&mut self) { - let box_style = self.style.get_box(); - let display = box_style.clone_display(); - - if !display.is_contents() { - if !self - .style - .get_text() - .clone_text_decoration_line() - .is_empty() - { - self.style - .add_flags(ComputedValueFlags::HAS_TEXT_DECORATION_LINES); - } - - if self.style.get_effects().clone_opacity() == 0. { - self.style - .add_flags(ComputedValueFlags::IS_IN_OPACITY_ZERO_SUBTREE); - } - } - - if self.style.is_pseudo_element() { - self.style - .add_flags(ComputedValueFlags::IS_IN_PSEUDO_ELEMENT_SUBTREE); - } - - if self.style.is_root_element { - self.style - .add_flags(ComputedValueFlags::IS_ROOT_ELEMENT_STYLE); - } - - #[cfg(feature = "gecko")] - if box_style - .clone_contain() - .contains(SpecifiedValue::STYLE) - { - self.style - .add_flags(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_CONTAIN_STYLE); - } - - if box_style.clone_container_type().is_size_container_type() { - self.style - .add_flags(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE); - } - - if self.style.get_parent_column().is_multicol() { - self.style.add_flags(ComputedValueFlags::CAN_BE_FRAGMENTED); - } - } - - /// Adjust the style for text style. - /// - /// The adjustments here are a subset of the adjustments generally, because - /// text only inherits properties. - /// - /// Note that this, for Gecko, comes through Servo_ComputedValues_Inherit. - #[cfg(feature = "gecko")] - pub fn adjust_for_text(&mut self) { - debug_assert!(!self.style.is_root_element); - self.adjust_for_text_combine_upright(); - self.adjust_for_text_in_ruby(); - self.set_bits(); - } - - /// Change writing mode of the text frame for text-combine-upright. - /// - /// It is safe to look at our own style because we are looking at inherited - /// properties, and text is just plain inheritance. - /// - /// TODO(emilio): we should (Gecko too) revise these adjustments in presence - /// of display: contents. - /// - /// FIXME(emilio): How does this play with logical properties? Doesn't - /// mutating writing-mode change the potential physical sides chosen? - #[cfg(feature = "gecko")] - fn adjust_for_text_combine_upright(&mut self) { - use crate::computed_values::text_combine_upright::T as TextCombineUpright; - use crate::computed_values::writing_mode::T as WritingMode; - use crate::logical_geometry; - - let writing_mode = self.style.get_inherited_box().clone_writing_mode(); - let text_combine_upright = self.style.get_inherited_text().clone_text_combine_upright(); - - if matches!( - writing_mode, - WritingMode::VerticalRl | WritingMode::VerticalLr - ) && text_combine_upright == TextCombineUpright::All - { - self.style.add_flags(ComputedValueFlags::IS_TEXT_COMBINED); - self.style - .mutate_inherited_box() - .set_writing_mode(WritingMode::HorizontalTb); - self.style.writing_mode = - logical_geometry::WritingMode::new(self.style.get_inherited_box()); - } - } - - /// Unconditionally propagates the line break suppression flag to text, and - /// additionally it applies it if it is in any ruby box. - /// - /// This is necessary because its parent may not itself have the flag set - /// (e.g. ruby or ruby containers), thus we may not inherit the flag from - /// them. - #[cfg(feature = "gecko")] - fn adjust_for_text_in_ruby(&mut self) { - let parent_display = self.style.get_parent_box().clone_display(); - if parent_display.is_ruby_type() || - self.style - .get_parent_flags() - .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK) - { - self.style - .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK); - } - } - - /// <https://drafts.csswg.org/css-writing-modes-3/#block-flow:> - /// - /// If a box has a different writing-mode value than its containing - /// block: - /// - /// - If the box has a specified display of inline, its display - /// computes to inline-block. [CSS21] - /// - /// This matches the adjustment that Gecko does, not exactly following - /// the spec. See also: - /// - /// <https://lists.w3.org/Archives/Public/www-style/2017Mar/0045.html> - /// <https://github.com/servo/servo/issues/15754> - fn adjust_for_writing_mode(&mut self, layout_parent_style: &ComputedValues) { - let our_writing_mode = self.style.get_inherited_box().clone_writing_mode(); - let parent_writing_mode = layout_parent_style.get_inherited_box().clone_writing_mode(); - - if our_writing_mode != parent_writing_mode && - self.style.get_box().clone_display() == Display::Inline - { - // TODO(emilio): Figure out if we can just set the adjusted display - // on Gecko too and unify this code path. - if cfg!(feature = "servo") { - self.style - .mutate_box() - .set_adjusted_display(Display::InlineBlock, false); - } else { - self.style.mutate_box().set_display(Display::InlineBlock); - } - } - } - - /// This implements an out-of-date spec. The new spec moves the handling of - /// this to layout, which Gecko implements but Servo doesn't. - /// - /// See https://github.com/servo/servo/issues/15229 - #[cfg(feature = "servo")] - fn adjust_for_alignment(&mut self, layout_parent_style: &ComputedValues) { - use crate::computed_values::align_items::T as AlignItems; - use crate::computed_values::align_self::T as AlignSelf; - - if self.style.get_position().clone_align_self() == AlignSelf::Auto && - !self.style.is_absolutely_positioned() - { - let self_align = match layout_parent_style.get_position().clone_align_items() { - AlignItems::Stretch => AlignSelf::Stretch, - AlignItems::Baseline => AlignSelf::Baseline, - AlignItems::FlexStart => AlignSelf::FlexStart, - AlignItems::FlexEnd => AlignSelf::FlexEnd, - AlignItems::Center => AlignSelf::Center, - }; - self.style.mutate_position().set_align_self(self_align); - } - } - - /// The initial value of border-*-width may be changed at computed value - /// time. - /// - /// This is moved to properties.rs for convenience. - fn adjust_for_border_width(&mut self) { - properties::adjust_border_width(self.style); - } - - /// column-rule-style: none causes a computed column-rule-width of zero - /// at computed value time. - #[cfg(feature = "gecko")] - fn adjust_for_column_rule_width(&mut self) { - let column_style = self.style.get_column(); - if !column_style.clone_column_rule_style().none_or_hidden() { - return; - } - if !column_style.column_rule_has_nonzero_width() { - return; - } - self.style - .mutate_column() - .set_column_rule_width(crate::Zero::zero()); - } - - /// outline-style: none causes a computed outline-width of zero at computed - /// value time. - fn adjust_for_outline_width(&mut self) { - let outline = self.style.get_outline(); - if !outline.clone_outline_style().none_or_hidden() { - return; - } - if !outline.outline_has_nonzero_width() { - return; - } - self.style - .mutate_outline() - .set_outline_width(crate::Zero::zero()); - } - - /// CSS overflow-x and overflow-y require some fixup as well in some cases. - /// https://drafts.csswg.org/css-overflow-3/#overflow-properties - /// "Computed value: as specified, except with `visible`/`clip` computing to - /// `auto`/`hidden` (respectively) if one of `overflow-x` or `overflow-y` is - /// neither `visible` nor `clip`." - fn adjust_for_overflow(&mut self) { - let overflow_x = self.style.get_box().clone_overflow_x(); - let overflow_y = self.style.get_box().clone_overflow_y(); - if overflow_x == overflow_y { - return; // optimization for the common case - } - - if overflow_x.is_scrollable() != overflow_y.is_scrollable() { - let box_style = self.style.mutate_box(); - box_style.set_overflow_x(overflow_x.to_scrollable()); - box_style.set_overflow_y(overflow_y.to_scrollable()); - } - } - - #[cfg(feature = "gecko")] - fn adjust_for_contain(&mut self) { - let box_style = self.style.get_box(); - debug_assert_eq!( - box_style.clone_contain(), - box_style.clone_effective_containment() - ); - let container_type = box_style.clone_container_type(); - let content_visibility = box_style.clone_content_visibility(); - if container_type == ContainerType::Normal && - content_visibility == ContentVisibility::Visible - { - return; - } - let old_contain = box_style.clone_contain(); - let mut new_contain = old_contain; - match content_visibility { - ContentVisibility::Visible => {}, - // `content-visibility:auto` also applies size containment when content - // is not relevant (and therefore skipped). This is checked in - // nsIFrame::GetContainSizeAxes. - ContentVisibility::Auto => { - new_contain.insert(Contain::LAYOUT | Contain::PAINT | Contain::STYLE) - }, - ContentVisibility::Hidden => new_contain - .insert(Contain::LAYOUT | Contain::PAINT | Contain::SIZE | Contain::STYLE), - } - match container_type { - ContainerType::Normal => {}, - // https://drafts.csswg.org/css-contain-3/#valdef-container-type-inline-size: - // Applies layout containment, style containment, and inline-size - // containment to the principal box. - ContainerType::InlineSize => { - new_contain.insert(Contain::LAYOUT | Contain::STYLE | Contain::INLINE_SIZE) - }, - // https://drafts.csswg.org/css-contain-3/#valdef-container-type-size: - // Applies layout containment, style containment, and size - // containment to the principal box. - ContainerType::Size => { - new_contain.insert(Contain::LAYOUT | Contain::STYLE | Contain::SIZE) - }, - } - if new_contain == old_contain { - return; - } - self.style - .mutate_box() - .set_effective_containment(new_contain); - } - - /// content-visibility: auto should force contain-intrinsic-size to gain - /// an auto value - /// - /// <https://github.com/w3c/csswg-drafts/issues/8407> - #[cfg(feature = "gecko")] - fn adjust_for_contain_intrinsic_size(&mut self) { - let content_visibility = self.style.get_box().clone_content_visibility(); - if content_visibility != ContentVisibility::Auto { - return; - } - - let pos = self.style.get_position(); - let new_width = pos.clone_contain_intrinsic_width().add_auto_if_needed(); - let new_height = pos.clone_contain_intrinsic_height().add_auto_if_needed(); - if new_width.is_none() && new_height.is_none() { - return; - } - - let pos = self.style.mutate_position(); - if let Some(width) = new_width { - pos.set_contain_intrinsic_width(width); - } - if let Some(height) = new_height { - pos.set_contain_intrinsic_height(height); - } - } - - /// Handles the relevant sections in: - /// - /// https://drafts.csswg.org/css-display/#unbox-html - /// - /// And forbidding display: contents in pseudo-elements, at least for now. - #[cfg(feature = "gecko")] - fn adjust_for_prohibited_display_contents<E>(&mut self, element: Option<E>) - where - E: TElement, - { - if self.style.get_box().clone_display() != Display::Contents { - return; - } - - // FIXME(emilio): ::before and ::after should support display: contents, - // see bug 1418138. - if self.style.pseudo.is_some() { - self.style.mutate_box().set_display(Display::Inline); - return; - } - - let element = match element { - Some(e) => e, - None => return, - }; - - if is_effective_display_none_for_display_contents(element) { - self.style.mutate_box().set_display(Display::None); - } - } - - /// <textarea>'s editor root needs to inherit the overflow value from its - /// parent, but we need to make sure it's still scrollable. - #[cfg(feature = "gecko")] - fn adjust_for_text_control_editing_root(&mut self) { - use crate::properties::longhands::overflow_x::computed_value::T as Overflow; - use crate::selector_parser::PseudoElement; - - if self.style.pseudo != Some(&PseudoElement::MozTextControlEditingRoot) { - return; - } - - let box_style = self.style.get_box(); - let overflow_x = box_style.clone_overflow_x(); - let overflow_y = box_style.clone_overflow_y(); - - // If at least one is scrollable we'll adjust the other one in - // adjust_for_overflow if needed. - if overflow_x.is_scrollable() || overflow_y.is_scrollable() { - return; - } - - let box_style = self.style.mutate_box(); - box_style.set_overflow_x(Overflow::Auto); - box_style.set_overflow_y(Overflow::Auto); - } - - /// If a <fieldset> has grid/flex display type, we need to inherit - /// this type into its ::-moz-fieldset-content anonymous box. - /// - /// NOTE(emilio): We don't need to handle the display change for this case - /// in matching.rs because anonymous box restyling works separately to the - /// normal cascading process. - #[cfg(feature = "gecko")] - fn adjust_for_fieldset_content(&mut self, layout_parent_style: &ComputedValues) { - use crate::selector_parser::PseudoElement; - - if self.style.pseudo != Some(&PseudoElement::FieldsetContent) { - return; - } - - debug_assert_eq!(self.style.get_box().clone_display(), Display::Block); - // TODO We actually want style from parent rather than layout - // parent, so that this fixup doesn't happen incorrectly when - // when <fieldset> has "display: contents". - let parent_display = layout_parent_style.get_box().clone_display(); - let new_display = match parent_display { - Display::Flex | Display::InlineFlex => Some(Display::Flex), - Display::Grid | Display::InlineGrid => Some(Display::Grid), - _ => None, - }; - if let Some(new_display) = new_display { - self.style.mutate_box().set_display(new_display); - } - } - - /// -moz-center, -moz-left and -moz-right are used for HTML's alignment. - /// - /// This is covering the <div align="right"><table>...</table></div> case. - /// - /// In this case, we don't want to inherit the text alignment into the - /// table. - #[cfg(feature = "gecko")] - fn adjust_for_table_text_align(&mut self) { - use crate::properties::longhands::text_align::computed_value::T as TextAlign; - if self.style.get_box().clone_display() != Display::Table { - return; - } - - match self.style.get_inherited_text().clone_text_align() { - TextAlign::MozLeft | TextAlign::MozCenter | TextAlign::MozRight => {}, - _ => return, - } - - self.style - .mutate_inherited_text() - .set_text_align(TextAlign::Start) - } - - /// Computes the used text decoration for Servo. - /// - /// FIXME(emilio): This is a layout tree concept, should move away from - /// style, since otherwise we're going to have the same subtle bugs WebKit - /// and Blink have with this very same thing. - #[cfg(feature = "servo")] - fn adjust_for_text_decorations_in_effect(&mut self) { - use crate::values::computed::text::TextDecorationsInEffect; - - let decorations_in_effect = TextDecorationsInEffect::from_style(&self.style); - if self.style.get_inherited_text().text_decorations_in_effect != decorations_in_effect { - self.style - .mutate_inherited_text() - .text_decorations_in_effect = decorations_in_effect; - } - } - - #[cfg(feature = "gecko")] - fn should_suppress_linebreak<E>( - &self, - layout_parent_style: &ComputedValues, - element: Option<E>, - ) -> bool - where - E: TElement, - { - // Line break suppression should only be propagated to in-flow children. - if self.style.is_floating() || self.style.is_absolutely_positioned() { - return false; - } - let parent_display = layout_parent_style.get_box().clone_display(); - if layout_parent_style - .flags - .contains(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK) - { - // Line break suppression is propagated to any children of - // line participants. - if parent_display.is_line_participant() { - return true; - } - } - match self.style.get_box().clone_display() { - // Ruby base and text are always non-breakable. - Display::RubyBase | Display::RubyText => true, - // Ruby base container and text container are breakable. - // Non-HTML elements may not form ruby base / text container because - // they may not respect ruby-internal display values, so we can't - // make them escaped from line break suppression. - // Note that, when certain HTML tags, e.g. form controls, have ruby - // level container display type, they could also escape from the - // line break suppression flag while they shouldn't. However, it is - // generally fine as far as they can't break the line inside them. - Display::RubyBaseContainer | Display::RubyTextContainer - if element.map_or(true, |e| e.is_html_element()) => - { - false - }, - // Anything else is non-breakable if and only if its layout parent - // has a ruby display type, because any of the ruby boxes can be - // anonymous. - _ => parent_display.is_ruby_type(), - } - } - - /// Do ruby-related style adjustments, which include: - /// * propagate the line break suppression flag, - /// * inlinify block descendants, - /// * suppress border and padding for ruby level containers, - /// * correct unicode-bidi. - #[cfg(feature = "gecko")] - fn adjust_for_ruby<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>) - where - E: TElement, - { - use crate::properties::longhands::unicode_bidi::computed_value::T as UnicodeBidi; - - let self_display = self.style.get_box().clone_display(); - // Check whether line break should be suppressed for this element. - if self.should_suppress_linebreak(layout_parent_style, element) { - self.style - .add_flags(ComputedValueFlags::SHOULD_SUPPRESS_LINEBREAK); - // Inlinify the display type if allowed. - if !self.skip_item_display_fixup(element) { - let inline_display = self_display.inlinify(); - if self_display != inline_display { - self.style - .mutate_box() - .set_adjusted_display(inline_display, false); - } - } - } - // Suppress border and padding for ruby level containers. - // This is actually not part of the spec. It is currently unspecified - // how border and padding should be handled for ruby level container, - // and suppressing them here make it easier for layout to handle. - if self_display.is_ruby_level_container() { - self.style.reset_border_struct(); - self.style.reset_padding_struct(); - } - - // Force bidi isolation on all internal ruby boxes and ruby container - // per spec https://drafts.csswg.org/css-ruby-1/#bidi - if self_display.is_ruby_type() { - let new_value = match self.style.get_text().clone_unicode_bidi() { - UnicodeBidi::Normal | UnicodeBidi::Embed => Some(UnicodeBidi::Isolate), - UnicodeBidi::BidiOverride => Some(UnicodeBidi::IsolateOverride), - _ => None, - }; - if let Some(new_value) = new_value { - self.style.mutate_text().set_unicode_bidi(new_value); - } - } - } - - /// Computes the RELEVANT_LINK_VISITED flag based on the parent style and on - /// whether we're a relevant link. - /// - /// NOTE(emilio): We don't do this for text styles, which is... dubious, but - /// Gecko doesn't seem to do it either. It's extremely easy to do if needed - /// though. - /// - /// FIXME(emilio): This isn't technically a style adjustment thingie, could - /// it move somewhere else? - fn adjust_for_visited<E>(&mut self, element: Option<E>) - where - E: TElement, - { - if !self.style.has_visited_style() { - return; - } - - let is_link_element = self.style.pseudo.is_none() && element.map_or(false, |e| e.is_link()); - - if !is_link_element { - return; - } - - if element.unwrap().is_visited_link() { - self.style - .add_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED); - } else { - // Need to remove to handle unvisited link inside visited. - self.style - .remove_flags(ComputedValueFlags::IS_RELEVANT_LINK_VISITED); - } - } - - /// Resolves "justify-items: legacy" based on the inherited style if needed - /// to comply with: - /// - /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy> - #[cfg(feature = "gecko")] - fn adjust_for_justify_items(&mut self) { - use crate::values::specified::align; - let justify_items = self.style.get_position().clone_justify_items(); - if justify_items.specified.0 != align::AlignFlags::LEGACY { - return; - } - - let parent_justify_items = self.style.get_parent_position().clone_justify_items(); - - if !parent_justify_items - .computed - .0 - .contains(align::AlignFlags::LEGACY) - { - return; - } - - if parent_justify_items.computed == justify_items.computed { - return; - } - - self.style - .mutate_position() - .set_computed_justify_items(parent_justify_items.computed); - } - - /// If '-webkit-appearance' is 'menulist' on a <select> element then - /// the computed value of 'line-height' is 'normal'. - /// - /// https://github.com/w3c/csswg-drafts/issues/3257 - #[cfg(feature = "gecko")] - fn adjust_for_appearance<E>(&mut self, element: Option<E>) - where - E: TElement, - { - use crate::properties::longhands::appearance::computed_value::T as Appearance; - use crate::properties::longhands::line_height::computed_value::T as LineHeight; - - let box_ = self.style.get_box(); - let appearance = match box_.clone_appearance() { - Appearance::Auto => box_.clone__moz_default_appearance(), - a => a, - }; - - if appearance == Appearance::Menulist { - if self.style.get_inherited_text().clone_line_height() == LineHeight::normal() { - return; - } - if self.style.pseudo.is_some() { - return; - } - let is_html_select_element = element.map_or(false, |e| { - e.is_html_element() && e.local_name() == &*atom!("select") - }); - if !is_html_select_element { - return; - } - self.style - .mutate_inherited_text() - .set_line_height(LineHeight::normal()); - } - } - - /// A legacy ::marker (i.e. no 'content') without an author-specified 'font-family' - /// and 'list-style-type:disc|circle|square|disclosure-closed|disclosure-open' - /// is assigned 'font-family:-moz-bullet-font'. (This is for <ul><li> etc.) - /// We don't want synthesized italic/bold for this font, so turn that off too. - /// Likewise for 'letter/word-spacing' -- unless the author specified it then reset - /// them to their initial value because traditionally we never added such spacing - /// between a legacy bullet and the list item's content, so we keep that behavior - /// for web-compat reasons. - /// We intentionally don't check 'list-style-image' below since we want it to use - /// the same font as its fallback ('list-style-type') in case it fails to load. - #[cfg(feature = "gecko")] - fn adjust_for_marker_pseudo(&mut self) { - use crate::values::computed::counters::Content; - use crate::values::computed::font::{FontFamily, FontSynthesis}; - use crate::values::computed::text::{LetterSpacing, WordSpacing}; - - let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker()) && - self.style.get_list().clone_list_style_type().is_bullet() && - self.style.get_counters().clone_content() == Content::Normal; - if !is_legacy_marker { - return; - } - let flags = self.style.flags.get(); - if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) { - self.style - .mutate_font() - .set_font_family(FontFamily::moz_bullet().clone()); - - // FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules. - // Then we can add it to the @font-face rule in html.css instead. - // https://github.com/w3c/csswg-drafts/issues/6081 - if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_WEIGHT) { - self.style - .mutate_font() - .set_font_synthesis_weight(FontSynthesis::None); - } - if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS_STYLE) { - self.style - .mutate_font() - .set_font_synthesis_style(FontSynthesis::None); - } - } - if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) { - self.style - .mutate_inherited_text() - .set_letter_spacing(LetterSpacing::normal()); - } - if !flags.contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) { - self.style - .mutate_inherited_text() - .set_word_spacing(WordSpacing::normal()); - } - } - - /// Adjusts the style to account for various fixups that don't fit naturally - /// into the cascade. - /// - /// When comparing to Gecko, this is similar to the work done by - /// `ComputedStyle::ApplyStyleFixups`, plus some parts of - /// `nsStyleSet::GetContext`. - pub fn adjust<E>(&mut self, layout_parent_style: &ComputedValues, element: Option<E>) - where - E: TElement, - { - if cfg!(debug_assertions) { - if element.map_or(false, |e| e.is_pseudo_element()) { - // It'd be nice to assert `self.style.pseudo == Some(&pseudo)`, - // but we do resolve ::-moz-list pseudos on ::before / ::after - // content, sigh. - debug_assert!(self.style.pseudo.is_some(), "Someone really messed up"); - } - } - // FIXME(emilio): The apply_declarations callsite in Servo's - // animation, and the font stuff for Gecko - // (Stylist::compute_for_declarations) should pass an element to - // cascade(), then we can make this assertion hold everywhere. - // debug_assert!( - // element.is_some() || self.style.pseudo.is_some(), - // "Should always have an element around for non-pseudo styles" - // ); - - self.adjust_for_visited(element); - #[cfg(feature = "gecko")] - { - self.adjust_for_prohibited_display_contents(element); - self.adjust_for_fieldset_content(layout_parent_style); - // NOTE: It's important that this happens before - // adjust_for_overflow. - self.adjust_for_text_control_editing_root(); - } - self.adjust_for_top_layer(); - self.blockify_if_necessary(layout_parent_style, element); - #[cfg(feature = "gecko")] - self.adjust_for_webkit_line_clamp(); - self.adjust_for_position(); - self.adjust_for_overflow(); - #[cfg(feature = "gecko")] - { - self.adjust_for_contain(); - self.adjust_for_contain_intrinsic_size(); - self.adjust_for_table_text_align(); - self.adjust_for_justify_items(); - } - #[cfg(feature = "servo")] - { - self.adjust_for_alignment(layout_parent_style); - } - self.adjust_for_border_width(); - #[cfg(feature = "gecko")] - self.adjust_for_column_rule_width(); - self.adjust_for_outline_width(); - self.adjust_for_writing_mode(layout_parent_style); - #[cfg(feature = "gecko")] - { - self.adjust_for_ruby(layout_parent_style, element); - } - #[cfg(feature = "servo")] - { - self.adjust_for_text_decorations_in_effect(); - } - #[cfg(feature = "gecko")] - { - self.adjust_for_appearance(element); - self.adjust_for_marker_pseudo(); - } - self.set_bits(); - } -} diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs deleted file mode 100644 index 7ebcb97eefb..00000000000 --- a/components/style/style_resolver.rs +++ /dev/null @@ -1,597 +0,0 @@ -/* 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/. */ - -//! Style resolution for a given element or pseudo-element. - -use crate::applicable_declarations::ApplicableDeclarationList; -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{CascadeInputs, ElementCascadeInputs, StyleContext}; -use crate::data::{EagerPseudoStyles, ElementStyles}; -use crate::dom::TElement; -use crate::matching::MatchMethods; -use crate::properties::longhands::display::computed_value::T as Display; -use crate::properties::ComputedValues; -use crate::rule_tree::StrongRuleNode; -use crate::selector_parser::{PseudoElement, SelectorImpl}; -use crate::stylist::RuleInclusion; -use log::Level::Trace; -use selectors::matching::{MatchingContext, NeedsSelectorFlags, RelativeSelectorMatchingState}; -use selectors::matching::{MatchingMode, VisitedHandlingMode}; -use servo_arc::Arc; - -/// Whether pseudo-elements should be resolved or not. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum PseudoElementResolution { - /// Only resolve pseudo-styles if possibly applicable. - IfApplicable, - /// Force pseudo-element resolution. - Force, -} - -/// A struct that takes care of resolving the style of a given element. -pub struct StyleResolverForElement<'a, 'ctx, 'le, E> -where - 'ctx: 'a, - 'le: 'ctx, - E: TElement + MatchMethods + 'le, -{ - element: E, - context: &'a mut StyleContext<'ctx, E>, - rule_inclusion: RuleInclusion, - pseudo_resolution: PseudoElementResolution, - _marker: ::std::marker::PhantomData<&'le E>, -} - -struct MatchingResults { - rule_node: StrongRuleNode, - flags: ComputedValueFlags, -} - -/// A style returned from the resolver machinery. -pub struct ResolvedStyle(pub Arc<ComputedValues>); - -/// The primary style of an element or an element-backed pseudo-element. -pub struct PrimaryStyle { - /// The style itself. - pub style: ResolvedStyle, - /// Whether the style was reused from another element via the rule node (see - /// `StyleSharingCache::lookup_by_rules`). - pub reused_via_rule_node: bool, -} - -/// A set of style returned from the resolver machinery. -pub struct ResolvedElementStyles { - /// Primary style. - pub primary: PrimaryStyle, - /// Pseudo styles. - pub pseudos: EagerPseudoStyles, -} - -impl ResolvedElementStyles { - /// Convenience accessor for the primary style. - pub fn primary_style(&self) -> &Arc<ComputedValues> { - &self.primary.style.0 - } - - /// Convenience mutable accessor for the style. - pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> { - &mut self.primary.style.0 - } -} - -impl PrimaryStyle { - /// Convenience accessor for the style. - pub fn style(&self) -> &ComputedValues { - &*self.style.0 - } -} - -impl From<ResolvedElementStyles> for ElementStyles { - fn from(r: ResolvedElementStyles) -> ElementStyles { - ElementStyles { - primary: Some(r.primary.style.0), - pseudos: r.pseudos, - } - } -} - -fn with_default_parent_styles<E, F, R>(element: E, f: F) -> R -where - E: TElement, - F: FnOnce(Option<&ComputedValues>, Option<&ComputedValues>) -> R, -{ - let parent_el = element.inheritance_parent(); - let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data()); - let parent_style = parent_data.as_ref().map(|d| d.styles.primary()); - - let mut layout_parent_el = parent_el.clone(); - let layout_parent_data; - let mut layout_parent_style = parent_style; - if parent_style.map_or(false, |s| s.is_display_contents()) { - layout_parent_el = Some(layout_parent_el.unwrap().layout_parent()); - layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap(); - layout_parent_style = Some(layout_parent_data.styles.primary()); - } - - f( - parent_style.map(|x| &**x), - layout_parent_style.map(|s| &**s), - ) -} - -fn layout_parent_style_for_pseudo<'a>( - primary_style: &'a PrimaryStyle, - layout_parent_style: Option<&'a ComputedValues>, -) -> Option<&'a ComputedValues> { - if primary_style.style().is_display_contents() { - layout_parent_style - } else { - Some(primary_style.style()) - } -} - -fn eager_pseudo_is_definitely_not_generated( - pseudo: &PseudoElement, - style: &ComputedValues, -) -> bool { - if !pseudo.is_before_or_after() { - return false; - } - - if !style - .flags - .intersects(ComputedValueFlags::DISPLAY_DEPENDS_ON_INHERITED_STYLE) && - style.get_box().clone_display() == Display::None - { - return true; - } - - if !style - .flags - .intersects(ComputedValueFlags::CONTENT_DEPENDS_ON_INHERITED_STYLE) && - style.ineffective_content_property() - { - return true; - } - - false -} - -impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E> -where - 'ctx: 'a, - 'le: 'ctx, - E: TElement + MatchMethods + 'le, -{ - /// Trivially construct a new StyleResolverForElement. - pub fn new( - element: E, - context: &'a mut StyleContext<'ctx, E>, - rule_inclusion: RuleInclusion, - pseudo_resolution: PseudoElementResolution, - ) -> Self { - Self { - element, - context, - rule_inclusion, - pseudo_resolution, - _marker: ::std::marker::PhantomData, - } - } - - /// Resolve just the style of a given element. - pub fn resolve_primary_style( - &mut self, - parent_style: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - ) -> PrimaryStyle { - let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited); - - let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some()); - - let visited_rules = if self.context.shared.visited_styles_enabled && - (inside_link || self.element.is_link()) - { - let visited_matching_results = - self.match_primary(VisitedHandlingMode::RelevantLinkVisited); - Some(visited_matching_results.rule_node) - } else { - None - }; - - self.cascade_primary_style( - CascadeInputs { - rules: Some(primary_results.rule_node), - visited_rules, - flags: primary_results.flags, - }, - parent_style, - layout_parent_style, - ) - } - - fn cascade_primary_style( - &mut self, - inputs: CascadeInputs, - parent_style: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - ) -> PrimaryStyle { - // Before doing the cascade, check the sharing cache and see if we can - // reuse the style via rule node identity. - let may_reuse = self.element.matches_user_and_content_rules() && - parent_style.is_some() && - inputs.rules.is_some(); - - if may_reuse { - let cached = self.context.thread_local.sharing_cache.lookup_by_rules( - self.context.shared, - parent_style.unwrap(), - inputs.rules.as_ref().unwrap(), - inputs.visited_rules.as_ref(), - self.element, - ); - if let Some(mut primary_style) = cached { - self.context.thread_local.statistics.styles_reused += 1; - primary_style.reused_via_rule_node |= true; - return primary_style; - } - } - - // No style to reuse. Cascade the style, starting with visited style - // if necessary. - PrimaryStyle { - style: self.cascade_style_and_visited( - inputs, - parent_style, - layout_parent_style, - /* pseudo = */ None, - ), - reused_via_rule_node: false, - } - } - - /// Resolve the style of a given element, and all its eager pseudo-elements. - pub fn resolve_style( - &mut self, - parent_style: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - ) -> ResolvedElementStyles { - let primary_style = self.resolve_primary_style(parent_style, layout_parent_style); - - let mut pseudo_styles = EagerPseudoStyles::default(); - - if !self.element.is_pseudo_element() { - let layout_parent_style_for_pseudo = - layout_parent_style_for_pseudo(&primary_style, layout_parent_style); - SelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| { - let pseudo_style = self.resolve_pseudo_style( - &pseudo, - &primary_style, - layout_parent_style_for_pseudo, - ); - - if let Some(style) = pseudo_style { - if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) && - eager_pseudo_is_definitely_not_generated(&pseudo, &style.0) - { - return; - } - pseudo_styles.set(&pseudo, style.0); - } - }) - } - - ResolvedElementStyles { - primary: primary_style, - pseudos: pseudo_styles, - } - } - - /// Resolve an element's styles with the default inheritance parent/layout - /// parents. - pub fn resolve_style_with_default_parents(&mut self) -> ResolvedElementStyles { - with_default_parent_styles(self.element, |parent_style, layout_parent_style| { - self.resolve_style(parent_style, layout_parent_style) - }) - } - - /// Cascade a set of rules, using the default parent for inheritance. - pub fn cascade_style_and_visited_with_default_parents( - &mut self, - inputs: CascadeInputs, - ) -> ResolvedStyle { - with_default_parent_styles(self.element, |parent_style, layout_parent_style| { - self.cascade_style_and_visited( - inputs, - parent_style, - layout_parent_style, - /* pseudo = */ None, - ) - }) - } - - /// Cascade a set of rules for pseudo element, using the default parent for inheritance. - pub fn cascade_style_and_visited_for_pseudo_with_default_parents( - &mut self, - inputs: CascadeInputs, - pseudo: &PseudoElement, - primary_style: &PrimaryStyle, - ) -> ResolvedStyle { - with_default_parent_styles(self.element, |_, layout_parent_style| { - let layout_parent_style_for_pseudo = - layout_parent_style_for_pseudo(primary_style, layout_parent_style); - - self.cascade_style_and_visited( - inputs, - Some(primary_style.style()), - layout_parent_style_for_pseudo, - Some(pseudo), - ) - }) - } - - fn cascade_style_and_visited( - &mut self, - inputs: CascadeInputs, - parent_style: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - pseudo: Option<&PseudoElement>, - ) -> ResolvedStyle { - debug_assert!(pseudo.map_or(true, |p| p.is_eager())); - - let implemented_pseudo = self.element.implemented_pseudo_element(); - let pseudo = pseudo.or(implemented_pseudo.as_ref()); - - let mut conditions = Default::default(); - let values = self.context.shared.stylist.cascade_style_and_visited( - Some(self.element), - pseudo, - inputs, - &self.context.shared.guards, - pseudo.and(parent_style), - parent_style, - parent_style, - layout_parent_style, - Some(&self.context.thread_local.rule_cache), - &mut conditions, - ); - - self.context.thread_local.rule_cache.insert_if_possible( - &self.context.shared.guards, - &values, - pseudo, - &conditions, - ); - - ResolvedStyle(values) - } - - /// Cascade the element and pseudo-element styles with the default parents. - pub fn cascade_styles_with_default_parents( - &mut self, - inputs: ElementCascadeInputs, - ) -> ResolvedElementStyles { - with_default_parent_styles(self.element, move |parent_style, layout_parent_style| { - let primary_style = - self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style); - - let mut pseudo_styles = EagerPseudoStyles::default(); - if let Some(mut pseudo_array) = inputs.pseudos.into_array() { - let layout_parent_style_for_pseudo = if primary_style.style().is_display_contents() - { - layout_parent_style - } else { - Some(primary_style.style()) - }; - - for (i, inputs) in pseudo_array.iter_mut().enumerate() { - if let Some(inputs) = inputs.take() { - let pseudo = PseudoElement::from_eager_index(i); - - let style = self.cascade_style_and_visited( - inputs, - Some(primary_style.style()), - layout_parent_style_for_pseudo, - Some(&pseudo), - ); - - if !matches!(self.pseudo_resolution, PseudoElementResolution::Force) && - eager_pseudo_is_definitely_not_generated(&pseudo, &style.0) - { - continue; - } - - pseudo_styles.set(&pseudo, style.0); - } - } - } - - ResolvedElementStyles { - primary: primary_style, - pseudos: pseudo_styles, - } - }) - } - - fn resolve_pseudo_style( - &mut self, - pseudo: &PseudoElement, - originating_element_style: &PrimaryStyle, - layout_parent_style: Option<&ComputedValues>, - ) -> Option<ResolvedStyle> { - let MatchingResults { - rule_node, - mut flags, - } = self.match_pseudo( - &originating_element_style.style.0, - pseudo, - VisitedHandlingMode::AllLinksUnvisited, - )?; - - let mut visited_rules = None; - if originating_element_style.style().visited_style().is_some() { - visited_rules = self - .match_pseudo( - &originating_element_style.style.0, - pseudo, - VisitedHandlingMode::RelevantLinkVisited, - ) - .map(|results| { - flags |= results.flags; - results.rule_node - }); - } - - Some(self.cascade_style_and_visited( - CascadeInputs { - rules: Some(rule_node), - visited_rules, - flags, - }, - Some(originating_element_style.style()), - layout_parent_style, - Some(pseudo), - )) - } - - fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults { - debug!( - "Match primary for {:?}, visited: {:?}", - self.element, visited_handling - ); - let mut applicable_declarations = ApplicableDeclarationList::new(); - - let bloom_filter = self.context.thread_local.bloom_filter.filter(); - let nth_index_cache = &mut self.context.thread_local.nth_index_cache; - let mut matching_context = MatchingContext::new_for_visited( - MatchingMode::Normal, - Some(bloom_filter), - nth_index_cache, - visited_handling, - self.context.shared.quirks_mode(), - NeedsSelectorFlags::Yes, - ); - - let stylist = &self.context.shared.stylist; - let implemented_pseudo = self.element.implemented_pseudo_element(); - // Compute the primary rule node. - stylist.push_applicable_declarations( - self.element, - implemented_pseudo.as_ref(), - self.element.style_attribute(), - self.element.smil_override(), - self.element.animation_declarations(self.context.shared), - self.rule_inclusion, - &mut applicable_declarations, - &mut matching_context, - ); - - // FIXME(emilio): This is a hack for animations, and should go away. - self.element.unset_dirty_style_attribute(); - - let rule_node = stylist - .rule_tree() - .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards); - - if log_enabled!(Trace) { - trace!("Matched rules for {:?}:", self.element); - for rn in rule_node.self_and_ancestors() { - let source = rn.style_source(); - if source.is_some() { - trace!(" > {:?}", source); - } - } - } - // This is a bit awkward - ideally, the flag is set directly where `considered_relative_selector` - // is; however, in that context, the implementation detail of `extra_data` is not visible, so - // it's done here. A trait for manipulating the flags is an option, but not worth it for a single flag. - match matching_context.considered_relative_selector { - RelativeSelectorMatchingState::None => (), - RelativeSelectorMatchingState::Considered => { - matching_context - .extra_data - .cascade_input_flags - .insert(ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR); - }, - RelativeSelectorMatchingState::ConsideredAnchor => { - matching_context.extra_data.cascade_input_flags.insert( - ComputedValueFlags::ANCHORS_RELATIVE_SELECTOR | - ComputedValueFlags::CONSIDERED_RELATIVE_SELECTOR, - ); - }, - }; - - MatchingResults { - rule_node, - flags: matching_context.extra_data.cascade_input_flags, - } - } - - fn match_pseudo( - &mut self, - originating_element_style: &ComputedValues, - pseudo_element: &PseudoElement, - visited_handling: VisitedHandlingMode, - ) -> Option<MatchingResults> { - debug!( - "Match pseudo {:?} for {:?}, visited: {:?}", - self.element, pseudo_element, visited_handling - ); - debug_assert!(pseudo_element.is_eager()); - debug_assert!( - !self.element.is_pseudo_element(), - "Element pseudos can't have any other eager pseudo." - ); - - let mut applicable_declarations = ApplicableDeclarationList::new(); - - let stylist = &self.context.shared.stylist; - - if !self - .element - .may_generate_pseudo(pseudo_element, originating_element_style) - { - return None; - } - - let bloom_filter = self.context.thread_local.bloom_filter.filter(); - let nth_index_cache = &mut self.context.thread_local.nth_index_cache; - - let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited( - MatchingMode::ForStatelessPseudoElement, - Some(bloom_filter), - nth_index_cache, - visited_handling, - self.context.shared.quirks_mode(), - NeedsSelectorFlags::Yes, - ); - matching_context.extra_data.originating_element_style = Some(originating_element_style); - - // NB: We handle animation rules for ::before and ::after when - // traversing them. - stylist.push_applicable_declarations( - self.element, - Some(pseudo_element), - None, - None, - /* animation_declarations = */ Default::default(), - self.rule_inclusion, - &mut applicable_declarations, - &mut matching_context, - ); - - if applicable_declarations.is_empty() { - return None; - } - - let rule_node = stylist - .rule_tree() - .compute_rule_node(&mut applicable_declarations, &self.context.shared.guards); - - Some(MatchingResults { - rule_node, - flags: matching_context.extra_data.cascade_input_flags, - }) - } -} diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs deleted file mode 100644 index e2937e06e75..00000000000 --- a/components/style/stylesheet_set.rs +++ /dev/null @@ -1,705 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A centralized set of stylesheets for a document. - -use crate::dom::TElement; -use crate::invalidation::stylesheets::{RuleChangeKind, StylesheetInvalidationSet}; -use crate::media_queries::Device; -use crate::selector_parser::SnapshotMap; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{ - CssRule, Origin, OriginSet, OriginSetIterator, PerOrigin, StylesheetInDocument, -}; -use std::{mem, slice}; - -/// Entry for a StylesheetSet. -#[derive(MallocSizeOf)] -struct StylesheetSetEntry<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The sheet. - sheet: S, - - /// Whether this sheet has been part of at least one flush. - committed: bool, -} - -impl<S> StylesheetSetEntry<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - fn new(sheet: S) -> Self { - Self { - sheet, - committed: false, - } - } -} - -/// A iterator over the stylesheets of a list of entries in the StylesheetSet. -pub struct StylesheetCollectionIterator<'a, S>(slice::Iter<'a, StylesheetSetEntry<S>>) -where - S: StylesheetInDocument + PartialEq + 'static; - -impl<'a, S> Clone for StylesheetCollectionIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - fn clone(&self) -> Self { - StylesheetCollectionIterator(self.0.clone()) - } -} - -impl<'a, S> Iterator for StylesheetCollectionIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - type Item = &'a S; - - fn next(&mut self) -> Option<Self::Item> { - self.0.next().map(|entry| &entry.sheet) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.0.size_hint() - } -} - -/// An iterator over the flattened view of the stylesheet collections. -#[derive(Clone)] -pub struct StylesheetIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - origins: OriginSetIterator, - collections: &'a PerOrigin<SheetCollection<S>>, - current: Option<(Origin, StylesheetCollectionIterator<'a, S>)>, -} - -impl<'a, S> Iterator for StylesheetIterator<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - type Item = (&'a S, Origin); - - fn next(&mut self) -> Option<Self::Item> { - loop { - if self.current.is_none() { - let next_origin = self.origins.next()?; - - self.current = Some(( - next_origin, - self.collections.borrow_for_origin(&next_origin).iter(), - )); - } - - { - let (origin, ref mut iter) = *self.current.as_mut().unwrap(); - if let Some(s) = iter.next() { - return Some((s, origin)); - } - } - - self.current = None; - } - } -} - -/// The validity of the data in a given cascade origin. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Ord, PartialEq, PartialOrd)] -pub enum DataValidity { - /// The origin is clean, all the data already there is valid, though we may - /// have new sheets at the end. - Valid = 0, - - /// The cascade data is invalid, but not the invalidation data (which is - /// order-independent), and thus only the cascade data should be inserted. - CascadeInvalid = 1, - - /// Everything needs to be rebuilt. - FullyInvalid = 2, -} - -impl Default for DataValidity { - fn default() -> Self { - DataValidity::Valid - } -} - -/// A struct to iterate over the different stylesheets to be flushed. -pub struct DocumentStylesheetFlusher<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - collections: &'a mut PerOrigin<SheetCollection<S>>, - had_invalidations: bool, -} - -/// The type of rebuild that we need to do for a given stylesheet. -#[derive(Clone, Copy, Debug)] -pub enum SheetRebuildKind { - /// A full rebuild, of both cascade data and invalidation data. - Full, - /// A partial rebuild, of only the cascade data. - CascadeOnly, -} - -impl SheetRebuildKind { - /// Whether the stylesheet invalidation data should be rebuilt. - pub fn should_rebuild_invalidation(&self) -> bool { - matches!(*self, SheetRebuildKind::Full) - } -} - -impl<'a, S> DocumentStylesheetFlusher<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Returns a flusher for `origin`. - pub fn flush_origin(&mut self, origin: Origin) -> SheetCollectionFlusher<S> { - self.collections.borrow_mut_for_origin(&origin).flush() - } - - /// Returns the list of stylesheets for `origin`. - /// - /// Only used for UA sheets. - pub fn origin_sheets(&mut self, origin: Origin) -> StylesheetCollectionIterator<S> { - self.collections.borrow_mut_for_origin(&origin).iter() - } - - /// Returns whether any DOM invalidations were processed as a result of the - /// stylesheet flush. - #[inline] - pub fn had_invalidations(&self) -> bool { - self.had_invalidations - } -} - -/// A flusher struct for a given collection, that takes care of returning the -/// appropriate stylesheets that need work. -pub struct SheetCollectionFlusher<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - // TODO: This can be made an iterator again once - // https://github.com/rust-lang/rust/pull/82771 lands on stable. - entries: &'a mut [StylesheetSetEntry<S>], - validity: DataValidity, - dirty: bool, -} - -impl<'a, S> SheetCollectionFlusher<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Whether the collection was originally dirty. - #[inline] - pub fn dirty(&self) -> bool { - self.dirty - } - - /// What the state of the sheet data is. - #[inline] - pub fn data_validity(&self) -> DataValidity { - self.validity - } - - /// Returns an iterator over the remaining list of sheets to consume. - pub fn sheets<'b>(&'b self) -> impl Iterator<Item = &'b S> { - self.entries.iter().map(|entry| &entry.sheet) - } -} - -impl<'a, S> SheetCollectionFlusher<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Iterates over all sheets and values that we have to invalidate. - /// - /// TODO(emilio): This would be nicer as an iterator but we can't do that - /// until https://github.com/rust-lang/rust/pull/82771 stabilizes. - /// - /// Since we don't have a good use-case for partial iteration, this does the - /// trick for now. - pub fn each(self, mut callback: impl FnMut(&S, SheetRebuildKind) -> bool) { - for potential_sheet in self.entries.iter_mut() { - let committed = mem::replace(&mut potential_sheet.committed, true); - let rebuild_kind = if !committed { - // If the sheet was uncommitted, we need to do a full rebuild - // anyway. - SheetRebuildKind::Full - } else { - match self.validity { - DataValidity::Valid => continue, - DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly, - DataValidity::FullyInvalid => SheetRebuildKind::Full, - } - }; - - if !callback(&potential_sheet.sheet, rebuild_kind) { - return; - } - } - } -} - -#[derive(MallocSizeOf)] -struct SheetCollection<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The actual list of stylesheets. - /// - /// This is only a list of top-level stylesheets, and as such it doesn't - /// include recursive `@import` rules. - entries: Vec<StylesheetSetEntry<S>>, - - /// The validity of the data that was already there for a given origin. - /// - /// Note that an origin may appear on `origins_dirty`, but still have - /// `DataValidity::Valid`, if only sheets have been appended into it (in - /// which case the existing data is valid, but the origin needs to be - /// rebuilt). - data_validity: DataValidity, - - /// Whether anything in the collection has changed. Note that this is - /// different from `data_validity`, in the sense that after a sheet append, - /// the data validity is still `Valid`, but we need to be marked as dirty. - dirty: bool, -} - -impl<S> Default for SheetCollection<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - fn default() -> Self { - Self { - entries: vec![], - data_validity: DataValidity::Valid, - dirty: false, - } - } -} - -impl<S> SheetCollection<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Returns the number of stylesheets in the set. - fn len(&self) -> usize { - self.entries.len() - } - - /// Returns the `index`th stylesheet in the set if present. - fn get(&self, index: usize) -> Option<&S> { - self.entries.get(index).map(|e| &e.sheet) - } - - fn remove(&mut self, sheet: &S) { - let index = self.entries.iter().position(|entry| entry.sheet == *sheet); - if cfg!(feature = "gecko") && index.is_none() { - // FIXME(emilio): Make Gecko's PresShell::AddUserSheet not suck. - return; - } - let sheet = self.entries.remove(index.unwrap()); - // Removing sheets makes us tear down the whole cascade and invalidation - // data, but only if the sheet has been involved in at least one flush. - // Checking whether the sheet has been committed allows us to avoid - // rebuilding the world when sites quickly append and remove a - // stylesheet. - // - // See bug 1434756. - if sheet.committed { - self.set_data_validity_at_least(DataValidity::FullyInvalid); - } else { - self.dirty = true; - } - } - - fn contains(&self, sheet: &S) -> bool { - self.entries.iter().any(|e| e.sheet == *sheet) - } - - /// Appends a given sheet into the collection. - fn append(&mut self, sheet: S) { - debug_assert!(!self.contains(&sheet)); - self.entries.push(StylesheetSetEntry::new(sheet)); - // Appending sheets doesn't alter the validity of the existing data, so - // we don't need to change `data_validity` here. - // - // But we need to be marked as dirty, otherwise we'll never add the new - // sheet! - self.dirty = true; - } - - fn insert_before(&mut self, sheet: S, before_sheet: &S) { - debug_assert!(!self.contains(&sheet)); - - let index = self - .entries - .iter() - .position(|entry| entry.sheet == *before_sheet) - .expect("`before_sheet` stylesheet not found"); - - // Inserting stylesheets somewhere but at the end changes the validity - // of the cascade data, but not the invalidation data. - self.set_data_validity_at_least(DataValidity::CascadeInvalid); - self.entries.insert(index, StylesheetSetEntry::new(sheet)); - } - - fn set_data_validity_at_least(&mut self, validity: DataValidity) { - use std::cmp; - - debug_assert_ne!(validity, DataValidity::Valid); - - self.dirty = true; - self.data_validity = cmp::max(validity, self.data_validity); - } - - /// Returns an iterator over the current list of stylesheets. - fn iter(&self) -> StylesheetCollectionIterator<S> { - StylesheetCollectionIterator(self.entries.iter()) - } - - fn flush(&mut self) -> SheetCollectionFlusher<S> { - let dirty = mem::replace(&mut self.dirty, false); - let validity = mem::replace(&mut self.data_validity, DataValidity::Valid); - - SheetCollectionFlusher { - entries: &mut self.entries, - dirty, - validity, - } - } -} - -/// The set of stylesheets effective for a given document. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct DocumentStylesheetSet<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The collections of sheets per each origin. - collections: PerOrigin<SheetCollection<S>>, - - /// The invalidations for stylesheets added or removed from this document. - invalidations: StylesheetInvalidationSet, -} - -/// This macro defines methods common to DocumentStylesheetSet and -/// AuthorStylesheetSet. -/// -/// We could simplify the setup moving invalidations to SheetCollection, but -/// that would imply not sharing invalidations across origins of the same -/// documents, which is slightly annoying. -macro_rules! sheet_set_methods { - ($set_name:expr) => { - fn collect_invalidations_for( - &mut self, - device: Option<&Device>, - sheet: &S, - guard: &SharedRwLockReadGuard, - ) { - if let Some(device) = device { - self.invalidations - .collect_invalidations_for(device, sheet, guard); - } - } - - /// Appends a new stylesheet to the current set. - /// - /// No device implies not computing invalidations. - pub fn append_stylesheet( - &mut self, - device: Option<&Device>, - sheet: S, - guard: &SharedRwLockReadGuard, - ) { - debug!(concat!($set_name, "::append_stylesheet")); - self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet); - collection.append(sheet); - } - - /// Insert a given stylesheet before another stylesheet in the document. - pub fn insert_stylesheet_before( - &mut self, - device: Option<&Device>, - sheet: S, - before_sheet: S, - guard: &SharedRwLockReadGuard, - ) { - debug!(concat!($set_name, "::insert_stylesheet_before")); - self.collect_invalidations_for(device, &sheet, guard); - - let collection = self.collection_for(&sheet); - collection.insert_before(sheet, &before_sheet); - } - - /// Remove a given stylesheet from the set. - pub fn remove_stylesheet( - &mut self, - device: Option<&Device>, - sheet: S, - guard: &SharedRwLockReadGuard, - ) { - debug!(concat!($set_name, "::remove_stylesheet")); - self.collect_invalidations_for(device, &sheet, guard); - - let collection = self.collection_for(&sheet); - collection.remove(&sheet) - } - - /// Notify the set that a rule from a given stylesheet has changed - /// somehow. - pub fn rule_changed( - &mut self, - device: Option<&Device>, - sheet: &S, - rule: &CssRule, - guard: &SharedRwLockReadGuard, - change_kind: RuleChangeKind, - ) { - if let Some(device) = device { - let quirks_mode = device.quirks_mode(); - self.invalidations.rule_changed( - sheet, - rule, - guard, - device, - quirks_mode, - change_kind, - ); - } - - let validity = match change_kind { - // Insertion / Removals need to rebuild both the cascade and - // invalidation data. For generic changes this is conservative, - // could be optimized on a per-case basis. - RuleChangeKind::Generic | RuleChangeKind::Insertion | RuleChangeKind::Removal => { - DataValidity::FullyInvalid - }, - // TODO(emilio): This, in theory, doesn't need to invalidate - // style data, if the rule we're modifying is actually in the - // CascadeData already. - // - // But this is actually a bit tricky to prove, because when we - // copy-on-write a stylesheet we don't bother doing a rebuild, - // so we may still have rules from the original stylesheet - // instead of the cloned one that we're modifying. So don't - // bother for now and unconditionally rebuild, it's no worse - // than what we were already doing anyway. - // - // Maybe we could record whether we saw a clone in this flush, - // and if so do the conservative thing, otherwise just - // early-return. - RuleChangeKind::StyleRuleDeclarations => DataValidity::FullyInvalid, - }; - - let collection = self.collection_for(&sheet); - collection.set_data_validity_at_least(validity); - } - }; -} - -impl<S> DocumentStylesheetSet<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Create a new empty DocumentStylesheetSet. - pub fn new() -> Self { - Self { - collections: Default::default(), - invalidations: StylesheetInvalidationSet::new(), - } - } - - fn collection_for(&mut self, sheet: &S) -> &mut SheetCollection<S> { - let origin = sheet.contents().origin; - self.collections.borrow_mut_for_origin(&origin) - } - - sheet_set_methods!("DocumentStylesheetSet"); - - /// Returns the number of stylesheets in the set. - pub fn len(&self) -> usize { - self.collections - .iter_origins() - .fold(0, |s, (item, _)| s + item.len()) - } - - /// Returns the count of stylesheets for a given origin. - #[inline] - pub fn sheet_count(&self, origin: Origin) -> usize { - self.collections.borrow_for_origin(&origin).len() - } - - /// Returns the `index`th stylesheet in the set for the given origin. - #[inline] - pub fn get(&self, origin: Origin, index: usize) -> Option<&S> { - self.collections.borrow_for_origin(&origin).get(index) - } - - /// Returns whether the given set has changed from the last flush. - pub fn has_changed(&self) -> bool { - !self.invalidations.is_empty() || - self.collections - .iter_origins() - .any(|(collection, _)| collection.dirty) - } - - /// Flush the current set, unmarking it as dirty, and returns a - /// `DocumentStylesheetFlusher` in order to rebuild the stylist. - pub fn flush<E>( - &mut self, - document_element: Option<E>, - snapshots: Option<&SnapshotMap>, - ) -> DocumentStylesheetFlusher<S> - where - E: TElement, - { - debug!("DocumentStylesheetSet::flush"); - - let had_invalidations = self.invalidations.flush(document_element, snapshots); - - DocumentStylesheetFlusher { - collections: &mut self.collections, - had_invalidations, - } - } - - /// Flush stylesheets, but without running any of the invalidation passes. - #[cfg(feature = "servo")] - pub fn flush_without_invalidation(&mut self) -> OriginSet { - debug!("DocumentStylesheetSet::flush_without_invalidation"); - - let mut origins = OriginSet::empty(); - self.invalidations.clear(); - - for (collection, origin) in self.collections.iter_mut_origins() { - if collection.flush().dirty() { - origins |= origin; - } - } - - origins - } - - /// Return an iterator over the flattened view of all the stylesheets. - pub fn iter(&self) -> StylesheetIterator<S> { - StylesheetIterator { - origins: OriginSet::all().iter(), - collections: &self.collections, - current: None, - } - } - - /// Mark the stylesheets for the specified origin as dirty, because - /// something external may have invalidated it. - pub fn force_dirty(&mut self, origins: OriginSet) { - self.invalidations.invalidate_fully(); - for origin in origins.iter() { - // We don't know what happened, assume the worse. - self.collections - .borrow_mut_for_origin(&origin) - .set_data_validity_at_least(DataValidity::FullyInvalid); - } - } -} - -/// The set of stylesheets effective for a given Shadow Root. -#[derive(MallocSizeOf)] -pub struct AuthorStylesheetSet<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The actual style sheets. - collection: SheetCollection<S>, - /// The set of invalidations scheduled for this collection. - invalidations: StylesheetInvalidationSet, -} - -/// A struct to flush an author style sheet collection. -pub struct AuthorStylesheetFlusher<'a, S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// The actual flusher for the collection. - pub sheets: SheetCollectionFlusher<'a, S>, - /// Whether any sheet invalidation matched. - pub had_invalidations: bool, -} - -impl<S> AuthorStylesheetSet<S> -where - S: StylesheetInDocument + PartialEq + 'static, -{ - /// Create a new empty AuthorStylesheetSet. - #[inline] - pub fn new() -> Self { - Self { - collection: Default::default(), - invalidations: StylesheetInvalidationSet::new(), - } - } - - /// Whether anything has changed since the last time this was flushed. - pub fn dirty(&self) -> bool { - self.collection.dirty - } - - /// Whether the collection is empty. - pub fn is_empty(&self) -> bool { - self.collection.len() == 0 - } - - /// Returns the `index`th stylesheet in the collection of author styles if present. - pub fn get(&self, index: usize) -> Option<&S> { - self.collection.get(index) - } - - /// Returns the number of author stylesheets. - pub fn len(&self) -> usize { - self.collection.len() - } - - fn collection_for(&mut self, _sheet: &S) -> &mut SheetCollection<S> { - &mut self.collection - } - - sheet_set_methods!("AuthorStylesheetSet"); - - /// Iterate over the list of stylesheets. - pub fn iter(&self) -> StylesheetCollectionIterator<S> { - self.collection.iter() - } - - /// Mark the sheet set dirty, as appropriate. - pub fn force_dirty(&mut self) { - self.invalidations.invalidate_fully(); - self.collection - .set_data_validity_at_least(DataValidity::FullyInvalid); - } - - /// Flush the stylesheets for this author set. - /// - /// `host` is the root of the affected subtree, like the shadow host, for - /// example. - pub fn flush<E>( - &mut self, - host: Option<E>, - snapshots: Option<&SnapshotMap>, - ) -> AuthorStylesheetFlusher<S> - where - E: TElement, - { - let had_invalidations = self.invalidations.flush(host, snapshots); - AuthorStylesheetFlusher { - sheets: self.collection.flush(), - had_invalidations, - } - } -} diff --git a/components/style/stylesheets/container_rule.rs b/components/style/stylesheets/container_rule.rs deleted file mode 100644 index f9d488b9b49..00000000000 --- a/components/style/stylesheets/container_rule.rs +++ /dev/null @@ -1,632 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A [`@container`][container] rule. -//! -//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule - -use crate::computed_value_flags::ComputedValueFlags; -use crate::dom::TElement; -use crate::logical_geometry::{LogicalSize, WritingMode}; -use crate::media_queries::Device; -use crate::parser::ParserContext; -use crate::properties::ComputedValues; -use crate::queries::condition::KleeneValue; -use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; -use crate::queries::values::Orientation; -use crate::queries::{FeatureType, QueryCondition}; -use crate::shared_lock::{ - DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, -}; -use crate::str::CssStringWriter; -use crate::stylesheets::CssRules; -use crate::values::computed::{CSSPixelLength, ContainerType, Context, Ratio}; -use crate::values::specified::ContainerName; -use app_units::Au; -use cssparser::{Parser, SourceLocation}; -use euclid::default::Size2D; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// A container rule. -#[derive(Debug, ToShmem)] -pub struct ContainerRule { - /// The container query and name. - pub condition: Arc<ContainerCondition>, - /// The nested rules inside the block. - pub rules: Arc<Locked<CssRules>>, - /// The source position where this rule was found. - pub source_location: SourceLocation, -} - -impl ContainerRule { - /// Returns the query condition. - pub fn query_condition(&self) -> &QueryCondition { - &self.condition.condition - } - - /// Returns the query name filter. - pub fn container_name(&self) -> &ContainerName { - &self.condition.name - } - - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - // Measurement of other fields may be added later. - self.rules.unconditional_shallow_size_of(ops) + - self.rules.read_with(guard).size_of(guard, ops) - } -} - -impl DeepCloneWithLock for ContainerRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - let rules = self.rules.read_with(guard); - Self { - condition: self.condition.clone(), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), - source_location: self.source_location.clone(), - } - } -} - -impl ToCssWithGuard for ContainerRule { - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@container ")?; - { - let mut writer = CssWriter::new(dest); - if !self.condition.name.is_none() { - self.condition.name.to_css(&mut writer)?; - writer.write_char(' ')?; - } - self.condition.condition.to_css(&mut writer)?; - } - self.rules.read_with(guard).to_css_block(guard, dest) - } -} - -/// A container condition and filter, combined. -#[derive(Debug, ToShmem, ToCss)] -pub struct ContainerCondition { - #[css(skip_if = "ContainerName::is_none")] - name: ContainerName, - condition: QueryCondition, - #[css(skip)] - flags: FeatureFlags, -} - -/// The result of a successful container query lookup. -pub struct ContainerLookupResult<E> { - /// The relevant container. - pub element: E, - /// The sizing / writing-mode information of the container. - pub info: ContainerInfo, - /// The style of the element. - pub style: Arc<ComputedValues>, -} - -fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags { - match ty_ { - ContainerType::Size => FeatureFlags::all_container_axes(), - ContainerType::InlineSize => { - let physical_axis = if wm.is_vertical() { - FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS - } else { - FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS - }; - FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis - }, - ContainerType::Normal => FeatureFlags::empty(), - } -} - -enum TraversalResult<T> { - InProgress, - StopTraversal, - Done(T), -} - -fn traverse_container<E, F, R>( - mut e: E, - originating_element_style: Option<&ComputedValues>, - evaluator: F, -) -> Option<(E, R)> -where - E: TElement, - F: Fn(E, Option<&ComputedValues>) -> TraversalResult<R>, -{ - if originating_element_style.is_some() { - match evaluator(e, originating_element_style) { - TraversalResult::InProgress => {}, - TraversalResult::StopTraversal => return None, - TraversalResult::Done(result) => return Some((e, result)), - } - } - while let Some(element) = e.traversal_parent() { - match evaluator(element, None) { - TraversalResult::InProgress => {}, - TraversalResult::StopTraversal => return None, - TraversalResult::Done(result) => return Some((element, result)), - } - e = element; - } - - None -} - -impl ContainerCondition { - /// Parse a container condition. - pub fn parse<'a>( - context: &ParserContext, - input: &mut Parser<'a, '_>, - ) -> Result<Self, ParseError<'a>> { - let name = input - .try_parse(|input| ContainerName::parse_for_query(context, input)) - .ok() - .unwrap_or_else(ContainerName::none); - let condition = QueryCondition::parse(context, input, FeatureType::Container)?; - let flags = condition.cumulative_flags(); - Ok(Self { - name, - condition, - flags, - }) - } - - fn valid_container_info<E>( - &self, - potential_container: E, - originating_element_style: Option<&ComputedValues>, - ) -> TraversalResult<ContainerLookupResult<E>> - where - E: TElement, - { - let data; - let style = match originating_element_style { - Some(s) => s, - None => { - data = match potential_container.borrow_data() { - Some(d) => d, - None => return TraversalResult::InProgress, - }; - &**data.styles.primary() - }, - }; - let wm = style.writing_mode; - let box_style = style.get_box(); - - // Filter by container-type. - let container_type = box_style.clone_container_type(); - let available_axes = container_type_axes(container_type, wm); - if !available_axes.contains(self.flags.container_axes()) { - return TraversalResult::InProgress; - } - - // Filter by container-name. - let container_name = box_style.clone_container_name(); - for filter_name in self.name.0.iter() { - if !container_name.0.contains(filter_name) { - return TraversalResult::InProgress; - } - } - - let size = potential_container.query_container_size(&box_style.clone_display()); - let style = style.to_arc(); - TraversalResult::Done(ContainerLookupResult { - element: potential_container, - info: ContainerInfo { size, wm }, - style, - }) - } - - /// Performs container lookup for a given element. - pub fn find_container<E>( - &self, - e: E, - originating_element_style: Option<&ComputedValues>, - ) -> Option<ContainerLookupResult<E>> - where - E: TElement, - { - match traverse_container( - e, - originating_element_style, - |element, originating_element_style| { - self.valid_container_info(element, originating_element_style) - }, - ) { - Some((_, result)) => Some(result), - None => None, - } - } - - /// Tries to match a container query condition for a given element. - pub(crate) fn matches<E>( - &self, - device: &Device, - element: E, - originating_element_style: Option<&ComputedValues>, - invalidation_flags: &mut ComputedValueFlags, - ) -> KleeneValue - where - E: TElement, - { - let result = self.find_container(element, originating_element_style); - let (container, info) = match result { - Some(r) => (Some(r.element), Some((r.info, r.style))), - None => (None, None), - }; - // Set up the lookup for the container in question, as the condition may be using container query lengths. - let size_query_container_lookup = ContainerSizeQuery::for_option_element(container, None); - Context::for_container_query_evaluation( - device, - info, - size_query_container_lookup, - |context| { - let matches = self.condition.matches(context); - if context - .style() - .flags() - .contains(ComputedValueFlags::USES_VIEWPORT_UNITS) - { - // TODO(emilio): Might need something similar to improve - // invalidation of font relative container-query lengths. - invalidation_flags - .insert(ComputedValueFlags::USES_VIEWPORT_UNITS_ON_CONTAINER_QUERIES); - } - matches - }, - ) - } -} - -/// Information needed to evaluate an individual container query. -#[derive(Copy, Clone)] -pub struct ContainerInfo { - size: Size2D<Option<Au>>, - wm: WritingMode, -} - -impl ContainerInfo { - fn size(&self) -> Option<Size2D<Au>> { - Some(Size2D::new(self.size.width?, self.size.height?)) - } -} - -fn eval_width(context: &Context) -> Option<CSSPixelLength> { - let info = context.container_info.as_ref()?; - Some(CSSPixelLength::new(info.size.width?.to_f32_px())) -} - -fn eval_height(context: &Context) -> Option<CSSPixelLength> { - let info = context.container_info.as_ref()?; - Some(CSSPixelLength::new(info.size.height?.to_f32_px())) -} - -fn eval_inline_size(context: &Context) -> Option<CSSPixelLength> { - let info = context.container_info.as_ref()?; - Some(CSSPixelLength::new( - LogicalSize::from_physical(info.wm, info.size) - .inline? - .to_f32_px(), - )) -} - -fn eval_block_size(context: &Context) -> Option<CSSPixelLength> { - let info = context.container_info.as_ref()?; - Some(CSSPixelLength::new( - LogicalSize::from_physical(info.wm, info.size) - .block? - .to_f32_px(), - )) -} - -fn eval_aspect_ratio(context: &Context) -> Option<Ratio> { - let info = context.container_info.as_ref()?; - Some(Ratio::new( - info.size.width?.0 as f32, - info.size.height?.0 as f32, - )) -} - -fn eval_orientation(context: &Context, value: Option<Orientation>) -> KleeneValue { - let size = match context.container_info.as_ref().and_then(|info| info.size()) { - Some(size) => size, - None => return KleeneValue::Unknown, - }; - KleeneValue::from(Orientation::eval(size, value)) -} - -/// https://drafts.csswg.org/css-contain-3/#container-features -/// -/// TODO: Support style queries, perhaps. -pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [ - feature!( - atom!("width"), - AllowsRanges::Yes, - Evaluator::OptionalLength(eval_width), - FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS, - ), - feature!( - atom!("height"), - AllowsRanges::Yes, - Evaluator::OptionalLength(eval_height), - FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS, - ), - feature!( - atom!("inline-size"), - AllowsRanges::Yes, - Evaluator::OptionalLength(eval_inline_size), - FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS, - ), - feature!( - atom!("block-size"), - AllowsRanges::Yes, - Evaluator::OptionalLength(eval_block_size), - FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS, - ), - feature!( - atom!("aspect-ratio"), - AllowsRanges::Yes, - Evaluator::OptionalNumberRatio(eval_aspect_ratio), - // XXX from_bits_truncate is const, but the pipe operator isn't, so this - // works around it. - FeatureFlags::from_bits_truncate( - FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | - FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits() - ), - ), - feature!( - atom!("orientation"), - AllowsRanges::No, - keyword_evaluator!(eval_orientation, Orientation), - FeatureFlags::from_bits_truncate( - FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | - FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits() - ), - ), -]; - -/// Result of a container size query, signifying the hypothetical containment boundary in terms of physical axes. -/// Defined by up to two size containers. Queries on logical axes are resolved with respect to the querying -/// element's writing mode. -#[derive(Copy, Clone, Default)] -pub struct ContainerSizeQueryResult { - width: Option<Au>, - height: Option<Au>, -} - -impl ContainerSizeQueryResult { - fn get_viewport_size(context: &Context) -> Size2D<Au> { - use crate::values::specified::ViewportVariant; - context.viewport_size_for_viewport_unit_resolution(ViewportVariant::Small) - } - - fn get_logical_viewport_size(context: &Context) -> LogicalSize<Au> { - LogicalSize::from_physical( - context.builder.writing_mode, - Self::get_viewport_size(context), - ) - } - - /// Get the inline-size of the query container. - pub fn get_container_inline_size(&self, context: &Context) -> Au { - if context.builder.writing_mode.is_horizontal() { - if let Some(w) = self.width { - return w; - } - } else { - if let Some(h) = self.height { - return h; - } - } - Self::get_logical_viewport_size(context).inline - } - - /// Get the block-size of the query container. - pub fn get_container_block_size(&self, context: &Context) -> Au { - if context.builder.writing_mode.is_horizontal() { - self.get_container_height(context) - } else { - self.get_container_width(context) - } - } - - /// Get the width of the query container. - pub fn get_container_width(&self, context: &Context) -> Au { - if let Some(w) = self.width { - return w; - } - Self::get_viewport_size(context).width - } - - /// Get the height of the query container. - pub fn get_container_height(&self, context: &Context) -> Au { - if let Some(h) = self.height { - return h; - } - Self::get_viewport_size(context).height - } - - // Merge the result of a subsequent lookup, preferring the initial result. - fn merge(self, new_result: Self) -> Self { - let mut result = self; - if let Some(width) = new_result.width { - result.width.get_or_insert(width); - } - if let Some(height) = new_result.height { - result.height.get_or_insert(height); - } - result - } - - fn is_complete(&self) -> bool { - self.width.is_some() && self.height.is_some() - } -} - -/// Unevaluated lazy container size query. -pub enum ContainerSizeQuery<'a> { - /// Query prior to evaluation. - NotEvaluated(Box<dyn Fn() -> ContainerSizeQueryResult + 'a>), - /// Cached evaluated result. - Evaluated(ContainerSizeQueryResult), -} - -impl<'a> ContainerSizeQuery<'a> { - fn evaluate_potential_size_container<E>( - e: E, - originating_element_style: Option<&ComputedValues>, - ) -> TraversalResult<ContainerSizeQueryResult> - where - E: TElement, - { - let data; - let style = match originating_element_style { - Some(s) => s, - None => { - data = match e.borrow_data() { - Some(d) => d, - None => return TraversalResult::InProgress, - }; - &**data.styles.primary() - }, - }; - if !style - .flags - .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE) - { - // We know we won't find a size container. - return TraversalResult::StopTraversal; - } - - let wm = style.writing_mode; - let box_style = style.get_box(); - - let container_type = box_style.clone_container_type(); - let size = e.query_container_size(&box_style.clone_display()); - match container_type { - ContainerType::Size => TraversalResult::Done(ContainerSizeQueryResult { - width: size.width, - height: size.height, - }), - ContainerType::InlineSize => { - if wm.is_horizontal() { - TraversalResult::Done(ContainerSizeQueryResult { - width: size.width, - height: None, - }) - } else { - TraversalResult::Done(ContainerSizeQueryResult { - width: None, - height: size.height, - }) - } - }, - ContainerType::Normal => TraversalResult::InProgress, - } - } - - /// Find the query container size for a given element. Meant to be used as a callback for new(). - fn lookup<E>( - element: E, - originating_element_style: Option<&ComputedValues>, - ) -> ContainerSizeQueryResult - where - E: TElement + 'a, - { - match traverse_container( - element, - originating_element_style, - |e, originating_element_style| { - Self::evaluate_potential_size_container(e, originating_element_style) - }, - ) { - Some((container, result)) => { - if result.is_complete() { - result - } else { - // Traverse up from the found size container to see if we can get a complete containment. - result.merge(Self::lookup(container, None)) - } - }, - None => ContainerSizeQueryResult::default(), - } - } - - /// Create a new instance of the container size query for given element, with a deferred lookup callback. - pub fn for_element<E>(element: E, originating_element_style: Option<&'a ComputedValues>) -> Self - where - E: TElement + 'a, - { - let parent; - let data; - let style = match originating_element_style { - Some(s) => Some(s), - None => { - // No need to bother if we're the top element. - parent = match element.traversal_parent() { - Some(parent) => parent, - None => return Self::none(), - }; - data = parent.borrow_data(); - data.as_ref().map(|data| &**data.styles.primary()) - }, - }; - let should_traverse = match style { - Some(style) => style - .flags - .contains(ComputedValueFlags::SELF_OR_ANCESTOR_HAS_SIZE_CONTAINER_TYPE), - None => true, // `display: none`, still want to show a correct computed value, so give it a try. - }; - if should_traverse { - return Self::NotEvaluated(Box::new(move || { - Self::lookup(element, originating_element_style) - })); - } - Self::none() - } - - /// Create a new instance, but with optional element. - pub fn for_option_element<E>( - element: Option<E>, - originating_element_style: Option<&'a ComputedValues>, - ) -> Self - where - E: TElement + 'a, - { - if let Some(e) = element { - Self::for_element(e, originating_element_style) - } else { - Self::none() - } - } - - /// Create a query that evaluates to empty, for cases where container size query is not required. - pub fn none() -> Self { - ContainerSizeQuery::Evaluated(ContainerSizeQueryResult::default()) - } - - /// Get the result of the container size query, doing the lookup if called for the first time. - pub fn get(&mut self) -> ContainerSizeQueryResult { - match self { - Self::NotEvaluated(lookup) => { - *self = Self::Evaluated((lookup)()); - match self { - Self::Evaluated(info) => *info, - _ => unreachable!("Just evaluated but not set?"), - } - }, - Self::Evaluated(info) => *info, - } - } -} diff --git a/components/style/stylesheets/counter_style_rule.rs b/components/style/stylesheets/counter_style_rule.rs deleted file mode 100644 index 974b76b8060..00000000000 --- a/components/style/stylesheets/counter_style_rule.rs +++ /dev/null @@ -1,7 +0,0 @@ -/* 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/. */ - -#![allow(missing_docs)] - -pub use crate::counter_style::CounterStyleRuleData as CounterStyleRule; diff --git a/components/style/stylesheets/document_rule.rs b/components/style/stylesheets/document_rule.rs deleted file mode 100644 index 75edab308db..00000000000 --- a/components/style/stylesheets/document_rule.rs +++ /dev/null @@ -1,305 +0,0 @@ -/* 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/. */ - -//! [@document rules](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document) -//! initially in CSS Conditional Rules Module Level 3, @document has been postponed to the level 4. -//! We implement the prefixed `@-moz-document`. - -use crate::media_queries::Device; -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::CssRules; -use crate::values::CssUrl; -use cssparser::{BasicParseErrorKind, Parser, SourceLocation}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -#[derive(Debug, ToShmem)] -/// A @-moz-document rule -pub struct DocumentRule { - /// The parsed condition - pub condition: DocumentCondition, - /// Child rules - pub rules: Arc<Locked<CssRules>>, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl DocumentRule { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - // Measurement of other fields may be added later. - self.rules.unconditional_shallow_size_of(ops) + - self.rules.read_with(guard).size_of(guard, ops) - } -} - -impl ToCssWithGuard for DocumentRule { - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@-moz-document ")?; - self.condition.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" {")?; - for rule in self.rules.read_with(guard).0.iter() { - dest.write_char(' ')?; - rule.to_css(guard, dest)?; - } - dest.write_str(" }") - } -} - -impl DeepCloneWithLock for DocumentRule { - /// Deep clones this DocumentRule. - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - let rules = self.rules.read_with(guard); - DocumentRule { - condition: self.condition.clone(), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), - source_location: self.source_location.clone(), - } - } -} - -/// The kind of media document that the rule will match. -#[derive(Clone, Copy, Debug, Parse, PartialEq, ToCss, ToShmem)] -#[allow(missing_docs)] -pub enum MediaDocumentKind { - All, - Plugin, - Image, - Video, -} - -/// A matching function for a `@document` rule's condition. -#[derive(Clone, Debug, ToCss, ToShmem)] -pub enum DocumentMatchingFunction { - /// Exact URL matching function. It evaluates to true whenever the - /// URL of the document being styled is exactly the URL given. - Url(CssUrl), - /// URL prefix matching function. It evaluates to true whenever the - /// URL of the document being styled has the argument to the - /// function as an initial substring (which is true when the two - /// strings are equal). When the argument is the empty string, - /// it evaluates to true for all documents. - #[css(function)] - UrlPrefix(String), - /// Domain matching function. It evaluates to true whenever the URL - /// of the document being styled has a host subcomponent and that - /// host subcomponent is exactly the argument to the ‘domain()’ - /// function or a final substring of the host component is a - /// period (U+002E) immediately followed by the argument to the - /// ‘domain()’ function. - #[css(function)] - Domain(String), - /// Regular expression matching function. It evaluates to true - /// whenever the regular expression matches the entirety of the URL - /// of the document being styled. - #[css(function)] - Regexp(String), - /// Matching function for a media document. - #[css(function)] - MediaDocument(MediaDocumentKind), - /// Matching function for a plain-text document. - #[css(function)] - PlainTextDocument(()), - /// Matching function for a document that can be observed by other content - /// documents. - #[css(function)] - UnobservableDocument(()), -} - -macro_rules! parse_quoted_or_unquoted_string { - ($input:ident, $url_matching_function:expr) => { - $input.parse_nested_block(|input| { - let start = input.position(); - input - .parse_entirely(|input| { - let string = input.expect_string()?; - Ok($url_matching_function(string.as_ref().to_owned())) - }) - .or_else(|_: ParseError| { - while let Ok(_) = input.next() {} - Ok($url_matching_function(input.slice_from(start).to_string())) - }) - }) - }; -} - -impl DocumentMatchingFunction { - /// Parse a URL matching function for a`@document` rule's condition. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(url) = input.try_parse(|input| CssUrl::parse(context, input)) { - return Ok(DocumentMatchingFunction::Url(url)); - } - - let location = input.current_source_location(); - let function = input.expect_function()?.clone(); - match_ignore_ascii_case! { &function, - "url-prefix" => { - parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::UrlPrefix) - }, - "domain" => { - parse_quoted_or_unquoted_string!(input, DocumentMatchingFunction::Domain) - }, - "regexp" => { - input.parse_nested_block(|input| { - Ok(DocumentMatchingFunction::Regexp( - input.expect_string()?.as_ref().to_owned(), - )) - }) - }, - "media-document" => { - input.parse_nested_block(|input| { - let kind = MediaDocumentKind::parse(input)?; - Ok(DocumentMatchingFunction::MediaDocument(kind)) - }) - }, - - "plain-text-document" => { - input.parse_nested_block(|input| { - input.expect_exhausted()?; - Ok(DocumentMatchingFunction::PlainTextDocument(())) - }) - }, - - "unobservable-document" => { - input.parse_nested_block(|input| { - input.expect_exhausted()?; - Ok(DocumentMatchingFunction::UnobservableDocument(())) - }) - }, - - _ => { - Err(location.new_custom_error( - StyleParseErrorKind::UnexpectedFunction(function.clone()) - )) - }, - } - } - - #[cfg(feature = "gecko")] - /// Evaluate a URL matching function. - pub fn evaluate(&self, device: &Device) -> bool { - use crate::gecko_bindings::bindings::Gecko_DocumentRule_UseForPresentation; - use crate::gecko_bindings::structs::DocumentMatchingFunction as GeckoDocumentMatchingFunction; - use nsstring::nsCStr; - - let func = match *self { - DocumentMatchingFunction::Url(_) => GeckoDocumentMatchingFunction::URL, - DocumentMatchingFunction::UrlPrefix(_) => GeckoDocumentMatchingFunction::URLPrefix, - DocumentMatchingFunction::Domain(_) => GeckoDocumentMatchingFunction::Domain, - DocumentMatchingFunction::Regexp(_) => GeckoDocumentMatchingFunction::RegExp, - DocumentMatchingFunction::MediaDocument(_) => { - GeckoDocumentMatchingFunction::MediaDocument - }, - DocumentMatchingFunction::PlainTextDocument(..) => { - GeckoDocumentMatchingFunction::PlainTextDocument - }, - DocumentMatchingFunction::UnobservableDocument(..) => { - GeckoDocumentMatchingFunction::UnobservableDocument - }, - }; - - let pattern = nsCStr::from(match *self { - DocumentMatchingFunction::Url(ref url) => url.as_str(), - DocumentMatchingFunction::UrlPrefix(ref pat) | - DocumentMatchingFunction::Domain(ref pat) | - DocumentMatchingFunction::Regexp(ref pat) => pat, - DocumentMatchingFunction::MediaDocument(kind) => match kind { - MediaDocumentKind::All => "all", - MediaDocumentKind::Image => "image", - MediaDocumentKind::Plugin => "plugin", - MediaDocumentKind::Video => "video", - }, - DocumentMatchingFunction::PlainTextDocument(()) | - DocumentMatchingFunction::UnobservableDocument(()) => "", - }); - unsafe { Gecko_DocumentRule_UseForPresentation(device.document(), &*pattern, func) } - } - - #[cfg(not(feature = "gecko"))] - /// Evaluate a URL matching function. - pub fn evaluate(&self, _: &Device) -> bool { - false - } -} - -/// A `@document` rule's condition. -/// -/// <https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document> -/// -/// The `@document` rule's condition is written as a comma-separated list of -/// URL matching functions, and the condition evaluates to true whenever any -/// one of those functions evaluates to true. -#[derive(Clone, Debug, ToCss, ToShmem)] -#[css(comma)] -pub struct DocumentCondition(#[css(iterable)] Vec<DocumentMatchingFunction>); - -impl DocumentCondition { - /// Parse a document condition. - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let conditions = - input.parse_comma_separated(|input| DocumentMatchingFunction::parse(context, input))?; - - let condition = DocumentCondition(conditions); - if !condition.allowed_in(context) { - return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid("-moz-document".into()))); - } - Ok(condition) - } - - /// Evaluate a document condition. - pub fn evaluate(&self, device: &Device) -> bool { - self.0 - .iter() - .any(|url_matching_function| url_matching_function.evaluate(device)) - } - - #[cfg(feature = "servo")] - fn allowed_in(&self, _: &ParserContext) -> bool { - false - } - - #[cfg(feature = "gecko")] - fn allowed_in(&self, context: &ParserContext) -> bool { - use static_prefs::pref; - - if context.in_ua_or_chrome_sheet() { - return true; - } - - if pref!("layout.css.moz-document.content.enabled") { - return true; - } - - // Allow a single url-prefix() for compatibility. - // - // See bug 1446470 and dependencies. - if self.0.len() != 1 { - return false; - } - - // NOTE(emilio): This technically allows url-prefix("") too, but... - match self.0[0] { - DocumentMatchingFunction::UrlPrefix(ref prefix) => prefix.is_empty(), - _ => false, - } - } -} diff --git a/components/style/stylesheets/font_feature_values_rule.rs b/components/style/stylesheets/font_feature_values_rule.rs deleted file mode 100644 index 06016ec2bd9..00000000000 --- a/components/style/stylesheets/font_feature_values_rule.rs +++ /dev/null @@ -1,490 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The [`@font-feature-values`][font-feature-values] at-rule. -//! -//! [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule - -use crate::error_reporting::ContextualParseError; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet, nsTArray}; -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::CssRuleType; -use crate::values::computed::font::FamilyName; -use crate::values::serialize_atom_identifier; -use crate::Atom; -use cssparser::{ - AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, Parser, ParserState, - QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, Token, -}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// A @font-feature-values block declaration. -/// It is `<ident>: <integer>+`. -/// This struct can take 3 value types. -/// - `SingleValue` is to keep just one unsigned integer value. -/// - `PairValues` is to keep one or two unsigned integer values. -/// - `VectorValues` is to keep a list of unsigned integer values. -#[derive(Clone, Debug, PartialEq, ToShmem)] -pub struct FFVDeclaration<T> { - /// An `<ident>` for declaration name. - pub name: Atom, - /// An `<integer>+` for declaration value. - pub value: T, -} - -impl<T: ToCss> ToCss for FFVDeclaration<T> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.name, dest)?; - dest.write_str(": ")?; - self.value.to_css(dest)?; - dest.write_char(';') - } -} - -/// A trait for @font-feature-values rule to gecko values conversion. -#[cfg(feature = "gecko")] -pub trait ToGeckoFontFeatureValues { - /// Sets the equivalent of declaration to gecko `nsTArray<u32>` array. - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>); -} - -/// A @font-feature-values block declaration value that keeps one value. -#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] -pub struct SingleValue(pub u32); - -impl Parse for SingleValue { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<SingleValue, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::Number { - int_value: Some(v), .. - } if v >= 0 => Ok(SingleValue(v as u32)), - ref t => Err(location.new_unexpected_token_error(t.clone())), - } - } -} - -#[cfg(feature = "gecko")] -impl ToGeckoFontFeatureValues for SingleValue { - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { - unsafe { - array.set_len_pod(1); - } - array[0] = self.0 as u32; - } -} - -/// A @font-feature-values block declaration value that keeps one or two values. -#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] -pub struct PairValues(pub u32, pub Option<u32>); - -impl Parse for PairValues { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<PairValues, ParseError<'i>> { - let location = input.current_source_location(); - let first = match *input.next()? { - Token::Number { - int_value: Some(a), .. - } if a >= 0 => a as u32, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }; - let location = input.current_source_location(); - match input.next() { - Ok(&Token::Number { - int_value: Some(b), .. - }) if b >= 0 => Ok(PairValues(first, Some(b as u32))), - // It can't be anything other than number. - Ok(t) => Err(location.new_unexpected_token_error(t.clone())), - // It can be just one value. - Err(_) => Ok(PairValues(first, None)), - } - } -} - -#[cfg(feature = "gecko")] -impl ToGeckoFontFeatureValues for PairValues { - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { - let len = if self.1.is_some() { 2 } else { 1 }; - - unsafe { - array.set_len_pod(len); - } - array[0] = self.0 as u32; - if let Some(second) = self.1 { - array[1] = second as u32; - }; - } -} - -/// A @font-feature-values block declaration value that keeps a list of values. -#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] -pub struct VectorValues(#[css(iterable)] pub Vec<u32>); - -impl Parse for VectorValues { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<VectorValues, ParseError<'i>> { - let mut vec = vec![]; - loop { - let location = input.current_source_location(); - match input.next() { - Ok(&Token::Number { - int_value: Some(a), .. - }) if a >= 0 => { - vec.push(a as u32); - }, - // It can't be anything other than number. - Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), - Err(_) => break, - } - } - - if vec.len() == 0 { - return Err(input.new_error(BasicParseErrorKind::EndOfInput)); - } - - Ok(VectorValues(vec)) - } -} - -#[cfg(feature = "gecko")] -impl ToGeckoFontFeatureValues for VectorValues { - fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) { - array.assign_from_iter_pod(self.0.iter().map(|v| *v)); - } -} - -/// Parses a list of `FamilyName`s. -pub fn parse_family_name_list<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<Vec<FamilyName>, ParseError<'i>> { - input - .parse_comma_separated(|i| FamilyName::parse(context, i)) - .map_err(|e| e.into()) -} - -/// @font-feature-values inside block parser. Parses a list of `FFVDeclaration`. -/// (`<ident>: <integer>+`) -struct FFVDeclarationsParser<'a, 'b: 'a, T: 'a> { - context: &'a ParserContext<'b>, - declarations: &'a mut Vec<FFVDeclaration<T>>, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i, T> AtRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i, T> QualifiedRuleParser<'i> for FFVDeclarationsParser<'a, 'b, T> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i, T> DeclarationParser<'i> for FFVDeclarationsParser<'a, 'b, T> -where - T: Parse, -{ - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - let value = input.parse_entirely(|i| T::parse(self.context, i))?; - let new = FFVDeclaration { - name: Atom::from(&*name), - value, - }; - update_or_push(&mut self.declarations, new); - Ok(()) - } -} - -impl<'a, 'b, 'i, T> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for FFVDeclarationsParser<'a, 'b, T> -where - T: Parse, -{ - fn parse_declarations(&self) -> bool { - true - } - fn parse_qualified(&self) -> bool { - false - } -} - -macro_rules! font_feature_values_blocks { - ( - blocks = [ - $( #[$doc: meta] $name: tt $ident: ident / $ident_camel: ident / $gecko_enum: ident: $ty: ty, )* - ] - ) => { - /// The [`@font-feature-values`][font-feature-values] at-rule. - /// - /// [font-feature-values]: https://drafts.csswg.org/css-fonts-3/#at-font-feature-values-rule - #[derive(Clone, Debug, PartialEq, ToShmem)] - pub struct FontFeatureValuesRule { - /// Font family list for @font-feature-values rule. - /// Family names cannot contain generic families. FamilyName - /// also accepts only non-generic names. - pub family_names: Vec<FamilyName>, - $( - #[$doc] - pub $ident: Vec<FFVDeclaration<$ty>>, - )* - /// The line and column of the rule's source code. - pub source_location: SourceLocation, - } - - impl FontFeatureValuesRule { - /// Creates an empty FontFeatureValuesRule with given location and family name list. - fn new(family_names: Vec<FamilyName>, location: SourceLocation) -> Self { - FontFeatureValuesRule { - family_names: family_names, - $( - $ident: vec![], - )* - source_location: location, - } - } - - /// Parses a `FontFeatureValuesRule`. - pub fn parse( - context: &ParserContext, - input: &mut Parser, - family_names: Vec<FamilyName>, - location: SourceLocation, - ) -> Self { - let mut rule = FontFeatureValuesRule::new(family_names, location); - let mut parser = FontFeatureValuesRuleParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(result) = iter.next() { - if let Err((error, slice)) = result { - let location = error.location; - let error = ContextualParseError::UnsupportedRule(slice, error); - context.log_css_error(location, error); - } - } - rule - } - - /// Prints inside of `@font-feature-values` block. - pub fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - $( - if self.$ident.len() > 0 { - dest.write_str(concat!("@", $name, " {\n"))?; - let iter = self.$ident.iter(); - for val in iter { - val.to_css(dest)?; - dest.write_str("\n")? - } - dest.write_str("}\n")? - } - )* - Ok(()) - } - - /// Returns length of all at-rules. - pub fn len(&self) -> usize { - let mut len = 0; - $( - len += self.$ident.len(); - )* - len - } - - /// Convert to Gecko gfxFontFeatureValueSet. - #[cfg(feature = "gecko")] - pub fn set_at_rules(&self, dest: *mut gfxFontFeatureValueSet) { - for ref family in self.family_names.iter() { - let family = family.name.to_ascii_lowercase(); - $( - if self.$ident.len() > 0 { - for val in self.$ident.iter() { - let array = unsafe { - Gecko_AppendFeatureValueHashEntry( - dest, - family.as_ptr(), - structs::$gecko_enum, - val.name.as_ptr() - ) - }; - unsafe { - val.value.to_gecko_font_feature_values(&mut *array); - } - } - } - )* - } - } - } - - impl ToCssWithGuard for FontFeatureValuesRule { - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@font-feature-values ")?; - self.family_names.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" {\n")?; - self.value_to_css(&mut CssWriter::new(dest))?; - dest.write_char('}') - } - } - - /// Updates with new value if same `ident` exists, otherwise pushes to the vector. - fn update_or_push<T>(vec: &mut Vec<FFVDeclaration<T>>, element: FFVDeclaration<T>) { - if let Some(item) = vec.iter_mut().find(|item| item.name == element.name) { - item.value = element.value; - } else { - vec.push(element); - } - } - - /// Keeps the information about block type like @swash, @styleset etc. - enum BlockType { - $( - $ident_camel, - )* - } - - /// Parser for `FontFeatureValuesRule`. Parses all blocks - /// <feature-type> { - /// <feature-value-declaration-list> - /// } - /// <feature-type> = @stylistic | @historical-forms | @styleset | - /// @character-variant | @swash | @ornaments | @annotation - struct FontFeatureValuesRuleParser<'a> { - context: &'a ParserContext<'a>, - rule: &'a mut FontFeatureValuesRule, - } - - /// Default methods reject all qualified rules. - impl<'a, 'i> QualifiedRuleParser<'i> for FontFeatureValuesRuleParser<'a> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; - } - - impl<'a, 'i> AtRuleParser<'i> for FontFeatureValuesRuleParser<'a> { - type Prelude = BlockType; - type AtRule = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_prelude<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<BlockType, ParseError<'i>> { - match_ignore_ascii_case! { &*name, - $( - $name => Ok(BlockType::$ident_camel), - )* - _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), - } - } - - fn parse_block<'t>( - &mut self, - prelude: BlockType, - _: &ParserState, - input: &mut Parser<'i, 't> - ) -> Result<Self::AtRule, ParseError<'i>> { - debug_assert!(self.context.rule_types().contains(CssRuleType::FontFeatureValues)); - match prelude { - $( - BlockType::$ident_camel => { - let mut parser = FFVDeclarationsParser { - context: &self.context, - declarations: &mut self.rule.$ident, - }; - - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if let Err((error, slice)) = declaration { - let location = error.location; - let error = ContextualParseError::UnsupportedKeyframePropertyDeclaration( - slice, error - ); - self.context.log_css_error(location, error); - } - } - }, - )* - } - - Ok(()) - } - } - - impl<'a, 'i> DeclarationParser<'i> for FontFeatureValuesRuleParser<'a> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - } - - impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> for FontFeatureValuesRuleParser<'a> { - fn parse_declarations(&self) -> bool { false } - fn parse_qualified(&self) -> bool { true } - } - } -} - -font_feature_values_blocks! { - blocks = [ - #[doc = "A @swash blocksck. \ - Specifies a feature name that will work with the swash() \ - functional notation of font-variant-alternates."] - "swash" swash / Swash / NS_FONT_VARIANT_ALTERNATES_SWASH: SingleValue, - - #[doc = "A @stylistic block. \ - Specifies a feature name that will work with the annotation() \ - functional notation of font-variant-alternates."] - "stylistic" stylistic / Stylistic / NS_FONT_VARIANT_ALTERNATES_STYLISTIC: SingleValue, - - #[doc = "A @ornaments block. \ - Specifies a feature name that will work with the ornaments() ] \ - functional notation of font-variant-alternates."] - "ornaments" ornaments / Ornaments / NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: SingleValue, - - #[doc = "A @annotation block. \ - Specifies a feature name that will work with the stylistic() \ - functional notation of font-variant-alternates."] - "annotation" annotation / Annotation / NS_FONT_VARIANT_ALTERNATES_ANNOTATION: SingleValue, - - #[doc = "A @character-variant block. \ - Specifies a feature name that will work with the styleset() \ - functional notation of font-variant-alternates. The value can be a pair."] - "character-variant" character_variant / CharacterVariant / NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT: - PairValues, - - #[doc = "A @styleset block. \ - Specifies a feature name that will work with the character-variant() \ - functional notation of font-variant-alternates. The value can be a list."] - "styleset" styleset / Styleset / NS_FONT_VARIANT_ALTERNATES_STYLESET: VectorValues, - ] -} diff --git a/components/style/stylesheets/font_palette_values_rule.rs b/components/style/stylesheets/font_palette_values_rule.rs deleted file mode 100644 index c604f9b3c6e..00000000000 --- a/components/style/stylesheets/font_palette_values_rule.rs +++ /dev/null @@ -1,268 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The [`@font-palette-values`][font-palette-values] at-rule. -//! -//! [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values - -use crate::error_reporting::ContextualParseError; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::{ - bindings::Gecko_AppendPaletteValueHashEntry, - bindings::{Gecko_SetFontPaletteBase, Gecko_SetFontPaletteOverride}, - structs::gfx::FontPaletteValueSet, - structs::gfx::FontPaletteValueSet_PaletteValues_kDark, - structs::gfx::FontPaletteValueSet_PaletteValues_kLight, -}; -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::font_feature_values_rule::parse_family_name_list; -use crate::values::computed::font::FamilyName; -use crate::values::specified::Color as SpecifiedColor; -use crate::values::specified::NonNegativeInteger; -use crate::values::DashedIdent; -use cssparser::{ - AtRuleParser, CowRcStr, DeclarationParser, Parser, QualifiedRuleParser, RuleBodyItemParser, - RuleBodyParser, SourceLocation, -}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use style_traits::{Comma, OneOrMoreSeparated}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -#[allow(missing_docs)] -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct FontPaletteOverrideColor { - index: NonNegativeInteger, - color: SpecifiedColor, -} - -impl Parse for FontPaletteOverrideColor { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<FontPaletteOverrideColor, ParseError<'i>> { - let index = NonNegativeInteger::parse(context, input)?; - let location = input.current_source_location(); - let color = SpecifiedColor::parse(context, input)?; - // Only absolute colors are accepted here. - if let SpecifiedColor::Absolute { .. } = color { - Ok(FontPaletteOverrideColor { index, color }) - } else { - Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -impl ToCss for FontPaletteOverrideColor { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.index.to_css(dest)?; - dest.write_char(' ')?; - self.color.to_css(dest) - } -} - -impl OneOrMoreSeparated for FontPaletteOverrideColor { - type S = Comma; -} - -impl OneOrMoreSeparated for FamilyName { - type S = Comma; -} - -#[allow(missing_docs)] -#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -pub enum FontPaletteBase { - Light, - Dark, - Index(NonNegativeInteger), -} - -/// The [`@font-palette-values`][font-palette-values] at-rule. -/// -/// [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values -#[derive(Clone, Debug, PartialEq, ToShmem)] -pub struct FontPaletteValuesRule { - /// Palette name. - pub name: DashedIdent, - /// Font family list for @font-palette-values rule. - /// Family names cannot contain generic families. FamilyName - /// also accepts only non-generic names. - pub family_names: Vec<FamilyName>, - /// The base palette. - pub base_palette: Option<FontPaletteBase>, - /// The list of override colors. - pub override_colors: Vec<FontPaletteOverrideColor>, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl FontPaletteValuesRule { - /// Creates an empty FontPaletteValuesRule with given location and name. - fn new(name: DashedIdent, location: SourceLocation) -> Self { - FontPaletteValuesRule { - name, - family_names: vec![], - base_palette: None, - override_colors: vec![], - source_location: location, - } - } - - /// Parses a `FontPaletteValuesRule`. - pub fn parse( - context: &ParserContext, - input: &mut Parser, - name: DashedIdent, - location: SourceLocation, - ) -> Self { - let mut rule = FontPaletteValuesRule::new(name, location); - let mut parser = FontPaletteValuesDeclarationParser { - context, - rule: &mut rule, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - if let Err((error, slice)) = declaration { - let location = error.location; - let error = - ContextualParseError::UnsupportedFontPaletteValuesDescriptor(slice, error); - context.log_css_error(location, error); - } - } - rule - } - - /// Prints inside of `@font-palette-values` block. - fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if !self.family_names.is_empty() { - dest.write_str("font-family: ")?; - self.family_names.to_css(dest)?; - dest.write_str("; ")?; - } - if let Some(base) = &self.base_palette { - dest.write_str("base-palette: ")?; - base.to_css(dest)?; - dest.write_str("; ")?; - } - if !self.override_colors.is_empty() { - dest.write_str("override-colors: ")?; - self.override_colors.to_css(dest)?; - dest.write_str("; ")?; - } - Ok(()) - } - - /// Convert to Gecko FontPaletteValueSet. - #[cfg(feature = "gecko")] - pub fn to_gecko_palette_value_set(&self, dest: *mut FontPaletteValueSet) { - for ref family in self.family_names.iter() { - let family = family.name.to_ascii_lowercase(); - let palette_values = unsafe { - Gecko_AppendPaletteValueHashEntry(dest, family.as_ptr(), self.name.0.as_ptr()) - }; - if let Some(base_palette) = &self.base_palette { - unsafe { - Gecko_SetFontPaletteBase( - palette_values, - match &base_palette { - FontPaletteBase::Light => FontPaletteValueSet_PaletteValues_kLight, - FontPaletteBase::Dark => FontPaletteValueSet_PaletteValues_kDark, - FontPaletteBase::Index(i) => i.0.value() as i32, - }, - ); - } - } - for c in &self.override_colors { - if let SpecifiedColor::Absolute(ref absolute) = c.color { - unsafe { - Gecko_SetFontPaletteOverride( - palette_values, - c.index.0.value(), - (&absolute.color) as *const _ as *mut _, - ); - } - } - } - } - } -} - -impl ToCssWithGuard for FontPaletteValuesRule { - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@font-palette-values ")?; - self.name.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" { ")?; - self.value_to_css(&mut CssWriter::new(dest))?; - dest.write_char('}') - } -} - -/// Parser for declarations in `FontPaletteValuesRule`. -struct FontPaletteValuesDeclarationParser<'a> { - context: &'a ParserContext<'a>, - rule: &'a mut FontPaletteValuesRule, -} - -impl<'a, 'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -fn parse_override_colors<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<Vec<FontPaletteOverrideColor>, ParseError<'i>> { - input.parse_comma_separated(|i| FontPaletteOverrideColor::parse(context, i)) -} - -impl<'a, 'b, 'i> DeclarationParser<'i> for FontPaletteValuesDeclarationParser<'a> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - match_ignore_ascii_case! { &*name, - "font-family" => { - self.rule.family_names = parse_family_name_list(self.context, input)? - }, - "base-palette" => { - self.rule.base_palette = Some(input.parse_entirely(|i| FontPaletteBase::parse(self.context, i))?) - }, - "override-colors" => { - self.rule.override_colors = parse_override_colors(self.context, input)? - }, - _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), - } - Ok(()) - } -} - -impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for FontPaletteValuesDeclarationParser<'a> -{ - fn parse_declarations(&self) -> bool { - true - } - fn parse_qualified(&self) -> bool { - false - } -} diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs deleted file mode 100644 index e7ea2748465..00000000000 --- a/components/style/stylesheets/import_rule.rs +++ /dev/null @@ -1,332 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The [`@import`][import] at-rule. -//! -//! [import]: https://drafts.csswg.org/css-cascade-3/#at-import - -use crate::media_queries::MediaList; -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{ - DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, -}; -use crate::str::CssStringWriter; -use crate::stylesheets::{ - layer_rule::LayerName, supports_rule::SupportsCondition, CssRule, CssRuleType, - StylesheetInDocument, -}; -use crate::values::CssUrl; -use cssparser::{Parser, SourceLocation}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -/// A sheet that is held from an import rule. -#[cfg(feature = "gecko")] -#[derive(Debug)] -pub enum ImportSheet { - /// A bonafide stylesheet. - Sheet(crate::gecko::data::GeckoStyleSheet), - - /// An @import created while parsing off-main-thread, whose Gecko sheet has - /// yet to be created and attached. - Pending, - - /// An @import created with a false <supports-condition>, so will never be fetched. - Refused, -} - -#[cfg(feature = "gecko")] -impl ImportSheet { - /// Creates a new ImportSheet from a GeckoStyleSheet. - pub fn new(sheet: crate::gecko::data::GeckoStyleSheet) -> Self { - ImportSheet::Sheet(sheet) - } - - /// Creates a pending ImportSheet for a load that has not started yet. - pub fn new_pending() -> Self { - ImportSheet::Pending - } - - /// Creates a refused ImportSheet for a load that will not happen. - pub fn new_refused() -> Self { - ImportSheet::Refused - } - - /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it - /// exists. - pub fn as_sheet(&self) -> Option<&crate::gecko::data::GeckoStyleSheet> { - match *self { - ImportSheet::Sheet(ref s) => { - debug_assert!(!s.hack_is_null()); - if s.hack_is_null() { - return None; - } - Some(s) - }, - ImportSheet::Refused | ImportSheet::Pending => None, - } - } - - /// Returns the media list for this import rule. - pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.as_sheet().and_then(|s| s.media(guard)) - } - - /// Returns the rule list for this import rule. - pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { - match self.as_sheet() { - Some(s) => s.rules(guard), - None => &[], - } - } -} - -#[cfg(feature = "gecko")] -impl DeepCloneWithLock for ImportSheet { - fn deep_clone_with_lock( - &self, - _lock: &SharedRwLock, - _guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - use crate::gecko::data::GeckoStyleSheet; - use crate::gecko_bindings::bindings; - match *self { - ImportSheet::Sheet(ref s) => { - let clone = unsafe { - bindings::Gecko_StyleSheet_Clone(s.raw() as *const _, params.reference_sheet) - }; - ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) }) - }, - ImportSheet::Pending => ImportSheet::Pending, - ImportSheet::Refused => ImportSheet::Refused, - } - } -} - -/// A sheet that is held from an import rule. -#[cfg(feature = "servo")] -#[derive(Debug)] -pub enum ImportSheet { - /// A bonafide stylesheet. - Sheet(::servo_arc::Arc<crate::stylesheets::Stylesheet>), - - /// An @import created with a false <supports-condition>, so will never be fetched. - Refused, -} - -#[cfg(feature = "servo")] -impl ImportSheet { - /// Creates a new ImportSheet from a stylesheet. - pub fn new(sheet: ::servo_arc::Arc<crate::stylesheets::Stylesheet>) -> Self { - ImportSheet::Sheet(sheet) - } - - /// Creates a refused ImportSheet for a load that will not happen. - pub fn new_refused() -> Self { - ImportSheet::Refused - } - - /// Returns a reference to the stylesheet in this ImportSheet, if it exists. - pub fn as_sheet(&self) -> Option<&::servo_arc::Arc<crate::stylesheets::Stylesheet>> { - match *self { - ImportSheet::Sheet(ref s) => Some(s), - ImportSheet::Refused => None, - } - } - - /// Returns the media list for this import rule. - pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.as_sheet().and_then(|s| s.media(guard)) - } - - /// Returns the rules for this import rule. - pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { - match self.as_sheet() { - Some(s) => s.rules(guard), - None => &[], - } - } -} - -#[cfg(feature = "servo")] -impl DeepCloneWithLock for ImportSheet { - fn deep_clone_with_lock( - &self, - _lock: &SharedRwLock, - _guard: &SharedRwLockReadGuard, - _params: &DeepCloneParams, - ) -> Self { - match *self { - ImportSheet::Sheet(ref s) => { - use servo_arc::Arc; - ImportSheet::Sheet(Arc::new((&**s).clone())) - }, - ImportSheet::Refused => ImportSheet::Refused, - } - } -} - -/// The layer specified in an import rule (can be none, anonymous, or named). -#[derive(Debug, Clone)] -pub enum ImportLayer { - /// No layer specified - None, - - /// Anonymous layer (`layer`) - Anonymous, - - /// Named layer (`layer(name)`) - Named(LayerName), -} - -/// The supports condition in an import rule. -#[derive(Debug, Clone)] -pub struct ImportSupportsCondition { - /// The supports condition. - pub condition: SupportsCondition, - - /// If the import is enabled, from the result of the import condition. - pub enabled: bool, -} - -impl ToCss for ImportLayer { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - ImportLayer::None => Ok(()), - ImportLayer::Anonymous => dest.write_str("layer"), - ImportLayer::Named(ref name) => { - dest.write_str("layer(")?; - name.to_css(dest)?; - dest.write_char(')') - }, - } - } -} - -/// The [`@import`][import] at-rule. -/// -/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import -#[derive(Debug)] -pub struct ImportRule { - /// The `<url>` this `@import` rule is loading. - pub url: CssUrl, - - /// The stylesheet is always present. However, in the case of gecko async - /// parsing, we don't actually have a Gecko sheet at first, and so the - /// ImportSheet just has stub behavior until it appears. - pub stylesheet: ImportSheet, - - /// A <supports-condition> for the rule. - pub supports: Option<ImportSupportsCondition>, - - /// A `layer()` function name. - pub layer: ImportLayer, - - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl ImportRule { - /// Parses the layer() / layer / supports() part of the import header, as per - /// https://drafts.csswg.org/css-cascade-5/#at-import: - /// - /// [ layer | layer(<layer-name>) ]? - /// [ supports([ <supports-condition> | <declaration> ]) ]? - /// - /// We do this here so that the import preloader can look at this without having to parse the - /// whole import rule or parse the media query list or what not. - pub fn parse_layer_and_supports<'i, 't>( - input: &mut Parser<'i, 't>, - context: &mut ParserContext, - ) -> (ImportLayer, Option<ImportSupportsCondition>) { - let layer = if input - .try_parse(|input| input.expect_ident_matching("layer")) - .is_ok() - { - ImportLayer::Anonymous - } else { - input - .try_parse(|input| { - input.expect_function_matching("layer")?; - input - .parse_nested_block(|input| LayerName::parse(context, input)) - .map(|name| ImportLayer::Named(name)) - }) - .ok() - .unwrap_or(ImportLayer::None) - }; - - let supports = if !static_prefs::pref!("layout.css.import-supports.enabled") { - None - } else { - input - .try_parse(SupportsCondition::parse_for_import) - .map(|condition| { - let enabled = context - .nest_for_rule(CssRuleType::Style, |context| condition.eval(context)); - ImportSupportsCondition { condition, enabled } - }) - .ok() - }; - - (layer, supports) - } -} - -impl ToShmem for ImportRule { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - Err(String::from( - "ToShmem failed for ImportRule: cannot handle imported style sheets", - )) - } -} - -impl DeepCloneWithLock for ImportRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - ImportRule { - url: self.url.clone(), - stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params), - supports: self.supports.clone(), - layer: self.layer.clone(), - source_location: self.source_location.clone(), - } - } -} - -impl ToCssWithGuard for ImportRule { - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@import ")?; - self.url.to_css(&mut CssWriter::new(dest))?; - - if !matches!(self.layer, ImportLayer::None) { - dest.write_char(' ')?; - self.layer.to_css(&mut CssWriter::new(dest))?; - } - - if let Some(ref supports) = self.supports { - dest.write_str(" supports(")?; - supports.condition.to_css(&mut CssWriter::new(dest))?; - dest.write_char(')')?; - } - - if let Some(media) = self.stylesheet.media(guard) { - if !media.is_empty() { - dest.write_char(' ')?; - media.to_css(&mut CssWriter::new(dest))?; - } - } - - dest.write_char(';') - } -} diff --git a/components/style/stylesheets/keyframes_rule.rs b/components/style/stylesheets/keyframes_rule.rs deleted file mode 100644 index 6e5016080e9..00000000000 --- a/components/style/stylesheets/keyframes_rule.rs +++ /dev/null @@ -1,691 +0,0 @@ -/* 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/. */ - -//! Keyframes: https://drafts.csswg.org/css-animations/#keyframes - -use crate::error_reporting::ContextualParseError; -use crate::parser::ParserContext; -use crate::properties::longhands::animation_composition::single_value::SpecifiedValue as SpecifiedComposition; -use crate::properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction; -use crate::properties::LonghandIdSet; -use crate::properties::{Importance, PropertyDeclaration}; -use crate::properties::{LonghandId, PropertyDeclarationBlock, PropertyId}; -use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; -use crate::shared_lock::{Locked, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::rule_parser::VendorPrefix; -use crate::stylesheets::{CssRuleType, StylesheetContents}; -use crate::values::{serialize_percentage, KeyframesName}; -use cssparser::{ - parse_one_rule, AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserInput, ParserState, - QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, Token, -}; -use servo_arc::Arc; -use std::borrow::Cow; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ParsingMode, StyleParseErrorKind, ToCss}; - -/// A [`@keyframes`][keyframes] rule. -/// -/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes -#[derive(Debug, ToShmem)] -pub struct KeyframesRule { - /// The name of the current animation. - pub name: KeyframesName, - /// The keyframes specified for this CSS rule. - pub keyframes: Vec<Arc<Locked<Keyframe>>>, - /// Vendor prefix type the @keyframes has. - pub vendor_prefix: Option<VendorPrefix>, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for KeyframesRule { - // Serialization of KeyframesRule is not specced. - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@keyframes ")?; - self.name.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" {")?; - let iter = self.keyframes.iter(); - for lock in iter { - dest.write_str("\n")?; - let keyframe = lock.read_with(&guard); - keyframe.to_css(guard, dest)?; - } - dest.write_str("\n}") - } -} - -impl KeyframesRule { - /// Returns the index of the last keyframe that matches the given selector. - /// If the selector is not valid, or no keyframe is found, returns None. - /// - /// Related spec: - /// <https://drafts.csswg.org/css-animations-1/#interface-csskeyframesrule-findrule> - pub fn find_rule(&self, guard: &SharedRwLockReadGuard, selector: &str) -> Option<usize> { - let mut input = ParserInput::new(selector); - if let Ok(selector) = Parser::new(&mut input).parse_entirely(KeyframeSelector::parse) { - for (i, keyframe) in self.keyframes.iter().enumerate().rev() { - if keyframe.read_with(guard).selector == selector { - return Some(i); - } - } - } - None - } -} - -impl DeepCloneWithLock for KeyframesRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - KeyframesRule { - name: self.name.clone(), - keyframes: self - .keyframes - .iter() - .map(|x| { - Arc::new( - lock.wrap(x.read_with(guard).deep_clone_with_lock(lock, guard, params)), - ) - }) - .collect(), - vendor_prefix: self.vendor_prefix.clone(), - source_location: self.source_location.clone(), - } - } -} - -/// A number from 0 to 1, indicating the percentage of the animation when this -/// keyframe should run. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] -pub struct KeyframePercentage(pub f32); - -impl ::std::cmp::Ord for KeyframePercentage { - #[inline] - fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { - // We know we have a number from 0 to 1, so unwrap() here is safe. - self.0.partial_cmp(&other.0).unwrap() - } -} - -impl ::std::cmp::Eq for KeyframePercentage {} - -impl ToCss for KeyframePercentage { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_percentage(self.0, dest) - } -} - -impl KeyframePercentage { - /// Trivially constructs a new `KeyframePercentage`. - #[inline] - pub fn new(value: f32) -> KeyframePercentage { - debug_assert!(value >= 0. && value <= 1.); - KeyframePercentage(value) - } - - fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<KeyframePercentage, ParseError<'i>> { - let token = input.next()?.clone(); - match token { - Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("from") => { - Ok(KeyframePercentage::new(0.)) - }, - Token::Ident(ref identifier) if identifier.as_ref().eq_ignore_ascii_case("to") => { - Ok(KeyframePercentage::new(1.)) - }, - Token::Percentage { - unit_value: percentage, - .. - } if percentage >= 0. && percentage <= 1. => Ok(KeyframePercentage::new(percentage)), - _ => Err(input.new_unexpected_token_error(token)), - } - } -} - -/// A keyframes selector is a list of percentages or from/to symbols, which are -/// converted at parse time to percentages. -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -#[css(comma)] -pub struct KeyframeSelector(#[css(iterable)] Vec<KeyframePercentage>); - -impl KeyframeSelector { - /// Return the list of percentages this selector contains. - #[inline] - pub fn percentages(&self) -> &[KeyframePercentage] { - &self.0 - } - - /// A dummy public function so we can write a unit test for this. - pub fn new_for_unit_testing(percentages: Vec<KeyframePercentage>) -> KeyframeSelector { - KeyframeSelector(percentages) - } - - /// Parse a keyframe selector from CSS input. - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - input - .parse_comma_separated(KeyframePercentage::parse) - .map(KeyframeSelector) - } -} - -/// A keyframe. -#[derive(Debug, ToShmem)] -pub struct Keyframe { - /// The selector this keyframe was specified from. - pub selector: KeyframeSelector, - - /// The declaration block that was declared inside this keyframe. - /// - /// Note that `!important` rules in keyframes don't apply, but we keep this - /// `Arc` just for convenience. - pub block: Arc<Locked<PropertyDeclarationBlock>>, - - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for Keyframe { - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - self.selector.to_css(&mut CssWriter::new(dest))?; - dest.write_str(" { ")?; - self.block.read_with(guard).to_css(dest)?; - dest.write_str(" }")?; - Ok(()) - } -} - -impl Keyframe { - /// Parse a CSS keyframe. - pub fn parse<'i>( - css: &'i str, - parent_stylesheet_contents: &StylesheetContents, - lock: &SharedRwLock, - ) -> Result<Arc<Locked<Self>>, ParseError<'i>> { - let url_data = parent_stylesheet_contents.url_data.read(); - let namespaces = parent_stylesheet_contents.namespaces.read(); - let mut context = ParserContext::new( - parent_stylesheet_contents.origin, - &url_data, - Some(CssRuleType::Keyframe), - ParsingMode::DEFAULT, - parent_stylesheet_contents.quirks_mode, - Cow::Borrowed(&*namespaces), - None, - None, - ); - let mut input = ParserInput::new(css); - let mut input = Parser::new(&mut input); - - let mut declarations = SourcePropertyDeclaration::default(); - let mut rule_parser = KeyframeListParser { - context: &mut context, - shared_lock: &lock, - declarations: &mut declarations, - }; - parse_one_rule(&mut input, &mut rule_parser) - } -} - -impl DeepCloneWithLock for Keyframe { - /// Deep clones this Keyframe. - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - _params: &DeepCloneParams, - ) -> Keyframe { - Keyframe { - selector: self.selector.clone(), - block: Arc::new(lock.wrap(self.block.read_with(guard).clone())), - source_location: self.source_location.clone(), - } - } -} - -/// A keyframes step value. This can be a synthetised keyframes animation, that -/// is, one autogenerated from the current computed values, or a list of -/// declarations to apply. -/// -/// TODO: Find a better name for this? -#[derive(Clone, Debug, MallocSizeOf)] -pub enum KeyframesStepValue { - /// A step formed by a declaration block specified by the CSS. - Declarations { - /// The declaration block per se. - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "XXX: Primary ref, measure if DMD says it's worthwhile" - )] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - block: Arc<Locked<PropertyDeclarationBlock>>, - }, - /// A synthetic step computed from the current computed values at the time - /// of the animation. - ComputedValues, -} - -/// A single step from a keyframe animation. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct KeyframesStep { - /// The percentage of the animation duration when this step starts. - pub start_percentage: KeyframePercentage, - /// Declarations that will determine the final style during the step, or - /// `ComputedValues` if this is an autogenerated step. - pub value: KeyframesStepValue, - /// Whether an animation-timing-function declaration exists in the list of - /// declarations. - /// - /// This is used to know when to override the keyframe animation style. - pub declared_timing_function: bool, - /// Whether an animation-composition declaration exists in the list of - /// declarations. - /// - /// This is used to know when to override the keyframe animation style. - pub declared_composition: bool, -} - -impl KeyframesStep { - #[inline] - fn new( - start_percentage: KeyframePercentage, - value: KeyframesStepValue, - guard: &SharedRwLockReadGuard, - ) -> Self { - let mut declared_timing_function = false; - let mut declared_composition = false; - if let KeyframesStepValue::Declarations { ref block } = value { - for prop_decl in block.read_with(guard).declarations().iter() { - match *prop_decl { - PropertyDeclaration::AnimationTimingFunction(..) => { - declared_timing_function = true; - }, - PropertyDeclaration::AnimationComposition(..) => { - declared_composition = true; - }, - _ => continue, - } - // Don't need to continue the loop if both are found. - if declared_timing_function && declared_composition { - break; - } - } - } - - KeyframesStep { - start_percentage, - value, - declared_timing_function, - declared_composition, - } - } - - /// Return specified PropertyDeclaration. - #[inline] - fn get_declared_property<'a>( - &'a self, - guard: &'a SharedRwLockReadGuard, - property: LonghandId, - ) -> Option<&'a PropertyDeclaration> { - match self.value { - KeyframesStepValue::Declarations { ref block } => { - let guard = block.read_with(guard); - let (declaration, _) = guard - .get(PropertyDeclarationId::Longhand(property)) - .unwrap(); - match *declaration { - PropertyDeclaration::CSSWideKeyword(..) => None, - // FIXME: Bug 1710735: Support css variable in @keyframes rule. - PropertyDeclaration::WithVariables(..) => None, - _ => Some(declaration), - } - }, - KeyframesStepValue::ComputedValues => { - panic!("Shouldn't happen to set this property in missing keyframes") - }, - } - } - - /// Return specified TransitionTimingFunction if this KeyframesSteps has - /// 'animation-timing-function'. - pub fn get_animation_timing_function( - &self, - guard: &SharedRwLockReadGuard, - ) -> Option<SpecifiedTimingFunction> { - if !self.declared_timing_function { - return None; - } - - self.get_declared_property(guard, LonghandId::AnimationTimingFunction) - .map(|decl| { - match *decl { - PropertyDeclaration::AnimationTimingFunction(ref value) => { - // Use the first value - value.0[0].clone() - }, - _ => unreachable!("Unexpected PropertyDeclaration"), - } - }) - } - - /// Return CompositeOperation if this KeyframesSteps has 'animation-composition'. - pub fn get_animation_composition( - &self, - guard: &SharedRwLockReadGuard, - ) -> Option<SpecifiedComposition> { - if !self.declared_composition { - return None; - } - - self.get_declared_property(guard, LonghandId::AnimationComposition) - .map(|decl| { - match *decl { - PropertyDeclaration::AnimationComposition(ref value) => { - // Use the first value - value.0[0].clone() - }, - _ => unreachable!("Unexpected PropertyDeclaration"), - } - }) - } -} - -/// This structure represents a list of animation steps computed from the list -/// of keyframes, in order. -/// -/// It only takes into account animable properties. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct KeyframesAnimation { - /// The difference steps of the animation. - pub steps: Vec<KeyframesStep>, - /// The properties that change in this animation. - pub properties_changed: LonghandIdSet, - /// Vendor prefix type the @keyframes has. - pub vendor_prefix: Option<VendorPrefix>, -} - -/// Get all the animated properties in a keyframes animation. -fn get_animated_properties( - keyframes: &[Arc<Locked<Keyframe>>], - guard: &SharedRwLockReadGuard, -) -> LonghandIdSet { - let mut ret = LonghandIdSet::new(); - // NB: declarations are already deduplicated, so we don't have to check for - // it here. - for keyframe in keyframes { - let keyframe = keyframe.read_with(&guard); - let block = keyframe.block.read_with(guard); - // CSS Animations spec clearly defines that properties with !important - // in keyframe rules are invalid and ignored, but it's still ambiguous - // whether we should drop the !important properties or retain the - // properties when they are set via CSSOM. So we assume there might - // be properties with !important in keyframe rules here. - // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824 - for declaration in block.normal_declaration_iter() { - let longhand_id = match declaration.id() { - PropertyDeclarationId::Longhand(id) => id, - _ => continue, - }; - - if longhand_id == LonghandId::Display { - continue; - } - - if !longhand_id.is_animatable() { - continue; - } - - ret.insert(longhand_id); - } - } - - ret -} - -impl KeyframesAnimation { - /// Create a keyframes animation from a given list of keyframes. - /// - /// This will return a keyframe animation with empty steps and - /// properties_changed if the list of keyframes is empty, or there are no - /// animated properties obtained from the keyframes. - /// - /// Otherwise, this will compute and sort the steps used for the animation, - /// and return the animation object. - pub fn from_keyframes( - keyframes: &[Arc<Locked<Keyframe>>], - vendor_prefix: Option<VendorPrefix>, - guard: &SharedRwLockReadGuard, - ) -> Self { - let mut result = KeyframesAnimation { - steps: vec![], - properties_changed: LonghandIdSet::new(), - vendor_prefix, - }; - - if keyframes.is_empty() { - return result; - } - - result.properties_changed = get_animated_properties(keyframes, guard); - if result.properties_changed.is_empty() { - return result; - } - - for keyframe in keyframes { - let keyframe = keyframe.read_with(&guard); - for percentage in keyframe.selector.0.iter() { - result.steps.push(KeyframesStep::new( - *percentage, - KeyframesStepValue::Declarations { - block: keyframe.block.clone(), - }, - guard, - )); - } - } - - // Sort by the start percentage, so we can easily find a frame. - result.steps.sort_by_key(|step| step.start_percentage); - - // Prepend autogenerated keyframes if appropriate. - if result.steps[0].start_percentage.0 != 0. { - result.steps.insert( - 0, - KeyframesStep::new( - KeyframePercentage::new(0.), - KeyframesStepValue::ComputedValues, - guard, - ), - ); - } - - if result.steps.last().unwrap().start_percentage.0 != 1. { - result.steps.push(KeyframesStep::new( - KeyframePercentage::new(1.), - KeyframesStepValue::ComputedValues, - guard, - )); - } - - result - } -} - -/// Parses a keyframes list, like: -/// 0%, 50% { -/// width: 50%; -/// } -/// -/// 40%, 60%, 100% { -/// width: 100%; -/// } -struct KeyframeListParser<'a, 'b> { - context: &'a mut ParserContext<'b>, - shared_lock: &'a SharedRwLock, - declarations: &'a mut SourcePropertyDeclaration, -} - -/// Parses a keyframe list from CSS input. -pub fn parse_keyframe_list<'a>( - context: &mut ParserContext<'a>, - input: &mut Parser, - shared_lock: &SharedRwLock, -) -> Vec<Arc<Locked<Keyframe>>> { - let mut declarations = SourcePropertyDeclaration::default(); - let mut parser = KeyframeListParser { - context, - shared_lock, - declarations: &mut declarations, - }; - RuleBodyParser::new(input, &mut parser) - .filter_map(Result::ok) - .collect() -} - -impl<'a, 'b, 'i> AtRuleParser<'i> for KeyframeListParser<'a, 'b> { - type Prelude = (); - type AtRule = Arc<Locked<Keyframe>>; - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeListParser<'a, 'b> { - type Declaration = Arc<Locked<Keyframe>>; - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for KeyframeListParser<'a, 'b> { - type Prelude = KeyframeSelector; - type QualifiedRule = Arc<Locked<Keyframe>>; - type Error = StyleParseErrorKind<'i>; - - fn parse_prelude<'t>( - &mut self, - input: &mut Parser<'i, 't>, - ) -> Result<Self::Prelude, ParseError<'i>> { - let start_position = input.position(); - KeyframeSelector::parse(input).map_err(|e| { - let location = e.location; - let error = ContextualParseError::InvalidKeyframeRule( - input.slice_from(start_position), - e.clone(), - ); - self.context.log_css_error(location, error); - e - }) - } - - fn parse_block<'t>( - &mut self, - selector: Self::Prelude, - start: &ParserState, - input: &mut Parser<'i, 't>, - ) -> Result<Self::QualifiedRule, ParseError<'i>> { - let mut block = PropertyDeclarationBlock::new(); - let declarations = &mut self.declarations; - self.context - .nest_for_rule(CssRuleType::Keyframe, |context| { - let mut parser = KeyframeDeclarationParser { - context: &context, - declarations, - }; - let mut iter = RuleBodyParser::new(input, &mut parser); - while let Some(declaration) = iter.next() { - match declaration { - Ok(()) => { - block.extend(iter.parser.declarations.drain(), Importance::Normal); - }, - Err((error, slice)) => { - iter.parser.declarations.clear(); - let location = error.location; - let error = - ContextualParseError::UnsupportedKeyframePropertyDeclaration( - slice, error, - ); - context.log_css_error(location, error); - }, - } - // `parse_important` is not called here, `!important` is not allowed in keyframe blocks. - } - }); - Ok(Arc::new(self.shared_lock.wrap(Keyframe { - selector, - block: Arc::new(self.shared_lock.wrap(block)), - source_location: start.source_location(), - }))) - } -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, Arc<Locked<Keyframe>>, StyleParseErrorKind<'i>> - for KeyframeListParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - true - } - fn parse_declarations(&self) -> bool { - false - } -} - -struct KeyframeDeclarationParser<'a, 'b: 'a> { - context: &'a ParserContext<'b>, - declarations: &'a mut SourcePropertyDeclaration, -} - -/// Default methods reject all at rules. -impl<'a, 'b, 'i> AtRuleParser<'i> for KeyframeDeclarationParser<'a, 'b> { - type Prelude = (); - type AtRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for KeyframeDeclarationParser<'a, 'b> { - type Prelude = (); - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; -} - -impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeDeclarationParser<'a, 'b> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - let id = match PropertyId::parse(&name, self.context) { - Ok(id) => id, - Err(()) => { - return Err(input.new_custom_error(StyleParseErrorKind::UnknownProperty(name))); - }, - }; - - // TODO(emilio): Shouldn't this use parse_entirely? - PropertyDeclaration::parse_into(self.declarations, id, self.context, input)?; - - // In case there is still unparsed text in the declaration, we should - // roll back. - input.expect_exhausted()?; - - Ok(()) - } -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for KeyframeDeclarationParser<'a, 'b> -{ - fn parse_qualified(&self) -> bool { - false - } - fn parse_declarations(&self) -> bool { - true - } -} diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs deleted file mode 100644 index 3ebe6bb34f8..00000000000 --- a/components/style/stylesheets/layer_rule.rs +++ /dev/null @@ -1,228 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A [`@layer`][layer] rule. -//! -//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering - -use crate::parser::{Parse, ParserContext}; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::values::AtomIdent; - -use super::CssRules; - -use cssparser::{Parser, SourceLocation, Token}; -use servo_arc::Arc; -use smallvec::SmallVec; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// The order of a given layer. We use 16 bits so that we can pack LayerOrder -/// and CascadeLevel in a single 32-bit struct. If we need more bits we can go -/// back to packing CascadeLevel in a single byte as we did before. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerOrder(u16); - -impl LayerOrder { - /// The order of the root layer. - pub const fn root() -> Self { - Self(std::u16::MAX - 1) - } - - /// The order of the style attribute layer. - pub const fn style_attribute() -> Self { - Self(std::u16::MAX) - } - - /// Returns whether this layer is for the style attribute, which behaves - /// differently in terms of !important, see - /// https://github.com/w3c/csswg-drafts/issues/6872 - /// - /// (This is a bit silly, mind-you, but it's needed so that revert-layer - /// behaves correctly). - #[inline] - pub fn is_style_attribute_layer(&self) -> bool { - *self == Self::style_attribute() - } - - /// The first cascade layer order. - pub const fn first() -> Self { - Self(0) - } - - /// Increment the cascade layer order. - #[inline] - pub fn inc(&mut self) { - if self.0 != std::u16::MAX - 1 { - self.0 += 1; - } - } -} - -/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name -#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] -pub struct LayerName(pub SmallVec<[AtomIdent; 1]>); - -impl LayerName { - /// Returns an empty layer name (which isn't a valid final state, so caller - /// is responsible to fill up the name before use). - pub fn new_empty() -> Self { - Self(Default::default()) - } - - /// Returns a synthesized name for an anonymous layer. - pub fn new_anonymous() -> Self { - use std::sync::atomic::{AtomicUsize, Ordering}; - static NEXT_ANONYMOUS_LAYER_NAME: AtomicUsize = AtomicUsize::new(0); - - let mut name = SmallVec::new(); - let next_id = NEXT_ANONYMOUS_LAYER_NAME.fetch_add(1, Ordering::Relaxed); - // The parens don't _technically_ prevent conflicts with authors, as - // authors could write escaped parens as part of the identifier, I - // think, but highly reduces the possibility. - name.push(AtomIdent::from(&*format!("-moz-anon-layer({})", next_id))); - - LayerName(name) - } - - /// Returns the names of the layers. That is, for a layer like `foo.bar`, - /// it'd return [foo, bar]. - pub fn layer_names(&self) -> &[AtomIdent] { - &self.0 - } -} - -impl Parse for LayerName { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut result = SmallVec::new(); - result.push(AtomIdent::from(&**input.expect_ident()?)); - loop { - let next_name = input.try_parse(|input| -> Result<AtomIdent, ParseError<'i>> { - match input.next_including_whitespace()? { - Token::Delim('.') => {}, - other => { - let t = other.clone(); - return Err(input.new_unexpected_token_error(t)); - }, - } - - let name = match input.next_including_whitespace()? { - Token::Ident(ref ident) => ident, - other => { - let t = other.clone(); - return Err(input.new_unexpected_token_error(t)); - }, - }; - - Ok(AtomIdent::from(&**name)) - }); - - match next_name { - Ok(name) => result.push(name), - Err(..) => break, - } - } - Ok(LayerName(result)) - } -} - -impl ToCss for LayerName { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let mut first = true; - for name in self.0.iter() { - if !first { - dest.write_char('.')?; - } - first = false; - name.to_css(dest)?; - } - Ok(()) - } -} - -#[derive(Debug, ToShmem)] -/// A block `@layer <name>? { ... }` -/// https://drafts.csswg.org/css-cascade-5/#layer-block -pub struct LayerBlockRule { - /// The layer name, or `None` if anonymous. - pub name: Option<LayerName>, - /// The nested rules. - pub rules: Arc<Locked<CssRules>>, - /// The source position where this rule was found. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for LayerBlockRule { - fn to_css( - &self, - guard: &SharedRwLockReadGuard, - dest: &mut crate::str::CssStringWriter, - ) -> fmt::Result { - dest.write_str("@layer")?; - if let Some(ref name) = self.name { - dest.write_char(' ')?; - name.to_css(&mut CssWriter::new(dest))?; - } - self.rules.read_with(guard).to_css_block(guard, dest) - } -} - -impl DeepCloneWithLock for LayerBlockRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - Self { - name: self.name.clone(), - rules: Arc::new( - lock.wrap( - self.rules - .read_with(guard) - .deep_clone_with_lock(lock, guard, params), - ), - ), - source_location: self.source_location.clone(), - } - } -} - -/// A statement `@layer <name>, <name>, <name>;` -/// -/// https://drafts.csswg.org/css-cascade-5/#layer-empty -#[derive(Clone, Debug, ToShmem)] -pub struct LayerStatementRule { - /// The list of layers to sort. - pub names: Vec<LayerName>, - /// The source position where this rule was found. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for LayerStatementRule { - fn to_css( - &self, - _: &SharedRwLockReadGuard, - dest: &mut crate::str::CssStringWriter, - ) -> fmt::Result { - let mut writer = CssWriter::new(dest); - writer.write_str("@layer ")?; - let mut first = true; - for name in &*self.names { - if !first { - writer.write_str(", ")?; - } - first = false; - name.to_css(&mut writer)?; - } - writer.write_char(';') - } -} diff --git a/components/style/stylesheets/loader.rs b/components/style/stylesheets/loader.rs deleted file mode 100644 index f987cf95972..00000000000 --- a/components/style/stylesheets/loader.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The stylesheet loader is the abstraction used to trigger network requests -//! for `@import` rules. - -use crate::media_queries::MediaList; -use crate::parser::ParserContext; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCondition}; -use crate::values::CssUrl; -use cssparser::SourceLocation; -use servo_arc::Arc; - -/// The stylesheet loader is the abstraction used to trigger network requests -/// for `@import` rules. -pub trait StylesheetLoader { - /// Request a stylesheet after parsing a given `@import` rule, and return - /// the constructed `@import` rule. - fn request_stylesheet( - &self, - url: CssUrl, - location: SourceLocation, - context: &ParserContext, - lock: &SharedRwLock, - media: Arc<Locked<MediaList>>, - supports: Option<ImportSupportsCondition>, - layer: ImportLayer, - ) -> Arc<Locked<ImportRule>>; -} diff --git a/components/style/stylesheets/media_rule.rs b/components/style/stylesheets/media_rule.rs deleted file mode 100644 index cde60a16bf7..00000000000 --- a/components/style/stylesheets/media_rule.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* 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/. */ - -//! An [`@media`][media] rule. -//! -//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media - -use crate::media_queries::MediaList; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::CssRules; -use cssparser::SourceLocation; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// An [`@media`][media] rule. -/// -/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media -#[derive(Debug, ToShmem)] -pub struct MediaRule { - /// The list of media queries used by this media rule. - pub media_queries: Arc<Locked<MediaList>>, - /// The nested rules to this media rule. - pub rules: Arc<Locked<CssRules>>, - /// The source position where this media rule was found. - pub source_location: SourceLocation, -} - -impl MediaRule { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - // Measurement of other fields may be added later. - self.rules.unconditional_shallow_size_of(ops) + - self.rules.read_with(guard).size_of(guard, ops) - } -} - -impl ToCssWithGuard for MediaRule { - // Serialization of MediaRule is not specced. - // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@media ")?; - self.media_queries - .read_with(guard) - .to_css(&mut CssWriter::new(dest))?; - self.rules.read_with(guard).to_css_block(guard, dest) - } -} - -impl DeepCloneWithLock for MediaRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - let media_queries = self.media_queries.read_with(guard); - let rules = self.rules.read_with(guard); - MediaRule { - media_queries: Arc::new(lock.wrap(media_queries.clone())), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), - source_location: self.source_location.clone(), - } - } -} diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs deleted file mode 100644 index 85d6619d135..00000000000 --- a/components/style/stylesheets/mod.rs +++ /dev/null @@ -1,582 +0,0 @@ -/* 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/. */ - -//! Style sheets and their CSS rules. - -pub mod container_rule; -mod counter_style_rule; -mod document_rule; -mod font_face_rule; -pub mod font_feature_values_rule; -pub mod font_palette_values_rule; -pub mod import_rule; -pub mod keyframes_rule; -pub mod layer_rule; -mod loader; -mod media_rule; -mod namespace_rule; -pub mod origin; -mod page_rule; -mod property_rule; -mod rule_list; -mod rule_parser; -mod rules_iterator; -mod style_rule; -mod stylesheet; -pub mod supports_rule; - -#[cfg(feature = "gecko")] -use crate::gecko_bindings::sugar::refptr::RefCounted; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::{bindings, structs}; -use crate::parser::ParserContext; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use cssparser::{parse_one_rule, Parser, ParserInput}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use servo_arc::Arc; -use std::borrow::Cow; -use std::fmt; -#[cfg(feature = "gecko")] -use std::mem::{self, ManuallyDrop}; -use style_traits::ParsingMode; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -pub use self::container_rule::ContainerRule; -pub use self::counter_style_rule::CounterStyleRule; -pub use self::document_rule::DocumentRule; -pub use self::font_face_rule::FontFaceRule; -pub use self::font_feature_values_rule::FontFeatureValuesRule; -pub use self::font_palette_values_rule::FontPaletteValuesRule; -pub use self::import_rule::ImportRule; -pub use self::keyframes_rule::KeyframesRule; -pub use self::layer_rule::{LayerBlockRule, LayerStatementRule}; -pub use self::loader::StylesheetLoader; -pub use self::media_rule::MediaRule; -pub use self::namespace_rule::NamespaceRule; -pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; -pub use self::page_rule::{PageRule, PageSelector, PageSelectors}; -pub use self::property_rule::PropertyRule; -pub use self::rule_list::{CssRules, CssRulesHelpers}; -pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser}; -pub use self::rules_iterator::{AllRules, EffectiveRules}; -pub use self::rules_iterator::{ - EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator, -}; -pub use self::style_rule::StyleRule; -pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; -pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; -pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; -pub use self::supports_rule::SupportsRule; - -/// The CORS mode used for a CSS load. -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] -pub enum CorsMode { - /// No CORS mode, so cross-origin loads can be done. - None, - /// Anonymous CORS request. - Anonymous, -} - -/// Extra data that the backend may need to resolve url values. -/// -/// If the usize's lowest bit is 0, then this is a strong reference to a -/// structs::URLExtraData object. -/// -/// Otherwise, shifting the usize's bits the right by one gives the -/// UserAgentStyleSheetID value corresponding to the style sheet whose -/// URLExtraData this is, which is stored in URLExtraData_sShared. We don't -/// hold a strong reference to that object from here, but we rely on that -/// array's objects being held alive until shutdown. -/// -/// We use this packed representation rather than an enum so that -/// `from_ptr_ref` can work. -#[cfg(feature = "gecko")] -#[derive(PartialEq)] -#[repr(C)] -pub struct UrlExtraData(usize); - -/// Extra data that the backend may need to resolve url values. -#[cfg(feature = "servo")] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct UrlExtraData(pub Arc<::url::Url>); - -#[cfg(feature = "servo")] -impl UrlExtraData { - /// True if this URL scheme is chrome. - pub fn chrome_rules_enabled(&self) -> bool { - self.0.scheme() == "chrome" - } - - /// Get the interior Url as a string. - pub fn as_str(&self) -> &str { - self.0.as_str() - } -} - -#[cfg(feature = "servo")] -impl From<::url::Url> for UrlExtraData { - fn from(url: ::url::Url) -> Self { - Self(Arc::new(url)) - } -} - -#[cfg(not(feature = "gecko"))] -impl ToShmem for UrlExtraData { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - unimplemented!("If servo wants to share stylesheets across processes, ToShmem for Url must be implemented"); - } -} - -#[cfg(feature = "gecko")] -impl Clone for UrlExtraData { - fn clone(&self) -> UrlExtraData { - UrlExtraData::new(self.ptr()) - } -} - -#[cfg(feature = "gecko")] -impl Drop for UrlExtraData { - fn drop(&mut self) { - // No need to release when we have an index into URLExtraData_sShared. - if self.0 & 1 == 0 { - unsafe { - self.as_ref().release(); - } - } - } -} - -#[cfg(feature = "gecko")] -impl ToShmem for UrlExtraData { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - if self.0 & 1 == 0 { - let shared_extra_datas = unsafe { &structs::URLExtraData_sShared }; - let self_ptr = self.as_ref() as *const _ as *mut _; - let sheet_id = shared_extra_datas - .iter() - .position(|r| r.mRawPtr == self_ptr); - let sheet_id = match sheet_id { - Some(id) => id, - None => { - return Err(String::from( - "ToShmem failed for UrlExtraData: expected sheet's URLExtraData to be in \ - URLExtraData::sShared", - )); - }, - }; - Ok(ManuallyDrop::new(UrlExtraData((sheet_id << 1) | 1))) - } else { - Ok(ManuallyDrop::new(UrlExtraData(self.0))) - } - } -} - -#[cfg(feature = "gecko")] -impl UrlExtraData { - /// Create a new UrlExtraData wrapping a pointer to the specified Gecko - /// URLExtraData object. - pub fn new(ptr: *mut structs::URLExtraData) -> UrlExtraData { - unsafe { - (*ptr).addref(); - } - UrlExtraData(ptr as usize) - } - - /// True if this URL scheme is chrome. - #[inline] - pub fn chrome_rules_enabled(&self) -> bool { - self.as_ref().mChromeRulesEnabled - } - - /// Create a reference to this `UrlExtraData` from a reference to pointer. - /// - /// The pointer must be valid and non null. - /// - /// This method doesn't touch refcount. - #[inline] - pub unsafe fn from_ptr_ref(ptr: &*mut structs::URLExtraData) -> &Self { - mem::transmute(ptr) - } - - /// Returns a pointer to the Gecko URLExtraData object. - pub fn ptr(&self) -> *mut structs::URLExtraData { - if self.0 & 1 == 0 { - self.0 as *mut structs::URLExtraData - } else { - unsafe { - let sheet_id = self.0 >> 1; - structs::URLExtraData_sShared[sheet_id].mRawPtr - } - } - } - - fn as_ref(&self) -> &structs::URLExtraData { - unsafe { &*(self.ptr() as *const structs::URLExtraData) } - } -} - -#[cfg(feature = "gecko")] -impl fmt::Debug for UrlExtraData { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - macro_rules! define_debug_struct { - ($struct_name:ident, $gecko_class:ident, $debug_fn:ident) => { - struct $struct_name(*mut structs::$gecko_class); - impl fmt::Debug for $struct_name { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - use nsstring::nsCString; - let mut spec = nsCString::new(); - unsafe { - bindings::$debug_fn(self.0, &mut spec); - } - spec.fmt(formatter) - } - } - }; - } - - define_debug_struct!(DebugURI, nsIURI, Gecko_nsIURI_Debug); - define_debug_struct!( - DebugReferrerInfo, - nsIReferrerInfo, - Gecko_nsIReferrerInfo_Debug - ); - - formatter - .debug_struct("URLExtraData") - .field("chrome_rules_enabled", &self.chrome_rules_enabled()) - .field("base", &DebugURI(self.as_ref().mBaseURI.raw())) - .field( - "referrer", - &DebugReferrerInfo(self.as_ref().mReferrerInfo.raw()), - ) - .finish() - } -} - -// XXX We probably need to figure out whether we should mark Eq here. -// It is currently marked so because properties::UnparsedValue wants Eq. -#[cfg(feature = "gecko")] -impl Eq for UrlExtraData {} - -/// A CSS rule. -/// -/// TODO(emilio): Lots of spec links should be around. -#[derive(Clone, Debug, ToShmem)] -#[allow(missing_docs)] -pub enum CssRule { - // No Charset here, CSSCharsetRule has been removed from CSSOM - // https://drafts.csswg.org/cssom/#changes-from-5-december-2013 - Namespace(Arc<NamespaceRule>), - Import(Arc<Locked<ImportRule>>), - Style(Arc<Locked<StyleRule>>), - Media(Arc<MediaRule>), - Container(Arc<ContainerRule>), - FontFace(Arc<Locked<FontFaceRule>>), - FontFeatureValues(Arc<FontFeatureValuesRule>), - FontPaletteValues(Arc<FontPaletteValuesRule>), - CounterStyle(Arc<Locked<CounterStyleRule>>), - Keyframes(Arc<Locked<KeyframesRule>>), - Supports(Arc<SupportsRule>), - Page(Arc<Locked<PageRule>>), - Property(Arc<PropertyRule>), - Document(Arc<DocumentRule>), - LayerBlock(Arc<LayerBlockRule>), - LayerStatement(Arc<LayerStatementRule>), -} - -impl CssRule { - /// Measure heap usage. - #[cfg(feature = "gecko")] - fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - match *self { - // Not all fields are currently fully measured. Extra measurement - // may be added later. - CssRule::Namespace(_) => 0, - - // We don't need to measure ImportRule::stylesheet because we measure - // it on the C++ side in the child list of the ServoStyleSheet. - CssRule::Import(_) => 0, - - CssRule::Style(ref lock) => { - lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) - }, - CssRule::Media(ref arc) => { - arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) - }, - CssRule::Container(ref arc) => { - arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) - }, - CssRule::FontFace(_) => 0, - CssRule::FontFeatureValues(_) => 0, - CssRule::FontPaletteValues(_) => 0, - CssRule::CounterStyle(_) => 0, - CssRule::Keyframes(_) => 0, - CssRule::Supports(ref arc) => { - arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) - }, - CssRule::Page(ref lock) => { - lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) - }, - CssRule::Property(ref rule) => { - rule.unconditional_shallow_size_of(ops) + rule.size_of(guard, ops) - }, - CssRule::Document(ref arc) => { - arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) - }, - // TODO(emilio): Add memory reporting for these rules. - CssRule::LayerBlock(_) | CssRule::LayerStatement(_) => 0, - } - } -} - -/// https://drafts.csswg.org/cssom-1/#dom-cssrule-type -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] -#[repr(u8)] -pub enum CssRuleType { - // https://drafts.csswg.org/cssom/#the-cssrule-interface - Style = 1, - // Charset = 2, // Historical - Import = 3, - Media = 4, - FontFace = 5, - Page = 6, - // https://drafts.csswg.org/css-animations-1/#interface-cssrule-idl - Keyframes = 7, - Keyframe = 8, - // https://drafts.csswg.org/cssom/#the-cssrule-interface - // Margin = 9, // Not implemented yet. - Namespace = 10, - // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface - CounterStyle = 11, - // https://drafts.csswg.org/css-conditional-3/#extentions-to-cssrule-interface - Supports = 12, - // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface - Document = 13, - // https://drafts.csswg.org/css-fonts/#om-fontfeaturevalues - FontFeatureValues = 14, - // After viewport, all rules should return 0 from the API, but we still need - // a constant somewhere. - LayerBlock = 16, - LayerStatement = 17, - Container = 18, - FontPaletteValues = 19, - // 20 is an arbitrary number to use for Property. - Property = 20, -} - -impl CssRuleType { - /// Returns a bit that identifies this rule type. - #[inline] - pub const fn bit(self) -> u32 { - 1 << self as u32 - } -} - -/// Set of rule types. -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] -pub struct CssRuleTypes(u32); - -impl From<CssRuleType> for CssRuleTypes { - fn from(ty: CssRuleType) -> Self { - Self(ty.bit()) - } -} - -impl CssRuleTypes { - /// Returns whether the rule is in the current set. - #[inline] - pub fn contains(self, ty: CssRuleType) -> bool { - self.0 & ty.bit() != 0 - } - - /// Returns all the rules specified in the set. - pub fn bits(self) -> u32 { - self.0 - } - - /// Inserts a rule type into the set. - #[inline] - pub fn insert(&mut self, ty: CssRuleType) { - self.0 |= ty.bit() - } -} - -#[allow(missing_docs)] -pub enum RulesMutateError { - Syntax, - IndexSize, - HierarchyRequest, - InvalidState, -} - -impl CssRule { - /// Returns the CSSOM rule type of this rule. - pub fn rule_type(&self) -> CssRuleType { - match *self { - CssRule::Style(_) => CssRuleType::Style, - CssRule::Import(_) => CssRuleType::Import, - CssRule::Media(_) => CssRuleType::Media, - CssRule::FontFace(_) => CssRuleType::FontFace, - CssRule::FontFeatureValues(_) => CssRuleType::FontFeatureValues, - CssRule::FontPaletteValues(_) => CssRuleType::FontPaletteValues, - CssRule::CounterStyle(_) => CssRuleType::CounterStyle, - CssRule::Keyframes(_) => CssRuleType::Keyframes, - CssRule::Namespace(_) => CssRuleType::Namespace, - CssRule::Supports(_) => CssRuleType::Supports, - CssRule::Page(_) => CssRuleType::Page, - CssRule::Property(_) => CssRuleType::Property, - CssRule::Document(_) => CssRuleType::Document, - CssRule::LayerBlock(_) => CssRuleType::LayerBlock, - CssRule::LayerStatement(_) => CssRuleType::LayerStatement, - CssRule::Container(_) => CssRuleType::Container, - } - } - - /// Parse a CSS rule. - /// - /// Returns a parsed CSS rule and the final state of the parser. - /// - /// Input state is None for a nested rule - pub fn parse( - css: &str, - insert_rule_context: InsertRuleContext, - parent_stylesheet_contents: &StylesheetContents, - shared_lock: &SharedRwLock, - state: State, - loader: Option<&dyn StylesheetLoader>, - allow_import_rules: AllowImportRules, - ) -> Result<Self, RulesMutateError> { - let url_data = parent_stylesheet_contents.url_data.read(); - let namespaces = parent_stylesheet_contents.namespaces.read(); - let context = ParserContext::new( - parent_stylesheet_contents.origin, - &url_data, - None, - ParsingMode::DEFAULT, - parent_stylesheet_contents.quirks_mode, - Cow::Borrowed(&*namespaces), - None, - None, - ); - - let mut input = ParserInput::new(css); - let mut input = Parser::new(&mut input); - - // nested rules are in the body state - let mut rule_parser = TopLevelRuleParser { - context, - shared_lock: &shared_lock, - loader, - state, - dom_error: None, - insert_rule_context: Some(insert_rule_context), - allow_import_rules, - declaration_parser_state: Default::default(), - rules: Default::default(), - }; - - match parse_one_rule(&mut input, &mut rule_parser) { - Ok(_) => Ok(rule_parser.rules.pop().unwrap()), - Err(_) => Err(rule_parser.dom_error.unwrap_or(RulesMutateError::Syntax)), - } - } -} - -impl DeepCloneWithLock for CssRule { - /// Deep clones this CssRule. - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> CssRule { - match *self { - CssRule::Namespace(ref arc) => CssRule::Namespace(arc.clone()), - CssRule::Import(ref arc) => { - let rule = arc - .read_with(guard) - .deep_clone_with_lock(lock, guard, params); - CssRule::Import(Arc::new(lock.wrap(rule))) - }, - CssRule::Style(ref arc) => { - let rule = arc.read_with(guard); - CssRule::Style(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), - )) - }, - CssRule::Container(ref arc) => { - CssRule::Container(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) - }, - CssRule::Media(ref arc) => { - CssRule::Media(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) - }, - CssRule::FontFace(ref arc) => { - let rule = arc.read_with(guard); - CssRule::FontFace(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::FontFeatureValues(ref arc) => CssRule::FontFeatureValues(arc.clone()), - CssRule::FontPaletteValues(ref arc) => CssRule::FontPaletteValues(arc.clone()), - CssRule::CounterStyle(ref arc) => { - let rule = arc.read_with(guard); - CssRule::CounterStyle(Arc::new(lock.wrap(rule.clone()))) - }, - CssRule::Keyframes(ref arc) => { - let rule = arc.read_with(guard); - CssRule::Keyframes(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), - )) - }, - CssRule::Supports(ref arc) => { - CssRule::Supports(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) - }, - CssRule::Page(ref arc) => { - let rule = arc.read_with(guard); - CssRule::Page(Arc::new( - lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), - )) - }, - CssRule::Property(ref arc) => { - // @property rules are immutable, so we don't need any of the `Locked` - // shenanigans, actually, and can just share the rule. - CssRule::Property(arc.clone()) - }, - CssRule::Document(ref arc) => { - CssRule::Document(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) - }, - CssRule::LayerStatement(ref arc) => CssRule::LayerStatement(arc.clone()), - CssRule::LayerBlock(ref arc) => { - CssRule::LayerBlock(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) - }, - } - } -} - -impl ToCssWithGuard for CssRule { - // https://drafts.csswg.org/cssom/#serialize-a-css-rule - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - match *self { - CssRule::Namespace(ref rule) => rule.to_css(guard, dest), - CssRule::Import(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Style(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::FontFace(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::FontFeatureValues(ref rule) => rule.to_css(guard, dest), - CssRule::FontPaletteValues(ref rule) => rule.to_css(guard, dest), - CssRule::CounterStyle(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Media(ref rule) => rule.to_css(guard, dest), - CssRule::Supports(ref rule) => rule.to_css(guard, dest), - CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Property(ref rule) => rule.to_css(guard, dest), - CssRule::Document(ref rule) => rule.to_css(guard, dest), - CssRule::LayerBlock(ref rule) => rule.to_css(guard, dest), - CssRule::LayerStatement(ref rule) => rule.to_css(guard, dest), - CssRule::Container(ref rule) => rule.to_css(guard, dest), - } - } -} diff --git a/components/style/stylesheets/namespace_rule.rs b/components/style/stylesheets/namespace_rule.rs deleted file mode 100644 index ad980b70a8b..00000000000 --- a/components/style/stylesheets/namespace_rule.rs +++ /dev/null @@ -1,43 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! The `@namespace` at-rule. - -use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::{Namespace, Prefix}; -use cssparser::SourceLocation; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A `@namespace` rule. -#[derive(Clone, Debug, PartialEq, ToShmem)] -#[allow(missing_docs)] -pub struct NamespaceRule { - /// The namespace prefix, and `None` if it's the default Namespace - pub prefix: Option<Prefix>, - /// The actual namespace url. - pub url: Namespace, - /// The source location this rule was found at. - pub source_location: SourceLocation, -} - -impl ToCssWithGuard for NamespaceRule { - // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule - fn to_css( - &self, - _guard: &SharedRwLockReadGuard, - dest_str: &mut CssStringWriter, - ) -> fmt::Result { - let mut dest = CssWriter::new(dest_str); - dest.write_str("@namespace ")?; - if let Some(ref prefix) = self.prefix { - prefix.to_css(&mut dest)?; - dest.write_char(' ')?; - } - dest.write_str("url(")?; - self.url.to_string().to_css(&mut dest)?; - dest.write_str(");") - } -} diff --git a/components/style/stylesheets/origin.rs b/components/style/stylesheets/origin.rs deleted file mode 100644 index 27ad3fa184a..00000000000 --- a/components/style/stylesheets/origin.rs +++ /dev/null @@ -1,247 +0,0 @@ -/* 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/. */ - -//! [CSS cascade origins](https://drafts.csswg.org/css-cascade/#cascading-origins). - -use std::marker::PhantomData; -use std::ops::BitOrAssign; - -/// Each style rule has an origin, which determines where it enters the cascade. -/// -/// <https://drafts.csswg.org/css-cascade/#cascading-origins> -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, PartialOrd, Ord)] -#[repr(u8)] -pub enum Origin { - /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent> - UserAgent = 0x1, - - /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user> - User = 0x2, - - /// <https://drafts.csswg.org/css-cascade/#cascade-origin-author> - Author = 0x4, -} - -impl Origin { - /// Returns an origin that goes in order for `index`. - /// - /// This is used for iterating across origins. - fn from_index(index: i8) -> Option<Self> { - Some(match index { - 0 => Origin::Author, - 1 => Origin::User, - 2 => Origin::UserAgent, - _ => return None, - }) - } - - fn to_index(self) -> i8 { - match self { - Origin::Author => 0, - Origin::User => 1, - Origin::UserAgent => 2, - } - } - - /// Returns an iterator from this origin, towards all the less specific - /// origins. So for `UserAgent`, it'd iterate through all origins. - #[inline] - pub fn following_including(self) -> OriginSetIterator { - OriginSetIterator { - set: OriginSet::ORIGIN_USER | OriginSet::ORIGIN_AUTHOR | OriginSet::ORIGIN_USER_AGENT, - cur: self.to_index(), - rev: true, - } - } -} - -bitflags! { - /// A set of origins. This is equivalent to Gecko's OriginFlags. - #[derive(MallocSizeOf)] - pub struct OriginSet: u8 { - /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user-agent> - const ORIGIN_USER_AGENT = Origin::UserAgent as u8; - /// <https://drafts.csswg.org/css-cascade/#cascade-origin-user> - const ORIGIN_USER = Origin::User as u8; - /// <https://drafts.csswg.org/css-cascade/#cascade-origin-author> - const ORIGIN_AUTHOR = Origin::Author as u8; - } -} - -impl OriginSet { - /// Returns an iterator over the origins present in this `OriginSet`. - /// - /// See the `OriginSet` documentation for information about the order - /// origins are iterated. - pub fn iter(&self) -> OriginSetIterator { - OriginSetIterator { - set: *self, - cur: 0, - rev: false, - } - } -} - -impl From<Origin> for OriginSet { - fn from(origin: Origin) -> Self { - Self::from_bits_truncate(origin as u8) - } -} - -impl BitOrAssign<Origin> for OriginSet { - fn bitor_assign(&mut self, origin: Origin) { - *self |= OriginSet::from(origin); - } -} - -/// Iterates over the origins present in an `OriginSet`, in order from -/// highest priority (author) to lower (user agent). -#[derive(Clone)] -pub struct OriginSetIterator { - set: OriginSet, - cur: i8, - rev: bool, -} - -impl Iterator for OriginSetIterator { - type Item = Origin; - - fn next(&mut self) -> Option<Origin> { - loop { - let origin = Origin::from_index(self.cur)?; - - if self.rev { - self.cur -= 1; - } else { - self.cur += 1; - } - - if self.set.contains(origin.into()) { - return Some(origin); - } - } - } -} - -/// An object that stores a `T` for each origin of the CSS cascade. -#[derive(Debug, Default, MallocSizeOf)] -pub struct PerOrigin<T> { - /// Data for `Origin::UserAgent`. - pub user_agent: T, - - /// Data for `Origin::User`. - pub user: T, - - /// Data for `Origin::Author`. - pub author: T, -} - -impl<T> PerOrigin<T> { - /// Returns a reference to the per-origin data for the specified origin. - #[inline] - pub fn borrow_for_origin(&self, origin: &Origin) -> &T { - match *origin { - Origin::UserAgent => &self.user_agent, - Origin::User => &self.user, - Origin::Author => &self.author, - } - } - - /// Returns a mutable reference to the per-origin data for the specified - /// origin. - #[inline] - pub fn borrow_mut_for_origin(&mut self, origin: &Origin) -> &mut T { - match *origin { - Origin::UserAgent => &mut self.user_agent, - Origin::User => &mut self.user, - Origin::Author => &mut self.author, - } - } - - /// Iterates over references to per-origin extra style data, from highest - /// level (author) to lowest (user agent). - pub fn iter_origins(&self) -> PerOriginIter<T> { - PerOriginIter { - data: &self, - cur: 0, - rev: false, - } - } - - /// Iterates over references to per-origin extra style data, from lowest - /// level (user agent) to highest (author). - pub fn iter_origins_rev(&self) -> PerOriginIter<T> { - PerOriginIter { - data: &self, - cur: 2, - rev: true, - } - } - - /// Iterates over mutable references to per-origin extra style data, from - /// highest level (author) to lowest (user agent). - pub fn iter_mut_origins(&mut self) -> PerOriginIterMut<T> { - PerOriginIterMut { - data: self, - cur: 0, - _marker: PhantomData, - } - } -} - -/// Iterator over `PerOrigin<T>`, from highest level (author) to lowest -/// (user agent). -/// -/// We rely on this specific order for correctly looking up @font-face, -/// @counter-style and @keyframes rules. -pub struct PerOriginIter<'a, T: 'a> { - data: &'a PerOrigin<T>, - cur: i8, - rev: bool, -} - -impl<'a, T> Iterator for PerOriginIter<'a, T> -where - T: 'a, -{ - type Item = (&'a T, Origin); - - fn next(&mut self) -> Option<Self::Item> { - let origin = Origin::from_index(self.cur)?; - - self.cur += if self.rev { -1 } else { 1 }; - - Some((self.data.borrow_for_origin(&origin), origin)) - } -} - -/// Like `PerOriginIter<T>`, but iterates over mutable references to the -/// per-origin data. -/// -/// We must use unsafe code here since it's not possible for the borrow -/// checker to know that we are safely returning a different reference -/// each time from `next()`. -pub struct PerOriginIterMut<'a, T: 'a> { - data: *mut PerOrigin<T>, - cur: i8, - _marker: PhantomData<&'a mut PerOrigin<T>>, -} - -impl<'a, T> Iterator for PerOriginIterMut<'a, T> -where - T: 'a, -{ - type Item = (&'a mut T, Origin); - - fn next(&mut self) -> Option<Self::Item> { - let origin = Origin::from_index(self.cur)?; - - self.cur += 1; - - Some(( - unsafe { (*self.data).borrow_mut_for_origin(&origin) }, - origin, - )) - } -} diff --git a/components/style/stylesheets/page_rule.rs b/components/style/stylesheets/page_rule.rs deleted file mode 100644 index 5cd2458aa25..00000000000 --- a/components/style/stylesheets/page_rule.rs +++ /dev/null @@ -1,145 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A [`@page`][page] rule. -//! -//! [page]: https://drafts.csswg.org/css2/page.html#page-box - -use crate::parser::{Parse, ParserContext}; -use crate::properties::PropertyDeclarationBlock; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::values::{AtomIdent, CustomIdent}; -use cssparser::{Parser, SourceLocation}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// Type of a single [`@page`][page selector] -/// -/// We do not support pseudo selectors yet. -/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct PageSelector(pub AtomIdent); - -impl PageSelector { - /// Checks if the ident matches a page-name's ident. - /// - /// This does not currently take pseudo selectors into account. - #[inline] - pub fn ident_matches(&self, other: &CustomIdent) -> bool { - self.0 .0 == other.0 - } -} - -impl Parse for PageSelector { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let s = input.expect_ident()?; - Ok(PageSelector(AtomIdent::from(&**s))) - } -} - -/// A list of [`@page`][page selectors] -/// -/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)] -#[css(comma)] -pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>); - -impl PageSelectors { - /// Creates a new PageSelectors from a Vec, as from parse_comma_separated - #[inline] - pub fn new(s: Vec<PageSelector>) -> Self { - PageSelectors(s.into()) - } - /// Returns true iff there are any page selectors - #[inline] - pub fn is_empty(&self) -> bool { - self.as_slice().is_empty() - } - /// Get the underlying PageSelector data as a slice - #[inline] - pub fn as_slice(&self) -> &[PageSelector] { - &*self.0 - } -} - -impl Parse for PageSelectors { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(PageSelectors::new(input.parse_comma_separated(|i| { - PageSelector::parse(context, i) - })?)) - } -} - -/// A [`@page`][page] rule. -/// -/// This implements only a limited subset of the CSS -/// 2.2 syntax. -/// -/// [page]: https://drafts.csswg.org/css2/page.html#page-box -/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Clone, Debug, ToShmem)] -pub struct PageRule { - /// Selectors of the page-rule - pub selectors: PageSelectors, - /// The declaration block this page rule contains. - pub block: Arc<Locked<PropertyDeclarationBlock>>, - /// The source position this rule was found at. - pub source_location: SourceLocation, -} - -impl PageRule { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - // Measurement of other fields may be added later. - self.block.unconditional_shallow_size_of(ops) + - self.block.read_with(guard).size_of(ops) + - self.selectors.size_of(ops) - } -} - -impl ToCssWithGuard for PageRule { - /// Serialization of PageRule is not specced, adapted from steps for - /// StyleRule. - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@page ")?; - if !self.selectors.is_empty() { - self.selectors.to_css(&mut CssWriter::new(dest))?; - dest.write_char(' ')?; - } - dest.write_str("{ ")?; - let declaration_block = self.block.read_with(guard); - declaration_block.to_css(dest)?; - if !declaration_block.declarations().is_empty() { - dest.write_char(' ')?; - } - dest.write_char('}') - } -} - -impl DeepCloneWithLock for PageRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - _params: &DeepCloneParams, - ) -> Self { - PageRule { - selectors: self.selectors.clone(), - block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), - source_location: self.source_location.clone(), - } - } -} diff --git a/components/style/stylesheets/property_rule.rs b/components/style/stylesheets/property_rule.rs deleted file mode 100644 index 1d1c1c982e9..00000000000 --- a/components/style/stylesheets/property_rule.rs +++ /dev/null @@ -1,5 +0,0 @@ -/* 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/. */ - -pub use crate::properties_and_values::rule::PropertyRuleData as PropertyRule; diff --git a/components/style/stylesheets/rule_list.rs b/components/style/stylesheets/rule_list.rs deleted file mode 100644 index ab747565ffe..00000000000 --- a/components/style/stylesheets/rule_list.rs +++ /dev/null @@ -1,198 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A list of CSS rules. - -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::loader::StylesheetLoader; -use crate::stylesheets::rule_parser::{InsertRuleContext, State}; -use crate::stylesheets::stylesheet::StylesheetContents; -use crate::stylesheets::{AllowImportRules, CssRule, RulesMutateError}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps}; -use servo_arc::Arc; -use std::fmt::{self, Write}; - -/// A list of CSS rules. -#[derive(Debug, ToShmem)] -pub struct CssRules(pub Vec<CssRule>); - -impl CssRules { - /// Whether this CSS rules is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl DeepCloneWithLock for CssRules { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - CssRules( - self.0 - .iter() - .map(|x| x.deep_clone_with_lock(lock, guard, params)) - .collect(), - ) - } -} - -impl CssRules { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - let mut n = self.0.shallow_size_of(ops); - for rule in self.0.iter() { - n += rule.size_of(guard, ops); - } - n - } - - /// Trivially construct a new set of CSS rules. - pub fn new(rules: Vec<CssRule>, shared_lock: &SharedRwLock) -> Arc<Locked<CssRules>> { - Arc::new(shared_lock.wrap(CssRules(rules))) - } - - /// Returns whether all the rules in this list are namespace or import - /// rules. - fn only_ns_or_import(&self) -> bool { - self.0.iter().all(|r| match *r { - CssRule::Namespace(..) | CssRule::Import(..) => true, - _ => false, - }) - } - - /// <https://drafts.csswg.org/cssom/#remove-a-css-rule> - pub fn remove_rule(&mut self, index: usize) -> Result<(), RulesMutateError> { - // Step 1, 2 - if index >= self.0.len() { - return Err(RulesMutateError::IndexSize); - } - - { - // Step 3 - let ref rule = self.0[index]; - - // Step 4 - if let CssRule::Namespace(..) = *rule { - if !self.only_ns_or_import() { - return Err(RulesMutateError::InvalidState); - } - } - } - - // Step 5, 6 - self.0.remove(index); - Ok(()) - } - - /// Serializes this CSSRules to CSS text as a block of rules. - /// - /// This should be speced into CSSOM spec at some point. See - /// <https://github.com/w3c/csswg-drafts/issues/1985> - pub fn to_css_block( - &self, - guard: &SharedRwLockReadGuard, - dest: &mut CssStringWriter, - ) -> fmt::Result { - dest.write_str(" {")?; - self.to_css_block_without_opening(guard, dest) - } - - /// As above, but without the opening curly bracket. That's needed for nesting. - pub fn to_css_block_without_opening( - &self, - guard: &SharedRwLockReadGuard, - dest: &mut CssStringWriter, - ) -> fmt::Result { - for rule in self.0.iter() { - dest.write_str("\n ")?; - rule.to_css(guard, dest)?; - } - dest.write_str("\n}") - } -} - -/// A trait to implement helpers for `Arc<Locked<CssRules>>`. -pub trait CssRulesHelpers { - /// <https://drafts.csswg.org/cssom/#insert-a-css-rule> - /// - /// Written in this funky way because parsing an @import rule may cause us - /// to clone a stylesheet from the same document due to caching in the CSS - /// loader. - /// - /// TODO(emilio): We could also pass the write guard down into the loader - /// instead, but that seems overkill. - fn insert_rule( - &self, - lock: &SharedRwLock, - rule: &str, - parent_stylesheet_contents: &StylesheetContents, - index: usize, - nested: bool, - loader: Option<&dyn StylesheetLoader>, - allow_import_rules: AllowImportRules, - ) -> Result<CssRule, RulesMutateError>; -} - -impl CssRulesHelpers for Locked<CssRules> { - fn insert_rule( - &self, - lock: &SharedRwLock, - rule: &str, - parent_stylesheet_contents: &StylesheetContents, - index: usize, - nested: bool, - loader: Option<&dyn StylesheetLoader>, - allow_import_rules: AllowImportRules, - ) -> Result<CssRule, RulesMutateError> { - let new_rule = { - let read_guard = lock.read(); - let rules = self.read_with(&read_guard); - - // Step 1, 2 - if index > rules.0.len() { - return Err(RulesMutateError::IndexSize); - } - - // Computes the parser state at the given index - let insert_rule_context = InsertRuleContext { - rule_list: &rules.0, - index, - }; - - let state = if nested { - State::Body - } else if index == 0 { - State::Start - } else { - insert_rule_context.max_rule_state_at_index(index - 1) - }; - - // Steps 3, 4, 5, 6 - CssRule::parse( - &rule, - insert_rule_context, - parent_stylesheet_contents, - lock, - state, - loader, - allow_import_rules, - )? - }; - - { - let mut write_guard = lock.write(); - let rules = self.write_with(&mut write_guard); - rules.0.insert(index, new_rule.clone()); - } - - Ok(new_rule) - } -} diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs deleted file mode 100644 index 71b27f2c943..00000000000 --- a/components/style/stylesheets/rule_parser.rs +++ /dev/null @@ -1,867 +0,0 @@ -/* 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/. */ - -//! Parsing of the stylesheet contents. - -use crate::counter_style::{parse_counter_style_body, parse_counter_style_name_definition}; -use crate::custom_properties::parse_name as parse_custom_property_name; -use crate::error_reporting::ContextualParseError; -use crate::font_face::parse_font_face_block; -use crate::media_queries::MediaList; -use crate::parser::{Parse, ParserContext}; -use crate::properties::declaration_block::{ - parse_property_declaration_list, DeclarationParserState, PropertyDeclarationBlock, -}; -use crate::properties_and_values::rule::{parse_property_block, PropertyRuleName}; -use crate::selector_parser::{SelectorImpl, SelectorParser}; -use crate::shared_lock::{Locked, SharedRwLock}; -use crate::str::starts_with_ignore_ascii_case; -use crate::stylesheets::container_rule::{ContainerCondition, ContainerRule}; -use crate::stylesheets::document_rule::DocumentCondition; -use crate::stylesheets::font_feature_values_rule::parse_family_name_list; -use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCondition}; -use crate::stylesheets::keyframes_rule::parse_keyframe_list; -use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule}; -use crate::stylesheets::supports_rule::SupportsCondition; -use crate::stylesheets::{ - AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, - FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, MediaRule, NamespaceRule, - PageRule, PageSelectors, RulesMutateError, StyleRule, StylesheetLoader, SupportsRule, -}; -use crate::values::computed::font::FamilyName; -use crate::values::{CssUrl, CustomIdent, DashedIdent, KeyframesName}; -use crate::{Atom, Namespace, Prefix}; -use cssparser::{ - AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, DeclarationParser, Parser, - ParserState, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation, - SourcePosition, -}; -use selectors::SelectorList; -use servo_arc::Arc; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// The information we need particularly to do CSSOM insertRule stuff. -pub struct InsertRuleContext<'a> { - /// The rule list we're about to insert into. - pub rule_list: &'a [CssRule], - /// The index we're about to get inserted at. - pub index: usize, -} - -impl<'a> InsertRuleContext<'a> { - /// Returns the max rule state allowable for insertion at a given index in - /// the rule list. - pub fn max_rule_state_at_index(&self, index: usize) -> State { - let rule = match self.rule_list.get(index) { - Some(rule) => rule, - None => return State::Body, - }; - match rule { - CssRule::Import(..) => State::Imports, - CssRule::Namespace(..) => State::Namespaces, - CssRule::LayerStatement(..) => { - // If there are @import / @namespace after this layer, then - // we're in the early-layers phase, otherwise we're in the body - // and everything is fair game. - let next_non_layer_statement_rule = self.rule_list[index + 1..] - .iter() - .find(|r| !matches!(*r, CssRule::LayerStatement(..))); - if let Some(non_layer) = next_non_layer_statement_rule { - if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) { - return State::EarlyLayers; - } - } - State::Body - }, - _ => State::Body, - } - } -} - -/// The parser for the top-level rules in a stylesheet. -pub struct TopLevelRuleParser<'a, 'i> { - /// A reference to the lock we need to use to create rules. - pub shared_lock: &'a SharedRwLock, - /// A reference to a stylesheet loader if applicable, for `@import` rules. - pub loader: Option<&'a dyn StylesheetLoader>, - /// The top-level parser context. - pub context: ParserContext<'a>, - /// The current state of the parser. - pub state: State, - /// Whether we have tried to parse was invalid due to being in the wrong - /// place (e.g. an @import rule was found while in the `Body` state). Reset - /// to `false` when `take_had_hierarchy_error` is called. - pub dom_error: Option<RulesMutateError>, - /// The info we need insert a rule in a list. - pub insert_rule_context: Option<InsertRuleContext<'a>>, - /// Whether @import rules will be allowed. - pub allow_import_rules: AllowImportRules, - /// Parser state for declaration blocks in either nested rules or style rules. - pub declaration_parser_state: DeclarationParserState<'i>, - /// The rules we've parsed so far. - pub rules: Vec<CssRule>, -} - -impl<'a, 'i> TopLevelRuleParser<'a, 'i> { - fn nested<'b>(&'b mut self) -> NestedRuleParser<'b, 'a, 'i> { - NestedRuleParser { - shared_lock: self.shared_lock, - context: &mut self.context, - declaration_parser_state: &mut self.declaration_parser_state, - rules: &mut self.rules, - } - } - - /// Returns the current state of the parser. - pub fn state(&self) -> State { - self.state - } - - /// Checks whether we can parse a rule that would transition us to - /// `new_state`. - /// - /// This is usually a simple branch, but we may need more bookkeeping if - /// doing `insertRule` from CSSOM. - fn check_state(&mut self, new_state: State) -> bool { - if self.state > new_state { - self.dom_error = Some(RulesMutateError::HierarchyRequest); - return false; - } - - let ctx = match self.insert_rule_context { - Some(ref ctx) => ctx, - None => return true, - }; - - let max_rule_state = ctx.max_rule_state_at_index(ctx.index); - if new_state > max_rule_state { - self.dom_error = Some(RulesMutateError::HierarchyRequest); - return false; - } - - // If there's anything that isn't a namespace rule (or import rule, but - // we checked that already at the beginning), reject with a - // StateError. - if new_state == State::Namespaces && - ctx.rule_list[ctx.index..] - .iter() - .any(|r| !matches!(*r, CssRule::Namespace(..))) - { - self.dom_error = Some(RulesMutateError::InvalidState); - return false; - } - - true - } -} - -/// The current state of the parser. -#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] -pub enum State { - /// We haven't started parsing rules. - Start = 1, - /// We're parsing early `@layer` statement rules. - EarlyLayers = 2, - /// We're parsing `@import` and early `@layer` statement rules. - Imports = 3, - /// We're parsing `@namespace` rules. - Namespaces = 4, - /// We're parsing the main body of the stylesheet. - Body = 5, -} - -#[derive(Clone, Debug, MallocSizeOf, ToShmem)] -/// Vendor prefix. -pub enum VendorPrefix { - /// -moz prefix. - Moz, - /// -webkit prefix. - WebKit, -} - -/// A rule prelude for at-rule with block. -pub enum AtRulePrelude { - /// A @font-face rule prelude. - FontFace, - /// A @font-feature-values rule prelude, with its FamilyName list. - FontFeatureValues(Vec<FamilyName>), - /// A @font-palette-values rule prelude, with its identifier. - FontPaletteValues(DashedIdent), - /// A @counter-style rule prelude, with its counter style name. - CounterStyle(CustomIdent), - /// A @media rule prelude, with its media queries. - Media(Arc<Locked<MediaList>>), - /// A @container rule prelude. - Container(Arc<ContainerCondition>), - /// An @supports rule, with its conditional - Supports(SupportsCondition), - /// A @keyframes rule, with its animation name and vendor prefix if exists. - Keyframes(KeyframesName, Option<VendorPrefix>), - /// A @page rule prelude, with its page name if it exists. - Page(PageSelectors), - /// A @property rule prelude. - Property(PropertyRuleName), - /// A @document rule, with its conditional. - Document(DocumentCondition), - /// A @import rule prelude. - Import( - CssUrl, - Arc<Locked<MediaList>>, - Option<ImportSupportsCondition>, - ImportLayer, - ), - /// A @namespace rule prelude. - Namespace(Option<Prefix>, Namespace), - /// A @layer rule prelude. - Layer(Vec<LayerName>), -} - -impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a, 'i> { - type Prelude = AtRulePrelude; - type AtRule = SourcePosition; - type Error = StyleParseErrorKind<'i>; - - fn parse_prelude<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<AtRulePrelude, ParseError<'i>> { - match_ignore_ascii_case! { &*name, - "import" => { - if !self.check_state(State::Imports) { - return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule)) - } - - if let AllowImportRules::No = self.allow_import_rules { - return Err(input.new_custom_error(StyleParseErrorKind::DisallowedImportRule)) - } - - // FIXME(emilio): We should always be able to have a loader - // around! See bug 1533783. - if self.loader.is_none() { - error!("Saw @import rule, but no way to trigger the load"); - return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedImportRule)) - } - - let url_string = input.expect_url_or_string()?.as_ref().to_owned(); - let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None); - - let (layer, supports) = ImportRule::parse_layer_and_supports(input, &mut self.context); - - let media = MediaList::parse(&self.context, input); - let media = Arc::new(self.shared_lock.wrap(media)); - - return Ok(AtRulePrelude::Import(url, media, supports, layer)); - }, - "namespace" => { - if !self.check_state(State::Namespaces) { - return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedNamespaceRule)) - } - - let prefix = input.try_parse(|i| i.expect_ident_cloned()) - .map(|s| Prefix::from(s.as_ref())).ok(); - let maybe_namespace = match input.expect_url_or_string() { - Ok(url_or_string) => url_or_string, - Err(BasicParseError { kind: BasicParseErrorKind::UnexpectedToken(t), location }) => { - return Err(location.new_custom_error(StyleParseErrorKind::UnexpectedTokenWithinNamespace(t))) - } - Err(e) => return Err(e.into()), - }; - let url = Namespace::from(maybe_namespace.as_ref()); - return Ok(AtRulePrelude::Namespace(prefix, url)); - }, - // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet - // anything left is invalid. - "charset" => { - self.dom_error = Some(RulesMutateError::HierarchyRequest); - return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule)) - }, - "layer" => { - let state_to_check = if self.state <= State::EarlyLayers { - // The real state depends on whether there's a block or not. - // We don't know that yet, but the parse_block check deals - // with that. - State::EarlyLayers - } else { - State::Body - }; - if !self.check_state(state_to_check) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }, - _ => { - // All other rules have blocks, so we do this check early in - // parse_block instead. - } - } - - AtRuleParser::parse_prelude(&mut self.nested(), name, input) - } - - #[inline] - fn parse_block<'t>( - &mut self, - prelude: AtRulePrelude, - start: &ParserState, - input: &mut Parser<'i, 't>, - ) -> Result<Self::AtRule, ParseError<'i>> { - if !self.check_state(State::Body) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?; - self.state = State::Body; - Ok(start.position()) - } - - #[inline] - fn rule_without_block( - &mut self, - prelude: AtRulePrelude, - start: &ParserState, - ) -> Result<Self::AtRule, ()> { - match prelude { - AtRulePrelude::Import(url, media, supports, layer) => { - let loader = self - .loader - .expect("Expected a stylesheet loader for @import"); - - let import_rule = loader.request_stylesheet( - url, - start.source_location(), - &self.context, - &self.shared_lock, - media, - supports, - layer, - ); - - self.state = State::Imports; - self.rules.push(CssRule::Import(import_rule)) - }, - AtRulePrelude::Namespace(prefix, url) => { - let namespaces = self.context.namespaces.to_mut(); - let prefix = if let Some(prefix) = prefix { - namespaces.prefixes.insert(prefix.clone(), url.clone()); - Some(prefix) - } else { - namespaces.default = Some(url.clone()); - None - }; - - self.state = State::Namespaces; - self.rules.push(CssRule::Namespace(Arc::new(NamespaceRule { - prefix, - url, - source_location: start.source_location(), - }))); - }, - AtRulePrelude::Layer(..) => { - AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)?; - if self.state <= State::EarlyLayers { - self.state = State::EarlyLayers; - } else { - self.state = State::Body; - } - }, - _ => AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)?, - }; - - Ok(start.position()) - } -} - -impl<'a, 'i> QualifiedRuleParser<'i> for TopLevelRuleParser<'a, 'i> { - type Prelude = SelectorList<SelectorImpl>; - type QualifiedRule = SourcePosition; - type Error = StyleParseErrorKind<'i>; - - #[inline] - fn parse_prelude<'t>( - &mut self, - input: &mut Parser<'i, 't>, - ) -> Result<Self::Prelude, ParseError<'i>> { - if !self.check_state(State::Body) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - QualifiedRuleParser::parse_prelude(&mut self.nested(), input) - } - - #[inline] - fn parse_block<'t>( - &mut self, - prelude: Self::Prelude, - start: &ParserState, - input: &mut Parser<'i, 't>, - ) -> Result<Self::QualifiedRule, ParseError<'i>> { - QualifiedRuleParser::parse_block(&mut self.nested(), prelude, start, input)?; - self.state = State::Body; - Ok(start.position()) - } -} - -struct NestedRuleParser<'a, 'b: 'a, 'i> { - shared_lock: &'a SharedRwLock, - context: &'a mut ParserContext<'b>, - declaration_parser_state: &'a mut DeclarationParserState<'i>, - rules: &'a mut Vec<CssRule>, -} - -struct NestedParseResult { - rules: Vec<CssRule>, - declarations: PropertyDeclarationBlock, -} - -impl NestedParseResult { - fn into_rules( - mut self, - shared_lock: &SharedRwLock, - source_location: SourceLocation, - ) -> Arc<Locked<CssRules>> { - lazy_static! { - static ref AMPERSAND: SelectorList<SelectorImpl> = { - let list = SelectorList::ampersand(); - list.0 - .iter() - .for_each(|selector| selector.mark_as_intentionally_leaked()); - list - }; - }; - - if !self.declarations.is_empty() { - self.rules.insert( - 0, - CssRule::Style(Arc::new(shared_lock.wrap(StyleRule { - selectors: AMPERSAND.clone(), - block: Arc::new(shared_lock.wrap(self.declarations)), - rules: None, - source_location, - }))), - ) - } - - CssRules::new(self.rules, shared_lock) - } -} - -impl<'a, 'b, 'i> NestedRuleParser<'a, 'b, 'i> { - /// When nesting is disabled, we prevent parsing at rules and qualified rules inside style - /// rules. - fn allow_at_and_qualified_rules(&self) -> bool { - if !self.context.rule_types.contains(CssRuleType::Style) { - return true; - } - static_prefs::pref!("layout.css.nesting.enabled") - } - - fn nest_for_rule<R>(&mut self, rule_type: CssRuleType, cb: impl FnOnce(&mut Self) -> R) -> R { - let old_rule_types = self.context.rule_types; - self.context.rule_types.insert(rule_type); - let r = cb(self); - self.context.rule_types = old_rule_types; - r - } - - fn parse_nested( - &mut self, - input: &mut Parser<'i, '_>, - rule_type: CssRuleType, - selectors: Option<&SelectorList<SelectorImpl>>, - ) -> NestedParseResult { - self.nest_for_rule(rule_type, |parser| { - let parse_declarations = parser.parse_declarations(); - let mut old_declaration_state = std::mem::take(parser.declaration_parser_state); - let mut rules = std::mem::take(parser.rules); - let mut iter = RuleBodyParser::new(input, parser); - while let Some(result) = iter.next() { - match result { - Ok(()) => {}, - Err((error, slice)) => { - if parse_declarations { - iter.parser.declaration_parser_state.did_error( - iter.parser.context, - error, - slice, - ); - } else { - let location = error.location; - let error = ContextualParseError::InvalidRule(slice, error); - iter.parser.context.log_css_error(location, error); - } - }, - } - } - let declarations = if parse_declarations { - parser - .declaration_parser_state - .report_errors_if_needed(parser.context, selectors); - parser.declaration_parser_state.take_declarations() - } else { - PropertyDeclarationBlock::default() - }; - debug_assert!( - !parser.declaration_parser_state.has_parsed_declarations(), - "Parsed but didn't consume declarations" - ); - std::mem::swap(parser.declaration_parser_state, &mut old_declaration_state); - std::mem::swap(parser.rules, &mut rules); - NestedParseResult { - rules, - declarations, - } - }) - } -} - -impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b, 'i> { - type Prelude = AtRulePrelude; - type AtRule = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_prelude<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<Self::Prelude, ParseError<'i>> { - if !self.allow_at_and_qualified_rules() { - return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name))); - } - Ok(match_ignore_ascii_case! { &*name, - "media" => { - let media_queries = MediaList::parse(self.context, input); - let arc = Arc::new(self.shared_lock.wrap(media_queries)); - AtRulePrelude::Media(arc) - }, - "supports" => { - let cond = SupportsCondition::parse(input)?; - AtRulePrelude::Supports(cond) - }, - "font-face" => { - AtRulePrelude::FontFace - }, - "container" if static_prefs::pref!("layout.css.container-queries.enabled") => { - let condition = Arc::new(ContainerCondition::parse(self.context, input)?); - AtRulePrelude::Container(condition) - }, - "layer" => { - let names = input.try_parse(|input| { - input.parse_comma_separated(|input| { - LayerName::parse(self.context, input) - }) - }).unwrap_or_default(); - AtRulePrelude::Layer(names) - }, - "font-feature-values" if cfg!(feature = "gecko") => { - let family_names = parse_family_name_list(self.context, input)?; - AtRulePrelude::FontFeatureValues(family_names) - }, - "font-palette-values" if static_prefs::pref!("layout.css.font-palette.enabled") => { - let name = DashedIdent::parse(self.context, input)?; - AtRulePrelude::FontPaletteValues(name) - }, - "counter-style" if cfg!(feature = "gecko") => { - let name = parse_counter_style_name_definition(input)?; - AtRulePrelude::CounterStyle(name) - }, - "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { - let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") { - Some(VendorPrefix::WebKit) - } else if starts_with_ignore_ascii_case(&*name, "-moz-") { - Some(VendorPrefix::Moz) - } else { - None - }; - if cfg!(feature = "servo") && - prefix.as_ref().map_or(false, |p| matches!(*p, VendorPrefix::Moz)) { - // Servo should not support @-moz-keyframes. - return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()))) - } - let name = KeyframesName::parse(self.context, input)?; - AtRulePrelude::Keyframes(name, prefix) - }, - "page" if cfg!(feature = "gecko") => { - AtRulePrelude::Page( - input.try_parse(|i| PageSelectors::parse(self.context, i)).unwrap_or_default() - ) - }, - "property" if static_prefs::pref!("layout.css.properties-and-values.enabled") => { - let name = input.expect_ident_cloned()?; - let name = parse_custom_property_name(&name).map_err(|_| { - input.new_custom_error(StyleParseErrorKind::UnexpectedIdent(name.clone())) - })?; - AtRulePrelude::Property(PropertyRuleName(Arc::new(Atom::from(name)))) - }, - "-moz-document" if cfg!(feature = "gecko") => { - let cond = DocumentCondition::parse(self.context, input)?; - AtRulePrelude::Document(cond) - }, - _ => return Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()))) - }) - } - - fn parse_block<'t>( - &mut self, - prelude: AtRulePrelude, - start: &ParserState, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - let rule = match prelude { - AtRulePrelude::FontFace => self.nest_for_rule(CssRuleType::FontFace, |p| { - CssRule::FontFace(Arc::new(p.shared_lock.wrap( - parse_font_face_block(&p.context, input, start.source_location()).into(), - ))) - }), - AtRulePrelude::FontFeatureValues(family_names) => { - self.nest_for_rule(CssRuleType::FontFeatureValues, |p| { - CssRule::FontFeatureValues(Arc::new(FontFeatureValuesRule::parse( - &p.context, - input, - family_names, - start.source_location(), - ))) - }) - }, - AtRulePrelude::FontPaletteValues(name) => { - self.nest_for_rule(CssRuleType::FontPaletteValues, |p| { - CssRule::FontPaletteValues(Arc::new(FontPaletteValuesRule::parse( - &p.context, - input, - name, - start.source_location(), - ))) - }) - }, - AtRulePrelude::CounterStyle(name) => { - let body = self.nest_for_rule(CssRuleType::CounterStyle, |p| { - parse_counter_style_body(name, &p.context, input, start.source_location()) - })?; - CssRule::CounterStyle(Arc::new(self.shared_lock.wrap(body))) - }, - AtRulePrelude::Media(media_queries) => { - let source_location = start.source_location(); - CssRule::Media(Arc::new(MediaRule { - media_queries, - rules: self - .parse_nested(input, CssRuleType::Media, None) - .into_rules(self.shared_lock, source_location), - source_location, - })) - }, - AtRulePrelude::Supports(condition) => { - let enabled = - self.nest_for_rule(CssRuleType::Style, |p| condition.eval(&p.context)); - let source_location = start.source_location(); - CssRule::Supports(Arc::new(SupportsRule { - condition, - rules: self - .parse_nested(input, CssRuleType::Supports, None) - .into_rules(self.shared_lock, source_location), - enabled, - source_location, - })) - }, - AtRulePrelude::Keyframes(name, vendor_prefix) => { - self.nest_for_rule(CssRuleType::Keyframe, |p| { - CssRule::Keyframes(Arc::new(p.shared_lock.wrap(KeyframesRule { - name, - keyframes: parse_keyframe_list(&mut p.context, input, p.shared_lock), - vendor_prefix, - source_location: start.source_location(), - }))) - }) - }, - AtRulePrelude::Page(selectors) => { - let declarations = self.nest_for_rule(CssRuleType::Page, |p| { - // TODO: Support nesting in @page rules? - parse_property_declaration_list(&p.context, input, None) - }); - CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule { - selectors, - block: Arc::new(self.shared_lock.wrap(declarations)), - source_location: start.source_location(), - }))) - }, - AtRulePrelude::Property(name) => self.nest_for_rule(CssRuleType::Property, |p| { - CssRule::Property(Arc::new(parse_property_block( - &p.context, - input, - name, - start.source_location(), - ))) - }), - AtRulePrelude::Document(condition) => { - if !cfg!(feature = "gecko") { - unreachable!() - } - let source_location = start.source_location(); - CssRule::Document(Arc::new(DocumentRule { - condition, - rules: self - .parse_nested(input, CssRuleType::Document, None) - .into_rules(self.shared_lock, source_location), - source_location, - })) - }, - AtRulePrelude::Container(condition) => { - let source_location = start.source_location(); - CssRule::Container(Arc::new(ContainerRule { - condition, - rules: self - .parse_nested(input, CssRuleType::Container, None) - .into_rules(self.shared_lock, source_location), - source_location, - })) - }, - AtRulePrelude::Layer(names) => { - let name = match names.len() { - 0 | 1 => names.into_iter().next(), - _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), - }; - let source_location = start.source_location(); - CssRule::LayerBlock(Arc::new(LayerBlockRule { - name, - rules: self - .parse_nested(input, CssRuleType::LayerBlock, None) - .into_rules(self.shared_lock, source_location), - source_location, - })) - }, - AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { - // These rules don't have blocks. - return Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)); - }, - }; - self.rules.push(rule); - Ok(()) - } - - #[inline] - fn rule_without_block( - &mut self, - prelude: AtRulePrelude, - start: &ParserState, - ) -> Result<(), ()> { - let rule = match prelude { - AtRulePrelude::Layer(names) => { - if names.is_empty() { - return Err(()); - } - CssRule::LayerStatement(Arc::new(LayerStatementRule { - names, - source_location: start.source_location(), - })) - }, - _ => return Err(()), - }; - self.rules.push(rule); - Ok(()) - } -} - -#[inline(never)] -fn check_for_useless_selector( - input: &mut Parser, - context: &ParserContext, - selectors: &SelectorList<SelectorImpl>, -) { - use cssparser::ToCss; - - 'selector_loop: for selector in selectors.0.iter() { - let mut current = selector.iter(); - loop { - let mut found_host = false; - let mut found_non_host = false; - for component in &mut current { - if component.is_host() { - found_host = true; - } else { - found_non_host = true; - } - if found_host && found_non_host { - let location = input.current_source_location(); - context.log_css_error( - location, - ContextualParseError::NeverMatchingHostSelector(selector.to_css_string()), - ); - continue 'selector_loop; - } - } - if current.next_sequence().is_none() { - break; - } - } - } -} - -impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b, 'i> { - type Prelude = SelectorList<SelectorImpl>; - type QualifiedRule = (); - type Error = StyleParseErrorKind<'i>; - - fn parse_prelude<'t>( - &mut self, - input: &mut Parser<'i, 't>, - ) -> Result<Self::Prelude, ParseError<'i>> { - let selector_parser = SelectorParser { - stylesheet_origin: self.context.stylesheet_origin, - namespaces: &self.context.namespaces, - url_data: self.context.url_data, - for_supports_rule: false, - }; - let selectors = SelectorList::parse(&selector_parser, input)?; - if self.context.error_reporting_enabled() { - check_for_useless_selector(input, &self.context, &selectors); - } - Ok(selectors) - } - - fn parse_block<'t>( - &mut self, - selectors: Self::Prelude, - start: &ParserState, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - let result = self.parse_nested(input, CssRuleType::Style, Some(&selectors)); - let block = Arc::new(self.shared_lock.wrap(result.declarations)); - self.rules - .push(CssRule::Style(Arc::new(self.shared_lock.wrap(StyleRule { - selectors, - block, - rules: if result.rules.is_empty() { - None - } else { - Some(CssRules::new(result.rules, self.shared_lock)) - }, - source_location: start.source_location(), - })))); - Ok(()) - } -} - -impl<'a, 'b, 'i> DeclarationParser<'i> for NestedRuleParser<'a, 'b, 'i> { - type Declaration = (); - type Error = StyleParseErrorKind<'i>; - fn parse_value<'t>( - &mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>, - ) -> Result<(), ParseError<'i>> { - self.declaration_parser_state - .parse_value(self.context, name, input) - } -} - -impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>> - for NestedRuleParser<'a, 'b, 'i> -{ - fn parse_qualified(&self) -> bool { - self.allow_at_and_qualified_rules() - } - - /// If nesting is disabled, we can't get there for a non-style-rule. If it's enabled, we parse - /// raw declarations there. - fn parse_declarations(&self) -> bool { - self.context.rule_types.contains(CssRuleType::Style) - } -} diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs deleted file mode 100644 index 0ab2b42c0bc..00000000000 --- a/components/style/stylesheets/rules_iterator.rs +++ /dev/null @@ -1,326 +0,0 @@ -/* 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/. */ - -//! An iterator over a list of rules. - -use crate::context::QuirksMode; -use crate::media_queries::Device; -use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, SupportsRule}; -use smallvec::SmallVec; -use std::slice; - -/// An iterator over a list of rules. -pub struct RulesIterator<'a, 'b, C> -where - 'b: 'a, - C: NestedRuleIterationCondition + 'static, -{ - device: &'a Device, - quirks_mode: QuirksMode, - guard: &'a SharedRwLockReadGuard<'b>, - stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>, - _phantom: ::std::marker::PhantomData<C>, -} - -impl<'a, 'b, C> RulesIterator<'a, 'b, C> -where - 'b: 'a, - C: NestedRuleIterationCondition + 'static, -{ - /// Creates a new `RulesIterator` to iterate over `rules`. - pub fn new( - device: &'a Device, - quirks_mode: QuirksMode, - guard: &'a SharedRwLockReadGuard<'b>, - rules: slice::Iter<'a, CssRule>, - ) -> Self { - let mut stack = SmallVec::new(); - stack.push(rules); - Self { - device, - quirks_mode, - guard, - stack, - _phantom: ::std::marker::PhantomData, - } - } - - /// Skips all the remaining children of the last nested rule processed. - pub fn skip_children(&mut self) { - self.stack.pop(); - } - - /// Returns the children of `rule`, and whether `rule` is effective. - pub fn children( - rule: &'a CssRule, - device: &'a Device, - quirks_mode: QuirksMode, - guard: &'a SharedRwLockReadGuard<'_>, - effective: &mut bool, - ) -> Option<slice::Iter<'a, CssRule>> { - *effective = true; - match *rule { - CssRule::Namespace(_) | - CssRule::FontFace(_) | - CssRule::CounterStyle(_) | - CssRule::Keyframes(_) | - CssRule::Page(_) | - CssRule::Property(_) | - CssRule::LayerStatement(_) | - CssRule::FontFeatureValues(_) | - CssRule::FontPaletteValues(_) => None, - CssRule::Style(ref style_rule) => { - let style_rule = style_rule.read_with(guard); - style_rule - .rules - .as_ref() - .map(|r| r.read_with(guard).0.iter()) - }, - CssRule::Import(ref import_rule) => { - let import_rule = import_rule.read_with(guard); - if !C::process_import(guard, device, quirks_mode, import_rule) { - *effective = false; - return None; - } - Some(import_rule.stylesheet.rules(guard).iter()) - }, - CssRule::Document(ref doc_rule) => { - if !C::process_document(guard, device, quirks_mode, doc_rule) { - *effective = false; - return None; - } - Some(doc_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Container(ref container_rule) => { - Some(container_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Media(ref media_rule) => { - if !C::process_media(guard, device, quirks_mode, media_rule) { - *effective = false; - return None; - } - Some(media_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Supports(ref supports_rule) => { - if !C::process_supports(guard, device, quirks_mode, supports_rule) { - *effective = false; - return None; - } - Some(supports_rule.rules.read_with(guard).0.iter()) - }, - CssRule::LayerBlock(ref layer_rule) => Some(layer_rule.rules.read_with(guard).0.iter()), - } - } -} - -impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C> -where - 'b: 'a, - C: NestedRuleIterationCondition + 'static, -{ - type Item = &'a CssRule; - - fn next(&mut self) -> Option<Self::Item> { - while !self.stack.is_empty() { - let rule = { - let nested_iter = self.stack.last_mut().unwrap(); - match nested_iter.next() { - Some(r) => r, - None => { - self.stack.pop(); - continue; - }, - } - }; - - let mut effective = true; - let children = Self::children( - rule, - self.device, - self.quirks_mode, - self.guard, - &mut effective, - ); - if !effective { - continue; - } - - if let Some(children) = children { - // NOTE: It's important that `children` gets pushed even if - // empty, so that `skip_children()` works as expected. - self.stack.push(children); - } - - return Some(rule); - } - - None - } -} - -/// RulesIterator. -pub trait NestedRuleIterationCondition { - /// Whether we should process the nested rules in a given `@import` rule. - fn process_import( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &ImportRule, - ) -> bool; - - /// Whether we should process the nested rules in a given `@media` rule. - fn process_media( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &MediaRule, - ) -> bool; - - /// Whether we should process the nested rules in a given `@-moz-document` - /// rule. - fn process_document( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &DocumentRule, - ) -> bool; - - /// Whether we should process the nested rules in a given `@supports` rule. - fn process_supports( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &SupportsRule, - ) -> bool; -} - -/// A struct that represents the condition that a rule applies to the document. -pub struct EffectiveRules; - -impl EffectiveRules { - /// Returns whether a given rule is effective. - pub fn is_effective( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &CssRule, - ) -> bool { - match *rule { - CssRule::Import(ref import_rule) => { - let import_rule = import_rule.read_with(guard); - Self::process_import(guard, device, quirks_mode, import_rule) - }, - CssRule::Document(ref doc_rule) => { - Self::process_document(guard, device, quirks_mode, doc_rule) - }, - CssRule::Media(ref media_rule) => { - Self::process_media(guard, device, quirks_mode, media_rule) - }, - CssRule::Supports(ref supports_rule) => { - Self::process_supports(guard, device, quirks_mode, supports_rule) - }, - _ => true, - } - } -} - -impl NestedRuleIterationCondition for EffectiveRules { - fn process_import( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &ImportRule, - ) -> bool { - match rule.stylesheet.media(guard) { - Some(m) => m.evaluate(device, quirks_mode), - None => true, - } - } - - fn process_media( - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - rule: &MediaRule, - ) -> bool { - rule.media_queries - .read_with(guard) - .evaluate(device, quirks_mode) - } - - fn process_document( - _: &SharedRwLockReadGuard, - device: &Device, - _: QuirksMode, - rule: &DocumentRule, - ) -> bool { - rule.condition.evaluate(device) - } - - fn process_supports( - _: &SharedRwLockReadGuard, - _: &Device, - _: QuirksMode, - rule: &SupportsRule, - ) -> bool { - rule.enabled - } -} - -/// A filter that processes all the rules in a rule list. -pub struct AllRules; - -impl NestedRuleIterationCondition for AllRules { - fn process_import( - _: &SharedRwLockReadGuard, - _: &Device, - _: QuirksMode, - _: &ImportRule, - ) -> bool { - true - } - - fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool { - true - } - - fn process_document( - _: &SharedRwLockReadGuard, - _: &Device, - _: QuirksMode, - _: &DocumentRule, - ) -> bool { - true - } - - fn process_supports( - _: &SharedRwLockReadGuard, - _: &Device, - _: QuirksMode, - _: &SupportsRule, - ) -> bool { - true - } -} - -/// An iterator over all the effective rules of a stylesheet. -/// -/// NOTE: This iterator recurses into `@import` rules. -pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>; - -impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { - /// Returns an iterator over the effective children of a rule, even if - /// `rule` itself is not effective. - pub fn effective_children( - device: &'a Device, - quirks_mode: QuirksMode, - guard: &'a SharedRwLockReadGuard<'b>, - rule: &'a CssRule, - ) -> Self { - let children = - RulesIterator::<AllRules>::children(rule, device, quirks_mode, guard, &mut false); - EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) - } -} diff --git a/components/style/stylesheets/style_rule.rs b/components/style/stylesheets/style_rule.rs deleted file mode 100644 index d5a22d6fc20..00000000000 --- a/components/style/stylesheets/style_rule.rs +++ /dev/null @@ -1,104 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A style rule. - -use crate::properties::PropertyDeclarationBlock; -use crate::selector_parser::SelectorImpl; -use crate::shared_lock::{ - DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, -}; -use crate::str::CssStringWriter; -use crate::stylesheets::CssRules; -use cssparser::SourceLocation; -#[cfg(feature = "gecko")] -use malloc_size_of::MallocUnconditionalShallowSizeOf; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use selectors::SelectorList; -use servo_arc::Arc; -use std::fmt::{self, Write}; - -/// A style rule, with selectors and declarations. -#[derive(Debug, ToShmem)] -pub struct StyleRule { - /// The list of selectors in this rule. - pub selectors: SelectorList<SelectorImpl>, - /// The declaration block with the properties it contains. - pub block: Arc<Locked<PropertyDeclarationBlock>>, - /// The nested rules to this style rule. Only non-`None` when nesting is enabled. - pub rules: Option<Arc<Locked<CssRules>>>, - /// The location in the sheet where it was found. - pub source_location: SourceLocation, -} - -impl DeepCloneWithLock for StyleRule { - /// Deep clones this StyleRule. - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> StyleRule { - StyleRule { - selectors: self.selectors.clone(), - block: Arc::new(lock.wrap(self.block.read_with(guard).clone())), - rules: self.rules.as_ref().map(|rules| { - let rules = rules.read_with(guard); - Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))) - }), - source_location: self.source_location.clone(), - } - } -} - -impl StyleRule { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - n += self.selectors.0.size_of(ops); - n += self.block.unconditional_shallow_size_of(ops) + - self.block.read_with(guard).size_of(ops); - if let Some(ref rules) = self.rules { - n += rules.unconditional_shallow_size_of(ops) + - rules.read_with(guard).size_of(guard, ops) - } - n - } -} - -impl ToCssWithGuard for StyleRule { - /// https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSStyleRule - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - use cssparser::ToCss; - // Step 1 - self.selectors.to_css(dest)?; - dest.write_str(" {")?; - - // Step 2 - let declaration_block = self.block.read_with(guard); - let has_declarations = !declaration_block.declarations().is_empty(); - - // Step 3 - if let Some(ref rules) = self.rules { - let rules = rules.read_with(guard); - // Step 6 (here because it's more convenient) - if !rules.is_empty() { - if has_declarations { - dest.write_str("\n ")?; - declaration_block.to_css(dest)?; - } - return rules.to_css_block_without_opening(guard, dest); - } - } - - // Steps 4 & 5 - if has_declarations { - dest.write_char(' ')?; - declaration_block.to_css(dest)?; - } - dest.write_str(" }") - } -} diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs deleted file mode 100644 index 6c205e03c66..00000000000 --- a/components/style/stylesheets/stylesheet.rs +++ /dev/null @@ -1,605 +0,0 @@ -/* 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/. */ - -use crate::context::QuirksMode; -use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; -use crate::media_queries::{Device, MediaList}; -use crate::parser::ParserContext; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard}; -use crate::stylesheets::loader::StylesheetLoader; -use crate::stylesheets::rule_parser::{State, TopLevelRuleParser}; -use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator}; -use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator}; -use crate::stylesheets::{CssRule, CssRules, Origin, UrlExtraData}; -use crate::use_counters::UseCounters; -use crate::{Namespace, Prefix}; -use cssparser::{Parser, ParserInput, StyleSheetParser}; -use fxhash::FxHashMap; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use parking_lot::RwLock; -use servo_arc::Arc; -use std::sync::atomic::{AtomicBool, Ordering}; -use style_traits::ParsingMode; - -/// This structure holds the user-agent and user stylesheets. -pub struct UserAgentStylesheets { - /// The lock used for user-agent stylesheets. - pub shared_lock: SharedRwLock, - /// The user or user agent stylesheets. - pub user_or_user_agent_stylesheets: Vec<DocumentStyleSheet>, - /// The quirks mode stylesheet. - pub quirks_mode_stylesheet: DocumentStyleSheet, -} - -/// A set of namespaces applying to a given stylesheet. -/// -/// The namespace id is used in gecko -#[derive(Clone, Debug, Default, MallocSizeOf)] -#[allow(missing_docs)] -pub struct Namespaces { - pub default: Option<Namespace>, - pub prefixes: FxHashMap<Prefix, Namespace>, -} - -/// The contents of a given stylesheet. This effectively maps to a -/// StyleSheetInner in Gecko. -#[derive(Debug)] -pub struct StylesheetContents { - /// List of rules in the order they were found (important for - /// cascading order) - pub rules: Arc<Locked<CssRules>>, - /// The origin of this stylesheet. - pub origin: Origin, - /// The url data this stylesheet should use. - pub url_data: RwLock<UrlExtraData>, - /// The namespaces that apply to this stylesheet. - pub namespaces: RwLock<Namespaces>, - /// The quirks mode of this stylesheet. - pub quirks_mode: QuirksMode, - /// This stylesheet's source map URL. - pub source_map_url: RwLock<Option<String>>, - /// This stylesheet's source URL. - pub source_url: RwLock<Option<String>>, - - /// We don't want to allow construction outside of this file, to guarantee - /// that all contents are created with Arc<>. - _forbid_construction: (), -} - -impl StylesheetContents { - /// Parse a given CSS string, with a given url-data, origin, and - /// quirks mode. - pub fn from_str( - css: &str, - url_data: UrlExtraData, - origin: Origin, - shared_lock: &SharedRwLock, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - quirks_mode: QuirksMode, - line_number_offset: u32, - use_counters: Option<&UseCounters>, - allow_import_rules: AllowImportRules, - sanitization_data: Option<&mut SanitizationData>, - ) -> Arc<Self> { - let (namespaces, rules, source_map_url, source_url) = Stylesheet::parse_rules( - css, - &url_data, - origin, - &shared_lock, - stylesheet_loader, - error_reporter, - quirks_mode, - line_number_offset, - use_counters, - allow_import_rules, - sanitization_data, - ); - - Arc::new(Self { - rules: CssRules::new(rules, &shared_lock), - origin, - url_data: RwLock::new(url_data), - namespaces: RwLock::new(namespaces), - quirks_mode, - source_map_url: RwLock::new(source_map_url), - source_url: RwLock::new(source_url), - _forbid_construction: (), - }) - } - - /// Creates a new StylesheetContents with the specified pre-parsed rules, - /// origin, URL data, and quirks mode. - /// - /// Since the rules have already been parsed, and the intention is that - /// this function is used for read only User Agent style sheets, an empty - /// namespace map is used, and the source map and source URLs are set to - /// None. - /// - /// An empty namespace map should be fine, as it is only used for parsing, - /// not serialization of existing selectors. Since UA sheets are read only, - /// we should never need the namespace map. - pub fn from_data( - rules: Arc<Locked<CssRules>>, - origin: Origin, - url_data: UrlExtraData, - quirks_mode: QuirksMode, - ) -> Arc<Self> { - Arc::new(Self { - rules, - origin, - url_data: RwLock::new(url_data), - namespaces: RwLock::new(Namespaces::default()), - quirks_mode, - source_map_url: RwLock::new(None), - source_url: RwLock::new(None), - _forbid_construction: (), - }) - } - - /// Same as above, but ensuring that the rules are static. - pub fn from_shared_data( - rules: Arc<Locked<CssRules>>, - origin: Origin, - url_data: UrlExtraData, - quirks_mode: QuirksMode, - ) -> Arc<Self> { - debug_assert!(rules.is_static()); - Self::from_data(rules, origin, url_data, quirks_mode) - } - - /// Returns a reference to the list of rules. - #[inline] - pub fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - &self.rules.read_with(guard).0 - } - - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - if self.rules.is_static() { - return 0; - } - // Measurement of other fields may be added later. - self.rules.unconditional_shallow_size_of(ops) + - self.rules.read_with(guard).size_of(guard, ops) - } -} - -impl DeepCloneWithLock for StylesheetContents { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - // Make a deep clone of the rules, using the new lock. - let rules = self - .rules - .read_with(guard) - .deep_clone_with_lock(lock, guard, params); - - Self { - rules: Arc::new(lock.wrap(rules)), - quirks_mode: self.quirks_mode, - origin: self.origin, - url_data: RwLock::new((*self.url_data.read()).clone()), - namespaces: RwLock::new((*self.namespaces.read()).clone()), - source_map_url: RwLock::new((*self.source_map_url.read()).clone()), - source_url: RwLock::new((*self.source_url.read()).clone()), - _forbid_construction: (), - } - } -} - -/// The structure servo uses to represent a stylesheet. -#[derive(Debug)] -pub struct Stylesheet { - /// The contents of this stylesheet. - pub contents: Arc<StylesheetContents>, - /// The lock used for objects inside this stylesheet - pub shared_lock: SharedRwLock, - /// List of media associated with the Stylesheet. - pub media: Arc<Locked<MediaList>>, - /// Whether this stylesheet should be disabled. - pub disabled: AtomicBool, -} - -macro_rules! rule_filter { - ($( $method: ident($variant:ident => $rule_type: ident), )+) => { - $( - #[allow(missing_docs)] - fn $method<F>(&self, device: &Device, guard: &SharedRwLockReadGuard, mut f: F) - where F: FnMut(&crate::stylesheets::$rule_type), - { - use crate::stylesheets::CssRule; - - for rule in self.effective_rules(device, guard) { - if let CssRule::$variant(ref lock) = *rule { - let rule = lock.read_with(guard); - f(&rule) - } - } - } - )+ - } -} - -/// A trait to represent a given stylesheet in a document. -pub trait StylesheetInDocument: ::std::fmt::Debug { - /// Get whether this stylesheet is enabled. - fn enabled(&self) -> bool; - - /// Get the media associated with this stylesheet. - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>; - - /// Returns a reference to the list of rules in this stylesheet. - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.contents().rules(guard) - } - - /// Returns a reference to the contents of the stylesheet. - fn contents(&self) -> &StylesheetContents; - - /// Return an iterator using the condition `C`. - #[inline] - fn iter_rules<'a, 'b, C>( - &'a self, - device: &'a Device, - guard: &'a SharedRwLockReadGuard<'b>, - ) -> RulesIterator<'a, 'b, C> - where - C: NestedRuleIterationCondition, - { - let contents = self.contents(); - RulesIterator::new( - device, - contents.quirks_mode, - guard, - contents.rules(guard).iter(), - ) - } - - /// Returns whether the style-sheet applies for the current device. - fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool { - match self.media(guard) { - Some(medialist) => medialist.evaluate(device, self.contents().quirks_mode), - None => true, - } - } - - /// Return an iterator over the effective rules within the style-sheet, as - /// according to the supplied `Device`. - #[inline] - fn effective_rules<'a, 'b>( - &'a self, - device: &'a Device, - guard: &'a SharedRwLockReadGuard<'b>, - ) -> EffectiveRulesIterator<'a, 'b> { - self.iter_rules::<EffectiveRules>(device, guard) - } - - rule_filter! { - effective_font_face_rules(FontFace => FontFaceRule), - } -} - -impl StylesheetInDocument for Stylesheet { - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - Some(self.media.read_with(guard)) - } - - fn enabled(&self) -> bool { - !self.disabled() - } - - #[inline] - fn contents(&self) -> &StylesheetContents { - &self.contents - } -} - -/// A simple wrapper over an `Arc<Stylesheet>`, with pointer comparison, and -/// suitable for its use in a `StylesheetSet`. -#[derive(Clone, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct DocumentStyleSheet( - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] pub Arc<Stylesheet>, -); - -impl PartialEq for DocumentStyleSheet { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } -} - -impl StylesheetInDocument for DocumentStyleSheet { - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.0.media(guard) - } - - fn enabled(&self) -> bool { - self.0.enabled() - } - - #[inline] - fn contents(&self) -> &StylesheetContents { - self.0.contents() - } -} - -/// The kind of sanitization to use when parsing a stylesheet. -#[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SanitizationKind { - /// Perform no sanitization. - None, - /// Allow only @font-face, style rules, and @namespace. - Standard, - /// Allow everything but conditional rules. - NoConditionalRules, -} - -/// Whether @import rules are allowed. -#[repr(u8)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum AllowImportRules { - /// @import rules will be parsed. - Yes, - /// @import rules will not be parsed. - No, -} - -impl SanitizationKind { - fn allows(self, rule: &CssRule) -> bool { - debug_assert_ne!(self, SanitizationKind::None); - // NOTE(emilio): If this becomes more complex (not filtering just by - // top-level rules), we should thread all the data through nested rules - // and such. But this doesn't seem necessary at the moment. - let is_standard = matches!(self, SanitizationKind::Standard); - match *rule { - CssRule::Document(..) | - CssRule::Media(..) | - CssRule::Supports(..) | - CssRule::Import(..) | - CssRule::Container(..) | - // TODO(emilio): Perhaps Layer should not be always sanitized? But - // we sanitize @media and co, so this seems safer for now. - CssRule::LayerStatement(..) | - CssRule::LayerBlock(..) => false, - - CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, - - CssRule::Keyframes(..) | - CssRule::Page(..) | - CssRule::Property(..) | - CssRule::FontFeatureValues(..) | - CssRule::FontPaletteValues(..) | - CssRule::CounterStyle(..) => !is_standard, - } - } -} - -/// A struct to hold the data relevant to style sheet sanitization. -#[derive(Debug)] -pub struct SanitizationData { - kind: SanitizationKind, - output: String, -} - -impl SanitizationData { - /// Create a new input for sanitization. - #[inline] - pub fn new(kind: SanitizationKind) -> Option<Self> { - if matches!(kind, SanitizationKind::None) { - return None; - } - Some(Self { - kind, - output: String::new(), - }) - } - - /// Take the sanitized output. - #[inline] - pub fn take(self) -> String { - self.output - } -} - -impl Stylesheet { - /// Updates an empty stylesheet from a given string of text. - pub fn update_from_str( - existing: &Stylesheet, - css: &str, - url_data: UrlExtraData, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - line_number_offset: u32, - allow_import_rules: AllowImportRules, - ) { - // FIXME: Consider adding use counters to Servo? - let (namespaces, rules, source_map_url, source_url) = Self::parse_rules( - css, - &url_data, - existing.contents.origin, - &existing.shared_lock, - stylesheet_loader, - error_reporter, - existing.contents.quirks_mode, - line_number_offset, - /* use_counters = */ None, - allow_import_rules, - /* sanitization_data = */ None, - ); - - *existing.contents.url_data.write() = url_data; - *existing.contents.namespaces.write() = namespaces; - - // Acquire the lock *after* parsing, to minimize the exclusive section. - let mut guard = existing.shared_lock.write(); - *existing.contents.rules.write_with(&mut guard) = CssRules(rules); - *existing.contents.source_map_url.write() = source_map_url; - *existing.contents.source_url.write() = source_url; - } - - fn parse_rules( - css: &str, - url_data: &UrlExtraData, - origin: Origin, - shared_lock: &SharedRwLock, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - quirks_mode: QuirksMode, - line_number_offset: u32, - use_counters: Option<&UseCounters>, - allow_import_rules: AllowImportRules, - mut sanitization_data: Option<&mut SanitizationData>, - ) -> (Namespaces, Vec<CssRule>, Option<String>, Option<String>) { - let mut input = ParserInput::new_with_line_number_offset(css, line_number_offset); - let mut input = Parser::new(&mut input); - - let context = ParserContext::new( - origin, - url_data, - None, - ParsingMode::DEFAULT, - quirks_mode, - /* namespaces = */ Default::default(), - error_reporter, - use_counters, - ); - - let mut rule_parser = TopLevelRuleParser { - shared_lock, - loader: stylesheet_loader, - context, - state: State::Start, - dom_error: None, - insert_rule_context: None, - allow_import_rules, - declaration_parser_state: Default::default(), - rules: Vec::new(), - }; - - { - let mut iter = StyleSheetParser::new(&mut input, &mut rule_parser); - while let Some(result) = iter.next() { - match result { - Ok(rule_start) => { - // TODO(emilio, nesting): sanitize nested CSS rules, probably? - if let Some(ref mut data) = sanitization_data { - if let Some(ref rule) = iter.parser.rules.last() { - if !data.kind.allows(rule) { - iter.parser.rules.pop(); - continue; - } - } - let end = iter.input.position().byte_index(); - data.output.push_str(&css[rule_start.byte_index()..end]); - } - }, - Err((error, slice)) => { - let location = error.location; - let error = ContextualParseError::InvalidRule(slice, error); - iter.parser.context.log_css_error(location, error); - }, - } - } - } - - let source_map_url = input.current_source_map_url().map(String::from); - let source_url = input.current_source_url().map(String::from); - ( - rule_parser.context.namespaces.into_owned(), - rule_parser.rules, - source_map_url, - source_url, - ) - } - - /// Creates an empty stylesheet and parses it with a given base url, origin - /// and media. - /// - /// Effectively creates a new stylesheet and forwards the hard work to - /// `Stylesheet::update_from_str`. - pub fn from_str( - css: &str, - url_data: UrlExtraData, - origin: Origin, - media: Arc<Locked<MediaList>>, - shared_lock: SharedRwLock, - stylesheet_loader: Option<&dyn StylesheetLoader>, - error_reporter: Option<&dyn ParseErrorReporter>, - quirks_mode: QuirksMode, - line_number_offset: u32, - allow_import_rules: AllowImportRules, - ) -> Self { - // FIXME: Consider adding use counters to Servo? - let contents = StylesheetContents::from_str( - css, - url_data, - origin, - &shared_lock, - stylesheet_loader, - error_reporter, - quirks_mode, - line_number_offset, - /* use_counters = */ None, - allow_import_rules, - /* sanitized_output = */ None, - ); - - Stylesheet { - contents, - shared_lock, - media, - disabled: AtomicBool::new(false), - } - } - - /// Returns whether the stylesheet has been explicitly disabled through the - /// CSSOM. - pub fn disabled(&self) -> bool { - self.disabled.load(Ordering::SeqCst) - } - - /// Records that the stylesheet has been explicitly disabled through the - /// CSSOM. - /// - /// Returns whether the the call resulted in a change in disabled state. - /// - /// Disabled stylesheets remain in the document, but their rules are not - /// added to the Stylist. - pub fn set_disabled(&self, disabled: bool) -> bool { - self.disabled.swap(disabled, Ordering::SeqCst) != disabled - } -} - -#[cfg(feature = "servo")] -impl Clone for Stylesheet { - fn clone(&self) -> Self { - // Create a new lock for our clone. - let lock = self.shared_lock.clone(); - let guard = self.shared_lock.read(); - - // Make a deep clone of the media, using the new lock. - let media = self.media.read_with(&guard).clone(); - let media = Arc::new(lock.wrap(media)); - let contents = Arc::new(self.contents.deep_clone_with_lock( - &lock, - &guard, - &DeepCloneParams, - )); - - Stylesheet { - contents, - media, - shared_lock: lock, - disabled: AtomicBool::new(self.disabled.load(Ordering::SeqCst)), - } - } -} diff --git a/components/style/stylesheets/supports_rule.rs b/components/style/stylesheets/supports_rule.rs deleted file mode 100644 index 936bcdb385f..00000000000 --- a/components/style/stylesheets/supports_rule.rs +++ /dev/null @@ -1,448 +0,0 @@ -/* 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/. */ - -//! [@supports rules](https://drafts.csswg.org/css-conditional-3/#at-supports) - -use crate::font_face::{FontFaceSourceFormatKeyword, FontFaceSourceTechFlags}; -use crate::parser::ParserContext; -use crate::properties::{PropertyDeclaration, PropertyId, SourcePropertyDeclaration}; -use crate::selector_parser::{SelectorImpl, SelectorParser}; -use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; -use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; -use crate::str::CssStringWriter; -use crate::stylesheets::{CssRuleType, CssRules}; -use cssparser::parse_important; -use cssparser::{Delimiter, Parser, SourceLocation, Token}; -use cssparser::{ParseError as CssParseError, ParserInput}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; -use selectors::parser::{Selector, SelectorParseErrorKind}; -use servo_arc::Arc; -use std::ffi::{CStr, CString}; -use std::fmt::{self, Write}; -use std::str; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// An [`@supports`][supports] rule. -/// -/// [supports]: https://drafts.csswg.org/css-conditional-3/#at-supports -#[derive(Debug, ToShmem)] -pub struct SupportsRule { - /// The parsed condition - pub condition: SupportsCondition, - /// Child rules - pub rules: Arc<Locked<CssRules>>, - /// The result of evaluating the condition - pub enabled: bool, - /// The line and column of the rule's source code. - pub source_location: SourceLocation, -} - -impl SupportsRule { - /// Measure heap usage. - #[cfg(feature = "gecko")] - pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { - // Measurement of other fields may be added later. - self.rules.unconditional_shallow_size_of(ops) + - self.rules.read_with(guard).size_of(guard, ops) - } -} - -impl ToCssWithGuard for SupportsRule { - fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@supports ")?; - self.condition.to_css(&mut CssWriter::new(dest))?; - self.rules.read_with(guard).to_css_block(guard, dest) - } -} - -impl DeepCloneWithLock for SupportsRule { - fn deep_clone_with_lock( - &self, - lock: &SharedRwLock, - guard: &SharedRwLockReadGuard, - params: &DeepCloneParams, - ) -> Self { - let rules = self.rules.read_with(guard); - SupportsRule { - condition: self.condition.clone(), - rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), - enabled: self.enabled, - source_location: self.source_location.clone(), - } - } -} - -/// An @supports condition -/// -/// <https://drafts.csswg.org/css-conditional-3/#at-supports> -#[derive(Clone, Debug, ToShmem)] -pub enum SupportsCondition { - /// `not (condition)` - Not(Box<SupportsCondition>), - /// `(condition)` - Parenthesized(Box<SupportsCondition>), - /// `(condition) and (condition) and (condition) ..` - And(Vec<SupportsCondition>), - /// `(condition) or (condition) or (condition) ..` - Or(Vec<SupportsCondition>), - /// `property-ident: value` (value can be any tokens) - Declaration(Declaration), - /// A `selector()` function. - Selector(RawSelector), - /// `-moz-bool-pref("pref-name")` - /// Since we need to pass it through FFI to get the pref value, - /// we store it as CString directly. - MozBoolPref(CString), - /// `font-format(<font-format>)` - FontFormat(FontFaceSourceFormatKeyword), - /// `font-tech(<font-tech>)` - FontTech(FontFaceSourceTechFlags), - /// `(any tokens)` or `func(any tokens)` - FutureSyntax(String), -} - -impl SupportsCondition { - /// Parse a condition - /// - /// <https://drafts.csswg.org/css-conditional/#supports_condition> - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("not")).is_ok() { - let inner = SupportsCondition::parse_in_parens(input)?; - return Ok(SupportsCondition::Not(Box::new(inner))); - } - - let in_parens = SupportsCondition::parse_in_parens(input)?; - - let location = input.current_source_location(); - let (keyword, wrapper) = match input.next() { - // End of input - Err(..) => return Ok(in_parens), - Ok(&Token::Ident(ref ident)) => { - match_ignore_ascii_case! { &ident, - "and" => ("and", SupportsCondition::And as fn(_) -> _), - "or" => ("or", SupportsCondition::Or as fn(_) -> _), - _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) - } - }, - Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), - }; - - let mut conditions = Vec::with_capacity(2); - conditions.push(in_parens); - loop { - conditions.push(SupportsCondition::parse_in_parens(input)?); - if input - .try_parse(|input| input.expect_ident_matching(keyword)) - .is_err() - { - // Did not find the expected keyword. - // If we found some other token, it will be rejected by - // `Parser::parse_entirely` somewhere up the stack. - return Ok(wrapper(conditions)); - } - } - } - - /// Parses a functional supports condition. - fn parse_functional<'i, 't>( - function: &str, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - match_ignore_ascii_case! { function, - // Although this is an internal syntax, it is not necessary - // to check parsing context as far as we accept any - // unexpected token as future syntax, and evaluate it to - // false when not in chrome / ua sheet. - // See https://drafts.csswg.org/css-conditional-3/#general_enclosed - "-moz-bool-pref" => { - let name = { - let name = input.expect_string()?; - CString::new(name.as_bytes()) - }.map_err(|_| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?; - Ok(SupportsCondition::MozBoolPref(name)) - }, - "selector" => { - let pos = input.position(); - consume_any_value(input)?; - Ok(SupportsCondition::Selector(RawSelector( - input.slice_from(pos).to_owned() - ))) - }, - "font-format" if static_prefs::pref!("layout.css.font-tech.enabled") => { - let kw = FontFaceSourceFormatKeyword::parse(input)?; - Ok(SupportsCondition::FontFormat(kw)) - }, - "font-tech" if static_prefs::pref!("layout.css.font-tech.enabled") => { - let flag = FontFaceSourceTechFlags::parse_one(input)?; - Ok(SupportsCondition::FontTech(flag)) - }, - _ => { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - } - } - - /// Parses an `@import` condition as per - /// https://drafts.csswg.org/css-cascade-5/#typedef-import-conditions - pub fn parse_for_import<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("supports")?; - input.parse_nested_block(parse_condition_or_declaration) - } - - /// <https://drafts.csswg.org/css-conditional-3/#supports_condition_in_parens> - fn parse_in_parens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - // Whitespace is normally taken care of in `Parser::next`, but we want to not include it in - // `pos` for the SupportsCondition::FutureSyntax cases. - input.skip_whitespace(); - let pos = input.position(); - let location = input.current_source_location(); - match *input.next()? { - Token::ParenthesisBlock => { - let nested = input - .try_parse(|input| input.parse_nested_block(parse_condition_or_declaration)); - if let Ok(nested) = nested { - return Ok(Self::Parenthesized(Box::new(nested))); - } - }, - Token::Function(ref ident) => { - let ident = ident.clone(); - let nested = input.try_parse(|input| { - input.parse_nested_block(|input| { - SupportsCondition::parse_functional(&ident, input) - }) - }); - if nested.is_ok() { - return nested; - } - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - } - input.parse_nested_block(consume_any_value)?; - Ok(SupportsCondition::FutureSyntax( - input.slice_from(pos).to_owned(), - )) - } - - /// Evaluate a supports condition - pub fn eval(&self, cx: &ParserContext) -> bool { - match *self { - SupportsCondition::Not(ref cond) => !cond.eval(cx), - SupportsCondition::Parenthesized(ref cond) => cond.eval(cx), - SupportsCondition::And(ref vec) => vec.iter().all(|c| c.eval(cx)), - SupportsCondition::Or(ref vec) => vec.iter().any(|c| c.eval(cx)), - SupportsCondition::Declaration(ref decl) => decl.eval(cx), - SupportsCondition::MozBoolPref(ref name) => eval_moz_bool_pref(name, cx), - SupportsCondition::Selector(ref selector) => selector.eval(cx), - SupportsCondition::FontFormat(ref format) => eval_font_format(format), - SupportsCondition::FontTech(ref tech) => eval_font_tech(tech), - SupportsCondition::FutureSyntax(_) => false, - } - } -} - -#[cfg(feature = "gecko")] -fn eval_moz_bool_pref(name: &CStr, cx: &ParserContext) -> bool { - use crate::gecko_bindings::bindings; - if !cx.in_ua_or_chrome_sheet() { - return false; - } - unsafe { bindings::Gecko_GetBoolPrefValue(name.as_ptr()) } -} - -#[cfg(feature = "gecko")] -fn eval_font_format(kw: &FontFaceSourceFormatKeyword) -> bool { - use crate::gecko_bindings::bindings; - unsafe { bindings::Gecko_IsFontFormatSupported(*kw) } -} - -#[cfg(feature = "gecko")] -fn eval_font_tech(flag: &FontFaceSourceTechFlags) -> bool { - use crate::gecko_bindings::bindings; - unsafe { bindings::Gecko_IsFontTechSupported(*flag) } -} - -#[cfg(feature = "servo")] -fn eval_moz_bool_pref(_: &CStr, _: &ParserContext) -> bool { - false -} - -#[cfg(feature = "servo")] -fn eval_font_format(_: &FontFaceSourceFormatKeyword) -> bool { - false -} - -#[cfg(feature = "servo")] -fn eval_font_tech(_: &FontFaceSourceTechFlags) -> bool { - false -} - -/// supports_condition | declaration -/// <https://drafts.csswg.org/css-conditional/#dom-css-supports-conditiontext-conditiontext> -pub fn parse_condition_or_declaration<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<SupportsCondition, ParseError<'i>> { - if let Ok(condition) = input.try_parse(SupportsCondition::parse) { - Ok(condition) - } else { - Declaration::parse(input).map(SupportsCondition::Declaration) - } -} - -impl ToCss for SupportsCondition { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - SupportsCondition::Not(ref cond) => { - dest.write_str("not ")?; - cond.to_css(dest) - }, - SupportsCondition::Parenthesized(ref cond) => { - dest.write_char('(')?; - cond.to_css(dest)?; - dest.write_char(')') - }, - SupportsCondition::And(ref vec) => { - let mut first = true; - for cond in vec { - if !first { - dest.write_str(" and ")?; - } - first = false; - cond.to_css(dest)?; - } - Ok(()) - }, - SupportsCondition::Or(ref vec) => { - let mut first = true; - for cond in vec { - if !first { - dest.write_str(" or ")?; - } - first = false; - cond.to_css(dest)?; - } - Ok(()) - }, - SupportsCondition::Declaration(ref decl) => decl.to_css(dest), - SupportsCondition::Selector(ref selector) => { - dest.write_str("selector(")?; - selector.to_css(dest)?; - dest.write_char(')') - }, - SupportsCondition::MozBoolPref(ref name) => { - dest.write_str("-moz-bool-pref(")?; - let name = - str::from_utf8(name.as_bytes()).expect("Should be parsed from valid UTF-8"); - name.to_css(dest)?; - dest.write_char(')') - }, - SupportsCondition::FontFormat(ref kw) => { - dest.write_str("font-format(")?; - kw.to_css(dest)?; - dest.write_char(')') - }, - SupportsCondition::FontTech(ref flag) => { - dest.write_str("font-tech(")?; - flag.to_css(dest)?; - dest.write_char(')') - }, - SupportsCondition::FutureSyntax(ref s) => dest.write_str(&s), - } - } -} - -#[derive(Clone, Debug, ToShmem)] -/// A possibly-invalid CSS selector. -pub struct RawSelector(pub String); - -impl ToCss for RawSelector { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str(&self.0) - } -} - -impl RawSelector { - /// Tries to evaluate a `selector()` function. - pub fn eval(&self, context: &ParserContext) -> bool { - let mut input = ParserInput::new(&self.0); - let mut input = Parser::new(&mut input); - input - .parse_entirely(|input| -> Result<(), CssParseError<()>> { - let parser = SelectorParser { - namespaces: &context.namespaces, - stylesheet_origin: context.stylesheet_origin, - url_data: context.url_data, - for_supports_rule: true, - }; - - Selector::<SelectorImpl>::parse(&parser, input) - .map_err(|_| input.new_custom_error(()))?; - - Ok(()) - }) - .is_ok() - } -} - -#[derive(Clone, Debug, ToShmem)] -/// A possibly-invalid property declaration -pub struct Declaration(pub String); - -impl ToCss for Declaration { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str(&self.0) - } -} - -/// <https://drafts.csswg.org/css-syntax-3/#typedef-any-value> -fn consume_any_value<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { - input.expect_no_error_token().map_err(|err| err.into()) -} - -impl Declaration { - /// Parse a declaration - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Declaration, ParseError<'i>> { - let pos = input.position(); - input.expect_ident()?; - input.expect_colon()?; - consume_any_value(input)?; - Ok(Declaration(input.slice_from(pos).to_owned())) - } - - /// Determine if a declaration parses - /// - /// <https://drafts.csswg.org/css-conditional-3/#support-definition> - pub fn eval(&self, context: &ParserContext) -> bool { - debug_assert!(context.rule_types().contains(CssRuleType::Style)); - - let mut input = ParserInput::new(&self.0); - let mut input = Parser::new(&mut input); - input - .parse_entirely(|input| -> Result<(), CssParseError<()>> { - let prop = input.expect_ident_cloned().unwrap(); - input.expect_colon().unwrap(); - - let id = - PropertyId::parse(&prop, context).map_err(|_| input.new_custom_error(()))?; - - let mut declarations = SourcePropertyDeclaration::default(); - input.parse_until_before(Delimiter::Bang, |input| { - PropertyDeclaration::parse_into(&mut declarations, id, &context, input) - .map_err(|_| input.new_custom_error(())) - })?; - let _ = input.try_parse(parse_important); - Ok(()) - }) - .is_ok() - } -} diff --git a/components/style/stylist.rs b/components/style/stylist.rs deleted file mode 100644 index b4fd4e3a64b..00000000000 --- a/components/style/stylist.rs +++ /dev/null @@ -1,3290 +0,0 @@ -/* 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/. */ - -//! Selector matching. - -use crate::applicable_declarations::{ - ApplicableDeclarationBlock, ApplicableDeclarationList, CascadePriority, -}; -use crate::context::{CascadeInputs, QuirksMode}; -use crate::dom::{TElement, TShadowRoot}; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion}; -use crate::invalidation::element::invalidation_map::InvalidationMap; -use crate::invalidation::media_queries::{ - EffectiveMediaQueryResults, MediaListKey, ToMediaListKey, -}; -use crate::invalidation::stylesheets::RuleChangeKind; -use crate::media_queries::Device; -use crate::properties::{self, CascadeMode, ComputedValues}; -use crate::properties::{AnimationDeclarations, PropertyDeclarationBlock}; -use crate::rule_cache::{RuleCache, RuleCacheConditions}; -use crate::rule_collector::{containing_shadow_ignoring_svg_use, RuleCollector}; -use crate::rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; -use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, SelectorMap, SelectorMapEntry}; -use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, SnapshotMap}; -use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; -use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; -use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; -use crate::stylesheets::container_rule::ContainerCondition; -use crate::stylesheets::import_rule::ImportLayer; -use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; -#[cfg(feature = "gecko")] -use crate::stylesheets::{ - CounterStyleRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule, PageRule, -}; -use crate::stylesheets::{ - CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, -}; -use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; -use crate::AllocErr; -use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; -use fxhash::FxHashMap; -use malloc_size_of::{MallocSizeOf, MallocShallowSizeOf, MallocSizeOfOps}; -#[cfg(feature = "gecko")] -use malloc_size_of::MallocUnconditionalShallowSizeOf; -use selectors::attr::{CaseSensitivity, NamespaceConstraint}; -use selectors::bloom::BloomFilter; -use selectors::matching::VisitedHandlingMode; -use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags}; -use selectors::parser::{ - AncestorHashes, Combinator, Component, Selector, SelectorIter, SelectorList, -}; -use selectors::visitor::{SelectorListKind, SelectorVisitor}; -use selectors::NthIndexCache; -use servo_arc::{Arc, ArcBorrow}; -use smallbitvec::SmallBitVec; -use smallvec::SmallVec; -use std::borrow::Cow; -use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; -use std::sync::Mutex; -use std::{mem, ops}; -use style_traits::dom::{DocumentState, ElementState}; - -/// The type of the stylesheets that the stylist contains. -#[cfg(feature = "servo")] -pub type StylistSheet = crate::stylesheets::DocumentStyleSheet; - -/// The type of the stylesheets that the stylist contains. -#[cfg(feature = "gecko")] -pub type StylistSheet = crate::gecko::data::GeckoStyleSheet; - -#[derive(Debug, Clone)] -struct StylesheetContentsPtr(Arc<StylesheetContents>); - -impl PartialEq for StylesheetContentsPtr { - #[inline] - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) - } -} - -impl Eq for StylesheetContentsPtr {} - -impl Hash for StylesheetContentsPtr { - fn hash<H: Hasher>(&self, state: &mut H) { - let contents: &StylesheetContents = &*self.0; - (contents as *const StylesheetContents).hash(state) - } -} - -type StyleSheetContentList = Vec<StylesheetContentsPtr>; - -/// A key in the cascade data cache. -#[derive(Debug, Hash, Default, PartialEq, Eq)] -struct CascadeDataCacheKey { - media_query_results: Vec<MediaListKey>, - contents: StyleSheetContentList, -} - -unsafe impl Send for CascadeDataCacheKey {} -unsafe impl Sync for CascadeDataCacheKey {} - -trait CascadeDataCacheEntry: Sized { - /// Returns a reference to the cascade data. - fn cascade_data(&self) -> &CascadeData; - /// Rebuilds the cascade data for the new stylesheet collection. The - /// collection is guaranteed to be dirty. - fn rebuild<S>( - device: &Device, - quirks_mode: QuirksMode, - collection: SheetCollectionFlusher<S>, - guard: &SharedRwLockReadGuard, - old_entry: &Self, - ) -> Result<Arc<Self>, AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static; - /// Measures heap memory usage. - #[cfg(feature = "gecko")] - fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes); -} - -struct CascadeDataCache<Entry> { - entries: FxHashMap<CascadeDataCacheKey, Arc<Entry>>, -} - -impl<Entry> CascadeDataCache<Entry> -where - Entry: CascadeDataCacheEntry, -{ - fn new() -> Self { - Self { - entries: Default::default(), - } - } - - fn len(&self) -> usize { - self.entries.len() - } - - // FIXME(emilio): This may need to be keyed on quirks-mode too, though for - // UA sheets there aren't class / id selectors on those sheets, usually, so - // it's probably ok... For the other cache the quirks mode shouldn't differ - // so also should be fine. - fn lookup<'a, S>( - &'a mut self, - device: &Device, - quirks_mode: QuirksMode, - collection: SheetCollectionFlusher<S>, - guard: &SharedRwLockReadGuard, - old_entry: &Entry, - ) -> Result<Option<Arc<Entry>>, AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static, - { - use std::collections::hash_map::Entry as HashMapEntry; - debug!("StyleSheetCache::lookup({})", self.len()); - - if !collection.dirty() { - return Ok(None); - } - - let mut key = CascadeDataCacheKey::default(); - for sheet in collection.sheets() { - CascadeData::collect_applicable_media_query_results_into( - device, - sheet, - guard, - &mut key.media_query_results, - &mut key.contents, - ) - } - - let new_entry; - match self.entries.entry(key) { - HashMapEntry::Vacant(e) => { - debug!("> Picking the slow path (not in the cache)"); - new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?; - e.insert(new_entry.clone()); - }, - HashMapEntry::Occupied(mut e) => { - // Avoid reusing our old entry (this can happen if we get - // invalidated due to CSSOM mutations and our old stylesheet - // contents were already unique, for example). - if !std::ptr::eq(&**e.get(), old_entry) { - if log_enabled!(log::Level::Debug) { - debug!("cache hit for:"); - for sheet in collection.sheets() { - debug!(" > {:?}", sheet); - } - } - // The line below ensures the "committed" bit is updated - // properly. - collection.each(|_, _| true); - return Ok(Some(e.get().clone())); - } - - debug!("> Picking the slow path due to same entry as old"); - new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?; - e.insert(new_entry.clone()); - }, - } - - Ok(Some(new_entry)) - } - - /// Returns all the cascade datas that are not being used (that is, that are - /// held alive just by this cache). - /// - /// We return them instead of dropping in place because some of them may - /// keep alive some other documents (like the SVG documents kept alive by - /// URL references), and thus we don't want to drop them while locking the - /// cache to not deadlock. - fn take_unused(&mut self) -> SmallVec<[Arc<Entry>; 3]> { - let mut unused = SmallVec::new(); - self.entries.retain(|_key, value| { - // is_unique() returns false for static references, but we never - // have static references to UserAgentCascadeDatas. If we did, it - // may not make sense to put them in the cache in the first place. - if !value.is_unique() { - return true; - } - unused.push(value.clone()); - false - }); - unused - } - - fn take_all(&mut self) -> FxHashMap<CascadeDataCacheKey, Arc<Entry>> { - mem::take(&mut self.entries) - } - - #[cfg(feature = "gecko")] - fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - sizes.mOther += self.entries.shallow_size_of(ops); - for (_key, arc) in self.entries.iter() { - // These are primary Arc references that can be measured - // unconditionally. - sizes.mOther += arc.unconditional_shallow_size_of(ops); - arc.add_size_of(ops, sizes); - } - } -} - -/// Measure heap usage of UA_CASCADE_DATA_CACHE. -#[cfg(feature = "gecko")] -pub fn add_size_of_ua_cache(ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - UA_CASCADE_DATA_CACHE - .lock() - .unwrap() - .add_size_of(ops, sizes); -} - -lazy_static! { - /// A cache of computed user-agent data, to be shared across documents. - static ref UA_CASCADE_DATA_CACHE: Mutex<UserAgentCascadeDataCache> = - Mutex::new(UserAgentCascadeDataCache::new()); -} - -impl CascadeDataCacheEntry for UserAgentCascadeData { - fn cascade_data(&self) -> &CascadeData { - &self.cascade_data - } - - fn rebuild<S>( - device: &Device, - quirks_mode: QuirksMode, - collection: SheetCollectionFlusher<S>, - guard: &SharedRwLockReadGuard, - _old: &Self, - ) -> Result<Arc<Self>, AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static, - { - // TODO: Maybe we should support incremental rebuilds, though they seem - // uncommon and rebuild() doesn't deal with - // precomputed_pseudo_element_decls for now so... - let mut new_data = Self { - cascade_data: CascadeData::new(), - precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(), - }; - - for sheet in collection.sheets() { - new_data.cascade_data.add_stylesheet( - device, - quirks_mode, - sheet, - guard, - SheetRebuildKind::Full, - Some(&mut new_data.precomputed_pseudo_element_decls), - )?; - } - - new_data.cascade_data.did_finish_rebuild(); - - Ok(Arc::new(new_data)) - } - - #[cfg(feature = "gecko")] - fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.cascade_data.add_size_of(ops, sizes); - sizes.mPrecomputedPseudos += self.precomputed_pseudo_element_decls.size_of(ops); - } -} - -type UserAgentCascadeDataCache = CascadeDataCache<UserAgentCascadeData>; - -type PrecomputedPseudoElementDeclarations = PerPseudoElementMap<Vec<ApplicableDeclarationBlock>>; - -#[derive(Default)] -struct UserAgentCascadeData { - cascade_data: CascadeData, - - /// Applicable declarations for a given non-eagerly cascaded pseudo-element. - /// - /// These are eagerly computed once, and then used to resolve the new - /// computed values on the fly on layout. - /// - /// These are only filled from UA stylesheets. - precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations, -} - -/// All the computed information for all the stylesheets that apply to the -/// document. -#[derive(Default)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct DocumentCascadeData { - #[cfg_attr( - feature = "servo", - ignore_malloc_size_of = "Arc, owned by UserAgentCascadeDataCache" - )] - user_agent: Arc<UserAgentCascadeData>, - user: CascadeData, - author: CascadeData, - per_origin: PerOrigin<()>, -} - -/// An iterator over the cascade data of a given document. -pub struct DocumentCascadeDataIter<'a> { - iter: PerOriginIter<'a, ()>, - cascade_data: &'a DocumentCascadeData, -} - -impl<'a> Iterator for DocumentCascadeDataIter<'a> { - type Item = (&'a CascadeData, Origin); - - fn next(&mut self) -> Option<Self::Item> { - let (_, origin) = self.iter.next()?; - Some((self.cascade_data.borrow_for_origin(origin), origin)) - } -} - -impl DocumentCascadeData { - /// Borrows the cascade data for a given origin. - #[inline] - pub fn borrow_for_origin(&self, origin: Origin) -> &CascadeData { - match origin { - Origin::UserAgent => &self.user_agent.cascade_data, - Origin::Author => &self.author, - Origin::User => &self.user, - } - } - - fn iter_origins(&self) -> DocumentCascadeDataIter { - DocumentCascadeDataIter { - iter: self.per_origin.iter_origins(), - cascade_data: self, - } - } - - fn iter_origins_rev(&self) -> DocumentCascadeDataIter { - DocumentCascadeDataIter { - iter: self.per_origin.iter_origins_rev(), - cascade_data: self, - } - } - - /// Rebuild the cascade data for the given document stylesheets, and - /// optionally with a set of user agent stylesheets. Returns Err(..) - /// to signify OOM. - fn rebuild<'a, S>( - &mut self, - device: &Device, - quirks_mode: QuirksMode, - mut flusher: DocumentStylesheetFlusher<'a, S>, - guards: &StylesheetGuards, - ) -> Result<(), AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static, - { - // First do UA sheets. - { - let origin_flusher = flusher.flush_origin(Origin::UserAgent); - // Dirty check is just a minor optimization (no need to grab the - // lock if nothing has changed). - if origin_flusher.dirty() { - let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap(); - let new_data = ua_cache.lookup( - device, - quirks_mode, - origin_flusher, - guards.ua_or_user, - &self.user_agent, - )?; - if let Some(new_data) = new_data { - self.user_agent = new_data; - } - let _unused_entries = ua_cache.take_unused(); - // See the comments in take_unused() as for why the following - // line. - std::mem::drop(ua_cache); - } - } - - // Now do the user sheets. - self.user.rebuild( - device, - quirks_mode, - flusher.flush_origin(Origin::User), - guards.ua_or_user, - )?; - - // And now the author sheets. - self.author.rebuild( - device, - quirks_mode, - flusher.flush_origin(Origin::Author), - guards.author, - )?; - - Ok(()) - } - - /// Measures heap usage. - #[cfg(feature = "gecko")] - pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.user.add_size_of(ops, sizes); - self.author.add_size_of(ops, sizes); - } -} - -/// Whether author styles are enabled. -/// -/// This is used to support Gecko. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -pub enum AuthorStylesEnabled { - Yes, - No, -} - -/// A wrapper over a DocumentStylesheetSet that can be `Sync`, since it's only -/// used and exposed via mutable methods in the `Stylist`. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -struct StylistStylesheetSet(DocumentStylesheetSet<StylistSheet>); -// Read above to see why this is fine. -unsafe impl Sync for StylistStylesheetSet {} - -impl StylistStylesheetSet { - fn new() -> Self { - StylistStylesheetSet(DocumentStylesheetSet::new()) - } -} - -impl ops::Deref for StylistStylesheetSet { - type Target = DocumentStylesheetSet<StylistSheet>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl ops::DerefMut for StylistStylesheetSet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// This structure holds all the selectors and device characteristics -/// for a given document. The selectors are converted into `Rule`s -/// and sorted into `SelectorMap`s keyed off stylesheet origin and -/// pseudo-element (see `CascadeData`). -/// -/// This structure is effectively created once per pipeline, in the -/// LayoutThread corresponding to that pipeline. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Stylist { - /// Device that the stylist is currently evaluating against. - /// - /// This field deserves a bigger comment due to the different use that Gecko - /// and Servo give to it (that we should eventually unify). - /// - /// With Gecko, the device is never changed. Gecko manually tracks whether - /// the device data should be reconstructed, and "resets" the state of the - /// device. - /// - /// On Servo, on the other hand, the device is a really cheap representation - /// that is recreated each time some constraint changes and calling - /// `set_device`. - device: Device, - - /// The list of stylesheets. - stylesheets: StylistStylesheetSet, - - /// A cache of CascadeDatas for AuthorStylesheetSets (i.e., shadow DOM). - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "XXX: how to handle this?")] - author_data_cache: CascadeDataCache<CascadeData>, - - /// If true, the quirks-mode stylesheet is applied. - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "defined in selectors")] - quirks_mode: QuirksMode, - - /// Selector maps for all of the style sheets in the stylist, after - /// evalutaing media rules against the current device, split out per - /// cascade level. - cascade_data: DocumentCascadeData, - - /// Whether author styles are enabled. - author_styles_enabled: AuthorStylesEnabled, - - /// The rule tree, that stores the results of selector matching. - rule_tree: RuleTree, - - /// The total number of times the stylist has been rebuilt. - num_rebuilds: usize, -} - -/// What cascade levels to include when styling elements. -#[derive(Clone, Copy, PartialEq)] -pub enum RuleInclusion { - /// Include rules for style sheets at all cascade levels. This is the - /// normal rule inclusion mode. - All, - /// Only include rules from UA and user level sheets. Used to implement - /// `getDefaultComputedStyle`. - DefaultOnly, -} - -#[cfg(feature = "gecko")] -impl From<StyleRuleInclusion> for RuleInclusion { - fn from(value: StyleRuleInclusion) -> Self { - match value { - StyleRuleInclusion::All => RuleInclusion::All, - StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly, - } - } -} - -type AncestorSelectorList<'a> = Cow<'a, SelectorList<SelectorImpl>>; - -/// A struct containing state from ancestor rules like @layer / @import / -/// @container / nesting. -struct ContainingRuleState<'a> { - layer_name: LayerName, - layer_id: LayerId, - container_condition_id: ContainerConditionId, - ancestor_selector_lists: SmallVec<[AncestorSelectorList<'a>; 2]>, -} - -impl<'a> Default for ContainingRuleState<'a> { - fn default() -> Self { - Self { - layer_name: LayerName::new_empty(), - layer_id: LayerId::root(), - container_condition_id: ContainerConditionId::none(), - ancestor_selector_lists: Default::default(), - } - } -} - -struct SavedContainingRuleState { - ancestor_selector_lists_len: usize, - layer_name_len: usize, - layer_id: LayerId, - container_condition_id: ContainerConditionId, -} - -impl<'a> ContainingRuleState<'a> { - fn save(&self) -> SavedContainingRuleState { - SavedContainingRuleState { - ancestor_selector_lists_len: self.ancestor_selector_lists.len(), - layer_name_len: self.layer_name.0.len(), - layer_id: self.layer_id, - container_condition_id: self.container_condition_id, - } - } - - fn restore(&mut self, saved: &SavedContainingRuleState) { - debug_assert!(self.layer_name.0.len() >= saved.layer_name_len); - debug_assert!(self.ancestor_selector_lists.len() >= saved.ancestor_selector_lists_len); - self.ancestor_selector_lists - .truncate(saved.ancestor_selector_lists_len); - self.layer_name.0.truncate(saved.layer_name_len); - self.layer_id = saved.layer_id; - self.container_condition_id = saved.container_condition_id; - } -} - -impl Stylist { - /// Construct a new `Stylist`, using given `Device` and `QuirksMode`. - /// If more members are added here, think about whether they should - /// be reset in clear(). - #[inline] - pub fn new(device: Device, quirks_mode: QuirksMode) -> Self { - Self { - device, - quirks_mode, - stylesheets: StylistStylesheetSet::new(), - author_data_cache: CascadeDataCache::new(), - cascade_data: Default::default(), - author_styles_enabled: AuthorStylesEnabled::Yes, - rule_tree: RuleTree::new(), - num_rebuilds: 0, - } - } - - /// Returns the document cascade data. - #[inline] - pub fn cascade_data(&self) -> &DocumentCascadeData { - &self.cascade_data - } - - /// Returns whether author styles are enabled or not. - #[inline] - pub fn author_styles_enabled(&self) -> AuthorStylesEnabled { - self.author_styles_enabled - } - - /// Iterate through all the cascade datas from the document. - #[inline] - pub fn iter_origins(&self) -> DocumentCascadeDataIter { - self.cascade_data.iter_origins() - } - - /// Does what the name says, to prevent author_data_cache to grow without - /// bound. - pub fn remove_unique_author_data_cache_entries(&mut self) { - self.author_data_cache.take_unused(); - } - - /// Rebuilds (if needed) the CascadeData given a sheet collection. - pub fn rebuild_author_data<S>( - &mut self, - old_data: &CascadeData, - collection: SheetCollectionFlusher<S>, - guard: &SharedRwLockReadGuard, - ) -> Result<Option<Arc<CascadeData>>, AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static, - { - self.author_data_cache - .lookup(&self.device, self.quirks_mode, collection, guard, old_data) - } - - /// Iterate over the extra data in origin order. - #[inline] - pub fn iter_extra_data_origins(&self) -> ExtraStyleDataIterator { - ExtraStyleDataIterator(self.cascade_data.iter_origins()) - } - - /// Iterate over the extra data in reverse origin order. - #[inline] - pub fn iter_extra_data_origins_rev(&self) -> ExtraStyleDataIterator { - ExtraStyleDataIterator(self.cascade_data.iter_origins_rev()) - } - - /// Returns the number of selectors. - pub fn num_selectors(&self) -> usize { - self.cascade_data - .iter_origins() - .map(|(d, _)| d.num_selectors) - .sum() - } - - /// Returns the number of declarations. - pub fn num_declarations(&self) -> usize { - self.cascade_data - .iter_origins() - .map(|(d, _)| d.num_declarations) - .sum() - } - - /// Returns the number of times the stylist has been rebuilt. - pub fn num_rebuilds(&self) -> usize { - self.num_rebuilds - } - - /// Returns the number of revalidation_selectors. - pub fn num_revalidation_selectors(&self) -> usize { - self.cascade_data - .iter_origins() - .map(|(data, _)| data.selectors_for_cache_revalidation.len()) - .sum() - } - - /// Returns the number of entries in invalidation maps. - pub fn num_invalidations(&self) -> usize { - self.cascade_data - .iter_origins() - .map(|(data, _)| data.invalidation_map.len()) - .sum() - } - - /// Returns whether the given DocumentState bit is relied upon by a selector - /// of some rule. - pub fn has_document_state_dependency(&self, state: DocumentState) -> bool { - self.cascade_data - .iter_origins() - .any(|(d, _)| d.document_state_dependencies.intersects(state)) - } - - /// Flush the list of stylesheets if they changed, ensuring the stylist is - /// up-to-date. - pub fn flush<E>( - &mut self, - guards: &StylesheetGuards, - document_element: Option<E>, - snapshots: Option<&SnapshotMap>, - ) -> bool - where - E: TElement, - { - if !self.stylesheets.has_changed() { - return false; - } - - self.num_rebuilds += 1; - - let flusher = self.stylesheets.flush(document_element, snapshots); - - let had_invalidations = flusher.had_invalidations(); - - self.cascade_data - .rebuild(&self.device, self.quirks_mode, flusher, guards) - .unwrap_or_else(|_| warn!("OOM in Stylist::flush")); - - had_invalidations - } - - /// Insert a given stylesheet before another stylesheet in the document. - pub fn insert_stylesheet_before( - &mut self, - sheet: StylistSheet, - before_sheet: StylistSheet, - guard: &SharedRwLockReadGuard, - ) { - self.stylesheets - .insert_stylesheet_before(Some(&self.device), sheet, before_sheet, guard) - } - - /// Marks a given stylesheet origin as dirty, due to, for example, changes - /// in the declarations that affect a given rule. - /// - /// FIXME(emilio): Eventually it'd be nice for this to become more - /// fine-grained. - pub fn force_stylesheet_origins_dirty(&mut self, origins: OriginSet) { - self.stylesheets.force_dirty(origins) - } - - /// Sets whether author style is enabled or not. - pub fn set_author_styles_enabled(&mut self, enabled: AuthorStylesEnabled) { - self.author_styles_enabled = enabled; - } - - /// Returns whether we've recorded any stylesheet change so far. - pub fn stylesheets_have_changed(&self) -> bool { - self.stylesheets.has_changed() - } - - /// Appends a new stylesheet to the current set. - pub fn append_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { - self.stylesheets - .append_stylesheet(Some(&self.device), sheet, guard) - } - - /// Remove a given stylesheet to the current set. - pub fn remove_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { - self.stylesheets - .remove_stylesheet(Some(&self.device), sheet, guard) - } - - /// Notify of a change of a given rule. - pub fn rule_changed( - &mut self, - sheet: &StylistSheet, - rule: &CssRule, - guard: &SharedRwLockReadGuard, - change_kind: RuleChangeKind, - ) { - self.stylesheets - .rule_changed(Some(&self.device), sheet, rule, guard, change_kind) - } - - /// Appends a new stylesheet to the current set. - #[inline] - pub fn sheet_count(&self, origin: Origin) -> usize { - self.stylesheets.sheet_count(origin) - } - - /// Appends a new stylesheet to the current set. - #[inline] - pub fn sheet_at(&self, origin: Origin, index: usize) -> Option<&StylistSheet> { - self.stylesheets.get(origin, index) - } - - /// Returns whether for any of the applicable style rule data a given - /// condition is true. - pub fn any_applicable_rule_data<E, F>(&self, element: E, mut f: F) -> bool - where - E: TElement, - F: FnMut(&CascadeData) -> bool, - { - if f(&self.cascade_data.user_agent.cascade_data) { - return true; - } - - let mut maybe = false; - - let doc_author_rules_apply = - element.each_applicable_non_document_style_rule_data(|data, _| { - maybe = maybe || f(&*data); - }); - - if maybe || f(&self.cascade_data.user) { - return true; - } - - doc_author_rules_apply && f(&self.cascade_data.author) - } - - /// Computes the style for a given "precomputed" pseudo-element, taking the - /// universal rules and applying them. - pub fn precomputed_values_for_pseudo<E>( - &self, - guards: &StylesheetGuards, - pseudo: &PseudoElement, - parent: Option<&ComputedValues>, - ) -> Arc<ComputedValues> - where - E: TElement, - { - debug_assert!(pseudo.is_precomputed()); - - let rule_node = self.rule_node_for_precomputed_pseudo(guards, pseudo, vec![]); - - self.precomputed_values_for_pseudo_with_rule_node::<E>(guards, pseudo, parent, rule_node) - } - - /// Computes the style for a given "precomputed" pseudo-element with - /// given rule node. - /// - /// TODO(emilio): The type parameter could go away with a void type - /// implementing TElement. - pub fn precomputed_values_for_pseudo_with_rule_node<E>( - &self, - guards: &StylesheetGuards, - pseudo: &PseudoElement, - parent: Option<&ComputedValues>, - rules: StrongRuleNode, - ) -> Arc<ComputedValues> - where - E: TElement, - { - self.compute_pseudo_element_style_with_inputs::<E>( - CascadeInputs { - rules: Some(rules), - visited_rules: None, - flags: Default::default(), - }, - pseudo, - guards, - /* originating_element_style */ None, - parent, - /* element */ None, - ) - } - - /// Returns the rule node for a given precomputed pseudo-element. - /// - /// If we want to include extra declarations to this precomputed - /// pseudo-element, we can provide a vector of ApplicableDeclarationBlocks - /// to extra_declarations. This is useful for @page rules. - pub fn rule_node_for_precomputed_pseudo( - &self, - guards: &StylesheetGuards, - pseudo: &PseudoElement, - mut extra_declarations: Vec<ApplicableDeclarationBlock>, - ) -> StrongRuleNode { - let mut declarations_with_extra; - let declarations = match self - .cascade_data - .user_agent - .precomputed_pseudo_element_decls - .get(pseudo) - { - Some(declarations) => { - if !extra_declarations.is_empty() { - declarations_with_extra = declarations.clone(); - declarations_with_extra.append(&mut extra_declarations); - &*declarations_with_extra - } else { - &**declarations - } - }, - None => &[], - }; - - self.rule_tree.insert_ordered_rules_with_important( - declarations.into_iter().map(|a| a.clone().for_rule_tree()), - guards, - ) - } - - /// Returns the style for an anonymous box of the given type. - /// - /// TODO(emilio): The type parameter could go away with a void type - /// implementing TElement. - #[cfg(feature = "servo")] - pub fn style_for_anonymous<E>( - &self, - guards: &StylesheetGuards, - pseudo: &PseudoElement, - parent_style: &ComputedValues, - ) -> Arc<ComputedValues> - where - E: TElement, - { - self.precomputed_values_for_pseudo::<E>(guards, &pseudo, Some(parent_style)) - } - - /// Computes a pseudo-element style lazily during layout. - /// - /// This can only be done for a certain set of pseudo-elements, like - /// :selection. - /// - /// Check the documentation on lazy pseudo-elements in - /// docs/components/style.md - pub fn lazily_compute_pseudo_element_style<E>( - &self, - guards: &StylesheetGuards, - element: E, - pseudo: &PseudoElement, - rule_inclusion: RuleInclusion, - originating_element_style: &ComputedValues, - parent_style: &Arc<ComputedValues>, - is_probe: bool, - matching_fn: Option<&dyn Fn(&PseudoElement) -> bool>, - ) -> Option<Arc<ComputedValues>> - where - E: TElement, - { - let cascade_inputs = self.lazy_pseudo_rules( - guards, - element, - originating_element_style, - parent_style, - pseudo, - is_probe, - rule_inclusion, - matching_fn, - )?; - - Some(self.compute_pseudo_element_style_with_inputs( - cascade_inputs, - pseudo, - guards, - Some(originating_element_style), - Some(parent_style), - Some(element), - )) - } - - /// Computes a pseudo-element style lazily using the given CascadeInputs. - /// This can be used for truly lazy pseudo-elements or to avoid redoing - /// selector matching for eager pseudo-elements when we need to recompute - /// their style with a new parent style. - pub fn compute_pseudo_element_style_with_inputs<E>( - &self, - inputs: CascadeInputs, - pseudo: &PseudoElement, - guards: &StylesheetGuards, - originating_element_style: Option<&ComputedValues>, - parent_style: Option<&ComputedValues>, - element: Option<E>, - ) -> Arc<ComputedValues> - where - E: TElement, - { - // FIXME(emilio): The lack of layout_parent_style here could be - // worrying, but we're probably dropping the display fixup for - // pseudos other than before and after, so it's probably ok. - // - // (Though the flags don't indicate so!) - // - // It'd be fine to assert that this isn't called with a parent style - // where display contents is in effect, but in practice this is hard to - // do for stuff like :-moz-fieldset-content with a - // <fieldset style="display: contents">. That is, the computed value of - // display for the fieldset is "contents", even though it's not the used - // value, so we don't need to adjust in a different way anyway. - self.cascade_style_and_visited( - element, - Some(pseudo), - inputs, - guards, - originating_element_style, - parent_style, - parent_style, - parent_style, - /* rule_cache = */ None, - &mut RuleCacheConditions::default(), - ) - } - - /// Computes a style using the given CascadeInputs. This can be used to - /// compute a style any time we know what rules apply and just need to use - /// the given parent styles. - /// - /// parent_style is the style to inherit from for properties affected by - /// first-line ancestors. - /// - /// parent_style_ignoring_first_line is the style to inherit from for - /// properties not affected by first-line ancestors. - /// - /// layout_parent_style is the style used for some property fixups. It's - /// the style of the nearest ancestor with a layout box. - pub fn cascade_style_and_visited<E>( - &self, - element: Option<E>, - pseudo: Option<&PseudoElement>, - inputs: CascadeInputs, - guards: &StylesheetGuards, - originating_element_style: Option<&ComputedValues>, - parent_style: Option<&ComputedValues>, - parent_style_ignoring_first_line: Option<&ComputedValues>, - layout_parent_style: Option<&ComputedValues>, - rule_cache: Option<&RuleCache>, - rule_cache_conditions: &mut RuleCacheConditions, - ) -> Arc<ComputedValues> - where - E: TElement, - { - debug_assert!(pseudo.is_some() || element.is_some(), "Huh?"); - - // We need to compute visited values if we have visited rules or if our - // parent has visited values. - let visited_rules = match inputs.visited_rules.as_ref() { - Some(rules) => Some(rules), - None => { - if parent_style.and_then(|s| s.visited_style()).is_some() { - Some(inputs.rules.as_ref().unwrap_or(self.rule_tree.root())) - } else { - None - } - }, - }; - - // Read the comment on `precomputed_values_for_pseudo` to see why it's - // difficult to assert that display: contents nodes never arrive here - // (tl;dr: It doesn't apply for replaced elements and such, but the - // computed value is still "contents"). - // - // FIXME(emilio): We should assert that it holds if pseudo.is_none()! - properties::cascade::<E>( - &self.device, - pseudo, - inputs.rules.as_ref().unwrap_or(self.rule_tree.root()), - guards, - originating_element_style, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - visited_rules, - inputs.flags, - self.quirks_mode, - rule_cache, - rule_cache_conditions, - element, - ) - } - - /// Computes the cascade inputs for a lazily-cascaded pseudo-element. - /// - /// See the documentation on lazy pseudo-elements in - /// docs/components/style.md - fn lazy_pseudo_rules<E>( - &self, - guards: &StylesheetGuards, - element: E, - originating_element_style: &ComputedValues, - parent_style: &Arc<ComputedValues>, - pseudo: &PseudoElement, - is_probe: bool, - rule_inclusion: RuleInclusion, - matching_fn: Option<&dyn Fn(&PseudoElement) -> bool>, - ) -> Option<CascadeInputs> - where - E: TElement, - { - debug_assert!(pseudo.is_lazy()); - - let mut nth_index_cache = Default::default(); - // No need to bother setting the selector flags when we're computing - // default styles. - let needs_selector_flags = if rule_inclusion == RuleInclusion::DefaultOnly { - NeedsSelectorFlags::No - } else { - NeedsSelectorFlags::Yes - }; - - let mut declarations = ApplicableDeclarationList::new(); - let mut matching_context = MatchingContext::<'_, E::Impl>::new( - MatchingMode::ForStatelessPseudoElement, - None, - &mut nth_index_cache, - self.quirks_mode, - needs_selector_flags, - ); - - matching_context.pseudo_element_matching_fn = matching_fn; - matching_context.extra_data.originating_element_style = Some(originating_element_style); - - self.push_applicable_declarations( - element, - Some(&pseudo), - None, - None, - /* animation_declarations = */ Default::default(), - rule_inclusion, - &mut declarations, - &mut matching_context, - ); - - if declarations.is_empty() && is_probe { - return None; - } - - let rules = self.rule_tree.compute_rule_node(&mut declarations, guards); - - let mut visited_rules = None; - if parent_style.visited_style().is_some() { - let mut declarations = ApplicableDeclarationList::new(); - let mut nth_index_cache = Default::default(); - - let mut matching_context = MatchingContext::<'_, E::Impl>::new_for_visited( - MatchingMode::ForStatelessPseudoElement, - None, - &mut nth_index_cache, - VisitedHandlingMode::RelevantLinkVisited, - self.quirks_mode, - needs_selector_flags, - ); - matching_context.pseudo_element_matching_fn = matching_fn; - matching_context.extra_data.originating_element_style = Some(originating_element_style); - - self.push_applicable_declarations( - element, - Some(&pseudo), - None, - None, - /* animation_declarations = */ Default::default(), - rule_inclusion, - &mut declarations, - &mut matching_context, - ); - if !declarations.is_empty() { - let rule_node = self.rule_tree.insert_ordered_rules_with_important( - declarations.drain(..).map(|a| a.for_rule_tree()), - guards, - ); - if rule_node != *self.rule_tree.root() { - visited_rules = Some(rule_node); - } - } - } - - Some(CascadeInputs { - rules: Some(rules), - visited_rules, - flags: matching_context.extra_data.cascade_input_flags, - }) - } - - /// Set a given device, which may change the styles that apply to the - /// document. - /// - /// Returns the sheet origins that were actually affected. - /// - /// This means that we may need to rebuild style data even if the - /// stylesheets haven't changed. - /// - /// Also, the device that arrives here may need to take the viewport rules - /// into account. - pub fn set_device(&mut self, device: Device, guards: &StylesheetGuards) -> OriginSet { - self.device = device; - self.media_features_change_changed_style(guards, &self.device) - } - - /// Returns whether, given a media feature change, any previously-applicable - /// style has become non-applicable, or vice-versa for each origin, using - /// `device`. - pub fn media_features_change_changed_style( - &self, - guards: &StylesheetGuards, - device: &Device, - ) -> OriginSet { - debug!("Stylist::media_features_change_changed_style {:?}", device); - - let mut origins = OriginSet::empty(); - let stylesheets = self.stylesheets.iter(); - - for (stylesheet, origin) in stylesheets { - if origins.contains(origin.into()) { - continue; - } - - let guard = guards.for_origin(origin); - let origin_cascade_data = self.cascade_data.borrow_for_origin(origin); - - let affected_changed = !origin_cascade_data.media_feature_affected_matches( - stylesheet, - guard, - device, - self.quirks_mode, - ); - - if affected_changed { - origins |= origin; - } - } - - origins - } - - /// Returns the Quirks Mode of the document. - pub fn quirks_mode(&self) -> QuirksMode { - self.quirks_mode - } - - /// Sets the quirks mode of the document. - pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) { - if self.quirks_mode == quirks_mode { - return; - } - self.quirks_mode = quirks_mode; - self.force_stylesheet_origins_dirty(OriginSet::all()); - } - - /// Returns the applicable CSS declarations for the given element. - pub fn push_applicable_declarations<E>( - &self, - element: E, - pseudo_element: Option<&PseudoElement>, - style_attribute: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, - smil_override: Option<ArcBorrow<Locked<PropertyDeclarationBlock>>>, - animation_declarations: AnimationDeclarations, - rule_inclusion: RuleInclusion, - applicable_declarations: &mut ApplicableDeclarationList, - context: &mut MatchingContext<E::Impl>, - ) where - E: TElement, - { - RuleCollector::new( - self, - element, - pseudo_element, - style_attribute, - smil_override, - animation_declarations, - rule_inclusion, - applicable_declarations, - context, - ) - .collect_all(); - } - - /// Given an id, returns whether there might be any rules for that id in any - /// of our rule maps. - #[inline] - pub fn may_have_rules_for_id<E>(&self, id: &WeakAtom, element: E) -> bool - where - E: TElement, - { - // If id needs to be compared case-insensitively, the logic below - // wouldn't work. Just conservatively assume it may have such rules. - match self.quirks_mode().classes_and_ids_case_sensitivity() { - CaseSensitivity::AsciiCaseInsensitive => return true, - CaseSensitivity::CaseSensitive => {}, - } - - self.any_applicable_rule_data(element, |data| data.mapped_ids.contains(id)) - } - - /// Returns the registered `@keyframes` animation for the specified name. - #[inline] - pub fn get_animation<'a, E>(&'a self, name: &Atom, element: E) -> Option<&'a KeyframesAnimation> - where - E: TElement + 'a, - { - macro_rules! try_find_in { - ($data:expr) => { - if let Some(animation) = $data.animations.get(name) { - return Some(animation); - } - }; - } - - // NOTE(emilio): We implement basically what Blink does for this case, - // which is [1] as of this writing. - // - // See [2] for the spec discussion about what to do about this. WebKit's - // behavior makes a bit more sense off-hand, but it's way more complex - // to implement, and it makes value computation having to thread around - // the cascade level, which is not great. Also, it breaks if you inherit - // animation-name from an element in a different tree. - // - // See [3] for the bug to implement whatever gets resolved, and related - // bugs for a bit more context. - // - // FIXME(emilio): This should probably work for pseudo-elements (i.e., - // use rule_hash_target().shadow_root() instead of - // element.shadow_root()). - // - // [1]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/ - // core/css/resolver/style_resolver.cc?l=1267&rcl=90f9f8680ebb4a87d177f3b0833372ae4e0c88d8 - // [2]: https://github.com/w3c/csswg-drafts/issues/1995 - // [3]: https://bugzil.la/1458189 - if let Some(shadow) = element.shadow_root() { - if let Some(data) = shadow.style_data() { - try_find_in!(data); - } - } - - // Use the same rules to look for the containing host as we do for rule - // collection. - if let Some(shadow) = containing_shadow_ignoring_svg_use(element) { - if let Some(data) = shadow.style_data() { - try_find_in!(data); - } - } else { - try_find_in!(self.cascade_data.author); - } - - try_find_in!(self.cascade_data.user); - try_find_in!(self.cascade_data.user_agent.cascade_data); - - None - } - - /// Computes the match results of a given element against the set of - /// revalidation selectors. - pub fn match_revalidation_selectors<E>( - &self, - element: E, - bloom: Option<&BloomFilter>, - nth_index_cache: &mut NthIndexCache, - needs_selector_flags: NeedsSelectorFlags, - ) -> SmallBitVec - where - E: TElement, - { - // NB: `MatchingMode` doesn't really matter, given we don't share style - // between pseudos. - let mut matching_context = MatchingContext::new( - MatchingMode::Normal, - bloom, - nth_index_cache, - self.quirks_mode, - needs_selector_flags, - ); - - // Note that, by the time we're revalidating, we're guaranteed that the - // candidate and the entry have the same id, classes, and local name. - // This means we're guaranteed to get the same rulehash buckets for all - // the lookups, which means that the bitvecs are comparable. We verify - // this in the caller by asserting that the bitvecs are same-length. - let mut results = SmallBitVec::new(); - - let matches_document_rules = - element.each_applicable_non_document_style_rule_data(|data, host| { - matching_context.with_shadow_host(Some(host), |matching_context| { - data.selectors_for_cache_revalidation.lookup( - element, - self.quirks_mode, - |selector_and_hashes| { - results.push(matches_selector( - &selector_and_hashes.selector, - selector_and_hashes.selector_offset, - Some(&selector_and_hashes.hashes), - &element, - matching_context, - )); - true - }, - ); - }) - }); - - for (data, origin) in self.cascade_data.iter_origins() { - if origin == Origin::Author && !matches_document_rules { - continue; - } - - data.selectors_for_cache_revalidation.lookup( - element, - self.quirks_mode, - |selector_and_hashes| { - results.push(matches_selector( - &selector_and_hashes.selector, - selector_and_hashes.selector_offset, - Some(&selector_and_hashes.hashes), - &element, - &mut matching_context, - )); - true - }, - ); - } - - results - } - - /// Computes styles for a given declaration with parent_style. - /// - /// FIXME(emilio): the lack of pseudo / cascade flags look quite dubious, - /// hopefully this is only used for some canvas font stuff. - /// - /// TODO(emilio): The type parameter can go away when - /// https://github.com/rust-lang/rust/issues/35121 is fixed. - pub fn compute_for_declarations<E>( - &self, - guards: &StylesheetGuards, - parent_style: &ComputedValues, - declarations: Arc<Locked<PropertyDeclarationBlock>>, - ) -> Arc<ComputedValues> - where - E: TElement, - { - let block = declarations.read_with(guards.author); - - // We don't bother inserting these declarations in the rule tree, since - // it'd be quite useless and slow. - // - // TODO(emilio): Now that we fixed bug 1493420, we should consider - // reversing this as it shouldn't be slow anymore, and should avoid - // generating two instantiations of apply_declarations. - properties::apply_declarations::<E, _>( - &self.device, - /* pseudo = */ None, - self.rule_tree.root(), - guards, - block.declaration_importance_iter().map(|(declaration, _)| { - ( - declaration, - CascadePriority::new( - CascadeLevel::same_tree_author_normal(), - LayerOrder::root(), - ), - ) - }), - /* originating_element_style */ None, - Some(parent_style), - Some(parent_style), - Some(parent_style), - CascadeMode::Unvisited { - visited_rules: None, - }, - Default::default(), - self.quirks_mode, - /* rule_cache = */ None, - &mut Default::default(), - /* element = */ None, - ) - } - - /// Accessor for a shared reference to the device. - #[inline] - pub fn device(&self) -> &Device { - &self.device - } - - /// Accessor for a mutable reference to the device. - #[inline] - pub fn device_mut(&mut self) -> &mut Device { - &mut self.device - } - - /// Accessor for a shared reference to the rule tree. - #[inline] - pub fn rule_tree(&self) -> &RuleTree { - &self.rule_tree - } - - /// Measures heap usage. - #[cfg(feature = "gecko")] - pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.cascade_data.add_size_of(ops, sizes); - self.author_data_cache.add_size_of(ops, sizes); - sizes.mRuleTree += self.rule_tree.size_of(ops); - - // We may measure other fields in the future if DMD says it's worth it. - } - - /// Shutdown the static data that this module stores. - pub fn shutdown() { - let _entries = UA_CASCADE_DATA_CACHE.lock().unwrap().take_all(); - } -} - -/// A vector that is sorted in layer order. -#[derive(Clone, Debug, Deref, MallocSizeOf)] -pub struct LayerOrderedVec<T>(Vec<(T, LayerId)>); -impl<T> Default for LayerOrderedVec<T> { - fn default() -> Self { - Self(Default::default()) - } -} - -/// A map that is sorted in layer order. -#[derive(Clone, Debug, Deref, MallocSizeOf)] -pub struct LayerOrderedMap<T>(PrecomputedHashMap<Atom, SmallVec<[(T, LayerId); 1]>>); -impl<T> Default for LayerOrderedMap<T> { - fn default() -> Self { - Self(Default::default()) - } -} - -#[cfg(feature = "gecko")] -impl<T: 'static> LayerOrderedVec<T> { - fn clear(&mut self) { - self.0.clear(); - } - fn push(&mut self, v: T, id: LayerId) { - self.0.push((v, id)); - } - fn sort(&mut self, layers: &[CascadeLayer]) { - self.0 - .sort_by_key(|&(_, ref id)| layers[id.0 as usize].order) - } -} - -impl<T: 'static> LayerOrderedMap<T> { - fn clear(&mut self) { - self.0.clear(); - } - #[cfg(feature = "gecko")] - fn try_insert(&mut self, name: Atom, v: T, id: LayerId) -> Result<(), AllocErr> { - self.try_insert_with(name, v, id, |_, _| Ordering::Equal) - } - fn try_insert_with( - &mut self, - name: Atom, - v: T, - id: LayerId, - cmp: impl Fn(&T, &T) -> Ordering, - ) -> Result<(), AllocErr> { - self.0.try_reserve(1)?; - let vec = self.0.entry(name).or_default(); - if let Some(&mut (ref mut val, ref last_id)) = vec.last_mut() { - if *last_id == id { - if cmp(&val, &v) != Ordering::Greater { - *val = v; - } - return Ok(()); - } - } - vec.push((v, id)); - Ok(()) - } - #[cfg(feature = "gecko")] - fn sort(&mut self, layers: &[CascadeLayer]) { - self.sort_with(layers, |_, _| Ordering::Equal) - } - fn sort_with(&mut self, layers: &[CascadeLayer], cmp: impl Fn(&T, &T) -> Ordering) { - for (_, v) in self.0.iter_mut() { - v.sort_by(|&(ref v1, ref id1), &(ref v2, ref id2)| { - let order1 = layers[id1.0 as usize].order; - let order2 = layers[id2.0 as usize].order; - order1.cmp(&order2).then_with(|| cmp(v1, v2)) - }) - } - } - /// Get an entry on the LayerOrderedMap by name. - pub fn get(&self, name: &Atom) -> Option<&T> { - let vec = self.0.get(name)?; - Some(&vec.last()?.0) - } -} - -/// Wrapper to allow better tracking of memory usage by page rule lists. -/// -/// This includes the layer ID for use with the named page table. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct PageRuleData { - /// Layer ID for sorting page rules after matching. - pub layer: LayerId, - /// Page rule - #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] - pub rule: Arc<Locked<PageRule>>, -} - -/// Wrapper to allow better tracking of memory usage by page rule lists. -/// -/// This is meant to be used by the global page rule list which are already -/// sorted by layer ID, since all global page rules are less specific than all -/// named page rules that match a certain page. -#[derive(Clone, Debug, Deref, MallocSizeOf)] -pub struct PageRuleDataNoLayer( - #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] pub Arc<Locked<PageRule>>, -); - -/// Stores page rules indexed by page names. -#[derive(Clone, Debug, Default, MallocSizeOf)] -pub struct PageRuleMap { - /// Global, unnamed page rules. - pub global: LayerOrderedVec<PageRuleDataNoLayer>, - /// Named page rules - pub named: PrecomputedHashMap<Atom, SmallVec<[PageRuleData; 1]>>, -} - -#[cfg(feature = "gecko")] -impl PageRuleMap { - #[inline] - fn clear(&mut self) { - self.global.clear(); - self.named.clear(); - } -} - -impl MallocShallowSizeOf for PageRuleMap { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.global.size_of(ops) + self.named.shallow_size_of(ops) - } -} - -/// This struct holds data which users of Stylist may want to extract -/// from stylesheets which can be done at the same time as updating. -#[derive(Clone, Debug, Default)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct ExtraStyleData { - /// A list of effective font-face rules and their origin. - #[cfg(feature = "gecko")] - pub font_faces: LayerOrderedVec<Arc<Locked<FontFaceRule>>>, - - /// A list of effective font-feature-values rules. - #[cfg(feature = "gecko")] - pub font_feature_values: LayerOrderedVec<Arc<FontFeatureValuesRule>>, - - /// A list of effective font-palette-values rules. - #[cfg(feature = "gecko")] - pub font_palette_values: LayerOrderedVec<Arc<FontPaletteValuesRule>>, - - /// A map of effective counter-style rules. - #[cfg(feature = "gecko")] - pub counter_styles: LayerOrderedMap<Arc<Locked<CounterStyleRule>>>, - - /// A map of effective page rules. - #[cfg(feature = "gecko")] - pub pages: PageRuleMap, -} - -#[cfg(feature = "gecko")] -impl ExtraStyleData { - /// Add the given @font-face rule. - fn add_font_face(&mut self, rule: &Arc<Locked<FontFaceRule>>, layer: LayerId) { - self.font_faces.push(rule.clone(), layer); - } - - /// Add the given @font-feature-values rule. - fn add_font_feature_values(&mut self, rule: &Arc<FontFeatureValuesRule>, layer: LayerId) { - self.font_feature_values.push(rule.clone(), layer); - } - - /// Add the given @font-palette-values rule. - fn add_font_palette_values(&mut self, rule: &Arc<FontPaletteValuesRule>, layer: LayerId) { - self.font_palette_values.push(rule.clone(), layer); - } - - /// Add the given @counter-style rule. - fn add_counter_style( - &mut self, - guard: &SharedRwLockReadGuard, - rule: &Arc<Locked<CounterStyleRule>>, - layer: LayerId, - ) -> Result<(), AllocErr> { - let name = rule.read_with(guard).name().0.clone(); - self.counter_styles.try_insert(name, rule.clone(), layer) - } - - /// Add the given @page rule. - fn add_page( - &mut self, - guard: &SharedRwLockReadGuard, - rule: &Arc<Locked<PageRule>>, - layer: LayerId, - ) -> Result<(), AllocErr> { - let page_rule = rule.read_with(guard); - if page_rule.selectors.0.is_empty() { - self.pages - .global - .push(PageRuleDataNoLayer(rule.clone()), layer); - } else { - // TODO: Handle pseudo-classes - self.pages.named.try_reserve(page_rule.selectors.0.len())?; - for name in page_rule.selectors.as_slice() { - let vec = self.pages.named.entry(name.0 .0.clone()).or_default(); - vec.try_reserve(1)?; - vec.push(PageRuleData { - layer, - rule: rule.clone(), - }); - } - } - Ok(()) - } - - fn sort_by_layer(&mut self, layers: &[CascadeLayer]) { - self.font_faces.sort(layers); - self.font_feature_values.sort(layers); - self.font_palette_values.sort(layers); - self.counter_styles.sort(layers); - self.pages.global.sort(layers); - } - - fn clear(&mut self) { - #[cfg(feature = "gecko")] - { - self.font_faces.clear(); - self.font_feature_values.clear(); - self.font_palette_values.clear(); - self.counter_styles.clear(); - self.pages.clear(); - } - } -} - -// Don't let a prefixed keyframes animation override -// a non-prefixed one. -fn compare_keyframes_in_same_layer(v1: &KeyframesAnimation, v2: &KeyframesAnimation) -> Ordering { - if v1.vendor_prefix.is_some() == v2.vendor_prefix.is_some() { - Ordering::Equal - } else if v2.vendor_prefix.is_some() { - Ordering::Greater - } else { - Ordering::Less - } -} - -/// An iterator over the different ExtraStyleData. -pub struct ExtraStyleDataIterator<'a>(DocumentCascadeDataIter<'a>); - -impl<'a> Iterator for ExtraStyleDataIterator<'a> { - type Item = (&'a ExtraStyleData, Origin); - - fn next(&mut self) -> Option<Self::Item> { - self.0.next().map(|d| (&d.0.extra_data, d.1)) - } -} - -#[cfg(feature = "gecko")] -impl MallocSizeOf for ExtraStyleData { - /// Measure heap usage. - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - n += self.font_faces.shallow_size_of(ops); - n += self.font_feature_values.shallow_size_of(ops); - n += self.font_palette_values.shallow_size_of(ops); - n += self.counter_styles.shallow_size_of(ops); - n += self.pages.shallow_size_of(ops); - n - } -} - -/// SelectorMapEntry implementation for use in our revalidation selector map. -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug)] -struct RevalidationSelectorAndHashes { - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "CssRules have primary refs, we measure there" - )] - selector: Selector<SelectorImpl>, - selector_offset: usize, - hashes: AncestorHashes, -} - -impl RevalidationSelectorAndHashes { - fn new(selector: Selector<SelectorImpl>, hashes: AncestorHashes) -> Self { - let selector_offset = { - // We basically want to check whether the first combinator is a - // pseudo-element combinator. If it is, we want to use the offset - // one past it. Otherwise, our offset is 0. - let mut index = 0; - let mut iter = selector.iter(); - - // First skip over the first ComplexSelector. - // - // We can't check what sort of what combinator we have until we do - // that. - for _ in &mut iter { - index += 1; // Simple selector - } - - match iter.next_sequence() { - Some(Combinator::PseudoElement) => index + 1, // +1 for the combinator - _ => 0, - } - }; - - RevalidationSelectorAndHashes { - selector, - selector_offset, - hashes, - } - } -} - -impl SelectorMapEntry for RevalidationSelectorAndHashes { - fn selector(&self) -> SelectorIter<SelectorImpl> { - self.selector.iter_from(self.selector_offset) - } -} - -/// A selector visitor implementation that collects all the state the Stylist -/// cares about a selector. -struct StylistSelectorVisitor<'a> { - /// Whether we've past the rightmost compound selector, not counting - /// pseudo-elements. - passed_rightmost_selector: bool, - - /// Whether the selector needs revalidation for the style sharing cache. - needs_revalidation: &'a mut bool, - - /// Flags for which selector list-containing components the visitor is - /// inside of, if any - in_selector_list_of: SelectorListKind, - - /// The filter with all the id's getting referenced from rightmost - /// selectors. - mapped_ids: &'a mut PrecomputedHashSet<Atom>, - - /// The filter with the IDs getting referenced from the selector list of - /// :nth-child(... of <selector list>) selectors. - nth_of_mapped_ids: &'a mut PrecomputedHashSet<Atom>, - - /// The filter with the local names of attributes there are selectors for. - attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>, - - /// The filter with the classes getting referenced from the selector list of - /// :nth-child(... of <selector list>) selectors. - nth_of_class_dependencies: &'a mut PrecomputedHashSet<Atom>, - - /// The filter with the local names of attributes there are selectors for - /// within the selector list of :nth-child(... of <selector list>) - /// selectors. - nth_of_attribute_dependencies: &'a mut PrecomputedHashSet<LocalName>, - - /// All the states selectors in the page reference. - state_dependencies: &'a mut ElementState, - - /// All the state selectors in the page reference within the selector list - /// of :nth-child(... of <selector list>) selectors. - nth_of_state_dependencies: &'a mut ElementState, - - /// All the document states selectors in the page reference. - document_state_dependencies: &'a mut DocumentState, -} - -fn component_needs_revalidation( - c: &Component<SelectorImpl>, - passed_rightmost_selector: bool, -) -> bool { - match *c { - Component::ID(_) => { - // TODO(emilio): This could also check that the ID is not already in - // the rule hash. In that case, we could avoid making this a - // revalidation selector too. - // - // See https://bugzilla.mozilla.org/show_bug.cgi?id=1369611 - passed_rightmost_selector - }, - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::AttributeOther(_) | - Component::Empty | - Component::Nth(_) | - Component::NthOf(_) => true, - Component::NonTSPseudoClass(ref p) => p.needs_cache_revalidation(), - _ => false, - } -} - -impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> { - type Impl = SelectorImpl; - - fn visit_complex_selector(&mut self, combinator: Option<Combinator>) -> bool { - *self.needs_revalidation = - *self.needs_revalidation || combinator.map_or(false, |c| c.is_sibling()); - - // NOTE(emilio): this call happens before we visit any of the simple - // selectors in the next ComplexSelector, so we can use this to skip - // looking at them. - self.passed_rightmost_selector = self.passed_rightmost_selector || - !matches!(combinator, None | Some(Combinator::PseudoElement)); - - true - } - - fn visit_selector_list( - &mut self, - list_kind: SelectorListKind, - list: &[Selector<Self::Impl>], - ) -> bool { - let in_selector_list_of = self.in_selector_list_of | list_kind; - for selector in list { - let mut nested = StylistSelectorVisitor { - passed_rightmost_selector: false, - needs_revalidation: &mut *self.needs_revalidation, - in_selector_list_of, - mapped_ids: &mut *self.mapped_ids, - nth_of_mapped_ids: &mut *self.nth_of_mapped_ids, - attribute_dependencies: &mut *self.attribute_dependencies, - nth_of_class_dependencies: &mut *self.nth_of_class_dependencies, - nth_of_attribute_dependencies: &mut *self.nth_of_attribute_dependencies, - state_dependencies: &mut *self.state_dependencies, - nth_of_state_dependencies: &mut *self.nth_of_state_dependencies, - document_state_dependencies: &mut *self.document_state_dependencies, - }; - let _ret = selector.visit(&mut nested); - debug_assert!(_ret, "We never return false"); - } - true - } - - fn visit_attribute_selector( - &mut self, - _ns: &NamespaceConstraint<&Namespace>, - name: &LocalName, - lower_name: &LocalName, - ) -> bool { - if self.in_selector_list_of.in_nth_of() { - self.nth_of_attribute_dependencies.insert(name.clone()); - if name != lower_name { - self.nth_of_attribute_dependencies - .insert(lower_name.clone()); - } - } - - self.attribute_dependencies.insert(name.clone()); - if name != lower_name { - self.attribute_dependencies.insert(lower_name.clone()); - } - - true - } - - fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool { - *self.needs_revalidation = *self.needs_revalidation || - component_needs_revalidation(s, self.passed_rightmost_selector); - - match *s { - Component::NonTSPseudoClass(ref p) => { - self.state_dependencies.insert(p.state_flag()); - self.document_state_dependencies - .insert(p.document_state_flag()); - - if self.in_selector_list_of.in_nth_of() { - self.nth_of_state_dependencies.insert(p.state_flag()); - } - }, - Component::ID(ref id) => { - // We want to stop storing mapped ids as soon as we've moved off - // the rightmost ComplexSelector that is not a pseudo-element. - // - // That can be detected by a visit_complex_selector call with a - // combinator other than None and PseudoElement. - // - // Importantly, this call happens before we visit any of the - // simple selectors in that ComplexSelector. - // - // NOTE(emilio): See the comment regarding on when this may - // break in visit_complex_selector. - if !self.passed_rightmost_selector { - self.mapped_ids.insert(id.0.clone()); - } - - if self.in_selector_list_of.in_nth_of() { - self.nth_of_mapped_ids.insert(id.0.clone()); - } - }, - Component::Class(ref class) if self.in_selector_list_of.in_nth_of() => { - self.nth_of_class_dependencies.insert(class.0.clone()); - }, - _ => {}, - } - - true - } -} - -/// A set of rules for element and pseudo-elements. -#[derive(Clone, Debug, Default, MallocSizeOf)] -struct GenericElementAndPseudoRules<Map> { - /// Rules from stylesheets at this `CascadeData`'s origin. - element_map: Map, - - /// Rules from stylesheets at this `CascadeData`'s origin that correspond - /// to a given pseudo-element. - /// - /// FIXME(emilio): There are a bunch of wasted entries here in practice. - /// Figure out a good way to do a `PerNonAnonBox` and `PerAnonBox` (for - /// `precomputed_values_for_pseudo`) without duplicating a lot of code. - pseudos_map: PerPseudoElementMap<Box<Map>>, -} - -impl<Map: Default + MallocSizeOf> GenericElementAndPseudoRules<Map> { - #[inline(always)] - fn for_insertion(&mut self, pseudo_element: Option<&PseudoElement>) -> &mut Map { - debug_assert!( - pseudo_element.map_or(true, |pseudo| { - !pseudo.is_precomputed() && !pseudo.is_unknown_webkit_pseudo_element() - }), - "Precomputed pseudos should end up in precomputed_pseudo_element_decls, \ - and unknown webkit pseudos should be discarded before getting here" - ); - - match pseudo_element { - None => &mut self.element_map, - Some(pseudo) => self - .pseudos_map - .get_or_insert_with(pseudo, || Box::new(Default::default())), - } - } - - #[inline] - fn rules(&self, pseudo: Option<&PseudoElement>) -> Option<&Map> { - match pseudo { - Some(pseudo) => self.pseudos_map.get(pseudo).map(|p| &**p), - None => Some(&self.element_map), - } - } - - /// Measures heap usage. - #[cfg(feature = "gecko")] - fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - sizes.mElementAndPseudosMaps += self.element_map.size_of(ops); - - for elem in self.pseudos_map.iter() { - if let Some(ref elem) = *elem { - sizes.mElementAndPseudosMaps += <Box<_> as MallocSizeOf>::size_of(elem, ops); - } - } - } -} - -type ElementAndPseudoRules = GenericElementAndPseudoRules<SelectorMap<Rule>>; -type PartMap = PrecomputedHashMap<Atom, SmallVec<[Rule; 1]>>; -type PartElementAndPseudoRules = GenericElementAndPseudoRules<PartMap>; - -impl ElementAndPseudoRules { - // TODO(emilio): Should we retain storage of these? - fn clear(&mut self) { - self.element_map.clear(); - self.pseudos_map.clear(); - } - - fn shrink_if_needed(&mut self) { - self.element_map.shrink_if_needed(); - for pseudo in self.pseudos_map.iter_mut() { - if let Some(ref mut pseudo) = pseudo { - pseudo.shrink_if_needed(); - } - } - } -} - -impl PartElementAndPseudoRules { - // TODO(emilio): Should we retain storage of these? - fn clear(&mut self) { - self.element_map.clear(); - self.pseudos_map.clear(); - } -} - -/// The id of a given layer, a sequentially-increasing identifier. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerId(u16); - -impl LayerId { - /// The id of the root layer. - pub const fn root() -> Self { - Self(0) - } -} - -#[derive(Clone, Debug, MallocSizeOf)] -struct CascadeLayer { - id: LayerId, - order: LayerOrder, - children: Vec<LayerId>, -} - -impl CascadeLayer { - const fn root() -> Self { - Self { - id: LayerId::root(), - order: LayerOrder::root(), - children: vec![], - } - } -} - -/// The id of a given container condition, a sequentially-increasing identifier -/// for a given style set. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct ContainerConditionId(u16); - -impl ContainerConditionId { - /// A special id that represents no container rule all. - pub const fn none() -> Self { - Self(0) - } -} - -#[derive(Clone, Debug, MallocSizeOf)] -struct ContainerConditionReference { - parent: ContainerConditionId, - #[ignore_malloc_size_of = "Arc"] - condition: Option<Arc<ContainerCondition>>, -} - -impl ContainerConditionReference { - const fn none() -> Self { - Self { - parent: ContainerConditionId::none(), - condition: None, - } - } -} - -/// Data resulting from performing the CSS cascade that is specific to a given -/// origin. -/// -/// FIXME(emilio): Consider renaming and splitting in `CascadeData` and -/// `InvalidationData`? That'd make `clear_cascade_data()` clearer. -#[derive(Debug, Clone, MallocSizeOf)] -pub struct CascadeData { - /// The data coming from normal style rules that apply to elements at this - /// cascade level. - normal_rules: ElementAndPseudoRules, - - /// The `:host` pseudo rules that are the rightmost selector (without - /// accounting for pseudo-elements). - host_rules: Option<Box<ElementAndPseudoRules>>, - - /// The data coming from ::slotted() pseudo-element rules. - /// - /// We need to store them separately because an element needs to match - /// ::slotted() pseudo-element rules in different shadow roots. - /// - /// In particular, we need to go through all the style data in all the - /// containing style scopes starting from the closest assigned slot. - slotted_rules: Option<Box<ElementAndPseudoRules>>, - - /// The data coming from ::part() pseudo-element rules. - /// - /// We need to store them separately because an element needs to match - /// ::part() pseudo-element rules in different shadow roots. - part_rules: Option<Box<PartElementAndPseudoRules>>, - - /// The invalidation map for these rules. - invalidation_map: InvalidationMap, - - /// The attribute local names that appear in attribute selectors. Used - /// to avoid taking element snapshots when an irrelevant attribute changes. - /// (We don't bother storing the namespace, since namespaced attributes are - /// rare.) - attribute_dependencies: PrecomputedHashSet<LocalName>, - - /// The classes that appear in the selector list of - /// :nth-child(... of <selector list>). Used to avoid restyling siblings of - /// an element when an irrelevant class changes. - nth_of_class_dependencies: PrecomputedHashSet<Atom>, - - /// The attributes that appear in the selector list of - /// :nth-child(... of <selector list>). Used to avoid restyling siblings of - /// an element when an irrelevant attribute changes. - nth_of_attribute_dependencies: PrecomputedHashSet<LocalName>, - - /// The element state bits that are relied on by selectors. Like - /// `attribute_dependencies`, this is used to avoid taking element snapshots - /// when an irrelevant element state bit changes. - state_dependencies: ElementState, - - /// The element state bits that are relied on by selectors that appear in - /// the selector list of :nth-child(... of <selector list>). - nth_of_state_dependencies: ElementState, - - /// The document state bits that are relied on by selectors. This is used - /// to tell whether we need to restyle the entire document when a document - /// state bit changes. - document_state_dependencies: DocumentState, - - /// The ids that appear in the rightmost complex selector of selectors (and - /// hence in our selector maps). Used to determine when sharing styles is - /// safe: we disallow style sharing for elements whose id matches this - /// filter, and hence might be in one of our selector maps. - mapped_ids: PrecomputedHashSet<Atom>, - - /// The IDs that appear in the selector list of - /// :nth-child(... of <selector list>). Used to avoid restyling siblings - /// of an element when an irrelevant ID changes. - nth_of_mapped_ids: PrecomputedHashSet<Atom>, - - /// Selectors that require explicit cache revalidation (i.e. which depend - /// on state that is not otherwise visible to the cache, like attributes or - /// tree-structural state like child index and pseudos). - #[ignore_malloc_size_of = "Arc"] - selectors_for_cache_revalidation: SelectorMap<RevalidationSelectorAndHashes>, - - /// A map with all the animations at this `CascadeData`'s origin, indexed - /// by name. - animations: LayerOrderedMap<KeyframesAnimation>, - - /// A map from cascade layer name to layer order. - layer_id: FxHashMap<LayerName, LayerId>, - - /// The list of cascade layers, indexed by their layer id. - layers: SmallVec<[CascadeLayer; 1]>, - - /// The list of container conditions, indexed by their id. - container_conditions: SmallVec<[ContainerConditionReference; 1]>, - - /// Effective media query results cached from the last rebuild. - effective_media_query_results: EffectiveMediaQueryResults, - - /// Extra data, like different kinds of rules, etc. - extra_data: ExtraStyleData, - - /// A monotonically increasing counter to represent the order on which a - /// style rule appears in a stylesheet, needed to sort them by source order. - rules_source_order: u32, - - /// The total number of selectors. - num_selectors: usize, - - /// The total number of declarations. - num_declarations: usize, -} - -impl CascadeData { - /// Creates an empty `CascadeData`. - pub fn new() -> Self { - Self { - normal_rules: ElementAndPseudoRules::default(), - host_rules: None, - slotted_rules: None, - part_rules: None, - invalidation_map: InvalidationMap::new(), - nth_of_mapped_ids: PrecomputedHashSet::default(), - nth_of_class_dependencies: PrecomputedHashSet::default(), - nth_of_attribute_dependencies: PrecomputedHashSet::default(), - nth_of_state_dependencies: ElementState::empty(), - attribute_dependencies: PrecomputedHashSet::default(), - state_dependencies: ElementState::empty(), - document_state_dependencies: DocumentState::empty(), - mapped_ids: PrecomputedHashSet::default(), - // NOTE: We disable attribute bucketing for revalidation because we - // rely on the buckets to match, but we don't want to just not share - // style across elements with different attributes. - // - // An alternative to this would be to perform a style sharing check - // like may_match_different_id_rules which would check that the - // attribute buckets match on all scopes. But that seems - // somewhat gnarly. - selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), - animations: Default::default(), - layer_id: Default::default(), - layers: smallvec::smallvec![CascadeLayer::root()], - container_conditions: smallvec::smallvec![ContainerConditionReference::none()], - extra_data: ExtraStyleData::default(), - effective_media_query_results: EffectiveMediaQueryResults::new(), - rules_source_order: 0, - num_selectors: 0, - num_declarations: 0, - } - } - - /// Rebuild the cascade data from a given SheetCollection, incrementally if - /// possible. - pub fn rebuild<'a, S>( - &mut self, - device: &Device, - quirks_mode: QuirksMode, - collection: SheetCollectionFlusher<S>, - guard: &SharedRwLockReadGuard, - ) -> Result<(), AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static, - { - if !collection.dirty() { - return Ok(()); - } - - let validity = collection.data_validity(); - - match validity { - DataValidity::Valid => {}, - DataValidity::CascadeInvalid => self.clear_cascade_data(), - DataValidity::FullyInvalid => self.clear(), - } - - let mut result = Ok(()); - - collection.each(|stylesheet, rebuild_kind| { - result = self.add_stylesheet( - device, - quirks_mode, - stylesheet, - guard, - rebuild_kind, - /* precomputed_pseudo_element_decls = */ None, - ); - result.is_ok() - }); - - self.did_finish_rebuild(); - - result - } - - /// Returns the invalidation map. - pub fn invalidation_map(&self) -> &InvalidationMap { - &self.invalidation_map - } - - /// Returns whether the given ElementState bit is relied upon by a selector - /// of some rule. - #[inline] - pub fn has_state_dependency(&self, state: ElementState) -> bool { - self.state_dependencies.intersects(state) - } - - /// Returns whether the given ElementState bit is relied upon by a selector - /// of some rule in the selector list of :nth-child(... of <selector list>). - #[inline] - pub fn has_nth_of_state_dependency(&self, state: ElementState) -> bool { - self.nth_of_state_dependencies.intersects(state) - } - - /// Returns whether the given attribute might appear in an attribute - /// selector of some rule. - #[inline] - pub fn might_have_attribute_dependency(&self, local_name: &LocalName) -> bool { - self.attribute_dependencies.contains(local_name) - } - - /// Returns whether the given ID might appear in an ID selector in the - /// selector list of :nth-child(... of <selector list>). - #[inline] - pub fn might_have_nth_of_id_dependency(&self, id: &Atom) -> bool { - self.nth_of_mapped_ids.contains(id) - } - - /// Returns whether the given class might appear in a class selector in the - /// selector list of :nth-child(... of <selector list>). - #[inline] - pub fn might_have_nth_of_class_dependency(&self, class: &Atom) -> bool { - self.nth_of_class_dependencies.contains(class) - } - - /// Returns whether the given attribute might appear in an attribute - /// selector in the selector list of :nth-child(... of <selector list>). - #[inline] - pub fn might_have_nth_of_attribute_dependency(&self, local_name: &LocalName) -> bool { - self.nth_of_attribute_dependencies.contains(local_name) - } - - /// Returns the normal rule map for a given pseudo-element. - #[inline] - pub fn normal_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> { - self.normal_rules.rules(pseudo) - } - - /// Returns the host pseudo rule map for a given pseudo-element. - #[inline] - pub fn host_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> { - self.host_rules.as_ref().and_then(|d| d.rules(pseudo)) - } - - /// Whether there's any host rule that could match in this scope. - pub fn any_host_rules(&self) -> bool { - self.host_rules.is_some() - } - - /// Returns the slotted rule map for a given pseudo-element. - #[inline] - pub fn slotted_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&SelectorMap<Rule>> { - self.slotted_rules.as_ref().and_then(|d| d.rules(pseudo)) - } - - /// Whether there's any ::slotted rule that could match in this scope. - pub fn any_slotted_rule(&self) -> bool { - self.slotted_rules.is_some() - } - - /// Returns the parts rule map for a given pseudo-element. - #[inline] - pub fn part_rules(&self, pseudo: Option<&PseudoElement>) -> Option<&PartMap> { - self.part_rules.as_ref().and_then(|d| d.rules(pseudo)) - } - - /// Whether there's any ::part rule that could match in this scope. - pub fn any_part_rule(&self) -> bool { - self.part_rules.is_some() - } - - #[inline] - fn layer_order_for(&self, id: LayerId) -> LayerOrder { - self.layers[id.0 as usize].order - } - - pub(crate) fn container_condition_matches<E>( - &self, - mut id: ContainerConditionId, - stylist: &Stylist, - element: E, - context: &mut MatchingContext<E::Impl>, - ) -> bool - where - E: TElement, - { - loop { - let condition_ref = &self.container_conditions[id.0 as usize]; - let condition = match condition_ref.condition { - None => return true, - Some(ref c) => c, - }; - let matches = condition - .matches( - stylist.device(), - element, - context.extra_data.originating_element_style, - &mut context.extra_data.cascade_input_flags, - ) - .to_bool(/* unknown = */ false); - if !matches { - return false; - } - id = condition_ref.parent; - } - } - - fn did_finish_rebuild(&mut self) { - self.shrink_maps_if_needed(); - self.compute_layer_order(); - } - - fn shrink_maps_if_needed(&mut self) { - self.normal_rules.shrink_if_needed(); - if let Some(ref mut host_rules) = self.host_rules { - host_rules.shrink_if_needed(); - } - if let Some(ref mut slotted_rules) = self.slotted_rules { - slotted_rules.shrink_if_needed(); - } - self.invalidation_map.shrink_if_needed(); - self.attribute_dependencies.shrink_if_needed(); - self.nth_of_attribute_dependencies.shrink_if_needed(); - self.nth_of_class_dependencies.shrink_if_needed(); - self.nth_of_mapped_ids.shrink_if_needed(); - self.mapped_ids.shrink_if_needed(); - self.layer_id.shrink_if_needed(); - self.selectors_for_cache_revalidation.shrink_if_needed(); - } - - fn compute_layer_order(&mut self) { - debug_assert_ne!( - self.layers.len(), - 0, - "There should be at least the root layer!" - ); - if self.layers.len() == 1 { - return; // Nothing to do - } - let (first, remaining) = self.layers.split_at_mut(1); - let root = &mut first[0]; - let mut order = LayerOrder::first(); - compute_layer_order_for_subtree(root, remaining, &mut order); - - // NOTE(emilio): This is a bit trickier than it should to avoid having - // to clone() around layer indices. - fn compute_layer_order_for_subtree( - parent: &mut CascadeLayer, - remaining_layers: &mut [CascadeLayer], - order: &mut LayerOrder, - ) { - for child in parent.children.iter() { - debug_assert!( - parent.id < *child, - "Children are always registered after parents" - ); - let child_index = (child.0 - parent.id.0 - 1) as usize; - let (first, remaining) = remaining_layers.split_at_mut(child_index + 1); - let child = &mut first[child_index]; - compute_layer_order_for_subtree(child, remaining, order); - } - - if parent.id != LayerId::root() { - parent.order = *order; - order.inc(); - } - } - #[cfg(feature = "gecko")] - { - self.extra_data.sort_by_layer(&self.layers); - } - self.animations - .sort_with(&self.layers, compare_keyframes_in_same_layer); - } - - /// Collects all the applicable media query results into `results`. - /// - /// This duplicates part of the logic in `add_stylesheet`, which is - /// a bit unfortunate. - /// - /// FIXME(emilio): With a bit of smartness in - /// `media_feature_affected_matches`, we could convert - /// `EffectiveMediaQueryResults` into a vector without too much effort. - fn collect_applicable_media_query_results_into<S>( - device: &Device, - stylesheet: &S, - guard: &SharedRwLockReadGuard, - results: &mut Vec<MediaListKey>, - contents_list: &mut StyleSheetContentList, - ) where - S: StylesheetInDocument + 'static, - { - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - return; - } - - debug!(" + {:?}", stylesheet); - let contents = stylesheet.contents(); - results.push(contents.to_media_list_key()); - - // Safety: StyleSheetContents are reference-counted with Arc. - contents_list.push(StylesheetContentsPtr(unsafe { - Arc::from_raw_addrefed(contents) - })); - - for rule in stylesheet.effective_rules(device, guard) { - match *rule { - CssRule::Import(ref lock) => { - let import_rule = lock.read_with(guard); - debug!(" + {:?}", import_rule.stylesheet.media(guard)); - results.push(import_rule.to_media_list_key()); - }, - CssRule::Media(ref media_rule) => { - debug!(" + {:?}", media_rule.media_queries.read_with(guard)); - results.push(media_rule.to_media_list_key()); - }, - _ => {}, - } - } - } - - fn add_rule_list<'a, S>( - &mut self, - rules: std::slice::Iter<'a, CssRule>, - device: &'a Device, - quirks_mode: QuirksMode, - stylesheet: &S, - guard: &'a SharedRwLockReadGuard, - rebuild_kind: SheetRebuildKind, - containing_rule_state: &mut ContainingRuleState<'a>, - mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, - ) -> Result<(), AllocErr> - where - S: StylesheetInDocument + 'static, - { - for rule in rules { - // Handle leaf rules first, as those are by far the most common - // ones, and are always effective, so we can skip some checks. - let mut handled = true; - let mut selectors_for_nested_rules = None; - match *rule { - CssRule::Style(ref locked) => { - let style_rule = locked.read_with(guard); - self.num_declarations += style_rule.block.read_with(&guard).len(); - - let has_nested_rules = style_rule.rules.is_some(); - let ancestor_selectors = containing_rule_state.ancestor_selector_lists.last(); - if has_nested_rules { - selectors_for_nested_rules = Some(if ancestor_selectors.is_some() { - Cow::Owned(SelectorList(Default::default())) - } else { - Cow::Borrowed(&style_rule.selectors) - }); - } - - for selector in &style_rule.selectors.0 { - self.num_selectors += 1; - - let pseudo_element = selector.pseudo_element(); - if let Some(pseudo) = pseudo_element { - if pseudo.is_precomputed() { - debug_assert!(selector.is_universal()); - debug_assert!(ancestor_selectors.is_none()); - debug_assert!(!has_nested_rules); - debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); - debug_assert_eq!(containing_rule_state.layer_id, LayerId::root()); - - precomputed_pseudo_element_decls - .as_mut() - .expect("Expected precomputed declarations for the UA level") - .get_or_insert_with(pseudo, Vec::new) - .push(ApplicableDeclarationBlock::new( - StyleSource::from_rule(locked.clone()), - self.rules_source_order, - CascadeLevel::UANormal, - selector.specificity(), - LayerOrder::root(), - )); - continue; - } - if pseudo.is_unknown_webkit_pseudo_element() { - continue; - } - } - - let selector = match ancestor_selectors { - Some(s) => selector.replace_parent_selector(&s.0), - None => selector.clone(), - }; - - let hashes = AncestorHashes::new(&selector, quirks_mode); - - let rule = Rule::new( - selector, - hashes, - locked.clone(), - self.rules_source_order, - containing_rule_state.layer_id, - containing_rule_state.container_condition_id, - ); - - if let Some(Cow::Owned(ref mut nested_selectors)) = - selectors_for_nested_rules - { - nested_selectors.0.push(rule.selector.clone()) - } - - if rebuild_kind.should_rebuild_invalidation() { - self.invalidation_map - .note_selector(&rule.selector, quirks_mode)?; - let mut needs_revalidation = false; - let mut visitor = StylistSelectorVisitor { - needs_revalidation: &mut needs_revalidation, - passed_rightmost_selector: false, - in_selector_list_of: SelectorListKind::default(), - mapped_ids: &mut self.mapped_ids, - nth_of_mapped_ids: &mut self.nth_of_mapped_ids, - attribute_dependencies: &mut self.attribute_dependencies, - nth_of_class_dependencies: &mut self.nth_of_class_dependencies, - nth_of_attribute_dependencies: &mut self - .nth_of_attribute_dependencies, - state_dependencies: &mut self.state_dependencies, - nth_of_state_dependencies: &mut self.nth_of_state_dependencies, - document_state_dependencies: &mut self.document_state_dependencies, - }; - rule.selector.visit(&mut visitor); - - if needs_revalidation { - self.selectors_for_cache_revalidation.insert( - RevalidationSelectorAndHashes::new( - rule.selector.clone(), - rule.hashes.clone(), - ), - quirks_mode, - )?; - } - } - - // Part is special, since given it doesn't have any - // selectors inside, it's not worth using a whole - // SelectorMap for it. - if let Some(parts) = rule.selector.parts() { - // ::part() has all semantics, so we just need to - // put any of them in the selector map. - // - // We choose the last one quite arbitrarily, - // expecting it's slightly more likely to be more - // specific. - let map = self - .part_rules - .get_or_insert_with(|| Box::new(Default::default())) - .for_insertion(pseudo_element); - map.try_reserve(1)?; - let vec = map.entry(parts.last().unwrap().clone().0).or_default(); - vec.try_reserve(1)?; - vec.push(rule); - } else { - // NOTE(emilio): It's fine to look at :host and then at - // ::slotted(..), since :host::slotted(..) could never - // possibly match, as <slot> is not a valid shadow host. - let rules = if rule - .selector - .is_featureless_host_selector_or_pseudo_element() - { - self.host_rules - .get_or_insert_with(|| Box::new(Default::default())) - } else if rule.selector.is_slotted() { - self.slotted_rules - .get_or_insert_with(|| Box::new(Default::default())) - } else { - &mut self.normal_rules - } - .for_insertion(pseudo_element); - rules.insert(rule, quirks_mode)?; - } - } - self.rules_source_order += 1; - handled = !has_nested_rules; - }, - CssRule::Keyframes(ref keyframes_rule) => { - debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - let keyframes_rule = keyframes_rule.read_with(guard); - let name = keyframes_rule.name.as_atom().clone(); - let animation = KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - guard, - ); - self.animations.try_insert_with( - name, - animation, - containing_rule_state.layer_id, - compare_keyframes_in_same_layer, - )?; - }, - #[cfg(feature = "gecko")] - CssRule::FontFace(ref rule) => { - // NOTE(emilio): We don't care about container_condition_id - // because: - // - // Global, name-defining at-rules such as @keyframes or - // @font-face or @layer that are defined inside container - // queries are not constrained by the container query - // conditions. - // - // https://drafts.csswg.org/css-contain-3/#container-rule - // (Same elsewhere) - self.extra_data - .add_font_face(rule, containing_rule_state.layer_id); - }, - #[cfg(feature = "gecko")] - CssRule::FontFeatureValues(ref rule) => { - self.extra_data - .add_font_feature_values(rule, containing_rule_state.layer_id); - }, - #[cfg(feature = "gecko")] - CssRule::FontPaletteValues(ref rule) => { - self.extra_data - .add_font_palette_values(rule, containing_rule_state.layer_id); - }, - #[cfg(feature = "gecko")] - CssRule::CounterStyle(ref rule) => { - self.extra_data.add_counter_style( - guard, - rule, - containing_rule_state.layer_id, - )?; - }, - #[cfg(feature = "gecko")] - CssRule::Page(ref rule) => { - self.extra_data - .add_page(guard, rule, containing_rule_state.layer_id)?; - }, - _ => { - handled = false; - }, - } - - if handled { - // Assert that there are no children, and that the rule is - // effective. - if cfg!(debug_assertions) { - let mut effective = false; - let children = EffectiveRulesIterator::children( - rule, - device, - quirks_mode, - guard, - &mut effective, - ); - debug_assert!(children.is_none()); - debug_assert!(effective); - } - continue; - } - - let mut effective = false; - let children = - EffectiveRulesIterator::children(rule, device, quirks_mode, guard, &mut effective); - - if !effective { - continue; - } - - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerId { - // TODO: Measure what's more common / expensive, if - // layer.clone() or the double hash lookup in the insert - // case. - if let Some(id) = data.layer_id.get(layer) { - return *id; - } - let id = LayerId(data.layers.len() as u16); - - let parent_layer_id = if layer.layer_names().len() > 1 { - let mut parent = layer.clone(); - parent.0.pop(); - - *data - .layer_id - .get_mut(&parent) - .expect("Parent layers should be registered before child layers") - } else { - LayerId::root() - }; - - data.layers[parent_layer_id.0 as usize].children.push(id); - data.layers.push(CascadeLayer { - id, - // NOTE(emilio): Order is evaluated after rebuild in - // compute_layer_order. - order: LayerOrder::first(), - children: vec![], - }); - - data.layer_id.insert(layer.clone(), id); - - id - } - - fn maybe_register_layers( - data: &mut CascadeData, - name: Option<&LayerName>, - containing_rule_state: &mut ContainingRuleState, - ) { - let anon_name; - let name = match name { - Some(name) => name, - None => { - anon_name = LayerName::new_anonymous(); - &anon_name - }, - }; - for name in name.layer_names() { - containing_rule_state.layer_name.0.push(name.clone()); - containing_rule_state.layer_id = - maybe_register_layer(data, &containing_rule_state.layer_name); - } - debug_assert_ne!(containing_rule_state.layer_id, LayerId::root()); - } - - let saved_containing_rule_state = containing_rule_state.save(); - match *rule { - CssRule::Import(ref lock) => { - let import_rule = lock.read_with(guard); - if rebuild_kind.should_rebuild_invalidation() { - self.effective_media_query_results - .saw_effective(import_rule); - } - match import_rule.layer { - ImportLayer::Named(ref name) => { - maybe_register_layers(self, Some(name), containing_rule_state) - }, - ImportLayer::Anonymous => { - maybe_register_layers(self, None, containing_rule_state) - }, - ImportLayer::None => {}, - } - }, - CssRule::Media(ref media_rule) => { - if rebuild_kind.should_rebuild_invalidation() { - self.effective_media_query_results - .saw_effective(&**media_rule); - } - }, - CssRule::LayerBlock(ref rule) => { - maybe_register_layers(self, rule.name.as_ref(), containing_rule_state); - }, - CssRule::LayerStatement(ref rule) => { - for name in &*rule.names { - maybe_register_layers(self, Some(name), containing_rule_state); - // Register each layer individually. - containing_rule_state.restore(&saved_containing_rule_state); - } - }, - CssRule::Style(..) => { - if let Some(s) = selectors_for_nested_rules { - containing_rule_state.ancestor_selector_lists.push(s); - } - }, - CssRule::Container(ref rule) => { - let id = ContainerConditionId(self.container_conditions.len() as u16); - self.container_conditions.push(ContainerConditionReference { - parent: containing_rule_state.container_condition_id, - condition: Some(rule.condition.clone()), - }); - containing_rule_state.container_condition_id = id; - }, - // We don't care about any other rule. - _ => {}, - } - - if let Some(children) = children { - self.add_rule_list( - children, - device, - quirks_mode, - stylesheet, - guard, - rebuild_kind, - containing_rule_state, - precomputed_pseudo_element_decls.as_deref_mut(), - )?; - } - - containing_rule_state.restore(&saved_containing_rule_state); - } - - Ok(()) - } - - // Returns Err(..) to signify OOM - fn add_stylesheet<S>( - &mut self, - device: &Device, - quirks_mode: QuirksMode, - stylesheet: &S, - guard: &SharedRwLockReadGuard, - rebuild_kind: SheetRebuildKind, - mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, - ) -> Result<(), AllocErr> - where - S: StylesheetInDocument + 'static, - { - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - return Ok(()); - } - - let contents = stylesheet.contents(); - - if rebuild_kind.should_rebuild_invalidation() { - self.effective_media_query_results.saw_effective(contents); - } - - let mut state = ContainingRuleState::default(); - self.add_rule_list( - contents.rules(guard).iter(), - device, - quirks_mode, - stylesheet, - guard, - rebuild_kind, - &mut state, - precomputed_pseudo_element_decls.as_deref_mut(), - )?; - - Ok(()) - } - - /// Returns whether all the media-feature affected values matched before and - /// match now in the given stylesheet. - pub fn media_feature_affected_matches<S>( - &self, - stylesheet: &S, - guard: &SharedRwLockReadGuard, - device: &Device, - quirks_mode: QuirksMode, - ) -> bool - where - S: StylesheetInDocument + 'static, - { - use crate::invalidation::media_queries::PotentiallyEffectiveMediaRules; - - let effective_now = stylesheet.is_effective_for_device(device, guard); - - let effective_then = self - .effective_media_query_results - .was_effective(stylesheet.contents()); - - if effective_now != effective_then { - debug!( - " > Stylesheet {:?} changed -> {}, {}", - stylesheet.media(guard), - effective_then, - effective_now - ); - return false; - } - - if !effective_now { - return true; - } - - let mut iter = stylesheet.iter_rules::<PotentiallyEffectiveMediaRules>(device, guard); - - while let Some(rule) = iter.next() { - match *rule { - CssRule::Style(..) | - CssRule::Namespace(..) | - CssRule::FontFace(..) | - CssRule::Container(..) | - CssRule::CounterStyle(..) | - CssRule::Supports(..) | - CssRule::Keyframes(..) | - CssRule::Page(..) | - CssRule::Property(..) | - CssRule::Document(..) | - CssRule::LayerBlock(..) | - CssRule::LayerStatement(..) | - CssRule::FontPaletteValues(..) | - CssRule::FontFeatureValues(..) => { - // Not affected by device changes. - continue; - }, - CssRule::Import(ref lock) => { - let import_rule = lock.read_with(guard); - let effective_now = match import_rule.stylesheet.media(guard) { - Some(m) => m.evaluate(device, quirks_mode), - None => true, - }; - let effective_then = self - .effective_media_query_results - .was_effective(import_rule); - if effective_now != effective_then { - debug!( - " > @import rule {:?} changed {} -> {}", - import_rule.stylesheet.media(guard), - effective_then, - effective_now - ); - return false; - } - - if !effective_now { - iter.skip_children(); - } - }, - CssRule::Media(ref media_rule) => { - let mq = media_rule.media_queries.read_with(guard); - let effective_now = mq.evaluate(device, quirks_mode); - let effective_then = self - .effective_media_query_results - .was_effective(&**media_rule); - - if effective_now != effective_then { - debug!( - " > @media rule {:?} changed {} -> {}", - mq, effective_then, effective_now - ); - return false; - } - - if !effective_now { - iter.skip_children(); - } - }, - } - } - - true - } - - /// Clears the cascade data, but not the invalidation data. - fn clear_cascade_data(&mut self) { - self.normal_rules.clear(); - if let Some(ref mut slotted_rules) = self.slotted_rules { - slotted_rules.clear(); - } - if let Some(ref mut part_rules) = self.part_rules { - part_rules.clear(); - } - if let Some(ref mut host_rules) = self.host_rules { - host_rules.clear(); - } - self.animations.clear(); - self.layer_id.clear(); - self.layers.clear(); - self.layers.push(CascadeLayer::root()); - self.container_conditions.clear(); - self.container_conditions - .push(ContainerConditionReference::none()); - #[cfg(feature = "gecko")] - { - self.extra_data.clear(); - } - self.rules_source_order = 0; - self.num_selectors = 0; - self.num_declarations = 0; - } - - fn clear(&mut self) { - self.clear_cascade_data(); - self.invalidation_map.clear(); - self.attribute_dependencies.clear(); - self.nth_of_attribute_dependencies.clear(); - self.nth_of_class_dependencies.clear(); - self.state_dependencies = ElementState::empty(); - self.nth_of_state_dependencies = ElementState::empty(); - self.document_state_dependencies = DocumentState::empty(); - self.mapped_ids.clear(); - self.nth_of_mapped_ids.clear(); - self.selectors_for_cache_revalidation.clear(); - self.effective_media_query_results.clear(); - } -} - -impl CascadeDataCacheEntry for CascadeData { - fn cascade_data(&self) -> &CascadeData { - self - } - - fn rebuild<S>( - device: &Device, - quirks_mode: QuirksMode, - collection: SheetCollectionFlusher<S>, - guard: &SharedRwLockReadGuard, - old: &Self, - ) -> Result<Arc<Self>, AllocErr> - where - S: StylesheetInDocument + PartialEq + 'static, - { - debug_assert!(collection.dirty(), "We surely need to do something?"); - // If we're doing a full rebuild anyways, don't bother cloning the data. - let mut updatable_entry = match collection.data_validity() { - DataValidity::Valid | DataValidity::CascadeInvalid => old.clone(), - DataValidity::FullyInvalid => Self::new(), - }; - updatable_entry.rebuild(device, quirks_mode, collection, guard)?; - Ok(Arc::new(updatable_entry)) - } - - #[cfg(feature = "gecko")] - fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.normal_rules.add_size_of(ops, sizes); - if let Some(ref slotted_rules) = self.slotted_rules { - slotted_rules.add_size_of(ops, sizes); - } - if let Some(ref part_rules) = self.part_rules { - part_rules.add_size_of(ops, sizes); - } - if let Some(ref host_rules) = self.host_rules { - host_rules.add_size_of(ops, sizes); - } - sizes.mInvalidationMap += self.invalidation_map.size_of(ops); - sizes.mRevalidationSelectors += self.selectors_for_cache_revalidation.size_of(ops); - sizes.mOther += self.animations.size_of(ops); - sizes.mOther += self.effective_media_query_results.size_of(ops); - sizes.mOther += self.extra_data.size_of(ops); - } -} - -impl Default for CascadeData { - fn default() -> Self { - CascadeData::new() - } -} - -/// A rule, that wraps a style rule, but represents a single selector of the -/// rule. -#[derive(Clone, Debug, MallocSizeOf)] -pub struct Rule { - /// The selector this struct represents. We store this and the - /// any_{important,normal} booleans inline in the Rule to avoid - /// pointer-chasing when gathering applicable declarations, which - /// can ruin performance when there are a lot of rules. - #[ignore_malloc_size_of = "CssRules have primary refs, we measure there"] - pub selector: Selector<SelectorImpl>, - - /// The ancestor hashes associated with the selector. - pub hashes: AncestorHashes, - - /// The source order this style rule appears in. Note that we only use - /// three bytes to store this value in ApplicableDeclarationsBlock, so - /// we could repurpose that storage here if we needed to. - pub source_order: u32, - - /// The current layer id of this style rule. - pub layer_id: LayerId, - - /// The current @container rule id. - pub container_condition_id: ContainerConditionId, - - /// The actual style rule. - #[cfg_attr( - feature = "gecko", - ignore_malloc_size_of = "Secondary ref. Primary ref is in StyleRule under Stylesheet." - )] - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - pub style_rule: Arc<Locked<StyleRule>>, -} - -impl SelectorMapEntry for Rule { - fn selector(&self) -> SelectorIter<SelectorImpl> { - self.selector.iter() - } -} - -impl Rule { - /// Returns the specificity of the rule. - pub fn specificity(&self) -> u32 { - self.selector.specificity() - } - - /// Turns this rule into an `ApplicableDeclarationBlock` for the given - /// cascade level. - pub fn to_applicable_declaration_block( - &self, - level: CascadeLevel, - cascade_data: &CascadeData, - ) -> ApplicableDeclarationBlock { - let source = StyleSource::from_rule(self.style_rule.clone()); - ApplicableDeclarationBlock::new( - source, - self.source_order, - level, - self.specificity(), - cascade_data.layer_order_for(self.layer_id), - ) - } - - /// Creates a new Rule. - pub fn new( - selector: Selector<SelectorImpl>, - hashes: AncestorHashes, - style_rule: Arc<Locked<StyleRule>>, - source_order: u32, - layer_id: LayerId, - container_condition_id: ContainerConditionId, - ) -> Self { - Rule { - selector, - hashes, - style_rule, - source_order, - layer_id, - container_condition_id, - } - } -} - -// The size of this is critical to performance on the bloom-basic -// microbenchmark. -// When iterating over a large Rule array, we want to be able to fast-reject -// selectors (with the inline hashes) with as few cache misses as possible. -size_of_test!(Rule, 40); - -/// A function to be able to test the revalidation stuff. -pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool { - let mut needs_revalidation = false; - let mut mapped_ids = Default::default(); - let mut nth_of_mapped_ids = Default::default(); - let mut attribute_dependencies = Default::default(); - let mut nth_of_class_dependencies = Default::default(); - let mut nth_of_attribute_dependencies = Default::default(); - let mut state_dependencies = ElementState::empty(); - let mut nth_of_state_dependencies = ElementState::empty(); - let mut document_state_dependencies = DocumentState::empty(); - let mut visitor = StylistSelectorVisitor { - passed_rightmost_selector: false, - needs_revalidation: &mut needs_revalidation, - in_selector_list_of: SelectorListKind::default(), - mapped_ids: &mut mapped_ids, - nth_of_mapped_ids: &mut nth_of_mapped_ids, - attribute_dependencies: &mut attribute_dependencies, - nth_of_class_dependencies: &mut nth_of_class_dependencies, - nth_of_attribute_dependencies: &mut nth_of_attribute_dependencies, - state_dependencies: &mut state_dependencies, - nth_of_state_dependencies: &mut nth_of_state_dependencies, - document_state_dependencies: &mut document_state_dependencies, - }; - s.visit(&mut visitor); - needs_revalidation -} diff --git a/components/style/thread_state.rs b/components/style/thread_state.rs deleted file mode 100644 index 2a39feef487..00000000000 --- a/components/style/thread_state.rs +++ /dev/null @@ -1,97 +0,0 @@ -/* 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/. */ - -//! Supports dynamic assertions in about what sort of thread is running and -//! what state it's in. - -#![deny(missing_docs)] - -use std::cell::RefCell; - -bitflags! { - /// A thread state flag, used for multiple assertions. - pub struct ThreadState: u32 { - /// Whether we're in a script thread. - const SCRIPT = 0x01; - /// Whether we're in a layout thread. - const LAYOUT = 0x02; - - /// Whether we're in a script worker thread (actual web workers), or in - /// a layout worker thread. - const IN_WORKER = 0x0100; - - /// Whether the current thread is going through a GC. - const IN_GC = 0x0200; - } -} - -macro_rules! thread_types ( ( $( $fun:ident = $flag:path ; )* ) => ( - impl ThreadState { - /// Whether the current thread is a worker thread. - pub fn is_worker(self) -> bool { - self.contains(ThreadState::IN_WORKER) - } - - $( - #[allow(missing_docs)] - pub fn $fun(self) -> bool { - self.contains($flag) - } - )* - } -)); - -thread_types! { - is_script = ThreadState::SCRIPT; - is_layout = ThreadState::LAYOUT; -} - -thread_local!(static STATE: RefCell<Option<ThreadState>> = RefCell::new(None)); - -/// Initializes the current thread state. -pub fn initialize(x: ThreadState) { - STATE.with(|ref k| { - if let Some(ref s) = *k.borrow() { - if x != *s { - panic!("Thread state already initialized as {:?}", s); - } - } - *k.borrow_mut() = Some(x); - }); -} - -/// Initializes the current thread as a layout worker thread. -pub fn initialize_layout_worker_thread() { - initialize(ThreadState::LAYOUT | ThreadState::IN_WORKER); -} - -/// Gets the current thread state. -pub fn get() -> ThreadState { - let state = STATE.with(|ref k| { - match *k.borrow() { - None => ThreadState::empty(), // Unknown thread. - Some(s) => s, - } - }); - - state -} - -/// Enters into a given temporary state. Panics if re-entring. -pub fn enter(x: ThreadState) { - let state = get(); - debug_assert!(!state.intersects(x)); - STATE.with(|ref k| { - *k.borrow_mut() = Some(state | x); - }) -} - -/// Exits a given temporary state. -pub fn exit(x: ThreadState) { - let state = get(); - debug_assert!(state.contains(x)); - STATE.with(|ref k| { - *k.borrow_mut() = Some(state & !x); - }) -} diff --git a/components/style/traversal.rs b/components/style/traversal.rs deleted file mode 100644 index 19cc45723cf..00000000000 --- a/components/style/traversal.rs +++ /dev/null @@ -1,841 +0,0 @@ -/* 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/. */ - -//! Traversing the DOM tree; the bloom filter. - -use crate::context::{ElementCascadeInputs, SharedStyleContext, StyleContext}; -use crate::data::{ElementData, ElementStyles, RestyleKind}; -use crate::dom::{NodeInfo, OpaqueNode, TElement, TNode}; -use crate::invalidation::element::restyle_hints::RestyleHint; -use crate::matching::{ChildRestyleRequirement, MatchMethods}; -use crate::selector_parser::PseudoElement; -use crate::sharing::StyleSharingTarget; -use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; -use crate::stylist::RuleInclusion; -use crate::traversal_flags::TraversalFlags; -use selectors::NthIndexCache; -use smallvec::SmallVec; -use std::collections::HashMap; - -/// A cache from element reference to known-valid computed style. -pub type UndisplayedStyleCache = - HashMap<selectors::OpaqueElement, servo_arc::Arc<crate::properties::ComputedValues>>; - -/// A per-traversal-level chunk of data. This is sent down by the traversal, and -/// currently only holds the dom depth for the bloom filter. -/// -/// NB: Keep this as small as possible, please! -#[derive(Clone, Copy, Debug)] -pub struct PerLevelTraversalData { - /// The current dom depth. - /// - /// This is kept with cooperation from the traversal code and the bloom - /// filter. - pub current_dom_depth: usize, -} - -/// We use this structure, rather than just returning a boolean from pre_traverse, -/// to enfore that callers process root invalidations before starting the traversal. -pub struct PreTraverseToken<E: TElement>(Option<E>); -impl<E: TElement> PreTraverseToken<E> { - /// Whether we should traverse children. - pub fn should_traverse(&self) -> bool { - self.0.is_some() - } - - /// Returns the traversal root for the current traversal. - pub(crate) fn traversal_root(self) -> Option<E> { - self.0 - } -} - -/// A global variable holding the state of -/// `is_servo_nonincremental_layout()`. -/// See [#22854](https://github.com/servo/servo/issues/22854). -#[cfg(feature = "servo")] -pub static IS_SERVO_NONINCREMENTAL_LAYOUT: std::sync::atomic::AtomicBool = - std::sync::atomic::AtomicBool::new(false); - -#[cfg(feature = "servo")] -#[inline] -fn is_servo_nonincremental_layout() -> bool { - use std::sync::atomic::Ordering; - - IS_SERVO_NONINCREMENTAL_LAYOUT.load(Ordering::Relaxed) -} - -#[cfg(not(feature = "servo"))] -#[inline] -fn is_servo_nonincremental_layout() -> bool { - false -} - -/// A DOM Traversal trait, that is used to generically implement styling for -/// Gecko and Servo. -pub trait DomTraversal<E: TElement>: Sync { - /// Process `node` on the way down, before its children have been processed. - /// - /// The callback is invoked for each child node that should be processed by - /// the traversal. - fn process_preorder<F>( - &self, - data: &PerLevelTraversalData, - context: &mut StyleContext<E>, - node: E::ConcreteNode, - note_child: F, - ) where - F: FnMut(E::ConcreteNode); - - /// Process `node` on the way up, after its children have been processed. - /// - /// This is only executed if `needs_postorder_traversal` returns true. - fn process_postorder(&self, contect: &mut StyleContext<E>, node: E::ConcreteNode); - - /// Boolean that specifies whether a bottom up traversal should be - /// performed. - /// - /// If it's false, then process_postorder has no effect at all. - fn needs_postorder_traversal() -> bool { - true - } - - /// Handles the postorder step of the traversal, if it exists, by bubbling - /// up the parent chain. - /// - /// If we are the last child that finished processing, recursively process - /// our parent. Else, stop. Also, stop at the root. - /// - /// Thus, if we start with all the leaves of a tree, we end up traversing - /// the whole tree bottom-up because each parent will be processed exactly - /// once (by the last child that finishes processing). - /// - /// The only communication between siblings is that they both - /// fetch-and-subtract the parent's children count. This makes it safe to - /// call durign the parallel traversal. - fn handle_postorder_traversal( - &self, - context: &mut StyleContext<E>, - root: OpaqueNode, - mut node: E::ConcreteNode, - children_to_process: isize, - ) { - // If the postorder step is a no-op, don't bother. - if !Self::needs_postorder_traversal() { - return; - } - - if children_to_process == 0 { - // We are a leaf. Walk up the chain. - loop { - self.process_postorder(context, node); - if node.opaque() == root { - break; - } - let parent = node.traversal_parent().unwrap(); - let remaining = parent.did_process_child(); - if remaining != 0 { - // The parent has other unprocessed descendants. We only - // perform postorder processing after the last descendant - // has been processed. - break; - } - - node = parent.as_node(); - } - } else { - // Otherwise record the number of children to process when the time - // comes. - node.as_element() - .unwrap() - .store_children_to_process(children_to_process); - } - } - - /// Style invalidations happen when traversing from a parent to its children. - /// However, this mechanism can't handle style invalidations on the root. As - /// such, we have a pre-traversal step to handle that part and determine whether - /// a full traversal is needed. - fn pre_traverse(root: E, shared_context: &SharedStyleContext) -> PreTraverseToken<E> { - use crate::invalidation::element::state_and_attributes::propagate_dirty_bit_up_to; - - let traversal_flags = shared_context.traversal_flags; - - let mut data = root.mutate_data(); - let mut data = data.as_mut().map(|d| &mut **d); - - if let Some(ref mut data) = data { - if !traversal_flags.for_animation_only() { - // Invalidate our style, and that of our siblings and - // descendants as needed. - let invalidation_result = data.invalidate_style_if_needed( - root, - shared_context, - None, - &mut NthIndexCache::default(), - ); - - if invalidation_result.has_invalidated_siblings() { - let actual_root = root.as_node().parent_element_or_host().expect( - "How in the world can you invalidate \ - siblings without a parent?", - ); - propagate_dirty_bit_up_to(actual_root, root); - return PreTraverseToken(Some(actual_root)); - } - } - } - - let should_traverse = - Self::element_needs_traversal(root, traversal_flags, data.as_mut().map(|d| &**d)); - - // If we're not going to traverse at all, we may need to clear some state - // off the root (which would normally be done at the end of recalc_style_at). - if !should_traverse && data.is_some() { - clear_state_after_traversing(root, data.unwrap(), traversal_flags); - } - - PreTraverseToken(if should_traverse { Some(root) } else { None }) - } - - /// Returns true if traversal should visit a text node. The style system - /// never processes text nodes, but Servo overrides this to visit them for - /// flow construction when necessary. - fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool { - debug_assert!(node.is_text_node()); - false - } - - /// Returns true if traversal is needed for the given element and subtree. - fn element_needs_traversal( - el: E, - traversal_flags: TraversalFlags, - data: Option<&ElementData>, - ) -> bool { - debug!( - "element_needs_traversal({:?}, {:?}, {:?})", - el, traversal_flags, data - ); - - // In case of animation-only traversal we need to traverse the element if the element has - // animation only dirty descendants bit, animation-only restyle hint. - if traversal_flags.for_animation_only() { - return data.map_or(false, |d| d.has_styles()) && - (el.has_animation_only_dirty_descendants() || - data.as_ref() - .unwrap() - .hint - .has_animation_hint_or_recascade()); - } - - // Non-incremental layout visits every node. - if is_servo_nonincremental_layout() { - return true; - } - - // Unwrap the data. - let data = match data { - Some(d) if d.has_styles() => d, - _ => return true, - }; - - // If the dirty descendants bit is set, we need to traverse no matter - // what. Skip examining the ElementData. - if el.has_dirty_descendants() { - return true; - } - - // If we have a restyle hint or need to recascade, we need to visit the - // element. - // - // Note that this is different than checking has_current_styles_for_traversal(), - // since that can return true even if we have a restyle hint indicating - // that the element's descendants (but not necessarily the element) need - // restyling. - if !data.hint.is_empty() { - return true; - } - - // Servo uses the post-order traversal for flow construction, so we need - // to traverse any element with damage so that we can perform fixup / - // reconstruction on our way back up the tree. - if cfg!(feature = "servo") && !data.damage.is_empty() { - return true; - } - - trace!("{:?} doesn't need traversal", el); - false - } - - /// Return the shared style context common to all worker threads. - fn shared_context(&self) -> &SharedStyleContext; -} - -/// Manually resolve style by sequentially walking up the parent chain to the -/// first styled Element, ignoring pending restyles. The resolved style is made -/// available via a callback, and can be dropped by the time this function -/// returns in the display:none subtree case. -pub fn resolve_style<E>( - context: &mut StyleContext<E>, - element: E, - rule_inclusion: RuleInclusion, - pseudo: Option<&PseudoElement>, - mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>, -) -> ElementStyles -where - E: TElement, -{ - debug_assert!( - rule_inclusion == RuleInclusion::DefaultOnly || - pseudo.map_or(false, |p| p.is_before_or_after()) || - element.borrow_data().map_or(true, |d| !d.has_styles()), - "Why are we here?" - ); - debug_assert!( - rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(), - "can't use the cache for default styles only" - ); - - let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new(); - - // Clear the bloom filter, just in case the caller is reusing TLS. - context.thread_local.bloom_filter.clear(); - - let mut style = None; - let mut ancestor = element.traversal_parent(); - while let Some(current) = ancestor { - if rule_inclusion == RuleInclusion::All { - if let Some(data) = current.borrow_data() { - if let Some(ancestor_style) = data.styles.get_primary() { - style = Some(ancestor_style.clone()); - break; - } - } - } - if let Some(ref mut cache) = undisplayed_style_cache { - if let Some(s) = cache.get(¤t.opaque()) { - style = Some(s.clone()); - break; - } - } - ancestors_requiring_style_resolution.push(current); - ancestor = current.traversal_parent(); - } - - if let Some(ancestor) = ancestor { - context.thread_local.bloom_filter.rebuild(ancestor); - context.thread_local.bloom_filter.push(ancestor); - } - - let mut layout_parent_style = style.clone(); - while let Some(style) = layout_parent_style.take() { - if !style.is_display_contents() { - layout_parent_style = Some(style); - break; - } - - ancestor = ancestor.unwrap().traversal_parent(); - layout_parent_style = - ancestor.and_then(|a| a.borrow_data().map(|data| data.styles.primary().clone())); - } - - for ancestor in ancestors_requiring_style_resolution.iter().rev() { - context.thread_local.bloom_filter.assert_complete(*ancestor); - - // Actually `PseudoElementResolution` doesn't really matter here. - // (but it does matter below!). - let primary_style = StyleResolverForElement::new( - *ancestor, - context, - rule_inclusion, - PseudoElementResolution::IfApplicable, - ) - .resolve_primary_style(style.as_deref(), layout_parent_style.as_deref()); - - let is_display_contents = primary_style.style().is_display_contents(); - - style = Some(primary_style.style.0); - if !is_display_contents { - layout_parent_style = style.clone(); - } - - if let Some(ref mut cache) = undisplayed_style_cache { - cache.insert(ancestor.opaque(), style.clone().unwrap()); - } - context.thread_local.bloom_filter.push(*ancestor); - } - - context.thread_local.bloom_filter.assert_complete(element); - let styles: ElementStyles = StyleResolverForElement::new( - element, - context, - rule_inclusion, - PseudoElementResolution::Force, - ) - .resolve_style(style.as_deref(), layout_parent_style.as_deref()) - .into(); - - if let Some(ref mut cache) = undisplayed_style_cache { - cache.insert(element.opaque(), styles.primary().clone()); - } - - styles -} - -/// Calculates the style for a single node. -#[inline] -#[allow(unsafe_code)] -pub fn recalc_style_at<E, D, F>( - _traversal: &D, - traversal_data: &PerLevelTraversalData, - context: &mut StyleContext<E>, - element: E, - data: &mut ElementData, - note_child: F, -) where - E: TElement, - D: DomTraversal<E>, - F: FnMut(E::ConcreteNode), -{ - use std::cmp; - - let flags = context.shared.traversal_flags; - let is_initial_style = !data.has_styles(); - - context.thread_local.statistics.elements_traversed += 1; - debug_assert!( - flags.intersects(TraversalFlags::AnimationOnly) || - !element.has_snapshot() || - element.handled_snapshot(), - "Should've handled snapshots here already" - ); - - let restyle_kind = data.restyle_kind(&context.shared); - debug!( - "recalc_style_at: {:?} (restyle_kind={:?}, dirty_descendants={:?}, data={:?})", - element, - restyle_kind, - element.has_dirty_descendants(), - data - ); - - let mut child_restyle_requirement = ChildRestyleRequirement::CanSkipCascade; - - // Compute style for this element if necessary. - if let Some(restyle_kind) = restyle_kind { - child_restyle_requirement = - compute_style(traversal_data, context, element, data, restyle_kind); - - if !element.matches_user_and_content_rules() { - // We must always cascade native anonymous subtrees, since they - // may have pseudo-elements underneath that would inherit from the - // closest non-NAC ancestor instead of us. - child_restyle_requirement = cmp::max( - child_restyle_requirement, - ChildRestyleRequirement::MustCascadeChildren, - ); - } - - // If we're restyling this element to display:none, throw away all style - // data in the subtree, notify the caller to early-return. - if data.styles.is_display_none() { - debug!( - "{:?} style is display:none - clearing data from descendants.", - element - ); - unsafe { - clear_descendant_data(element); - } - } - - // Inform any paint worklets of changed style, to speculatively - // evaluate the worklet code. In the case that the size hasn't changed, - // this will result in increased concurrency between script and layout. - notify_paint_worklet(context, data); - } else { - debug_assert!(data.has_styles()); - data.set_traversed_without_styling(); - } - - // Now that matching and cascading is done, clear the bits corresponding to - // those operations and compute the propagated restyle hint (unless we're - // not processing invalidations, in which case don't need to propagate it - // and must avoid clearing it). - debug_assert!( - flags.for_animation_only() || !data.hint.has_animation_hint(), - "animation restyle hint should be handled during \ - animation-only restyles" - ); - let mut propagated_hint = data.hint.propagate(&flags); - trace!( - "propagated_hint={:?}, restyle_requirement={:?}, \ - is_display_none={:?}, implementing_pseudo={:?}", - propagated_hint, - child_restyle_requirement, - data.styles.is_display_none(), - element.implemented_pseudo_element() - ); - - // Integrate the child cascade requirement into the propagated hint. - match child_restyle_requirement { - ChildRestyleRequirement::CanSkipCascade => {}, - ChildRestyleRequirement::MustCascadeDescendants => { - propagated_hint |= RestyleHint::RECASCADE_SELF | RestyleHint::RECASCADE_DESCENDANTS; - }, - ChildRestyleRequirement::MustCascadeChildrenIfInheritResetStyle => { - propagated_hint |= RestyleHint::RECASCADE_SELF_IF_INHERIT_RESET_STYLE; - }, - ChildRestyleRequirement::MustCascadeChildren => { - propagated_hint |= RestyleHint::RECASCADE_SELF; - }, - ChildRestyleRequirement::MustMatchDescendants => { - propagated_hint |= RestyleHint::restyle_subtree(); - }, - } - - let has_dirty_descendants_for_this_restyle = if flags.for_animation_only() { - element.has_animation_only_dirty_descendants() - } else { - element.has_dirty_descendants() - }; - - // Before examining each child individually, try to prove that our children - // don't need style processing. They need processing if any of the following - // conditions hold: - // - // * We have the dirty descendants bit. - // * We're propagating a restyle hint. - // * This is a servo non-incremental traversal. - // - // We only do this if we're not a display: none root, since in that case - // it's useless to style children. - let mut traverse_children = has_dirty_descendants_for_this_restyle || - !propagated_hint.is_empty() || - is_servo_nonincremental_layout(); - - traverse_children = traverse_children && !data.styles.is_display_none(); - - // Examine our children, and enqueue the appropriate ones for traversal. - if traverse_children { - note_children::<E, D, F>( - context, - element, - data, - propagated_hint, - is_initial_style, - note_child, - ); - } - - // FIXME(bholley): Make these assertions pass for servo. - if cfg!(feature = "gecko") && cfg!(debug_assertions) && data.styles.is_display_none() { - debug_assert!(!element.has_dirty_descendants()); - debug_assert!(!element.has_animation_only_dirty_descendants()); - } - - clear_state_after_traversing(element, data, flags); -} - -fn clear_state_after_traversing<E>(element: E, data: &mut ElementData, flags: TraversalFlags) -where - E: TElement, -{ - if flags.intersects(TraversalFlags::FinalAnimationTraversal) { - debug_assert!(flags.for_animation_only()); - data.clear_restyle_flags_and_damage(); - unsafe { - element.unset_animation_only_dirty_descendants(); - } - } -} - -fn compute_style<E>( - traversal_data: &PerLevelTraversalData, - context: &mut StyleContext<E>, - element: E, - data: &mut ElementData, - kind: RestyleKind, -) -> ChildRestyleRequirement -where - E: TElement, -{ - use crate::data::RestyleKind::*; - - context.thread_local.statistics.elements_styled += 1; - debug!("compute_style: {:?} (kind={:?})", element, kind); - - if data.has_styles() { - data.set_restyled(); - } - - let mut important_rules_changed = false; - let new_styles = match kind { - MatchAndCascade => { - debug_assert!( - !context.shared.traversal_flags.for_animation_only(), - "MatchAndCascade shouldn't be processed during \ - animation-only traversal" - ); - // Ensure the bloom filter is up to date. - context - .thread_local - .bloom_filter - .insert_parents_recovering(element, traversal_data.current_dom_depth); - - context.thread_local.bloom_filter.assert_complete(element); - debug_assert_eq!( - context.thread_local.bloom_filter.matching_depth(), - traversal_data.current_dom_depth - ); - - // This is only relevant for animations as of right now. - important_rules_changed = true; - - let mut target = StyleSharingTarget::new(element); - - // Now that our bloom filter is set up, try the style sharing - // cache. - match target.share_style_if_possible(context) { - Some(shared_styles) => { - context.thread_local.statistics.styles_shared += 1; - shared_styles - }, - None => { - context.thread_local.statistics.elements_matched += 1; - // Perform the matching and cascading. - let new_styles = { - let mut resolver = StyleResolverForElement::new( - element, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ); - - resolver.resolve_style_with_default_parents() - }; - - context.thread_local.sharing_cache.insert_if_possible( - &element, - &new_styles.primary, - Some(&mut target), - traversal_data.current_dom_depth, - &context.shared, - ); - - new_styles - }, - } - }, - CascadeWithReplacements(flags) => { - // Skipping full matching, load cascade inputs from previous values. - let mut cascade_inputs = ElementCascadeInputs::new_from_element_data(data); - important_rules_changed = element.replace_rules(flags, context, &mut cascade_inputs); - - let mut resolver = StyleResolverForElement::new( - element, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ); - - resolver.cascade_styles_with_default_parents(cascade_inputs) - }, - CascadeOnly => { - // Skipping full matching, load cascade inputs from previous values. - let cascade_inputs = ElementCascadeInputs::new_from_element_data(data); - - let new_styles = { - let mut resolver = StyleResolverForElement::new( - element, - context, - RuleInclusion::All, - PseudoElementResolution::IfApplicable, - ); - - resolver.cascade_styles_with_default_parents(cascade_inputs) - }; - - // Insert into the cache, but only if this style isn't reused from a - // sibling or cousin. Otherwise, recascading a bunch of identical - // elements would unnecessarily flood the cache with identical entries. - // - // This is analogous to the obvious "don't insert an element that just - // got a hit in the style sharing cache" behavior in the MatchAndCascade - // handling above. - // - // Note that, for the MatchAndCascade path, we still insert elements that - // shared styles via the rule node, because we know that there's something - // different about them that caused them to miss the sharing cache before - // selector matching. If we didn't, we would still end up with the same - // number of eventual styles, but would potentially miss out on various - // opportunities for skipping selector matching, which could hurt - // performance. - if !new_styles.primary.reused_via_rule_node { - context.thread_local.sharing_cache.insert_if_possible( - &element, - &new_styles.primary, - None, - traversal_data.current_dom_depth, - &context.shared, - ); - } - - new_styles - }, - }; - - element.finish_restyle(context, data, new_styles, important_rules_changed) -} - -#[cfg(feature = "servo")] -fn notify_paint_worklet<E>(context: &StyleContext<E>, data: &ElementData) -where - E: TElement, -{ - use crate::values::generics::image::Image; - use style_traits::ToCss; - - // We speculatively evaluate any paint worklets during styling. - // This allows us to run paint worklets in parallel with style and layout. - // Note that this is wasted effort if the size of the node has - // changed, but in may cases it won't have. - if let Some(ref values) = data.styles.primary { - for image in &values.get_background().background_image.0 { - let (name, arguments) = match *image { - Image::PaintWorklet(ref worklet) => (&worklet.name, &worklet.arguments), - _ => continue, - }; - let painter = match context.shared.registered_speculative_painters.get(name) { - Some(painter) => painter, - None => continue, - }; - let properties = painter - .properties() - .iter() - .filter_map(|(name, id)| id.as_shorthand().err().map(|id| (name, id))) - .map(|(name, id)| (name.clone(), values.computed_value_to_string(id))) - .collect(); - let arguments = arguments - .iter() - .map(|argument| argument.to_css_string()) - .collect(); - debug!("Notifying paint worklet {}.", painter.name()); - painter.speculatively_draw_a_paint_image(properties, arguments); - } - } -} - -#[cfg(not(feature = "servo"))] -fn notify_paint_worklet<E>(_context: &StyleContext<E>, _data: &ElementData) -where - E: TElement, -{ - // The CSS paint API is Servo-only at the moment -} - -fn note_children<E, D, F>( - context: &mut StyleContext<E>, - element: E, - data: &ElementData, - propagated_hint: RestyleHint, - is_initial_style: bool, - mut note_child: F, -) where - E: TElement, - D: DomTraversal<E>, - F: FnMut(E::ConcreteNode), -{ - trace!("note_children: {:?}", element); - let flags = context.shared.traversal_flags; - - // Loop over all the traversal children. - for child_node in element.traversal_children() { - let child = match child_node.as_element() { - Some(el) => el, - None => { - if is_servo_nonincremental_layout() || - D::text_node_needs_traversal(child_node, data) - { - note_child(child_node); - } - continue; - }, - }; - - let mut child_data = child.mutate_data(); - let mut child_data = child_data.as_mut().map(|d| &mut **d); - trace!( - " > {:?} -> {:?} + {:?}, pseudo: {:?}", - child, - child_data.as_ref().map(|d| d.hint), - propagated_hint, - child.implemented_pseudo_element() - ); - - if let Some(ref mut child_data) = child_data { - child_data.hint.insert(propagated_hint); - - // Handle element snapshots and invalidation of descendants and siblings - // as needed. - // - // NB: This will be a no-op if there's no snapshot. - child_data.invalidate_style_if_needed( - child, - &context.shared, - Some(&context.thread_local.stack_limit_checker), - &mut context.thread_local.nth_index_cache, - ); - } - - if D::element_needs_traversal(child, flags, child_data.map(|d| &*d)) { - note_child(child_node); - - // Set the dirty descendants bit on the parent as needed, so that we - // can find elements during the post-traversal. - // - // Note that these bits may be cleared again at the bottom of - // recalc_style_at if requested by the caller. - if !is_initial_style { - if flags.for_animation_only() { - unsafe { - element.set_animation_only_dirty_descendants(); - } - } else { - unsafe { - element.set_dirty_descendants(); - } - } - } - } - } -} - -/// Clear style data for all the subtree under `root` (but not for root itself). -/// -/// We use a list to avoid unbounded recursion, which we need to avoid in the -/// parallel traversal because the rayon stacks are small. -pub unsafe fn clear_descendant_data<E>(root: E) -where - E: TElement, -{ - let mut parents = SmallVec::<[E; 32]>::new(); - parents.push(root); - while let Some(p) = parents.pop() { - for kid in p.traversal_children() { - if let Some(kid) = kid.as_element() { - // We maintain an invariant that, if an element has data, all its - // ancestors have data as well. - // - // By consequence, any element without data has no descendants with - // data. - if kid.has_data() { - kid.clear_data(); - parents.push(kid); - } - } - } - } - - // Make sure not to clear NODE_NEEDS_FRAME on the root. - root.clear_descendant_bits(); -} diff --git a/components/style/traversal_flags.rs b/components/style/traversal_flags.rs deleted file mode 100644 index 0987230ed02..00000000000 --- a/components/style/traversal_flags.rs +++ /dev/null @@ -1,67 +0,0 @@ -/* 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/. */ - -//! Flags that control the traversal process. -//! -//! We CamelCase rather than UPPER_CASING so that we can grep for the same -//! strings across gecko and servo. -#![allow(non_upper_case_globals)] - -bitflags! { - /// Flags that control the traversal process. - pub struct TraversalFlags: u32 { - /// Traverse only elements for animation restyles. - const AnimationOnly = 1 << 0; - /// Traverse and update all elements with CSS animations since - /// @keyframes rules may have changed. Triggered by CSS rule changes. - const ForCSSRuleChanges = 1 << 1; - /// The final animation-only traversal, which shouldn't really care about other - /// style changes anymore. - const FinalAnimationTraversal = 1 << 2; - /// Allows the traversal to run in parallel if there are sufficient cores on - /// the machine. - const ParallelTraversal = 1 << 7; - /// Flush throttled animations. By default, we only update throttled animations - /// when we have other non-throttled work to do. With this flag, we - /// unconditionally tick and process them. - const FlushThrottledAnimations = 1 << 8; - - } -} - -/// Asserts that all TraversalFlags flags have a matching ServoTraversalFlags value in gecko. -#[cfg(feature = "gecko")] -#[inline] -pub fn assert_traversal_flags_match() { - use crate::gecko_bindings::structs; - - macro_rules! check_traversal_flags { - ( $( $a:ident => $b:path ),*, ) => { - if cfg!(debug_assertions) { - let mut modes = TraversalFlags::all(); - $( - assert_eq!(structs::$a as usize, $b.bits() as usize, stringify!($b)); - modes.remove($b); - )* - assert_eq!(modes, TraversalFlags::empty(), "all TraversalFlags bits should have an assertion"); - } - } - } - - check_traversal_flags! { - ServoTraversalFlags_AnimationOnly => TraversalFlags::AnimationOnly, - ServoTraversalFlags_ForCSSRuleChanges => TraversalFlags::ForCSSRuleChanges, - ServoTraversalFlags_FinalAnimationTraversal => TraversalFlags::FinalAnimationTraversal, - ServoTraversalFlags_ParallelTraversal => TraversalFlags::ParallelTraversal, - ServoTraversalFlags_FlushThrottledAnimations => TraversalFlags::FlushThrottledAnimations, - } -} - -impl TraversalFlags { - /// Returns true if the traversal is for animation-only restyles. - #[inline] - pub fn for_animation_only(&self) -> bool { - self.contains(TraversalFlags::AnimationOnly) - } -} diff --git a/components/style/use_counters/mod.rs b/components/style/use_counters/mod.rs deleted file mode 100644 index bfa73291ee7..00000000000 --- a/components/style/use_counters/mod.rs +++ /dev/null @@ -1,96 +0,0 @@ -/* 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/. */ - -//! Various stuff for CSS property use counters. - -use crate::properties::{CountedUnknownProperty, COUNTED_UNKNOWN_PROPERTY_COUNT}; -use crate::properties::{NonCustomPropertyId, NON_CUSTOM_PROPERTY_ID_COUNT}; -use std::cell::Cell; - -#[cfg(target_pointer_width = "64")] -const BITS_PER_ENTRY: usize = 64; - -#[cfg(target_pointer_width = "32")] -const BITS_PER_ENTRY: usize = 32; - -/// One bit per each non-custom CSS property. -#[derive(Default)] -pub struct CountedUnknownPropertyUseCounters { - storage: [Cell<usize>; (COUNTED_UNKNOWN_PROPERTY_COUNT - 1 + BITS_PER_ENTRY) / BITS_PER_ENTRY], -} - -/// One bit per each non-custom CSS property. -#[derive(Default)] -pub struct NonCustomPropertyUseCounters { - storage: [Cell<usize>; (NON_CUSTOM_PROPERTY_ID_COUNT - 1 + BITS_PER_ENTRY) / BITS_PER_ENTRY], -} - -macro_rules! property_use_counters_methods { - ($id: ident) => { - /// Returns the bucket a given property belongs in, and the bitmask for that - /// property. - #[inline(always)] - fn bucket_and_pattern(id: $id) -> (usize, usize) { - let bit = id.bit(); - let bucket = bit / BITS_PER_ENTRY; - let bit_in_bucket = bit % BITS_PER_ENTRY; - (bucket, 1 << bit_in_bucket) - } - - /// Record that a given property ID has been parsed. - #[inline] - pub fn record(&self, id: $id) { - let (bucket, pattern) = Self::bucket_and_pattern(id); - let bucket = &self.storage[bucket]; - bucket.set(bucket.get() | pattern) - } - - /// Returns whether a given property ID has been recorded - /// earlier. - #[inline] - pub fn recorded(&self, id: $id) -> bool { - let (bucket, pattern) = Self::bucket_and_pattern(id); - self.storage[bucket].get() & pattern != 0 - } - - /// Merge `other` into `self`. - #[inline] - fn merge(&self, other: &Self) { - for (bucket, other_bucket) in self.storage.iter().zip(other.storage.iter()) { - bucket.set(bucket.get() | other_bucket.get()) - } - } - }; -} - -impl CountedUnknownPropertyUseCounters { - property_use_counters_methods!(CountedUnknownProperty); -} - -impl NonCustomPropertyUseCounters { - property_use_counters_methods!(NonCustomPropertyId); -} - -/// The use-counter data related to a given document we want to store. -#[derive(Default)] -pub struct UseCounters { - /// The counters for non-custom properties that have been parsed in the - /// document's stylesheets. - pub non_custom_properties: NonCustomPropertyUseCounters, - /// The counters for css properties which we haven't implemented yet. - pub counted_unknown_properties: CountedUnknownPropertyUseCounters, -} - -impl UseCounters { - /// Merge the use counters. - /// - /// Used for parallel parsing, where we parse off-main-thread. - #[inline] - pub fn merge(&self, other: &Self) { - self.non_custom_properties - .merge(&other.non_custom_properties); - self.counted_unknown_properties - .merge(&other.counted_unknown_properties); - } -} diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs deleted file mode 100644 index 3e05ba724de..00000000000 --- a/components/style/values/animated/color.rs +++ /dev/null @@ -1,88 +0,0 @@ -/* 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/. */ - -//! Animated types for CSS colors. - -use crate::color::mix::ColorInterpolationMethod; -use crate::color::AbsoluteColor; -use crate::values::animated::{Animate, Procedure, ToAnimatedZero}; -use crate::values::computed::Percentage; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::color::{GenericColor, GenericColorMix}; - -impl Animate for AbsoluteColor { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let (left_weight, right_weight) = procedure.weights(); - Ok(crate::color::mix::mix( - ColorInterpolationMethod::best_interpolation_between(self, other), - self, - left_weight as f32, - other, - right_weight as f32, - /* normalize_weights = */ false, - )) - } -} - -impl ComputeSquaredDistance for AbsoluteColor { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let start = [ - self.alpha, - self.components.0 * self.alpha, - self.components.1 * self.alpha, - self.components.2 * self.alpha, - ]; - let end = [ - other.alpha, - other.components.0 * other.alpha, - other.components.1 * other.alpha, - other.components.2 * other.alpha, - ]; - start - .iter() - .zip(&end) - .map(|(this, other)| this.compute_squared_distance(other)) - .sum() - } -} - -/// An animated value for `<color>`. -pub type Color = GenericColor<Percentage>; - -/// An animated value for `<color-mix>`. -pub type ColorMix = GenericColorMix<Color, Percentage>; - -impl Animate for Color { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let (left_weight, right_weight) = procedure.weights(); - Ok(Self::from_color_mix(ColorMix { - interpolation: ColorInterpolationMethod::srgb(), - left: self.clone(), - left_percentage: Percentage(left_weight as f32), - right: other.clone(), - right_percentage: Percentage(right_weight as f32), - // See https://github.com/w3c/csswg-drafts/issues/7324 - normalize_weights: false, - })) - } -} - -impl ComputeSquaredDistance for Color { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let current_color = AbsoluteColor::transparent(); - self.resolve_to_absolute(¤t_color) - .compute_squared_distance(&other.resolve_to_absolute(¤t_color)) - } -} - -impl ToAnimatedZero for Color { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(Color::Absolute(AbsoluteColor::transparent())) - } -} diff --git a/components/style/values/animated/effects.rs b/components/style/values/animated/effects.rs deleted file mode 100644 index e2b37cecbe7..00000000000 --- a/components/style/values/animated/effects.rs +++ /dev/null @@ -1,27 +0,0 @@ -/* 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/. */ - -//! Animated types for CSS values related to effects. - -use crate::values::animated::color::Color; -use crate::values::computed::length::Length; -#[cfg(feature = "gecko")] -use crate::values::computed::url::ComputedUrl; -use crate::values::computed::{Angle, Number}; -use crate::values::generics::effects::Filter as GenericFilter; -use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow; -#[cfg(not(feature = "gecko"))] -use crate::values::Impossible; - -/// An animated value for the `drop-shadow()` filter. -pub type AnimatedSimpleShadow = GenericSimpleShadow<Color, Length, Length>; - -/// An animated value for a single `filter`. -#[cfg(feature = "gecko")] -pub type AnimatedFilter = - GenericFilter<Angle, Number, Number, Length, AnimatedSimpleShadow, ComputedUrl>; - -/// An animated value for a single `filter`. -#[cfg(not(feature = "gecko"))] -pub type AnimatedFilter = GenericFilter<Angle, Number, Number, Length, AnimatedSimpleShadow, Impossible>; diff --git a/components/style/values/animated/font.rs b/components/style/values/animated/font.rs deleted file mode 100644 index 63d4a14b2fe..00000000000 --- a/components/style/values/animated/font.rs +++ /dev/null @@ -1,37 +0,0 @@ -/* 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/. */ - -//! Animation implementation for various font-related types. - -use super::{Animate, Procedure, ToAnimatedZero}; -use crate::values::computed::font::FontVariationSettings; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; - -/// <https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def> -/// -/// Note that the ComputedValue implementation will already have sorted and de-dup'd -/// the lists of settings, so we can just iterate over the two lists together and -/// animate their individual values. -impl Animate for FontVariationSettings { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let result: Vec<_> = - super::lists::by_computed_value::animate(&self.0, &other.0, procedure)?; - Ok(Self(result.into_boxed_slice())) - } -} - -impl ComputeSquaredDistance for FontVariationSettings { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - super::lists::by_computed_value::squared_distance(&self.0, &other.0) - } -} - -impl ToAnimatedZero for FontVariationSettings { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} diff --git a/components/style/values/animated/grid.rs b/components/style/values/animated/grid.rs deleted file mode 100644 index 8136ba5ece3..00000000000 --- a/components/style/values/animated/grid.rs +++ /dev/null @@ -1,157 +0,0 @@ -/* 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/. */ - -//! Animation implementation for various grid-related types. - -// Note: we can implement Animate on their generic types directly, but in this case we need to -// make sure two trait bounds, L: Clone and I: PartialEq, are satisfied on almost all the -// grid-related types and their other trait implementations because Animate needs them. So in -// order to avoid adding these two trait bounds (or maybe more..) everywhere, we implement -// Animate for the computed types, instead of the generic types. - -use super::{Animate, Procedure, ToAnimatedZero}; -use crate::values::computed::Integer; -use crate::values::computed::LengthPercentage; -use crate::values::computed::{GridTemplateComponent, TrackList, TrackSize}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::grid as generics; - -fn discrete<T: Clone>(from: &T, to: &T, procedure: Procedure) -> Result<T, ()> { - if let Procedure::Interpolate { progress } = procedure { - Ok(if progress < 0.5 { - from.clone() - } else { - to.clone() - }) - } else { - Err(()) - } -} - -fn animate_with_discrete_fallback<T: Animate + Clone>( - from: &T, - to: &T, - procedure: Procedure, -) -> Result<T, ()> { - from.animate(to, procedure) - .or_else(|_| discrete(from, to, procedure)) -} - -impl Animate for TrackSize { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self, other) { - (&generics::TrackSize::Breadth(ref from), &generics::TrackSize::Breadth(ref to)) => { - animate_with_discrete_fallback(from, to, procedure) - .map(generics::TrackSize::Breadth) - }, - ( - &generics::TrackSize::Minmax(ref from_min, ref from_max), - &generics::TrackSize::Minmax(ref to_min, ref to_max), - ) => Ok(generics::TrackSize::Minmax( - animate_with_discrete_fallback(from_min, to_min, procedure)?, - animate_with_discrete_fallback(from_max, to_max, procedure)?, - )), - ( - &generics::TrackSize::FitContent(ref from), - &generics::TrackSize::FitContent(ref to), - ) => animate_with_discrete_fallback(from, to, procedure) - .map(generics::TrackSize::FitContent), - (_, _) => discrete(self, other, procedure), - } - } -} - -impl Animate for generics::TrackRepeat<LengthPercentage, Integer> { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - // If the keyword, auto-fit/fill, is the same it can result in different - // number of tracks. For both auto-fit/fill, the number of columns isn't - // known until you do layout since it depends on the container size, item - // placement and other factors, so we cannot do the correct interpolation - // by computed values. Therefore, return Err(()) if it's keywords. If it - // is Number, we support animation only if the count is the same and the - // length of track_sizes is the same. - // https://github.com/w3c/csswg-drafts/issues/3503 - match (&self.count, &other.count) { - (&generics::RepeatCount::Number(from), &generics::RepeatCount::Number(to)) - if from == to => - { - () - }, - (_, _) => return Err(()), - } - - let count = self.count; - let track_sizes = super::lists::by_computed_value::animate( - &self.track_sizes, - &other.track_sizes, - procedure, - )?; - - // The length of |line_names| is always 0 or N+1, where N is the length - // of |track_sizes|. Besides, <line-names> is always discrete. - let line_names = discrete(&self.line_names, &other.line_names, procedure)?; - - Ok(generics::TrackRepeat { - count, - line_names, - track_sizes, - }) - } -} - -impl Animate for TrackList { - // Based on https://github.com/w3c/csswg-drafts/issues/3201: - // 1. Check interpolation type per track, so we need to handle discrete animations - // in TrackSize, so any Err(()) returned from TrackSize doesn't make all TrackSize - // fallback to discrete animation. - // 2. line-names is always discrete. - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if self.values.len() != other.values.len() { - return Err(()); - } - - if self.is_explicit() != other.is_explicit() { - return Err(()); - } - - // For now, repeat(auto-fill/auto-fit, ...) is not animatable. - // TrackRepeat will return Err(()) if we use keywords. Therefore, we can - // early return here to avoid traversing |values| in <auto-track-list>. - // This may be updated in the future. - // https://github.com/w3c/csswg-drafts/issues/3503 - if self.has_auto_repeat() || other.has_auto_repeat() { - return Err(()); - } - - let values = - super::lists::by_computed_value::animate(&self.values, &other.values, procedure)?; - - // The length of |line_names| is always 0 or N+1, where N is the length - // of |track_sizes|. Besides, <line-names> is always discrete. - let line_names = discrete(&self.line_names, &other.line_names, procedure)?; - - Ok(TrackList { - values, - line_names, - auto_repeat_index: self.auto_repeat_index, - }) - } -} - -impl ComputeSquaredDistance for GridTemplateComponent { - #[inline] - fn compute_squared_distance(&self, _other: &Self) -> Result<SquaredDistance, ()> { - // TODO: Bug 1518585, we should implement ComputeSquaredDistance. - Err(()) - } -} - -impl ToAnimatedZero for GridTemplateComponent { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - // It's not clear to get a zero grid track list based on the current definition - // of spec, so we return Err(()) directly. - Err(()) - } -} diff --git a/components/style/values/animated/lists.rs b/components/style/values/animated/lists.rs deleted file mode 100644 index 8b3898c4971..00000000000 --- a/components/style/values/animated/lists.rs +++ /dev/null @@ -1,141 +0,0 @@ -/* 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/. */ - -//! Lists have various ways of being animated, this module implements them. -//! -//! See https://drafts.csswg.org/web-animations-1/#animating-properties - -/// https://drafts.csswg.org/web-animations-1/#by-computed-value -pub mod by_computed_value { - use crate::values::{ - animated::{Animate, Procedure}, - distance::{ComputeSquaredDistance, SquaredDistance}, - }; - use std::iter::FromIterator; - - #[allow(missing_docs)] - pub fn animate<T, C>(left: &[T], right: &[T], procedure: Procedure) -> Result<C, ()> - where - T: Animate, - C: FromIterator<T>, - { - if left.len() != right.len() { - return Err(()); - } - left.iter() - .zip(right.iter()) - .map(|(left, right)| left.animate(right, procedure)) - .collect() - } - - #[allow(missing_docs)] - pub fn squared_distance<T>(left: &[T], right: &[T]) -> Result<SquaredDistance, ()> - where - T: ComputeSquaredDistance, - { - if left.len() != right.len() { - return Err(()); - } - left.iter() - .zip(right.iter()) - .map(|(left, right)| left.compute_squared_distance(right)) - .sum() - } -} - -/// This is the animation used for some of the types like shadows and filters, where the -/// interpolation happens with the zero value if one of the sides is not present. -/// -/// https://drafts.csswg.org/web-animations-1/#animating-shadow-lists -pub mod with_zero { - use crate::values::animated::ToAnimatedZero; - use crate::values::{ - animated::{Animate, Procedure}, - distance::{ComputeSquaredDistance, SquaredDistance}, - }; - use itertools::{EitherOrBoth, Itertools}; - use std::iter::FromIterator; - - #[allow(missing_docs)] - pub fn animate<T, C>(left: &[T], right: &[T], procedure: Procedure) -> Result<C, ()> - where - T: Animate + Clone + ToAnimatedZero, - C: FromIterator<T>, - { - if procedure == Procedure::Add { - return Ok(left.iter().chain(right.iter()).cloned().collect()); - } - left.iter() - .zip_longest(right.iter()) - .map(|it| match it { - EitherOrBoth::Both(left, right) => left.animate(right, procedure), - EitherOrBoth::Left(left) => left.animate(&left.to_animated_zero()?, procedure), - EitherOrBoth::Right(right) => right.to_animated_zero()?.animate(right, procedure), - }) - .collect() - } - - #[allow(missing_docs)] - pub fn squared_distance<T>(left: &[T], right: &[T]) -> Result<SquaredDistance, ()> - where - T: ToAnimatedZero + ComputeSquaredDistance, - { - left.iter() - .zip_longest(right.iter()) - .map(|it| match it { - EitherOrBoth::Both(left, right) => left.compute_squared_distance(right), - EitherOrBoth::Left(item) | EitherOrBoth::Right(item) => { - item.to_animated_zero()?.compute_squared_distance(item) - }, - }) - .sum() - } -} - -/// https://drafts.csswg.org/web-animations-1/#repeatable-list -pub mod repeatable_list { - use crate::values::{ - animated::{Animate, Procedure}, - distance::{ComputeSquaredDistance, SquaredDistance}, - }; - use std::iter::FromIterator; - - #[allow(missing_docs)] - pub fn animate<T, C>(left: &[T], right: &[T], procedure: Procedure) -> Result<C, ()> - where - T: Animate, - C: FromIterator<T>, - { - use num_integer::lcm; - // If the length of either list is zero, the least common multiple is undefined. - if left.is_empty() || right.is_empty() { - return Err(()); - } - let len = lcm(left.len(), right.len()); - left.iter() - .cycle() - .zip(right.iter().cycle()) - .take(len) - .map(|(left, right)| left.animate(right, procedure)) - .collect() - } - - #[allow(missing_docs)] - pub fn squared_distance<T>(left: &[T], right: &[T]) -> Result<SquaredDistance, ()> - where - T: ComputeSquaredDistance, - { - use num_integer::lcm; - if left.is_empty() || right.is_empty() { - return Err(()); - } - let len = lcm(left.len(), right.len()); - left.iter() - .cycle() - .zip(right.iter().cycle()) - .take(len) - .map(|(left, right)| left.compute_squared_distance(right)) - .sum() - } -} diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs deleted file mode 100644 index 04a05a81ac4..00000000000 --- a/components/style/values/animated/mod.rs +++ /dev/null @@ -1,488 +0,0 @@ -/* 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/. */ - -//! Animated values. -//! -//! Some values, notably colors, cannot be interpolated directly with their -//! computed values and need yet another intermediate representation. This -//! module's raison d'être is to ultimately contain all these types. - -use crate::color::AbsoluteColor; -use crate::properties::PropertyId; -use crate::values::computed::length::LengthPercentage; -use crate::values::computed::url::ComputedUrl; -use crate::values::computed::Angle as ComputedAngle; -use crate::values::computed::Image; -use crate::values::specified::SVGPathData; -use crate::values::CSSFloat; -use app_units::Au; -use smallvec::SmallVec; -use std::cmp; - -pub mod color; -pub mod effects; -mod font; -mod grid; -pub mod lists; -mod svg; -pub mod transform; - -/// The category a property falls into for ordering purposes. -/// -/// https://drafts.csswg.org/web-animations/#calculating-computed-keyframes -#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] -enum PropertyCategory { - Custom, - PhysicalLonghand, - LogicalLonghand, - Shorthand, -} - -impl PropertyCategory { - fn of(id: &PropertyId) -> Self { - match *id { - PropertyId::Shorthand(..) | PropertyId::ShorthandAlias(..) => { - PropertyCategory::Shorthand - }, - PropertyId::Longhand(id) | PropertyId::LonghandAlias(id, ..) => { - if id.is_logical() { - PropertyCategory::LogicalLonghand - } else { - PropertyCategory::PhysicalLonghand - } - }, - PropertyId::Custom(..) => PropertyCategory::Custom, - } - } -} - -/// A comparator to sort PropertyIds such that physical longhands are sorted -/// before logical longhands and shorthands, shorthands with fewer components -/// are sorted before shorthands with more components, and otherwise shorthands -/// are sorted by IDL name as defined by [Web Animations][property-order]. -/// -/// Using this allows us to prioritize values specified by longhands (or smaller -/// shorthand subsets) when longhands and shorthands are both specified on the -/// one keyframe. -/// -/// [property-order] https://drafts.csswg.org/web-animations/#calculating-computed-keyframes -pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Ordering { - let a_category = PropertyCategory::of(a); - let b_category = PropertyCategory::of(b); - - if a_category != b_category { - return a_category.cmp(&b_category); - } - - if a_category != PropertyCategory::Shorthand { - return cmp::Ordering::Equal; - } - - let a = a.as_shorthand().unwrap(); - let b = b.as_shorthand().unwrap(); - // Within shorthands, sort by the number of subproperties, then by IDL - // name. - let subprop_count_a = a.longhands().count(); - let subprop_count_b = b.longhands().count(); - subprop_count_a - .cmp(&subprop_count_b) - .then_with(|| a.idl_name_sort_order().cmp(&b.idl_name_sort_order())) -} - -/// A helper function to animate two multiplicative factor. -pub fn animate_multiplicative_factor( - this: CSSFloat, - other: CSSFloat, - procedure: Procedure, -) -> Result<CSSFloat, ()> { - Ok((this - 1.).animate(&(other - 1.), procedure)? + 1.) -} - -/// Animate from one value to another. -/// -/// This trait is derivable with `#[derive(Animate)]`. The derived -/// implementation uses a `match` expression with identical patterns for both -/// `self` and `other`, calling `Animate::animate` on each fields of the values. -/// If a field is annotated with `#[animation(constant)]`, the two values should -/// be equal or an error is returned. -/// -/// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm returns an error. -/// -/// Trait bounds for type parameter `Foo` can be opted out of with -/// `#[animation(no_bound(Foo))]` on the type definition, trait bounds for -/// fields can be opted into with `#[animation(field_bound)]` on the field. -pub trait Animate: Sized { - /// Animate a value towards another one, given an animation procedure. - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>; -} - -/// An animation procedure. -/// -/// <https://drafts.csswg.org/web-animations/#procedures-for-animating-properties> -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Procedure { - /// <https://drafts.csswg.org/web-animations/#animation-interpolation> - Interpolate { progress: f64 }, - /// <https://drafts.csswg.org/web-animations/#animation-addition> - Add, - /// <https://drafts.csswg.org/web-animations/#animation-accumulation> - Accumulate { count: u64 }, -} - -/// Conversion between computed values and intermediate values for animations. -/// -/// Notably, colors are represented as four floats during animations. -/// -/// This trait is derivable with `#[derive(ToAnimatedValue)]`. -pub trait ToAnimatedValue { - /// The type of the animated value. - type AnimatedValue; - - /// Converts this value to an animated value. - fn to_animated_value(self) -> Self::AnimatedValue; - - /// Converts back an animated value into a computed value. - fn from_animated_value(animated: Self::AnimatedValue) -> Self; -} - -/// Returns a value similar to `self` that represents zero. -/// -/// This trait is derivable with `#[derive(ToAnimatedValue)]`. If a field is -/// annotated with `#[animation(constant)]`, a clone of its value will be used -/// instead of calling `ToAnimatedZero::to_animated_zero` on it. -/// -/// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm is not generated. -/// -/// Trait bounds for type parameter `Foo` can be opted out of with -/// `#[animation(no_bound(Foo))]` on the type definition. -pub trait ToAnimatedZero: Sized { - /// Returns a value that, when added with an underlying value, will produce the underlying - /// value. This is used for SMIL animation's "by-animation" where SMIL first interpolates from - /// the zero value to the 'by' value, and then adds the result to the underlying value. - /// - /// This is not the necessarily the same as the initial value of a property. For example, the - /// initial value of 'stroke-width' is 1, but the zero value is 0, since adding 1 to the - /// underlying value will not produce the underlying value. - fn to_animated_zero(&self) -> Result<Self, ()>; -} - -impl Procedure { - /// Returns this procedure as a pair of weights. - /// - /// This is useful for animations that don't animate differently - /// depending on the used procedure. - #[inline] - pub fn weights(self) -> (f64, f64) { - match self { - Procedure::Interpolate { progress } => (1. - progress, progress), - Procedure::Add => (1., 1.), - Procedure::Accumulate { count } => (count as f64, 1.), - } - } -} - -/// <https://drafts.csswg.org/css-transitions/#animtype-number> -impl Animate for i32 { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(((*self as f64).animate(&(*other as f64), procedure)? + 0.5).floor() as i32) - } -} - -/// <https://drafts.csswg.org/css-transitions/#animtype-number> -impl Animate for f32 { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let ret = (*self as f64).animate(&(*other as f64), procedure)?; - Ok(ret.min(f32::MAX as f64).max(f32::MIN as f64) as f32) - } -} - -/// <https://drafts.csswg.org/css-transitions/#animtype-number> -impl Animate for f64 { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let (self_weight, other_weight) = procedure.weights(); - - let ret = *self * self_weight + *other * other_weight; - Ok(ret.min(f64::MAX).max(f64::MIN)) - } -} - -impl<T> Animate for Option<T> -where - T: Animate, -{ - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self.as_ref(), other.as_ref()) { - (Some(ref this), Some(ref other)) => Ok(Some(this.animate(other, procedure)?)), - (None, None) => Ok(None), - _ => Err(()), - } - } -} - -impl Animate for Au { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Au::new(self.0.animate(&other.0, procedure)?)) - } -} - -impl<T: Animate> Animate for Box<T> { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Box::new((**self).animate(&other, procedure)?)) - } -} - -impl<T> ToAnimatedValue for Option<T> -where - T: ToAnimatedValue, -{ - type AnimatedValue = Option<<T as ToAnimatedValue>::AnimatedValue>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.map(T::to_animated_value) - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated.map(T::from_animated_value) - } -} - -impl<T> ToAnimatedValue for Vec<T> -where - T: ToAnimatedValue, -{ - type AnimatedValue = Vec<<T as ToAnimatedValue>::AnimatedValue>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.into_iter().map(T::to_animated_value).collect() - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated.into_iter().map(T::from_animated_value).collect() - } -} - -impl<T> ToAnimatedValue for Box<T> -where - T: ToAnimatedValue, -{ - type AnimatedValue = Box<<T as ToAnimatedValue>::AnimatedValue>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - Box::new((*self).to_animated_value()) - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - Box::new(T::from_animated_value(*animated)) - } -} - -impl<T> ToAnimatedValue for Box<[T]> -where - T: ToAnimatedValue, -{ - type AnimatedValue = Box<[<T as ToAnimatedValue>::AnimatedValue]>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.into_vec() - .into_iter() - .map(T::to_animated_value) - .collect::<Vec<_>>() - .into_boxed_slice() - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated - .into_vec() - .into_iter() - .map(T::from_animated_value) - .collect::<Vec<_>>() - .into_boxed_slice() - } -} - -impl<T> ToAnimatedValue for crate::OwnedSlice<T> -where - T: ToAnimatedValue, -{ - type AnimatedValue = crate::OwnedSlice<<T as ToAnimatedValue>::AnimatedValue>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.into_box().to_animated_value().into() - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - Self::from(Box::from_animated_value(animated.into_box())) - } -} - -impl<T> ToAnimatedValue for SmallVec<[T; 1]> -where - T: ToAnimatedValue, -{ - type AnimatedValue = SmallVec<[T::AnimatedValue; 1]>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.into_iter().map(T::to_animated_value).collect() - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated.into_iter().map(T::from_animated_value).collect() - } -} - -macro_rules! trivial_to_animated_value { - ($ty:ty) => { - impl $crate::values::animated::ToAnimatedValue for $ty { - type AnimatedValue = Self; - - #[inline] - fn to_animated_value(self) -> Self { - self - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated - } - } - }; -} - -trivial_to_animated_value!(Au); -trivial_to_animated_value!(LengthPercentage); -trivial_to_animated_value!(ComputedAngle); -trivial_to_animated_value!(ComputedUrl); -trivial_to_animated_value!(bool); -trivial_to_animated_value!(f32); -trivial_to_animated_value!(i32); -trivial_to_animated_value!(AbsoluteColor); -// Note: This implementation is for ToAnimatedValue of ShapeSource. -// -// SVGPathData uses Box<[T]>. If we want to derive ToAnimatedValue for all the -// types, we have to do "impl ToAnimatedValue for Box<[T]>" first. -// However, the general version of "impl ToAnimatedValue for Box<[T]>" needs to -// clone |T| and convert it into |T::AnimatedValue|. However, for SVGPathData -// that is unnecessary--moving |T| is sufficient. So here, we implement this -// trait manually. -trivial_to_animated_value!(SVGPathData); -// FIXME: Bug 1514342, Image is not animatable, but we still need to implement -// this to avoid adding this derive to generic::Image and all its arms. We can -// drop this after landing Bug 1514342. -trivial_to_animated_value!(Image); - -impl ToAnimatedZero for Au { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(Au(0)) - } -} - -impl ToAnimatedZero for f32 { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(0.) - } -} - -impl ToAnimatedZero for f64 { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(0.) - } -} - -impl ToAnimatedZero for i32 { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(0) - } -} - -impl<T> ToAnimatedZero for Box<T> -where - T: ToAnimatedZero, -{ - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(Box::new((**self).to_animated_zero()?)) - } -} - -impl<T> ToAnimatedZero for Option<T> -where - T: ToAnimatedZero, -{ - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - match *self { - Some(ref value) => Ok(Some(value.to_animated_zero()?)), - None => Ok(None), - } - } -} - -impl<T> ToAnimatedZero for Vec<T> -where - T: ToAnimatedZero, -{ - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - self.iter().map(|v| v.to_animated_zero()).collect() - } -} - -impl<T> ToAnimatedZero for Box<[T]> -where - T: ToAnimatedZero, -{ - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - self.iter().map(|v| v.to_animated_zero()).collect() - } -} - -impl<T> ToAnimatedZero for crate::OwnedSlice<T> -where - T: ToAnimatedZero, -{ - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - self.iter().map(|v| v.to_animated_zero()).collect() - } -} - -impl<T> ToAnimatedZero for crate::ArcSlice<T> -where - T: ToAnimatedZero, -{ - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - let v = self - .iter() - .map(|v| v.to_animated_zero()) - .collect::<Result<Vec<_>, _>>()?; - Ok(crate::ArcSlice::from_iter(v.into_iter())) - } -} diff --git a/components/style/values/animated/svg.rs b/components/style/values/animated/svg.rs deleted file mode 100644 index 04e35098adb..00000000000 --- a/components/style/values/animated/svg.rs +++ /dev/null @@ -1,46 +0,0 @@ -/* 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/. */ - -//! Animation implementations for various SVG-related types. - -use super::{Animate, Procedure}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::svg::SVGStrokeDashArray; - -/// <https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty> -impl<L> Animate for SVGStrokeDashArray<L> -where - L: Clone + Animate, -{ - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if matches!(procedure, Procedure::Add | Procedure::Accumulate { .. }) { - // Non-additive. - return Err(()); - } - match (self, other) { - (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => { - Ok(SVGStrokeDashArray::Values( - super::lists::repeatable_list::animate(this, other, procedure)?, - )) - }, - _ => Err(()), - } - } -} - -impl<L> ComputeSquaredDistance for SVGStrokeDashArray<L> -where - L: ComputeSquaredDistance, -{ - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - match (self, other) { - (&SVGStrokeDashArray::Values(ref this), &SVGStrokeDashArray::Values(ref other)) => { - super::lists::repeatable_list::squared_distance(this, other) - }, - _ => Err(()), - } - } -} diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs deleted file mode 100644 index d30f8b74f80..00000000000 --- a/components/style/values/animated/transform.rs +++ /dev/null @@ -1,1473 +0,0 @@ -/* 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/. */ - -//! Animated types for transform. -// There are still some implementation on Matrix3D in animated_properties.mako.rs -// because they still need mako to generate the code. - -use super::animate_multiplicative_factor; -use super::{Animate, Procedure, ToAnimatedZero}; -use crate::values::computed::transform::Rotate as ComputedRotate; -use crate::values::computed::transform::Scale as ComputedScale; -use crate::values::computed::transform::Transform as ComputedTransform; -use crate::values::computed::transform::TransformOperation as ComputedTransformOperation; -use crate::values::computed::transform::Translate as ComputedTranslate; -use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D}; -use crate::values::computed::Angle; -use crate::values::computed::{Length, LengthPercentage}; -use crate::values::computed::{Number, Percentage}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::transform::{self, Transform, TransformOperation}; -use crate::values::generics::transform::{Rotate, Scale, Translate}; -use crate::values::CSSFloat; -use crate::Zero; -use std::cmp; - -// ------------------------------------ -// Animations for Matrix/Matrix3D. -// ------------------------------------ -/// A 2d matrix for interpolation. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[allow(missing_docs)] -// FIXME: We use custom derive for ComputeSquaredDistance. However, If possible, we should convert -// the InnerMatrix2D into types with physical meaning. This custom derive computes the squared -// distance from each matrix item, and this makes the result different from that in Gecko if we -// have skew factor in the Matrix3D. -pub struct InnerMatrix2D { - pub m11: CSSFloat, - pub m12: CSSFloat, - pub m21: CSSFloat, - pub m22: CSSFloat, -} - -impl Animate for InnerMatrix2D { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(InnerMatrix2D { - m11: animate_multiplicative_factor(self.m11, other.m11, procedure)?, - m12: self.m12.animate(&other.m12, procedure)?, - m21: self.m21.animate(&other.m21, procedure)?, - m22: animate_multiplicative_factor(self.m22, other.m22, procedure)?, - }) - } -} - -/// A 2d translation function. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] -pub struct Translate2D(f32, f32); - -/// A 2d scale function. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Scale2D(f32, f32); - -impl Animate for Scale2D { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Scale2D( - animate_multiplicative_factor(self.0, other.0, procedure)?, - animate_multiplicative_factor(self.1, other.1, procedure)?, - )) - } -} - -/// A decomposed 2d matrix. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MatrixDecomposed2D { - /// The translation function. - pub translate: Translate2D, - /// The scale function. - pub scale: Scale2D, - /// The rotation angle. - pub angle: f32, - /// The inner matrix. - pub matrix: InnerMatrix2D, -} - -impl Animate for MatrixDecomposed2D { - /// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values> - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - // If x-axis of one is flipped, and y-axis of the other, - // convert to an unflipped rotation. - let mut scale = self.scale; - let mut angle = self.angle; - let mut other_angle = other.angle; - if (scale.0 < 0.0 && other.scale.1 < 0.0) || (scale.1 < 0.0 && other.scale.0 < 0.0) { - scale.0 = -scale.0; - scale.1 = -scale.1; - angle += if angle < 0.0 { 180. } else { -180. }; - } - - // Don't rotate the long way around. - if angle == 0.0 { - angle = 360. - } - if other_angle == 0.0 { - other_angle = 360. - } - - if (angle - other_angle).abs() > 180. { - if angle > other_angle { - angle -= 360. - } else { - other_angle -= 360. - } - } - - // Interpolate all values. - let translate = self.translate.animate(&other.translate, procedure)?; - let scale = scale.animate(&other.scale, procedure)?; - let angle = angle.animate(&other_angle, procedure)?; - let matrix = self.matrix.animate(&other.matrix, procedure)?; - - Ok(MatrixDecomposed2D { - translate, - scale, - angle, - matrix, - }) - } -} - -impl ComputeSquaredDistance for MatrixDecomposed2D { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // Use Radian to compute the distance. - const RAD_PER_DEG: f64 = std::f64::consts::PI / 180.0; - let angle1 = self.angle as f64 * RAD_PER_DEG; - let angle2 = other.angle as f64 * RAD_PER_DEG; - Ok(self.translate.compute_squared_distance(&other.translate)? + - self.scale.compute_squared_distance(&other.scale)? + - angle1.compute_squared_distance(&angle2)? + - self.matrix.compute_squared_distance(&other.matrix)?) - } -} - -impl From<Matrix3D> for MatrixDecomposed2D { - /// Decompose a 2D matrix. - /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix> - fn from(matrix: Matrix3D) -> MatrixDecomposed2D { - let mut row0x = matrix.m11; - let mut row0y = matrix.m12; - let mut row1x = matrix.m21; - let mut row1y = matrix.m22; - - let translate = Translate2D(matrix.m41, matrix.m42); - let mut scale = Scale2D( - (row0x * row0x + row0y * row0y).sqrt(), - (row1x * row1x + row1y * row1y).sqrt(), - ); - - // If determinant is negative, one axis was flipped. - let determinant = row0x * row1y - row0y * row1x; - if determinant < 0. { - if row0x < row1y { - scale.0 = -scale.0; - } else { - scale.1 = -scale.1; - } - } - - // Renormalize matrix to remove scale. - if scale.0 != 0.0 { - row0x *= 1. / scale.0; - row0y *= 1. / scale.0; - } - if scale.1 != 0.0 { - row1x *= 1. / scale.1; - row1y *= 1. / scale.1; - } - - // Compute rotation and renormalize matrix. - let mut angle = row0y.atan2(row0x); - if angle != 0.0 { - let sn = -row0y; - let cs = row0x; - let m11 = row0x; - let m12 = row0y; - let m21 = row1x; - let m22 = row1y; - row0x = cs * m11 + sn * m21; - row0y = cs * m12 + sn * m22; - row1x = -sn * m11 + cs * m21; - row1y = -sn * m12 + cs * m22; - } - - let m = InnerMatrix2D { - m11: row0x, - m12: row0y, - m21: row1x, - m22: row1y, - }; - - // Convert into degrees because our rotation functions expect it. - angle = angle.to_degrees(); - MatrixDecomposed2D { - translate: translate, - scale: scale, - angle: angle, - matrix: m, - } - } -} - -impl From<MatrixDecomposed2D> for Matrix3D { - /// Recompose a 2D matrix. - /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix> - fn from(decomposed: MatrixDecomposed2D) -> Matrix3D { - let mut computed_matrix = Matrix3D::identity(); - computed_matrix.m11 = decomposed.matrix.m11; - computed_matrix.m12 = decomposed.matrix.m12; - computed_matrix.m21 = decomposed.matrix.m21; - computed_matrix.m22 = decomposed.matrix.m22; - - // Translate matrix. - computed_matrix.m41 = decomposed.translate.0; - computed_matrix.m42 = decomposed.translate.1; - - // Rotate matrix. - let angle = decomposed.angle.to_radians(); - let cos_angle = angle.cos(); - let sin_angle = angle.sin(); - - let mut rotate_matrix = Matrix3D::identity(); - rotate_matrix.m11 = cos_angle; - rotate_matrix.m12 = sin_angle; - rotate_matrix.m21 = -sin_angle; - rotate_matrix.m22 = cos_angle; - - // Multiplication of computed_matrix and rotate_matrix - computed_matrix = rotate_matrix.multiply(&computed_matrix); - - // Scale matrix. - computed_matrix.m11 *= decomposed.scale.0; - computed_matrix.m12 *= decomposed.scale.0; - computed_matrix.m21 *= decomposed.scale.1; - computed_matrix.m22 *= decomposed.scale.1; - computed_matrix - } -} - -impl Animate for Matrix { - #[cfg(feature = "servo")] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let this = Matrix3D::from(*self); - let other = Matrix3D::from(*other); - let this = MatrixDecomposed2D::from(this); - let other = MatrixDecomposed2D::from(other); - Matrix3D::from(this.animate(&other, procedure)?).into_2d() - } - - #[cfg(feature = "gecko")] - // Gecko doesn't exactly follow the spec here; we use a different procedure - // to match it - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let this = Matrix3D::from(*self); - let other = Matrix3D::from(*other); - let from = decompose_2d_matrix(&this)?; - let to = decompose_2d_matrix(&other)?; - Matrix3D::from(from.animate(&to, procedure)?).into_2d() - } -} - -/// A 3d translation. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] -pub struct Translate3D(pub f32, pub f32, pub f32); - -/// A 3d scale function. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Scale3D(pub f32, pub f32, pub f32); - -impl Scale3D { - /// Negate self. - fn negate(&mut self) { - self.0 *= -1.0; - self.1 *= -1.0; - self.2 *= -1.0; - } -} - -impl Animate for Scale3D { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Scale3D( - animate_multiplicative_factor(self.0, other.0, procedure)?, - animate_multiplicative_factor(self.1, other.1, procedure)?, - animate_multiplicative_factor(self.2, other.2, procedure)?, - )) - } -} - -/// A 3d skew function. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Animate, Clone, Copy, Debug)] -pub struct Skew(f32, f32, f32); - -impl ComputeSquaredDistance for Skew { - // We have to use atan() to convert the skew factors into skew angles, so implement - // ComputeSquaredDistance manually. - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(self.0.atan().compute_squared_distance(&other.0.atan())? + - self.1.atan().compute_squared_distance(&other.1.atan())? + - self.2.atan().compute_squared_distance(&other.2.atan())?) - } -} - -/// A 3d perspective transformation. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Perspective(pub f32, pub f32, pub f32, pub f32); - -impl Animate for Perspective { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Perspective( - self.0.animate(&other.0, procedure)?, - self.1.animate(&other.1, procedure)?, - self.2.animate(&other.2, procedure)?, - animate_multiplicative_factor(self.3, other.3, procedure)?, - )) - } -} - -/// A quaternion used to represent a rotation. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Quaternion(f64, f64, f64, f64); - -impl Quaternion { - /// Return a quaternion from a unit direction vector and angle (unit: radian). - #[inline] - fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self { - debug_assert!( - (vector.length() - 1.).abs() < 0.0001, - "Only accept an unit direction vector to create a quaternion" - ); - // Reference: - // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation - // - // if the direction axis is (x, y, z) = xi + yj + zk, - // and the angle is |theta|, this formula can be done using - // an extension of Euler's formula: - // q = cos(theta/2) + (xi + yj + zk)(sin(theta/2)) - // = cos(theta/2) + - // x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k - Quaternion( - vector.x as f64 * (angle / 2.).sin(), - vector.y as f64 * (angle / 2.).sin(), - vector.z as f64 * (angle / 2.).sin(), - (angle / 2.).cos(), - ) - } - - /// Calculate the dot product. - #[inline] - fn dot(&self, other: &Self) -> f64 { - self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3 - } - - /// Return the scaled quaternion by a factor. - #[inline] - fn scale(&self, factor: f64) -> Self { - Quaternion( - self.0 * factor, - self.1 * factor, - self.2 * factor, - self.3 * factor, - ) - } -} - -impl Animate for Quaternion { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let (this_weight, other_weight) = procedure.weights(); - debug_assert!( - // Doule EPSILON since both this_weight and other_weght have calculation errors - // which are approximately equal to EPSILON. - (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0 || - other_weight == 1.0f64 || - other_weight == 0.0f64, - "animate should only be used for interpolating or accumulating transforms" - ); - - // We take a specialized code path for accumulation (where other_weight - // is 1). - if let Procedure::Accumulate { .. } = procedure { - debug_assert_eq!(other_weight, 1.0); - if this_weight == 0.0 { - return Ok(*other); - } - - let clamped_w = self.3.min(1.0).max(-1.0); - - // Determine the scale factor. - let mut theta = clamped_w.acos(); - let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() }; - theta *= this_weight; - scale *= theta.sin(); - - // Scale the self matrix by this_weight. - let mut scaled_self = *self; - scaled_self.0 *= scale; - scaled_self.1 *= scale; - scaled_self.2 *= scale; - scaled_self.3 = theta.cos(); - - // Multiply scaled-self by other. - let a = &scaled_self; - let b = other; - return Ok(Quaternion( - a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1, - a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0, - a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3, - a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2, - )); - } - - // Straight from gfxQuaternion::Slerp. - // - // Dot product, clamped between -1 and 1. - let dot = (self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3) - .min(1.0) - .max(-1.0); - - if dot.abs() == 1.0 { - return Ok(*self); - } - - let theta = dot.acos(); - let rsintheta = 1.0 / (1.0 - dot * dot).sqrt(); - - let right_weight = (other_weight * theta).sin() * rsintheta; - let left_weight = (other_weight * theta).cos() - dot * right_weight; - - let left = self.scale(left_weight); - let right = other.scale(right_weight); - - Ok(Quaternion( - left.0 + right.0, - left.1 + right.1, - left.2 + right.2, - left.3 + right.3, - )) - } -} - -impl ComputeSquaredDistance for Quaternion { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors, - // so we can get their angle difference by: - // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2. - let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0; - Ok(SquaredDistance::from_sqrt(distance)) - } -} - -/// A decomposed 3d matrix. -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MatrixDecomposed3D { - /// A translation function. - pub translate: Translate3D, - /// A scale function. - pub scale: Scale3D, - /// The skew component of the transformation. - pub skew: Skew, - /// The perspective component of the transformation. - pub perspective: Perspective, - /// The quaternion used to represent the rotation. - pub quaternion: Quaternion, -} - -impl From<MatrixDecomposed3D> for Matrix3D { - /// Recompose a 3D matrix. - /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix> - fn from(decomposed: MatrixDecomposed3D) -> Matrix3D { - let mut matrix = Matrix3D::identity(); - - // Apply perspective - matrix.set_perspective(&decomposed.perspective); - - // Apply translation - matrix.apply_translate(&decomposed.translate); - - // Apply rotation - { - let x = decomposed.quaternion.0; - let y = decomposed.quaternion.1; - let z = decomposed.quaternion.2; - let w = decomposed.quaternion.3; - - // Construct a composite rotation matrix from the quaternion values - // rotationMatrix is a identity 4x4 matrix initially - let mut rotation_matrix = Matrix3D::identity(); - rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32; - rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32; - rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32; - rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32; - rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32; - rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32; - rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32; - rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32; - rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32; - - matrix = rotation_matrix.multiply(&matrix); - } - - // Apply skew - { - let mut temp = Matrix3D::identity(); - if decomposed.skew.2 != 0.0 { - temp.m32 = decomposed.skew.2; - matrix = temp.multiply(&matrix); - temp.m32 = 0.0; - } - - if decomposed.skew.1 != 0.0 { - temp.m31 = decomposed.skew.1; - matrix = temp.multiply(&matrix); - temp.m31 = 0.0; - } - - if decomposed.skew.0 != 0.0 { - temp.m21 = decomposed.skew.0; - matrix = temp.multiply(&matrix); - } - } - - // Apply scale - matrix.apply_scale(&decomposed.scale); - - matrix - } -} - -/// Decompose a 3D matrix. -/// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix -/// http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c -fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()> { - // Combine 2 point. - let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| { - [ - (ascl * a[0]) + (bscl * b[0]), - (ascl * a[1]) + (bscl * b[1]), - (ascl * a[2]) + (bscl * b[2]), - ] - }; - // Dot product. - let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; - // Cross product. - let cross = |row1: [f32; 3], row2: [f32; 3]| { - [ - row1[1] * row2[2] - row1[2] * row2[1], - row1[2] * row2[0] - row1[0] * row2[2], - row1[0] * row2[1] - row1[1] * row2[0], - ] - }; - - if matrix.m44 == 0.0 { - return Err(()); - } - - let scaling_factor = matrix.m44; - - // Normalize the matrix. - matrix.scale_by_factor(1.0 / scaling_factor); - - // perspective_matrix is used to solve for perspective, but it also provides - // an easy way to test for singularity of the upper 3x3 component. - let mut perspective_matrix = matrix; - - perspective_matrix.m14 = 0.0; - perspective_matrix.m24 = 0.0; - perspective_matrix.m34 = 0.0; - perspective_matrix.m44 = 1.0; - - if perspective_matrix.determinant() == 0.0 { - return Err(()); - } - - // First, isolate perspective. - let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 { - let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44]; - - perspective_matrix = perspective_matrix.inverse().unwrap().transpose(); - let perspective = perspective_matrix.pre_mul_point4(&right_hand_side); - // NOTE(emilio): Even though the reference algorithm clears the - // fourth column here (matrix.m14..matrix.m44), they're not used below - // so it's not really needed. - Perspective( - perspective[0], - perspective[1], - perspective[2], - perspective[3], - ) - } else { - Perspective(0.0, 0.0, 0.0, 1.0) - }; - - // Next take care of translation (easy). - let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43); - - // Now get scale and shear. 'row' is a 3 element array of 3 component vectors - let mut row = matrix.get_matrix_3x3_part(); - - // Compute X scale factor and normalize first row. - let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt(); - let mut scale = Scale3D(row0len, 0.0, 0.0); - row[0] = [ - row[0][0] / row0len, - row[0][1] / row0len, - row[0][2] / row0len, - ]; - - // Compute XY shear factor and make 2nd row orthogonal to 1st. - let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0); - row[1] = combine(row[1], row[0], 1.0, -skew.0); - - // Now, compute Y scale and normalize 2nd row. - let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt(); - scale.1 = row1len; - row[1] = [ - row[1][0] / row1len, - row[1][1] / row1len, - row[1][2] / row1len, - ]; - skew.0 /= scale.1; - - // Compute XZ and YZ shears, orthogonalize 3rd row - skew.1 = dot(row[0], row[2]); - row[2] = combine(row[2], row[0], 1.0, -skew.1); - skew.2 = dot(row[1], row[2]); - row[2] = combine(row[2], row[1], 1.0, -skew.2); - - // Next, get Z scale and normalize 3rd row. - let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt(); - scale.2 = row2len; - row[2] = [ - row[2][0] / row2len, - row[2][1] / row2len, - row[2][2] / row2len, - ]; - skew.1 /= scale.2; - skew.2 /= scale.2; - - // At this point, the matrix (in rows) is orthonormal. - // Check for a coordinate system flip. If the determinant - // is -1, then negate the matrix and the scaling factors. - if dot(row[0], cross(row[1], row[2])) < 0.0 { - scale.negate(); - for i in 0..3 { - row[i][0] *= -1.0; - row[i][1] *= -1.0; - row[i][2] *= -1.0; - } - } - - // Now, get the rotations out. - let mut quaternion = Quaternion( - 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), - 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), - 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(), - 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(), - ); - - if row[2][1] > row[1][2] { - quaternion.0 = -quaternion.0 - } - if row[0][2] > row[2][0] { - quaternion.1 = -quaternion.1 - } - if row[1][0] > row[0][1] { - quaternion.2 = -quaternion.2 - } - - Ok(MatrixDecomposed3D { - translate, - scale, - skew, - perspective, - quaternion, - }) -} - -/// Decompose a 2D matrix for Gecko. -// Use the algorithm from nsStyleTransformMatrix::Decompose2DMatrix() in Gecko. -#[cfg(feature = "gecko")] -fn decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()> { - // The index is column-major, so the equivalent transform matrix is: - // | m11 m21 0 m41 | => | m11 m21 | and translate(m41, m42) - // | m12 m22 0 m42 | | m12 m22 | - // | 0 0 1 0 | - // | 0 0 0 1 | - let (mut m11, mut m12) = (matrix.m11, matrix.m12); - let (mut m21, mut m22) = (matrix.m21, matrix.m22); - // Check if this is a singular matrix. - if m11 * m22 == m12 * m21 { - return Err(()); - } - - let mut scale_x = (m11 * m11 + m12 * m12).sqrt(); - m11 /= scale_x; - m12 /= scale_x; - - let mut shear_xy = m11 * m21 + m12 * m22; - m21 -= m11 * shear_xy; - m22 -= m12 * shear_xy; - - let scale_y = (m21 * m21 + m22 * m22).sqrt(); - m21 /= scale_y; - m22 /= scale_y; - shear_xy /= scale_y; - - let determinant = m11 * m22 - m12 * m21; - // Determinant should now be 1 or -1. - if 0.99 > determinant.abs() || determinant.abs() > 1.01 { - return Err(()); - } - - if determinant < 0. { - m11 = -m11; - m12 = -m12; - shear_xy = -shear_xy; - scale_x = -scale_x; - } - - Ok(MatrixDecomposed3D { - translate: Translate3D(matrix.m41, matrix.m42, 0.), - scale: Scale3D(scale_x, scale_y, 1.), - skew: Skew(shear_xy, 0., 0.), - perspective: Perspective(0., 0., 0., 1.), - quaternion: Quaternion::from_direction_and_angle( - &DirectionVector::new(0., 0., 1.), - m12.atan2(m11) as f64, - ), - }) -} - -impl Animate for Matrix3D { - #[cfg(feature = "servo")] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if self.is_3d() || other.is_3d() { - let decomposed_from = decompose_3d_matrix(*self); - let decomposed_to = decompose_3d_matrix(*other); - match (decomposed_from, decomposed_to) { - (Ok(this), Ok(other)) => Ok(Matrix3D::from(this.animate(&other, procedure)?)), - // Matrices can be undecomposable due to couple reasons, e.g., - // non-invertible matrices. In this case, we should report Err - // here, and let the caller do the fallback procedure. - _ => Err(()), - } - } else { - let this = MatrixDecomposed2D::from(*self); - let other = MatrixDecomposed2D::from(*other); - Ok(Matrix3D::from(this.animate(&other, procedure)?)) - } - } - - #[cfg(feature = "gecko")] - // Gecko doesn't exactly follow the spec here; we use a different procedure - // to match it - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let (from, to) = if self.is_3d() || other.is_3d() { - (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?) - } else { - (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?) - }; - // Matrices can be undecomposable due to couple reasons, e.g., - // non-invertible matrices. In this case, we should report Err here, - // and let the caller do the fallback procedure. - Ok(Matrix3D::from(from.animate(&to, procedure)?)) - } -} - -impl ComputeSquaredDistance for Matrix3D { - #[inline] - #[cfg(feature = "servo")] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - if self.is_3d() || other.is_3d() { - let from = decompose_3d_matrix(*self)?; - let to = decompose_3d_matrix(*other)?; - from.compute_squared_distance(&to) - } else { - let from = MatrixDecomposed2D::from(*self); - let to = MatrixDecomposed2D::from(*other); - from.compute_squared_distance(&to) - } - } - - #[inline] - #[cfg(feature = "gecko")] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let (from, to) = if self.is_3d() || other.is_3d() { - (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?) - } else { - (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?) - }; - from.compute_squared_distance(&to) - } -} - -// ------------------------------------ -// Animation for Transform list. -// ------------------------------------ -fn is_matched_operation( - first: &ComputedTransformOperation, - second: &ComputedTransformOperation, -) -> bool { - match (first, second) { - (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | - (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..)) | - (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | - (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..)) | - (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..)) | - (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | - (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..)) | - (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..)) | - (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..)) | - (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..)) | - (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true, - // Match functions that have the same primitive transform function - (a, b) if a.is_translate() && b.is_translate() => true, - (a, b) if a.is_scale() && b.is_scale() => true, - (a, b) if a.is_rotate() && b.is_rotate() => true, - // InterpolateMatrix and AccumulateMatrix are for mismatched transforms - _ => false, - } -} - -/// <https://drafts.csswg.org/css-transforms/#interpolation-of-transforms> -impl Animate for ComputedTransform { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - use std::borrow::Cow; - - // Addition for transforms simply means appending to the list of - // transform functions. This is different to how we handle the other - // animation procedures so we treat it separately here rather than - // handling it in TransformOperation. - if procedure == Procedure::Add { - let result = self.0.iter().chain(&*other.0).cloned().collect(); - return Ok(Transform(result)); - } - - let this = Cow::Borrowed(&self.0); - let other = Cow::Borrowed(&other.0); - - // Interpolate the common prefix - let mut result = this - .iter() - .zip(other.iter()) - .take_while(|(this, other)| is_matched_operation(this, other)) - .map(|(this, other)| this.animate(other, procedure)) - .collect::<Result<Vec<_>, _>>()?; - - // Deal with the remainders - let this_remainder = if this.len() > result.len() { - Some(&this[result.len()..]) - } else { - None - }; - let other_remainder = if other.len() > result.len() { - Some(&other[result.len()..]) - } else { - None - }; - - match (this_remainder, other_remainder) { - // If there is a remainder from *both* lists we must have had mismatched functions. - // => Add the remainders to a suitable ___Matrix function. - (Some(this_remainder), Some(other_remainder)) => { - result.push(TransformOperation::animate_mismatched_transforms( - this_remainder, - other_remainder, - procedure, - )?); - }, - // If there is a remainder from just one list, then one list must be shorter but - // completely match the type of the corresponding functions in the longer list. - // => Interpolate the remainder with identity transforms. - (Some(remainder), None) | (None, Some(remainder)) => { - let fill_right = this_remainder.is_some(); - result.append( - &mut remainder - .iter() - .map(|transform| { - let identity = transform.to_animated_zero().unwrap(); - - match transform { - TransformOperation::AccumulateMatrix { .. } | - TransformOperation::InterpolateMatrix { .. } => { - let (from, to) = if fill_right { - (transform, &identity) - } else { - (&identity, transform) - }; - - TransformOperation::animate_mismatched_transforms( - &[from.clone()], - &[to.clone()], - procedure, - ) - }, - _ => { - let (lhs, rhs) = if fill_right { - (transform, &identity) - } else { - (&identity, transform) - }; - lhs.animate(rhs, procedure) - }, - } - }) - .collect::<Result<Vec<_>, _>>()?, - ); - }, - (None, None) => {}, - } - - Ok(Transform(result.into())) - } -} - -impl ComputeSquaredDistance for ComputedTransform { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let squared_dist = super::lists::with_zero::squared_distance(&self.0, &other.0); - - // Roll back to matrix interpolation if there is any Err(()) in the - // transform lists, such as mismatched transform functions. - // - // FIXME: Using a zero size here seems a bit sketchy but matches the - // previous behavior. - if squared_dist.is_err() { - let rect = euclid::Rect::zero(); - let matrix1: Matrix3D = self.to_transform_3d_matrix(Some(&rect))?.0.into(); - let matrix2: Matrix3D = other.to_transform_3d_matrix(Some(&rect))?.0.into(); - return matrix1.compute_squared_distance(&matrix2); - } - - squared_dist - } -} - -/// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms> -impl Animate for ComputedTransformOperation { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self, other) { - (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => { - Ok(TransformOperation::Matrix3D( - this.animate(other, procedure)?, - )) - }, - (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => { - Ok(TransformOperation::Matrix(this.animate(other, procedure)?)) - }, - ( - &TransformOperation::Skew(ref fx, ref fy), - &TransformOperation::Skew(ref tx, ref ty), - ) => Ok(TransformOperation::Skew( - fx.animate(tx, procedure)?, - fy.animate(ty, procedure)?, - )), - (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) => { - Ok(TransformOperation::SkewX(f.animate(t, procedure)?)) - }, - (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => { - Ok(TransformOperation::SkewY(f.animate(t, procedure)?)) - }, - ( - &TransformOperation::Translate3D(ref fx, ref fy, ref fz), - &TransformOperation::Translate3D(ref tx, ref ty, ref tz), - ) => Ok(TransformOperation::Translate3D( - fx.animate(tx, procedure)?, - fy.animate(ty, procedure)?, - fz.animate(tz, procedure)?, - )), - ( - &TransformOperation::Translate(ref fx, ref fy), - &TransformOperation::Translate(ref tx, ref ty), - ) => Ok(TransformOperation::Translate( - fx.animate(tx, procedure)?, - fy.animate(ty, procedure)?, - )), - (&TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t)) => { - Ok(TransformOperation::TranslateX(f.animate(t, procedure)?)) - }, - (&TransformOperation::TranslateY(ref f), &TransformOperation::TranslateY(ref t)) => { - Ok(TransformOperation::TranslateY(f.animate(t, procedure)?)) - }, - (&TransformOperation::TranslateZ(ref f), &TransformOperation::TranslateZ(ref t)) => { - Ok(TransformOperation::TranslateZ(f.animate(t, procedure)?)) - }, - ( - &TransformOperation::Scale3D(ref fx, ref fy, ref fz), - &TransformOperation::Scale3D(ref tx, ref ty, ref tz), - ) => Ok(TransformOperation::Scale3D( - animate_multiplicative_factor(*fx, *tx, procedure)?, - animate_multiplicative_factor(*fy, *ty, procedure)?, - animate_multiplicative_factor(*fz, *tz, procedure)?, - )), - (&TransformOperation::ScaleX(ref f), &TransformOperation::ScaleX(ref t)) => Ok( - TransformOperation::ScaleX(animate_multiplicative_factor(*f, *t, procedure)?), - ), - (&TransformOperation::ScaleY(ref f), &TransformOperation::ScaleY(ref t)) => Ok( - TransformOperation::ScaleY(animate_multiplicative_factor(*f, *t, procedure)?), - ), - (&TransformOperation::ScaleZ(ref f), &TransformOperation::ScaleZ(ref t)) => Ok( - TransformOperation::ScaleZ(animate_multiplicative_factor(*f, *t, procedure)?), - ), - ( - &TransformOperation::Scale(ref fx, ref fy), - &TransformOperation::Scale(ref tx, ref ty), - ) => Ok(TransformOperation::Scale( - animate_multiplicative_factor(*fx, *tx, procedure)?, - animate_multiplicative_factor(*fy, *ty, procedure)?, - )), - ( - &TransformOperation::Rotate3D(fx, fy, fz, fa), - &TransformOperation::Rotate3D(tx, ty, tz, ta), - ) => { - let animated = Rotate::Rotate3D(fx, fy, fz, fa) - .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?; - let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated); - Ok(TransformOperation::Rotate3D(fx, fy, fz, fa)) - }, - (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) => { - Ok(TransformOperation::RotateX(fa.animate(&ta, procedure)?)) - }, - (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) => { - Ok(TransformOperation::RotateY(fa.animate(&ta, procedure)?)) - }, - (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) => { - Ok(TransformOperation::RotateZ(fa.animate(&ta, procedure)?)) - }, - (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => { - Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?)) - }, - (&TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta)) => { - Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?)) - }, - (&TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta)) => { - Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?)) - }, - ( - &TransformOperation::Perspective(ref fd), - &TransformOperation::Perspective(ref td), - ) => { - use crate::values::computed::CSSPixelLength; - use crate::values::generics::transform::create_perspective_matrix; - - // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions: - // - // The transform functions matrix(), matrix3d() and - // perspective() get converted into 4x4 matrices first and - // interpolated as defined in section Interpolation of - // Matrices afterwards. - // - let from = create_perspective_matrix(fd.infinity_or(|l| l.px())); - let to = create_perspective_matrix(td.infinity_or(|l| l.px())); - - let interpolated = Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?; - - let decomposed = decompose_3d_matrix(interpolated)?; - let perspective_z = decomposed.perspective.2; - // Clamp results outside of the -1 to 0 range so that we get perspective - // function values between 1 and infinity. - let used_value = if perspective_z >= 0. { - transform::PerspectiveFunction::None - } else { - transform::PerspectiveFunction::Length(CSSPixelLength::new( - if perspective_z <= -1. { - 1. - } else { - -1. / perspective_z - }, - )) - }; - Ok(TransformOperation::Perspective(used_value)) - }, - _ if self.is_translate() && other.is_translate() => self - .to_translate_3d() - .animate(&other.to_translate_3d(), procedure), - _ if self.is_scale() && other.is_scale() => { - self.to_scale_3d().animate(&other.to_scale_3d(), procedure) - }, - _ if self.is_rotate() && other.is_rotate() => self - .to_rotate_3d() - .animate(&other.to_rotate_3d(), procedure), - _ => Err(()), - } - } -} - -impl ComputedTransformOperation { - /// If there are no size dependencies, we try to animate in-place, to avoid - /// creating deeply nested Interpolate* operations. - fn try_animate_mismatched_transforms_in_place( - left: &[Self], - right: &[Self], - procedure: Procedure, - ) -> Result<Self, ()> { - let (left, _left_3d) = Transform::components_to_transform_3d_matrix(left, None)?; - let (right, _right_3d) = Transform::components_to_transform_3d_matrix(right, None)?; - Ok(Self::Matrix3D( - Matrix3D::from(left).animate(&Matrix3D::from(right), procedure)?, - )) - } - - fn animate_mismatched_transforms( - left: &[Self], - right: &[Self], - procedure: Procedure, - ) -> Result<Self, ()> { - if let Ok(op) = Self::try_animate_mismatched_transforms_in_place(left, right, procedure) { - return Ok(op); - } - let from_list = Transform(left.to_vec().into()); - let to_list = Transform(right.to_vec().into()); - Ok(match procedure { - Procedure::Add => { - debug_assert!(false, "Addition should've been handled earlier"); - return Err(()); - }, - Procedure::Interpolate { progress } => Self::InterpolateMatrix { - from_list, - to_list, - progress: Percentage(progress as f32), - }, - Procedure::Accumulate { count } => Self::AccumulateMatrix { - from_list, - to_list, - count: cmp::min(count, i32::max_value() as u64) as i32, - }, - }) - } -} - -// This might not be the most useful definition of distance. It might be better, for example, -// to trace the distance travelled by a point as its transform is interpolated between the two -// lists. That, however, proves to be quite complicated so we take a simple approach for now. -// See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0. -impl ComputeSquaredDistance for ComputedTransformOperation { - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - match (self, other) { - (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => { - this.compute_squared_distance(other) - }, - (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => { - let this: Matrix3D = (*this).into(); - let other: Matrix3D = (*other).into(); - this.compute_squared_distance(&other) - }, - ( - &TransformOperation::Skew(ref fx, ref fy), - &TransformOperation::Skew(ref tx, ref ty), - ) => Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)?), - (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) | - (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => { - f.compute_squared_distance(&t) - }, - ( - &TransformOperation::Translate3D(ref fx, ref fy, ref fz), - &TransformOperation::Translate3D(ref tx, ref ty, ref tz), - ) => { - // For translate, We don't want to require doing layout in order - // to calculate the result, so drop the percentage part. - // - // However, dropping percentage makes us impossible to compute - // the distance for the percentage-percentage case, but Gecko - // uses the same formula, so it's fine for now. - let basis = Length::new(0.); - let fx = fx.resolve(basis).px(); - let fy = fy.resolve(basis).px(); - let tx = tx.resolve(basis).px(); - let ty = ty.resolve(basis).px(); - - Ok(fx.compute_squared_distance(&tx)? + - fy.compute_squared_distance(&ty)? + - fz.compute_squared_distance(&tz)?) - }, - ( - &TransformOperation::Scale3D(ref fx, ref fy, ref fz), - &TransformOperation::Scale3D(ref tx, ref ty, ref tz), - ) => Ok(fx.compute_squared_distance(&tx)? + - fy.compute_squared_distance(&ty)? + - fz.compute_squared_distance(&tz)?), - ( - &TransformOperation::Rotate3D(fx, fy, fz, fa), - &TransformOperation::Rotate3D(tx, ty, tz, ta), - ) => Rotate::Rotate3D(fx, fy, fz, fa) - .compute_squared_distance(&Rotate::Rotate3D(tx, ty, tz, ta)), - (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) | - (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) | - (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) | - (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => { - fa.compute_squared_distance(&ta) - }, - ( - &TransformOperation::Perspective(ref fd), - &TransformOperation::Perspective(ref td), - ) => fd - .infinity_or(|l| l.px()) - .compute_squared_distance(&td.infinity_or(|l| l.px())), - (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) | - (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => { - // FIXME(emilio): Is this right? Why interpolating this with - // Perspective but not with anything else? - let mut p_matrix = Matrix3D::identity(); - let p = p.infinity_or(|p| p.px()); - if p >= 0. { - p_matrix.m34 = -1. / p.max(1.); - } - p_matrix.compute_squared_distance(&m) - }, - // Gecko cross-interpolates amongst all translate and all scale - // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp) - // without falling back to InterpolateMatrix - _ if self.is_translate() && other.is_translate() => self - .to_translate_3d() - .compute_squared_distance(&other.to_translate_3d()), - _ if self.is_scale() && other.is_scale() => self - .to_scale_3d() - .compute_squared_distance(&other.to_scale_3d()), - _ if self.is_rotate() && other.is_rotate() => self - .to_rotate_3d() - .compute_squared_distance(&other.to_rotate_3d()), - _ => Err(()), - } - } -} - -// ------------------------------------ -// Individual transforms. -// ------------------------------------ -/// <https://drafts.csswg.org/css-transforms-2/#propdef-rotate> -impl ComputedRotate { - fn resolve(&self) -> (Number, Number, Number, Angle) { - // According to the spec: - // https://drafts.csswg.org/css-transforms-2/#individual-transforms - // - // If the axis is unspecified, it defaults to "0 0 1" - match *self { - Rotate::None => (0., 0., 1., Angle::zero()), - Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle), - Rotate::Rotate(angle) => (0., 0., 1., angle), - } - } -} - -impl Animate for ComputedRotate { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self, other) { - (&Rotate::None, &Rotate::None) => Ok(Rotate::None), - (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => { - // We always normalize direction vector for rotate3d() first, so we should also - // apply the same rule for rotate property. In other words, we promote none into - // a 3d rotate, and normalize both direction vector first, and then do - // interpolation. - let (fx, fy, fz, fa) = transform::get_normalized_vector_and_angle(fx, fy, fz, fa); - Ok(Rotate::Rotate3D( - fx, - fy, - fz, - fa.animate(&Angle::zero(), procedure)?, - )) - }, - (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => { - // Normalize direction vector first. - let (tx, ty, tz, ta) = transform::get_normalized_vector_and_angle(tx, ty, tz, ta); - Ok(Rotate::Rotate3D( - tx, - ty, - tz, - Angle::zero().animate(&ta, procedure)?, - )) - }, - (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - let (mut fx, mut fy, mut fz, fa) = - transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); - let (mut tx, mut ty, mut tz, ta) = - transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); - - if fa == Angle::from_degrees(0.) { - fx = tx; - fy = ty; - fz = tz; - } else if ta == Angle::from_degrees(0.) { - tx = fx; - ty = fy; - tz = fz; - } - - if (fx, fy, fz) == (tx, ty, tz) { - return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?)); - } - - let fv = DirectionVector::new(fx, fy, fz); - let tv = DirectionVector::new(tx, ty, tz); - let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64()); - let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64()); - - let rq = Quaternion::animate(&fq, &tq, procedure)?; - let (x, y, z, angle) = transform::get_normalized_vector_and_angle( - rq.0 as f32, - rq.1 as f32, - rq.2 as f32, - rq.3.acos() as f32 * 2.0, - ); - - Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle))) - }, - (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => { - // If this is a 2D rotation, we just animate the <angle> - let (from, to) = (self.resolve().3, other.resolve().3); - Ok(Rotate::Rotate(from.animate(&to, procedure)?)) - }, - } - } -} - -impl ComputeSquaredDistance for ComputedRotate { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - match (self, other) { - (&Rotate::None, &Rotate::None) => Ok(SquaredDistance::from_sqrt(0.)), - (&Rotate::Rotate3D(_, _, _, a), &Rotate::None) | - (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => { - a.compute_squared_distance(&Angle::zero()) - }, - (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - let (mut fx, mut fy, mut fz, angle1) = - transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); - let (mut tx, mut ty, mut tz, angle2) = - transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); - - if angle1 == Angle::zero() { - fx = tx; - fy = ty; - fz = tz; - } else if angle2 == Angle::zero() { - tx = fx; - ty = fy; - tz = fz; - } - - if (fx, fy, fz) == (tx, ty, tz) { - angle1.compute_squared_distance(&angle2) - } else { - let v1 = DirectionVector::new(fx, fy, fz); - let v2 = DirectionVector::new(tx, ty, tz); - let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64()); - let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64()); - q1.compute_squared_distance(&q2) - } - }, - (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => self - .resolve() - .3 - .compute_squared_distance(&other.resolve().3), - } - } -} - -/// <https://drafts.csswg.org/css-transforms-2/#propdef-translate> -impl ComputedTranslate { - fn resolve(&self) -> (LengthPercentage, LengthPercentage, Length) { - // According to the spec: - // https://drafts.csswg.org/css-transforms-2/#individual-transforms - // - // Unspecified translations default to 0px - match *self { - Translate::None => ( - LengthPercentage::zero(), - LengthPercentage::zero(), - Length::zero(), - ), - Translate::Translate(ref tx, ref ty, ref tz) => (tx.clone(), ty.clone(), tz.clone()), - } - } -} - -impl Animate for ComputedTranslate { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self, other) { - (&Translate::None, &Translate::None) => Ok(Translate::None), - (&Translate::Translate(_, ..), _) | (_, &Translate::Translate(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - Ok(Translate::Translate( - from.0.animate(&to.0, procedure)?, - from.1.animate(&to.1, procedure)?, - from.2.animate(&to.2, procedure)?, - )) - }, - } - } -} - -impl ComputeSquaredDistance for ComputedTranslate { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let (from, to) = (self.resolve(), other.resolve()); - Ok(from.0.compute_squared_distance(&to.0)? + - from.1.compute_squared_distance(&to.1)? + - from.2.compute_squared_distance(&to.2)?) - } -} - -/// <https://drafts.csswg.org/css-transforms-2/#propdef-scale> -impl ComputedScale { - fn resolve(&self) -> (Number, Number, Number) { - // According to the spec: - // https://drafts.csswg.org/css-transforms-2/#individual-transforms - // - // Unspecified scales default to 1 - match *self { - Scale::None => (1.0, 1.0, 1.0), - Scale::Scale(sx, sy, sz) => (sx, sy, sz), - } - } -} - -impl Animate for ComputedScale { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self, other) { - (&Scale::None, &Scale::None) => Ok(Scale::None), - (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - // For transform lists, we add by appending to the list of - // transform functions. However, ComputedScale cannot be - // simply concatenated, so we have to calculate the additive - // result here. - if procedure == Procedure::Add { - // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2) - return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1, from.2 * to.2)); - } - Ok(Scale::Scale( - animate_multiplicative_factor(from.0, to.0, procedure)?, - animate_multiplicative_factor(from.1, to.1, procedure)?, - animate_multiplicative_factor(from.2, to.2, procedure)?, - )) - }, - } - } -} - -impl ComputeSquaredDistance for ComputedScale { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let (from, to) = (self.resolve(), other.resolve()); - Ok(from.0.compute_squared_distance(&to.0)? + - from.1.compute_squared_distance(&to.1)? + - from.2.compute_squared_distance(&to.2)?) - } -} diff --git a/components/style/values/computed/align.rs b/components/style/values/computed/align.rs deleted file mode 100644 index 94847fd80f0..00000000000 --- a/components/style/values/computed/align.rs +++ /dev/null @@ -1,91 +0,0 @@ -/* 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/. */ - -//! Values for CSS Box Alignment properties -//! -//! https://drafts.csswg.org/css-align/ - -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::specified; - -pub use super::specified::{ - AlignContent, AlignItems, AlignTracks, ContentDistribution, JustifyContent, JustifyTracks, - SelfAlignment, -}; -pub use super::specified::{AlignSelf, JustifySelf}; - -/// The computed value for the `justify-items` property. -/// -/// Need to carry around both the specified and computed value to handle the -/// special legacy keyword without destroying style sharing. -/// -/// In particular, `justify-items` is a reset property, so we ought to be able -/// to share its computed representation across elements as long as they match -/// the same rules. Except that it's not true if the specified value for -/// `justify-items` is `legacy` and the computed value of the parent has the -/// `legacy` modifier. -/// -/// So instead of computing `legacy` "normally" looking at get_parent_position(), -/// marking it as uncacheable, we carry the specified value around and handle -/// the special case in `StyleAdjuster` instead, only when the result of the -/// computation would vary. -/// -/// Note that we also need to special-case this property in matching.rs, in -/// order to properly handle changes to the legacy keyword... This all kinda -/// sucks :(. -/// -/// See the discussion in https://bugzil.la/1384542. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] -#[repr(C)] -pub struct ComputedJustifyItems { - /// The specified value for the property. Can contain the bare `legacy` - /// keyword. - #[css(skip)] - pub specified: specified::JustifyItems, - /// The computed value for the property. Cannot contain the bare `legacy` - /// keyword, but note that it could contain it in combination with other - /// keywords like `left`, `right` or `center`. - pub computed: specified::JustifyItems, -} - -pub use self::ComputedJustifyItems as JustifyItems; - -impl JustifyItems { - /// Returns the `legacy` value. - pub fn legacy() -> Self { - Self { - specified: specified::JustifyItems::legacy(), - computed: specified::JustifyItems::normal(), - } - } -} - -impl ToComputedValue for specified::JustifyItems { - type ComputedValue = JustifyItems; - - /// <https://drafts.csswg.org/css-align/#valdef-justify-items-legacy> - fn to_computed_value(&self, _context: &Context) -> JustifyItems { - use crate::values::specified::align; - let specified = *self; - let computed = if self.0 != align::AlignFlags::LEGACY { - *self - } else { - // If the inherited value of `justify-items` includes the - // `legacy` keyword, `legacy` computes to the inherited value, but - // we assume it computes to `normal`, and handle that special-case - // in StyleAdjuster. - Self::normal() - }; - - JustifyItems { - specified, - computed, - } - } - - #[inline] - fn from_computed_value(computed: &JustifyItems) -> Self { - computed.specified - } -} diff --git a/components/style/values/computed/angle.rs b/components/style/values/computed/angle.rs deleted file mode 100644 index ea321d22337..00000000000 --- a/components/style/values/computed/angle.rs +++ /dev/null @@ -1,101 +0,0 @@ -/* 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/. */ - -//! Computed angles. - -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::CSSFloat; -use crate::Zero; -use std::f64::consts::PI; -use std::fmt::{self, Write}; -use std::{f32, f64}; -use style_traits::{CssWriter, ToCss}; - -/// A computed angle in degrees. -#[derive( - Add, - Animate, - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - PartialOrd, - Serialize, - ToAnimatedZero, - ToResolvedValue, -)] -#[repr(C)] -pub struct Angle(CSSFloat); - -impl ToCss for Angle { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.degrees().to_css(dest)?; - dest.write_str("deg") - } -} - -const RAD_PER_DEG: f64 = PI / 180.0; - -impl Angle { - /// Creates a computed `Angle` value from a radian amount. - pub fn from_radians(radians: CSSFloat) -> Self { - Angle(radians / RAD_PER_DEG as f32) - } - - /// Creates a computed `Angle` value from a degrees amount. - #[inline] - pub fn from_degrees(degrees: CSSFloat) -> Self { - Angle(degrees) - } - - /// Returns the amount of radians this angle represents. - #[inline] - pub fn radians(&self) -> CSSFloat { - self.radians64().min(f32::MAX as f64).max(f32::MIN as f64) as f32 - } - - /// Returns the amount of radians this angle represents as a `f64`. - /// - /// Gecko stores angles as singles, but does this computation using doubles. - /// - /// This is significant enough to mess up rounding to the nearest - /// quarter-turn for 225 degrees, for example. - #[inline] - pub fn radians64(&self) -> f64 { - self.0 as f64 * RAD_PER_DEG - } - - /// Return the value in degrees. - #[inline] - pub fn degrees(&self) -> CSSFloat { - self.0 - } -} - -impl Zero for Angle { - #[inline] - fn zero() -> Self { - Angle(0.0) - } - - #[inline] - fn is_zero(&self) -> bool { - self.0 == 0. - } -} - -impl ComputeSquaredDistance for Angle { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // Use the formula for calculating the distance between angles defined in SVG: - // https://www.w3.org/TR/SVG/animate.html#complexDistances - self.radians64() - .compute_squared_distance(&other.radians64()) - } -} diff --git a/components/style/values/computed/animation.rs b/components/style/values/computed/animation.rs deleted file mode 100644 index 81e702a3b65..00000000000 --- a/components/style/values/computed/animation.rs +++ /dev/null @@ -1,69 +0,0 @@ -/* 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/. */ - -//! Computed values for properties related to animations and transitions - -use crate::values::computed::{Context, LengthPercentage, ToComputedValue}; -use crate::values::generics::animation as generics; -use crate::values::specified::animation as specified; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -pub use crate::values::specified::animation::{ - AnimationName, ScrollAxis, ScrollTimelineName, TransitionProperty, -}; - -/// A computed value for the `animation-iteration-count` property. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem)] -#[repr(C)] -pub struct AnimationIterationCount(pub f32); - -impl ToComputedValue for specified::AnimationIterationCount { - type ComputedValue = AnimationIterationCount; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - AnimationIterationCount(match *self { - specified::AnimationIterationCount::Number(n) => n.to_computed_value(context).0, - specified::AnimationIterationCount::Infinite => f32::INFINITY, - }) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - use crate::values::specified::NonNegativeNumber; - if computed.0.is_infinite() { - specified::AnimationIterationCount::Infinite - } else { - specified::AnimationIterationCount::Number(NonNegativeNumber::new(computed.0)) - } - } -} - -impl AnimationIterationCount { - /// Returns the value `1.0`. - #[inline] - pub fn one() -> Self { - Self(1.0) - } -} - -impl ToCss for AnimationIterationCount { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.0.is_infinite() { - dest.write_str("infinite") - } else { - self.0.to_css(dest) - } - } -} - -/// A computed value for the `animation-timeline` property. -pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>; - -/// A computed value for the `view-timeline-inset` property. -pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>; diff --git a/components/style/values/computed/background.rs b/components/style/values/computed/background.rs deleted file mode 100644 index e2a58f8b74e..00000000000 --- a/components/style/values/computed/background.rs +++ /dev/null @@ -1,13 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values related to backgrounds. - -use crate::values::computed::length::NonNegativeLengthPercentage; -use crate::values::generics::background::BackgroundSize as GenericBackgroundSize; - -pub use crate::values::specified::background::BackgroundRepeat; - -/// A computed value for the `background-size` property. -pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthPercentage>; diff --git a/components/style/values/computed/basic_shape.rs b/components/style/values/computed/basic_shape.rs deleted file mode 100644 index fa30220157b..00000000000 --- a/components/style/values/computed/basic_shape.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* 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/. */ - -//! CSS handling for the computed value of -//! [`basic-shape`][basic-shape]s -//! -//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape - -use crate::values::computed::url::ComputedUrl; -use crate::values::computed::{Image, LengthPercentage, NonNegativeLengthPercentage}; -use crate::values::generics::basic_shape as generic; - -/// A computed alias for FillRule. -pub use crate::values::generics::basic_shape::FillRule; - -/// A computed `clip-path` value. -pub type ClipPath = generic::GenericClipPath<BasicShape, ComputedUrl>; - -/// A computed `shape-outside` value. -pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>; - -/// A computed basic shape. -pub type BasicShape = generic::GenericBasicShape< - LengthPercentage, - LengthPercentage, - LengthPercentage, - NonNegativeLengthPercentage, ->; - -/// The computed value of `inset()` -pub type InsetRect = generic::InsetRect<LengthPercentage, NonNegativeLengthPercentage>; - -/// A computed circle. -pub type Circle = generic::Circle<LengthPercentage, LengthPercentage, NonNegativeLengthPercentage>; - -/// A computed ellipse. -pub type Ellipse = - generic::Ellipse<LengthPercentage, LengthPercentage, NonNegativeLengthPercentage>; - -/// The computed value of `ShapeRadius` -pub type ShapeRadius = generic::GenericShapeRadius<NonNegativeLengthPercentage>; diff --git a/components/style/values/computed/border.rs b/components/style/values/computed/border.rs deleted file mode 100644 index e073f671b39..00000000000 --- a/components/style/values/computed/border.rs +++ /dev/null @@ -1,84 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values related to borders. - -use crate::values::computed::length::{NonNegativeLength, NonNegativeLengthPercentage}; -use crate::values::computed::{NonNegativeNumber, NonNegativeNumberOrPercentage}; -use crate::values::generics::border::BorderCornerRadius as GenericBorderCornerRadius; -use crate::values::generics::border::BorderImageSlice as GenericBorderImageSlice; -use crate::values::generics::border::BorderRadius as GenericBorderRadius; -use crate::values::generics::border::BorderSpacing as GenericBorderSpacing; -use crate::values::generics::border::GenericBorderImageSideWidth; -use crate::values::generics::rect::Rect; -use crate::values::generics::size::Size2D; -use crate::values::generics::NonNegative; -use crate::Zero; -use app_units::Au; - -pub use crate::values::specified::border::BorderImageRepeat; - -/// A computed value for -webkit-text-stroke-width. -pub type LineWidth = Au; - -/// A computed value for border-width (and the like). -pub type BorderSideWidth = Au; - -/// A computed value for the `border-image-width` property. -pub type BorderImageWidth = Rect<BorderImageSideWidth>; - -/// A computed value for a single side of a `border-image-width` property. -pub type BorderImageSideWidth = - GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>; - -/// A computed value for the `border-image-slice` property. -pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>; - -/// A computed value for the `border-radius` property. -pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>; - -/// A computed value for the `border-*-radius` longhand properties. -pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>; - -/// A computed value for the `border-spacing` longhand property. -pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>; - -impl BorderImageSideWidth { - /// Returns `1`. - #[inline] - pub fn one() -> Self { - GenericBorderImageSideWidth::Number(NonNegative(1.)) - } -} - -impl BorderImageSlice { - /// Returns the `100%` value. - #[inline] - pub fn hundred_percent() -> Self { - GenericBorderImageSlice { - offsets: Rect::all(NonNegativeNumberOrPercentage::hundred_percent()), - fill: false, - } - } -} - -impl BorderSpacing { - /// Returns `0 0`. - pub fn zero() -> Self { - GenericBorderSpacing(Size2D::new( - NonNegativeLength::zero(), - NonNegativeLength::zero(), - )) - } - - /// Returns the horizontal spacing. - pub fn horizontal(&self) -> Au { - Au::from(*self.0.width()) - } - - /// Returns the vertical spacing. - pub fn vertical(&self) -> Au { - Au::from(*self.0.height()) - } -} diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs deleted file mode 100644 index 5f1f68249da..00000000000 --- a/components/style/values/computed/box.rs +++ /dev/null @@ -1,268 +0,0 @@ -/* 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/. */ - -//! Computed types for box properties. - -use crate::values::animated::{Animate, Procedure}; -use crate::values::computed::length::{LengthPercentage, NonNegativeLength}; -use crate::values::computed::{Context, Integer, ToComputedValue}; -use crate::values::generics::box_::{ - GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, -}; -use crate::values::specified::box_ as specified; - -pub use crate::values::specified::box_::{ - Appearance, BaselineSource, BreakBetween, BreakWithin, Clear as SpecifiedClear, Contain, - ContainerName, ContainerType, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, - ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, WillChange, -}; - -/// A computed value for the `vertical-align` property. -pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>; - -/// A computed value for the `contain-intrinsic-size` property. -pub type ContainIntrinsicSize = GenericContainIntrinsicSize<NonNegativeLength>; - -impl ContainIntrinsicSize { - /// Converts contain-intrinsic-size to auto style. - pub fn add_auto_if_needed(&self) -> Option<Self> { - use crate::Zero; - // TODO: support contain-intrinsic-size: auto none, see - // https://bugzilla.mozilla.org/show_bug.cgi?id=1835813 - Some(match *self { - Self::None => Self::AutoLength(Zero::zero()), - Self::Length(ref l) => Self::AutoLength(*l), - Self::AutoLength(..) => return None, - }) - } -} - -/// A computed value for the `line-clamp` property. -pub type LineClamp = GenericLineClamp<Integer>; - -impl Animate for LineClamp { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if self.is_none() != other.is_none() { - return Err(()); - } - if self.is_none() { - return Ok(Self::none()); - } - Ok(Self(self.0.animate(&other.0, procedure)?.max(1))) - } -} - -/// A computed value for the `perspective` property. -pub type Perspective = GenericPerspective<NonNegativeLength>; - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToResolvedValue, -)] -#[repr(u8)] -/// A computed value for the `float` property. -pub enum Float { - Left, - Right, - None, -} - -impl Float { - /// Returns true if `self` is not `None`. - pub fn is_floating(self) -> bool { - self != Self::None - } -} - -impl ToComputedValue for SpecifiedFloat { - type ComputedValue = Float; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - let ltr = context.style().writing_mode.is_bidi_ltr(); - // https://drafts.csswg.org/css-logical-props/#float-clear - match *self { - SpecifiedFloat::InlineStart => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if ltr { - Float::Left - } else { - Float::Right - } - }, - SpecifiedFloat::InlineEnd => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if ltr { - Float::Right - } else { - Float::Left - } - }, - SpecifiedFloat::Left => Float::Left, - SpecifiedFloat::Right => Float::Right, - SpecifiedFloat::None => Float::None, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> SpecifiedFloat { - match *computed { - Float::Left => SpecifiedFloat::Left, - Float::Right => SpecifiedFloat::Right, - Float::None => SpecifiedFloat::None, - } - } -} - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToResolvedValue, -)] -/// A computed value for the `clear` property. -#[repr(u8)] -pub enum Clear { - None, - Left, - Right, - Both, -} - -impl ToComputedValue for SpecifiedClear { - type ComputedValue = Clear; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - let ltr = context.style().writing_mode.is_bidi_ltr(); - // https://drafts.csswg.org/css-logical-props/#float-clear - match *self { - SpecifiedClear::InlineStart => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if ltr { - Clear::Left - } else { - Clear::Right - } - }, - SpecifiedClear::InlineEnd => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if ltr { - Clear::Right - } else { - Clear::Left - } - }, - SpecifiedClear::None => Clear::None, - SpecifiedClear::Left => Clear::Left, - SpecifiedClear::Right => Clear::Right, - SpecifiedClear::Both => Clear::Both, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> SpecifiedClear { - match *computed { - Clear::None => SpecifiedClear::None, - Clear::Left => SpecifiedClear::Left, - Clear::Right => SpecifiedClear::Right, - Clear::Both => SpecifiedClear::Both, - } - } -} - -/// A computed value for the `resize` property. -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, ToCss, ToResolvedValue)] -#[repr(u8)] -pub enum Resize { - None, - Both, - Horizontal, - Vertical, -} - -impl ToComputedValue for specified::Resize { - type ComputedValue = Resize; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Resize { - let is_vertical = context.style().writing_mode.is_vertical(); - match self { - specified::Resize::Inline => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if is_vertical { - Resize::Vertical - } else { - Resize::Horizontal - } - }, - specified::Resize::Block => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if is_vertical { - Resize::Horizontal - } else { - Resize::Vertical - } - }, - specified::Resize::None => Resize::None, - specified::Resize::Both => Resize::Both, - specified::Resize::Horizontal => Resize::Horizontal, - specified::Resize::Vertical => Resize::Vertical, - } - } - - #[inline] - fn from_computed_value(computed: &Resize) -> specified::Resize { - match computed { - Resize::None => specified::Resize::None, - Resize::Both => specified::Resize::Both, - Resize::Horizontal => specified::Resize::Horizontal, - Resize::Vertical => specified::Resize::Vertical, - } - } -} diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs deleted file mode 100644 index 35cfdb9710d..00000000000 --- a/components/style/values/computed/color.rs +++ /dev/null @@ -1,104 +0,0 @@ -/* 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/. */ - -//! Computed color values. - -use crate::color::AbsoluteColor; -use crate::values::animated::ToAnimatedZero; -use crate::values::computed::percentage::Percentage; -use crate::values::generics::color::{ - GenericCaretColor, GenericColor, GenericColorMix, GenericColorOrAuto, -}; -use cssparser::Color as CSSParserColor; -use std::fmt; -use style_traits::{CssWriter, ToCss}; - -pub use crate::values::specified::color::{ColorScheme, ForcedColorAdjust, PrintColorAdjust}; - -/// The computed value of the `color` property. -pub type ColorPropertyValue = AbsoluteColor; - -/// The computed value of `-moz-font-smoothing-background-color`. -pub type MozFontSmoothingBackgroundColor = AbsoluteColor; - -/// A computed value for `<color>`. -pub type Color = GenericColor<Percentage>; - -/// A computed color-mix(). -pub type ColorMix = GenericColorMix<Color, Percentage>; - -impl ToCss for Color { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - match *self { - Self::Absolute(ref c) => c.to_css(dest), - Self::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest), - Self::ColorMix(ref m) => m.to_css(dest), - } - } -} - -impl Color { - /// Create a new computed [`Color`] from a given color-mix, simplifying it to an absolute color - /// if possible. - pub fn from_color_mix(color_mix: ColorMix) -> Self { - if let Some(absolute) = color_mix.mix_to_absolute() { - Self::Absolute(absolute) - } else { - Self::ColorMix(Box::new(color_mix)) - } - } - - /// Returns a complex color value representing transparent. - pub fn transparent() -> Color { - Color::Absolute(AbsoluteColor::transparent()) - } - - /// Returns opaque black. - pub fn black() -> Color { - Color::Absolute(AbsoluteColor::black()) - } - - /// Returns opaque white. - pub fn white() -> Color { - Color::Absolute(AbsoluteColor::white()) - } - - /// Combine this complex color with the given foreground color into an - /// absolute color. - pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor { - use crate::values::specified::percentage::ToPercentage; - - match *self { - Self::Absolute(c) => c, - Self::CurrentColor => *current_color, - Self::ColorMix(ref mix) => { - let left = mix.left.resolve_to_absolute(current_color); - let right = mix.right.resolve_to_absolute(current_color); - crate::color::mix::mix( - mix.interpolation, - &left, - mix.left_percentage.to_percentage(), - &right, - mix.right_percentage.to_percentage(), - mix.normalize_weights, - ) - }, - } - } -} - -impl ToAnimatedZero for AbsoluteColor { - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(Self::transparent()) - } -} - -/// auto | <color> -pub type ColorOrAuto = GenericColorOrAuto<Color>; - -/// caret-color -pub type CaretColor = GenericCaretColor<Color>; diff --git a/components/style/values/computed/column.rs b/components/style/values/computed/column.rs deleted file mode 100644 index 38437ea1104..00000000000 --- a/components/style/values/computed/column.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* 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/. */ - -//! Computed types for the column properties. - -use crate::values::computed::PositiveInteger; -use crate::values::generics::column::ColumnCount as GenericColumnCount; - -/// A computed type for `column-count` values. -pub type ColumnCount = GenericColumnCount<PositiveInteger>; diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs deleted file mode 100644 index fd5e915c4a8..00000000000 --- a/components/style/values/computed/counters.rs +++ /dev/null @@ -1,26 +0,0 @@ -/* 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/. */ - -//! Computed values for counter properties - -use crate::values::computed::image::Image; -use crate::values::generics::counters as generics; -use crate::values::generics::counters::CounterIncrement as GenericCounterIncrement; -use crate::values::generics::counters::CounterReset as GenericCounterReset; -use crate::values::generics::counters::CounterSet as GenericCounterSet; - -/// A computed value for the `counter-increment` property. -pub type CounterIncrement = GenericCounterIncrement<i32>; - -/// A computed value for the `counter-reset` property. -pub type CounterReset = GenericCounterReset<i32>; - -/// A computed value for the `counter-set` property. -pub type CounterSet = GenericCounterSet<i32>; - -/// A computed value for the `content` property. -pub type Content = generics::GenericContent<Image>; - -/// A computed content item. -pub type ContentItem = generics::GenericContentItem<Image>; diff --git a/components/style/values/computed/easing.rs b/components/style/values/computed/easing.rs deleted file mode 100644 index d351b3c71d0..00000000000 --- a/components/style/values/computed/easing.rs +++ /dev/null @@ -1,109 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS Easing functions. - -use euclid::approxeq::ApproxEq; - -use crate::bezier::Bezier; -use crate::piecewise_linear::PiecewiseLinearFunction; -use crate::values::computed::{Integer, Number}; -use crate::values::generics::easing::{self, BeforeFlag, StepPosition, TimingKeyword}; - -/// A computed timing function. -pub type ComputedTimingFunction = easing::TimingFunction<Integer, Number, PiecewiseLinearFunction>; - -/// An alias of the computed timing function. -pub type TimingFunction = ComputedTimingFunction; - -impl ComputedTimingFunction { - fn calculate_step_output( - steps: i32, - pos: StepPosition, - progress: f64, - before_flag: BeforeFlag, - ) -> f64 { - // User specified values can cause overflow (bug 1706157). Increments/decrements - // should be gravefully handled. - let mut current_step = (progress * (steps as f64)).floor() as i32; - - // Increment current step if it is jump-start or start. - if pos == StepPosition::Start || - pos == StepPosition::JumpStart || - pos == StepPosition::JumpBoth - { - current_step = current_step.checked_add(1).unwrap_or(current_step); - } - - // If the "before flag" is set and we are at a transition point, - // drop back a step - if before_flag == BeforeFlag::Set && - (progress * steps as f64).rem_euclid(1.0).approx_eq(&0.0) - { - current_step = current_step.checked_sub(1).unwrap_or(current_step); - } - - // We should not produce a result outside [0, 1] unless we have an - // input outside that range. This takes care of steps that would otherwise - // occur at boundaries. - if progress >= 0.0 && current_step < 0 { - current_step = 0; - } - - // |jumps| should always be in [1, i32::MAX]. - let jumps = if pos == StepPosition::JumpBoth { - steps.checked_add(1).unwrap_or(steps) - } else if pos == StepPosition::JumpNone { - steps.checked_sub(1).unwrap_or(steps) - } else { - steps - }; - - if progress <= 1.0 && current_step > jumps { - current_step = jumps; - } - - (current_step as f64) / (jumps as f64) - } - - /// The output of the timing function given the progress ratio of this animation. - pub fn calculate_output(&self, progress: f64, before_flag: BeforeFlag, epsilon: f64) -> f64 { - let progress = match self { - TimingFunction::CubicBezier { x1, y1, x2, y2 } => { - Bezier::calculate_bezier_output(progress, epsilon, *x1, *y1, *x2, *y2) - }, - TimingFunction::Steps(steps, pos) => { - Self::calculate_step_output(*steps, *pos, progress, before_flag) - }, - TimingFunction::LinearFunction(function) => function.at(progress as f32).into(), - TimingFunction::Keyword(keyword) => match keyword { - TimingKeyword::Linear => progress, - TimingKeyword::Ease => { - Bezier::calculate_bezier_output(progress, epsilon, 0.25, 0.1, 0.25, 1.) - }, - TimingKeyword::EaseIn => { - Bezier::calculate_bezier_output(progress, epsilon, 0.42, 0., 1., 1.) - }, - TimingKeyword::EaseOut => { - Bezier::calculate_bezier_output(progress, epsilon, 0., 0., 0.58, 1.) - }, - TimingKeyword::EaseInOut => { - Bezier::calculate_bezier_output(progress, epsilon, 0.42, 0., 0.58, 1.) - }, - }, - }; - - // The output progress value of an easing function is a real number in the range: - // [-inf, inf]. - // https://drafts.csswg.org/css-easing-1/#output-progress-value - // - // However, we expect to use the finite progress for interpolation and web-animations - // https://drafts.csswg.org/css-values-4/#interpolation - // https://drafts.csswg.org/web-animations-1/#dom-computedeffecttiming-progress - // - // So we clamp the infinite progress, per the spec issue: - // https://github.com/w3c/csswg-drafts/issues/8344 - progress.min(f64::MAX).max(f64::MIN) - } -} diff --git a/components/style/values/computed/effects.rs b/components/style/values/computed/effects.rs deleted file mode 100644 index ce3498d1410..00000000000 --- a/components/style/values/computed/effects.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values related to effects. - -use crate::values::computed::color::Color; -use crate::values::computed::length::{Length, NonNegativeLength}; -#[cfg(feature = "gecko")] -use crate::values::computed::url::ComputedUrl; -use crate::values::computed::{Angle, NonNegativeNumber, ZeroToOneNumber}; -use crate::values::generics::effects::BoxShadow as GenericBoxShadow; -use crate::values::generics::effects::Filter as GenericFilter; -use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow; -#[cfg(not(feature = "gecko"))] -use crate::values::Impossible; - -/// A computed value for a single shadow of the `box-shadow` property. -pub type BoxShadow = GenericBoxShadow<Color, Length, NonNegativeLength, Length>; - -/// A computed value for a single `filter`. -#[cfg(feature = "gecko")] -pub type Filter = GenericFilter< - Angle, - NonNegativeNumber, - ZeroToOneNumber, - NonNegativeLength, - SimpleShadow, - ComputedUrl, ->; - -/// A computed value for a single `filter`. -#[cfg(feature = "servo")] -pub type Filter = GenericFilter< - Angle, - NonNegativeNumber, - ZeroToOneNumber, - NonNegativeLength, - SimpleShadow, - Impossible, ->; - -/// A computed value for the `drop-shadow()` filter. -pub type SimpleShadow = GenericSimpleShadow<Color, Length, NonNegativeLength>; diff --git a/components/style/values/computed/flex.rs b/components/style/values/computed/flex.rs deleted file mode 100644 index 95c497ecf63..00000000000 --- a/components/style/values/computed/flex.rs +++ /dev/null @@ -1,19 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values related to flexbox. - -use crate::values::computed::Size; -use crate::values::generics::flex::FlexBasis as GenericFlexBasis; - -/// A computed value for the `flex-basis` property. -pub type FlexBasis = GenericFlexBasis<Size>; - -impl FlexBasis { - /// `auto` - #[inline] - pub fn auto() -> Self { - GenericFlexBasis::Size(Size::auto()) - } -} diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs deleted file mode 100644 index 68a4391cf82..00000000000 --- a/components/style/values/computed/font.rs +++ /dev/null @@ -1,1281 +0,0 @@ -/* 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/. */ - -//! Computed values for font properties - -use crate::parser::{Parse, ParserContext}; -use crate::values::animated::ToAnimatedValue; -use crate::values::computed::{ - Angle, Context, Integer, Length, NonNegativeLength, NonNegativeNumber, Number, Percentage, - ToComputedValue, -}; -use crate::values::generics::font::{ - FeatureTagValue, FontSettings, TaggedFontValue, VariationValue, -}; -use crate::values::generics::{font as generics, NonNegative}; -use crate::values::specified::font::{ - self as specified, KeywordInfo, MAX_FONT_WEIGHT, MIN_FONT_WEIGHT, -}; -use crate::values::specified::length::{FontBaseSize, NoCalcLength}; -use crate::Atom; -use cssparser::{serialize_identifier, CssStringWriter, Parser}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -pub use crate::values::computed::Length as MozScriptMinSize; -pub use crate::values::specified::font::MozScriptSizeMultiplier; -pub use crate::values::specified::font::{FontPalette, FontSynthesis}; -pub use crate::values::specified::font::{ - FontVariantAlternates, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric, XLang, - XTextScale, -}; -pub use crate::values::specified::Integer as SpecifiedInteger; -pub use crate::values::specified::Number as SpecifiedNumber; - -/// Generic template for font property type classes that use a fixed-point -/// internal representation with `FRACTION_BITS` for the fractional part. -/// -/// Values are constructed from and exposed as floating-point, but stored -/// internally as fixed point, so there will be a quantization effect on -/// fractional values, depending on the number of fractional bits used. -/// -/// Using (16-bit) fixed-point types rather than floats for these style -/// attributes reduces the memory footprint of gfxFontEntry and gfxFontStyle; it -/// will also tend to reduce the number of distinct font instances that get -/// created, particularly when styles are animated or set to arbitrary values -/// (e.g. by sliders in the UI), which should reduce pressure on graphics -/// resources and improve cache hit rates. -/// -/// cbindgen:derive-lt -/// cbindgen:derive-lte -/// cbindgen:derive-gt -/// cbindgen:derive-gte -#[repr(C)] -#[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - PartialOrd, - ToResolvedValue, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -pub struct FixedPoint<T, const FRACTION_BITS: u16> { - value: T, -} - -impl<T, const FRACTION_BITS: u16> FixedPoint<T, FRACTION_BITS> -where - T: num_traits::cast::AsPrimitive<f32>, - f32: num_traits::cast::AsPrimitive<T>, -{ - const SCALE: u16 = 1 << FRACTION_BITS; - const INVERSE_SCALE: f32 = 1.0 / Self::SCALE as f32; - - /// Returns a fixed-point bit from a floating-point context. - fn from_float(v: f32) -> Self { - use num_traits::cast::AsPrimitive; - Self { - value: (v * Self::SCALE as f32).round().as_(), - } - } - - /// Returns the floating-point representation. - fn to_float(&self) -> f32 { - self.value.as_() * Self::INVERSE_SCALE - } -} - -/// font-weight: range 1..1000, fractional values permitted; keywords -/// 'normal', 'bold' aliased to 400, 700 respectively. -/// -/// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375) -pub const FONT_WEIGHT_FRACTION_BITS: u16 = 6; - -/// This is an alias which is useful mostly as a cbindgen / C++ inference -/// workaround. -pub type FontWeightFixedPoint = FixedPoint<u16, FONT_WEIGHT_FRACTION_BITS>; - -/// A value for the font-weight property per: -/// -/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight -/// -/// cbindgen:derive-lt -/// cbindgen:derive-lte -/// cbindgen:derive-gt -/// cbindgen:derive-gte -#[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Hash, - MallocSizeOf, - PartialEq, - PartialOrd, - ToResolvedValue, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(C)] -pub struct FontWeight(FontWeightFixedPoint); -impl ToAnimatedValue for FontWeight { - type AnimatedValue = Number; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.value() - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - FontWeight::from_float(animated) - } -} - -impl ToCss for FontWeight { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.value().to_css(dest) - } -} - -impl FontWeight { - /// The `normal` keyword. - pub const NORMAL: FontWeight = FontWeight(FontWeightFixedPoint { - value: 400 << FONT_WEIGHT_FRACTION_BITS, - }); - - /// The `bold` value. - pub const BOLD: FontWeight = FontWeight(FontWeightFixedPoint { - value: 700 << FONT_WEIGHT_FRACTION_BITS, - }); - - /// The threshold from which we consider a font bold. - pub const BOLD_THRESHOLD: FontWeight = FontWeight(FontWeightFixedPoint { - value: 600 << FONT_WEIGHT_FRACTION_BITS, - }); - - /// Returns the `normal` keyword value. - pub fn normal() -> Self { - Self::NORMAL - } - - /// Weither this weight is bold - pub fn is_bold(&self) -> bool { - *self >= Self::BOLD_THRESHOLD - } - - /// Returns the value as a float. - pub fn value(&self) -> f32 { - self.0.to_float() - } - - /// Construct a valid weight from a float value. - pub fn from_float(v: f32) -> Self { - Self(FixedPoint::from_float( - v.max(MIN_FONT_WEIGHT).min(MAX_FONT_WEIGHT), - )) - } - - /// Return the bolder weight. - /// - /// See the table in: - /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values - pub fn bolder(self) -> Self { - let value = self.value(); - if value < 350. { - return Self::NORMAL; - } - if value < 550. { - return Self::BOLD; - } - Self::from_float(value.max(900.)) - } - - /// Return the lighter weight. - /// - /// See the table in: - /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values - pub fn lighter(self) -> Self { - let value = self.value(); - if value < 550. { - return Self::from_float(value.min(100.)); - } - if value < 750. { - return Self::NORMAL; - } - Self::BOLD - } -} - -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedZero, - ToCss, - ToResolvedValue, -)] -#[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] -/// The computed value of font-size -pub struct FontSize { - /// The computed size, that we use to compute ems etc. This accounts for - /// e.g., text-zoom. - pub computed_size: NonNegativeLength, - /// The actual used size. This is the computed font size, potentially - /// constrained by other factors like minimum font-size settings and so on. - #[css(skip)] - pub used_size: NonNegativeLength, - /// If derived from a keyword, the keyword and additional transformations applied to it - #[css(skip)] - pub keyword_info: KeywordInfo, -} - -impl FontSize { - /// The actual computed font size. - #[inline] - pub fn computed_size(&self) -> Length { - self.computed_size.0 - } - - /// The actual used font size. - #[inline] - pub fn used_size(&self) -> Length { - self.used_size.0 - } - - #[inline] - /// Get default value of font size. - pub fn medium() -> Self { - Self { - computed_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)), - used_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)), - keyword_info: KeywordInfo::medium(), - } - } -} - -impl ToAnimatedValue for FontSize { - type AnimatedValue = Length; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.computed_size.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - FontSize { - computed_size: NonNegative(animated.clamp_to_non_negative()), - used_size: NonNegative(animated.clamp_to_non_negative()), - keyword_info: KeywordInfo::none(), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToResolvedValue)] -#[cfg_attr(feature = "servo", derive(Hash, MallocSizeOf, Serialize, Deserialize))] -/// Specifies a prioritized list of font family names or generic family names. -#[repr(C)] -pub struct FontFamily { - /// The actual list of family names. - pub families: FontFamilyList, - /// Whether this font-family came from a specified system-font. - pub is_system_font: bool, - /// Whether this is the initial font-family that might react to language - /// changes. - pub is_initial: bool, -} - -macro_rules! static_font_family { - ($ident:ident, $family:expr) => { - lazy_static! { - static ref $ident: FontFamily = FontFamily { - families: FontFamilyList { - #[cfg(feature = "gecko")] - list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)), - #[cfg(feature = "servo")] - list: Box::new([$family]), - }, - is_system_font: false, - is_initial: false, - }; - } - }; -} - -impl FontFamily { - #[inline] - /// Get default font family as `serif` which is a generic font-family - pub fn serif() -> Self { - Self::generic(GenericFontFamily::Serif).clone() - } - - /// Returns the font family for `-moz-bullet-font`. - #[cfg(feature = "gecko")] - pub(crate) fn moz_bullet() -> &'static Self { - static_font_family!( - MOZ_BULLET, - SingleFontFamily::FamilyName(FamilyName { - name: atom!("-moz-bullet-font"), - syntax: FontFamilyNameSyntax::Identifiers, - }) - ); - - &*MOZ_BULLET - } - - /// Returns a font family for a single system font. - #[cfg(feature = "gecko")] - pub fn for_system_font(name: &str) -> Self { - Self { - families: FontFamilyList { - list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName( - FamilyName { - name: Atom::from(name), - syntax: FontFamilyNameSyntax::Identifiers, - }, - ))), - }, - is_system_font: true, - is_initial: false, - } - } - - /// Returns a generic font family. - pub fn generic(generic: GenericFontFamily) -> &'static Self { - macro_rules! generic_font_family { - ($ident:ident, $family:ident) => { - static_font_family!( - $ident, - SingleFontFamily::Generic(GenericFontFamily::$family) - ) - }; - } - - generic_font_family!(SERIF, Serif); - generic_font_family!(SANS_SERIF, SansSerif); - generic_font_family!(MONOSPACE, Monospace); - generic_font_family!(CURSIVE, Cursive); - generic_font_family!(FANTASY, Fantasy); - #[cfg(feature = "gecko")] - generic_font_family!(MOZ_EMOJI, MozEmoji); - generic_font_family!(SYSTEM_UI, SystemUi); - - match generic { - GenericFontFamily::None => { - debug_assert!(false, "Bogus caller!"); - &*SERIF - }, - GenericFontFamily::Serif => &*SERIF, - GenericFontFamily::SansSerif => &*SANS_SERIF, - GenericFontFamily::Monospace => &*MONOSPACE, - GenericFontFamily::Cursive => &*CURSIVE, - GenericFontFamily::Fantasy => &*FANTASY, - #[cfg(feature = "gecko")] - GenericFontFamily::MozEmoji => &*MOZ_EMOJI, - GenericFontFamily::SystemUi => &*SYSTEM_UI, - } - } -} - -#[cfg(feature = "gecko")] -impl MallocSizeOf for FontFamily { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - use malloc_size_of::MallocUnconditionalSizeOf; - // SharedFontList objects are generally measured from the pointer stored - // in the specified value. So only count this if the SharedFontList is - // unshared. - let shared_font_list = &self.families.list; - if shared_font_list.is_unique() { - shared_font_list.unconditional_size_of(ops) - } else { - 0 - } - } -} - -impl ToCss for FontFamily { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - let mut iter = self.families.iter(); - match iter.next() { - Some(f) => f.to_css(dest)?, - None => { - #[cfg(feature = "gecko")] - return return Ok(()); - #[cfg(feature = "servo")] - unreachable!(); - }, - } - for family in iter { - dest.write_str(", ")?; - family.to_css(dest)?; - } - Ok(()) - } -} - -/// The name of a font family of choice. -#[derive( - Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(C)] -pub struct FamilyName { - /// Name of the font family. - pub name: Atom, - /// Syntax of the font family. - pub syntax: FontFamilyNameSyntax, -} - -#[cfg(feature = "gecko")] -impl FamilyName { - fn is_known_icon_font_family(&self) -> bool { - use crate::gecko_bindings::bindings; - unsafe { bindings::Gecko_IsKnownIconFontFamily(self.name.as_ptr()) } - } -} - -impl ToCss for FamilyName { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - match self.syntax { - FontFamilyNameSyntax::Quoted => { - dest.write_char('"')?; - write!(CssStringWriter::new(dest), "{}", self.name)?; - dest.write_char('"') - }, - FontFamilyNameSyntax::Identifiers => { - let mut first = true; - for ident in self.name.to_string().split(' ') { - if first { - first = false; - } else { - dest.write_char(' ')?; - } - debug_assert!( - !ident.is_empty(), - "Family name with leading, \ - trailing, or consecutive white spaces should \ - have been marked quoted by the parser" - ); - serialize_identifier(ident, dest)?; - } - Ok(()) - }, - } - } -} - -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -/// Font family names must either be given quoted as strings, -/// or unquoted as a sequence of one or more identifiers. -#[repr(u8)] -pub enum FontFamilyNameSyntax { - /// The family name was specified in a quoted form, e.g. "Font Name" - /// or 'Font Name'. - Quoted, - - /// The family name was specified in an unquoted form as a sequence of - /// identifiers. - Identifiers, -} - -/// A set of faces that vary in weight, width or slope. -/// cbindgen:derive-mut-casts=true -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize, Hash))] -#[repr(u8)] -pub enum SingleFontFamily { - /// The name of a font family of choice. - FamilyName(FamilyName), - /// Generic family name. - Generic(GenericFontFamily), -} - -fn system_ui_enabled(_: &ParserContext) -> bool { - static_prefs::pref!("layout.css.system-ui.enabled") -} - -/// A generic font-family name. -/// -/// The order here is important, if you change it make sure that -/// `gfxPlatformFontList.h`s ranged array and `gfxFontFamilyList`'s -/// sSingleGenerics are updated as well. -/// -/// NOTE(emilio): Should be u8, but it's a u32 because of ABI issues between GCC -/// and LLVM see https://bugs.llvm.org/show_bug.cgi?id=44228 / bug 1600735 / -/// bug 1726515. -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - Parse, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(u32)] -#[allow(missing_docs)] -pub enum GenericFontFamily { - /// No generic family specified, only for internal usage. - /// - /// NOTE(emilio): Gecko code relies on this variant being zero. - #[css(skip)] - None = 0, - Serif, - SansSerif, - #[parse(aliases = "-moz-fixed")] - Monospace, - Cursive, - Fantasy, - #[parse(condition = "system_ui_enabled")] - SystemUi, - /// An internal value for emoji font selection. - #[css(skip)] - #[cfg(feature = "gecko")] - MozEmoji, -} - -impl GenericFontFamily { - /// When we disallow websites to override fonts, we ignore some generic - /// families that the website might specify, since they're not configured by - /// the user. See bug 789788 and bug 1730098. - #[cfg(feature = "gecko")] - pub(crate) fn valid_for_user_font_prioritization(self) -> bool { - match self { - Self::None | Self::Fantasy | Self::Cursive | Self::SystemUi | Self::MozEmoji => false, - - Self::Serif | Self::SansSerif | Self::Monospace => true, - } - } -} - -impl Parse for SingleFontFamily { - /// Parse a font-family value. - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) { - return Ok(SingleFontFamily::FamilyName(FamilyName { - name: Atom::from(&*value), - syntax: FontFamilyNameSyntax::Quoted, - })); - } - - if let Ok(generic) = input.try_parse(|i| GenericFontFamily::parse(context, i)) { - return Ok(SingleFontFamily::Generic(generic)); - } - - let first_ident = input.expect_ident_cloned()?; - let reserved = match_ignore_ascii_case! { &first_ident, - // https://drafts.csswg.org/css-fonts/#propdef-font-family - // "Font family names that happen to be the same as a keyword value - // (`inherit`, `serif`, `sans-serif`, `monospace`, `fantasy`, and `cursive`) - // must be quoted to prevent confusion with the keywords with the same names. - // The keywords ‘initial’ and ‘default’ are reserved for future use - // and must also be quoted when used as font names. - // UAs must not consider these keywords as matching the <family-name> type." - "inherit" | "initial" | "unset" | "revert" | "default" => true, - _ => false, - }; - - let mut value = first_ident.as_ref().to_owned(); - let mut serialize_quoted = value.contains(' '); - - // These keywords are not allowed by themselves. - // The only way this value can be valid with with another keyword. - if reserved { - let ident = input.expect_ident()?; - serialize_quoted = serialize_quoted || ident.contains(' '); - value.push(' '); - value.push_str(&ident); - } - while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { - serialize_quoted = serialize_quoted || ident.contains(' '); - value.push(' '); - value.push_str(&ident); - } - let syntax = if serialize_quoted { - // For font family names which contains special white spaces, e.g. - // `font-family: \ a\ \ b\ \ c\ ;`, it is tricky to serialize them - // as identifiers correctly. Just mark them quoted so we don't need - // to worry about them in serialization code. - FontFamilyNameSyntax::Quoted - } else { - FontFamilyNameSyntax::Identifiers - }; - Ok(SingleFontFamily::FamilyName(FamilyName { - name: Atom::from(value), - syntax, - })) - } -} - -#[cfg(feature = "servo")] -impl SingleFontFamily { - /// Get the corresponding font-family with Atom - pub fn from_atom(input: Atom) -> SingleFontFamily { - match input { - atom!("serif") => return SingleFontFamily::Generic(GenericFontFamily::Serif), - atom!("sans-serif") => return SingleFontFamily::Generic(GenericFontFamily::SansSerif), - atom!("cursive") => return SingleFontFamily::Generic(GenericFontFamily::Cursive), - atom!("fantasy") => return SingleFontFamily::Generic(GenericFontFamily::Fantasy), - atom!("monospace") => return SingleFontFamily::Generic(GenericFontFamily::Monospace), - atom!("system-ui") => return SingleFontFamily::Generic(GenericFontFamily::SystemUi), - _ => {}, - } - - match_ignore_ascii_case! { &input, - "serif" => return SingleFontFamily::Generic(GenericFontFamily::Serif), - "sans-serif" => return SingleFontFamily::Generic(GenericFontFamily::SansSerif), - "cursive" => return SingleFontFamily::Generic(GenericFontFamily::Cursive), - "fantasy" => return SingleFontFamily::Generic(GenericFontFamily::Fantasy), - "monospace" => return SingleFontFamily::Generic(GenericFontFamily::Monospace), - "system-ui" => return SingleFontFamily::Generic(GenericFontFamily::SystemUi), - _ => {} - } - - // We don't know if it's quoted or not. So we set it to - // quoted by default. - SingleFontFamily::FamilyName(FamilyName { - name: input, - syntax: FontFamilyNameSyntax::Quoted, - }) - } -} - -/// A list of font families. -#[cfg(feature = "gecko")] -#[derive(Clone, Debug, ToComputedValue, ToResolvedValue, ToShmem, PartialEq, Eq)] -#[repr(C)] -pub struct FontFamilyList { - /// The actual list of font families specified. - pub list: crate::ArcSlice<SingleFontFamily>, -} - -/// A list of font families. -#[cfg(feature = "servo")] -#[derive( - Clone, - Debug, - Deserialize, - Eq, - Hash, - MallocSizeOf, - PartialEq, - Serialize, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct FontFamilyList { - /// The actual list of font families specified. - pub list: Box<[SingleFontFamily]>, -} - -impl FontFamilyList { - /// Return iterator of SingleFontFamily - pub fn iter(&self) -> impl Iterator<Item = &SingleFontFamily> { - self.list.iter() - } - - /// If there's a generic font family on the list which is suitable for user - /// font prioritization, then move it ahead of the other families in the list, - /// except for any families known to be ligature-based icon fonts, where using a - /// generic instead of the site's specified font may cause substantial breakage. - /// If no suitable generic is found in the list, insert the default generic ahead - /// of all the listed families except for known ligature-based icon fonts. - #[cfg(feature = "gecko")] - pub(crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { - let mut index_of_first_generic = None; - let mut target_index = None; - - for (i, f) in self.iter().enumerate() { - match &*f { - SingleFontFamily::Generic(f) => { - if index_of_first_generic.is_none() && f.valid_for_user_font_prioritization() { - // If we haven't found a target position, there's nothing to do; - // this entry is already ahead of everything except any whitelisted - // icon fonts. - if target_index.is_none() { - return; - } - index_of_first_generic = Some(i); - break; - } - // A non-prioritized generic (e.g. cursive, fantasy) becomes the target - // position for prioritization, just like arbitrary named families. - if target_index.is_none() { - target_index = Some(i); - } - }, - SingleFontFamily::FamilyName(fam) => { - // Target position for the first generic is in front of the first - // non-whitelisted icon font family we find. - if target_index.is_none() && !fam.is_known_icon_font_family() { - target_index = Some(i); - } - }, - } - } - - let mut new_list = self.list.iter().cloned().collect::<Vec<_>>(); - let first_generic = match index_of_first_generic { - Some(i) => new_list.remove(i), - None => SingleFontFamily::Generic(generic), - }; - - if let Some(i) = target_index { - new_list.insert(i, first_generic); - } else { - new_list.push(first_generic); - } - self.list = crate::ArcSlice::from_iter(new_list.into_iter()); - } - - /// Returns whether we need to prioritize user fonts. - #[cfg(feature = "gecko")] - pub(crate) fn needs_user_font_prioritization(&self) -> bool { - self.iter().next().map_or(true, |f| match f { - SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), - _ => true, - }) - } - - /// Return the generic ID if it is a single generic font - pub fn single_generic(&self) -> Option<GenericFontFamily> { - let mut iter = self.iter(); - if let Some(SingleFontFamily::Generic(f)) = iter.next() { - if iter.next().is_none() { - return Some(*f); - } - } - None - } -} - -/// Preserve the readability of text when font fallback occurs -pub type FontSizeAdjust = generics::GenericFontSizeAdjust<NonNegativeNumber>; - -impl FontSizeAdjust { - #[inline] - /// Default value of font-size-adjust - pub fn none() -> Self { - FontSizeAdjust::None - } -} - -/// Use FontSettings as computed type of FontFeatureSettings. -pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>; - -/// The computed value for font-variation-settings. -pub type FontVariationSettings = FontSettings<VariationValue<Number>>; - -// The computed value of font-{feature,variation}-settings discards values -// with duplicate tags, keeping only the last occurrence of each tag. -fn dedup_font_settings<T>(settings_list: &mut Vec<T>) -where - T: TaggedFontValue, -{ - if settings_list.len() > 1 { - settings_list.sort_by_key(|k| k.tag().0); - // dedup() keeps the first of any duplicates, but we want the last, - // so we implement it manually here. - let mut prev_tag = settings_list.last().unwrap().tag(); - for i in (0..settings_list.len() - 1).rev() { - let cur_tag = settings_list[i].tag(); - if cur_tag == prev_tag { - settings_list.remove(i); - } - prev_tag = cur_tag; - } - } -} - -impl<T> ToComputedValue for FontSettings<T> -where - T: ToComputedValue, - <T as ToComputedValue>::ComputedValue: TaggedFontValue, -{ - type ComputedValue = FontSettings<T::ComputedValue>; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - let mut v = self - .0 - .iter() - .map(|item| item.to_computed_value(context)) - .collect::<Vec<_>>(); - dedup_font_settings(&mut v); - FontSettings(v.into_boxed_slice()) - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self( - computed - .0 - .iter() - .map(T::from_computed_value) - .collect::<Vec<_>>() - .into_boxed_slice(), - ) - } -} - -/// font-language-override can only have a single 1-4 ASCII character -/// OpenType "language system" tag, so we should be able to compute -/// it and store it as a 32-bit integer -/// (see http://www.microsoft.com/typography/otspec/languagetags.htm). -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -#[value_info(other_values = "normal")] -pub struct FontLanguageOverride(pub u32); - -impl FontLanguageOverride { - #[inline] - /// Get computed default value of `font-language-override` with 0 - pub fn normal() -> FontLanguageOverride { - FontLanguageOverride(0) - } - - /// Returns this value as a `&str`, backed by `storage`. - #[inline] - pub(crate) fn to_str(self, storage: &mut [u8; 4]) -> &str { - *storage = u32::to_be_bytes(self.0); - // Safe because we ensure it's ASCII during parsing - let slice = if cfg!(debug_assertions) { - std::str::from_utf8(&storage[..]).unwrap() - } else { - unsafe { std::str::from_utf8_unchecked(&storage[..]) } - }; - slice.trim_end() - } - - /// Unsafe because `Self::to_str` requires the value to represent a UTF-8 - /// string. - #[inline] - pub unsafe fn from_u32(value: u32) -> Self { - Self(value) - } -} - -impl ToCss for FontLanguageOverride { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - if self.0 == 0 { - return dest.write_str("normal"); - } - self.to_str(&mut [0; 4]).to_css(dest) - } -} - -// FIXME(emilio): Make Gecko use the cbindgen'd fontLanguageOverride, then -// remove this. -#[cfg(feature = "gecko")] -impl From<u32> for FontLanguageOverride { - fn from(v: u32) -> Self { - unsafe { Self::from_u32(v) } - } -} - -#[cfg(feature = "gecko")] -impl From<FontLanguageOverride> for u32 { - fn from(v: FontLanguageOverride) -> u32 { - v.0 - } -} - -impl ToComputedValue for specified::MozScriptMinSize { - type ComputedValue = MozScriptMinSize; - - fn to_computed_value(&self, cx: &Context) -> MozScriptMinSize { - // this value is used in the computation of font-size, so - // we use the parent size - let base_size = FontBaseSize::InheritedStyle; - match self.0 { - NoCalcLength::FontRelative(value) => value.to_computed_value(cx, base_size), - NoCalcLength::ServoCharacterWidth(value) => { - value.to_computed_value(base_size.resolve(cx).computed_size()) - }, - ref l => l.to_computed_value(cx), - } - } - - fn from_computed_value(other: &MozScriptMinSize) -> Self { - specified::MozScriptMinSize(ToComputedValue::from_computed_value(other)) - } -} - -/// The computed value of the math-depth property. -pub type MathDepth = i8; - -#[cfg(feature = "gecko")] -impl ToComputedValue for specified::MathDepth { - type ComputedValue = MathDepth; - - fn to_computed_value(&self, cx: &Context) -> i8 { - use crate::properties::longhands::math_style::SpecifiedValue as MathStyleValue; - use std::{cmp, i8}; - - let int = match *self { - specified::MathDepth::AutoAdd => { - let parent = cx.builder.get_parent_font().clone_math_depth() as i32; - let style = cx.builder.get_parent_font().clone_math_style(); - if style == MathStyleValue::Compact { - parent.saturating_add(1) - } else { - parent - } - }, - specified::MathDepth::Add(rel) => { - let parent = cx.builder.get_parent_font().clone_math_depth(); - (parent as i32).saturating_add(rel.to_computed_value(cx)) - }, - specified::MathDepth::Absolute(abs) => abs.to_computed_value(cx), - }; - cmp::min(int, i8::MAX as i32) as i8 - } - - fn from_computed_value(other: &i8) -> Self { - let computed_value = *other as i32; - specified::MathDepth::Absolute(SpecifiedInteger::from_computed_value(&computed_value)) - } -} - -/// - Use a signed 8.8 fixed-point value (representable range -128.0..128) -/// -/// Values of <angle> below -90 or above 90 not permitted, so we use out of -/// range values to represent normal | oblique -pub const FONT_STYLE_FRACTION_BITS: u16 = 8; - -/// This is an alias which is useful mostly as a cbindgen / C++ inference -/// workaround. -pub type FontStyleFixedPoint = FixedPoint<i16, FONT_STYLE_FRACTION_BITS>; - -/// The computed value of `font-style`. -/// -/// - Define out of range values min value (-128.0) as meaning 'normal' -/// - Define max value (127.99609375) as 'italic' -/// - Other values represent 'oblique <angle>' -/// - Note that 'oblique 0deg' is distinct from 'normal' (should it be?) -/// -/// cbindgen:derive-lt -/// cbindgen:derive-lte -/// cbindgen:derive-gt -/// cbindgen:derive-gte -#[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - PartialOrd, - ToResolvedValue, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(C)] -pub struct FontStyle(FontStyleFixedPoint); - -impl FontStyle { - /// The normal keyword. - pub const NORMAL: FontStyle = FontStyle(FontStyleFixedPoint { - value: 100 << FONT_STYLE_FRACTION_BITS, - }); - /// The italic keyword. - pub const ITALIC: FontStyle = FontStyle(FontStyleFixedPoint { - value: 101 << FONT_STYLE_FRACTION_BITS, - }); - - /// The default angle for `font-style: oblique`. - /// See also https://github.com/w3c/csswg-drafts/issues/2295 - pub const DEFAULT_OBLIQUE_DEGREES: i16 = 14; - - /// The `oblique` keyword with the default degrees. - pub const OBLIQUE: FontStyle = FontStyle(FontStyleFixedPoint { - value: Self::DEFAULT_OBLIQUE_DEGREES << FONT_STYLE_FRACTION_BITS, - }); - - /// The `normal` value. - #[inline] - pub fn normal() -> Self { - Self::NORMAL - } - - /// Returns the oblique angle for this style. - pub fn oblique(degrees: f32) -> Self { - Self(FixedPoint::from_float( - degrees - .max(specified::FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES) - .min(specified::FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES), - )) - } - - /// Returns the oblique angle for this style. - pub fn oblique_degrees(&self) -> f32 { - debug_assert_ne!(*self, Self::NORMAL); - debug_assert_ne!(*self, Self::ITALIC); - self.0.to_float() - } -} - -impl ToCss for FontStyle { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - if *self == Self::NORMAL { - return dest.write_str("normal"); - } - if *self == Self::ITALIC { - return dest.write_str("italic"); - } - if *self == Self::OBLIQUE { - return dest.write_str("oblique"); - } - dest.write_str("oblique ")?; - let angle = Angle::from_degrees(self.oblique_degrees()); - angle.to_css(dest)?; - Ok(()) - } -} - -impl ToAnimatedValue for FontStyle { - type AnimatedValue = generics::FontStyle<Angle>; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - if self == Self::NORMAL { - // This allows us to animate between normal and oblique values. Per spec, - // https://drafts.csswg.org/css-fonts-4/#font-style-prop: - // Animation type: by computed value type; 'normal' animates as 'oblique 0deg' - return generics::FontStyle::Oblique(Angle::from_degrees(0.0)); - } - if self == Self::ITALIC { - return generics::FontStyle::Italic; - } - generics::FontStyle::Oblique(Angle::from_degrees(self.oblique_degrees())) - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - match animated { - generics::FontStyle::Normal => Self::NORMAL, - generics::FontStyle::Italic => Self::ITALIC, - generics::FontStyle::Oblique(ref angle) => { - if angle.degrees() == 0.0 { - // Reverse the conversion done in to_animated_value() - Self::NORMAL - } else { - Self::oblique(angle.degrees()) - } - }, - } - } -} - -/// font-stretch is a percentage relative to normal. -/// -/// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375) -/// -/// We arbitrarily limit here to 1000%. (If that becomes a problem, we could -/// reduce the number of fractional bits and increase the limit.) -pub const FONT_STRETCH_FRACTION_BITS: u16 = 6; - -/// This is an alias which is useful mostly as a cbindgen / C++ inference -/// workaround. -pub type FontStretchFixedPoint = FixedPoint<u16, FONT_STRETCH_FRACTION_BITS>; - -/// A value for the font-stretch property per: -/// -/// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch -/// -/// cbindgen:derive-lt -/// cbindgen:derive-lte -/// cbindgen:derive-gt -/// cbindgen:derive-gte -#[derive( - Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] -#[repr(C)] -pub struct FontStretch(pub FontStretchFixedPoint); - -impl FontStretch { - /// The fraction bits, as an easy-to-access-constant. - pub const FRACTION_BITS: u16 = FONT_STRETCH_FRACTION_BITS; - /// 0.5 in our floating point representation. - pub const HALF: u16 = 1 << (Self::FRACTION_BITS - 1); - - /// The `ultra-condensed` keyword. - pub const ULTRA_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { - value: 50 << Self::FRACTION_BITS, - }); - /// The `extra-condensed` keyword. - pub const EXTRA_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { - value: (62 << Self::FRACTION_BITS) + Self::HALF, - }); - /// The `condensed` keyword. - pub const CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { - value: 75 << Self::FRACTION_BITS, - }); - /// The `semi-condensed` keyword. - pub const SEMI_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { - value: (87 << Self::FRACTION_BITS) + Self::HALF, - }); - /// The `normal` keyword. - pub const NORMAL: FontStretch = FontStretch(FontStretchFixedPoint { - value: 100 << Self::FRACTION_BITS, - }); - /// The `semi-expanded` keyword. - pub const SEMI_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { - value: (112 << Self::FRACTION_BITS) + Self::HALF, - }); - /// The `expanded` keyword. - pub const EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { - value: 125 << Self::FRACTION_BITS, - }); - /// The `extra-expanded` keyword. - pub const EXTRA_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { - value: 150 << Self::FRACTION_BITS, - }); - /// The `ultra-expanded` keyword. - pub const ULTRA_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { - value: 200 << Self::FRACTION_BITS, - }); - - /// 100% - pub fn hundred() -> Self { - Self::NORMAL - } - - /// Converts to a computed percentage. - #[inline] - pub fn to_percentage(&self) -> Percentage { - Percentage(self.0.to_float() / 100.0) - } - - /// Converts from a computed percentage value. - pub fn from_percentage(p: f32) -> Self { - Self(FixedPoint::from_float((p * 100.).max(0.0).min(1000.0))) - } - - /// Returns a relevant stretch value from a keyword. - /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop - pub fn from_keyword(kw: specified::FontStretchKeyword) -> Self { - use specified::FontStretchKeyword::*; - match kw { - UltraCondensed => Self::ULTRA_CONDENSED, - ExtraCondensed => Self::EXTRA_CONDENSED, - Condensed => Self::CONDENSED, - SemiCondensed => Self::SEMI_CONDENSED, - Normal => Self::NORMAL, - SemiExpanded => Self::SEMI_EXPANDED, - Expanded => Self::EXPANDED, - ExtraExpanded => Self::EXTRA_EXPANDED, - UltraExpanded => Self::ULTRA_EXPANDED, - } - } - - /// Returns the stretch keyword if we map to one of the relevant values. - pub fn as_keyword(&self) -> Option<specified::FontStretchKeyword> { - use specified::FontStretchKeyword::*; - // TODO: Can we use match here? - if *self == Self::ULTRA_CONDENSED { - return Some(UltraCondensed); - } - if *self == Self::EXTRA_CONDENSED { - return Some(ExtraCondensed); - } - if *self == Self::CONDENSED { - return Some(Condensed); - } - if *self == Self::SEMI_CONDENSED { - return Some(SemiCondensed); - } - if *self == Self::NORMAL { - return Some(Normal); - } - if *self == Self::SEMI_EXPANDED { - return Some(SemiExpanded); - } - if *self == Self::EXPANDED { - return Some(Expanded); - } - if *self == Self::EXTRA_EXPANDED { - return Some(ExtraExpanded); - } - if *self == Self::ULTRA_EXPANDED { - return Some(UltraExpanded); - } - None - } -} - -impl ToCss for FontStretch { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.to_percentage().to_css(dest) - } -} - -impl ToAnimatedValue for FontStretch { - type AnimatedValue = Percentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.to_percentage() - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - Self::from_percentage(animated.0) - } -} diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs deleted file mode 100644 index 0f8a11f9197..00000000000 --- a/components/style/values/computed/image.rs +++ /dev/null @@ -1,213 +0,0 @@ -/* 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/. */ - -//! CSS handling for the computed value of -//! [`image`][image]s -//! -//! [image]: https://drafts.csswg.org/css-images/#image-values - -use crate::values::computed::percentage::Percentage; -use crate::values::computed::position::Position; -use crate::values::computed::url::ComputedImageUrl; -#[cfg(feature = "gecko")] -use crate::values::computed::NumberOrPercentage; -use crate::values::computed::{Angle, Color, Context}; -use crate::values::computed::{ - AngleOrPercentage, LengthPercentage, NonNegativeLength, NonNegativeLengthPercentage, - Resolution, ToComputedValue, -}; -use crate::values::generics::image::{self as generic, GradientCompatMode}; -use crate::values::specified::image as specified; -use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword}; -use std::f32::consts::PI; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -pub use specified::ImageRendering; - -/// Computed values for an image according to CSS-IMAGES. -/// <https://drafts.csswg.org/css-images/#image-values> -pub type Image = - generic::GenericImage<Gradient, MozImageRect, ComputedImageUrl, Color, Percentage, Resolution>; - -// Images should remain small, see https://github.com/servo/servo/pull/18430 -size_of_test!(Image, 40); - -/// Computed values for a CSS gradient. -/// <https://drafts.csswg.org/css-images/#gradients> -pub type Gradient = generic::GenericGradient< - LineDirection, - LengthPercentage, - NonNegativeLength, - NonNegativeLengthPercentage, - Position, - Angle, - AngleOrPercentage, - Color, ->; - -/// Computed values for CSS cross-fade -/// <https://drafts.csswg.org/css-images-4/#cross-fade-function> -pub type CrossFade = generic::CrossFade<Image, Color, Percentage>; - -/// A computed radial gradient ending shape. -pub type EndingShape = generic::GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>; - -/// A computed gradient line direction. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToResolvedValue)] -#[repr(C, u8)] -pub enum LineDirection { - /// An angle. - Angle(Angle), - /// A horizontal direction. - Horizontal(HorizontalPositionKeyword), - /// A vertical direction. - Vertical(VerticalPositionKeyword), - /// A corner. - Corner(HorizontalPositionKeyword, VerticalPositionKeyword), -} - -/// The computed value for an `image-set()` image. -pub type ImageSet = generic::GenericImageSet<Image, Resolution>; - -impl ToComputedValue for specified::ImageSet { - type ComputedValue = ImageSet; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - let items = self.items.to_computed_value(context); - let dpr = context.device().device_pixel_ratio().get(); - - let mut supported_image = false; - let mut selected_index = std::usize::MAX; - let mut selected_resolution = 0.0; - - for (i, item) in items.iter().enumerate() { - if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) { - // If the MIME type is not supported, we discard the ImageSetItem. - continue; - } - - let candidate_resolution = item.resolution.dppx(); - debug_assert!(candidate_resolution >= 0.0, "Resolutions should be non-negative"); - if candidate_resolution == 0.0 { - // If the resolution is 0, we also treat it as an invalid image. - continue; - } - - // https://drafts.csswg.org/css-images-4/#image-set-notation: - // - // Make a UA-specific choice of which to load, based on whatever criteria deemed - // relevant (such as the resolution of the display, connection speed, etc). - // - // For now, select the lowest resolution greater than display density, otherwise the - // greatest resolution available. - let better_candidate = || { - if selected_resolution < dpr && candidate_resolution > selected_resolution { - return true; - } - if candidate_resolution < selected_resolution && candidate_resolution >= dpr { - return true; - } - false - }; - - // The first item with a supported MIME type is obviously the current best candidate - if !supported_image || better_candidate() { - supported_image = true; - selected_index = i; - selected_resolution = candidate_resolution; - } - } - - ImageSet { - selected_index, - items, - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self { - selected_index: std::usize::MAX, - items: ToComputedValue::from_computed_value(&computed.items), - } - } -} - -/// Computed values for `-moz-image-rect(...)`. -#[cfg(feature = "gecko")] -pub type MozImageRect = generic::GenericMozImageRect<NumberOrPercentage, ComputedImageUrl>; - -/// Empty enum on non-gecko -#[cfg(not(feature = "gecko"))] -pub type MozImageRect = specified::MozImageRect; - -impl generic::LineDirection for LineDirection { - fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool { - match *self { - LineDirection::Angle(angle) => angle.radians() == PI, - LineDirection::Vertical(VerticalPositionKeyword::Bottom) => { - compat_mode == GradientCompatMode::Modern - }, - LineDirection::Vertical(VerticalPositionKeyword::Top) => { - compat_mode != GradientCompatMode::Modern - }, - _ => false, - } - } - - fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result - where - W: Write, - { - match *self { - LineDirection::Angle(ref angle) => angle.to_css(dest), - LineDirection::Horizontal(x) => { - if compat_mode == GradientCompatMode::Modern { - dest.write_str("to ")?; - } - x.to_css(dest) - }, - LineDirection::Vertical(y) => { - if compat_mode == GradientCompatMode::Modern { - dest.write_str("to ")?; - } - y.to_css(dest) - }, - LineDirection::Corner(x, y) => { - if compat_mode == GradientCompatMode::Modern { - dest.write_str("to ")?; - } - x.to_css(dest)?; - dest.write_char(' ')?; - y.to_css(dest) - }, - } - } -} - -impl ToComputedValue for specified::LineDirection { - type ComputedValue = LineDirection; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - specified::LineDirection::Angle(ref angle) => { - LineDirection::Angle(angle.to_computed_value(context)) - }, - specified::LineDirection::Horizontal(x) => LineDirection::Horizontal(x), - specified::LineDirection::Vertical(y) => LineDirection::Vertical(y), - specified::LineDirection::Corner(x, y) => LineDirection::Corner(x, y), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - LineDirection::Angle(ref angle) => { - specified::LineDirection::Angle(ToComputedValue::from_computed_value(angle)) - }, - LineDirection::Horizontal(x) => specified::LineDirection::Horizontal(x), - LineDirection::Vertical(y) => specified::LineDirection::Vertical(y), - LineDirection::Corner(x, y) => specified::LineDirection::Corner(x, y), - } - } -} diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs deleted file mode 100644 index 64285e7c124..00000000000 --- a/components/style/values/computed/length.rs +++ /dev/null @@ -1,522 +0,0 @@ -/* 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/. */ - -//! `<length>` computed values, and related ones. - -use super::{Context, Number, ToComputedValue}; -use crate::values::animated::ToAnimatedValue; -use crate::values::computed::NonNegativeNumber; -use crate::values::generics::length as generics; -use crate::values::generics::length::{ - GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, -}; -use crate::values::generics::NonNegative; -use crate::values::specified::length::{AbsoluteLength, FontBaseSize}; -use crate::values::{specified, CSSFloat}; -use crate::Zero; -use app_units::Au; -use std::fmt::{self, Write}; -use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign}; -use style_traits::{CSSPixel, CssWriter, ToCss}; - -pub use super::image::Image; -pub use super::length_percentage::{LengthPercentage, NonNegativeLengthPercentage}; -pub use crate::values::specified::url::UrlOrNone; -pub use crate::values::specified::{Angle, BorderStyle, Time}; - -impl ToComputedValue for specified::NoCalcLength { - type ComputedValue = Length; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - self.to_computed_value_with_base_size(context, FontBaseSize::CurrentStyle) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self::Absolute(AbsoluteLength::Px(computed.px())) - } -} - -impl specified::NoCalcLength { - /// Computes a length with a given font-relative base size. - pub fn to_computed_value_with_base_size( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> Length { - match *self { - Self::Absolute(length) => length.to_computed_value(context), - Self::FontRelative(length) => length.to_computed_value(context, base_size), - Self::ViewportPercentage(length) => length.to_computed_value(context), - Self::ContainerRelative(length) => length.to_computed_value(context), - Self::ServoCharacterWidth(length) => length - .to_computed_value(context.style().get_font().clone_font_size().computed_size()), - } - } -} - -impl ToComputedValue for specified::Length { - type ComputedValue = Length; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - Self::NoCalc(l) => l.to_computed_value(context), - Self::Calc(ref calc) => calc.to_computed_value(context).to_length().unwrap(), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self::NoCalc(specified::NoCalcLength::from_computed_value(computed)) - } -} - -/// Some boilerplate to share between negative and non-negative -/// length-percentage or auto. -macro_rules! computed_length_percentage_or_auto { - ($inner:ty) => { - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, percentage_basis: Au) -> Option<Au> { - match *self { - Self::Auto => None, - Self::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), - } - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - use crate::values::generics::length::LengthPercentageOrAuto::*; - match *self { - LengthPercentage(ref l) => l.is_definitely_zero(), - Auto => false, - } - } - }; -} - -/// A computed type for `<length-percentage> | auto`. -pub type LengthPercentageOrAuto = generics::GenericLengthPercentageOrAuto<LengthPercentage>; - -impl LengthPercentageOrAuto { - /// Clamps the value to a non-negative value. - pub fn clamp_to_non_negative(self) -> Self { - use crate::values::generics::length::LengthPercentageOrAuto::*; - match self { - LengthPercentage(l) => LengthPercentage(l.clamp_to_non_negative()), - Auto => Auto, - } - } - - /// Convert to have a borrow inside the enum - pub fn as_ref(&self) -> generics::GenericLengthPercentageOrAuto<&LengthPercentage> { - use crate::values::generics::length::LengthPercentageOrAuto::*; - match *self { - LengthPercentage(ref lp) => LengthPercentage(lp), - Auto => Auto, - } - } - - computed_length_percentage_or_auto!(LengthPercentage); -} - -impl generics::GenericLengthPercentageOrAuto<&LengthPercentage> { - /// Resolves the percentage. - #[inline] - pub fn percentage_relative_to(&self, basis: Length) -> LengthOrAuto { - use crate::values::generics::length::LengthPercentageOrAuto::*; - match self { - LengthPercentage(length_percentage) => { - LengthPercentage(length_percentage.percentage_relative_to(basis)) - }, - Auto => Auto, - } - } - - /// Maybe resolves the percentage. - #[inline] - pub fn maybe_percentage_relative_to(&self, basis: Option<Length>) -> LengthOrAuto { - use crate::values::generics::length::LengthPercentageOrAuto::*; - match self { - LengthPercentage(length_percentage) => length_percentage - .maybe_percentage_relative_to(basis) - .map_or(Auto, LengthPercentage), - Auto => Auto, - } - } -} - -/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0. -pub type NonNegativeLengthPercentageOrAuto = - generics::GenericLengthPercentageOrAuto<NonNegativeLengthPercentage>; - -impl NonNegativeLengthPercentageOrAuto { - computed_length_percentage_or_auto!(NonNegativeLengthPercentage); -} - -#[cfg(feature = "servo")] -impl MaxSize { - /// Convert the computed value into used value. - #[inline] - pub fn to_used_value(&self, percentage_basis: Au) -> Option<Au> { - match *self { - GenericMaxSize::None => None, - GenericMaxSize::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), - } - } -} - -impl Size { - /// Convert the computed value into used value. - #[inline] - #[cfg(feature = "servo")] - pub fn to_used_value(&self, percentage_basis: Au) -> Option<Au> { - match *self { - GenericSize::Auto => None, - GenericSize::LengthPercentage(ref lp) => Some(lp.to_used_value(percentage_basis)), - } - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - match *self { - Self::Auto => false, - Self::LengthPercentage(ref lp) => lp.is_definitely_zero(), - #[cfg(feature = "gecko")] - Self::MinContent | - Self::MaxContent | - Self::FitContent | - Self::MozAvailable | - Self::FitContentFunction(_) => false, - } - } -} - -/// The computed `<length>` value. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Deserialize, - MallocSizeOf, - PartialEq, - PartialOrd, - Serialize, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct CSSPixelLength(CSSFloat); - -impl fmt::Debug for CSSPixelLength { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f)?; - f.write_str(" px") - } -} - -impl CSSPixelLength { - /// Return a new CSSPixelLength. - #[inline] - pub fn new(px: CSSFloat) -> Self { - CSSPixelLength(px) - } - - /// Returns a normalized (NaN turned to zero) version of this length. - #[inline] - pub fn normalized(self) -> Self { - Self::new(crate::values::normalize(self.0)) - } - - /// Returns a finite (normalized and clamped to float min and max) version of this length. - #[inline] - pub fn finite(self) -> Self { - Self::new(crate::values::normalize(self.0).min(f32::MAX).max(f32::MIN)) - } - - /// Scale the length by a given amount. - #[inline] - pub fn scale_by(self, scale: CSSFloat) -> Self { - CSSPixelLength(self.0 * scale) - } - - /// Return the containing pixel value. - #[inline] - pub fn px(self) -> CSSFloat { - self.0 - } - - /// Return the length with app_unit i32 type. - #[inline] - pub fn to_i32_au(self) -> i32 { - Au::from(self).0 - } - - /// Return the absolute value of this length. - #[inline] - pub fn abs(self) -> Self { - CSSPixelLength::new(self.0.abs()) - } - - /// Return the clamped value of this length. - #[inline] - pub fn clamp_to_non_negative(self) -> Self { - CSSPixelLength::new(self.0.max(0.)) - } - - /// Returns the minimum between `self` and `other`. - #[inline] - pub fn min(self, other: Self) -> Self { - CSSPixelLength::new(self.0.min(other.0)) - } - - /// Returns the maximum between `self` and `other`. - #[inline] - pub fn max(self, other: Self) -> Self { - CSSPixelLength::new(self.0.max(other.0)) - } - - /// Sets `self` to the maximum between `self` and `other`. - #[inline] - pub fn max_assign(&mut self, other: Self) { - *self = self.max(other); - } - - /// Clamp the value to a lower bound and an optional upper bound. - /// - /// Can be used for example with `min-width` and `max-width`. - #[inline] - pub fn clamp_between_extremums(self, min_size: Self, max_size: Option<Self>) -> Self { - self.clamp_below_max(max_size).max(min_size) - } - - /// Clamp the value to an optional upper bound. - /// - /// Can be used for example with `max-width`. - #[inline] - pub fn clamp_below_max(self, max_size: Option<Self>) -> Self { - match max_size { - None => self, - Some(max_size) => self.min(max_size), - } - } -} - -impl num_traits::Zero for CSSPixelLength { - fn zero() -> Self { - CSSPixelLength::new(0.) - } - - fn is_zero(&self) -> bool { - self.px() == 0. - } -} - -impl ToCss for CSSPixelLength { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0.to_css(dest)?; - dest.write_str("px") - } -} - -impl std::iter::Sum for CSSPixelLength { - fn sum<I: Iterator<Item = Self>>(iter: I) -> Self { - iter.fold(Length::zero(), Add::add) - } -} - -impl Add for CSSPixelLength { - type Output = Self; - - #[inline] - fn add(self, other: Self) -> Self { - Self::new(self.px() + other.px()) - } -} - -impl AddAssign for CSSPixelLength { - #[inline] - fn add_assign(&mut self, other: Self) { - self.0 += other.0; - } -} - -impl Div for CSSPixelLength { - type Output = CSSFloat; - - #[inline] - fn div(self, other: Self) -> CSSFloat { - self.px() / other.px() - } -} - -impl Div<CSSFloat> for CSSPixelLength { - type Output = Self; - - #[inline] - fn div(self, other: CSSFloat) -> Self { - Self::new(self.px() / other) - } -} - -impl MulAssign<CSSFloat> for CSSPixelLength { - #[inline] - fn mul_assign(&mut self, other: CSSFloat) { - self.0 *= other; - } -} - -impl Mul<CSSFloat> for CSSPixelLength { - type Output = Self; - - #[inline] - fn mul(self, other: CSSFloat) -> Self { - Self::new(self.px() * other) - } -} - -impl Neg for CSSPixelLength { - type Output = Self; - - #[inline] - fn neg(self) -> Self { - CSSPixelLength::new(-self.0) - } -} - -impl Sub for CSSPixelLength { - type Output = Self; - - #[inline] - fn sub(self, other: Self) -> Self { - Self::new(self.px() - other.px()) - } -} - -impl SubAssign for CSSPixelLength { - #[inline] - fn sub_assign(&mut self, other: Self) { - self.0 -= other.0; - } -} - -impl From<CSSPixelLength> for Au { - #[inline] - fn from(len: CSSPixelLength) -> Self { - Au::from_f32_px(len.0) - } -} - -impl From<Au> for CSSPixelLength { - #[inline] - fn from(len: Au) -> Self { - CSSPixelLength::new(len.to_f32_px()) - } -} - -impl From<CSSPixelLength> for euclid::Length<CSSFloat, CSSPixel> { - #[inline] - fn from(length: CSSPixelLength) -> Self { - Self::new(length.0) - } -} - -/// An alias of computed `<length>` value. -pub type Length = CSSPixelLength; - -/// Either a computed `<length>` or the `auto` keyword. -pub type LengthOrAuto = generics::GenericLengthPercentageOrAuto<Length>; - -/// Either a non-negative `<length>` or the `auto` keyword. -pub type NonNegativeLengthOrAuto = generics::GenericLengthPercentageOrAuto<NonNegativeLength>; - -/// Either a computed `<length>` or a `<number>` value. -pub type LengthOrNumber = GenericLengthOrNumber<Length, Number>; - -/// A wrapper of Length, whose value must be >= 0. -pub type NonNegativeLength = NonNegative<Length>; - -impl ToAnimatedValue for NonNegativeLength { - type AnimatedValue = Length; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegativeLength::new(animated.px().max(0.)) - } -} - -impl NonNegativeLength { - /// Create a NonNegativeLength. - #[inline] - pub fn new(px: CSSFloat) -> Self { - NonNegative(Length::new(px.max(0.))) - } - - /// Return the pixel value of |NonNegativeLength|. - #[inline] - pub fn px(&self) -> CSSFloat { - self.0.px() - } - - #[inline] - /// Ensures it is non negative - pub fn clamp(self) -> Self { - if (self.0).0 < 0. { - Self::zero() - } else { - self - } - } -} - -impl From<Length> for NonNegativeLength { - #[inline] - fn from(len: Length) -> Self { - NonNegative(len) - } -} - -impl From<Au> for NonNegativeLength { - #[inline] - fn from(au: Au) -> Self { - NonNegative(au.into()) - } -} - -impl From<NonNegativeLength> for Au { - #[inline] - fn from(non_negative_len: NonNegativeLength) -> Self { - Au::from(non_negative_len.0) - } -} - -/// Either a computed NonNegativeLengthPercentage or the `normal` keyword. -pub type NonNegativeLengthPercentageOrNormal = - GenericLengthPercentageOrNormal<NonNegativeLengthPercentage>; - -/// Either a non-negative `<length>` or a `<number>`. -pub type NonNegativeLengthOrNumber = GenericLengthOrNumber<NonNegativeLength, NonNegativeNumber>; - -/// A computed value for `min-width`, `min-height`, `width` or `height` property. -pub type Size = GenericSize<NonNegativeLengthPercentage>; - -/// A computed value for `max-width` or `min-height` property. -pub type MaxSize = GenericMaxSize<NonNegativeLengthPercentage>; diff --git a/components/style/values/computed/length_percentage.rs b/components/style/values/computed/length_percentage.rs deleted file mode 100644 index a8f5868b997..00000000000 --- a/components/style/values/computed/length_percentage.rs +++ /dev/null @@ -1,899 +0,0 @@ -/* 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/. */ - -//! `<length-percentage>` computed values, and related ones. -//! -//! The over-all design is a tagged pointer, with the lower bits of the pointer -//! being non-zero if it is a non-calc value. -//! -//! It is expected to take 64 bits both in x86 and x86-64. This is implemented -//! as a `union`, with 4 different variants: -//! -//! * The length and percentage variants have a { tag, f32 } (effectively) -//! layout. The tag has to overlap with the lower 2 bits of the calc variant. -//! -//! * The `calc()` variant is a { tag, pointer } in x86 (so same as the -//! others), or just a { pointer } in x86-64 (so that the two bits of the tag -//! can be obtained from the lower bits of the pointer). -//! -//! * There's a `tag` variant just to make clear when only the tag is intended -//! to be read. Note that the tag needs to be masked always by `TAG_MASK`, to -//! deal with the pointer variant in x86-64. -//! -//! The assertions in the constructor methods ensure that the tag getter matches -//! our expectations. - -use super::{Context, Length, Percentage, ToComputedValue}; -use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::{calc, NonNegative}; -use crate::values::specified::length::FontBaseSize; -use crate::values::{specified, CSSFloat}; -use crate::{Zero, ZeroNoPercent}; -use app_units::Au; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; -use serde::{Deserialize, Serialize}; -use std::borrow::Cow; -use std::fmt::{self, Write}; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ToCss}; - -#[doc(hidden)] -#[derive(Clone, Copy)] -#[repr(C)] -pub struct LengthVariant { - tag: u8, - length: Length, -} - -#[doc(hidden)] -#[derive(Clone, Copy)] -#[repr(C)] -pub struct PercentageVariant { - tag: u8, - percentage: Percentage, -} - -// NOTE(emilio): cbindgen only understands the #[cfg] on the top level -// definition. -#[doc(hidden)] -#[derive(Clone, Copy)] -#[repr(C)] -#[cfg(target_pointer_width = "32")] -pub struct CalcVariant { - tag: u8, - ptr: *mut CalcLengthPercentage, -} - -#[doc(hidden)] -#[derive(Clone, Copy)] -#[repr(C)] -#[cfg(target_pointer_width = "64")] -pub struct CalcVariant { - ptr: usize, // In little-endian byte order -} - -// `CalcLengthPercentage` is `Send + Sync` as asserted below. -unsafe impl Send for CalcVariant {} -unsafe impl Sync for CalcVariant {} - -#[doc(hidden)] -#[derive(Clone, Copy)] -#[repr(C)] -pub struct TagVariant { - tag: u8, -} - -/// A `<length-percentage>` value. This can be either a `<length>`, a -/// `<percentage>`, or a combination of both via `calc()`. -/// -/// cbindgen:private-default-tagged-enum-constructor=false -/// cbindgen:derive-mut-casts=true -/// -/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage -/// -/// The tag is stored in the lower two bits. -/// -/// We need to use a struct instead of the union directly because unions with -/// Drop implementations are unstable, looks like. -/// -/// Also we need the union and the variants to be `pub` (even though the member -/// is private) so that cbindgen generates it. They're not part of the public -/// API otherwise. -#[repr(transparent)] -pub struct LengthPercentage(LengthPercentageUnion); - -#[doc(hidden)] -#[repr(C)] -pub union LengthPercentageUnion { - length: LengthVariant, - percentage: PercentageVariant, - calc: CalcVariant, - tag: TagVariant, -} - -impl LengthPercentageUnion { - #[doc(hidden)] // Need to be public so that cbindgen generates it. - pub const TAG_CALC: u8 = 0; - #[doc(hidden)] - pub const TAG_LENGTH: u8 = 1; - #[doc(hidden)] - pub const TAG_PERCENTAGE: u8 = 2; - #[doc(hidden)] - pub const TAG_MASK: u8 = 0b11; -} - -#[derive(Clone, Copy, Debug, PartialEq)] -#[repr(u8)] -enum Tag { - Calc = LengthPercentageUnion::TAG_CALC, - Length = LengthPercentageUnion::TAG_LENGTH, - Percentage = LengthPercentageUnion::TAG_PERCENTAGE, -} - -// All the members should be 64 bits, even in 32-bit builds. -#[allow(unused)] -unsafe fn static_assert() { - fn assert_send_and_sync<T: Send + Sync>() {} - std::mem::transmute::<u64, LengthVariant>(0u64); - std::mem::transmute::<u64, PercentageVariant>(0u64); - std::mem::transmute::<u64, CalcVariant>(0u64); - std::mem::transmute::<u64, LengthPercentage>(0u64); - assert_send_and_sync::<LengthVariant>(); - assert_send_and_sync::<PercentageVariant>(); - assert_send_and_sync::<CalcLengthPercentage>(); -} - -impl Drop for LengthPercentage { - fn drop(&mut self) { - if self.tag() == Tag::Calc { - let _ = unsafe { Box::from_raw(self.calc_ptr()) }; - } - } -} - -impl MallocSizeOf for LengthPercentage { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match self.unpack() { - Unpacked::Length(..) | Unpacked::Percentage(..) => 0, - Unpacked::Calc(c) => unsafe { ops.malloc_size_of(c) }, - } - } -} - -/// An unpacked `<length-percentage>` that borrows the `calc()` variant. -#[derive(Clone, Debug, PartialEq, ToCss)] -enum Unpacked<'a> { - Calc(&'a CalcLengthPercentage), - Length(Length), - Percentage(Percentage), -} - -/// An unpacked `<length-percentage>` that mutably borrows the `calc()` variant. -enum UnpackedMut<'a> { - Calc(&'a mut CalcLengthPercentage), - Length(Length), - Percentage(Percentage), -} - -/// An unpacked `<length-percentage>` that owns the `calc()` variant, for -/// serialization purposes. -#[derive(Deserialize, PartialEq, Serialize)] -enum Serializable { - Calc(CalcLengthPercentage), - Length(Length), - Percentage(Percentage), -} - -impl LengthPercentage { - /// 1px length value for SVG defaults - #[inline] - pub fn one() -> Self { - Self::new_length(Length::new(1.)) - } - - /// 0% - #[inline] - pub fn zero_percent() -> Self { - Self::new_percent(Percentage::zero()) - } - - fn to_calc_node(&self) -> Cow<CalcNode> { - match self.unpack() { - Unpacked::Length(l) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l))), - Unpacked::Percentage(p) => { - Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p))) - }, - Unpacked::Calc(p) => Cow::Borrowed(&p.node), - } - } - - /// Constructs a length value. - #[inline] - pub fn new_length(length: Length) -> Self { - let length = Self(LengthPercentageUnion { - length: LengthVariant { - tag: LengthPercentageUnion::TAG_LENGTH, - length, - }, - }); - debug_assert_eq!(length.tag(), Tag::Length); - length - } - - /// Constructs a percentage value. - #[inline] - pub fn new_percent(percentage: Percentage) -> Self { - let percent = Self(LengthPercentageUnion { - percentage: PercentageVariant { - tag: LengthPercentageUnion::TAG_PERCENTAGE, - percentage, - }, - }); - debug_assert_eq!(percent.tag(), Tag::Percentage); - percent - } - - /// Given a `LengthPercentage` value `v`, construct the value representing - /// `calc(100% - v)`. - pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self { - // TODO: This could in theory take ownership of the calc node in `v` if - // possible instead of cloning. - let mut node = v.to_calc_node().into_owned(); - node.map(std::ops::Neg::neg); - - let new_node = CalcNode::Sum( - vec![ - CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())), - node, - ] - .into(), - ); - - Self::new_calc(new_node, clamping_mode) - } - - /// Constructs a `calc()` value. - #[inline] - pub fn new_calc(mut node: CalcNode, clamping_mode: AllowedNumericType) -> Self { - node.simplify_and_sort(); - - match node { - CalcNode::Leaf(l) => { - return match l { - CalcLengthPercentageLeaf::Length(l) => { - Self::new_length(Length::new(clamping_mode.clamp(l.px())).normalized()) - }, - CalcLengthPercentageLeaf::Percentage(p) => Self::new_percent(Percentage( - clamping_mode.clamp(crate::values::normalize(p.0)), - )), - } - }, - _ => Self::new_calc_unchecked(Box::new(CalcLengthPercentage { - clamping_mode, - node, - })), - } - } - - /// Private version of new_calc() that constructs a calc() variant without - /// checking. - fn new_calc_unchecked(calc: Box<CalcLengthPercentage>) -> Self { - let ptr = Box::into_raw(calc); - - #[cfg(target_pointer_width = "32")] - let calc = CalcVariant { - tag: LengthPercentageUnion::TAG_CALC, - ptr, - }; - - #[cfg(target_pointer_width = "64")] - let calc = CalcVariant { - #[cfg(target_endian = "little")] - ptr: ptr as usize, - #[cfg(target_endian = "big")] - ptr: (ptr as usize).swap_bytes(), - }; - - let calc = Self(LengthPercentageUnion { calc }); - debug_assert_eq!(calc.tag(), Tag::Calc); - calc - } - - #[inline] - fn tag(&self) -> Tag { - match unsafe { self.0.tag.tag & LengthPercentageUnion::TAG_MASK } { - LengthPercentageUnion::TAG_CALC => Tag::Calc, - LengthPercentageUnion::TAG_LENGTH => Tag::Length, - LengthPercentageUnion::TAG_PERCENTAGE => Tag::Percentage, - _ => unsafe { debug_unreachable!("Bogus tag?") }, - } - } - - #[inline] - fn unpack_mut<'a>(&'a mut self) -> UnpackedMut<'a> { - unsafe { - match self.tag() { - Tag::Calc => UnpackedMut::Calc(&mut *self.calc_ptr()), - Tag::Length => UnpackedMut::Length(self.0.length.length), - Tag::Percentage => UnpackedMut::Percentage(self.0.percentage.percentage), - } - } - } - - #[inline] - fn unpack<'a>(&'a self) -> Unpacked<'a> { - unsafe { - match self.tag() { - Tag::Calc => Unpacked::Calc(&*self.calc_ptr()), - Tag::Length => Unpacked::Length(self.0.length.length), - Tag::Percentage => Unpacked::Percentage(self.0.percentage.percentage), - } - } - } - - #[inline] - unsafe fn calc_ptr(&self) -> *mut CalcLengthPercentage { - #[cfg(not(all(target_endian = "big", target_pointer_width = "64")))] - { - self.0.calc.ptr as *mut _ - } - #[cfg(all(target_endian = "big", target_pointer_width = "64"))] - { - self.0.calc.ptr.swap_bytes() as *mut _ - } - } - - #[inline] - fn to_serializable(&self) -> Serializable { - match self.unpack() { - Unpacked::Calc(c) => Serializable::Calc(c.clone()), - Unpacked::Length(l) => Serializable::Length(l), - Unpacked::Percentage(p) => Serializable::Percentage(p), - } - } - - #[inline] - fn from_serializable(s: Serializable) -> Self { - match s { - Serializable::Calc(c) => Self::new_calc_unchecked(Box::new(c)), - Serializable::Length(l) => Self::new_length(l), - Serializable::Percentage(p) => Self::new_percent(p), - } - } - - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - match self.unpack() { - Unpacked::Length(l) => l.px() == 0.0, - Unpacked::Percentage(p) => p.0 == 0.0, - Unpacked::Calc(..) => false, - } - } - - /// Resolves the percentage. - #[inline] - pub fn resolve(&self, basis: Length) -> Length { - match self.unpack() { - Unpacked::Length(l) => l, - Unpacked::Percentage(p) => (basis * p.0).normalized(), - Unpacked::Calc(ref c) => c.resolve(basis), - } - } - - /// Resolves the percentage. Just an alias of resolve(). - #[inline] - pub fn percentage_relative_to(&self, basis: Length) -> Length { - self.resolve(basis) - } - - /// Return whether there's any percentage in this value. - #[inline] - pub fn has_percentage(&self) -> bool { - match self.unpack() { - Unpacked::Length(..) => false, - Unpacked::Percentage(..) | Unpacked::Calc(..) => true, - } - } - - /// Converts to a `<length>` if possible. - pub fn to_length(&self) -> Option<Length> { - match self.unpack() { - Unpacked::Length(l) => Some(l), - Unpacked::Percentage(..) | Unpacked::Calc(..) => { - debug_assert!(self.has_percentage()); - return None; - }, - } - } - - /// Converts to a `<percentage>` if possible. - #[inline] - pub fn to_percentage(&self) -> Option<Percentage> { - match self.unpack() { - Unpacked::Percentage(p) => Some(p), - Unpacked::Length(..) | Unpacked::Calc(..) => None, - } - } - - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, containing_length: Au) -> Au { - Au::from(self.to_pixel_length(containing_length)) - } - - /// Returns the used value as CSSPixelLength. - #[inline] - pub fn to_pixel_length(&self, containing_length: Au) -> Length { - self.resolve(containing_length.into()) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, container_len: Option<Length>) -> Option<Au> { - self.maybe_percentage_relative_to(container_len) - .map(Au::from) - } - - /// If there are special rules for computing percentages in a value (e.g. - /// the height property), they apply whenever a calc() expression contains - /// percentages. - pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> { - if let Unpacked::Length(l) = self.unpack() { - return Some(l); - } - Some(self.resolve(container_len?)) - } - - /// Returns the clamped non-negative values. - #[inline] - pub fn clamp_to_non_negative(mut self) -> Self { - match self.unpack_mut() { - UnpackedMut::Length(l) => Self::new_length(l.clamp_to_non_negative()), - UnpackedMut::Percentage(p) => Self::new_percent(p.clamp_to_non_negative()), - UnpackedMut::Calc(ref mut c) => { - c.clamping_mode = AllowedNumericType::NonNegative; - self - }, - } - } -} - -impl PartialEq for LengthPercentage { - fn eq(&self, other: &Self) -> bool { - self.unpack() == other.unpack() - } -} - -impl fmt::Debug for LengthPercentage { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - self.unpack().fmt(formatter) - } -} - -impl ToAnimatedZero for LengthPercentage { - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(match self.unpack() { - Unpacked::Length(l) => Self::new_length(l.to_animated_zero()?), - Unpacked::Percentage(p) => Self::new_percent(p.to_animated_zero()?), - Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.to_animated_zero()?)), - }) - } -} - -impl Clone for LengthPercentage { - fn clone(&self) -> Self { - match self.unpack() { - Unpacked::Length(l) => Self::new_length(l), - Unpacked::Percentage(p) => Self::new_percent(p), - Unpacked::Calc(c) => Self::new_calc_unchecked(Box::new(c.clone())), - } - } -} - -impl ToComputedValue for specified::LengthPercentage { - type ComputedValue = LengthPercentage; - - fn to_computed_value(&self, context: &Context) -> LengthPercentage { - match *self { - specified::LengthPercentage::Length(ref value) => { - LengthPercentage::new_length(value.to_computed_value(context)) - }, - specified::LengthPercentage::Percentage(value) => LengthPercentage::new_percent(value), - specified::LengthPercentage::Calc(ref calc) => (**calc).to_computed_value(context), - } - } - - fn from_computed_value(computed: &LengthPercentage) -> Self { - match computed.unpack() { - Unpacked::Length(ref l) => { - specified::LengthPercentage::Length(ToComputedValue::from_computed_value(l)) - }, - Unpacked::Percentage(p) => specified::LengthPercentage::Percentage(p), - Unpacked::Calc(c) => { - // We simplify before constructing the LengthPercentage if - // needed, so this is always fine. - specified::LengthPercentage::Calc(Box::new( - specified::CalcLengthPercentage::from_computed_value(c), - )) - }, - } - } -} - -impl ComputeSquaredDistance for LengthPercentage { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // A somewhat arbitrary base, it doesn't really make sense to mix - // lengths with percentages, but we can't do much better here, and this - // ensures that the distance between length-only and percentage-only - // lengths makes sense. - let basis = Length::new(100.); - self.resolve(basis) - .compute_squared_distance(&other.resolve(basis)) - } -} - -impl ToCss for LengthPercentage { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.unpack().to_css(dest) - } -} - -impl Zero for LengthPercentage { - fn zero() -> Self { - LengthPercentage::new_length(Length::zero()) - } - - #[inline] - fn is_zero(&self) -> bool { - self.is_definitely_zero() - } -} - -impl ZeroNoPercent for LengthPercentage { - #[inline] - fn is_zero_no_percent(&self) -> bool { - self.is_definitely_zero() && !self.has_percentage() - } -} - -impl Serialize for LengthPercentage { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: serde::Serializer, - { - self.to_serializable().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for LengthPercentage { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: serde::Deserializer<'de>, - { - Ok(Self::from_serializable(Serializable::deserialize( - deserializer, - )?)) - } -} - -/// The leaves of a `<length-percentage>` calc expression. -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - ToAnimatedZero, - ToCss, - ToResolvedValue, -)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum CalcLengthPercentageLeaf { - Length(Length), - Percentage(Percentage), -} - -impl CalcLengthPercentageLeaf { - fn is_zero_length(&self) -> bool { - match *self { - Self::Length(ref l) => l.is_zero(), - Self::Percentage(..) => false, - } - } -} - -impl PartialOrd for CalcLengthPercentageLeaf { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - use self::CalcLengthPercentageLeaf::*; - // NOTE: Percentages can't be compared reasonably here because the - // percentage basis might be negative, see bug 1709018. - match (self, other) { - (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), - _ => None, - } - } -} - -impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf { - fn unitless_value(&self) -> f32 { - match *self { - Self::Length(ref l) => l.px(), - Self::Percentage(ref p) => p.0, - } - } - - fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { - use self::CalcLengthPercentageLeaf::*; - - // 0px plus anything else is equal to the right hand side. - if self.is_zero_length() { - *self = other.clone(); - return Ok(()); - } - - if other.is_zero_length() { - return Ok(()); - } - - match (self, other) { - (&mut Length(ref mut one), &Length(ref other)) => { - *one += *other; - }, - (&mut Percentage(ref mut one), &Percentage(ref other)) => { - one.0 += other.0; - }, - _ => return Err(()), - } - - Ok(()) - } - - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - match (self, other) { - ( - &CalcLengthPercentageLeaf::Length(ref one), - &CalcLengthPercentageLeaf::Length(ref other), - ) => Ok(CalcLengthPercentageLeaf::Length(Length::new(op( - one.px(), - other.px(), - )))), - ( - &CalcLengthPercentageLeaf::Percentage(one), - &CalcLengthPercentageLeaf::Percentage(other), - ) => Ok(CalcLengthPercentageLeaf::Percentage(Percentage(op( - one.0, other.0, - )))), - _ => Err(()), - } - } - - fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - match self { - CalcLengthPercentageLeaf::Length(value) => { - *value = Length::new(op(value.px())); - }, - CalcLengthPercentageLeaf::Percentage(value) => { - *value = Percentage(op(value.0)); - }, - } - } - - fn simplify(&mut self) {} - - fn sort_key(&self) -> calc::SortKey { - match *self { - Self::Length(..) => calc::SortKey::Px, - Self::Percentage(..) => calc::SortKey::Percentage, - } - } -} - -/// The computed version of a calc() node for `<length-percentage>` values. -pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>; - -/// The representation of a calc() function with mixed lengths and percentages. -#[derive( - Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, ToCss, -)] -#[repr(C)] -pub struct CalcLengthPercentage { - #[animation(constant)] - #[css(skip)] - clamping_mode: AllowedNumericType, - node: CalcNode, -} - -impl CalcLengthPercentage { - /// Resolves the percentage. - #[inline] - fn resolve(&self, basis: Length) -> Length { - // unwrap() is fine because the conversion below is infallible. - let px = self - .node - .resolve(|l| { - Ok(match *l { - CalcLengthPercentageLeaf::Length(l) => l.px(), - CalcLengthPercentageLeaf::Percentage(ref p) => basis.px() * p.0, - }) - }) - .unwrap(); - Length::new(self.clamping_mode.clamp(px)).normalized() - } -} - -// NOTE(emilio): We don't compare `clamping_mode` since we want to preserve the -// invariant that `from_computed_value(length).to_computed_value(..) == length`. -// -// Right now for e.g. a non-negative length, we set clamping_mode to `All` -// unconditionally for non-calc values, and to `NonNegative` for calc. -// -// If we determine that it's sound, from_computed_value() can generate an -// absolute length, which then would get `All` as the clamping mode. -// -// We may want to just eagerly-detect whether we can clamp in -// `LengthPercentage::new` and switch to `AllowedNumericType::NonNegative` then, -// maybe. -impl PartialEq for CalcLengthPercentage { - fn eq(&self, other: &Self) -> bool { - self.node == other.node - } -} - -impl specified::CalcLengthPercentage { - /// Compute the value, zooming any absolute units by the zoom function. - fn to_computed_value_with_zoom<F>( - &self, - context: &Context, - zoom_fn: F, - base_size: FontBaseSize, - ) -> LengthPercentage - where - F: Fn(Length) -> Length, - { - use crate::values::specified::calc::Leaf; - - let node = self.node.map_leaves(|leaf| match *leaf { - Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)), - Leaf::Length(l) => CalcLengthPercentageLeaf::Length({ - let result = l.to_computed_value_with_base_size(context, base_size); - if l.should_zoom_text() { - zoom_fn(result) - } else { - result - } - }), - Leaf::Number(..) | Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => { - unreachable!("Shouldn't have parsed") - }, - }); - - LengthPercentage::new_calc(node, self.clamping_mode) - } - - /// Compute font-size or line-height taking into account text-zoom if necessary. - pub fn to_computed_value_zoomed( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> LengthPercentage { - self.to_computed_value_with_zoom(context, |abs| context.maybe_zoom_text(abs), base_size) - } - - /// Compute the value into pixel length as CSSFloat without context, - /// so it returns Err(()) if there is any non-absolute unit. - pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { - use crate::values::specified::calc::Leaf; - use crate::values::specified::length::NoCalcLength; - - // Simplification should've turned this into an absolute length, - // otherwise it wouldn't have been able to. - match self.node { - calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()), - _ => Err(()), - } - } - - /// Compute the calc using the current font-size (and without text-zoom). - pub fn to_computed_value(&self, context: &Context) -> LengthPercentage { - self.to_computed_value_with_zoom(context, |abs| abs, FontBaseSize::CurrentStyle) - } - - #[inline] - fn from_computed_value(computed: &CalcLengthPercentage) -> Self { - use crate::values::specified::calc::Leaf; - use crate::values::specified::length::NoCalcLength; - - specified::CalcLengthPercentage { - clamping_mode: computed.clamping_mode, - node: computed.node.map_leaves(|l| match l { - CalcLengthPercentageLeaf::Length(ref l) => { - Leaf::Length(NoCalcLength::from_px(l.px())) - }, - CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0), - }), - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -/// https://drafts.csswg.org/css-values-4/#combine-math -/// https://drafts.csswg.org/css-values-4/#combine-mixed -impl Animate for LengthPercentage { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(match (self.unpack(), other.unpack()) { - (Unpacked::Length(one), Unpacked::Length(other)) => { - Self::new_length(one.animate(&other, procedure)?) - }, - (Unpacked::Percentage(one), Unpacked::Percentage(other)) => { - Self::new_percent(one.animate(&other, procedure)?) - }, - _ => { - let mut one = self.to_calc_node().into_owned(); - let mut other = other.to_calc_node().into_owned(); - let (l, r) = procedure.weights(); - - one.mul_by(l as f32); - other.mul_by(r as f32); - - Self::new_calc( - CalcNode::Sum(vec![one, other].into()), - AllowedNumericType::All, - ) - }, - }) - } -} - -/// A wrapper of LengthPercentage, whose value must be >= 0. -pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; - -impl ToAnimatedValue for NonNegativeLengthPercentage { - type AnimatedValue = LengthPercentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegative(animated.clamp_to_non_negative()) - } -} - -impl NonNegativeLengthPercentage { - /// Returns true if the computed value is absolute 0 or 0%. - #[inline] - pub fn is_definitely_zero(&self) -> bool { - self.0.is_definitely_zero() - } - - /// Returns the used value. - #[inline] - pub fn to_used_value(&self, containing_length: Au) -> Au { - let resolved = self.0.to_used_value(containing_length); - std::cmp::max(resolved, Au(0)) - } - - /// Convert the computed value into used value. - #[inline] - pub fn maybe_to_used_value(&self, containing_length: Option<Au>) -> Option<Au> { - let resolved = self - .0 - .maybe_to_used_value(containing_length.map(|v| v.into()))?; - Some(std::cmp::max(resolved, Au(0))) - } -} diff --git a/components/style/values/computed/list.rs b/components/style/values/computed/list.rs deleted file mode 100644 index 3e5d1eb220d..00000000000 --- a/components/style/values/computed/list.rs +++ /dev/null @@ -1,17 +0,0 @@ -/* 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/. */ - -//! `list` computed values. - -#[cfg(feature = "gecko")] -pub use crate::values::specified::list::ListStyleType; -pub use crate::values::specified::list::Quotes; - -impl Quotes { - /// Initial value for `quotes`. - #[inline] - pub fn get_initial_value() -> Quotes { - Quotes::Auto - } -} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs deleted file mode 100644 index 8f9af36c478..00000000000 --- a/components/style/values/computed/mod.rs +++ /dev/null @@ -1,998 +0,0 @@ -/* 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/. */ - -//! Computed values. - -use self::transform::DirectionVector; -use super::animated::ToAnimatedValue; -use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; -use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks; -use super::generics::grid::{GenericGridLine, GenericTrackBreadth}; -use super::generics::grid::{GenericTrackSize, TrackList as GenericTrackList}; -use super::generics::transform::IsParallelTo; -use super::generics::{self, GreaterThanOrEqualToOne, NonNegative, ZeroToOne}; -use super::specified; -use super::{CSSFloat, CSSInteger}; -use crate::computed_value_flags::ComputedValueFlags; -use crate::context::QuirksMode; -use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; -use crate::media_queries::Device; -#[cfg(feature = "gecko")] -use crate::properties; -use crate::properties::{ComputedValues, StyleBuilder}; -use crate::rule_cache::RuleCacheConditions; -use crate::stylesheets::container_rule::{ - ContainerInfo, ContainerSizeQuery, ContainerSizeQueryResult, -}; -use crate::values::specified::length::FontBaseSize; -use crate::{ArcSlice, Atom, One}; -use euclid::{default, Point2D, Rect, Size2D}; -use servo_arc::Arc; -use std::cell::RefCell; -use std::cmp; -use std::f32; -use std::ops::{Add, Sub}; - -#[cfg(feature = "gecko")] -pub use self::align::{ - AlignContent, AlignItems, AlignTracks, JustifyContent, JustifyItems, JustifyTracks, - SelfAlignment, -}; -#[cfg(feature = "gecko")] -pub use self::align::{AlignSelf, JustifySelf}; -pub use self::angle::Angle; -pub use self::animation::{AnimationIterationCount, AnimationName, AnimationTimeline}; -pub use self::animation::{ScrollAxis, ScrollTimelineName, TransitionProperty, ViewTimelineInset}; -pub use self::background::{BackgroundRepeat, BackgroundSize}; -pub use self::basic_shape::FillRule; -pub use self::border::{ - BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, - BorderImageWidth, BorderRadius, BorderSideWidth, BorderSpacing, LineWidth, -}; -pub use self::box_::{ - Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainIntrinsicSize, - ContainerName, ContainerType, ContentVisibility, Display, Float, LineClamp, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollSnapAlign, - ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, - TouchAction, VerticalAlign, WillChange, -}; -pub use self::color::{ - Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust, -}; -pub use self::column::ColumnCount; -pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; -pub use self::easing::TimingFunction; -pub use self::effects::{BoxShadow, Filter, SimpleShadow}; -pub use self::flex::FlexBasis; -pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle}; -pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric}; -pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis}; -pub use self::font::{FontVariantAlternates, FontWeight}; -pub use self::font::{FontVariantEastAsian, FontVariationSettings}; -pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale}; -pub use self::image::{Gradient, Image, ImageRendering, LineDirection, MozImageRect}; -pub use self::length::{CSSPixelLength, NonNegativeLength}; -pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; -pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; -pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto}; -#[cfg(feature = "gecko")] -pub use self::list::ListStyleType; -pub use self::list::Quotes; -pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate}; -pub use self::outline::OutlineStyle; -pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize}; -pub use self::percentage::{NonNegativePercentage, Percentage}; -pub use self::position::AspectRatio; -pub use self::position::{ - GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, ZIndex, -}; -pub use self::ratio::Ratio; -pub use self::rect::NonNegativeLengthOrNumberRect; -pub use self::resolution::Resolution; -pub use self::svg::{DProperty, MozContextProperties}; -pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind}; -pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; -pub use self::text::HyphenateCharacter; -pub use self::text::TextUnderlinePosition; -pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight}; -pub use self::text::{OverflowWrap, RubyPosition, TextOverflow, WordBreak, WordSpacing}; -pub use self::text::{TextAlign, TextAlignLast, TextEmphasisPosition, TextEmphasisStyle}; -pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify}; -pub use self::time::Time; -pub use self::transform::{Rotate, Scale, Transform, TransformOperation}; -pub use self::transform::{TransformOrigin, TransformStyle, Translate}; -#[cfg(feature = "gecko")] -pub use self::ui::CursorImage; -pub use self::ui::{BoolInteger, Cursor, UserSelect}; -pub use super::specified::TextTransform; -pub use super::specified::ViewportVariant; -pub use super::specified::{BorderStyle, TextDecorationLine}; -pub use app_units::Au; - -#[cfg(feature = "gecko")] -pub mod align; -pub mod angle; -pub mod animation; -pub mod background; -pub mod basic_shape; -pub mod border; -#[path = "box.rs"] -pub mod box_; -pub mod color; -pub mod column; -pub mod counters; -pub mod easing; -pub mod effects; -pub mod flex; -pub mod font; -pub mod image; -pub mod length; -pub mod length_percentage; -pub mod list; -pub mod motion; -pub mod outline; -pub mod page; -pub mod percentage; -pub mod position; -pub mod ratio; -pub mod rect; -pub mod resolution; -pub mod svg; -pub mod table; -pub mod text; -pub mod time; -pub mod transform; -pub mod ui; -pub mod url; - -/// A `Context` is all the data a specified value could ever need to compute -/// itself and be transformed to a computed value. -pub struct Context<'a> { - /// Values accessed through this need to be in the properties "computed - /// early": color, text-decoration, font-size, display, position, float, - /// border-*-style, outline-style, font-family, writing-mode... - pub builder: StyleBuilder<'a>, - - /// A cached computed system font value, for use by gecko. - /// - /// See properties/longhands/font.mako.rs - #[cfg(feature = "gecko")] - pub cached_system_font: Option<properties::longhands::system_font::ComputedSystemFont>, - - /// A dummy option for servo so initializing a computed::Context isn't - /// painful. - /// - /// TODO(emilio): Make constructors for Context, and drop this. - #[cfg(feature = "servo")] - pub cached_system_font: Option<()>, - - /// Whether or not we are computing the media list in a media query. - pub in_media_query: bool, - - /// Whether or not we are computing the container query condition. - pub in_container_query: bool, - - /// The quirks mode of this context. - pub quirks_mode: QuirksMode, - - /// Whether this computation is being done for a SMIL animation. - /// - /// This is used to allow certain properties to generate out-of-range - /// values, which SMIL allows. - pub for_smil_animation: bool, - - /// Returns the container information to evaluate a given container query. - pub container_info: Option<ContainerInfo>, - - /// Whether we're computing a value for a non-inherited property. - /// False if we are computed a value for an inherited property or not computing for a property - /// at all (e.g. in a media query evaluation). - pub for_non_inherited_property: bool, - - /// The conditions to cache a rule node on the rule cache. - /// - /// FIXME(emilio): Drop the refcell. - pub rule_cache_conditions: RefCell<&'a mut RuleCacheConditions>, - - /// Container size query for this context. - container_size_query: RefCell<ContainerSizeQuery<'a>>, -} - -impl<'a> Context<'a> { - /// Lazily evaluate the container size query, returning the result. - pub fn get_container_size_query(&self) -> ContainerSizeQueryResult { - let mut resolved = self.container_size_query.borrow_mut(); - resolved.get().clone() - } - - /// Creates a suitable context for media query evaluation, in which - /// font-relative units compute against the system_font, and executes `f` - /// with it. - pub fn for_media_query_evaluation<F, R>(device: &Device, quirks_mode: QuirksMode, f: F) -> R - where - F: FnOnce(&Context) -> R, - { - let mut conditions = RuleCacheConditions::default(); - let context = Context { - builder: StyleBuilder::for_inheritance(device, None, None), - cached_system_font: None, - in_media_query: true, - in_container_query: false, - quirks_mode, - for_smil_animation: false, - container_info: None, - for_non_inherited_property: false, - rule_cache_conditions: RefCell::new(&mut conditions), - container_size_query: RefCell::new(ContainerSizeQuery::none()), - }; - f(&context) - } - - /// Creates a suitable context for container query evaluation for the style - /// specified. - pub fn for_container_query_evaluation<F, R>( - device: &Device, - container_info_and_style: Option<(ContainerInfo, Arc<ComputedValues>)>, - container_size_query: ContainerSizeQuery, - f: F, - ) -> R - where - F: FnOnce(&Context) -> R, - { - let mut conditions = RuleCacheConditions::default(); - - let (container_info, style) = match container_info_and_style { - Some((ci, s)) => (Some(ci), Some(s)), - None => (None, None), - }; - - let style = style.as_ref().map(|s| &**s); - let quirks_mode = device.quirks_mode(); - let context = Context { - builder: StyleBuilder::for_inheritance(device, style, None), - cached_system_font: None, - in_media_query: false, - in_container_query: true, - quirks_mode, - for_smil_animation: false, - container_info, - for_non_inherited_property: false, - rule_cache_conditions: RefCell::new(&mut conditions), - container_size_query: RefCell::new(container_size_query), - }; - - f(&context) - } - - /// Creates a context suitable for more general cases. - pub fn new( - builder: StyleBuilder<'a>, - quirks_mode: QuirksMode, - rule_cache_conditions: &'a mut RuleCacheConditions, - container_size_query: ContainerSizeQuery<'a>, - ) -> Self { - Self { - builder, - cached_system_font: None, - in_media_query: false, - in_container_query: false, - quirks_mode, - container_info: None, - for_smil_animation: false, - for_non_inherited_property: false, - rule_cache_conditions: RefCell::new(rule_cache_conditions), - container_size_query: RefCell::new(container_size_query), - } - } - - /// Creates a context suitable for computing animations. - pub fn new_for_animation( - builder: StyleBuilder<'a>, - for_smil_animation: bool, - quirks_mode: QuirksMode, - rule_cache_conditions: &'a mut RuleCacheConditions, - container_size_query: ContainerSizeQuery<'a>, - ) -> Self { - Self { - builder, - cached_system_font: None, - in_media_query: false, - in_container_query: false, - quirks_mode, - container_info: None, - for_smil_animation, - for_non_inherited_property: false, - rule_cache_conditions: RefCell::new(rule_cache_conditions), - container_size_query: RefCell::new(container_size_query), - } - } - - /// The current device. - pub fn device(&self) -> &Device { - self.builder.device - } - - /// Queries font metrics. - pub fn query_font_metrics( - &self, - base_size: FontBaseSize, - orientation: FontMetricsOrientation, - retrieve_math_scales: bool, - ) -> FontMetrics { - if self.for_non_inherited_property { - self.rule_cache_conditions.borrow_mut().set_uncacheable(); - } - self.builder.add_flags(match base_size { - FontBaseSize::CurrentStyle => ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS, - FontBaseSize::InheritedStyle => ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS, - }); - let size = base_size.resolve(self).used_size(); - let style = self.style(); - - let (wm, font) = match base_size { - FontBaseSize::CurrentStyle => (style.writing_mode, style.get_font()), - // This is only used for font-size computation. - FontBaseSize::InheritedStyle => { - (*style.inherited_writing_mode(), style.get_parent_font()) - }, - }; - - let vertical = match orientation { - FontMetricsOrientation::MatchContextPreferHorizontal => { - wm.is_vertical() && wm.is_upright() - }, - FontMetricsOrientation::MatchContextPreferVertical => { - wm.is_vertical() && !wm.is_sideways() - }, - FontMetricsOrientation::Horizontal => false, - }; - self.device().query_font_metrics( - vertical, - font, - size, - self.in_media_or_container_query(), - retrieve_math_scales, - ) - } - - /// The current viewport size, used to resolve viewport units. - pub fn viewport_size_for_viewport_unit_resolution( - &self, - variant: ViewportVariant, - ) -> default::Size2D<Au> { - self.builder - .add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); - self.builder - .device - .au_viewport_size_for_viewport_unit_resolution(variant) - } - - /// Whether we're in a media or container query. - pub fn in_media_or_container_query(&self) -> bool { - self.in_media_query || self.in_container_query - } - - /// The default computed style we're getting our reset style from. - pub fn default_style(&self) -> &ComputedValues { - self.builder.default_style() - } - - /// The current style. - pub fn style(&self) -> &StyleBuilder { - &self.builder - } - - /// Apply text-zoom if enabled. - #[cfg(feature = "gecko")] - pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength { - if self - .style() - .get_font() - .clone__x_text_scale() - .text_zoom_enabled() - { - self.device().zoom_text(size) - } else { - size - } - } - - /// (Servo doesn't do text-zoom) - #[cfg(feature = "servo")] - pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength { - size - } -} - -/// An iterator over a slice of computed values -#[derive(Clone)] -pub struct ComputedVecIter<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> { - cx: &'cx Context<'cx_a>, - values: &'a [S], -} - -impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_a, S> { - /// Construct an iterator from a slice of specified values and a context - pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self { - ComputedVecIter { cx, values } - } -} - -impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ExactSizeIterator - for ComputedVecIter<'a, 'cx, 'cx_a, S> -{ - fn len(&self) -> usize { - self.values.len() - } -} - -impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> Iterator for ComputedVecIter<'a, 'cx, 'cx_a, S> { - type Item = S::ComputedValue; - fn next(&mut self) -> Option<Self::Item> { - if let Some((next, rest)) = self.values.split_first() { - let ret = next.to_computed_value(self.cx); - self.values = rest; - Some(ret) - } else { - None - } - } - - fn size_hint(&self) -> (usize, Option<usize>) { - (self.values.len(), Some(self.values.len())) - } -} - -/// A trait to represent the conversion between computed and specified values. -/// -/// This trait is derivable with `#[derive(ToComputedValue)]`. The derived -/// implementation just calls `ToComputedValue::to_computed_value` on each field -/// of the passed value. The deriving code assumes that if the type isn't -/// generic, then the trait can be implemented as simple `Clone::clone` calls, -/// this means that a manual implementation with `ComputedValue = Self` is bogus -/// if it returns anything else than a clone. -pub trait ToComputedValue { - /// The computed value type we're going to be converted to. - type ComputedValue; - - /// Convert a specified value to a computed value, using itself and the data - /// inside the `Context`. - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue; - - /// Convert a computed value to specified value form. - /// - /// This will be used for recascading during animation. - /// Such from_computed_valued values should recompute to the same value. - fn from_computed_value(computed: &Self::ComputedValue) -> Self; -} - -impl<A, B> ToComputedValue for (A, B) -where - A: ToComputedValue, - B: ToComputedValue, -{ - type ComputedValue = ( - <A as ToComputedValue>::ComputedValue, - <B as ToComputedValue>::ComputedValue, - ); - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - ( - self.0.to_computed_value(context), - self.1.to_computed_value(context), - ) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - ( - A::from_computed_value(&computed.0), - B::from_computed_value(&computed.1), - ) - } -} - -impl<T> ToComputedValue for Option<T> -where - T: ToComputedValue, -{ - type ComputedValue = Option<<T as ToComputedValue>::ComputedValue>; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - self.as_ref().map(|item| item.to_computed_value(context)) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - computed.as_ref().map(T::from_computed_value) - } -} - -impl<T> ToComputedValue for default::Size2D<T> -where - T: ToComputedValue, -{ - type ComputedValue = default::Size2D<<T as ToComputedValue>::ComputedValue>; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - Size2D::new( - self.width.to_computed_value(context), - self.height.to_computed_value(context), - ) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Size2D::new( - T::from_computed_value(&computed.width), - T::from_computed_value(&computed.height), - ) - } -} - -impl<T> ToComputedValue for Vec<T> -where - T: ToComputedValue, -{ - type ComputedValue = Vec<<T as ToComputedValue>::ComputedValue>; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - self.iter() - .map(|item| item.to_computed_value(context)) - .collect() - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - computed.iter().map(T::from_computed_value).collect() - } -} - -impl<T> ToComputedValue for Box<T> -where - T: ToComputedValue, -{ - type ComputedValue = Box<<T as ToComputedValue>::ComputedValue>; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - Box::new(T::to_computed_value(self, context)) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Box::new(T::from_computed_value(computed)) - } -} - -impl<T> ToComputedValue for Box<[T]> -where - T: ToComputedValue, -{ - type ComputedValue = Box<[<T as ToComputedValue>::ComputedValue]>; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - self.iter() - .map(|item| item.to_computed_value(context)) - .collect::<Vec<_>>() - .into_boxed_slice() - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - computed - .iter() - .map(T::from_computed_value) - .collect::<Vec<_>>() - .into_boxed_slice() - } -} - -impl<T> ToComputedValue for crate::OwnedSlice<T> -where - T: ToComputedValue, -{ - type ComputedValue = crate::OwnedSlice<<T as ToComputedValue>::ComputedValue>; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - self.iter() - .map(|item| item.to_computed_value(context)) - .collect() - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - computed.iter().map(T::from_computed_value).collect() - } -} - -// NOTE(emilio): This is implementable more generically, but it's unlikely -// what you want there, as it forces you to have an extra allocation. -// -// We could do that if needed, ideally with specialization for the case where -// ComputedValue = T. But we don't need it for now. -impl<T> ToComputedValue for Arc<T> -where - T: ToComputedValue<ComputedValue = T>, -{ - type ComputedValue = Self; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self { - self.clone() - } - - #[inline] - fn from_computed_value(computed: &Self) -> Self { - computed.clone() - } -} - -// Same caveat as above applies. -impl<T> ToComputedValue for ArcSlice<T> -where - T: ToComputedValue<ComputedValue = T>, -{ - type ComputedValue = Self; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self { - self.clone() - } - - #[inline] - fn from_computed_value(computed: &Self) -> Self { - computed.clone() - } -} - -trivial_to_computed_value!(()); -trivial_to_computed_value!(bool); -trivial_to_computed_value!(f32); -trivial_to_computed_value!(i32); -trivial_to_computed_value!(u8); -trivial_to_computed_value!(u16); -trivial_to_computed_value!(u32); -trivial_to_computed_value!(usize); -trivial_to_computed_value!(Atom); -trivial_to_computed_value!(crate::values::AtomIdent); -#[cfg(feature = "servo")] -trivial_to_computed_value!(crate::Namespace); -#[cfg(feature = "servo")] -trivial_to_computed_value!(crate::Prefix); -trivial_to_computed_value!(String); -trivial_to_computed_value!(Box<str>); -trivial_to_computed_value!(crate::OwnedStr); -trivial_to_computed_value!(style_traits::values::specified::AllowedNumericType); - -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedZero, - ToCss, - ToResolvedValue, -)] -#[repr(C, u8)] -pub enum AngleOrPercentage { - Percentage(Percentage), - Angle(Angle), -} - -impl ToComputedValue for specified::AngleOrPercentage { - type ComputedValue = AngleOrPercentage; - - #[inline] - fn to_computed_value(&self, context: &Context) -> AngleOrPercentage { - match *self { - specified::AngleOrPercentage::Percentage(percentage) => { - AngleOrPercentage::Percentage(percentage.to_computed_value(context)) - }, - specified::AngleOrPercentage::Angle(angle) => { - AngleOrPercentage::Angle(angle.to_computed_value(context)) - }, - } - } - #[inline] - fn from_computed_value(computed: &AngleOrPercentage) -> Self { - match *computed { - AngleOrPercentage::Percentage(percentage) => specified::AngleOrPercentage::Percentage( - ToComputedValue::from_computed_value(&percentage), - ), - AngleOrPercentage::Angle(angle) => { - specified::AngleOrPercentage::Angle(ToComputedValue::from_computed_value(&angle)) - }, - } - } -} - -/// A `<number>` value. -pub type Number = CSSFloat; - -impl IsParallelTo for (Number, Number, Number) { - fn is_parallel_to(&self, vector: &DirectionVector) -> bool { - use euclid::approxeq::ApproxEq; - // If a and b is parallel, the angle between them is 0deg, so - // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. - let self_vector = DirectionVector::new(self.0, self.1, self.2); - self_vector - .cross(*vector) - .square_length() - .approx_eq(&0.0f32) - } -} - -/// A wrapper of Number, but the value >= 0. -pub type NonNegativeNumber = NonNegative<CSSFloat>; - -impl ToAnimatedValue for NonNegativeNumber { - type AnimatedValue = CSSFloat; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated.max(0.).into() - } -} - -impl From<CSSFloat> for NonNegativeNumber { - #[inline] - fn from(number: CSSFloat) -> NonNegativeNumber { - NonNegative::<CSSFloat>(number) - } -} - -impl From<NonNegativeNumber> for CSSFloat { - #[inline] - fn from(number: NonNegativeNumber) -> CSSFloat { - number.0 - } -} - -impl One for NonNegativeNumber { - #[inline] - fn one() -> Self { - NonNegative(1.0) - } - - #[inline] - fn is_one(&self) -> bool { - self.0 == 1.0 - } -} - -/// A wrapper of Number, but the value between 0 and 1 -pub type ZeroToOneNumber = ZeroToOne<CSSFloat>; - -impl ToAnimatedValue for ZeroToOneNumber { - type AnimatedValue = CSSFloat; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - Self(animated.max(0.).min(1.)) - } -} - -impl From<CSSFloat> for ZeroToOneNumber { - #[inline] - fn from(number: CSSFloat) -> Self { - Self(number) - } -} - -/// A wrapper of Number, but the value >= 1. -pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<CSSFloat>; - -impl ToAnimatedValue for GreaterThanOrEqualToOneNumber { - type AnimatedValue = CSSFloat; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - animated.max(1.).into() - } -} - -impl From<CSSFloat> for GreaterThanOrEqualToOneNumber { - #[inline] - fn from(number: CSSFloat) -> GreaterThanOrEqualToOneNumber { - GreaterThanOrEqualToOne::<CSSFloat>(number) - } -} - -impl From<GreaterThanOrEqualToOneNumber> for CSSFloat { - #[inline] - fn from(number: GreaterThanOrEqualToOneNumber) -> CSSFloat { - number.0 - } -} - -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedZero, - ToCss, - ToResolvedValue, -)] -#[repr(C, u8)] -pub enum NumberOrPercentage { - Percentage(Percentage), - Number(Number), -} - -impl NumberOrPercentage { - fn clamp_to_non_negative(self) -> Self { - match self { - NumberOrPercentage::Percentage(p) => { - NumberOrPercentage::Percentage(p.clamp_to_non_negative()) - }, - NumberOrPercentage::Number(n) => NumberOrPercentage::Number(n.max(0.)), - } - } -} - -impl ToComputedValue for specified::NumberOrPercentage { - type ComputedValue = NumberOrPercentage; - - #[inline] - fn to_computed_value(&self, context: &Context) -> NumberOrPercentage { - match *self { - specified::NumberOrPercentage::Percentage(percentage) => { - NumberOrPercentage::Percentage(percentage.to_computed_value(context)) - }, - specified::NumberOrPercentage::Number(number) => { - NumberOrPercentage::Number(number.to_computed_value(context)) - }, - } - } - #[inline] - fn from_computed_value(computed: &NumberOrPercentage) -> Self { - match *computed { - NumberOrPercentage::Percentage(percentage) => { - specified::NumberOrPercentage::Percentage(ToComputedValue::from_computed_value( - &percentage, - )) - }, - NumberOrPercentage::Number(number) => { - specified::NumberOrPercentage::Number(ToComputedValue::from_computed_value(&number)) - }, - } - } -} - -/// A non-negative <number-percentage>. -pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>; - -impl NonNegativeNumberOrPercentage { - /// Returns the `100%` value. - #[inline] - pub fn hundred_percent() -> Self { - NonNegative(NumberOrPercentage::Percentage(Percentage::hundred())) - } -} - -impl ToAnimatedValue for NonNegativeNumberOrPercentage { - type AnimatedValue = NumberOrPercentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegative(animated.clamp_to_non_negative()) - } -} - -/// A type used for opacity. -pub type Opacity = CSSFloat; - -/// A `<integer>` value. -pub type Integer = CSSInteger; - -/// A wrapper of Integer, but only accept a value >= 1. -pub type PositiveInteger = GreaterThanOrEqualToOne<CSSInteger>; - -impl ToAnimatedValue for PositiveInteger { - type AnimatedValue = CSSInteger; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - cmp::max(animated, 1).into() - } -} - -impl From<CSSInteger> for PositiveInteger { - #[inline] - fn from(int: CSSInteger) -> PositiveInteger { - GreaterThanOrEqualToOne::<CSSInteger>(int) - } -} - -/// rect(...) | auto -pub type ClipRect = generics::GenericClipRect<LengthOrAuto>; - -/// rect(...) | auto -pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>; - -/// The computed value of a grid `<track-breadth>` -pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>; - -/// The computed value of a grid `<track-size>` -pub type TrackSize = GenericTrackSize<LengthPercentage>; - -/// The computed value of a grid `<track-size>+` -pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>; - -/// The computed value of a grid `<track-list>` -/// (could also be `<auto-track-list>` or `<explicit-track-list>`) -pub type TrackList = GenericTrackList<LengthPercentage, Integer>; - -/// The computed value of a `<grid-line>`. -pub type GridLine = GenericGridLine<Integer>; - -/// `<grid-template-rows> | <grid-template-columns>` -pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>; - -impl ClipRect { - /// Given a border box, resolves the clip rect against the border box - /// in the same space the border box is in - pub fn for_border_rect<T: Copy + From<Length> + Add<Output = T> + Sub<Output = T>, U>( - &self, - border_box: Rect<T, U>, - ) -> Rect<T, U> { - fn extract_clip_component<T: From<Length>>(p: &LengthOrAuto, or: T) -> T { - match *p { - LengthOrAuto::Auto => or, - LengthOrAuto::LengthPercentage(ref length) => T::from(*length), - } - } - - let clip_origin = Point2D::new( - From::from(self.left.auto_is(|| Length::new(0.))), - From::from(self.top.auto_is(|| Length::new(0.))), - ); - let right = extract_clip_component(&self.right, border_box.size.width); - let bottom = extract_clip_component(&self.bottom, border_box.size.height); - let clip_size = Size2D::new(right - clip_origin.x, bottom - clip_origin.y); - - Rect::new(clip_origin, clip_size).translate(border_box.origin.to_vector()) - } -} diff --git a/components/style/values/computed/motion.rs b/components/style/values/computed/motion.rs deleted file mode 100644 index 8fd578bb72c..00000000000 --- a/components/style/values/computed/motion.rs +++ /dev/null @@ -1,65 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values that are related to motion path. - -use crate::values::computed::{Angle, LengthPercentage, Position}; -use crate::values::generics::motion::{ - GenericOffsetPath, GenericOffsetPosition, GenericRayFunction, -}; -use crate::Zero; - -/// The computed value of ray() function. -pub type RayFunction = GenericRayFunction<Angle, Position>; - -/// The computed value of `offset-path`. -pub type OffsetPath = GenericOffsetPath<RayFunction>; - -/// The computed value of `offset-position`. -pub type OffsetPosition = GenericOffsetPosition<LengthPercentage, LengthPercentage>; - -#[inline] -fn is_auto_zero_angle(auto: &bool, angle: &Angle) -> bool { - *auto && angle.is_zero() -} - -/// A computed offset-rotate. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - ToAnimatedZero, - ToCss, - ToResolvedValue, -)] -#[repr(C)] -pub struct OffsetRotate { - /// If auto is false, this is a fixed angle which indicates a - /// constant clockwise rotation transformation applied to it by this - /// specified rotation angle. Otherwise, the angle will be added to - /// the angle of the direction in layout. - #[animation(constant)] - #[css(represents_keyword)] - pub auto: bool, - /// The angle value. - #[css(contextual_skip_if = "is_auto_zero_angle")] - pub angle: Angle, -} - -impl OffsetRotate { - /// Returns "auto 0deg". - #[inline] - pub fn auto() -> Self { - OffsetRotate { - auto: true, - angle: Zero::zero(), - } - } -} diff --git a/components/style/values/computed/outline.rs b/components/style/values/computed/outline.rs deleted file mode 100644 index f872176529f..00000000000 --- a/components/style/values/computed/outline.rs +++ /dev/null @@ -1,7 +0,0 @@ -/* 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/. */ - -//! Computed values for outline properties - -pub use crate::values::specified::OutlineStyle; diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs deleted file mode 100644 index 6f71c912cfb..00000000000 --- a/components/style/values/computed/page.rs +++ /dev/null @@ -1,75 +0,0 @@ -/* 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/. */ - -//! Computed @page at-rule properties and named-page style properties - -use crate::values::computed::length::NonNegativeLength; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics; -use crate::values::generics::size::Size2D; - -use crate::values::specified::page as specified; -pub use generics::page::GenericPageSize; -pub use generics::page::PageOrientation; -pub use generics::page::PageSizeOrientation; -pub use generics::page::PaperSize; -pub use specified::PageName; - -/// Computed value of the @page size descriptor -/// -/// The spec says that the computed value should be the same as the specified -/// value but with all absolute units, but it's not currently possibly observe -/// the computed value of page-size. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] -#[repr(C, u8)] -pub enum PageSize { - /// Specified size, paper size, or paper size and orientation. - Size(Size2D<NonNegativeLength>), - /// `landscape` or `portrait` value, no specified size. - Orientation(PageSizeOrientation), - /// `auto` value - Auto, -} - -impl ToComputedValue for specified::PageSize { - type ComputedValue = PageSize; - - fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue { - match &*self { - Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)), - Self::PaperSize(p, PageSizeOrientation::Landscape) => PageSize::Size(Size2D { - width: p.long_edge().to_computed_value(ctx), - height: p.short_edge().to_computed_value(ctx), - }), - Self::PaperSize(p, PageSizeOrientation::Portrait) => PageSize::Size(Size2D { - width: p.short_edge().to_computed_value(ctx), - height: p.long_edge().to_computed_value(ctx), - }), - Self::Orientation(o) => PageSize::Orientation(*o), - Self::Auto => PageSize::Auto, - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - PageSize::Size(s) => Self::Size(ToComputedValue::from_computed_value(&s)), - PageSize::Orientation(o) => Self::Orientation(o), - PageSize::Auto => Self::Auto, - } - } -} - -impl PageSize { - /// `auto` value. - #[inline] - pub fn auto() -> Self { - PageSize::Auto - } - - /// Whether this is the `auto` value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, PageSize::Auto) - } -} diff --git a/components/style/values/computed/percentage.rs b/components/style/values/computed/percentage.rs deleted file mode 100644 index 994c01594a3..00000000000 --- a/components/style/values/computed/percentage.rs +++ /dev/null @@ -1,136 +0,0 @@ -/* 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/. */ - -//! Computed percentages. - -use crate::values::animated::ToAnimatedValue; -use crate::values::generics::NonNegative; -use crate::values::specified::percentage::ToPercentage; -use crate::values::{serialize_normalized_percentage, CSSFloat}; -use crate::Zero; -use std::fmt; -use style_traits::{CssWriter, ToCss}; - -/// A computed percentage. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Default, - Deserialize, - MallocSizeOf, - PartialEq, - PartialOrd, - Serialize, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct Percentage(pub CSSFloat); - -impl Percentage { - /// 100% - #[inline] - pub fn hundred() -> Self { - Percentage(1.) - } - - /// Returns the absolute value for this percentage. - #[inline] - pub fn abs(&self) -> Self { - Percentage(self.0.abs()) - } - - /// Clamps this percentage to a non-negative percentage. - #[inline] - pub fn clamp_to_non_negative(self) -> Self { - Percentage(self.0.max(0.)) - } -} - -impl Zero for Percentage { - fn zero() -> Self { - Percentage(0.) - } - - fn is_zero(&self) -> bool { - self.0 == 0. - } -} - -impl ToPercentage for Percentage { - fn to_percentage(&self) -> CSSFloat { - self.0 - } -} - -impl std::ops::AddAssign for Percentage { - fn add_assign(&mut self, other: Self) { - self.0 += other.0 - } -} - -impl std::ops::Add for Percentage { - type Output = Self; - - fn add(self, other: Self) -> Self { - Percentage(self.0 + other.0) - } -} - -impl std::ops::Sub for Percentage { - type Output = Self; - - fn sub(self, other: Self) -> Self { - Percentage(self.0 - other.0) - } -} - -impl std::ops::Rem for Percentage { - type Output = Self; - - fn rem(self, other: Self) -> Self { - Percentage(self.0 % other.0) - } -} - -impl ToCss for Percentage { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - serialize_normalized_percentage(self.0, dest) - } -} - -/// A wrapper over a `Percentage`, whose value should be clamped to 0. -pub type NonNegativePercentage = NonNegative<Percentage>; - -impl NonNegativePercentage { - /// 100% - #[inline] - pub fn hundred() -> Self { - NonNegative(Percentage::hundred()) - } -} - -impl ToAnimatedValue for NonNegativePercentage { - type AnimatedValue = Percentage; - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - self.0 - } - - #[inline] - fn from_animated_value(animated: Self::AnimatedValue) -> Self { - NonNegative(animated.clamp_to_non_negative()) - } -} diff --git a/components/style/values/computed/position.rs b/components/style/values/computed/position.rs deleted file mode 100644 index 5a10c0f23d7..00000000000 --- a/components/style/values/computed/position.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* 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/. */ - -//! CSS handling for the computed value of -//! [`position`][position] values. -//! -//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position - -use crate::values::computed::{Integer, LengthPercentage, NonNegativeNumber, Percentage}; -use crate::values::generics::position::AspectRatio as GenericAspectRatio; -use crate::values::generics::position::Position as GenericPosition; -use crate::values::generics::position::PositionComponent as GenericPositionComponent; -use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; -use crate::values::generics::position::ZIndex as GenericZIndex; -pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow}; -use crate::Zero; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// The computed value of a CSS `<position>` -pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>; - -/// The computed value of an `auto | <position>` -pub type PositionOrAuto = GenericPositionOrAuto<Position>; - -/// The computed value of a CSS horizontal position. -pub type HorizontalPosition = LengthPercentage; - -/// The computed value of a CSS vertical position. -pub type VerticalPosition = LengthPercentage; - -impl Position { - /// `50% 50%` - #[inline] - pub fn center() -> Self { - Self::new( - LengthPercentage::new_percent(Percentage(0.5)), - LengthPercentage::new_percent(Percentage(0.5)), - ) - } - - /// `0% 0%` - #[inline] - pub fn zero() -> Self { - Self::new(LengthPercentage::zero(), LengthPercentage::zero()) - } -} - -impl ToCss for Position { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.horizontal.to_css(dest)?; - dest.write_char(' ')?; - self.vertical.to_css(dest) - } -} - -impl GenericPositionComponent for LengthPercentage { - fn is_center(&self) -> bool { - match self.to_percentage() { - Some(Percentage(per)) => per == 0.5, - _ => false, - } - } -} - -/// A computed value for the `z-index` property. -pub type ZIndex = GenericZIndex<Integer>; - -/// A computed value for the `aspect-ratio` property. -pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>; diff --git a/components/style/values/computed/ratio.rs b/components/style/values/computed/ratio.rs deleted file mode 100644 index ae8997cfc06..00000000000 --- a/components/style/values/computed/ratio.rs +++ /dev/null @@ -1,115 +0,0 @@ -/* 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/. */ - -//! `<ratio>` computed values. - -use crate::values::animated::{Animate, Procedure}; -use crate::values::computed::NonNegativeNumber; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::ratio::Ratio as GenericRatio; -use crate::{One, Zero}; -use std::cmp::{Ordering, PartialOrd}; - -/// A computed <ratio> value. -pub type Ratio = GenericRatio<NonNegativeNumber>; - -impl PartialOrd for Ratio { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - f64::partial_cmp( - &((self.0).0 as f64 * (other.1).0 as f64), - &((self.1).0 as f64 * (other.0).0 as f64), - ) - } -} - -/// https://drafts.csswg.org/css-values/#combine-ratio -impl Animate for Ratio { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - // If either <ratio> is degenerate, the values cannot be interpolated. - if self.is_degenerate() || other.is_degenerate() { - return Err(()); - } - - // Addition of <ratio>s is not possible, and based on - // https://drafts.csswg.org/css-values-4/#not-additive, - // we simply use the first value as the result value. - // Besides, the procedure for accumulation should be identical to addition here. - if matches!(procedure, Procedure::Add | Procedure::Accumulate { .. }) { - return Ok(self.clone()); - } - - // The interpolation of a <ratio> is defined by converting each <ratio> to a number by - // dividing the first value by the second (so a ratio of 3 / 2 would become 1.5), taking - // the logarithm of that result (so the 1.5 would become approximately 0.176), then - // interpolating those values. - // - // The result during the interpolation is converted back to a <ratio> by inverting the - // logarithm, then interpreting the result as a <ratio> with the result as the first value - // and 1 as the second value. - let start = self.to_f32().ln(); - let end = other.to_f32().ln(); - let e = std::f32::consts::E; - let result = e.powf(start.animate(&end, procedure)?); - // The range of the result is [0, inf), based on the easing function. - if result.is_zero() || result.is_infinite() { - return Err(()); - } - Ok(Ratio::new(result, 1.0f32)) - } -} - -impl ComputeSquaredDistance for Ratio { - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - if self.is_degenerate() || other.is_degenerate() { - return Err(()); - } - // Use the distance of their logarithm values. (This is used by testing, so don't need to - // care about the base. Here we use the same base as that in animate().) - self.to_f32() - .ln() - .compute_squared_distance(&other.to_f32().ln()) - } -} - -impl Zero for Ratio { - fn zero() -> Self { - Self::new(Zero::zero(), One::one()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - -impl Ratio { - /// Returns a new Ratio. - #[inline] - pub fn new(a: f32, b: f32) -> Self { - GenericRatio(a.into(), b.into()) - } - - /// Returns the used value. A ratio of 0/0 behaves as the ratio 1/0. - /// https://drafts.csswg.org/css-values-4/#ratios - pub fn used_value(self) -> Self { - if self.0.is_zero() && self.1.is_zero() { - Ratio::new(One::one(), Zero::zero()) - } else { - self - } - } - - /// Returns true if this is a degenerate ratio. - /// https://drafts.csswg.org/css-values/#degenerate-ratio - #[inline] - pub fn is_degenerate(&self) -> bool { - self.0.is_zero() || self.1.is_zero() - } - - /// Returns the f32 value by dividing the first value by the second one. - #[inline] - fn to_f32(&self) -> f32 { - debug_assert!(!self.is_degenerate()); - (self.0).0 / (self.1).0 - } -} diff --git a/components/style/values/computed/rect.rs b/components/style/values/computed/rect.rs deleted file mode 100644 index ec44360fc81..00000000000 --- a/components/style/values/computed/rect.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS borders. - -use crate::values::computed::length::NonNegativeLengthOrNumber; -use crate::values::generics::rect::Rect; - -/// A specified rectangle made of four `<length-or-number>` values. -pub type NonNegativeLengthOrNumberRect = Rect<NonNegativeLengthOrNumber>; diff --git a/components/style/values/computed/resolution.rs b/components/style/values/computed/resolution.rs deleted file mode 100644 index 28153911809..00000000000 --- a/components/style/values/computed/resolution.rs +++ /dev/null @@ -1,56 +0,0 @@ -/* 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/. */ - -//! Resolution values: -//! -//! https://drafts.csswg.org/css-values/#resolution - -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::specified; -use crate::values::CSSFloat; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A computed `<resolution>`. -#[repr(C)] -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem)] -pub struct Resolution(CSSFloat); - -impl Resolution { - /// Returns this resolution value as dppx. - #[inline] - pub fn dppx(&self) -> CSSFloat { - self.0 - } - - /// Return a computed `resolution` value from a dppx float value. - #[inline] - pub fn from_dppx(dppx: CSSFloat) -> Self { - Resolution(dppx) - } -} - -impl ToComputedValue for specified::Resolution { - type ComputedValue = Resolution; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - Resolution(crate::values::normalize(self.dppx().max(0.0))) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - specified::Resolution::from_dppx(computed.dppx()) - } -} - -impl ToCss for Resolution { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.dppx().to_css(dest)?; - dest.write_str("dppx") - } -} diff --git a/components/style/values/computed/svg.rs b/components/style/values/computed/svg.rs deleted file mode 100644 index 640c3bfda70..00000000000 --- a/components/style/values/computed/svg.rs +++ /dev/null @@ -1,68 +0,0 @@ -/* 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/. */ - -//! Computed types for SVG properties. - -use crate::values::computed::color::Color; -use crate::values::computed::url::ComputedUrl; -use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity}; -use crate::values::generics::svg as generic; -use crate::Zero; - -pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder}; - -/// Computed SVG Paint value -pub type SVGPaint = generic::GenericSVGPaint<Color, ComputedUrl>; - -/// Computed SVG Paint Kind value -pub type SVGPaintKind = generic::GenericSVGPaintKind<Color, ComputedUrl>; - -impl SVGPaint { - /// Opaque black color - pub fn black() -> Self { - SVGPaint { - kind: generic::SVGPaintKind::Color(Color::black()), - fallback: generic::SVGPaintFallback::Unset, - } - } -} - -/// <length> | <percentage> | <number> | context-value -pub type SVGLength = generic::GenericSVGLength<LengthPercentage>; - -impl SVGLength { - /// `0px` - pub fn zero() -> Self { - generic::SVGLength::LengthPercentage(LengthPercentage::zero()) - } -} - -/// An non-negative wrapper of SVGLength. -pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>; - -impl SVGWidth { - /// `1px`. - pub fn one() -> Self { - use crate::values::generics::NonNegative; - generic::SVGLength::LengthPercentage(NonNegative(LengthPercentage::one())) - } -} - -/// [ <length> | <percentage> | <number> ]# | context-value -pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>; - -impl Default for SVGStrokeDashArray { - fn default() -> Self { - generic::SVGStrokeDashArray::Values(Default::default()) - } -} - -/// <opacity-value> | context-fill-opacity | context-stroke-opacity -pub type SVGOpacity = generic::GenericSVGOpacity<Opacity>; - -impl Default for SVGOpacity { - fn default() -> Self { - generic::SVGOpacity::Opacity(1.) - } -} diff --git a/components/style/values/computed/table.rs b/components/style/values/computed/table.rs deleted file mode 100644 index 47109e20eca..00000000000 --- a/components/style/values/computed/table.rs +++ /dev/null @@ -1,7 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values related to tables. - -pub use super::specified::table::CaptionSide; diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs deleted file mode 100644 index 3c9d13031f2..00000000000 --- a/components/style/values/computed/text.rs +++ /dev/null @@ -1,262 +0,0 @@ -/* 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/. */ - -//! Computed types for text properties. - -#[cfg(feature = "servo")] -use crate::properties::StyleBuilder; -use crate::values::computed::length::{Length, LengthPercentage}; -use crate::values::computed::{Context, NonNegativeLength, NonNegativeNumber, ToComputedValue}; -use crate::values::generics::text::InitialLetter as GenericInitialLetter; -use crate::values::generics::text::LineHeight as GenericLineHeight; -use crate::values::generics::text::{GenericTextDecorationLength, Spacing}; -use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue}; -use crate::values::specified::text::{self as specified, TextOverflowSide}; -use crate::values::specified::text::{TextEmphasisFillMode, TextEmphasisShapeKeyword}; -use crate::values::{CSSFloat, CSSInteger}; -use crate::Zero; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -pub use crate::values::specified::text::{ - MozControlCharacterVisibility, TextAlignLast, TextUnderlinePosition, -}; -pub use crate::values::specified::HyphenateCharacter; -pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; -pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; -pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; - -/// A computed value for the `initial-letter` property. -pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>; - -/// Implements type for `text-decoration-thickness` property. -pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>; - -/// The computed value of `text-align`. -pub type TextAlign = specified::TextAlignKeyword; - -/// A computed value for the `letter-spacing` property. -#[repr(transparent)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedValue, - ToAnimatedZero, - ToResolvedValue, -)] -pub struct LetterSpacing(pub Length); - -impl LetterSpacing { - /// Return the `normal` computed value, which is just zero. - #[inline] - pub fn normal() -> Self { - LetterSpacing(Length::zero()) - } -} - -impl ToCss for LetterSpacing { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - // https://drafts.csswg.org/css-text/#propdef-letter-spacing - // - // For legacy reasons, a computed letter-spacing of zero yields a - // resolved value (getComputedStyle() return value) of normal. - if self.0.is_zero() { - return dest.write_str("normal"); - } - self.0.to_css(dest) - } -} - -impl ToComputedValue for specified::LetterSpacing { - type ComputedValue = LetterSpacing; - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - Spacing::Normal => LetterSpacing(Length::zero()), - Spacing::Value(ref v) => LetterSpacing(v.to_computed_value(context)), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - if computed.0.is_zero() { - return Spacing::Normal; - } - Spacing::Value(ToComputedValue::from_computed_value(&computed.0)) - } -} - -/// A computed value for the `word-spacing` property. -pub type WordSpacing = LengthPercentage; - -impl ToComputedValue for specified::WordSpacing { - type ComputedValue = WordSpacing; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - Spacing::Normal => LengthPercentage::zero(), - Spacing::Value(ref v) => v.to_computed_value(context), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Spacing::Value(ToComputedValue::from_computed_value(computed)) - } -} - -/// A computed value for the `line-height` property. -pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLength>; - -impl ToResolvedValue for LineHeight { - type ResolvedValue = Self; - - fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue { - // Resolve <number> to an absolute <length> based on font size. - #[cfg(feature = "gecko")] { - if matches!(self, Self::Normal | Self::MozBlockHeight) { - return self; - } - let wm = context.style.writing_mode; - let vertical = wm.is_vertical() && !wm.is_sideways(); - return Self::Length(context.device.calc_line_height( - &self, - vertical, - context.style.get_font(), - Some(context.element_info.element), - )); - } - if let LineHeight::Number(num) = &self { - let size = context.style.get_font().clone_font_size().computed_size(); - LineHeight::Length(NonNegativeLength::new(size.px() * num.0)) - } else { - self - } - } - - #[inline] - fn from_resolved_value(value: Self::ResolvedValue) -> Self { - value - } -} - -impl WordSpacing { - /// Return the `normal` computed value, which is just zero. - #[inline] - pub fn normal() -> Self { - LengthPercentage::zero() - } -} - -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue)] -#[repr(C)] -/// text-overflow. -/// When the specified value only has one side, that's the "second" -/// side, and the sides are logical, so "second" means "end". The -/// start side is Clip in that case. -/// -/// When the specified value has two sides, those are our "first" -/// and "second" sides, and they are physical sides ("left" and -/// "right"). -pub struct TextOverflow { - /// First side - pub first: TextOverflowSide, - /// Second side - pub second: TextOverflowSide, - /// True if the specified value only has one side. - pub sides_are_logical: bool, -} - -impl TextOverflow { - /// Returns the initial `text-overflow` value - pub fn get_initial_value() -> TextOverflow { - TextOverflow { - first: TextOverflowSide::Clip, - second: TextOverflowSide::Clip, - sides_are_logical: true, - } - } -} - -impl ToCss for TextOverflow { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.sides_are_logical { - debug_assert_eq!(self.first, TextOverflowSide::Clip); - self.second.to_css(dest)?; - } else { - self.first.to_css(dest)?; - dest.write_char(' ')?; - self.second.to_css(dest)?; - } - Ok(()) - } -} - -/// A struct that represents the _used_ value of the text-decoration property. -/// -/// FIXME(emilio): This is done at style resolution time, though probably should -/// be done at layout time, otherwise we need to account for display: contents -/// and similar stuff when we implement it. -/// -/// FIXME(emilio): Also, should be just a bitfield instead of three bytes. -#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToResolvedValue)] -pub struct TextDecorationsInEffect { - /// Whether an underline is in effect. - pub underline: bool, - /// Whether an overline decoration is in effect. - pub overline: bool, - /// Whether a line-through style is in effect. - pub line_through: bool, -} - -impl TextDecorationsInEffect { - /// Computes the text-decorations in effect for a given style. - #[cfg(feature = "servo")] - pub fn from_style(style: &StyleBuilder) -> Self { - // Start with no declarations if this is an atomic inline-level box; - // otherwise, start with the declarations in effect and add in the text - // decorations that this block specifies. - let mut result = if style.get_box().clone_display().is_atomic_inline_level() { - Self::default() - } else { - style - .get_parent_inherited_text() - .text_decorations_in_effect - .clone() - }; - - let line = style.get_text().clone_text_decoration_line(); - - result.underline |= line.contains(TextDecorationLine::UNDERLINE); - result.overline |= line.contains(TextDecorationLine::OVERLINE); - result.line_through |= line.contains(TextDecorationLine::LINE_THROUGH); - - result - } -} - -/// Computed value for the text-emphasis-style property -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] -#[allow(missing_docs)] -#[repr(C, u8)] -pub enum TextEmphasisStyle { - /// [ <fill> || <shape> ] - Keyword { - #[css(skip_if = "TextEmphasisFillMode::is_filled")] - fill: TextEmphasisFillMode, - shape: TextEmphasisShapeKeyword, - }, - /// `none` - None, - /// `<string>` (of which only the first grapheme cluster will be used). - String(crate::OwnedStr), -} diff --git a/components/style/values/computed/time.rs b/components/style/values/computed/time.rs deleted file mode 100644 index f542333a823..00000000000 --- a/components/style/values/computed/time.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* 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/. */ - -//! Computed time values. - -use crate::values::CSSFloat; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A computed `<time>` value. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(C)] -pub struct Time { - seconds: CSSFloat, -} - -impl Time { - /// Creates a time value from a seconds amount. - pub fn from_seconds(seconds: CSSFloat) -> Self { - Time { seconds } - } - - /// Returns `0s`. - pub fn zero() -> Self { - Self::from_seconds(0.0) - } - - /// Returns the amount of seconds this time represents. - #[inline] - pub fn seconds(&self) -> CSSFloat { - self.seconds - } -} - -impl ToCss for Time { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.seconds().to_css(dest)?; - dest.write_char('s') - } -} diff --git a/components/style/values/computed/transform.rs b/components/style/values/computed/transform.rs deleted file mode 100644 index d70349ee0fe..00000000000 --- a/components/style/values/computed/transform.rs +++ /dev/null @@ -1,558 +0,0 @@ -/* 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/. */ - -//! Computed types for CSS values that are related to transformations. - -use super::CSSFloat; -use crate::values::animated::transform::{Perspective, Scale3D, Translate3D}; -use crate::values::animated::ToAnimatedZero; -use crate::values::computed::{Angle, Integer, Length, LengthPercentage, Number, Percentage}; -use crate::values::generics::transform as generic; -use crate::Zero; -use euclid::default::{Transform3D, Vector3D}; - -pub use crate::values::generics::transform::TransformStyle; - -/// A single operation in a computed CSS `transform` -pub type TransformOperation = - generic::GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>; -/// A computed CSS `transform` -pub type Transform = generic::GenericTransform<TransformOperation>; - -/// The computed value of a CSS `<transform-origin>` -pub type TransformOrigin = - generic::GenericTransformOrigin<LengthPercentage, LengthPercentage, Length>; - -/// The computed value of the `perspective()` transform function. -pub type PerspectiveFunction = generic::PerspectiveFunction<Length>; - -/// A vector to represent the direction vector (rotate axis) for Rotate3D. -pub type DirectionVector = Vector3D<CSSFloat>; - -impl TransformOrigin { - /// Returns the initial computed value for `transform-origin`. - #[inline] - pub fn initial_value() -> Self { - Self::new( - LengthPercentage::new_percent(Percentage(0.5)), - LengthPercentage::new_percent(Percentage(0.5)), - Length::new(0.), - ) - } -} - -/// computed value of matrix3d() -pub type Matrix3D = generic::Matrix3D<Number>; - -/// computed value of matrix() -pub type Matrix = generic::Matrix<Number>; - -// we rustfmt_skip here because we want the matrices to look like -// matrices instead of being split across lines -#[cfg_attr(rustfmt, rustfmt_skip)] -impl Matrix3D { - /// Get an identity matrix - #[inline] - pub fn identity() -> Self { - Self { - m11: 1.0, m12: 0.0, m13: 0.0, m14: 0.0, - m21: 0.0, m22: 1.0, m23: 0.0, m24: 0.0, - m31: 0.0, m32: 0.0, m33: 1.0, m34: 0.0, - m41: 0., m42: 0., m43: 0., m44: 1.0 - } - } - - /// Convert to a 2D Matrix - #[inline] - pub fn into_2d(self) -> Result<Matrix, ()> { - if self.m13 == 0. && self.m23 == 0. && - self.m31 == 0. && self.m32 == 0. && - self.m33 == 1. && self.m34 == 0. && - self.m14 == 0. && self.m24 == 0. && - self.m43 == 0. && self.m44 == 1. { - Ok(Matrix { - a: self.m11, c: self.m21, e: self.m41, - b: self.m12, d: self.m22, f: self.m42, - }) - } else { - Err(()) - } - } - - /// Return true if this has 3D components. - #[inline] - pub fn is_3d(&self) -> bool { - self.m13 != 0.0 || self.m14 != 0.0 || - self.m23 != 0.0 || self.m24 != 0.0 || - self.m31 != 0.0 || self.m32 != 0.0 || - self.m33 != 1.0 || self.m34 != 0.0 || - self.m43 != 0.0 || self.m44 != 1.0 - } - - /// Return determinant value. - #[inline] - pub fn determinant(&self) -> CSSFloat { - self.m14 * self.m23 * self.m32 * self.m41 - - self.m13 * self.m24 * self.m32 * self.m41 - - self.m14 * self.m22 * self.m33 * self.m41 + - self.m12 * self.m24 * self.m33 * self.m41 + - self.m13 * self.m22 * self.m34 * self.m41 - - self.m12 * self.m23 * self.m34 * self.m41 - - self.m14 * self.m23 * self.m31 * self.m42 + - self.m13 * self.m24 * self.m31 * self.m42 + - self.m14 * self.m21 * self.m33 * self.m42 - - self.m11 * self.m24 * self.m33 * self.m42 - - self.m13 * self.m21 * self.m34 * self.m42 + - self.m11 * self.m23 * self.m34 * self.m42 + - self.m14 * self.m22 * self.m31 * self.m43 - - self.m12 * self.m24 * self.m31 * self.m43 - - self.m14 * self.m21 * self.m32 * self.m43 + - self.m11 * self.m24 * self.m32 * self.m43 + - self.m12 * self.m21 * self.m34 * self.m43 - - self.m11 * self.m22 * self.m34 * self.m43 - - self.m13 * self.m22 * self.m31 * self.m44 + - self.m12 * self.m23 * self.m31 * self.m44 + - self.m13 * self.m21 * self.m32 * self.m44 - - self.m11 * self.m23 * self.m32 * self.m44 - - self.m12 * self.m21 * self.m33 * self.m44 + - self.m11 * self.m22 * self.m33 * self.m44 - } - - /// Transpose a matrix. - #[inline] - pub fn transpose(&self) -> Self { - Self { - m11: self.m11, m12: self.m21, m13: self.m31, m14: self.m41, - m21: self.m12, m22: self.m22, m23: self.m32, m24: self.m42, - m31: self.m13, m32: self.m23, m33: self.m33, m34: self.m43, - m41: self.m14, m42: self.m24, m43: self.m34, m44: self.m44, - } - } - - /// Return inverse matrix. - pub fn inverse(&self) -> Result<Matrix3D, ()> { - let mut det = self.determinant(); - - if det == 0.0 { - return Err(()); - } - - det = 1.0 / det; - let x = Matrix3D { - m11: det * - (self.m23 * self.m34 * self.m42 - self.m24 * self.m33 * self.m42 + - self.m24 * self.m32 * self.m43 - self.m22 * self.m34 * self.m43 - - self.m23 * self.m32 * self.m44 + self.m22 * self.m33 * self.m44), - m12: det * - (self.m14 * self.m33 * self.m42 - self.m13 * self.m34 * self.m42 - - self.m14 * self.m32 * self.m43 + self.m12 * self.m34 * self.m43 + - self.m13 * self.m32 * self.m44 - self.m12 * self.m33 * self.m44), - m13: det * - (self.m13 * self.m24 * self.m42 - self.m14 * self.m23 * self.m42 + - self.m14 * self.m22 * self.m43 - self.m12 * self.m24 * self.m43 - - self.m13 * self.m22 * self.m44 + self.m12 * self.m23 * self.m44), - m14: det * - (self.m14 * self.m23 * self.m32 - self.m13 * self.m24 * self.m32 - - self.m14 * self.m22 * self.m33 + self.m12 * self.m24 * self.m33 + - self.m13 * self.m22 * self.m34 - self.m12 * self.m23 * self.m34), - m21: det * - (self.m24 * self.m33 * self.m41 - self.m23 * self.m34 * self.m41 - - self.m24 * self.m31 * self.m43 + self.m21 * self.m34 * self.m43 + - self.m23 * self.m31 * self.m44 - self.m21 * self.m33 * self.m44), - m22: det * - (self.m13 * self.m34 * self.m41 - self.m14 * self.m33 * self.m41 + - self.m14 * self.m31 * self.m43 - self.m11 * self.m34 * self.m43 - - self.m13 * self.m31 * self.m44 + self.m11 * self.m33 * self.m44), - m23: det * - (self.m14 * self.m23 * self.m41 - self.m13 * self.m24 * self.m41 - - self.m14 * self.m21 * self.m43 + self.m11 * self.m24 * self.m43 + - self.m13 * self.m21 * self.m44 - self.m11 * self.m23 * self.m44), - m24: det * - (self.m13 * self.m24 * self.m31 - self.m14 * self.m23 * self.m31 + - self.m14 * self.m21 * self.m33 - self.m11 * self.m24 * self.m33 - - self.m13 * self.m21 * self.m34 + self.m11 * self.m23 * self.m34), - m31: det * - (self.m22 * self.m34 * self.m41 - self.m24 * self.m32 * self.m41 + - self.m24 * self.m31 * self.m42 - self.m21 * self.m34 * self.m42 - - self.m22 * self.m31 * self.m44 + self.m21 * self.m32 * self.m44), - m32: det * - (self.m14 * self.m32 * self.m41 - self.m12 * self.m34 * self.m41 - - self.m14 * self.m31 * self.m42 + self.m11 * self.m34 * self.m42 + - self.m12 * self.m31 * self.m44 - self.m11 * self.m32 * self.m44), - m33: det * - (self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + - self.m14 * self.m21 * self.m42 - self.m11 * self.m24 * self.m42 - - self.m12 * self.m21 * self.m44 + self.m11 * self.m22 * self.m44), - m34: det * - (self.m14 * self.m22 * self.m31 - self.m12 * self.m24 * self.m31 - - self.m14 * self.m21 * self.m32 + self.m11 * self.m24 * self.m32 + - self.m12 * self.m21 * self.m34 - self.m11 * self.m22 * self.m34), - m41: det * - (self.m23 * self.m32 * self.m41 - self.m22 * self.m33 * self.m41 - - self.m23 * self.m31 * self.m42 + self.m21 * self.m33 * self.m42 + - self.m22 * self.m31 * self.m43 - self.m21 * self.m32 * self.m43), - m42: det * - (self.m12 * self.m33 * self.m41 - self.m13 * self.m32 * self.m41 + - self.m13 * self.m31 * self.m42 - self.m11 * self.m33 * self.m42 - - self.m12 * self.m31 * self.m43 + self.m11 * self.m32 * self.m43), - m43: det * - (self.m13 * self.m22 * self.m41 - self.m12 * self.m23 * self.m41 - - self.m13 * self.m21 * self.m42 + self.m11 * self.m23 * self.m42 + - self.m12 * self.m21 * self.m43 - self.m11 * self.m22 * self.m43), - m44: det * - (self.m12 * self.m23 * self.m31 - self.m13 * self.m22 * self.m31 + - self.m13 * self.m21 * self.m32 - self.m11 * self.m23 * self.m32 - - self.m12 * self.m21 * self.m33 + self.m11 * self.m22 * self.m33), - }; - - Ok(x) - } - - /// Multiply `pin * self`. - #[inline] - pub fn pre_mul_point4(&self, pin: &[f32; 4]) -> [f32; 4] { - [ - pin[0] * self.m11 + pin[1] * self.m21 + pin[2] * self.m31 + pin[3] * self.m41, - pin[0] * self.m12 + pin[1] * self.m22 + pin[2] * self.m32 + pin[3] * self.m42, - pin[0] * self.m13 + pin[1] * self.m23 + pin[2] * self.m33 + pin[3] * self.m43, - pin[0] * self.m14 + pin[1] * self.m24 + pin[2] * self.m34 + pin[3] * self.m44, - ] - } - - /// Return the multiplication of two 4x4 matrices. - #[inline] - pub fn multiply(&self, other: &Self) -> Self { - Matrix3D { - m11: self.m11 * other.m11 + self.m12 * other.m21 + - self.m13 * other.m31 + self.m14 * other.m41, - m12: self.m11 * other.m12 + self.m12 * other.m22 + - self.m13 * other.m32 + self.m14 * other.m42, - m13: self.m11 * other.m13 + self.m12 * other.m23 + - self.m13 * other.m33 + self.m14 * other.m43, - m14: self.m11 * other.m14 + self.m12 * other.m24 + - self.m13 * other.m34 + self.m14 * other.m44, - m21: self.m21 * other.m11 + self.m22 * other.m21 + - self.m23 * other.m31 + self.m24 * other.m41, - m22: self.m21 * other.m12 + self.m22 * other.m22 + - self.m23 * other.m32 + self.m24 * other.m42, - m23: self.m21 * other.m13 + self.m22 * other.m23 + - self.m23 * other.m33 + self.m24 * other.m43, - m24: self.m21 * other.m14 + self.m22 * other.m24 + - self.m23 * other.m34 + self.m24 * other.m44, - m31: self.m31 * other.m11 + self.m32 * other.m21 + - self.m33 * other.m31 + self.m34 * other.m41, - m32: self.m31 * other.m12 + self.m32 * other.m22 + - self.m33 * other.m32 + self.m34 * other.m42, - m33: self.m31 * other.m13 + self.m32 * other.m23 + - self.m33 * other.m33 + self.m34 * other.m43, - m34: self.m31 * other.m14 + self.m32 * other.m24 + - self.m33 * other.m34 + self.m34 * other.m44, - m41: self.m41 * other.m11 + self.m42 * other.m21 + - self.m43 * other.m31 + self.m44 * other.m41, - m42: self.m41 * other.m12 + self.m42 * other.m22 + - self.m43 * other.m32 + self.m44 * other.m42, - m43: self.m41 * other.m13 + self.m42 * other.m23 + - self.m43 * other.m33 + self.m44 * other.m43, - m44: self.m41 * other.m14 + self.m42 * other.m24 + - self.m43 * other.m34 + self.m44 * other.m44, - } - } - - /// Scale the matrix by a factor. - #[inline] - pub fn scale_by_factor(&mut self, scaling_factor: CSSFloat) { - self.m11 *= scaling_factor; - self.m12 *= scaling_factor; - self.m13 *= scaling_factor; - self.m14 *= scaling_factor; - self.m21 *= scaling_factor; - self.m22 *= scaling_factor; - self.m23 *= scaling_factor; - self.m24 *= scaling_factor; - self.m31 *= scaling_factor; - self.m32 *= scaling_factor; - self.m33 *= scaling_factor; - self.m34 *= scaling_factor; - self.m41 *= scaling_factor; - self.m42 *= scaling_factor; - self.m43 *= scaling_factor; - self.m44 *= scaling_factor; - } - - /// Return the matrix 3x3 part (top-left corner). - /// This is used by retrieving the scale and shear factors - /// during decomposing a 3d matrix. - #[inline] - pub fn get_matrix_3x3_part(&self) -> [[f32; 3]; 3] { - [ - [ self.m11, self.m12, self.m13 ], - [ self.m21, self.m22, self.m23 ], - [ self.m31, self.m32, self.m33 ], - ] - } - - /// Set perspective on the matrix. - #[inline] - pub fn set_perspective(&mut self, perspective: &Perspective) { - self.m14 = perspective.0; - self.m24 = perspective.1; - self.m34 = perspective.2; - self.m44 = perspective.3; - } - - /// Apply translate on the matrix. - #[inline] - pub fn apply_translate(&mut self, translate: &Translate3D) { - self.m41 += translate.0 * self.m11 + translate.1 * self.m21 + translate.2 * self.m31; - self.m42 += translate.0 * self.m12 + translate.1 * self.m22 + translate.2 * self.m32; - self.m43 += translate.0 * self.m13 + translate.1 * self.m23 + translate.2 * self.m33; - self.m44 += translate.0 * self.m14 + translate.1 * self.m24 + translate.2 * self.m34; - } - - /// Apply scale on the matrix. - #[inline] - pub fn apply_scale(&mut self, scale: &Scale3D) { - self.m11 *= scale.0; - self.m12 *= scale.0; - self.m13 *= scale.0; - self.m14 *= scale.0; - self.m21 *= scale.1; - self.m22 *= scale.1; - self.m23 *= scale.1; - self.m24 *= scale.1; - self.m31 *= scale.2; - self.m32 *= scale.2; - self.m33 *= scale.2; - self.m34 *= scale.2; - } -} - -#[cfg_attr(rustfmt, rustfmt_skip)] -impl Matrix { - #[inline] - /// Get an identity matrix - pub fn identity() -> Self { - Self { - a: 1., c: 0., /* 0 0*/ - b: 0., d: 1., /* 0 0*/ - /* 0 0 1 0 */ - e: 0., f: 0., /* 0 1 */ - } - } -} - -#[cfg_attr(rustfmt, rustfmt_skip)] -impl From<Matrix> for Matrix3D { - fn from(m: Matrix) -> Self { - Self { - m11: m.a, m12: m.b, m13: 0.0, m14: 0.0, - m21: m.c, m22: m.d, m23: 0.0, m24: 0.0, - m31: 0.0, m32: 0.0, m33: 1.0, m34: 0.0, - m41: m.e, m42: m.f, m43: 0.0, m44: 1.0 - } - } -} - -#[cfg_attr(rustfmt, rustfmt_skip)] -impl From<Transform3D<CSSFloat>> for Matrix3D { - #[inline] - fn from(m: Transform3D<CSSFloat>) -> Self { - Matrix3D { - m11: m.m11, m12: m.m12, m13: m.m13, m14: m.m14, - m21: m.m21, m22: m.m22, m23: m.m23, m24: m.m24, - m31: m.m31, m32: m.m32, m33: m.m33, m34: m.m34, - m41: m.m41, m42: m.m42, m43: m.m43, m44: m.m44 - } - } -} - -impl TransformOperation { - /// Convert to a Translate3D. - /// - /// Must be called on a Translate function - pub fn to_translate_3d(&self) -> Self { - match *self { - generic::TransformOperation::Translate3D(..) => self.clone(), - generic::TransformOperation::TranslateX(ref x) => { - generic::TransformOperation::Translate3D( - x.clone(), - LengthPercentage::zero(), - Length::zero(), - ) - }, - generic::TransformOperation::Translate(ref x, ref y) => { - generic::TransformOperation::Translate3D(x.clone(), y.clone(), Length::zero()) - }, - generic::TransformOperation::TranslateY(ref y) => { - generic::TransformOperation::Translate3D( - LengthPercentage::zero(), - y.clone(), - Length::zero(), - ) - }, - generic::TransformOperation::TranslateZ(ref z) => { - generic::TransformOperation::Translate3D( - LengthPercentage::zero(), - LengthPercentage::zero(), - z.clone(), - ) - }, - _ => unreachable!(), - } - } - - /// Convert to a Rotate3D. - /// - /// Must be called on a Rotate function. - pub fn to_rotate_3d(&self) -> Self { - match *self { - generic::TransformOperation::Rotate3D(..) => self.clone(), - generic::TransformOperation::RotateZ(ref angle) | - generic::TransformOperation::Rotate(ref angle) => { - generic::TransformOperation::Rotate3D(0., 0., 1., angle.clone()) - }, - generic::TransformOperation::RotateX(ref angle) => { - generic::TransformOperation::Rotate3D(1., 0., 0., angle.clone()) - }, - generic::TransformOperation::RotateY(ref angle) => { - generic::TransformOperation::Rotate3D(0., 1., 0., angle.clone()) - }, - _ => unreachable!(), - } - } - - /// Convert to a Scale3D. - /// - /// Must be called on a Scale function - pub fn to_scale_3d(&self) -> Self { - match *self { - generic::TransformOperation::Scale3D(..) => self.clone(), - generic::TransformOperation::Scale(x, y) => { - generic::TransformOperation::Scale3D(x, y, 1.) - }, - generic::TransformOperation::ScaleX(x) => { - generic::TransformOperation::Scale3D(x, 1., 1.) - }, - generic::TransformOperation::ScaleY(y) => { - generic::TransformOperation::Scale3D(1., y, 1.) - }, - generic::TransformOperation::ScaleZ(z) => { - generic::TransformOperation::Scale3D(1., 1., z) - }, - _ => unreachable!(), - } - } -} - -/// Build an equivalent 'identity transform function list' based -/// on an existing transform list. -/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation -impl ToAnimatedZero for TransformOperation { - fn to_animated_zero(&self) -> Result<Self, ()> { - match *self { - generic::TransformOperation::Matrix3D(..) => { - Ok(generic::TransformOperation::Matrix3D(Matrix3D::identity())) - }, - generic::TransformOperation::Matrix(..) => { - Ok(generic::TransformOperation::Matrix(Matrix::identity())) - }, - generic::TransformOperation::Skew(sx, sy) => Ok(generic::TransformOperation::Skew( - sx.to_animated_zero()?, - sy.to_animated_zero()?, - )), - generic::TransformOperation::SkewX(s) => { - Ok(generic::TransformOperation::SkewX(s.to_animated_zero()?)) - }, - generic::TransformOperation::SkewY(s) => { - Ok(generic::TransformOperation::SkewY(s.to_animated_zero()?)) - }, - generic::TransformOperation::Translate3D(ref tx, ref ty, ref tz) => { - Ok(generic::TransformOperation::Translate3D( - tx.to_animated_zero()?, - ty.to_animated_zero()?, - tz.to_animated_zero()?, - )) - }, - generic::TransformOperation::Translate(ref tx, ref ty) => { - Ok(generic::TransformOperation::Translate( - tx.to_animated_zero()?, - ty.to_animated_zero()?, - )) - }, - generic::TransformOperation::TranslateX(ref t) => Ok( - generic::TransformOperation::TranslateX(t.to_animated_zero()?), - ), - generic::TransformOperation::TranslateY(ref t) => Ok( - generic::TransformOperation::TranslateY(t.to_animated_zero()?), - ), - generic::TransformOperation::TranslateZ(ref t) => Ok( - generic::TransformOperation::TranslateZ(t.to_animated_zero()?), - ), - generic::TransformOperation::Scale3D(..) => { - Ok(generic::TransformOperation::Scale3D(1.0, 1.0, 1.0)) - }, - generic::TransformOperation::Scale(_, _) => { - Ok(generic::TransformOperation::Scale(1.0, 1.0)) - }, - generic::TransformOperation::ScaleX(..) => Ok(generic::TransformOperation::ScaleX(1.0)), - generic::TransformOperation::ScaleY(..) => Ok(generic::TransformOperation::ScaleY(1.0)), - generic::TransformOperation::ScaleZ(..) => Ok(generic::TransformOperation::ScaleZ(1.0)), - generic::TransformOperation::Rotate3D(x, y, z, a) => { - let (x, y, z, _) = generic::get_normalized_vector_and_angle(x, y, z, a); - Ok(generic::TransformOperation::Rotate3D( - x, - y, - z, - Angle::zero(), - )) - }, - generic::TransformOperation::RotateX(_) => { - Ok(generic::TransformOperation::RotateX(Angle::zero())) - }, - generic::TransformOperation::RotateY(_) => { - Ok(generic::TransformOperation::RotateY(Angle::zero())) - }, - generic::TransformOperation::RotateZ(_) => { - Ok(generic::TransformOperation::RotateZ(Angle::zero())) - }, - generic::TransformOperation::Rotate(_) => { - Ok(generic::TransformOperation::Rotate(Angle::zero())) - }, - generic::TransformOperation::Perspective(_) => Ok( - generic::TransformOperation::Perspective(generic::PerspectiveFunction::None), - ), - generic::TransformOperation::AccumulateMatrix { .. } | - generic::TransformOperation::InterpolateMatrix { .. } => { - // AccumulateMatrix/InterpolateMatrix: We do interpolation on - // AccumulateMatrix/InterpolateMatrix by reading it as a ComputedMatrix - // (with layout information), and then do matrix interpolation. - // - // Therefore, we use an identity matrix to represent the identity transform list. - // http://dev.w3.org/csswg/css-transforms/#identity-transform-function - Ok(generic::TransformOperation::Matrix3D(Matrix3D::identity())) - }, - } - } -} - -impl ToAnimatedZero for Transform { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Ok(generic::Transform( - self.0 - .iter() - .map(|op| op.to_animated_zero()) - .collect::<Result<crate::OwnedSlice<_>, _>>()?, - )) - } -} - -/// A computed CSS `rotate` -pub type Rotate = generic::GenericRotate<Number, Angle>; - -/// A computed CSS `translate` -pub type Translate = generic::GenericTranslate<LengthPercentage, Length>; - -/// A computed CSS `scale` -pub type Scale = generic::GenericScale<Number>; diff --git a/components/style/values/computed/ui.rs b/components/style/values/computed/ui.rs deleted file mode 100644 index 6fa5137adf0..00000000000 --- a/components/style/values/computed/ui.rs +++ /dev/null @@ -1,22 +0,0 @@ -/* 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/. */ - -//! Computed values for UI properties - -use crate::values::computed::color::Color; -use crate::values::computed::image::Image; -use crate::values::computed::Number; -use crate::values::generics::ui as generics; - -pub use crate::values::specified::ui::CursorKind; -pub use crate::values::specified::ui::{BoolInteger, UserSelect}; - -/// A computed value for the `cursor` property. -pub type Cursor = generics::GenericCursor<CursorImage>; - -/// A computed value for item of `image cursors`. -pub type CursorImage = generics::GenericCursorImage<Image, Number>; - -/// A computed value for `scrollbar-color` property. -pub type ScrollbarColor = generics::GenericScrollbarColor<Color>; diff --git a/components/style/values/computed/url.rs b/components/style/values/computed/url.rs deleted file mode 100644 index 9f0d8f5bb3e..00000000000 --- a/components/style/values/computed/url.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* 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/. */ - -//! Common handling for the computed value CSS url() values. - -use crate::values::generics::url::UrlOrNone as GenericUrlOrNone; - -#[cfg(feature = "gecko")] -pub use crate::gecko::url::{ComputedImageUrl, ComputedUrl}; -#[cfg(feature = "servo")] -pub use crate::servo::url::{ComputedImageUrl, ComputedUrl}; - -/// Computed <url> | <none> -pub type UrlOrNone = GenericUrlOrNone<ComputedUrl>; diff --git a/components/style/values/distance.rs b/components/style/values/distance.rs deleted file mode 100644 index fef376cf5f7..00000000000 --- a/components/style/values/distance.rs +++ /dev/null @@ -1,138 +0,0 @@ -/* 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/. */ - -//! Machinery to compute distances between animatable values. - -use app_units::Au; -use euclid::default::Size2D; -use std::iter::Sum; -use std::ops::Add; - -/// A trait to compute squared distances between two animatable values. -/// -/// This trait is derivable with `#[derive(ComputeSquaredDistance)]`. The derived -/// implementation uses a `match` expression with identical patterns for both -/// `self` and `other`, calling `ComputeSquaredDistance::compute_squared_distance` -/// on each fields of the values. -/// -/// If a variant is annotated with `#[animation(error)]`, the corresponding -/// `match` arm returns an error. -/// -/// Trait bounds for type parameter `Foo` can be opted out of with -/// `#[animation(no_bound(Foo))]` on the type definition, trait bounds for -/// fields can be opted into with `#[distance(field_bound)]` on the field. -pub trait ComputeSquaredDistance { - /// Computes the squared distance between two animatable values. - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()>; -} - -/// A distance between two animatable values. -#[derive(Add, Clone, Copy, Debug, From)] -pub struct SquaredDistance { - value: f64, -} - -impl SquaredDistance { - /// Returns a squared distance from its square root. - #[inline] - pub fn from_sqrt(sqrt: f64) -> Self { - Self { value: sqrt * sqrt } - } -} - -impl ComputeSquaredDistance for u16 { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(SquaredDistance::from_sqrt( - ((*self as f64) - (*other as f64)).abs(), - )) - } -} - -impl ComputeSquaredDistance for i16 { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(SquaredDistance::from_sqrt((*self - *other).abs() as f64)) - } -} - -impl ComputeSquaredDistance for i32 { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(SquaredDistance::from_sqrt((*self - *other).abs() as f64)) - } -} - -impl ComputeSquaredDistance for f32 { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(SquaredDistance::from_sqrt((*self - *other).abs() as f64)) - } -} - -impl ComputeSquaredDistance for f64 { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(SquaredDistance::from_sqrt((*self - *other).abs())) - } -} - -impl ComputeSquaredDistance for Au { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - self.0.compute_squared_distance(&other.0) - } -} - -impl<T> ComputeSquaredDistance for Box<T> -where - T: ComputeSquaredDistance, -{ - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - (**self).compute_squared_distance(&**other) - } -} - -impl<T> ComputeSquaredDistance for Option<T> -where - T: ComputeSquaredDistance, -{ - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - match (self.as_ref(), other.as_ref()) { - (Some(this), Some(other)) => this.compute_squared_distance(other), - (None, None) => Ok(SquaredDistance::from_sqrt(0.)), - _ => Err(()), - } - } -} - -impl<T> ComputeSquaredDistance for Size2D<T> -where - T: ComputeSquaredDistance, -{ - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(self.width.compute_squared_distance(&other.width)? + - self.height.compute_squared_distance(&other.height)?) - } -} - -impl SquaredDistance { - /// Returns the square root of this squared distance. - #[inline] - pub fn sqrt(self) -> f64 { - self.value.sqrt() - } -} - -impl Sum for SquaredDistance { - fn sum<I>(iter: I) -> Self - where - I: Iterator<Item = Self>, - { - iter.fold(SquaredDistance::from_sqrt(0.), Add::add) - } -} diff --git a/components/style/values/generics/animation.rs b/components/style/values/generics/animation.rs deleted file mode 100644 index edee9e9f257..00000000000 --- a/components/style/values/generics/animation.rs +++ /dev/null @@ -1,140 +0,0 @@ -/* 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/. */ - -//! Generic values for properties related to animations and transitions. - -use crate::values::generics::length::GenericLengthPercentageOrAuto; -use crate::values::specified::animation::{ScrollAxis, ScrollFunction}; -use crate::values::TimelineName; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// The view() notation. -/// https://drafts.csswg.org/scroll-animations-1/#view-notation -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(function = "view")] -#[repr(C)] -pub struct GenericViewFunction<LengthPercent> { - /// The axis of scrolling that drives the progress of the timeline. - #[css(skip_if = "ScrollAxis::is_default")] - pub axis: ScrollAxis, - /// An adjustment of the view progress visibility range. - #[css(skip_if = "GenericViewTimelineInset::is_auto")] - #[css(field_bound)] - pub inset: GenericViewTimelineInset<LengthPercent>, -} - -pub use self::GenericViewFunction as ViewFunction; - -/// A value for the <single-animation-timeline>. -/// -/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericAnimationTimeline<LengthPercent> { - /// Use default timeline. The animation’s timeline is a DocumentTimeline. - Auto, - /// The scroll-timeline name or view-timeline-name. - /// https://drafts.csswg.org/scroll-animations-1/#scroll-timelines-named - /// https://drafts.csswg.org/scroll-animations-1/#view-timeline-name - Timeline(TimelineName), - /// The scroll() notation. - /// https://drafts.csswg.org/scroll-animations-1/#scroll-notation - Scroll(ScrollFunction), - /// The view() notation. - /// https://drafts.csswg.org/scroll-animations-1/#view-notation - View(#[css(field_bound)] GenericViewFunction<LengthPercent>), -} - -pub use self::GenericAnimationTimeline as AnimationTimeline; - -impl<LengthPercent> AnimationTimeline<LengthPercent> { - /// Returns the `auto` value. - pub fn auto() -> Self { - Self::Auto - } - - /// Returns true if it is auto (i.e. the default value). - pub fn is_auto(&self) -> bool { - matches!(self, Self::Auto) - } -} - -/// A generic value for the `[ [ auto | <length-percentage> ]{1,2} ]`. -/// -/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-inset -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericViewTimelineInset<LengthPercent> { - /// The start inset in the relevant axis. - pub start: GenericLengthPercentageOrAuto<LengthPercent>, - /// The end inset. - pub end: GenericLengthPercentageOrAuto<LengthPercent>, -} - -pub use self::GenericViewTimelineInset as ViewTimelineInset; - -impl<LengthPercent> ViewTimelineInset<LengthPercent> { - /// Returns true if it is auto. - #[inline] - fn is_auto(&self) -> bool { - self.start.is_auto() && self.end.is_auto() - } -} - -impl<LengthPercent> ToCss for ViewTimelineInset<LengthPercent> -where - LengthPercent: PartialEq + ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.start.to_css(dest)?; - if self.end != self.start { - dest.write_char(' ')?; - self.end.to_css(dest)?; - } - Ok(()) - } -} - -impl<LengthPercent> Default for ViewTimelineInset<LengthPercent> { - fn default() -> Self { - Self { - start: GenericLengthPercentageOrAuto::auto(), - end: GenericLengthPercentageOrAuto::auto(), - } - } -} diff --git a/components/style/values/generics/background.rs b/components/style/values/generics/background.rs deleted file mode 100644 index d9b6624595d..00000000000 --- a/components/style/values/generics/background.rs +++ /dev/null @@ -1,54 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values related to backgrounds. - -use crate::values::generics::length::{GenericLengthPercentageOrAuto, LengthPercentageOrAuto}; - -/// A generic value for the `background-size` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericBackgroundSize<LengthPercent> { - /// `<width> <height>` - ExplicitSize { - /// Explicit width. - width: GenericLengthPercentageOrAuto<LengthPercent>, - /// Explicit height. - #[css(skip_if = "GenericLengthPercentageOrAuto::is_auto")] - height: GenericLengthPercentageOrAuto<LengthPercent>, - }, - /// `cover` - #[animation(error)] - Cover, - /// `contain` - #[animation(error)] - Contain, -} - -pub use self::GenericBackgroundSize as BackgroundSize; - -impl<LengthPercentage> BackgroundSize<LengthPercentage> { - /// Returns `auto auto`. - pub fn auto() -> Self { - GenericBackgroundSize::ExplicitSize { - width: LengthPercentageOrAuto::Auto, - height: LengthPercentageOrAuto::Auto, - } - } -} diff --git a/components/style/values/generics/basic_shape.rs b/components/style/values/generics/basic_shape.rs deleted file mode 100644 index 00d682a169f..00000000000 --- a/components/style/values/generics/basic_shape.rs +++ /dev/null @@ -1,513 +0,0 @@ -/* 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/. */ - -//! CSS handling for the [`basic-shape`](https://drafts.csswg.org/css-shapes/#typedef-basic-shape) -//! types that are generic over their `ToCss` implementations. - -use crate::values::animated::{lists, Animate, Procedure, ToAnimatedZero}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::border::GenericBorderRadius; -use crate::values::generics::position::GenericPosition; -use crate::values::generics::rect::Rect; -use crate::values::specified::SVGPathData; -use crate::Zero; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// <https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box> -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ShapeGeometryBox { - /// Depending on which kind of element this style value applied on, the - /// default value of the reference-box can be different. For an HTML - /// element, the default value of reference-box is border-box; for an SVG - /// element, the default value is fill-box. Since we can not determine the - /// default value at parsing time, we keep this value to make a decision on - /// it. - #[css(skip)] - ElementDependent, - FillBox, - StrokeBox, - ViewBox, - ShapeBox(ShapeBox), -} - -impl Default for ShapeGeometryBox { - fn default() -> Self { - Self::ElementDependent - } -} - -/// https://drafts.csswg.org/css-shapes-1/#typedef-shape-box -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Animate, - Clone, - Copy, - ComputeSquaredDistance, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ShapeBox { - MarginBox, - BorderBox, - PaddingBox, - ContentBox, -} - -impl Default for ShapeBox { - fn default() -> Self { - ShapeBox::MarginBox - } -} - -/// A value for the `clip-path` property. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[animation(no_bound(U))] -#[repr(u8)] -pub enum GenericClipPath<BasicShape, U> { - #[animation(error)] - None, - #[animation(error)] - Url(U), - #[css(function)] - Path(Path), - Shape( - Box<BasicShape>, - #[css(skip_if = "is_default")] ShapeGeometryBox, - ), - #[animation(error)] - Box(ShapeGeometryBox), -} - -pub use self::GenericClipPath as ClipPath; - -/// A value for the `shape-outside` property. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[animation(no_bound(I))] -#[repr(u8)] -pub enum GenericShapeOutside<BasicShape, I> { - #[animation(error)] - None, - #[animation(error)] - Image(I), - Shape(Box<BasicShape>, #[css(skip_if = "is_default")] ShapeBox), - #[animation(error)] - Box(ShapeBox), -} - -pub use self::GenericShapeOutside as ShapeOutside; - -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericBasicShape<H, V, LengthPercentage, NonNegativeLengthPercentage> { - Inset( - #[css(field_bound)] - #[shmem(field_bound)] - InsetRect<LengthPercentage, NonNegativeLengthPercentage>, - ), - Circle( - #[css(field_bound)] - #[shmem(field_bound)] - Circle<H, V, NonNegativeLengthPercentage>, - ), - Ellipse( - #[css(field_bound)] - #[shmem(field_bound)] - Ellipse<H, V, NonNegativeLengthPercentage>, - ), - Polygon(GenericPolygon<LengthPercentage>), -} - -pub use self::GenericBasicShape as BasicShape; - -/// <https://drafts.csswg.org/css-shapes/#funcdef-inset> -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[css(function = "inset")] -#[repr(C)] -pub struct InsetRect<LengthPercentage, NonNegativeLengthPercentage> { - pub rect: Rect<LengthPercentage>, - #[shmem(field_bound)] - pub round: GenericBorderRadius<NonNegativeLengthPercentage>, -} - -/// <https://drafts.csswg.org/css-shapes/#funcdef-circle> -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[css(function)] -#[repr(C)] -pub struct Circle<H, V, NonNegativeLengthPercentage> { - pub position: GenericPosition<H, V>, - pub radius: GenericShapeRadius<NonNegativeLengthPercentage>, -} - -/// <https://drafts.csswg.org/css-shapes/#funcdef-ellipse> -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[css(function)] -#[repr(C)] -pub struct Ellipse<H, V, NonNegativeLengthPercentage> { - pub position: GenericPosition<H, V>, - pub semiaxis_x: GenericShapeRadius<NonNegativeLengthPercentage>, - pub semiaxis_y: GenericShapeRadius<NonNegativeLengthPercentage>, -} - -/// <https://drafts.csswg.org/css-shapes/#typedef-shape-radius> -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericShapeRadius<NonNegativeLengthPercentage> { - Length(NonNegativeLengthPercentage), - #[animation(error)] - ClosestSide, - #[animation(error)] - FarthestSide, -} - -pub use self::GenericShapeRadius as ShapeRadius; - -/// A generic type for representing the `polygon()` function -/// -/// <https://drafts.csswg.org/css-shapes/#funcdef-polygon> -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(comma, function = "polygon")] -#[repr(C)] -pub struct GenericPolygon<LengthPercentage> { - /// The filling rule for a polygon. - #[css(skip_if = "is_default")] - pub fill: FillRule, - /// A collection of (x, y) coordinates to draw the polygon. - #[css(iterable)] - pub coordinates: crate::OwnedSlice<PolygonCoord<LengthPercentage>>, -} - -pub use self::GenericPolygon as Polygon; - -/// Coordinates for Polygon. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct PolygonCoord<LengthPercentage>(pub LengthPercentage, pub LengthPercentage); - -// https://drafts.csswg.org/css-shapes/#typedef-fill-rule -// NOTE: Basic shapes spec says that these are the only two values, however -// https://www.w3.org/TR/SVG/painting.html#FillRuleProperty -// says that it can also be `inherit` -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum FillRule { - Nonzero, - Evenodd, -} - -/// The path function defined in css-shape-2. -/// -/// https://drafts.csswg.org/css-shapes-2/#funcdef-path -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(comma)] -#[repr(C)] -pub struct Path { - /// The filling rule for the svg path. - #[css(skip_if = "is_default")] - #[animation(constant)] - pub fill: FillRule, - /// The svg path data. - pub path: SVGPathData, -} - -impl<B, U> ToAnimatedZero for ClipPath<B, U> { - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} - -impl<B, U> ToAnimatedZero for ShapeOutside<B, U> { - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} - -impl<Length, NonNegativeLength> ToCss for InsetRect<Length, NonNegativeLength> -where - Length: ToCss + PartialEq, - NonNegativeLength: ToCss + PartialEq + Zero, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("inset(")?; - self.rect.to_css(dest)?; - if !self.round.is_zero() { - dest.write_str(" round ")?; - self.round.to_css(dest)?; - } - dest.write_char(')') - } -} - -impl<H, V, NonNegativeLengthPercentage> ToCss for Circle<H, V, NonNegativeLengthPercentage> -where - GenericPosition<H, V>: ToCss, - NonNegativeLengthPercentage: ToCss + PartialEq, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("circle(")?; - if self.radius != Default::default() { - self.radius.to_css(dest)?; - dest.write_char(' ')?; - } - dest.write_str("at ")?; - self.position.to_css(dest)?; - dest.write_char(')') - } -} - -impl<H, V, NonNegativeLengthPercentage> ToCss for Ellipse<H, V, NonNegativeLengthPercentage> -where - GenericPosition<H, V>: ToCss, - NonNegativeLengthPercentage: ToCss + PartialEq, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("ellipse(")?; - if self.semiaxis_x != Default::default() || self.semiaxis_y != Default::default() { - self.semiaxis_x.to_css(dest)?; - dest.write_char(' ')?; - self.semiaxis_y.to_css(dest)?; - dest.write_char(' ')?; - } - dest.write_str("at ")?; - self.position.to_css(dest)?; - dest.write_char(')') - } -} - -impl<L> Default for ShapeRadius<L> { - #[inline] - fn default() -> Self { - ShapeRadius::ClosestSide - } -} - -impl<L> Animate for Polygon<L> -where - L: Animate, -{ - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if self.fill != other.fill { - return Err(()); - } - let coordinates = - lists::by_computed_value::animate(&self.coordinates, &other.coordinates, procedure)?; - Ok(Polygon { - fill: self.fill, - coordinates, - }) - } -} - -impl<L> ComputeSquaredDistance for Polygon<L> -where - L: ComputeSquaredDistance, -{ - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - if self.fill != other.fill { - return Err(()); - } - lists::by_computed_value::squared_distance(&self.coordinates, &other.coordinates) - } -} - -impl Default for FillRule { - #[inline] - fn default() -> Self { - FillRule::Nonzero - } -} - -#[inline] -fn is_default<T: Default + PartialEq>(fill: &T) -> bool { - *fill == Default::default() -} diff --git a/components/style/values/generics/border.rs b/components/style/values/generics/border.rs deleted file mode 100644 index e4e78e31222..00000000000 --- a/components/style/values/generics/border.rs +++ /dev/null @@ -1,257 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values related to borders. - -use crate::values::generics::rect::Rect; -use crate::values::generics::size::Size2D; -use crate::Zero; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A generic value for a single side of a `border-image-width` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericBorderImageSideWidth<LP, N> { - /// `<number>` - /// - /// NOTE: Numbers need to be before length-percentagess, in order to parse - /// them first, since `0` should be a number, not the `0px` length. - Number(N), - /// `<length-or-percentage>` - LengthPercentage(LP), - /// `auto` - Auto, -} - -pub use self::GenericBorderImageSideWidth as BorderImageSideWidth; - -/// A generic value for the `border-image-slice` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericBorderImageSlice<NumberOrPercentage> { - /// The offsets. - #[css(field_bound)] - pub offsets: Rect<NumberOrPercentage>, - /// Whether to fill the middle part. - #[animation(constant)] - #[css(represents_keyword)] - pub fill: bool, -} - -pub use self::GenericBorderImageSlice as BorderImageSlice; - -/// A generic value for the `border-*-radius` longhand properties. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericBorderCornerRadius<L>( - #[css(field_bound)] - #[shmem(field_bound)] - pub Size2D<L>, -); - -pub use self::GenericBorderCornerRadius as BorderCornerRadius; - -impl<L> BorderCornerRadius<L> { - /// Trivially create a `BorderCornerRadius`. - pub fn new(w: L, h: L) -> Self { - BorderCornerRadius(Size2D::new(w, h)) - } -} - -impl<L: Zero> Zero for BorderCornerRadius<L> { - fn zero() -> Self { - BorderCornerRadius(Size2D::zero()) - } - - fn is_zero(&self) -> bool { - self.0.is_zero() - } -} - -/// A generic value for the `border-spacing` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct BorderSpacing<L>( - #[css(field_bound)] - #[shmem(field_bound)] - pub Size2D<L>, -); - -impl<L> BorderSpacing<L> { - /// Trivially create a `BorderCornerRadius`. - pub fn new(w: L, h: L) -> Self { - BorderSpacing(Size2D::new(w, h)) - } -} - -/// A generic value for `border-radius` and `inset()`. -/// -/// <https://drafts.csswg.org/css-backgrounds-3/#border-radius> -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericBorderRadius<LengthPercentage> { - /// The top left radius. - #[shmem(field_bound)] - pub top_left: GenericBorderCornerRadius<LengthPercentage>, - /// The top right radius. - pub top_right: GenericBorderCornerRadius<LengthPercentage>, - /// The bottom right radius. - pub bottom_right: GenericBorderCornerRadius<LengthPercentage>, - /// The bottom left radius. - pub bottom_left: GenericBorderCornerRadius<LengthPercentage>, -} - -pub use self::GenericBorderRadius as BorderRadius; - -impl<L> BorderRadius<L> { - /// Returns a new `BorderRadius<L>`. - #[inline] - pub fn new( - tl: BorderCornerRadius<L>, - tr: BorderCornerRadius<L>, - br: BorderCornerRadius<L>, - bl: BorderCornerRadius<L>, - ) -> Self { - BorderRadius { - top_left: tl, - top_right: tr, - bottom_right: br, - bottom_left: bl, - } - } - - /// Serialises two given rects following the syntax of the `border-radius`` - /// property. - pub fn serialize_rects<W>( - widths: Rect<&L>, - heights: Rect<&L>, - dest: &mut CssWriter<W>, - ) -> fmt::Result - where - L: PartialEq + ToCss, - W: Write, - { - widths.to_css(dest)?; - if widths != heights { - dest.write_str(" / ")?; - heights.to_css(dest)?; - } - Ok(()) - } -} - -impl<L: Zero> Zero for BorderRadius<L> { - fn zero() -> Self { - Self::new( - BorderCornerRadius::<L>::zero(), - BorderCornerRadius::<L>::zero(), - BorderCornerRadius::<L>::zero(), - BorderCornerRadius::<L>::zero(), - ) - } - - fn is_zero(&self) -> bool { - self.top_left.is_zero() && - self.top_right.is_zero() && - self.bottom_right.is_zero() && - self.bottom_left.is_zero() - } -} - -impl<L> ToCss for BorderRadius<L> -where - L: PartialEq + ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let BorderRadius { - top_left: BorderCornerRadius(ref tl), - top_right: BorderCornerRadius(ref tr), - bottom_right: BorderCornerRadius(ref br), - bottom_left: BorderCornerRadius(ref bl), - } = *self; - - let widths = Rect::new(&tl.width, &tr.width, &br.width, &bl.width); - let heights = Rect::new(&tl.height, &tr.height, &br.height, &bl.height); - - Self::serialize_rects(widths, heights, dest) - } -} diff --git a/components/style/values/generics/box.rs b/components/style/values/generics/box.rs deleted file mode 100644 index 3cc2b051455..00000000000 --- a/components/style/values/generics/box.rs +++ /dev/null @@ -1,208 +0,0 @@ -/* 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/. */ - -//! Generic types for box properties. - -use crate::values::animated::ToAnimatedZero; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - FromPrimitive, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -#[allow(missing_docs)] -pub enum VerticalAlignKeyword { - Baseline, - Sub, - Super, - Top, - TextTop, - Middle, - Bottom, - TextBottom, - #[cfg(feature = "gecko")] - MozMiddleWithBaseline, -} - -/// A generic value for the `vertical-align` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericVerticalAlign<LengthPercentage> { - /// One of the vertical-align keywords. - Keyword(VerticalAlignKeyword), - /// `<length-percentage>` - Length(LengthPercentage), -} - -pub use self::GenericVerticalAlign as VerticalAlign; - -impl<L> VerticalAlign<L> { - /// Returns `baseline`. - #[inline] - pub fn baseline() -> Self { - VerticalAlign::Keyword(VerticalAlignKeyword::Baseline) - } -} - -impl<L> ToAnimatedZero for VerticalAlign<L> { - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} - -/// https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToAnimatedValue, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -#[value_info(other_values = "auto")] -#[repr(C, u8)] -pub enum GenericContainIntrinsicSize<L> { - /// The keyword `none`. - None, - /// A non-negative length. - Length(L), - /// "auto <Length>" - AutoLength(L), -} - -pub use self::GenericContainIntrinsicSize as ContainIntrinsicSize; - -impl<L: ToCss> ToCss for ContainIntrinsicSize<L> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - Self::None => dest.write_str("none"), - Self::Length(ref l) => l.to_css(dest), - Self::AutoLength(ref l) => { - dest.write_str("auto ")?; - l.to_css(dest) - }, - } - } -} - -/// Note that we only implement -webkit-line-clamp as a single, longhand -/// property for now, but the spec defines line-clamp as a shorthand for -/// separate max-lines, block-ellipsis, and continue properties. -/// -/// https://drafts.csswg.org/css-overflow-3/#line-clamp -#[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToAnimatedValue, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -#[value_info(other_values = "none")] -pub struct GenericLineClamp<I>(pub I); - -pub use self::GenericLineClamp as LineClamp; - -impl<I: crate::Zero> LineClamp<I> { - /// Returns the `none` value. - pub fn none() -> Self { - Self(crate::Zero::zero()) - } - - /// Returns whether we're the `none` value. - pub fn is_none(&self) -> bool { - self.0.is_zero() - } -} - -impl<I: crate::Zero + ToCss> ToCss for LineClamp<I> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_none() { - return dest.write_str("none"); - } - self.0.to_css(dest) - } -} - -/// A generic value for the `perspective` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericPerspective<NonNegativeLength> { - /// A non-negative length. - Length(NonNegativeLength), - /// The keyword `none`. - None, -} - -pub use self::GenericPerspective as Perspective; - -impl<L> Perspective<L> { - /// Returns `none`. - #[inline] - pub fn none() -> Self { - Perspective::None - } -} diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs deleted file mode 100644 index 3132e56342f..00000000000 --- a/components/style/values/generics/calc.rs +++ /dev/null @@ -1,1343 +0,0 @@ -/* 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/. */ - -//! [Calc expressions][calc]. -//! -//! [calc]: https://drafts.csswg.org/css-values/#calc-notation - -use num_traits::{Float, Zero}; -use smallvec::SmallVec; -use std::fmt::{self, Write}; -use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; -use std::{cmp, mem}; -use style_traits::{CssWriter, ToCss}; - -/// Whether we're a `min` or `max` function. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum MinMaxOp { - /// `min()` - Min, - /// `max()` - Max, -} - -/// Whether we're a `mod` or `rem` function. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ModRemOp { - /// `mod()` - Mod, - /// `rem()` - Rem, -} - -/// The strategy used in `round()` -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum RoundingStrategy { - /// `round(nearest, a, b)` - /// round a to the nearest multiple of b - Nearest, - /// `round(up, a, b)` - /// round a up to the nearest multiple of b - Up, - /// `round(down, a, b)` - /// round a down to the nearest multiple of b - Down, - /// `round(to-zero, a, b)` - /// round a to the nearest multiple of b that is towards zero - ToZero, -} - -/// This determines the order in which we serialize members of a calc() sum. -/// -/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] -#[allow(missing_docs)] -pub enum SortKey { - Number, - Percentage, - Cap, - Ch, - Cqb, - Cqh, - Cqi, - Cqmax, - Cqmin, - Cqw, - Deg, - Dppx, - Dvb, - Dvh, - Dvi, - Dvmax, - Dvmin, - Dvw, - Em, - Ex, - Ic, - Lvb, - Lvh, - Lvi, - Lvmax, - Lvmin, - Lvw, - Px, - Rem, - Sec, - Svb, - Svh, - Svi, - Svmax, - Svmin, - Svw, - Vb, - Vh, - Vi, - Vmax, - Vmin, - Vw, - Other, -} - -/// A generic node in a calc expression. -/// -/// FIXME: This would be much more elegant if we used `Self` in the types below, -/// but we can't because of https://github.com/serde-rs/serde/issues/1565. -/// -/// FIXME: The following annotations are to workaround an LLVM inlining bug, see -/// bug 1631929. -/// -/// cbindgen:destructor-attributes=MOZ_NEVER_INLINE -/// cbindgen:copy-constructor-attributes=MOZ_NEVER_INLINE -/// cbindgen:eq-attributes=MOZ_NEVER_INLINE -#[repr(u8)] -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -pub enum GenericCalcNode<L> { - /// A leaf node. - Leaf(L), - /// A node that negates its children, e.g. Negate(1) == -1. - Negate(Box<GenericCalcNode<L>>), - /// A sum node, representing `a + b + c` where a, b, and c are the - /// arguments. - Sum(crate::OwnedSlice<GenericCalcNode<L>>), - /// A `min` or `max` function. - MinMax(crate::OwnedSlice<GenericCalcNode<L>>, MinMaxOp), - /// A `clamp()` function. - Clamp { - /// The minimum value. - min: Box<GenericCalcNode<L>>, - /// The central value. - center: Box<GenericCalcNode<L>>, - /// The maximum value. - max: Box<GenericCalcNode<L>>, - }, - /// A `round()` function. - Round { - /// The rounding strategy. - strategy: RoundingStrategy, - /// The value to round. - value: Box<GenericCalcNode<L>>, - /// The step value. - step: Box<GenericCalcNode<L>>, - }, - /// A `mod()` or `rem()` function. - ModRem { - /// The dividend calculation. - dividend: Box<GenericCalcNode<L>>, - /// The divisor calculation. - divisor: Box<GenericCalcNode<L>>, - /// Is the function mod or rem? - op: ModRemOp, - }, - /// A `hypot()` function - Hypot(crate::OwnedSlice<GenericCalcNode<L>>), -} - -pub use self::GenericCalcNode as CalcNode; - -/// A trait that represents all the stuff a valid leaf of a calc expression. -pub trait CalcNodeLeaf: Clone + Sized + PartialOrd + PartialEq + ToCss { - /// Returns the unitless value of this leaf. - fn unitless_value(&self) -> f32; - - /// Whether this value is known-negative. - fn is_negative(&self) -> bool { - self.unitless_value().is_sign_negative() - } - - /// Whether this value is infinite. - fn is_infinite(&self) -> bool { - self.unitless_value().is_infinite() - } - - /// Whether this value is zero. - fn is_zero(&self) -> bool { - self.unitless_value().is_zero() - } - - /// Whether this value is NaN. - fn is_nan(&self) -> bool { - self.unitless_value().is_nan() - } - - /// Tries to merge one sum to another, that is, perform `x` + `y`. - fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()>; - - /// Tries a generic arithmetic operation. - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32; - - /// Map the value of this node with the given operation. - fn map(&mut self, op: impl FnMut(f32) -> f32); - - /// Negates the leaf. - fn negate(&mut self) { - self.map(std::ops::Neg::neg); - } - - /// Canonicalizes the expression if necessary. - fn simplify(&mut self); - - /// Returns the sort key for simplification. - fn sort_key(&self) -> SortKey; -} - -/// The level of any argument being serialized in `to_css_impl`. -enum ArgumentLevel { - /// The root of a calculation tree. - CalculationRoot, - /// The root of an operand node's argument, e.g. `min(10, 20)`, `10` and `20` will have this - /// level, but min in this case will have `TopMost`. - ArgumentRoot, - /// Any other values serialized in the tree. - Nested, -} - -impl<L: CalcNodeLeaf> CalcNode<L> { - /// Negate the node inline. If the node is distributive, it is replaced by the result, - /// otherwise the node is wrapped in a [`Negate`] node. - pub fn negate(&mut self) { - match *self { - CalcNode::Leaf(ref mut leaf) => leaf.map(|l| l.neg()), - CalcNode::Negate(ref mut value) => { - // Don't negate the value here. Replace `self` with it's child. - let result = mem::replace( - value.as_mut(), - Self::MinMax(Default::default(), MinMaxOp::Max), - ); - *self = result; - }, - CalcNode::Sum(ref mut children) => { - for child in children.iter_mut() { - child.negate(); - } - }, - CalcNode::MinMax(ref mut children, ref mut op) => { - for child in children.iter_mut() { - child.negate(); - } - - // Negating min-max means the operation is swapped. - *op = match *op { - MinMaxOp::Min => MinMaxOp::Max, - MinMaxOp::Max => MinMaxOp::Min, - }; - }, - CalcNode::Clamp { - ref mut min, - ref mut center, - ref mut max, - } => { - min.negate(); - center.negate(); - max.negate(); - - mem::swap(min, max); - }, - CalcNode::Round { - ref mut value, - ref mut step, - .. - } => { - value.negate(); - step.negate(); - }, - CalcNode::ModRem { - ref mut dividend, - ref mut divisor, - .. - } => { - dividend.negate(); - divisor.negate(); - }, - CalcNode::Hypot(ref mut children) => { - for child in children.iter_mut() { - child.negate(); - } - }, - } - } - - fn sort_key(&self) -> SortKey { - match *self { - Self::Leaf(ref l) => l.sort_key(), - _ => SortKey::Other, - } - } - - /// Returns the leaf if we can (if simplification has allowed it). - pub fn as_leaf(&self) -> Option<&L> { - match *self { - Self::Leaf(ref l) => Some(l), - _ => None, - } - } - - /// Tries to merge one sum to another, that is, perform `x` + `y`. - fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { - match (self, other) { - (&mut CalcNode::Leaf(ref mut one), &CalcNode::Leaf(ref other)) => { - one.try_sum_in_place(other) - }, - _ => Err(()), - } - } - - /// Tries to apply a generic arithmentic operator - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - match (self, other) { - (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => { - Ok(CalcNode::Leaf(one.try_op(other, op)?)) - }, - _ => Err(()), - } - } - - /// Map the value of this node with the given operation. - pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) { - match node { - CalcNode::Leaf(l) => l.map(op), - CalcNode::Negate(v) => map_internal(v, op), - CalcNode::Sum(children) => { - for node in &mut **children { - map_internal(node, op); - } - }, - CalcNode::MinMax(children, _) => { - for node in &mut **children { - map_internal(node, op); - } - }, - CalcNode::Clamp { min, center, max } => { - map_internal(min, op); - map_internal(center, op); - map_internal(max, op); - }, - CalcNode::Round { value, step, .. } => { - map_internal(value, op); - map_internal(step, op); - }, - CalcNode::ModRem { - dividend, divisor, .. - } => { - map_internal(dividend, op); - map_internal(divisor, op); - }, - CalcNode::Hypot(children) => { - for node in &mut **children { - map_internal(node, op); - } - }, - } - } - - map_internal(self, &mut op); - } - - /// Convert this `CalcNode` into a `CalcNode` with a different leaf kind. - pub fn map_leaves<O, F>(&self, mut map: F) -> CalcNode<O> - where - O: CalcNodeLeaf, - F: FnMut(&L) -> O, - { - self.map_leaves_internal(&mut map) - } - - fn map_leaves_internal<O, F>(&self, map: &mut F) -> CalcNode<O> - where - O: CalcNodeLeaf, - F: FnMut(&L) -> O, - { - fn map_children<L, O, F>( - children: &[CalcNode<L>], - map: &mut F, - ) -> crate::OwnedSlice<CalcNode<O>> - where - L: CalcNodeLeaf, - O: CalcNodeLeaf, - F: FnMut(&L) -> O, - { - children - .iter() - .map(|c| c.map_leaves_internal(map)) - .collect() - } - - match *self { - Self::Leaf(ref l) => CalcNode::Leaf(map(l)), - Self::Negate(ref c) => CalcNode::Negate(Box::new(c.map_leaves_internal(map))), - Self::Sum(ref c) => CalcNode::Sum(map_children(c, map)), - Self::MinMax(ref c, op) => CalcNode::MinMax(map_children(c, map), op), - Self::Clamp { - ref min, - ref center, - ref max, - } => { - let min = Box::new(min.map_leaves_internal(map)); - let center = Box::new(center.map_leaves_internal(map)); - let max = Box::new(max.map_leaves_internal(map)); - CalcNode::Clamp { min, center, max } - }, - Self::Round { - strategy, - ref value, - ref step, - } => { - let value = Box::new(value.map_leaves_internal(map)); - let step = Box::new(step.map_leaves_internal(map)); - CalcNode::Round { - strategy, - value, - step, - } - }, - Self::ModRem { - ref dividend, - ref divisor, - op, - } => { - let dividend = Box::new(dividend.map_leaves_internal(map)); - let divisor = Box::new(divisor.map_leaves_internal(map)); - CalcNode::ModRem { - dividend, - divisor, - op, - } - }, - Self::Hypot(ref c) => CalcNode::Hypot(map_children(c, map)), - } - } - - /// Resolves the expression returning a value of `O`, given a function to - /// turn a leaf into the relevant value. - pub fn resolve<O>( - &self, - mut leaf_to_output_fn: impl FnMut(&L) -> Result<O, ()>, - ) -> Result<O, ()> - where - O: PartialOrd - + PartialEq - + Add<Output = O> - + Mul<Output = O> - + Div<Output = O> - + Sub<Output = O> - + Zero - + Float - + Copy, - { - self.resolve_internal(&mut leaf_to_output_fn) - } - - fn resolve_internal<O, F>(&self, leaf_to_output_fn: &mut F) -> Result<O, ()> - where - O: PartialOrd - + PartialEq - + Add<Output = O> - + Mul<Output = O> - + Div<Output = O> - + Sub<Output = O> - + Zero - + Float - + Copy, - F: FnMut(&L) -> Result<O, ()>, - { - Ok(match *self { - Self::Leaf(ref l) => return leaf_to_output_fn(l), - Self::Negate(ref c) => c.resolve_internal(leaf_to_output_fn)?.neg(), - Self::Sum(ref c) => { - let mut result = Zero::zero(); - for child in &**c { - result = result + child.resolve_internal(leaf_to_output_fn)?; - } - result - }, - Self::MinMax(ref nodes, op) => { - let mut result = nodes[0].resolve_internal(leaf_to_output_fn)?; - - if result.is_nan() { - return Ok(result); - } - - for node in nodes.iter().skip(1) { - let candidate = node.resolve_internal(leaf_to_output_fn)?; - - if candidate.is_nan() { - result = candidate; - break; - } - - let candidate_wins = match op { - MinMaxOp::Min => candidate < result, - MinMaxOp::Max => candidate > result, - }; - if candidate_wins { - result = candidate; - } - } - result - }, - Self::Clamp { - ref min, - ref center, - ref max, - } => { - let min = min.resolve_internal(leaf_to_output_fn)?; - let center = center.resolve_internal(leaf_to_output_fn)?; - let max = max.resolve_internal(leaf_to_output_fn)?; - - let mut result = center; - if result > max { - result = max; - } - if result < min { - result = min - } - - if min.is_nan() || center.is_nan() || max.is_nan() { - result = <O as Float>::nan(); - } - - result - }, - Self::Round { - strategy, - ref value, - ref step, - } => { - let value = value.resolve_internal(leaf_to_output_fn)?; - let step = step.resolve_internal(leaf_to_output_fn)?; - - // TODO(emilio): Seems like at least a few of these - // special-cases could be removed if we do the math in a - // particular order. - if step.is_zero() { - return Ok(<O as Float>::nan()); - } - - if value.is_infinite() && step.is_infinite() { - return Ok(<O as Float>::nan()); - } - - if value.is_infinite() { - return Ok(value); - } - - if step.is_infinite() { - match strategy { - RoundingStrategy::Nearest | RoundingStrategy::ToZero => { - return if value.is_sign_negative() { - Ok(<O as Float>::neg_zero()) - } else { - Ok(<O as Zero>::zero()) - } - }, - RoundingStrategy::Up => { - return if !value.is_sign_negative() && !value.is_zero() { - Ok(<O as Float>::infinity()) - } else if !value.is_sign_negative() && value.is_zero() { - Ok(value) - } else { - Ok(<O as Float>::neg_zero()) - } - }, - RoundingStrategy::Down => { - return if value.is_sign_negative() && !value.is_zero() { - Ok(<O as Float>::neg_infinity()) - } else if value.is_sign_negative() && value.is_zero() { - Ok(value) - } else { - Ok(<O as Zero>::zero()) - } - }, - } - } - - let div = value / step; - let lower_bound = div.floor() * step; - let upper_bound = div.ceil() * step; - - match strategy { - RoundingStrategy::Nearest => { - // In case of a tie, use the upper bound - if value - lower_bound < upper_bound - value { - lower_bound - } else { - upper_bound - } - }, - RoundingStrategy::Up => upper_bound, - RoundingStrategy::Down => lower_bound, - RoundingStrategy::ToZero => { - // In case of a tie, use the upper bound - if lower_bound.abs() < upper_bound.abs() { - lower_bound - } else { - upper_bound - } - }, - } - }, - Self::ModRem { - ref dividend, - ref divisor, - op, - } => { - let dividend = dividend.resolve_internal(leaf_to_output_fn)?; - let divisor = divisor.resolve_internal(leaf_to_output_fn)?; - - // In mod(A, B) only, if B is infinite and A has opposite sign to B - // (including an oppositely-signed zero), the result is NaN. - // https://drafts.csswg.org/css-values/#round-infinities - if matches!(op, ModRemOp::Mod) && - divisor.is_infinite() && - dividend.is_sign_negative() != divisor.is_sign_negative() - { - return Ok(<O as Float>::nan()); - } - - match op { - ModRemOp::Mod => dividend - divisor * (dividend / divisor).floor(), - ModRemOp::Rem => dividend - divisor * (dividend / divisor).trunc(), - } - }, - Self::Hypot(ref c) => { - let mut result: O = Zero::zero(); - for child in &**c { - result = result + child.resolve_internal(leaf_to_output_fn)?.powi(2); - } - result.sqrt() - }, - }) - } - - fn is_negative_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_negative(), - _ => false, - } - } - - fn is_zero_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_zero(), - _ => false, - } - } - - fn is_infinite_leaf(&self) -> bool { - match *self { - Self::Leaf(ref l) => l.is_infinite(), - _ => false, - } - } - - /// Multiplies the node by a scalar. - pub fn mul_by(&mut self, scalar: f32) { - match *self { - Self::Leaf(ref mut l) => l.map(|v| v * scalar), - Self::Negate(ref mut value) => value.mul_by(scalar), - // Multiplication is distributive across this. - Self::Sum(ref mut children) => { - for node in &mut **children { - node.mul_by(scalar); - } - }, - // This one is a bit trickier. - Self::MinMax(ref mut children, ref mut op) => { - for node in &mut **children { - node.mul_by(scalar); - } - - // For negatives we need to invert the operation. - if scalar < 0. { - *op = match *op { - MinMaxOp::Min => MinMaxOp::Max, - MinMaxOp::Max => MinMaxOp::Min, - } - } - }, - // This one is slightly tricky too. - Self::Clamp { - ref mut min, - ref mut center, - ref mut max, - } => { - min.mul_by(scalar); - center.mul_by(scalar); - max.mul_by(scalar); - // For negatives we need to swap min / max. - if scalar < 0. { - mem::swap(min, max); - } - }, - Self::Round { - ref mut value, - ref mut step, - .. - } => { - value.mul_by(scalar); - step.mul_by(scalar); - }, - Self::ModRem { - ref mut dividend, - ref mut divisor, - .. - } => { - dividend.mul_by(scalar); - divisor.mul_by(scalar); - }, - // Not possible to handle negatives in this case, see: https://bugzil.la/1815448 - Self::Hypot(ref mut children) => { - for node in &mut **children { - node.mul_by(scalar); - } - }, - } - } - - /// Visits all the nodes in this calculation tree recursively, starting by - /// the leaves and bubbling all the way up. - /// - /// This is useful for simplification, but can also be used for validation - /// and such. - pub fn visit_depth_first(&mut self, mut f: impl FnMut(&mut Self)) { - self.visit_depth_first_internal(&mut f); - } - - fn visit_depth_first_internal(&mut self, f: &mut impl FnMut(&mut Self)) { - match *self { - Self::Clamp { - ref mut min, - ref mut center, - ref mut max, - } => { - min.visit_depth_first_internal(f); - center.visit_depth_first_internal(f); - max.visit_depth_first_internal(f); - }, - Self::Round { - ref mut value, - ref mut step, - .. - } => { - value.visit_depth_first_internal(f); - step.visit_depth_first_internal(f); - }, - Self::ModRem { - ref mut dividend, - ref mut divisor, - .. - } => { - dividend.visit_depth_first_internal(f); - divisor.visit_depth_first_internal(f); - }, - Self::Sum(ref mut children) | - Self::MinMax(ref mut children, _) | - Self::Hypot(ref mut children) => { - for child in &mut **children { - child.visit_depth_first_internal(f); - } - }, - Self::Negate(ref mut value) => { - value.visit_depth_first_internal(f); - }, - Self::Leaf(..) => {}, - } - f(self); - } - - /// Simplifies and sorts the calculation of a given node. All the nodes - /// below it should be simplified already, this only takes care of - /// simplifying directly nested nodes. So, probably should always be used in - /// combination with `visit_depth_first()`. - /// - /// This is only needed if it's going to be preserved after parsing (so, for - /// `<length-percentage>`). Otherwise we can just evaluate it using - /// `resolve()`, and we'll come up with a simplified value anyways. - /// - /// <https://drafts.csswg.org/css-values-4/#calc-simplification> - pub fn simplify_and_sort_direct_children(&mut self) { - macro_rules! replace_self_with { - ($slot:expr) => {{ - let dummy = Self::MinMax(Default::default(), MinMaxOp::Max); - let result = mem::replace($slot, dummy); - *self = result; - }}; - } - match *self { - Self::Clamp { - ref mut min, - ref mut center, - ref mut max, - } => { - // NOTE: clamp() is max(min, min(center, max)) - let min_cmp_center = match min.partial_cmp(¢er) { - Some(o) => o, - None => return, - }; - - // So if we can prove that min is more than center, then we won, - // as that's what we should always return. - if matches!(min_cmp_center, cmp::Ordering::Greater) { - return replace_self_with!(&mut **min); - } - - // Otherwise try with max. - let max_cmp_center = match max.partial_cmp(¢er) { - Some(o) => o, - None => return, - }; - - if matches!(max_cmp_center, cmp::Ordering::Less) { - // max is less than center, so we need to return effectively - // `max(min, max)`. - let max_cmp_min = match max.partial_cmp(&min) { - Some(o) => o, - None => { - debug_assert!( - false, - "We compared center with min and max, how are \ - min / max not comparable with each other?" - ); - return; - }, - }; - - if matches!(max_cmp_min, cmp::Ordering::Less) { - return replace_self_with!(&mut **min); - } - - return replace_self_with!(&mut **max); - } - - // Otherwise we're the center node. - return replace_self_with!(&mut **center); - }, - Self::Round { - strategy, - ref mut value, - ref mut step, - } => { - if step.is_zero_leaf() { - value.mul_by(f32::NAN); - return replace_self_with!(&mut **value); - } - - if value.is_infinite_leaf() && step.is_infinite_leaf() { - value.mul_by(f32::NAN); - return replace_self_with!(&mut **value); - } - - if value.is_infinite_leaf() { - return replace_self_with!(&mut **value); - } - - if step.is_infinite_leaf() { - match strategy { - RoundingStrategy::Nearest | RoundingStrategy::ToZero => { - value.mul_by(0.); - return replace_self_with!(&mut **value); - }, - RoundingStrategy::Up => { - if !value.is_negative_leaf() && !value.is_zero_leaf() { - value.mul_by(f32::INFINITY); - return replace_self_with!(&mut **value); - } else if !value.is_negative_leaf() && value.is_zero_leaf() { - return replace_self_with!(&mut **value); - } else { - value.mul_by(0.); - return replace_self_with!(&mut **value); - } - }, - RoundingStrategy::Down => { - if value.is_negative_leaf() && !value.is_zero_leaf() { - value.mul_by(f32::INFINITY); - return replace_self_with!(&mut **value); - } else if value.is_negative_leaf() && value.is_zero_leaf() { - return replace_self_with!(&mut **value); - } else { - value.mul_by(0.); - return replace_self_with!(&mut **value); - } - }, - } - } - - if step.is_negative_leaf() { - step.negate(); - } - - let remainder = match value.try_op(step, Rem::rem) { - Ok(res) => res, - Err(..) => return, - }; - - let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() { - let upper_bound = match value.try_op(&remainder, Sub::sub) { - Ok(res) => res, - Err(..) => return, - }; - - let lower_bound = match upper_bound.try_op(&step, Sub::sub) { - Ok(res) => res, - Err(..) => return, - }; - - (lower_bound, upper_bound) - } else { - let lower_bound = match value.try_op(&remainder, Sub::sub) { - Ok(res) => res, - Err(..) => return, - }; - - let upper_bound = match lower_bound.try_op(&step, Add::add) { - Ok(res) => res, - Err(..) => return, - }; - - (lower_bound, upper_bound) - }; - - match strategy { - RoundingStrategy::Nearest => { - let lower_diff = match value.try_op(&lower_bound, Sub::sub) { - Ok(res) => res, - Err(..) => return, - }; - - let upper_diff = match upper_bound.try_op(value, Sub::sub) { - Ok(res) => res, - Err(..) => return, - }; - - // In case of a tie, use the upper bound - if lower_diff < upper_diff { - return replace_self_with!(&mut lower_bound); - } else { - return replace_self_with!(&mut upper_bound); - } - }, - RoundingStrategy::Up => return replace_self_with!(&mut upper_bound), - RoundingStrategy::Down => return replace_self_with!(&mut lower_bound), - RoundingStrategy::ToZero => { - let mut lower_diff = lower_bound.clone(); - let mut upper_diff = upper_bound.clone(); - - if lower_diff.is_negative_leaf() { - lower_diff.negate(); - } - - if upper_diff.is_negative_leaf() { - upper_diff.negate(); - } - - // In case of a tie, use the upper bound - if lower_diff < upper_diff { - return replace_self_with!(&mut lower_bound); - } else { - return replace_self_with!(&mut upper_bound); - } - }, - }; - }, - Self::ModRem { - ref dividend, - ref divisor, - op, - } => { - let mut result = dividend.clone(); - - // In mod(A, B) only, if B is infinite and A has opposite sign to B - // (including an oppositely-signed zero), the result is NaN. - // https://drafts.csswg.org/css-values/#round-infinities - if matches!(op, ModRemOp::Mod) && - divisor.is_infinite_leaf() && - dividend.is_negative_leaf() != divisor.is_negative_leaf() - { - result.mul_by(f32::NAN); - return replace_self_with!(&mut *result); - } - - let result = match op { - ModRemOp::Mod => dividend.try_op(divisor, |a, b| a - b * (a / b).floor()), - ModRemOp::Rem => dividend.try_op(divisor, |a, b| a - b * (a / b).trunc()), - }; - - let mut result = match result { - Ok(res) => res, - Err(..) => return, - }; - - return replace_self_with!(&mut result); - }, - Self::MinMax(ref mut children, op) => { - let winning_order = match op { - MinMaxOp::Min => cmp::Ordering::Less, - MinMaxOp::Max => cmp::Ordering::Greater, - }; - - let mut result = 0; - for i in 1..children.len() { - let o = match children[i].partial_cmp(&children[result]) { - // We can't compare all the children, so we can't - // know which one will actually win. Bail out and - // keep ourselves as a min / max function. - // - // TODO: Maybe we could simplify compatible children, - // see https://github.com/w3c/csswg-drafts/issues/4756 - None => return, - Some(o) => o, - }; - - if o == winning_order { - result = i; - } - } - - replace_self_with!(&mut children[result]); - }, - Self::Sum(ref mut children_slot) => { - let mut sums_to_merge = SmallVec::<[_; 3]>::new(); - let mut extra_kids = 0; - for (i, child) in children_slot.iter().enumerate() { - if let Self::Sum(ref children) = *child { - extra_kids += children.len(); - sums_to_merge.push(i); - } - } - - // If we only have one kid, we've already simplified it, and it - // doesn't really matter whether it's a sum already or not, so - // lift it up and continue. - if children_slot.len() == 1 { - return replace_self_with!(&mut children_slot[0]); - } - - let mut children = mem::replace(children_slot, Default::default()).into_vec(); - - if !sums_to_merge.is_empty() { - children.reserve(extra_kids - sums_to_merge.len()); - // Merge all our nested sums, in reverse order so that the - // list indices are not invalidated. - for i in sums_to_merge.drain(..).rev() { - let kid_children = match children.swap_remove(i) { - Self::Sum(c) => c, - _ => unreachable!(), - }; - - // This would be nicer with - // https://github.com/rust-lang/rust/issues/59878 fixed. - children.extend(kid_children.into_vec()); - } - } - - debug_assert!(children.len() >= 2, "Should still have multiple kids!"); - - // Sort by spec order. - children.sort_unstable_by_key(|c| c.sort_key()); - - // NOTE: if the function returns true, by the docs of dedup_by, - // a is removed. - children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok()); - - if children.len() == 1 { - // If only one children remains, lift it up, and carry on. - replace_self_with!(&mut children[0]); - } else { - // Else put our simplified children back. - *children_slot = children.into_boxed_slice().into(); - } - }, - Self::Hypot(ref children) => { - let mut result = match children[0].try_op(&children[0], Mul::mul) { - Ok(res) => res, - Err(..) => return, - }; - - for child in children.iter().skip(1) { - let square = match child.try_op(&child, Mul::mul) { - Ok(res) => res, - Err(..) => return, - }; - result = match result.try_op(&square, Add::add) { - Ok(res) => res, - Err(..) => return, - } - } - - result = match result.try_op(&result, |a, _| a.sqrt()) { - Ok(res) => res, - Err(..) => return, - }; - - replace_self_with!(&mut result); - }, - Self::Negate(ref mut child) => { - // Step 6. - match &mut **child { - CalcNode::Leaf(_) => { - // 1. If root’s child is a numeric value, return an equivalent numeric value, but - // with the value negated (0 - value). - child.negate(); - replace_self_with!(&mut **child); - }, - CalcNode::Negate(value) => { - // 2. If root’s child is a Negate node, return the child’s child. - replace_self_with!(&mut **value); - }, - _ => { - // 3. Return root. - }, - } - }, - Self::Leaf(ref mut l) => { - l.simplify(); - }, - } - } - - /// Simplifies and sorts the kids in the whole calculation subtree. - pub fn simplify_and_sort(&mut self) { - self.visit_depth_first(|node| node.simplify_and_sort_direct_children()) - } - - fn to_css_impl<W>(&self, dest: &mut CssWriter<W>, level: ArgumentLevel) -> fmt::Result - where - W: Write, - { - let write_closing_paren = match *self { - Self::MinMax(_, op) => { - dest.write_str(match op { - MinMaxOp::Max => "max(", - MinMaxOp::Min => "min(", - })?; - true - }, - Self::Clamp { .. } => { - dest.write_str("clamp(")?; - true - }, - Self::Round { strategy, .. } => { - match strategy { - RoundingStrategy::Nearest => dest.write_str("round("), - RoundingStrategy::Up => dest.write_str("round(up, "), - RoundingStrategy::Down => dest.write_str("round(down, "), - RoundingStrategy::ToZero => dest.write_str("round(to-zero, "), - }?; - - true - }, - Self::ModRem { op, .. } => { - dest.write_str(match op { - ModRemOp::Mod => "mod(", - ModRemOp::Rem => "rem(", - })?; - - true - }, - Self::Hypot(_) => { - dest.write_str("hypot(")?; - true - }, - Self::Negate(_) => { - // We never generate a [`Negate`] node as the root of a calculation, only inside - // [`Sum`] nodes as a child. Because negate nodes are handled by the [`Sum`] node - // directly (see below), this node will never be serialized. - debug_assert!( - false, - "We never serialize Negate nodes as they are handled inside Sum nodes." - ); - dest.write_str("(-1 * ")?; - true - }, - Self::Sum(_) => match level { - ArgumentLevel::CalculationRoot => { - dest.write_str("calc(")?; - true - }, - ArgumentLevel::ArgumentRoot => false, - ArgumentLevel::Nested => { - dest.write_str("(")?; - true - }, - }, - Self::Leaf(_) => match level { - ArgumentLevel::CalculationRoot => { - dest.write_str("calc(")?; - true - }, - ArgumentLevel::ArgumentRoot | ArgumentLevel::Nested => false, - }, - }; - - match *self { - Self::MinMax(ref children, _) | Self::Hypot(ref children) => { - let mut first = true; - for child in &**children { - if !first { - dest.write_str(", ")?; - } - first = false; - child.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - } - }, - Self::Negate(ref value) => value.to_css_impl(dest, ArgumentLevel::Nested)?, - Self::Sum(ref children) => { - let mut first = true; - for child in &**children { - if !first { - match child { - Self::Leaf(l) => { - if l.is_negative() { - dest.write_str(" - ")?; - let mut negated = l.clone(); - negated.negate(); - negated.to_css(dest)?; - } else { - dest.write_str(" + ")?; - l.to_css(dest)?; - } - }, - Self::Negate(n) => { - dest.write_str(" - ")?; - n.to_css_impl(dest, ArgumentLevel::Nested)?; - }, - _ => { - dest.write_str(" + ")?; - child.to_css_impl(dest, ArgumentLevel::Nested)?; - }, - } - } else { - first = false; - child.to_css_impl(dest, ArgumentLevel::Nested)?; - } - } - }, - Self::Clamp { - ref min, - ref center, - ref max, - } => { - min.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - dest.write_str(", ")?; - center.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - dest.write_str(", ")?; - max.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - }, - Self::Round { - ref value, - ref step, - .. - } => { - value.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - dest.write_str(", ")?; - step.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - }, - Self::ModRem { - ref dividend, - ref divisor, - .. - } => { - dividend.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - dest.write_str(", ")?; - divisor.to_css_impl(dest, ArgumentLevel::ArgumentRoot)?; - }, - Self::Leaf(ref l) => l.to_css(dest)?, - } - - if write_closing_paren { - dest.write_char(')')?; - } - Ok(()) - } -} - -impl<L: CalcNodeLeaf> PartialOrd for CalcNode<L> { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - match (self, other) { - (&CalcNode::Leaf(ref one), &CalcNode::Leaf(ref other)) => one.partial_cmp(other), - _ => None, - } - } -} - -impl<L: CalcNodeLeaf> ToCss for CalcNode<L> { - /// <https://drafts.csswg.org/css-values/#calc-serialize> - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.to_css_impl(dest, ArgumentLevel::CalculationRoot) - } -} diff --git a/components/style/values/generics/color.rs b/components/style/values/generics/color.rs deleted file mode 100644 index d143e9d3c86..00000000000 --- a/components/style/values/generics/color.rs +++ /dev/null @@ -1,196 +0,0 @@ -/* 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/. */ - -//! Generic types for color properties. - -use crate::color::mix::ColorInterpolationMethod; -use crate::color::AbsoluteColor; -use crate::values::specified::percentage::ToPercentage; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// This struct represents a combined color from a numeric color and -/// the current foreground color (currentcolor keyword). -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] -#[repr(C)] -pub enum GenericColor<Percentage> { - /// The actual numeric color. - Absolute(AbsoluteColor), - /// The `CurrentColor` keyword. - CurrentColor, - /// The color-mix() function. - ColorMix(Box<GenericColorMix<Self, Percentage>>), -} - -/// A restricted version of the css `color-mix()` function, which only supports -/// percentages. -/// -/// https://drafts.csswg.org/css-color-5/#color-mix -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(C)] -pub struct GenericColorMix<Color, Percentage> { - pub interpolation: ColorInterpolationMethod, - pub left: Color, - pub left_percentage: Percentage, - pub right: Color, - pub right_percentage: Percentage, - pub normalize_weights: bool, -} - -pub use self::GenericColorMix as ColorMix; - -impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - fn can_omit<Percentage: ToPercentage>( - percent: &Percentage, - other: &Percentage, - is_left: bool, - ) -> bool { - if percent.is_calc() { - return false; - } - if percent.to_percentage() == 0.5 { - return other.to_percentage() == 0.5; - } - if is_left { - return false; - } - (1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON - } - - dest.write_str("color-mix(")?; - self.interpolation.to_css(dest)?; - dest.write_str(", ")?; - self.left.to_css(dest)?; - if !can_omit(&self.left_percentage, &self.right_percentage, true) { - dest.write_char(' ')?; - self.left_percentage.to_css(dest)?; - } - dest.write_str(", ")?; - self.right.to_css(dest)?; - if !can_omit(&self.right_percentage, &self.left_percentage, false) { - dest.write_char(' ')?; - self.right_percentage.to_css(dest)?; - } - dest.write_char(')') - } -} - -impl<Percentage> ColorMix<GenericColor<Percentage>, Percentage> { - /// Mix the colors so that we get a single color. If any of the 2 colors are - /// not mixable (perhaps not absolute?), then return None. - pub fn mix_to_absolute(&self) -> Option<AbsoluteColor> - where - Percentage: ToPercentage, - { - let left = self.left.as_absolute()?; - let right = self.right.as_absolute()?; - - Some(crate::color::mix::mix( - self.interpolation, - &left, - self.left_percentage.to_percentage(), - &right, - self.right_percentage.to_percentage(), - self.normalize_weights, - )) - } -} - -pub use self::GenericColor as Color; - -impl<Percentage> Color<Percentage> { - /// If this color is absolute return it's value, otherwise return None. - pub fn as_absolute(&self) -> Option<&AbsoluteColor> { - match *self { - Self::Absolute(ref absolute) => Some(absolute), - _ => None, - } - } - - /// Returns a color value representing currentcolor. - pub fn currentcolor() -> Self { - Self::CurrentColor - } - - /// Whether it is a currentcolor value (no numeric color component). - pub fn is_currentcolor(&self) -> bool { - matches!(*self, Self::CurrentColor) - } - - /// Whether this color is an absolute color. - pub fn is_absolute(&self) -> bool { - matches!(*self, Self::Absolute(..)) - } -} - -/// Either `<color>` or `auto`. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToCss, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericColorOrAuto<C> { - /// A `<color>`. - Color(C), - /// `auto` - Auto, -} - -pub use self::GenericColorOrAuto as ColorOrAuto; - -/// Caret color is effectively a ColorOrAuto, but resolves `auto` to -/// currentColor. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToShmem, -)] -#[repr(transparent)] -pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>); - -impl<C> GenericCaretColor<C> { - /// Returns the `auto` value. - pub fn auto() -> Self { - GenericCaretColor(GenericColorOrAuto::Auto) - } -} - -pub use self::GenericCaretColor as CaretColor; diff --git a/components/style/values/generics/column.rs b/components/style/values/generics/column.rs deleted file mode 100644 index 4b5f0e03993..00000000000 --- a/components/style/values/generics/column.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* 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/. */ - -//! Generic types for the column properties. - -/// A generic type for `column-count` values. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum ColumnCount<PositiveInteger> { - /// A positive integer. - Integer(PositiveInteger), - /// The keyword `auto`. - #[animation(error)] - Auto, -} - -impl<I> ColumnCount<I> { - /// Returns `auto`. - #[inline] - pub fn auto() -> Self { - ColumnCount::Auto - } - - /// Returns whether this value is `auto`. - #[inline] - pub fn is_auto(self) -> bool { - matches!(self, ColumnCount::Auto) - } -} diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs deleted file mode 100644 index 3f23c74b334..00000000000 --- a/components/style/values/generics/counters.rs +++ /dev/null @@ -1,287 +0,0 @@ -/* 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/. */ - -//! Generic types for counters-related CSS values. - -#[cfg(feature = "servo")] -use crate::computed_values::list_style_type::T as ListStyleType; -#[cfg(feature = "gecko")] -use crate::values::generics::CounterStyle; -use crate::values::specified::Attr; -use crate::values::CustomIdent; -use std::fmt::{self, Write}; -use std::ops::Deref; -use style_traits::{CssWriter, ToCss}; - -/// A name / value pair for counters. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericCounterPair<Integer> { - /// The name of the counter. - pub name: CustomIdent, - /// The value of the counter / increment / etc. - pub value: Integer, - /// If true, then this represents `reversed(name)`. - /// NOTE: It can only be true on `counter-reset` values. - pub is_reversed: bool, -} -pub use self::GenericCounterPair as CounterPair; - -impl<Integer> ToCss for CounterPair<Integer> -where - Integer: ToCss + PartialEq<i32>, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_reversed { - dest.write_str("reversed(")?; - } - self.name.to_css(dest)?; - if self.is_reversed { - dest.write_char(')')?; - if self.value == i32::min_value() { - return Ok(()); - } - } - dest.write_char(' ')?; - self.value.to_css(dest) - } -} - -/// A generic value for the `counter-increment` property. -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct GenericCounterIncrement<I>(#[css(field_bound)] pub GenericCounters<I>); -pub use self::GenericCounterIncrement as CounterIncrement; - -impl<I> CounterIncrement<I> { - /// Returns a new value for `counter-increment`. - #[inline] - pub fn new(counters: Vec<CounterPair<I>>) -> Self { - CounterIncrement(Counters(counters.into())) - } -} - -impl<I> Deref for CounterIncrement<I> { - type Target = [CounterPair<I>]; - - #[inline] - fn deref(&self) -> &Self::Target { - &(self.0).0 - } -} - -/// A generic value for the `counter-set` property. -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct GenericCounterSet<I>(#[css(field_bound)] pub GenericCounters<I>); -pub use self::GenericCounterSet as CounterSet; - -impl<I> CounterSet<I> { - /// Returns a new value for `counter-set`. - #[inline] - pub fn new(counters: Vec<CounterPair<I>>) -> Self { - CounterSet(Counters(counters.into())) - } -} - -impl<I> Deref for CounterSet<I> { - type Target = [CounterPair<I>]; - - #[inline] - fn deref(&self) -> &Self::Target { - &(self.0).0 - } -} - -/// A generic value for the `counter-reset` property. -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct GenericCounterReset<I>(#[css(field_bound)] pub GenericCounters<I>); -pub use self::GenericCounterReset as CounterReset; - -impl<I> CounterReset<I> { - /// Returns a new value for `counter-reset`. - #[inline] - pub fn new(counters: Vec<CounterPair<I>>) -> Self { - CounterReset(Counters(counters.into())) - } -} - -impl<I> Deref for CounterReset<I> { - type Target = [CounterPair<I>]; - - #[inline] - fn deref(&self) -> &Self::Target { - &(self.0).0 - } -} - -/// A generic value for lists of counters. -/// -/// Keyword `none` is represented by an empty vector. -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct GenericCounters<I>( - #[css(field_bound)] - #[css(iterable, if_empty = "none")] - crate::OwnedSlice<GenericCounterPair<I>>, -); -pub use self::GenericCounters as Counters; - -#[cfg(feature = "servo")] -type CounterStyleType = ListStyleType; - -#[cfg(feature = "gecko")] -type CounterStyleType = CounterStyle; - -#[cfg(feature = "servo")] -#[inline] -fn is_decimal(counter_type: &CounterStyleType) -> bool { - *counter_type == ListStyleType::Decimal -} - -#[cfg(feature = "gecko")] -#[inline] -fn is_decimal(counter_type: &CounterStyleType) -> bool { - *counter_type == CounterStyle::decimal() -} - -/// The specified value for the `content` property. -/// -/// https://drafts.csswg.org/css-content/#propdef-content -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum GenericContent<Image> { - /// `normal` reserved keyword. - Normal, - /// `none` reserved keyword. - None, - /// Content items. - Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>), -} - -pub use self::GenericContent as Content; - -impl<Image> Content<Image> { - /// Whether `self` represents list of items. - #[inline] - pub fn is_items(&self) -> bool { - matches!(*self, Self::Items(..)) - } - - /// Set `content` property to `normal`. - #[inline] - pub fn normal() -> Self { - Content::Normal - } -} - -/// Items for the `content` property. -#[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - SpecifiedValueInfo, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum GenericContentItem<I> { - /// Literal string content. - String(crate::OwnedStr), - /// `counter(name, style)`. - #[css(comma, function)] - Counter(CustomIdent, #[css(skip_if = "is_decimal")] CounterStyleType), - /// `counters(name, separator, style)`. - #[css(comma, function)] - Counters( - CustomIdent, - crate::OwnedStr, - #[css(skip_if = "is_decimal")] CounterStyleType, - ), - /// `open-quote`. - OpenQuote, - /// `close-quote`. - CloseQuote, - /// `no-open-quote`. - NoOpenQuote, - /// `no-close-quote`. - NoCloseQuote, - /// `-moz-alt-content`. - #[cfg(feature = "gecko")] - MozAltContent, - /// `-moz-label-content`. - /// This is needed to make `accesskey` work for XUL labels. It's basically - /// attr(value) otherwise. - #[cfg(feature = "gecko")] - MozLabelContent, - /// `attr([namespace? `|`]? ident)` - Attr(Attr), - /// image-set(url) | url(url) - Image(I), -} - -pub use self::GenericContentItem as ContentItem; diff --git a/components/style/values/generics/easing.rs b/components/style/values/generics/easing.rs deleted file mode 100644 index 803a07fada6..00000000000 --- a/components/style/values/generics/easing.rs +++ /dev/null @@ -1,135 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS Easing Functions. -//! https://drafts.csswg.org/css-easing/#timing-functions - -use crate::parser::ParserContext; - -/// A generic easing function. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToShmem, - Serialize, - Deserialize, -)] -#[value_info(ty = "TIMING_FUNCTION")] -#[repr(u8, C)] -pub enum TimingFunction<Integer, Number, LinearStops> { - /// `linear | ease | ease-in | ease-out | ease-in-out` - Keyword(TimingKeyword), - /// `cubic-bezier(<number>, <number>, <number>, <number>)` - #[allow(missing_docs)] - #[css(comma, function)] - CubicBezier { - x1: Number, - y1: Number, - x2: Number, - y2: Number, - }, - /// `step-start | step-end | steps(<integer>, [ <step-position> ]?)` - /// `<step-position> = jump-start | jump-end | jump-none | jump-both | start | end` - #[css(comma, function)] - #[value_info(other_values = "step-start,step-end")] - Steps(Integer, #[css(skip_if = "is_end")] StepPosition), - /// linear([<linear-stop>]#) - /// <linear-stop> = <output> && <linear-stop-length>? - /// <linear-stop-length> = <percentage>{1, 2} - #[css(function = "linear")] - LinearFunction(LinearStops), -} - -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, - Serialize, - Deserialize, -)] -#[repr(u8)] -pub enum TimingKeyword { - Linear, - Ease, - EaseIn, - EaseOut, - EaseInOut, -} - -/// Before flag, defined as per https://drafts.csswg.org/css-easing/#before-flag -/// This flag is never user-specified. -#[allow(missing_docs)] -#[derive(PartialEq)] -#[repr(u8)] -pub enum BeforeFlag { - Unset, - Set, -} - -#[cfg(feature = "gecko")] -fn step_position_jump_enabled(_context: &ParserContext) -> bool { - static_prefs::pref!("layout.css.step-position-jump.enabled") -} - -#[cfg(feature = "servo")] -fn step_position_jump_enabled(_context: &ParserContext) -> bool { - false -} - -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, - Serialize, - Deserialize, -)] -#[repr(u8)] -pub enum StepPosition { - #[parse(condition = "step_position_jump_enabled")] - JumpStart, - #[parse(condition = "step_position_jump_enabled")] - JumpEnd, - #[parse(condition = "step_position_jump_enabled")] - JumpNone, - #[parse(condition = "step_position_jump_enabled")] - JumpBoth, - Start, - End, -} - -#[inline] -fn is_end(position: &StepPosition) -> bool { - *position == StepPosition::JumpEnd || *position == StepPosition::End -} - -impl<Integer, Number, LinearStops> TimingFunction<Integer, Number, LinearStops> { - /// `ease` - #[inline] - pub fn ease() -> Self { - TimingFunction::Keyword(TimingKeyword::Ease) - } -} diff --git a/components/style/values/generics/effects.rs b/components/style/values/generics/effects.rs deleted file mode 100644 index f5666f30552..00000000000 --- a/components/style/values/generics/effects.rs +++ /dev/null @@ -1,121 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values related to effects. - -/// A generic value for a single `box-shadow`. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericBoxShadow<Color, SizeLength, BlurShapeLength, ShapeLength> { - /// The base shadow. - pub base: GenericSimpleShadow<Color, SizeLength, BlurShapeLength>, - /// The spread radius. - pub spread: ShapeLength, - /// Whether this is an inset box shadow. - #[animation(constant)] - #[css(represents_keyword)] - pub inset: bool, -} - -pub use self::GenericBoxShadow as BoxShadow; - -/// A generic value for a single `filter`. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[animation(no_bound(U))] -#[repr(C, u8)] -pub enum GenericFilter<Angle, NonNegativeFactor, ZeroToOneFactor, Length, Shadow, U> { - /// `blur(<length>)` - #[css(function)] - Blur(Length), - /// `brightness(<factor>)` - #[css(function)] - Brightness(NonNegativeFactor), - /// `contrast(<factor>)` - #[css(function)] - Contrast(NonNegativeFactor), - /// `grayscale(<factor>)` - #[css(function)] - Grayscale(ZeroToOneFactor), - /// `hue-rotate(<angle>)` - #[css(function)] - HueRotate(Angle), - /// `invert(<factor>)` - #[css(function)] - Invert(ZeroToOneFactor), - /// `opacity(<factor>)` - #[css(function)] - Opacity(ZeroToOneFactor), - /// `saturate(<factor>)` - #[css(function)] - Saturate(NonNegativeFactor), - /// `sepia(<factor>)` - #[css(function)] - Sepia(ZeroToOneFactor), - /// `drop-shadow(...)` - #[css(function)] - DropShadow(Shadow), - /// `<url>` - #[animation(error)] - Url(U), -} - -pub use self::GenericFilter as Filter; - -/// A generic value for the `drop-shadow()` filter and the `text-shadow` property. -/// -/// Contrary to the canonical order from the spec, the color is serialised -/// first, like in Gecko and Webkit. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericSimpleShadow<Color, SizeLength, ShapeLength> { - /// Color. - pub color: Color, - /// Horizontal radius. - pub horizontal: SizeLength, - /// Vertical radius. - pub vertical: SizeLength, - /// Blur radius. - pub blur: ShapeLength, -} - -pub use self::GenericSimpleShadow as SimpleShadow; diff --git a/components/style/values/generics/flex.rs b/components/style/values/generics/flex.rs deleted file mode 100644 index 85b64000f2b..00000000000 --- a/components/style/values/generics/flex.rs +++ /dev/null @@ -1,33 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values related to flexbox. - -/// A generic value for the `flex-basis` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub enum GenericFlexBasis<S> { - /// `content` - Content, - /// `<width>` - Size(S), -} - -pub use self::GenericFlexBasis as FlexBasis; diff --git a/components/style/values/generics/font.rs b/components/style/values/generics/font.rs deleted file mode 100644 index 09ed542e97a..00000000000 --- a/components/style/values/generics/font.rs +++ /dev/null @@ -1,271 +0,0 @@ -/* 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/. */ - -//! Generic types for font stuff. - -use crate::parser::{Parse, ParserContext}; -use crate::One; -use byteorder::{BigEndian, ReadBytesExt}; -use cssparser::Parser; -use std::fmt::{self, Write}; -use std::io::Cursor; -use style_traits::{CssWriter, ParseError}; -use style_traits::{StyleParseErrorKind, ToCss}; - -/// A trait for values that are labelled with a FontTag (for feature and -/// variation settings). -pub trait TaggedFontValue { - /// The value's tag. - fn tag(&self) -> FontTag; -} - -/// https://drafts.csswg.org/css-fonts-4/#feature-tag-value -#[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct FeatureTagValue<Integer> { - /// A four-character tag, packed into a u32 (one byte per character). - pub tag: FontTag, - /// The actual value. - pub value: Integer, -} - -impl<T> TaggedFontValue for FeatureTagValue<T> { - fn tag(&self) -> FontTag { - self.tag - } -} - -impl<Integer> ToCss for FeatureTagValue<Integer> -where - Integer: One + ToCss + PartialEq, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.tag.to_css(dest)?; - // Don't serialize the default value. - if !self.value.is_one() { - dest.write_char(' ')?; - self.value.to_css(dest)?; - } - - Ok(()) - } -} - -/// Variation setting for a single feature, see: -/// -/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub struct VariationValue<Number> { - /// A four-character tag, packed into a u32 (one byte per character). - #[animation(constant)] - pub tag: FontTag, - /// The actual value. - pub value: Number, -} - -impl<T> TaggedFontValue for VariationValue<T> { - fn tag(&self) -> FontTag { - self.tag - } -} - -/// A value both for font-variation-settings and font-feature-settings. -#[derive( - Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToResolvedValue, ToShmem, -)] -#[css(comma)] -pub struct FontSettings<T>(#[css(if_empty = "normal", iterable)] pub Box<[T]>); - -impl<T> FontSettings<T> { - /// Default value of font settings as `normal`. - #[inline] - pub fn normal() -> Self { - FontSettings(vec![].into_boxed_slice()) - } -} - -impl<T: Parse> Parse for FontSettings<T> { - /// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings - /// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|i| i.expect_ident_matching("normal")) - .is_ok() - { - return Ok(Self::normal()); - } - - Ok(FontSettings( - input - .parse_comma_separated(|i| T::parse(context, i))? - .into_boxed_slice(), - )) - } -} - -/// A font four-character tag, represented as a u32 for convenience. -/// -/// See: -/// https://drafts.csswg.org/css-fonts-4/#font-variation-settings-def -/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-feature-settings -/// -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct FontTag(pub u32); - -impl ToCss for FontTag { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - use byteorder::ByteOrder; - use std::str; - - let mut raw = [0u8; 4]; - BigEndian::write_u32(&mut raw, self.0); - str::from_utf8(&raw).unwrap_or_default().to_css(dest) - } -} - -impl Parse for FontTag { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let tag = input.expect_string()?; - - // allowed strings of length 4 containing chars: <U+20, U+7E> - if tag.len() != 4 || tag.as_bytes().iter().any(|c| *c < b' ' || *c > b'~') { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - let mut raw = Cursor::new(tag.as_bytes()); - Ok(FontTag(raw.read_u32::<BigEndian>().unwrap())) - } -} - -/// A generic value for the `font-style` property. -/// -/// https://drafts.csswg.org/css-fonts-4/#font-style-prop -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToResolvedValue, - ToShmem, -)] -pub enum FontStyle<Angle> { - #[animation(error)] - Normal, - #[animation(error)] - Italic, - #[value_info(starts_with_keyword)] - Oblique(Angle), -} - -/// A generic value for the `font-size-adjust` property. -/// -/// https://www.w3.org/TR/css-fonts-4/#font-size-adjust-prop -/// https://github.com/w3c/csswg-drafts/issues/6160 -/// https://github.com/w3c/csswg-drafts/issues/6288 -#[allow(missing_docs)] -#[repr(u8)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub enum GenericFontSizeAdjust<Number> { - #[animation(error)] - None, - // 'ex-height' is the implied basis, so the keyword can be omitted - ExHeight(Number), - #[value_info(starts_with_keyword)] - CapHeight(Number), - #[value_info(starts_with_keyword)] - ChWidth(Number), - #[value_info(starts_with_keyword)] - IcWidth(Number), - #[value_info(starts_with_keyword)] - IcHeight(Number), -} - -impl<Number: ToCss> ToCss for GenericFontSizeAdjust<Number> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let (prefix, value) = match self { - Self::None => return dest.write_str("none"), - Self::ExHeight(v) => ("", v), - Self::CapHeight(v) => ("cap-height ", v), - Self::ChWidth(v) => ("ch-width ", v), - Self::IcWidth(v) => ("ic-width ", v), - Self::IcHeight(v) => ("ic-height ", v), - }; - - dest.write_str(prefix)?; - value.to_css(dest) - } -} diff --git a/components/style/values/generics/grid.rs b/components/style/values/generics/grid.rs deleted file mode 100644 index e35c96a28ca..00000000000 --- a/components/style/values/generics/grid.rs +++ /dev/null @@ -1,829 +0,0 @@ -/* 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/. */ - -//! Generic types for the handling of -//! [grids](https://drafts.csswg.org/css-grid/). - -use crate::parser::{Parse, ParserContext}; -use crate::values::specified; -use crate::values::specified::grid::parse_line_names; -use crate::values::{CSSFloat, CustomIdent}; -use crate::{Atom, Zero}; -use cssparser::Parser; -use std::fmt::{self, Write}; -use std::{cmp, usize}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// These are the limits that we choose to clamp grid line numbers to. -/// http://drafts.csswg.org/css-grid/#overlarge-grids -/// line_num is clamped to this range at parse time. -pub const MIN_GRID_LINE: i32 = -10000; -/// See above. -pub const MAX_GRID_LINE: i32 = 10000; - -/// A `<grid-line>` type. -/// -/// <https://drafts.csswg.org/css-grid/#typedef-grid-row-start-grid-line> -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericGridLine<Integer> { - /// A custom identifier for named lines, or the empty atom otherwise. - /// - /// <https://drafts.csswg.org/css-grid/#grid-placement-slot> - pub ident: Atom, - /// Denotes the nth grid line from grid item's placement. - /// - /// This is clamped by MIN_GRID_LINE and MAX_GRID_LINE. - /// - /// NOTE(emilio): If we ever allow animating these we need to either do - /// something more complicated for the clamping, or do this clamping at - /// used-value time. - pub line_num: Integer, - /// Flag to check whether it's a `span` keyword. - pub is_span: bool, -} - -pub use self::GenericGridLine as GridLine; - -impl<Integer> GridLine<Integer> -where - Integer: PartialEq + Zero, -{ - /// The `auto` value. - pub fn auto() -> Self { - Self { - is_span: false, - line_num: Zero::zero(), - ident: atom!(""), - } - } - - /// Check whether this `<grid-line>` represents an `auto` value. - pub fn is_auto(&self) -> bool { - self.ident == atom!("") && self.line_num.is_zero() && !self.is_span - } - - /// Check whether this `<grid-line>` represents a `<custom-ident>` value. - pub fn is_ident_only(&self) -> bool { - self.ident != atom!("") && self.line_num.is_zero() && !self.is_span - } - - /// Check if `self` makes `other` omittable according to the rules at: - /// https://drafts.csswg.org/css-grid/#propdef-grid-column - /// https://drafts.csswg.org/css-grid/#propdef-grid-area - pub fn can_omit(&self, other: &Self) -> bool { - if self.is_ident_only() { - self == other - } else { - other.is_auto() - } - } -} - -impl<Integer> ToCss for GridLine<Integer> -where - Integer: ToCss + PartialEq + Zero, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_auto() { - return dest.write_str("auto"); - } - - if self.is_span { - dest.write_str("span")?; - } - - if !self.line_num.is_zero() { - if self.is_span { - dest.write_char(' ')?; - } - self.line_num.to_css(dest)?; - } - - if self.ident != atom!("") { - if self.is_span || !self.line_num.is_zero() { - dest.write_char(' ')?; - } - CustomIdent(self.ident.clone()).to_css(dest)?; - } - - Ok(()) - } -} - -impl Parse for GridLine<specified::Integer> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut grid_line = Self::auto(); - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(grid_line); - } - - // <custom-ident> | [ <integer> && <custom-ident>? ] | [ span && [ <integer> || <custom-ident> ] ] - // This <grid-line> horror is simply, - // [ span? && [ <custom-ident> || <integer> ] ] - // And, for some magical reason, "span" should be the first or last value and not in-between. - let mut val_before_span = false; - - for _ in 0..3 { - // Maximum possible entities for <grid-line> - let location = input.current_source_location(); - if input.try_parse(|i| i.expect_ident_matching("span")).is_ok() { - if grid_line.is_span { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - if !grid_line.line_num.is_zero() || grid_line.ident != atom!("") { - val_before_span = true; - } - - grid_line.is_span = true; - } else if let Ok(i) = input.try_parse(|i| specified::Integer::parse(context, i)) { - // FIXME(emilio): Probably shouldn't reject if it's calc()... - let value = i.value(); - if value == 0 || val_before_span || !grid_line.line_num.is_zero() { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - grid_line.line_num = specified::Integer::new(cmp::max( - MIN_GRID_LINE, - cmp::min(value, MAX_GRID_LINE), - )); - } else if let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) { - if val_before_span || grid_line.ident != atom!("") { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - // NOTE(emilio): `span` is consumed above, so we only need to - // reject `auto`. - grid_line.ident = CustomIdent::from_ident(location, &name, &["auto"])?.0; - } else { - break; - } - } - - if grid_line.is_auto() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - if grid_line.is_span { - if !grid_line.line_num.is_zero() { - if grid_line.line_num.value() <= 0 { - // disallow negative integers for grid spans - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } else if grid_line.ident == atom!("") { - // integer could be omitted - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - - Ok(grid_line) - } -} - -/// A track breadth for explicit grid track sizing. It's generic solely to -/// avoid re-implementing it for the computed type. -/// -/// <https://drafts.csswg.org/css-grid/#typedef-track-breadth> -#[derive( - Animate, - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericTrackBreadth<L> { - /// The generic type is almost always a non-negative `<length-percentage>` - Breadth(L), - /// A flex fraction specified in `fr` units. - #[css(dimension)] - Fr(CSSFloat), - /// `auto` - Auto, - /// `min-content` - MinContent, - /// `max-content` - MaxContent, -} - -pub use self::GenericTrackBreadth as TrackBreadth; - -impl<L> TrackBreadth<L> { - /// Check whether this is a `<fixed-breadth>` (i.e., it only has `<length-percentage>`) - /// - /// <https://drafts.csswg.org/css-grid/#typedef-fixed-breadth> - #[inline] - pub fn is_fixed(&self) -> bool { - matches!(*self, TrackBreadth::Breadth(..)) - } -} - -/// A `<track-size>` type for explicit grid track sizing. Like `<track-breadth>`, this is -/// generic only to avoid code bloat. It only takes `<length-percentage>` -/// -/// <https://drafts.csswg.org/css-grid/#typedef-track-size> -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericTrackSize<L> { - /// A flexible `<track-breadth>` - Breadth(GenericTrackBreadth<L>), - /// A `minmax` function for a range over an inflexible `<track-breadth>` - /// and a flexible `<track-breadth>` - /// - /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-minmax> - #[css(function)] - Minmax(GenericTrackBreadth<L>, GenericTrackBreadth<L>), - /// A `fit-content` function. - /// - /// This stores a TrackBreadth<L> for convenience, but it can only be a - /// LengthPercentage. - /// - /// <https://drafts.csswg.org/css-grid/#valdef-grid-template-columns-fit-content> - #[css(function)] - FitContent(GenericTrackBreadth<L>), -} - -pub use self::GenericTrackSize as TrackSize; - -impl<L> TrackSize<L> { - /// The initial value. - const INITIAL_VALUE: Self = TrackSize::Breadth(TrackBreadth::Auto); - - /// Returns the initial value. - pub const fn initial_value() -> Self { - Self::INITIAL_VALUE - } - - /// Returns true if `self` is the initial value. - pub fn is_initial(&self) -> bool { - matches!(*self, TrackSize::Breadth(TrackBreadth::Auto)) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 - } - - /// Check whether this is a `<fixed-size>` - /// - /// <https://drafts.csswg.org/css-grid/#typedef-fixed-size> - pub fn is_fixed(&self) -> bool { - match *self { - TrackSize::Breadth(ref breadth) => breadth.is_fixed(), - // For minmax function, it could be either - // minmax(<fixed-breadth>, <track-breadth>) or minmax(<inflexible-breadth>, <fixed-breadth>), - // and since both variants are a subset of minmax(<inflexible-breadth>, <track-breadth>), we only - // need to make sure that they're fixed. So, we don't have to modify the parsing function. - TrackSize::Minmax(ref breadth_1, ref breadth_2) => { - if breadth_1.is_fixed() { - return true; // the second value is always a <track-breadth> - } - - match *breadth_1 { - TrackBreadth::Fr(_) => false, // should be <inflexible-breadth> at this point - _ => breadth_2.is_fixed(), - } - }, - TrackSize::FitContent(_) => false, - } - } -} - -impl<L> Default for TrackSize<L> { - fn default() -> Self { - Self::initial_value() - } -} - -impl<L: ToCss> ToCss for TrackSize<L> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - TrackSize::Breadth(ref breadth) => breadth.to_css(dest), - TrackSize::Minmax(ref min, ref max) => { - // According to gecko minmax(auto, <flex>) is equivalent to <flex>, - // and both are serialized as <flex>. - if let TrackBreadth::Auto = *min { - if let TrackBreadth::Fr(_) = *max { - return max.to_css(dest); - } - } - - dest.write_str("minmax(")?; - min.to_css(dest)?; - dest.write_str(", ")?; - max.to_css(dest)?; - dest.write_char(')') - }, - TrackSize::FitContent(ref lp) => { - dest.write_str("fit-content(")?; - lp.to_css(dest)?; - dest.write_char(')') - }, - } - } -} - -/// A `<track-size>+`. -/// We use the empty slice as `auto`, and always parse `auto` as an empty slice. -/// This means it's impossible to have a slice containing only one auto item. -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct GenericImplicitGridTracks<T>( - #[css(if_empty = "auto", iterable)] pub crate::OwnedSlice<T>, -); - -pub use self::GenericImplicitGridTracks as ImplicitGridTracks; - -impl<T: fmt::Debug + Default + PartialEq> ImplicitGridTracks<T> { - /// Returns true if current value is same as its initial value (i.e. auto). - pub fn is_initial(&self) -> bool { - debug_assert_ne!( - *self, - ImplicitGridTracks(crate::OwnedSlice::from(vec![Default::default()])) - ); - self.0.is_empty() - } -} - -/// Helper function for serializing identifiers with a prefix and suffix, used -/// for serializing <line-names> (in grid). -pub fn concat_serialize_idents<W>( - prefix: &str, - suffix: &str, - slice: &[CustomIdent], - sep: &str, - dest: &mut CssWriter<W>, -) -> fmt::Result -where - W: Write, -{ - if let Some((ref first, rest)) = slice.split_first() { - dest.write_str(prefix)?; - first.to_css(dest)?; - for thing in rest { - dest.write_str(sep)?; - thing.to_css(dest)?; - } - - dest.write_str(suffix)?; - } - - Ok(()) -} - -/// The initial argument of the `repeat` function. -/// -/// <https://drafts.csswg.org/css-grid/#typedef-track-repeat> -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, -)] -#[repr(C, u8)] -pub enum RepeatCount<Integer> { - /// A positive integer. This is allowed only for `<track-repeat>` and `<fixed-repeat>` - Number(Integer), - /// An `<auto-fill>` keyword allowed only for `<auto-repeat>` - AutoFill, - /// An `<auto-fit>` keyword allowed only for `<auto-repeat>` - AutoFit, -} - -impl Parse for RepeatCount<specified::Integer> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(mut i) = input.try_parse(|i| specified::Integer::parse_positive(context, i)) { - if i.value() > MAX_GRID_LINE { - i = specified::Integer::new(MAX_GRID_LINE); - } - return Ok(RepeatCount::Number(i)); - } - try_match_ident_ignore_ascii_case! { input, - "auto-fill" => Ok(RepeatCount::AutoFill), - "auto-fit" => Ok(RepeatCount::AutoFit), - } - } -} - -/// The structure containing `<line-names>` and `<track-size>` values. -/// -/// It can also hold `repeat()` function parameters, which expands into the respective -/// values in its computed form. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[css(function = "repeat")] -#[repr(C)] -pub struct GenericTrackRepeat<L, I> { - /// The number of times for the value to be repeated (could also be `auto-fit` or `auto-fill`) - pub count: RepeatCount<I>, - /// `<line-names>` accompanying `<track_size>` values. - /// - /// If there's no `<line-names>`, then it's represented by an empty vector. - /// For N `<track-size>` values, there will be N+1 `<line-names>`, and so this vector's - /// length is always one value more than that of the `<track-size>`. - pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, - /// `<track-size>` values. - pub track_sizes: crate::OwnedSlice<GenericTrackSize<L>>, -} - -pub use self::GenericTrackRepeat as TrackRepeat; - -impl<L: ToCss, I: ToCss> ToCss for TrackRepeat<L, I> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("repeat(")?; - self.count.to_css(dest)?; - dest.write_str(", ")?; - - let mut line_names_iter = self.line_names.iter(); - for (i, (ref size, ref names)) in self - .track_sizes - .iter() - .zip(&mut line_names_iter) - .enumerate() - { - if i > 0 { - dest.write_char(' ')?; - } - - concat_serialize_idents("[", "] ", names, " ", dest)?; - size.to_css(dest)?; - } - - if let Some(line_names_last) = line_names_iter.next() { - concat_serialize_idents(" [", "]", line_names_last, " ", dest)?; - } - - dest.write_char(')')?; - - Ok(()) - } -} - -/// Track list values. Can be <track-size> or <track-repeat> -#[derive( - Animate, - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericTrackListValue<LengthPercentage, Integer> { - /// A <track-size> value. - TrackSize(#[animation(field_bound)] GenericTrackSize<LengthPercentage>), - /// A <track-repeat> value. - TrackRepeat(#[animation(field_bound)] GenericTrackRepeat<LengthPercentage, Integer>), -} - -pub use self::GenericTrackListValue as TrackListValue; - -impl<L, I> TrackListValue<L, I> { - // FIXME: can't use TrackSize::initial_value() here b/c rustc error "is not yet stable as a const fn" - const INITIAL_VALUE: Self = TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)); - - fn is_repeat(&self) -> bool { - matches!(*self, TrackListValue::TrackRepeat(..)) - } - - /// Returns true if `self` is the initial value. - pub fn is_initial(&self) -> bool { - matches!( - *self, - TrackListValue::TrackSize(TrackSize::Breadth(TrackBreadth::Auto)) - ) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 - } -} - -impl<L, I> Default for TrackListValue<L, I> { - #[inline] - fn default() -> Self { - Self::INITIAL_VALUE - } -} - -/// A grid `<track-list>` type. -/// -/// <https://drafts.csswg.org/css-grid/#typedef-track-list> -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericTrackList<LengthPercentage, Integer> { - /// The index in `values` where our `<auto-repeat>` value is, if in bounds. - #[css(skip)] - pub auto_repeat_index: usize, - /// A vector of `<track-size> | <track-repeat>` values. - pub values: crate::OwnedSlice<GenericTrackListValue<LengthPercentage, Integer>>, - /// `<line-names>` accompanying `<track-size> | <track-repeat>` values. - /// - /// If there's no `<line-names>`, then it's represented by an empty vector. - /// For N values, there will be N+1 `<line-names>`, and so this vector's - /// length is always one value more than that of the `<track-size>`. - pub line_names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, -} - -pub use self::GenericTrackList as TrackList; - -impl<L, I> TrackList<L, I> { - /// Whether this track list is an explicit track list (that is, doesn't have - /// any repeat values). - pub fn is_explicit(&self) -> bool { - !self.values.iter().any(|v| v.is_repeat()) - } - - /// Whether this track list has an `<auto-repeat>` value. - pub fn has_auto_repeat(&self) -> bool { - self.auto_repeat_index < self.values.len() - } -} - -impl<L: ToCss, I: ToCss> ToCss for TrackList<L, I> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let mut values_iter = self.values.iter().peekable(); - let mut line_names_iter = self.line_names.iter().peekable(); - - for idx in 0.. { - let names = line_names_iter.next().unwrap(); // This should exist! - concat_serialize_idents("[", "]", names, " ", dest)?; - - match values_iter.next() { - Some(value) => { - if !names.is_empty() { - dest.write_char(' ')?; - } - - value.to_css(dest)?; - }, - None => break, - } - - if values_iter.peek().is_some() || - line_names_iter.peek().map_or(false, |v| !v.is_empty()) || - (idx + 1 == self.auto_repeat_index) - { - dest.write_char(' ')?; - } - } - - Ok(()) - } -} - -/// The `<line-name-list>` for subgrids. -/// -/// `subgrid [ <line-names> | repeat(<positive-integer> | auto-fill, <line-names>+) ]+` -/// -/// https://drafts.csswg.org/css-grid-2/#typedef-line-name-list -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct LineNameList { - /// The optional `<line-name-list>` - pub names: crate::OwnedSlice<crate::OwnedSlice<CustomIdent>>, - /// Indicates the starting line names that requires `auto-fill`, if in bounds. - pub fill_start: usize, - /// Indicates the number of line names in the auto-fill - pub fill_len: usize, -} - -impl Parse for LineNameList { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_ident_matching("subgrid")?; - let mut line_names = vec![]; - let mut fill_data = None; - // Rather than truncating the result after inserting values, just - // have a maximum number of values. This gives us an early out on very - // large name lists, but more importantly prevents OOM on huge repeat - // expansions. (bug 1583429) - let mut max_remaining = MAX_GRID_LINE as usize; - - loop { - let repeat_parse_result = input.try_parse(|input| { - input.expect_function_matching("repeat")?; - input.parse_nested_block(|input| { - let count = RepeatCount::parse(context, input)?; - input.expect_comma()?; - let mut names_list = vec![]; - names_list.push(parse_line_names(input)?); // there should be at least one - while let Ok(names) = input.try_parse(parse_line_names) { - names_list.push(names); - } - Ok((names_list, count)) - }) - }); - if let Ok((names_list, count)) = repeat_parse_result { - let mut handle_size = |n| { - let n = cmp::min(n, max_remaining); - max_remaining -= n; - n - }; - match count { - // FIXME(emilio): we shouldn't expand repeat() at - // parse time for subgrid. (bug 1583429) - RepeatCount::Number(num) => { - let n = handle_size(num.value() as usize * names_list.len()); - line_names.extend(names_list.iter().cloned().cycle().take(n)); - }, - RepeatCount::AutoFill if fill_data.is_none() => { - let fill_idx = line_names.len(); - let fill_len = names_list.len(); - fill_data = Some((fill_idx, fill_len)); - let n = handle_size(fill_len); - line_names.extend(names_list.into_iter().take(n)); - }, - _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } else if let Ok(names) = input.try_parse(parse_line_names) { - if max_remaining > 0 { - line_names.push(names); - max_remaining -= 1; - } - } else { - break; - } - } - - debug_assert!(line_names.len() <= MAX_GRID_LINE as usize); - - let (fill_start, fill_len) = fill_data.unwrap_or((0, 0)); - - Ok(LineNameList { - names: line_names.into(), - fill_start: fill_start, - fill_len: fill_len, - }) - } -} - -impl ToCss for LineNameList { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("subgrid")?; - let fill_start = self.fill_start; - let fill_len = self.fill_len; - for (i, names) in self.names.iter().enumerate() { - if fill_len > 0 && i == fill_start { - dest.write_str(" repeat(auto-fill,")?; - } - - dest.write_str(" [")?; - - if let Some((ref first, rest)) = names.split_first() { - first.to_css(dest)?; - for name in rest { - dest.write_char(' ')?; - name.to_css(dest)?; - } - } - - dest.write_char(']')?; - if fill_len > 0 && i == fill_start + fill_len - 1 { - dest.write_char(')')?; - } - } - - Ok(()) - } -} - -/// Variants for `<grid-template-rows> | <grid-template-columns>` -#[derive( - Animate, - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[value_info(other_values = "subgrid")] -#[repr(C, u8)] -pub enum GenericGridTemplateComponent<L, I> { - /// `none` value. - None, - /// The grid `<track-list>` - TrackList( - #[animation(field_bound)] - #[compute(field_bound)] - #[resolve(field_bound)] - #[shmem(field_bound)] - Box<GenericTrackList<L, I>>, - ), - /// A `subgrid <line-name-list>?` - /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. - #[animation(error)] - Subgrid(Box<LineNameList>), - /// `masonry` value. - /// https://github.com/w3c/csswg-drafts/issues/4650 - Masonry, -} - -pub use self::GenericGridTemplateComponent as GridTemplateComponent; - -impl<L, I> GridTemplateComponent<L, I> { - /// The initial value. - const INITIAL_VALUE: Self = Self::None; - - /// Returns length of the <track-list>s <track-size> - pub fn track_list_len(&self) -> usize { - match *self { - GridTemplateComponent::TrackList(ref tracklist) => tracklist.values.len(), - _ => 0, - } - } - - /// Returns true if `self` is the initial value. - pub fn is_initial(&self) -> bool { - matches!(*self, Self::None) // FIXME: can't use Self::INITIAL_VALUE here yet: https://github.com/rust-lang/rust/issues/66585 - } -} - -impl<L, I> Default for GridTemplateComponent<L, I> { - #[inline] - fn default() -> Self { - Self::INITIAL_VALUE - } -} diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs deleted file mode 100644 index 7e0ac7ec5b2..00000000000 --- a/components/style/values/generics/image.rs +++ /dev/null @@ -1,614 +0,0 @@ -/* 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/. */ - -//! Generic types for the handling of [images]. -//! -//! [images]: https://drafts.csswg.org/css-images/#image-values - -use crate::custom_properties; -use crate::values::generics::position::PositionComponent; -use crate::values::generics::Optional; -use crate::values::serialize_atom_identifier; -use crate::Atom; -use crate::Zero; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// An `<image> | none` value. -/// -/// https://drafts.csswg.org/css-images/#image-values -#[derive( - Clone, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[repr(C, u8)] -pub enum GenericImage<G, MozImageRect, ImageUrl, Color, Percentage, Resolution> { - /// `none` variant. - None, - /// A `<url()>` image. - Url(ImageUrl), - - /// A `<gradient>` image. Gradients are rather large, and not nearly as - /// common as urls, so we box them here to keep the size of this enum sane. - Gradient(Box<G>), - /// A `-moz-image-rect` image. Also fairly large and rare. - // not cfg’ed out on non-Gecko to avoid `error[E0392]: parameter `MozImageRect` is never used` - // Instead we make MozImageRect an empty enum - Rect(Box<MozImageRect>), - - /// A `-moz-element(# <element-id>)` - #[cfg(feature = "gecko")] - #[css(function = "-moz-element")] - Element(Atom), - - /// A paint worklet image. - /// <https://drafts.css-houdini.org/css-paint-api/> - #[cfg(feature = "servo")] - PaintWorklet(PaintWorklet), - - /// A `<cross-fade()>` image. Storing this directly inside of - /// GenericImage increases the size by 8 bytes so we box it here - /// and store images directly inside of cross-fade instead of - /// boxing them there. - CrossFade(Box<GenericCrossFade<Self, Color, Percentage>>), - - /// An `image-set()` function. - ImageSet(#[compute(field_bound)] Box<GenericImageSet<Self, Resolution>>), -} - -pub use self::GenericImage as Image; - -/// <https://drafts.csswg.org/css-images-4/#cross-fade-function> -#[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToResolvedValue, ToShmem, ToCss, ToComputedValue, -)] -#[css(comma, function = "cross-fade")] -#[repr(C)] -pub struct GenericCrossFade<Image, Color, Percentage> { - /// All of the image percent pairings passed as arguments to - /// cross-fade. - #[css(iterable)] - pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>, -} - -/// An optional percent and a cross fade image. -#[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, -)] -#[repr(C)] -pub struct GenericCrossFadeElement<Image, Color, Percentage> { - /// The percent of the final image that `image` will be. - pub percent: Optional<Percentage>, - /// A color or image that will be blended when cross-fade is - /// evaluated. - pub image: GenericCrossFadeImage<Image, Color>, -} - -/// An image or a color. `cross-fade` takes either when blending -/// images together. -#[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, -)] -#[repr(C, u8)] -pub enum GenericCrossFadeImage<I, C> { - /// A boxed image value. Boxing provides indirection so images can - /// be cross-fades and cross-fades can be images. - Image(I), - /// A color value. - Color(C), -} - -pub use self::GenericCrossFade as CrossFade; -pub use self::GenericCrossFadeElement as CrossFadeElement; -pub use self::GenericCrossFadeImage as CrossFadeImage; - -/// https://drafts.csswg.org/css-images-4/#image-set-notation -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] -#[css(comma, function = "image-set")] -#[repr(C)] -pub struct GenericImageSet<Image, Resolution> { - /// The index of the selected candidate. usize::MAX for specified values or invalid images. - #[css(skip)] - pub selected_index: usize, - - /// All of the image and resolution pairs. - #[css(iterable)] - pub items: crate::OwnedSlice<GenericImageSetItem<Image, Resolution>>, -} - -/// An optional percent and a cross fade image. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] -#[repr(C)] -pub struct GenericImageSetItem<Image, Resolution> { - /// `<image>`. `<string>` is converted to `Image::Url` at parse time. - pub image: Image, - /// The `<resolution>`. - /// - /// TODO: Skip serialization if it is 1x. - pub resolution: Resolution, - - /// The `type(<string>)` - /// (Optional) Specify the image's MIME type - pub mime_type: crate::OwnedStr, - - /// True if mime_type has been specified - pub has_mime_type: bool, -} - -impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - self.image.to_css(dest)?; - dest.write_char(' ')?; - self.resolution.to_css(dest)?; - - if self.has_mime_type { - dest.write_char(' ')?; - dest.write_str("type(")?; - self.mime_type.to_css(dest)?; - dest.write_char(')')?; - } - Ok(()) - } -} - -pub use self::GenericImageSet as ImageSet; -pub use self::GenericImageSetItem as ImageSetItem; - -/// A CSS gradient. -/// <https://drafts.csswg.org/css-images/#gradients> -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] -#[repr(C)] -pub enum GenericGradient< - LineDirection, - LengthPercentage, - NonNegativeLength, - NonNegativeLengthPercentage, - Position, - Angle, - AngleOrPercentage, - Color, -> { - /// A linear gradient. - Linear { - /// Line direction - direction: LineDirection, - /// The color stops and interpolation hints. - items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, - /// True if this is a repeating gradient. - repeating: bool, - /// Compatibility mode. - compat_mode: GradientCompatMode, - }, - /// A radial gradient. - Radial { - /// Shape of gradient - shape: GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>, - /// Center of gradient - position: Position, - /// The color stops and interpolation hints. - items: crate::OwnedSlice<GenericGradientItem<Color, LengthPercentage>>, - /// True if this is a repeating gradient. - repeating: bool, - /// Compatibility mode. - compat_mode: GradientCompatMode, - }, - /// A conic gradient. - Conic { - /// Start angle of gradient - angle: Angle, - /// Center of gradient - position: Position, - /// The color stops and interpolation hints. - items: crate::OwnedSlice<GenericGradientItem<Color, AngleOrPercentage>>, - /// True if this is a repeating gradient. - repeating: bool, - }, -} - -pub use self::GenericGradient as Gradient; - -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[repr(u8)] -/// Whether we used the modern notation or the compatibility `-webkit`, `-moz` prefixes. -pub enum GradientCompatMode { - /// Modern syntax. - Modern, - /// `-webkit` prefix. - WebKit, - /// `-moz` prefix - Moz, -} - -/// A radial gradient's ending shape. -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, -)] -#[repr(C, u8)] -pub enum GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage> { - /// A circular gradient. - Circle(GenericCircle<NonNegativeLength>), - /// An elliptic gradient. - Ellipse(GenericEllipse<NonNegativeLengthPercentage>), -} - -pub use self::GenericEndingShape as EndingShape; - -/// A circle shape. -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[repr(C, u8)] -pub enum GenericCircle<NonNegativeLength> { - /// A circle radius. - Radius(NonNegativeLength), - /// A circle extent. - Extent(ShapeExtent), -} - -pub use self::GenericCircle as Circle; - -/// An ellipse shape. -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, -)] -#[repr(C, u8)] -pub enum GenericEllipse<NonNegativeLengthPercentage> { - /// An ellipse pair of radii. - Radii(NonNegativeLengthPercentage, NonNegativeLengthPercentage), - /// An ellipse extent. - Extent(ShapeExtent), -} - -pub use self::GenericEllipse as Ellipse; - -/// <https://drafts.csswg.org/css-images/#typedef-extent-keyword> -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ShapeExtent { - ClosestSide, - FarthestSide, - ClosestCorner, - FarthestCorner, - Contain, - Cover, -} - -/// A gradient item. -/// <https://drafts.csswg.org/css-images-4/#color-stop-syntax> -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, -)] -#[repr(C, u8)] -pub enum GenericGradientItem<Color, T> { - /// A simple color stop, without position. - SimpleColorStop(Color), - /// A complex color stop, with a position. - ComplexColorStop { - /// The color for the stop. - color: Color, - /// The position for the stop. - position: T, - }, - /// An interpolation hint. - InterpolationHint(T), -} - -pub use self::GenericGradientItem as GradientItem; - -/// A color stop. -/// <https://drafts.csswg.org/css-images/#typedef-color-stop-list> -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, -)] -pub struct ColorStop<Color, T> { - /// The color of this stop. - pub color: Color, - /// The position of this stop. - pub position: Option<T>, -} - -impl<Color, T> ColorStop<Color, T> { - /// Convert the color stop into an appropriate `GradientItem`. - #[inline] - pub fn into_item(self) -> GradientItem<Color, T> { - match self.position { - Some(position) => GradientItem::ComplexColorStop { - color: self.color, - position, - }, - None => GradientItem::SimpleColorStop(self.color), - } - } -} - -/// Specified values for a paint worklet. -/// <https://drafts.css-houdini.org/css-paint-api/> -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] -pub struct PaintWorklet { - /// The name the worklet was registered with. - pub name: Atom, - /// The arguments for the worklet. - /// TODO: store a parsed representation of the arguments. - #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] - #[compute(no_field_bound)] - #[resolve(no_field_bound)] - pub arguments: Vec<Arc<custom_properties::SpecifiedValue>>, -} - -impl ::style_traits::SpecifiedValueInfo for PaintWorklet {} - -impl ToCss for PaintWorklet { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("paint(")?; - serialize_atom_identifier(&self.name, dest)?; - for argument in &self.arguments { - dest.write_str(", ")?; - argument.to_css(dest)?; - } - dest.write_char(')') - } -} - -/// Values for `moz-image-rect`. -/// -/// `-moz-image-rect(<uri>, top, right, bottom, left);` -#[allow(missing_docs)] -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(comma, function = "-moz-image-rect")] -#[repr(C)] -pub struct GenericMozImageRect<NumberOrPercentage, MozImageRectUrl> { - pub url: MozImageRectUrl, - pub top: NumberOrPercentage, - pub right: NumberOrPercentage, - pub bottom: NumberOrPercentage, - pub left: NumberOrPercentage, -} - -pub use self::GenericMozImageRect as MozImageRect; - -impl<G, R, U, C, P, Resolution> fmt::Debug for Image<G, R, U, C, P, Resolution> -where - Image<G, R, U, C, P, Resolution>: ToCss, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(&mut CssWriter::new(f)) - } -} - -impl<G, R, U, C, P, Resolution> ToCss for Image<G, R, U, C, P, Resolution> -where - G: ToCss, - R: ToCss, - U: ToCss, - C: ToCss, - P: ToCss, - Resolution: ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - Image::None => dest.write_str("none"), - Image::Url(ref url) => url.to_css(dest), - Image::Gradient(ref gradient) => gradient.to_css(dest), - Image::Rect(ref rect) => rect.to_css(dest), - #[cfg(feature = "servo")] - Image::PaintWorklet(ref paint_worklet) => paint_worklet.to_css(dest), - #[cfg(feature = "gecko")] - Image::Element(ref selector) => { - dest.write_str("-moz-element(#")?; - serialize_atom_identifier(selector, dest)?; - dest.write_char(')') - }, - Image::ImageSet(ref is) => is.to_css(dest), - Image::CrossFade(ref cf) => cf.to_css(dest), - } - } -} - -impl<D, LP, NL, NLP, P, A: Zero, AoP, C> ToCss for Gradient<D, LP, NL, NLP, P, A, AoP, C> -where - D: LineDirection, - LP: ToCss, - NL: ToCss, - NLP: ToCss, - P: PositionComponent + ToCss, - A: ToCss, - AoP: ToCss, - C: ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let (compat_mode, repeating) = match *self { - Gradient::Linear { - compat_mode, - repeating, - .. - } => (compat_mode, repeating), - Gradient::Radial { - compat_mode, - repeating, - .. - } => (compat_mode, repeating), - Gradient::Conic { repeating, .. } => (GradientCompatMode::Modern, repeating), - }; - - match compat_mode { - GradientCompatMode::WebKit => dest.write_str("-webkit-")?, - GradientCompatMode::Moz => dest.write_str("-moz-")?, - _ => {}, - } - - if repeating { - dest.write_str("repeating-")?; - } - - match *self { - Gradient::Linear { - ref direction, - ref items, - compat_mode, - .. - } => { - dest.write_str("linear-gradient(")?; - let mut skip_comma = if !direction.points_downwards(compat_mode) { - direction.to_css(dest, compat_mode)?; - false - } else { - true - }; - for item in &**items { - if !skip_comma { - dest.write_str(", ")?; - } - skip_comma = false; - item.to_css(dest)?; - } - }, - Gradient::Radial { - ref shape, - ref position, - ref items, - compat_mode, - .. - } => { - dest.write_str("radial-gradient(")?; - let omit_shape = match *shape { - EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::Cover)) | - EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) => true, - _ => false, - }; - let omit_position = position.is_center(); - if compat_mode == GradientCompatMode::Modern { - if !omit_shape { - shape.to_css(dest)?; - if !omit_position { - dest.write_char(' ')?; - } - } - if !omit_position { - dest.write_str("at ")?; - position.to_css(dest)?; - } - } else { - if !omit_position { - position.to_css(dest)?; - if !omit_shape { - dest.write_str(", ")?; - } - } - if !omit_shape { - shape.to_css(dest)?; - } - } - let mut skip_comma = omit_shape && omit_position; - for item in &**items { - if !skip_comma { - dest.write_str(", ")?; - } - skip_comma = false; - item.to_css(dest)?; - } - }, - Gradient::Conic { - ref angle, - ref position, - ref items, - .. - } => { - dest.write_str("conic-gradient(")?; - let omit_angle = angle.is_zero(); - let omit_position = position.is_center(); - if !omit_angle { - dest.write_str("from ")?; - angle.to_css(dest)?; - if !omit_position { - dest.write_char(' ')?; - } - } - if !omit_position { - dest.write_str("at ")?; - position.to_css(dest)?; - } - let mut skip_comma = omit_angle && omit_position; - for item in &**items { - if !skip_comma { - dest.write_str(", ")?; - } - skip_comma = false; - item.to_css(dest)?; - } - }, - } - dest.write_char(')') - } -} - -/// The direction of a linear gradient. -pub trait LineDirection { - /// Whether this direction points towards, and thus can be omitted. - fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool; - - /// Serialises this direction according to the compatibility mode. - fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result - where - W: Write; -} - -impl<L> ToCss for Circle<L> -where - L: ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - Circle::Extent(ShapeExtent::FarthestCorner) | Circle::Extent(ShapeExtent::Cover) => { - dest.write_str("circle") - }, - Circle::Extent(keyword) => { - dest.write_str("circle ")?; - keyword.to_css(dest) - }, - Circle::Radius(ref length) => length.to_css(dest), - } - } -} diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs deleted file mode 100644 index 2eb7050870f..00000000000 --- a/components/style/values/generics/length.rs +++ /dev/null @@ -1,314 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values related to length. - -use crate::parser::{Parse, ParserContext}; -use crate::Zero; -use cssparser::Parser; -use style_traits::ParseError; - -/// A `<length-percentage> | auto` value. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[repr(C, u8)] -pub enum GenericLengthPercentageOrAuto<LengthPercent> { - LengthPercentage(LengthPercent), - Auto, -} - -pub use self::GenericLengthPercentageOrAuto as LengthPercentageOrAuto; - -impl<LengthPercentage> LengthPercentageOrAuto<LengthPercentage> { - /// `auto` value. - #[inline] - pub fn auto() -> Self { - LengthPercentageOrAuto::Auto - } - - /// Whether this is the `auto` value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, LengthPercentageOrAuto::Auto) - } - - /// A helper function to parse this with quirks or not and so forth. - pub fn parse_with<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - parser: impl FnOnce( - &ParserContext, - &mut Parser<'i, 't>, - ) -> Result<LengthPercentage, ParseError<'i>>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(LengthPercentageOrAuto::Auto); - } - - Ok(LengthPercentageOrAuto::LengthPercentage(parser( - context, input, - )?)) - } -} - -impl<T> LengthPercentageOrAuto<T> -where - T: Clone, -{ - /// Resolves `auto` values by calling `f`. - #[inline] - pub fn auto_is(&self, f: impl FnOnce() -> T) -> T { - match self { - LengthPercentageOrAuto::LengthPercentage(length) => length.clone(), - LengthPercentageOrAuto::Auto => f(), - } - } - - /// Returns the non-`auto` value, if any. - #[inline] - pub fn non_auto(&self) -> Option<T> { - match self { - LengthPercentageOrAuto::LengthPercentage(length) => Some(length.clone()), - LengthPercentageOrAuto::Auto => None, - } - } - - /// Maps the length of this value. - pub fn map<U>(&self, f: impl FnOnce(T) -> U) -> LengthPercentageOrAuto<U> { - match self { - LengthPercentageOrAuto::LengthPercentage(l) => { - LengthPercentageOrAuto::LengthPercentage(f(l.clone())) - }, - LengthPercentageOrAuto::Auto => LengthPercentageOrAuto::Auto, - } - } -} - -impl<LengthPercentage: Zero> Zero for LengthPercentageOrAuto<LengthPercentage> { - fn zero() -> Self { - LengthPercentageOrAuto::LengthPercentage(Zero::zero()) - } - - fn is_zero(&self) -> bool { - match *self { - LengthPercentageOrAuto::LengthPercentage(ref l) => l.is_zero(), - LengthPercentageOrAuto::Auto => false, - } - } -} - -impl<LengthPercentage: Parse> Parse for LengthPercentageOrAuto<LengthPercentage> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with(context, input, LengthPercentage::parse) - } -} - -/// A generic value for the `width`, `height`, `min-width`, or `min-height` property. -/// -/// Unlike `max-width` or `max-height` properties, a Size can be `auto`, -/// and cannot be `none`. -/// -/// Note that it only accepts non-negative values. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericSize<LengthPercent> { - LengthPercentage(LengthPercent), - Auto, - #[cfg(feature = "gecko")] - #[animation(error)] - MaxContent, - #[cfg(feature = "gecko")] - #[animation(error)] - MinContent, - #[cfg(feature = "gecko")] - #[animation(error)] - FitContent, - #[cfg(feature = "gecko")] - #[animation(error)] - MozAvailable, - #[cfg(feature = "gecko")] - #[animation(error)] - #[css(function = "fit-content")] - FitContentFunction(LengthPercent) -} - -pub use self::GenericSize as Size; - -impl<LengthPercentage> Size<LengthPercentage> { - /// `auto` value. - #[inline] - pub fn auto() -> Self { - Size::Auto - } - - /// Returns whether we're the auto value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, Size::Auto) - } -} - -/// A generic value for the `max-width` or `max-height` property. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericMaxSize<LengthPercent> { - LengthPercentage(LengthPercent), - None, - #[cfg(feature = "gecko")] - #[animation(error)] - MaxContent, - #[cfg(feature = "gecko")] - #[animation(error)] - MinContent, - #[cfg(feature = "gecko")] - #[animation(error)] - FitContent, - #[cfg(feature = "gecko")] - #[animation(error)] - MozAvailable, - #[cfg(feature = "gecko")] - #[animation(error)] - #[css(function = "fit-content")] - FitContentFunction(LengthPercent), -} - -pub use self::GenericMaxSize as MaxSize; - -impl<LengthPercentage> MaxSize<LengthPercentage> { - /// `none` value. - #[inline] - pub fn none() -> Self { - MaxSize::None - } -} - -/// A generic `<length>` | `<number>` value for the `tab-size` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericLengthOrNumber<L, N> { - /// A number. - /// - /// NOTE: Numbers need to be before lengths, in order to parse them - /// first, since `0` should be a number, not the `0px` length. - Number(N), - /// A length. - Length(L), -} - -pub use self::GenericLengthOrNumber as LengthOrNumber; - -impl<L, N: Zero> Zero for LengthOrNumber<L, N> { - fn zero() -> Self { - LengthOrNumber::Number(Zero::zero()) - } - - fn is_zero(&self) -> bool { - match *self { - LengthOrNumber::Number(ref n) => n.is_zero(), - LengthOrNumber::Length(..) => false, - } - } -} - -/// A generic `<length-percentage>` | normal` value. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -#[allow(missing_docs)] -pub enum GenericLengthPercentageOrNormal<LengthPercent> { - LengthPercentage(LengthPercent), - Normal, -} - -pub use self::GenericLengthPercentageOrNormal as LengthPercentageOrNormal; - -impl<LengthPercent> LengthPercentageOrNormal<LengthPercent> { - /// Returns the normal value. - #[inline] - pub fn normal() -> Self { - LengthPercentageOrNormal::Normal - } -} diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs deleted file mode 100644 index 237d3c16a32..00000000000 --- a/components/style/values/generics/mod.rs +++ /dev/null @@ -1,386 +0,0 @@ -/* 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/. */ - -//! Generic types that share their serialization implementations -//! for both specified and computed values. - -use super::CustomIdent; -use crate::counter_style::{parse_counter_style_name, Symbols}; -use crate::parser::{Parse, ParserContext}; -use crate::Zero; -use cssparser::Parser; -use std::ops::Add; -use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind}; - -pub mod animation; -pub mod background; -pub mod basic_shape; -pub mod border; -#[path = "box.rs"] -pub mod box_; -pub mod calc; -pub mod color; -pub mod column; -pub mod counters; -pub mod easing; -pub mod effects; -pub mod flex; -pub mod font; -pub mod grid; -pub mod image; -pub mod length; -pub mod motion; -pub mod page; -pub mod position; -pub mod ratio; -pub mod rect; -pub mod size; -pub mod svg; -pub mod text; -pub mod transform; -pub mod ui; -pub mod url; - -/// https://drafts.csswg.org/css-counter-styles/#typedef-symbols-type -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum SymbolsType { - Cyclic, - Numeric, - Alphabetic, - Symbolic, - Fixed, -} - -/// <https://drafts.csswg.org/css-counter-styles/#typedef-counter-style> -/// -/// Note that 'none' is not a valid name. -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] -#[repr(u8)] -pub enum CounterStyle { - /// `<counter-style-name>` - Name(CustomIdent), - /// `symbols()` - #[css(function)] - Symbols(#[css(skip_if = "is_symbolic")] SymbolsType, Symbols), -} - -#[inline] -fn is_symbolic(symbols_type: &SymbolsType) -> bool { - *symbols_type == SymbolsType::Symbolic -} - -impl CounterStyle { - /// disc value - pub fn disc() -> Self { - CounterStyle::Name(CustomIdent(atom!("disc"))) - } - - /// decimal value - pub fn decimal() -> Self { - CounterStyle::Name(CustomIdent(atom!("decimal"))) - } - - /// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`) - #[inline] - pub fn is_bullet(&self) -> bool { - match self { - CounterStyle::Name(CustomIdent(ref name)) => { - name == &atom!("disc") || - name == &atom!("circle") || - name == &atom!("square") || - name == &atom!("disclosure-closed") || - name == &atom!("disclosure-open") - }, - _ => false, - } - } -} - -impl Parse for CounterStyle { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(name) = input.try_parse(|i| parse_counter_style_name(i)) { - return Ok(CounterStyle::Name(name)); - } - input.expect_function_matching("symbols")?; - input.parse_nested_block(|input| { - let symbols_type = input - .try_parse(SymbolsType::parse) - .unwrap_or(SymbolsType::Symbolic); - let symbols = Symbols::parse(context, input)?; - // There must be at least two symbols for alphabetic or - // numeric system. - if (symbols_type == SymbolsType::Alphabetic || symbols_type == SymbolsType::Numeric) && - symbols.0.len() < 2 - { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - // Identifier is not allowed in symbols() function. - if symbols.0.iter().any(|sym| !sym.is_allowed_in_symbols()) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(CounterStyle::Symbols(symbols_type, symbols)) - }) - } -} - -impl SpecifiedValueInfo for CounterStyle { - fn collect_completion_keywords(f: KeywordsCollectFn) { - // XXX The best approach for implementing this is probably - // having a CounterStyleName type wrapping CustomIdent, and - // put the predefined list for that type in counter_style mod. - // But that's a non-trivial change itself, so we use a simpler - // approach here. - macro_rules! predefined { - ($($name:expr,)+) => { - f(&["symbols", $($name,)+]) - } - } - include!("../../counter_style/predefined.rs"); - } -} - -/// A wrapper of Non-negative values. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Hash, - MallocSizeOf, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct NonNegative<T>(pub T); - -impl<T: Add<Output = T>> Add<NonNegative<T>> for NonNegative<T> { - type Output = Self; - - fn add(self, other: Self) -> Self { - NonNegative(self.0 + other.0) - } -} - -impl<T: Zero> Zero for NonNegative<T> { - fn is_zero(&self) -> bool { - self.0.is_zero() - } - - fn zero() -> Self { - NonNegative(T::zero()) - } -} - -/// A wrapper of greater-than-or-equal-to-one values. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub struct GreaterThanOrEqualToOne<T>(pub T); - -/// A wrapper of values between zero and one. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Hash, - MallocSizeOf, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct ZeroToOne<T>(pub T); - -/// A clip rect for clip and image-region -#[allow(missing_docs)] -#[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(function = "rect", comma)] -#[repr(C)] -pub struct GenericClipRect<LengthOrAuto> { - pub top: LengthOrAuto, - pub right: LengthOrAuto, - pub bottom: LengthOrAuto, - pub left: LengthOrAuto, -} - -pub use self::GenericClipRect as ClipRect; - -/// Either a clip-rect or `auto`. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericClipRectOrAuto<R> { - Auto, - Rect(R), -} - -pub use self::GenericClipRectOrAuto as ClipRectOrAuto; - -impl<L> ClipRectOrAuto<L> { - /// Returns the `auto` value. - #[inline] - pub fn auto() -> Self { - ClipRectOrAuto::Auto - } - - /// Returns whether this value is the `auto` value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, ClipRectOrAuto::Auto) - } -} - -pub use page::PageSize; - -/// An optional value, much like `Option<T>`, but with a defined struct layout -/// to be able to use it from C++ as well. -/// -/// Note that this is relatively inefficient, struct-layout-wise, as you have -/// one byte for the tag, but padding to the alignment of T. If you have -/// multiple optional values and care about struct compactness, you might be -/// better off "coalescing" the combinations into a parent enum. But that -/// shouldn't matter for most use cases. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, - Serialize, - Deserialize, -)] -#[repr(C, u8)] -pub enum Optional<T> { - #[css(skip)] - None, - Some(T), -} - -impl<T> Optional<T> { - /// Returns whether this value is present. - pub fn is_some(&self) -> bool { - matches!(*self, Self::Some(..)) - } - - /// Returns whether this value is not present. - pub fn is_none(&self) -> bool { - matches!(*self, Self::None) - } - - /// Turns this Optional<> into a regular rust Option<>. - pub fn into_rust(self) -> Option<T> { - match self { - Self::Some(v) => Some(v), - Self::None => None, - } - } - - /// Return a reference to the containing value, if any, as a plain rust - /// Option<>. - pub fn as_ref(&self) -> Option<&T> { - match *self { - Self::Some(ref v) => Some(v), - Self::None => None, - } - } -} - -impl<T> From<Option<T>> for Optional<T> { - fn from(rust: Option<T>) -> Self { - match rust { - Some(t) => Self::Some(t), - None => Self::None, - } - } -} diff --git a/components/style/values/generics/motion.rs b/components/style/values/generics/motion.rs deleted file mode 100644 index 92c1d004bb8..00000000000 --- a/components/style/values/generics/motion.rs +++ /dev/null @@ -1,205 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS Motion Path. - -use crate::values::animated::ToAnimatedZero; -use crate::values::generics::position::{GenericPosition, GenericPositionOrAuto}; -use crate::values::specified::SVGPathData; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// The <size> in ray() function. -/// -/// https://drafts.fxtf.org/motion-1/#valdef-offsetpath-size -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - Parse, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum RaySize { - ClosestSide, - ClosestCorner, - FarthestSide, - FarthestCorner, - Sides, -} - -/// The `ray()` function, `ray( [ <angle> && <size> && contain? && [at <position>]? ] )` -/// -/// https://drafts.fxtf.org/motion-1/#valdef-offsetpath-ray -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericRayFunction<Angle, Position> { - /// The bearing angle with `0deg` pointing up and positive angles - /// representing clockwise rotation. - pub angle: Angle, - /// Decide the path length used when `offset-distance` is expressed - /// as a percentage. - pub size: RaySize, - /// Clamp `offset-distance` so that the box is entirely contained - /// within the path. - #[animation(constant)] - pub contain: bool, - /// The "at <position>" part. If omitted, we use auto to represent it. - pub position: GenericPositionOrAuto<Position>, -} - -pub use self::GenericRayFunction as RayFunction; - -impl<Angle, Position> ToCss for RayFunction<Angle, Position> -where - Angle: ToCss, - Position: ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.angle.to_css(dest)?; - - if !matches!(self.size, RaySize::ClosestSide) { - dest.write_char(' ')?; - self.size.to_css(dest)?; - } - - if self.contain { - dest.write_str(" contain")?; - } - - if !matches!(self.position, GenericPositionOrAuto::Auto) { - dest.write_str(" at ")?; - self.position.to_css(dest)?; - } - - Ok(()) - } -} - -/// The offset-path value. -/// -/// https://drafts.fxtf.org/motion-1/#offset-path-property -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericOffsetPath<RayFunction> { - // We could merge SVGPathData into ShapeSource, so we could reuse them. However, - // we don't want to support other value for offset-path, so use SVGPathData only for now. - /// Path value for path(<string>). - #[css(function)] - Path(SVGPathData), - /// ray() function, which defines a path in the polar coordinate system. - /// Use Box<> to make sure the size of offset-path is not too large. - #[css(function)] - Ray(Box<RayFunction>), - /// None value. - #[animation(error)] - None, - // Bug 1186329: Implement <basic-shape>, <geometry-box>, and <url>. -} - -pub use self::GenericOffsetPath as OffsetPath; - -impl<Ray> OffsetPath<Ray> { - /// Return None. - #[inline] - pub fn none() -> Self { - OffsetPath::None - } -} - -impl<Ray> ToAnimatedZero for OffsetPath<Ray> { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} - -/// The offset-position property, which specifies the offset starting position that is used by the -/// <offset-path> functions if they don’t specify their own starting position. -/// -/// https://drafts.fxtf.org/motion-1/#offset-position-property -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - Parse, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericOffsetPosition<H, V> { - /// The element does not have an offset starting position. - Normal, - /// The offset starting position is the top-left corner of the box. - Auto, - /// The offset starting position is the result of using the <position> to position a 0x0 object - /// area within the box’s containing block. - Position( - #[css(field_bound)] - #[parse(field_bound)] - GenericPosition<H, V>, - ), -} - -pub use self::GenericOffsetPosition as OffsetPosition; - -impl<H, V> OffsetPosition<H, V> { - /// Returns the initial value, auto. - #[inline] - pub fn auto() -> Self { - Self::Auto - } -} diff --git a/components/style/values/generics/page.rs b/components/style/values/generics/page.rs deleted file mode 100644 index 91f02bc4b37..00000000000 --- a/components/style/values/generics/page.rs +++ /dev/null @@ -1,162 +0,0 @@ -/* 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/. */ - -//! @page at-rule properties - -use crate::values::generics::NonNegative; -use crate::values::specified::length::AbsoluteLength; - -/// Page size names. -/// -/// https://drafts.csswg.org/css-page-3/#typedef-page-size-page-size -#[derive( - Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum PaperSize { - /// ISO A5 media - A5, - /// ISO A4 media - A4, - /// ISO A3 media - A3, - /// ISO B5 media - B5, - /// ISO B4 media - B4, - /// JIS B5 media - JisB5, - /// JIS B4 media - JisB4, - /// North American Letter size - Letter, - /// North American Legal size - Legal, - /// North American Ledger size - Ledger, -} - -impl PaperSize { - /// Gets the long edge length of the paper size - pub fn long_edge(&self) -> NonNegative<AbsoluteLength> { - NonNegative(match *self { - PaperSize::A5 => AbsoluteLength::Mm(210.0), - PaperSize::A4 => AbsoluteLength::Mm(297.0), - PaperSize::A3 => AbsoluteLength::Mm(420.0), - PaperSize::B5 => AbsoluteLength::Mm(250.0), - PaperSize::B4 => AbsoluteLength::Mm(353.0), - PaperSize::JisB5 => AbsoluteLength::Mm(257.0), - PaperSize::JisB4 => AbsoluteLength::Mm(364.0), - PaperSize::Letter => AbsoluteLength::In(11.0), - PaperSize::Legal => AbsoluteLength::In(14.0), - PaperSize::Ledger => AbsoluteLength::In(17.0), - }) - } - /// Gets the short edge length of the paper size - pub fn short_edge(&self) -> NonNegative<AbsoluteLength> { - NonNegative(match *self { - PaperSize::A5 => AbsoluteLength::Mm(148.0), - PaperSize::A4 => AbsoluteLength::Mm(210.0), - PaperSize::A3 => AbsoluteLength::Mm(297.0), - PaperSize::B5 => AbsoluteLength::Mm(176.0), - PaperSize::B4 => AbsoluteLength::Mm(250.0), - PaperSize::JisB5 => AbsoluteLength::Mm(182.0), - PaperSize::JisB4 => AbsoluteLength::Mm(257.0), - PaperSize::Letter => AbsoluteLength::In(8.5), - PaperSize::Legal => AbsoluteLength::In(8.5), - PaperSize::Ledger => AbsoluteLength::In(11.0), - }) - } -} - -/// Page orientation names. -/// -/// https://drafts.csswg.org/css-page-3/#page-orientation-prop -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum PageOrientation { - /// upright - Upright, - /// rotate-left (counter-clockwise) - RotateLeft, - /// rotate-right (clockwise) - RotateRight, -} - -/// Paper orientation -/// -/// https://drafts.csswg.org/css-page-3/#page-size-prop -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum PageSizeOrientation { - /// Portrait orientation - Portrait, - /// Landscape orientation - Landscape, -} - -#[inline] -fn is_portrait(orientation: &PageSizeOrientation) -> bool { - *orientation == PageSizeOrientation::Portrait -} - -/// Page size property -/// -/// https://drafts.csswg.org/css-page-3/#page-size-prop -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -#[repr(C, u8)] -pub enum GenericPageSize<S> { - /// `auto` value. - Auto, - /// Page dimensions. - Size(S), - /// An orientation with no size. - Orientation(PageSizeOrientation), - /// Paper size by name - PaperSize( - PaperSize, - #[css(skip_if = "is_portrait")] PageSizeOrientation, - ), -} - -pub use self::GenericPageSize as PageSize; - -impl<S> PageSize<S> { - /// `auto` value. - #[inline] - pub fn auto() -> Self { - PageSize::Auto - } - - /// Whether this is the `auto` value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, PageSize::Auto) - } -} diff --git a/components/style/values/generics/position.rs b/components/style/values/generics/position.rs deleted file mode 100644 index a6282f463bb..00000000000 --- a/components/style/values/generics/position.rs +++ /dev/null @@ -1,237 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS handling of specified and computed values of -//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position) - -use crate::values::animated::ToAnimatedZero; -use crate::values::generics::ratio::Ratio; - -/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position). -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericPosition<H, V> { - /// The horizontal component of position. - pub horizontal: H, - /// The vertical component of position. - pub vertical: V, -} - -impl<H, V> PositionComponent for Position<H, V> -where - H: PositionComponent, - V: PositionComponent, -{ - #[inline] - fn is_center(&self) -> bool { - self.horizontal.is_center() && self.vertical.is_center() - } -} - -pub use self::GenericPosition as Position; - -impl<H, V> Position<H, V> { - /// Returns a new position. - pub fn new(horizontal: H, vertical: V) -> Self { - Self { - horizontal, - vertical, - } - } -} - -/// Implements a method that checks if the position is centered. -pub trait PositionComponent { - /// Returns if the position component is 50% or center. - /// For pixel lengths, it always returns false. - fn is_center(&self) -> bool; -} - -/// A generic type for representing an `Auto | <position>`. -/// This is used by <offset-anchor> for now. -/// https://drafts.fxtf.org/motion-1/#offset-anchor-property -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - Parse, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericPositionOrAuto<Pos> { - /// The <position> value. - Position(Pos), - /// The keyword `auto`. - Auto, -} - -pub use self::GenericPositionOrAuto as PositionOrAuto; - -impl<Pos> PositionOrAuto<Pos> { - /// Return `auto`. - #[inline] - pub fn auto() -> Self { - PositionOrAuto::Auto - } - - /// Return true if it is 'auto'. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(self, PositionOrAuto::Auto) - } -} - -/// A generic value for the `z-index` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericZIndex<I> { - /// An integer value. - Integer(I), - /// The keyword `auto`. - Auto, -} - -pub use self::GenericZIndex as ZIndex; - -impl<Integer> ZIndex<Integer> { - /// Returns `auto` - #[inline] - pub fn auto() -> Self { - ZIndex::Auto - } - - /// Returns whether `self` is `auto`. - #[inline] - pub fn is_auto(self) -> bool { - matches!(self, ZIndex::Auto) - } - - /// Returns the integer value if it is an integer, or `auto`. - #[inline] - pub fn integer_or(self, auto: Integer) -> Integer { - match self { - ZIndex::Integer(n) => n, - ZIndex::Auto => auto, - } - } -} - -/// Ratio or None. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum PreferredRatio<N> { - /// Without specified ratio - #[css(skip)] - None, - /// With specified ratio - Ratio( - #[animation(field_bound)] - #[css(field_bound)] - #[distance(field_bound)] - Ratio<N>, - ), -} - -/// A generic value for the `aspect-ratio` property, the value is `auto || <ratio>`. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericAspectRatio<N> { - /// Specifiy auto or not. - #[animation(constant)] - #[css(represents_keyword)] - pub auto: bool, - /// The preferred aspect-ratio value. - #[animation(field_bound)] - #[css(field_bound)] - #[distance(field_bound)] - pub ratio: PreferredRatio<N>, -} - -pub use self::GenericAspectRatio as AspectRatio; - -impl<N> AspectRatio<N> { - /// Returns `auto` - #[inline] - pub fn auto() -> Self { - AspectRatio { - auto: true, - ratio: PreferredRatio::None, - } - } -} - -impl<N> ToAnimatedZero for AspectRatio<N> { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} diff --git a/components/style/values/generics/ratio.rs b/components/style/values/generics/ratio.rs deleted file mode 100644 index 8c66fed6026..00000000000 --- a/components/style/values/generics/ratio.rs +++ /dev/null @@ -1,50 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values related to <ratio>. -//! https://drafts.csswg.org/css-values/#ratios - -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A generic value for the `<ratio>` value. -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct Ratio<N>(pub N, pub N); - -impl<N> ToCss for Ratio<N> -where - N: ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0.to_css(dest)?; - // Even though 1 could be omitted, we don't per - // https://drafts.csswg.org/css-values-4/#ratio-value: - // - // The second <number> is optional, defaulting to 1. However, - // <ratio> is always serialized with both components. - // - // And for compat reasons, see bug 1669742. - // - // We serialize with spaces for consistency with all other - // slash-delimited things, see - // https://github.com/w3c/csswg-drafts/issues/4282 - dest.write_str(" / ")?; - self.1.to_css(dest)?; - Ok(()) - } -} diff --git a/components/style/values/generics/rect.rs b/components/style/values/generics/rect.rs deleted file mode 100644 index e6358373d66..00000000000 --- a/components/style/values/generics/rect.rs +++ /dev/null @@ -1,126 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values that are composed of four sides. - -use crate::parser::{Parse, ParserContext}; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// A CSS value made of four components, where its `ToCss` impl will try to -/// serialize as few components as possible, like for example in `border-width`. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct Rect<T>(pub T, pub T, pub T, pub T); - -impl<T> Rect<T> { - /// Returns a new `Rect<T>` value. - pub fn new(first: T, second: T, third: T, fourth: T) -> Self { - Rect(first, second, third, fourth) - } -} - -impl<T> Rect<T> -where - T: Clone, -{ - /// Returns a rect with all the values equal to `v`. - pub fn all(v: T) -> Self { - Rect::new(v.clone(), v.clone(), v.clone(), v) - } - - /// Parses a new `Rect<T>` value with the given parse function. - pub fn parse_with<'i, 't, Parse>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - parse: Parse, - ) -> Result<Self, ParseError<'i>> - where - Parse: Fn(&ParserContext, &mut Parser<'i, 't>) -> Result<T, ParseError<'i>>, - { - let first = parse(context, input)?; - let second = if let Ok(second) = input.try_parse(|i| parse(context, i)) { - second - } else { - // <first> - return Ok(Self::new( - first.clone(), - first.clone(), - first.clone(), - first, - )); - }; - let third = if let Ok(third) = input.try_parse(|i| parse(context, i)) { - third - } else { - // <first> <second> - return Ok(Self::new(first.clone(), second.clone(), first, second)); - }; - let fourth = if let Ok(fourth) = input.try_parse(|i| parse(context, i)) { - fourth - } else { - // <first> <second> <third> - return Ok(Self::new(first, second.clone(), third, second)); - }; - // <first> <second> <third> <fourth> - Ok(Self::new(first, second, third, fourth)) - } -} - -impl<T> Parse for Rect<T> -where - T: Clone + Parse, -{ - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with(context, input, T::parse) - } -} - -impl<T> ToCss for Rect<T> -where - T: PartialEq + ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0.to_css(dest)?; - let same_vertical = self.0 == self.2; - let same_horizontal = self.1 == self.3; - if same_vertical && same_horizontal && self.0 == self.1 { - return Ok(()); - } - dest.write_char(' ')?; - self.1.to_css(dest)?; - if same_vertical && same_horizontal { - return Ok(()); - } - dest.write_char(' ')?; - self.2.to_css(dest)?; - if same_horizontal { - return Ok(()); - } - dest.write_char(' ')?; - self.3.to_css(dest) - } -} diff --git a/components/style/values/generics/size.rs b/components/style/values/generics/size.rs deleted file mode 100644 index 0027245e488..00000000000 --- a/components/style/values/generics/size.rs +++ /dev/null @@ -1,99 +0,0 @@ -/* 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/. */ - -//! Generic type for CSS properties that are composed by two dimensions. - -use crate::parser::ParserContext; -use crate::Zero; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// A generic size, for `border-*-radius` longhand properties, or -/// `border-spacing`. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedZero, - ToAnimatedValue, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(C)] -pub struct Size2D<L> { - pub width: L, - pub height: L, -} - -impl<L> Size2D<L> { - #[inline] - /// Create a new `Size2D` for an area of given width and height. - pub fn new(width: L, height: L) -> Self { - Self { width, height } - } - - /// Returns the width component. - pub fn width(&self) -> &L { - &self.width - } - - /// Returns the height component. - pub fn height(&self) -> &L { - &self.height - } - - /// Parse a `Size2D` with a given parsing function. - pub fn parse_with<'i, 't, F>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - parse_one: F, - ) -> Result<Self, ParseError<'i>> - where - L: Clone, - F: Fn(&ParserContext, &mut Parser<'i, 't>) -> Result<L, ParseError<'i>>, - { - let first = parse_one(context, input)?; - let second = input - .try_parse(|i| parse_one(context, i)) - .unwrap_or_else(|_| first.clone()); - Ok(Self::new(first, second)) - } -} - -impl<L> ToCss for Size2D<L> -where - L: ToCss + PartialEq, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.width.to_css(dest)?; - - if self.height != self.width { - dest.write_char(' ')?; - self.height.to_css(dest)?; - } - - Ok(()) - } -} - -impl<L: Zero> Zero for Size2D<L> { - fn zero() -> Self { - Self::new(L::zero(), L::zero()) - } - - fn is_zero(&self) -> bool { - self.width.is_zero() && self.height.is_zero() - } -} diff --git a/components/style/values/generics/svg.rs b/components/style/values/generics/svg.rs deleted file mode 100644 index 43ba77f1ff4..00000000000 --- a/components/style/values/generics/svg.rs +++ /dev/null @@ -1,221 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values in SVG - -use crate::parser::{Parse, ParserContext}; -use cssparser::Parser; -use style_traits::ParseError; - -/// The fallback of an SVG paint server value. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericSVGPaintFallback<C> { - /// The `none` keyword. - None, - /// A magic value that represents no fallback specified and serializes to - /// the empty string. - #[css(skip)] - Unset, - /// A color. - Color(C), -} - -pub use self::GenericSVGPaintFallback as SVGPaintFallback; - -/// An SVG paint value -/// -/// <https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint> -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[animation(no_bound(Url))] -#[repr(C)] -pub struct GenericSVGPaint<Color, Url> { - /// The paint source. - pub kind: GenericSVGPaintKind<Color, Url>, - /// The fallback color. - pub fallback: GenericSVGPaintFallback<Color>, -} - -pub use self::GenericSVGPaint as SVGPaint; - -impl<C, U> Default for SVGPaint<C, U> { - fn default() -> Self { - Self { - kind: SVGPaintKind::None, - fallback: SVGPaintFallback::Unset, - } - } -} - -/// An SVG paint value without the fallback. -/// -/// Whereas the spec only allows PaintServer to have a fallback, Gecko lets the -/// context properties have a fallback as well. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[animation(no_bound(U))] -#[repr(C, u8)] -pub enum GenericSVGPaintKind<C, U> { - /// `none` - #[animation(error)] - None, - /// `<color>` - Color(C), - /// `url(...)` - #[animation(error)] - PaintServer(U), - /// `context-fill` - ContextFill, - /// `context-stroke` - ContextStroke, -} - -pub use self::GenericSVGPaintKind as SVGPaintKind; - -impl<C: Parse, U: Parse> Parse for SVGPaint<C, U> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let kind = SVGPaintKind::parse(context, input)?; - if matches!(kind, SVGPaintKind::None | SVGPaintKind::Color(..)) { - return Ok(SVGPaint { - kind, - fallback: SVGPaintFallback::Unset, - }); - } - let fallback = input - .try_parse(|i| SVGPaintFallback::parse(context, i)) - .unwrap_or(SVGPaintFallback::Unset); - Ok(SVGPaint { kind, fallback }) - } -} - -/// An SVG length value supports `context-value` in addition to length. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericSVGLength<L> { - /// `<length> | <percentage> | <number>` - LengthPercentage(L), - /// `context-value` - #[animation(error)] - ContextValue, -} - -pub use self::GenericSVGLength as SVGLength; - -/// Generic value for stroke-dasharray. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericSVGStrokeDashArray<L> { - /// `[ <length> | <percentage> | <number> ]#` - #[css(comma)] - Values(#[css(if_empty = "none", iterable)] crate::OwnedSlice<L>), - /// `context-value` - ContextValue, -} - -pub use self::GenericSVGStrokeDashArray as SVGStrokeDashArray; - -/// An SVG opacity value accepts `context-{fill,stroke}-opacity` in -/// addition to opacity value. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericSVGOpacity<OpacityType> { - /// `<opacity-value>` - Opacity(OpacityType), - /// `context-fill-opacity` - #[animation(error)] - ContextFillOpacity, - /// `context-stroke-opacity` - #[animation(error)] - ContextStrokeOpacity, -} - -pub use self::GenericSVGOpacity as SVGOpacity; diff --git a/components/style/values/generics/text.rs b/components/style/values/generics/text.rs deleted file mode 100644 index 9b59ff0dc3f..00000000000 --- a/components/style/values/generics/text.rs +++ /dev/null @@ -1,156 +0,0 @@ -/* 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/. */ - -//! Generic types for text properties. - -use crate::parser::ParserContext; -use crate::values::animated::ToAnimatedZero; -use cssparser::Parser; -use style_traits::ParseError; - -/// A generic value for the `initial-letter` property. -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum InitialLetter<Number, Integer> { - /// `normal` - Normal, - /// `<number> <integer>?` - Specified(Number, Option<Integer>), -} - -impl<N, I> InitialLetter<N, I> { - /// Returns `normal`. - #[inline] - pub fn normal() -> Self { - InitialLetter::Normal - } -} - -/// A generic spacing value for the `letter-spacing` and `word-spacing` properties. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum Spacing<Value> { - /// `normal` - Normal, - /// `<value>` - Value(Value), -} - -impl<Value> Spacing<Value> { - /// Returns `normal`. - #[inline] - pub fn normal() -> Self { - Spacing::Normal - } - - /// Parses. - #[inline] - pub fn parse_with<'i, 't, F>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - parse: F, - ) -> Result<Self, ParseError<'i>> - where - F: FnOnce(&ParserContext, &mut Parser<'i, 't>) -> Result<Value, ParseError<'i>>, - { - if input - .try_parse(|i| i.expect_ident_matching("normal")) - .is_ok() - { - return Ok(Spacing::Normal); - } - parse(context, input).map(Spacing::Value) - } -} - -#[cfg(feature = "gecko")] -fn line_height_moz_block_height_enabled(context: &ParserContext) -> bool { - context.in_ua_sheet() || - static_prefs::pref!("layout.css.line-height-moz-block-height.content.enabled") -} - -/// A generic value for the `line-height` property. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToCss, - ToShmem, - Parse, -)] -#[repr(C, u8)] -pub enum GenericLineHeight<N, L> { - /// `normal` - Normal, - /// `-moz-block-height` - #[cfg(feature = "gecko")] - #[parse(condition = "line_height_moz_block_height_enabled")] - MozBlockHeight, - /// `<number>` - Number(N), - /// `<length-percentage>` - Length(L), -} - -pub use self::GenericLineHeight as LineHeight; - -impl<N, L> ToAnimatedZero for LineHeight<N, L> { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - Err(()) - } -} - -impl<N, L> LineHeight<N, L> { - /// Returns `normal`. - #[inline] - pub fn normal() -> Self { - LineHeight::Normal - } -} - -/// Implements type for text-decoration-thickness -/// which takes the grammar of auto | from-font | <length> | <percentage> -/// -/// https://drafts.csswg.org/css-text-decor-4/ -#[repr(C, u8)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Animate, - Clone, - Copy, - ComputeSquaredDistance, - ToAnimatedZero, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum GenericTextDecorationLength<L> { - LengthPercentage(L), - Auto, - FromFont, -} diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs deleted file mode 100644 index f1fd062b1ee..00000000000 --- a/components/style/values/generics/transform.rs +++ /dev/null @@ -1,886 +0,0 @@ -/* 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/. */ - -//! Generic types for CSS values that are related to transformations. - -use crate::values::computed::length::Length as ComputedLength; -use crate::values::computed::length::LengthPercentage as ComputedLengthPercentage; -use crate::values::specified::angle::Angle as SpecifiedAngle; -use crate::values::specified::length::Length as SpecifiedLength; -use crate::values::specified::length::LengthPercentage as SpecifiedLengthPercentage; -use crate::values::{computed, CSSFloat}; -use crate::{Zero, ZeroNoPercent}; -use euclid; -use euclid::default::{Rect, Transform3D}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A generic 2D transformation matrix. -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(comma, function = "matrix")] -#[repr(C)] -pub struct GenericMatrix<T> { - pub a: T, - pub b: T, - pub c: T, - pub d: T, - pub e: T, - pub f: T, -} - -pub use self::GenericMatrix as Matrix; - -#[allow(missing_docs)] -#[cfg_attr(rustfmt, rustfmt_skip)] -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(comma, function = "matrix3d")] -#[repr(C)] -pub struct GenericMatrix3D<T> { - pub m11: T, pub m12: T, pub m13: T, pub m14: T, - pub m21: T, pub m22: T, pub m23: T, pub m24: T, - pub m31: T, pub m32: T, pub m33: T, pub m34: T, - pub m41: T, pub m42: T, pub m43: T, pub m44: T, -} - -pub use self::GenericMatrix3D as Matrix3D; - -#[cfg_attr(rustfmt, rustfmt_skip)] -impl<T: Into<f64>> From<Matrix<T>> for Transform3D<f64> { - #[inline] - fn from(m: Matrix<T>) -> Self { - Transform3D::new( - m.a.into(), m.b.into(), 0.0, 0.0, - m.c.into(), m.d.into(), 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - m.e.into(), m.f.into(), 0.0, 1.0, - ) - } -} - -#[cfg_attr(rustfmt, rustfmt_skip)] -impl<T: Into<f64>> From<Matrix3D<T>> for Transform3D<f64> { - #[inline] - fn from(m: Matrix3D<T>) -> Self { - Transform3D::new( - m.m11.into(), m.m12.into(), m.m13.into(), m.m14.into(), - m.m21.into(), m.m22.into(), m.m23.into(), m.m24.into(), - m.m31.into(), m.m32.into(), m.m33.into(), m.m34.into(), - m.m41.into(), m.m42.into(), m.m43.into(), m.m44.into(), - ) - } -} - -/// A generic transform origin. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericTransformOrigin<H, V, Depth> { - /// The horizontal origin. - pub horizontal: H, - /// The vertical origin. - pub vertical: V, - /// The depth. - pub depth: Depth, -} - -pub use self::GenericTransformOrigin as TransformOrigin; - -impl<H, V, D> TransformOrigin<H, V, D> { - /// Returns a new transform origin. - pub fn new(horizontal: H, vertical: V, depth: D) -> Self { - Self { - horizontal, - vertical, - depth, - } - } -} - -fn is_same<N: PartialEq>(x: &N, y: &N) -> bool { - x == y -} - -/// A value for the `perspective()` transform function, which is either a -/// non-negative `<length>` or `none`. -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericPerspectiveFunction<L> { - /// `none` - None, - /// A `<length>`. - Length(L), -} - -impl<L> GenericPerspectiveFunction<L> { - /// Returns `f32::INFINITY` or the result of a function on the length value. - pub fn infinity_or(&self, f: impl FnOnce(&L) -> f32) -> f32 { - match *self { - Self::None => f32::INFINITY, - Self::Length(ref l) => f(l), - } - } -} - -pub use self::GenericPerspectiveFunction as PerspectiveFunction; - -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -/// A single operation in the list of a `transform` value -pub enum GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage> -where - Angle: Zero, - LengthPercentage: Zero + ZeroNoPercent, - Number: PartialEq, -{ - /// Represents a 2D 2x3 matrix. - Matrix(GenericMatrix<Number>), - /// Represents a 3D 4x4 matrix. - Matrix3D(GenericMatrix3D<Number>), - /// A 2D skew. - /// - /// If the second angle is not provided it is assumed zero. - /// - /// Syntax can be skew(angle) or skew(angle, angle) - #[css(comma, function)] - Skew(Angle, #[css(skip_if = "Zero::is_zero")] Angle), - /// skewX(angle) - #[css(function = "skewX")] - SkewX(Angle), - /// skewY(angle) - #[css(function = "skewY")] - SkewY(Angle), - /// translate(x, y) or translate(x) - #[css(comma, function)] - Translate( - LengthPercentage, - #[css(skip_if = "ZeroNoPercent::is_zero_no_percent")] LengthPercentage, - ), - /// translateX(x) - #[css(function = "translateX")] - TranslateX(LengthPercentage), - /// translateY(y) - #[css(function = "translateY")] - TranslateY(LengthPercentage), - /// translateZ(z) - #[css(function = "translateZ")] - TranslateZ(Length), - /// translate3d(x, y, z) - #[css(comma, function = "translate3d")] - Translate3D(LengthPercentage, LengthPercentage, Length), - /// A 2D scaling factor. - /// - /// Syntax can be scale(factor) or scale(factor, factor) - #[css(comma, function)] - Scale(Number, #[css(contextual_skip_if = "is_same")] Number), - /// scaleX(factor) - #[css(function = "scaleX")] - ScaleX(Number), - /// scaleY(factor) - #[css(function = "scaleY")] - ScaleY(Number), - /// scaleZ(factor) - #[css(function = "scaleZ")] - ScaleZ(Number), - /// scale3D(factorX, factorY, factorZ) - #[css(comma, function = "scale3d")] - Scale3D(Number, Number, Number), - /// Describes a 2D Rotation. - /// - /// In a 3D scene `rotate(angle)` is equivalent to `rotateZ(angle)`. - #[css(function)] - Rotate(Angle), - /// Rotation in 3D space around the x-axis. - #[css(function = "rotateX")] - RotateX(Angle), - /// Rotation in 3D space around the y-axis. - #[css(function = "rotateY")] - RotateY(Angle), - /// Rotation in 3D space around the z-axis. - #[css(function = "rotateZ")] - RotateZ(Angle), - /// Rotation in 3D space. - /// - /// Generalization of rotateX, rotateY and rotateZ. - #[css(comma, function = "rotate3d")] - Rotate3D(Number, Number, Number, Angle), - /// Specifies a perspective projection matrix. - /// - /// Part of CSS Transform Module Level 2 and defined at - /// [§ 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective). - /// - /// The value must be greater than or equal to zero. - #[css(function)] - Perspective(GenericPerspectiveFunction<Length>), - /// A intermediate type for interpolation of mismatched transform lists. - #[allow(missing_docs)] - #[css(comma, function = "interpolatematrix")] - InterpolateMatrix { - from_list: GenericTransform< - GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, - >, - to_list: GenericTransform< - GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, - >, - progress: computed::Percentage, - }, - /// A intermediate type for accumulation of mismatched transform lists. - #[allow(missing_docs)] - #[css(comma, function = "accumulatematrix")] - AccumulateMatrix { - from_list: GenericTransform< - GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, - >, - to_list: GenericTransform< - GenericTransformOperation<Angle, Number, Length, Integer, LengthPercentage>, - >, - count: Integer, - }, -} - -pub use self::GenericTransformOperation as TransformOperation; - -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// A value of the `transform` property -pub struct GenericTransform<T>(#[css(if_empty = "none", iterable)] pub crate::OwnedSlice<T>); - -pub use self::GenericTransform as Transform; - -impl<Angle, Number, Length, Integer, LengthPercentage> - TransformOperation<Angle, Number, Length, Integer, LengthPercentage> -where - Angle: Zero, - LengthPercentage: Zero + ZeroNoPercent, - Number: PartialEq, -{ - /// Check if it is any rotate function. - pub fn is_rotate(&self) -> bool { - use self::TransformOperation::*; - matches!( - *self, - Rotate(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | RotateZ(..) - ) - } - - /// Check if it is any translate function - pub fn is_translate(&self) -> bool { - use self::TransformOperation::*; - match *self { - Translate(..) | Translate3D(..) | TranslateX(..) | TranslateY(..) | TranslateZ(..) => { - true - }, - _ => false, - } - } - - /// Check if it is any scale function - pub fn is_scale(&self) -> bool { - use self::TransformOperation::*; - match *self { - Scale(..) | Scale3D(..) | ScaleX(..) | ScaleY(..) | ScaleZ(..) => true, - _ => false, - } - } -} - -/// Convert a length type into the absolute lengths. -pub trait ToAbsoluteLength { - /// Returns the absolute length as pixel value. - fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()>; -} - -impl ToAbsoluteLength for SpecifiedLength { - // This returns Err(()) if there is any relative length or percentage. We use this when - // parsing a transform list of DOMMatrix because we want to return a DOM Exception - // if there is relative length. - #[inline] - fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { - match *self { - SpecifiedLength::NoCalc(len) => len.to_computed_pixel_length_without_context(), - SpecifiedLength::Calc(ref calc) => calc.to_computed_pixel_length_without_context(), - } - } -} - -impl ToAbsoluteLength for SpecifiedLengthPercentage { - // This returns Err(()) if there is any relative length or percentage. We use this when - // parsing a transform list of DOMMatrix because we want to return a DOM Exception - // if there is relative length. - #[inline] - fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { - use self::SpecifiedLengthPercentage::*; - match *self { - Length(len) => len.to_computed_pixel_length_without_context(), - Calc(ref calc) => calc.to_computed_pixel_length_without_context(), - Percentage(..) => Err(()), - } - } -} - -impl ToAbsoluteLength for ComputedLength { - #[inline] - fn to_pixel_length(&self, _containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { - Ok(self.px()) - } -} - -impl ToAbsoluteLength for ComputedLengthPercentage { - #[inline] - fn to_pixel_length(&self, containing_len: Option<ComputedLength>) -> Result<CSSFloat, ()> { - Ok(self - .maybe_percentage_relative_to(containing_len) - .ok_or(())? - .px()) - } -} - -/// Support the conversion to a 3d matrix. -pub trait ToMatrix { - /// Check if it is a 3d transform function. - fn is_3d(&self) -> bool; - - /// Return the equivalent 3d matrix. - fn to_3d_matrix( - &self, - reference_box: Option<&Rect<ComputedLength>>, - ) -> Result<Transform3D<f64>, ()>; -} - -/// A little helper to deal with both specified and computed angles. -pub trait ToRadians { - /// Return the radians value as a 64-bit floating point value. - fn radians64(&self) -> f64; -} - -impl ToRadians for computed::angle::Angle { - #[inline] - fn radians64(&self) -> f64 { - computed::angle::Angle::radians64(self) - } -} - -impl ToRadians for SpecifiedAngle { - #[inline] - fn radians64(&self) -> f64 { - computed::angle::Angle::from_degrees(self.degrees()).radians64() - } -} - -impl<Angle, Number, Length, Integer, LoP> ToMatrix - for TransformOperation<Angle, Number, Length, Integer, LoP> -where - Angle: Zero + ToRadians + Copy, - Number: PartialEq + Copy + Into<f32> + Into<f64>, - Length: ToAbsoluteLength, - LoP: Zero + ToAbsoluteLength + ZeroNoPercent, -{ - #[inline] - fn is_3d(&self) -> bool { - use self::TransformOperation::*; - match *self { - Translate3D(..) | TranslateZ(..) | Rotate3D(..) | RotateX(..) | RotateY(..) | - RotateZ(..) | Scale3D(..) | ScaleZ(..) | Perspective(..) | Matrix3D(..) => true, - _ => false, - } - } - - /// If |reference_box| is None, we will drop the percent part from translate because - /// we cannot resolve it without the layout info, for computed TransformOperation. - /// However, for specified TransformOperation, we will return Err(()) if there is any relative - /// lengths because the only caller, DOMMatrix, doesn't accept relative lengths. - #[inline] - fn to_3d_matrix( - &self, - reference_box: Option<&Rect<ComputedLength>>, - ) -> Result<Transform3D<f64>, ()> { - use self::TransformOperation::*; - - let reference_width = reference_box.map(|v| v.size.width); - let reference_height = reference_box.map(|v| v.size.height); - let matrix = match *self { - Rotate3D(ax, ay, az, theta) => { - let theta = theta.radians64(); - let (ax, ay, az, theta) = - get_normalized_vector_and_angle(ax.into(), ay.into(), az.into(), theta); - Transform3D::rotation( - ax as f64, - ay as f64, - az as f64, - euclid::Angle::radians(theta), - ) - }, - RotateX(theta) => { - let theta = euclid::Angle::radians(theta.radians64()); - Transform3D::rotation(1., 0., 0., theta) - }, - RotateY(theta) => { - let theta = euclid::Angle::radians(theta.radians64()); - Transform3D::rotation(0., 1., 0., theta) - }, - RotateZ(theta) | Rotate(theta) => { - let theta = euclid::Angle::radians(theta.radians64()); - Transform3D::rotation(0., 0., 1., theta) - }, - Perspective(ref p) => { - let px = match p { - PerspectiveFunction::None => f32::INFINITY, - PerspectiveFunction::Length(ref p) => p.to_pixel_length(None)?, - }; - create_perspective_matrix(px).cast() - }, - Scale3D(sx, sy, sz) => Transform3D::scale(sx.into(), sy.into(), sz.into()), - Scale(sx, sy) => Transform3D::scale(sx.into(), sy.into(), 1.), - ScaleX(s) => Transform3D::scale(s.into(), 1., 1.), - ScaleY(s) => Transform3D::scale(1., s.into(), 1.), - ScaleZ(s) => Transform3D::scale(1., 1., s.into()), - Translate3D(ref tx, ref ty, ref tz) => { - let tx = tx.to_pixel_length(reference_width)? as f64; - let ty = ty.to_pixel_length(reference_height)? as f64; - Transform3D::translation(tx, ty, tz.to_pixel_length(None)? as f64) - }, - Translate(ref tx, ref ty) => { - let tx = tx.to_pixel_length(reference_width)? as f64; - let ty = ty.to_pixel_length(reference_height)? as f64; - Transform3D::translation(tx, ty, 0.) - }, - TranslateX(ref t) => { - let t = t.to_pixel_length(reference_width)? as f64; - Transform3D::translation(t, 0., 0.) - }, - TranslateY(ref t) => { - let t = t.to_pixel_length(reference_height)? as f64; - Transform3D::translation(0., t, 0.) - }, - TranslateZ(ref z) => Transform3D::translation(0., 0., z.to_pixel_length(None)? as f64), - Skew(theta_x, theta_y) => Transform3D::skew( - euclid::Angle::radians(theta_x.radians64()), - euclid::Angle::radians(theta_y.radians64()), - ), - SkewX(theta) => Transform3D::skew( - euclid::Angle::radians(theta.radians64()), - euclid::Angle::radians(0.), - ), - SkewY(theta) => Transform3D::skew( - euclid::Angle::radians(0.), - euclid::Angle::radians(theta.radians64()), - ), - Matrix3D(m) => m.into(), - Matrix(m) => m.into(), - InterpolateMatrix { .. } | AccumulateMatrix { .. } => { - // TODO: Convert InterpolateMatrix/AccumulateMatrix into a valid Transform3D by - // the reference box and do interpolation on these two Transform3D matrices. - // Both Gecko and Servo don't support this for computing distance, and Servo - // doesn't support animations on InterpolateMatrix/AccumulateMatrix, so - // return an identity matrix. - // Note: DOMMatrix doesn't go into this arm. - Transform3D::identity() - }, - }; - Ok(matrix) - } -} - -impl<T> Transform<T> { - /// `none` - pub fn none() -> Self { - Transform(Default::default()) - } -} - -impl<T: ToMatrix> Transform<T> { - /// Return the equivalent 3d matrix of this transform list. - /// - /// We return a pair: the first one is the transform matrix, and the second one - /// indicates if there is any 3d transform function in this transform list. - #[cfg_attr(rustfmt, rustfmt_skip)] - pub fn to_transform_3d_matrix( - &self, - reference_box: Option<&Rect<ComputedLength>> - ) -> Result<(Transform3D<CSSFloat>, bool), ()> { - Self::components_to_transform_3d_matrix(&self.0, reference_box) - } - - /// Converts a series of components to a 3d matrix. - #[cfg_attr(rustfmt, rustfmt_skip)] - pub fn components_to_transform_3d_matrix( - ops: &[T], - reference_box: Option<&Rect<ComputedLength>>, - ) -> Result<(Transform3D<CSSFloat>, bool), ()> { - let cast_3d_transform = |m: Transform3D<f64>| -> Transform3D<CSSFloat> { - use std::{f32, f64}; - let cast = |v: f64| v.min(f32::MAX as f64).max(f32::MIN as f64) as f32; - Transform3D::new( - cast(m.m11), cast(m.m12), cast(m.m13), cast(m.m14), - cast(m.m21), cast(m.m22), cast(m.m23), cast(m.m24), - cast(m.m31), cast(m.m32), cast(m.m33), cast(m.m34), - cast(m.m41), cast(m.m42), cast(m.m43), cast(m.m44), - ) - }; - - let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?; - Ok((cast_3d_transform(m), is_3d)) - } - - /// Same as Transform::to_transform_3d_matrix but a f64 version. - pub fn to_transform_3d_matrix_f64( - &self, - reference_box: Option<&Rect<ComputedLength>> - ) -> Result<(Transform3D<f64>, bool), ()> { - Self::components_to_transform_3d_matrix_f64(&self.0, reference_box) - } - - fn components_to_transform_3d_matrix_f64( - ops: &[T], - reference_box: Option<&Rect<ComputedLength>>, - ) -> Result<(Transform3D<f64>, bool), ()> { - // We intentionally use Transform3D<f64> during computation to avoid - // error propagation because using f32 to compute triangle functions - // (e.g. in rotation()) is not accurate enough. In Gecko, we also use - // "double" to compute the triangle functions. Therefore, let's use - // Transform3D<f64> during matrix computation and cast it into f32 in - // the end. - let mut transform = Transform3D::<f64>::identity(); - let mut contain_3d = false; - - for operation in ops { - let matrix = operation.to_3d_matrix(reference_box)?; - contain_3d = contain_3d || operation.is_3d(); - transform = matrix.then(&transform); - } - - Ok((transform, contain_3d)) - } -} - -/// Return the transform matrix from a perspective length. -#[inline] -pub fn create_perspective_matrix(d: CSSFloat) -> Transform3D<CSSFloat> { - if d.is_finite() { - Transform3D::perspective(d.max(1.)) - } else { - Transform3D::identity() - } -} - -/// Return the normalized direction vector and its angle for Rotate3D. -pub fn get_normalized_vector_and_angle<T: Zero>( - x: CSSFloat, - y: CSSFloat, - z: CSSFloat, - angle: T, -) -> (CSSFloat, CSSFloat, CSSFloat, T) { - use crate::values::computed::transform::DirectionVector; - use euclid::approxeq::ApproxEq; - let vector = DirectionVector::new(x, y, z); - if vector.square_length().approx_eq(&f32::zero()) { - // https://www.w3.org/TR/css-transforms-1/#funcdef-rotate3d - // A direction vector that cannot be normalized, such as [0, 0, 0], will cause the - // rotation to not be applied, so we use identity matrix (i.e. rotate3d(0, 0, 1, 0)). - (0., 0., 1., T::zero()) - } else { - let vector = vector.robust_normalize(); - (vector.x, vector.y, vector.z, angle) - } -} - -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -/// A value of the `Rotate` property -/// -/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> -pub enum GenericRotate<Number, Angle> { - /// 'none' - None, - /// '<angle>' - Rotate(Angle), - /// '<number>{3} <angle>' - Rotate3D(Number, Number, Number, Angle), -} - -pub use self::GenericRotate as Rotate; - -/// A trait to check if the current 3D vector is parallel to the DirectionVector. -/// This is especially for serialization on Rotate. -pub trait IsParallelTo { - /// Returns true if this is parallel to the vector. - fn is_parallel_to(&self, vector: &computed::transform::DirectionVector) -> bool; -} - -impl<Number, Angle> ToCss for Rotate<Number, Angle> -where - Number: Copy + ToCss + Zero, - Angle: ToCss, - (Number, Number, Number): IsParallelTo, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - use crate::values::computed::transform::DirectionVector; - match *self { - Rotate::None => dest.write_str("none"), - Rotate::Rotate(ref angle) => angle.to_css(dest), - Rotate::Rotate3D(x, y, z, ref angle) => { - // If the axis is parallel with the x or y axes, it must serialize as the - // appropriate keyword. If a rotation about the z axis (that is, in 2D) is - // specified, the property must serialize as just an <angle> - // - // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization - let v = (x, y, z); - let axis = if x.is_zero() && y.is_zero() && z.is_zero() { - // The zero length vector is parallel to every other vector, so - // is_parallel_to() returns true for it. However, it is definitely different - // from x axis, y axis, or z axis, and it's meaningless to perform a rotation - // using that direction vector. So we *have* to serialize it using that same - // vector - we can't simplify to some theoretically parallel axis-aligned - // vector. - None - } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { - Some("x ") - } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { - Some("y ") - } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { - // When we're parallel to the z-axis, we can just serialize the angle. - return angle.to_css(dest); - } else { - None - }; - match axis { - Some(a) => dest.write_str(a)?, - None => { - x.to_css(dest)?; - dest.write_char(' ')?; - y.to_css(dest)?; - dest.write_char(' ')?; - z.to_css(dest)?; - dest.write_char(' ')?; - }, - } - angle.to_css(dest) - }, - } - } -} - -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -/// A value of the `Scale` property -/// -/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> -pub enum GenericScale<Number> { - /// 'none' - None, - /// '<number>{1,3}' - Scale(Number, Number, Number), -} - -pub use self::GenericScale as Scale; - -impl<Number> ToCss for Scale<Number> -where - Number: ToCss + PartialEq + Copy, - f32: From<Number>, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - f32: From<Number>, - { - match *self { - Scale::None => dest.write_str("none"), - Scale::Scale(ref x, ref y, ref z) => { - x.to_css(dest)?; - - let is_3d = f32::from(*z) != 1.0; - if is_3d || x != y { - dest.write_char(' ')?; - y.to_css(dest)?; - } - - if is_3d { - dest.write_char(' ')?; - z.to_css(dest)?; - } - Ok(()) - }, - } - } -} - -#[inline] -fn y_axis_and_z_axis_are_zero<LengthPercentage: Zero + ZeroNoPercent, Length: Zero>( - _: &LengthPercentage, - y: &LengthPercentage, - z: &Length, -) -> bool { - y.is_zero_no_percent() && z.is_zero() -} - -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -/// A value of the `translate` property -/// -/// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization: -/// -/// If a 2d translation is specified, the property must serialize with only one -/// or two values (per usual, if the second value is 0px, the default, it must -/// be omitted when serializing; however if 0% is the second value, it is included). -/// -/// If a 3d translation is specified and the value can be expressed as 2d, we treat as 2d and -/// serialize accoringly. Otherwise, we serialize all three values. -/// https://github.com/w3c/csswg-drafts/issues/3305 -/// -/// <https://drafts.csswg.org/css-transforms-2/#individual-transforms> -pub enum GenericTranslate<LengthPercentage, Length> -where - LengthPercentage: Zero + ZeroNoPercent, - Length: Zero, -{ - /// 'none' - None, - /// <length-percentage> [ <length-percentage> <length>? ]? - Translate( - LengthPercentage, - #[css(contextual_skip_if = "y_axis_and_z_axis_are_zero")] LengthPercentage, - #[css(skip_if = "Zero::is_zero")] Length, - ), -} - -pub use self::GenericTranslate as Translate; - -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum TransformStyle { - Flat, - #[css(keyword = "preserve-3d")] - Preserve3d, -} diff --git a/components/style/values/generics/ui.rs b/components/style/values/generics/ui.rs deleted file mode 100644 index 87c8674182c..00000000000 --- a/components/style/values/generics/ui.rs +++ /dev/null @@ -1,129 +0,0 @@ -/* 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/. */ - -//! Generic values for UI properties. - -use crate::values::specified::ui::CursorKind; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ToCss}; - -/// A generic value for the `cursor` property. -/// -/// https://drafts.csswg.org/css-ui/#cursor -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct GenericCursor<Image> { - /// The parsed images for the cursor. - pub images: crate::OwnedSlice<Image>, - /// The kind of the cursor [default | help | ...]. - pub keyword: CursorKind, -} - -pub use self::GenericCursor as Cursor; - -impl<Image> Cursor<Image> { - /// Set `cursor` to `auto` - #[inline] - pub fn auto() -> Self { - Self { - images: Default::default(), - keyword: CursorKind::Auto, - } - } -} - -impl<Image: ToCss> ToCss for Cursor<Image> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - for image in &*self.images { - image.to_css(dest)?; - dest.write_str(", ")?; - } - self.keyword.to_css(dest) - } -} - -/// A generic value for item of `image cursors`. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] -#[repr(C)] -pub struct GenericCursorImage<Image, Number> { - /// The url to parse images from. - pub image: Image, - /// Whether the image has a hotspot or not. - pub has_hotspot: bool, - /// The x coordinate. - pub hotspot_x: Number, - /// The y coordinate. - pub hotspot_y: Number, -} - -pub use self::GenericCursorImage as CursorImage; - -impl<Image: ToCss, Number: ToCss> ToCss for CursorImage<Image, Number> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.image.to_css(dest)?; - if self.has_hotspot { - dest.write_char(' ')?; - self.hotspot_x.to_css(dest)?; - dest.write_char(' ')?; - self.hotspot_y.to_css(dest)?; - } - Ok(()) - } -} - -/// A generic value for `scrollbar-color` property. -/// -/// https://drafts.csswg.org/css-scrollbars-1/#scrollbar-color -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericScrollbarColor<Color> { - /// `auto` - Auto, - /// `<color>{2}` - Colors { - /// First `<color>`, for color of the scrollbar thumb. - thumb: Color, - /// Second `<color>`, for color of the scrollbar track. - track: Color, - }, -} - -pub use self::GenericScrollbarColor as ScrollbarColor; - -impl<Color> Default for ScrollbarColor<Color> { - #[inline] - fn default() -> Self { - ScrollbarColor::Auto - } -} diff --git a/components/style/values/generics/url.rs b/components/style/values/generics/url.rs deleted file mode 100644 index 46ed453e82d..00000000000 --- a/components/style/values/generics/url.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* 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/. */ - -//! Generic types for url properties. - -/// An image url or none, used for example in list-style-image -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum GenericUrlOrNone<U> { - /// `none` - None, - /// A URL. - Url(U), -} - -pub use self::GenericUrlOrNone as UrlOrNone; - -impl<Url> UrlOrNone<Url> { - /// Initial "none" value for properties such as `list-style-image` - pub fn none() -> Self { - UrlOrNone::None - } - - /// Returns whether the value is `none`. - pub fn is_none(&self) -> bool { - match *self { - UrlOrNone::None => true, - UrlOrNone::Url(..) => false, - } - } -} diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs deleted file mode 100644 index 5eb83b1f36b..00000000000 --- a/components/style/values/mod.rs +++ /dev/null @@ -1,793 +0,0 @@ -/* 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/. */ - -//! Common [values][values] used in CSS. -//! -//! [values]: https://drafts.csswg.org/css-values/ - -#![deny(missing_docs)] - -use crate::parser::{Parse, ParserContext}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::Atom; -pub use cssparser::{serialize_identifier, serialize_name, CowRcStr, Parser}; -pub use cssparser::{SourceLocation, Token, RGBA}; -use precomputed_hash::PrecomputedHash; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Debug, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; -use to_shmem::impl_trivial_to_shmem; - -#[cfg(feature = "gecko")] -pub use crate::gecko::url::CssUrl; -#[cfg(feature = "servo")] -pub use crate::servo::url::CssUrl; - -pub mod animated; -pub mod computed; -pub mod distance; -pub mod generics; -pub mod resolved; -pub mod specified; - -/// A CSS float value. -pub type CSSFloat = f32; - -/// Normalizes a float value to zero after a set of operations that might turn -/// it into NaN. -#[inline] -pub fn normalize(v: CSSFloat) -> CSSFloat { - if v.is_nan() { - 0.0 - } else { - v - } -} - -/// A CSS integer value. -pub type CSSInteger = i32; - -/// Serialize an identifier which is represented as an atom. -#[cfg(feature = "gecko")] -pub fn serialize_atom_identifier<W>(ident: &Atom, dest: &mut W) -> fmt::Result -where - W: Write, -{ - ident.with_str(|s| serialize_identifier(s, dest)) -} - -/// Serialize an identifier which is represented as an atom. -#[cfg(feature = "servo")] -pub fn serialize_atom_identifier<Static, W>( - ident: &::string_cache::Atom<Static>, - dest: &mut W, -) -> fmt::Result -where - Static: string_cache::StaticAtomSet, - W: Write, -{ - serialize_identifier(&ident, dest) -} - -/// Serialize a name which is represented as an Atom. -#[cfg(feature = "gecko")] -pub fn serialize_atom_name<W>(ident: &Atom, dest: &mut W) -> fmt::Result -where - W: Write, -{ - ident.with_str(|s| serialize_name(s, dest)) -} - -/// Serialize a name which is represented as an Atom. -#[cfg(feature = "servo")] -pub fn serialize_atom_name<Static, W>( - ident: &::string_cache::Atom<Static>, - dest: &mut W, -) -> fmt::Result -where - Static: string_cache::StaticAtomSet, - W: Write, -{ - serialize_name(&ident, dest) -} - -fn nan_inf_enabled() -> bool { - static_prefs::pref!("layout.css.nan-inf.enabled") -} - -/// Serialize a number with calc, and NaN/infinity handling (if enabled) -pub fn serialize_number<W>(v: f32, was_calc: bool, dest: &mut CssWriter<W>) -> fmt::Result -where - W: Write, -{ - serialize_specified_dimension(v, "", was_calc, dest) -} - -/// Serialize a specified dimension with unit, calc, and NaN/infinity handling (if enabled) -pub fn serialize_specified_dimension<W>( - v: f32, - unit: &str, - was_calc: bool, - dest: &mut CssWriter<W>, -) -> fmt::Result -where - W: Write, -{ - if was_calc { - dest.write_str("calc(")?; - } - - if !v.is_finite() && nan_inf_enabled() { - // https://drafts.csswg.org/css-values/#calc-error-constants: - // "While not technically numbers, these keywords act as numeric values, - // similar to e and pi. Thus to get an infinite length, for example, - // requires an expression like calc(infinity * 1px)." - - if v.is_nan() { - dest.write_str("NaN")?; - } else if v == f32::INFINITY { - dest.write_str("infinity")?; - } else if v == f32::NEG_INFINITY { - dest.write_str("-infinity")?; - } - - if !unit.is_empty() { - dest.write_str(" * 1")?; - } - } else { - v.to_css(dest)?; - } - - dest.write_str(unit)?; - - if was_calc { - dest.write_char(')')?; - } - Ok(()) -} - -/// A CSS string stored as an `Atom`. -#[repr(transparent)] -#[derive( - Clone, - Debug, - Default, - Deref, - Eq, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct AtomString(pub Atom); - -#[cfg(feature = "servo")] -impl AsRef<str> for AtomString { - fn as_ref(&self) -> &str { - &*self.0 - } -} - -impl cssparser::ToCss for AtomString { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: Write, - { - #[cfg(feature = "servo")] - { - cssparser::CssStringWriter::new(dest).write_str(self.as_ref()) - } - #[cfg(feature = "gecko")] - { - self.0 - .with_str(|s| cssparser::CssStringWriter::new(dest).write_str(s)) - } - } -} - -impl PrecomputedHash for AtomString { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.0.precomputed_hash() - } -} - -impl<'a> From<&'a str> for AtomString { - #[inline] - fn from(string: &str) -> Self { - Self(Atom::from(string)) - } -} - -/// A generic CSS `<ident>` stored as an `Atom`. -#[cfg(feature = "servo")] -#[repr(transparent)] -#[derive(Deref)] -pub struct GenericAtomIdent<Set>(pub string_cache::Atom<Set>) -where - Set: string_cache::StaticAtomSet; - -/// A generic CSS `<ident>` stored as an `Atom`, for the default atom set. -#[cfg(feature = "servo")] -pub type AtomIdent = GenericAtomIdent<servo_atoms::AtomStaticSet>; - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> style_traits::SpecifiedValueInfo for GenericAtomIdent<Set> {} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> Default for GenericAtomIdent<Set> { - fn default() -> Self { - Self(string_cache::Atom::default()) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> std::fmt::Debug for GenericAtomIdent<Set> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.0.fmt(f) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> std::hash::Hash for GenericAtomIdent<Set> { - fn hash<H: std::hash::Hasher>(&self, state: &mut H) { - self.0.hash(state) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> Eq for GenericAtomIdent<Set> {} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> PartialEq for GenericAtomIdent<Set> { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> Clone for GenericAtomIdent<Set> { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> to_shmem::ToShmem for GenericAtomIdent<Set> { - fn to_shmem(&self, builder: &mut to_shmem::SharedMemoryBuilder) -> to_shmem::Result<Self> { - use std::mem::ManuallyDrop; - - let atom = self.0.to_shmem(builder)?; - Ok(ManuallyDrop::new(Self(ManuallyDrop::into_inner(atom)))) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> malloc_size_of::MallocSizeOf for GenericAtomIdent<Set> { - fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { - self.0.size_of(ops) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> cssparser::ToCss for GenericAtomIdent<Set> { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> style_traits::ToCss for GenericAtomIdent<Set> { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> PrecomputedHash for GenericAtomIdent<Set> { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.0.precomputed_hash() - } -} - -#[cfg(feature = "servo")] -impl<'a, Set: string_cache::StaticAtomSet> From<&'a str> for GenericAtomIdent<Set> { - #[inline] - fn from(string: &str) -> Self { - Self(string_cache::Atom::from(string)) - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> std::borrow::Borrow<string_cache::Atom<Set>> - for GenericAtomIdent<Set> -{ - #[inline] - fn borrow(&self) -> &string_cache::Atom<Set> { - &self.0 - } -} - -#[cfg(feature = "servo")] -impl<Set: string_cache::StaticAtomSet> GenericAtomIdent<Set> { - /// Constructs a new GenericAtomIdent. - #[inline] - pub fn new(atom: string_cache::Atom<Set>) -> Self { - Self(atom) - } - - /// Cast an atom ref to an AtomIdent ref. - #[inline] - pub fn cast<'a>(atom: &'a string_cache::Atom<Set>) -> &'a Self { - let ptr = atom as *const _ as *const Self; - // safety: repr(transparent) - unsafe { &*ptr } - } -} - -/// A CSS `<ident>` stored as an `Atom`. -#[cfg(feature = "gecko")] -#[repr(transparent)] -#[derive( - Clone, Debug, Default, Deref, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem, -)] -pub struct AtomIdent(pub Atom); - -#[cfg(feature = "gecko")] -impl cssparser::ToCss for AtomIdent { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} - -#[cfg(feature = "gecko")] -impl style_traits::ToCss for AtomIdent { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - cssparser::ToCss::to_css(self, dest) - } -} - -#[cfg(feature = "gecko")] -impl PrecomputedHash for AtomIdent { - #[inline] - fn precomputed_hash(&self) -> u32 { - self.0.precomputed_hash() - } -} - -#[cfg(feature = "gecko")] -impl<'a> From<&'a str> for AtomIdent { - #[inline] - fn from(string: &str) -> Self { - Self(Atom::from(string)) - } -} - -#[cfg(feature = "gecko")] -impl AtomIdent { - /// Constructs a new AtomIdent. - #[inline] - pub fn new(atom: Atom) -> Self { - Self(atom) - } - - /// Like `Atom::with` but for `AtomIdent`. - pub unsafe fn with<F, R>(ptr: *const crate::gecko_bindings::structs::nsAtom, callback: F) -> R - where - F: FnOnce(&Self) -> R, - { - Atom::with(ptr, |atom: &Atom| { - // safety: repr(transparent) - let atom = atom as *const Atom as *const AtomIdent; - callback(&*atom) - }) - } - - /// Cast an atom ref to an AtomIdent ref. - #[inline] - pub fn cast<'a>(atom: &'a Atom) -> &'a Self { - let ptr = atom as *const _ as *const Self; - // safety: repr(transparent) - unsafe { &*ptr } - } -} - -#[cfg(feature = "gecko")] -impl std::borrow::Borrow<crate::gecko_string_cache::WeakAtom> for AtomIdent { - #[inline] - fn borrow(&self) -> &crate::gecko_string_cache::WeakAtom { - self.0.borrow() - } -} - -/// Serialize a value into percentage. -pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result -where - W: Write, -{ - serialize_specified_dimension(value * 100., "%", /* was_calc = */ false, dest) -} - -/// Serialize a value into normalized (no NaN/inf serialization) percentage. -pub fn serialize_normalized_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result -where - W: Write, -{ - (value * 100.).to_css(dest)?; - dest.write_char('%') -} - -/// Convenience void type to disable some properties and values through types. -#[cfg_attr(feature = "servo", derive(Deserialize, MallocSizeOf, Serialize))] -#[derive( - Clone, - Copy, - Debug, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToComputedValue, - ToCss, - ToResolvedValue, -)] -pub enum Impossible {} - -// FIXME(nox): This should be derived but the derive code cannot cope -// with uninhabited enums. -impl ComputeSquaredDistance for Impossible { - #[inline] - fn compute_squared_distance(&self, _other: &Self) -> Result<SquaredDistance, ()> { - match *self {} - } -} - -impl_trivial_to_shmem!(Impossible); - -impl Parse for Impossible { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -/// A struct representing one of two kinds of values. -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum Either<A, B> { - /// The first value. - First(A), - /// The second kind of value. - Second(B), -} - -impl<A: Debug, B: Debug> Debug for Either<A, B> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Either::First(ref v) => v.fmt(f), - Either::Second(ref v) => v.fmt(f), - } - } -} - -/// <https://drafts.csswg.org/css-values-4/#custom-idents> -#[derive( - Clone, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct CustomIdent(pub Atom); - -impl CustomIdent { - /// Parse an already-tokenizer identifier - pub fn from_ident<'i>( - location: SourceLocation, - ident: &CowRcStr<'i>, - excluding: &[&str], - ) -> Result<Self, ParseError<'i>> { - if !Self::is_valid(ident, excluding) { - return Err( - location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) - ); - } - if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { - Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(CustomIdent(Atom::from(ident.as_ref()))) - } - } - - fn is_valid(ident: &str, excluding: &[&str]) -> bool { - use crate::properties::CSSWideKeyword; - // https://drafts.csswg.org/css-values-4/#custom-idents: - // - // The CSS-wide keywords are not valid <custom-ident>s. The default - // keyword is reserved and is also not a valid <custom-ident>. - if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { - return false; - } - - // https://drafts.csswg.org/css-values-4/#custom-idents: - // - // Excluded keywords are excluded in all ASCII case permutations. - !excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) - } -} - -impl ToCss for CustomIdent { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} - -/// <https://www.w3.org/TR/css-values-4/#dashed-idents> -/// This is simply an Atom, but will only parse if the identifier starts with "--". -#[repr(transparent)] -#[derive( - Clone, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct DashedIdent(pub Atom); - -impl Parse for DashedIdent { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - if ident.starts_with("--") { - Ok(Self(Atom::from(ident.as_ref()))) - } else { - Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) - } - } -} - -impl ToCss for DashedIdent { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} - -/// The <timeline-name> or <keyframes-name>. -/// The definition of these two names are the same, so we use the same type for them. -/// -/// <https://drafts.csswg.org/css-animations-2/#typedef-timeline-name> -/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name> -/// -/// We use a single atom for these. Empty atom represents `none` animation. -#[repr(transparent)] -#[derive( - Clone, - Debug, - Hash, - PartialEq, - MallocSizeOf, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct TimelineOrKeyframesName(Atom); - -impl TimelineOrKeyframesName { - /// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name> - pub fn from_ident(value: &str) -> Self { - Self(Atom::from(value)) - } - - /// Returns the `none` value. - pub fn none() -> Self { - Self(atom!("")) - } - - /// Returns whether this is the special `none` value. - pub fn is_none(&self) -> bool { - self.0 == atom!("") - } - - /// Create a new TimelineOrKeyframesName from Atom. - #[cfg(feature = "gecko")] - pub fn from_atom(atom: Atom) -> Self { - Self(atom) - } - - /// The name as an Atom - pub fn as_atom(&self) -> &Atom { - &self.0 - } - - fn parse<'i, 't>(input: &mut Parser<'i, 't>, invalid: &[&str]) -> Result<Self, ParseError<'i>> { - debug_assert!(invalid.contains(&"none")); - let location = input.current_source_location(); - Ok(match *input.next()? { - Token::Ident(ref s) => Self(CustomIdent::from_ident(location, s, invalid)?.0), - Token::QuotedString(ref s) => Self(Atom::from(s.as_ref())), - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }) - } - - fn to_css<W>(&self, dest: &mut CssWriter<W>, invalid: &[&str]) -> fmt::Result - where - W: Write, - { - debug_assert!(invalid.contains(&"none")); - - if self.0 == atom!("") { - return dest.write_str("none"); - } - - let mut serialize = |s: &_| { - if CustomIdent::is_valid(s, invalid) { - serialize_identifier(s, dest) - } else { - s.to_css(dest) - } - }; - #[cfg(feature = "gecko")] - return self.0.with_str(|s| serialize(s)); - #[cfg(feature = "servo")] - return serialize(self.0.as_ref()); - } -} - -impl Eq for TimelineOrKeyframesName {} - -/// The typedef of <timeline-name>. -#[repr(transparent)] -#[derive( - Clone, - Debug, - Deref, - Hash, - Eq, - PartialEq, - MallocSizeOf, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct TimelineName(TimelineOrKeyframesName); - -impl TimelineName { - /// Create a new TimelineName from Atom. - #[cfg(feature = "gecko")] - pub fn from_atom(atom: Atom) -> Self { - Self(TimelineOrKeyframesName::from_atom(atom)) - } - - /// Returns the `none` value. - pub fn none() -> Self { - Self(TimelineOrKeyframesName::none()) - } -} - -impl Parse for TimelineName { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Self(TimelineOrKeyframesName::parse( - input, - &["none", "auto"], - )?)) - } -} - -impl ToCss for TimelineName { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0.to_css(dest, &["none", "auto"]) - } -} - -/// The typedef of <keyframes-name>. -#[repr(transparent)] -#[derive( - Clone, - Debug, - Deref, - Hash, - Eq, - PartialEq, - MallocSizeOf, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct KeyframesName(TimelineOrKeyframesName); - -impl KeyframesName { - /// Create a new KeyframesName from Atom. - #[cfg(feature = "gecko")] - pub fn from_atom(atom: Atom) -> Self { - Self(TimelineOrKeyframesName::from_atom(atom)) - } - - /// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name> - pub fn from_ident(value: &str) -> Self { - Self(TimelineOrKeyframesName::from_ident(value)) - } - - /// Returns the `none` value. - pub fn none() -> Self { - Self(TimelineOrKeyframesName::none()) - } -} - -impl Parse for KeyframesName { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Self(TimelineOrKeyframesName::parse(input, &["none"])?)) - } -} - -impl ToCss for KeyframesName { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0.to_css(dest, &["none"]) - } -} diff --git a/components/style/values/resolved/color.rs b/components/style/values/resolved/color.rs deleted file mode 100644 index 79dfd8685fe..00000000000 --- a/components/style/values/resolved/color.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* 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/. */ - -//! Resolved color values. - -use super::{Context, ToResolvedValue}; - -use crate::color::AbsoluteColor; -use crate::values::computed::color as computed; -use crate::values::generics::color as generics; - -impl ToResolvedValue for computed::Color { - // A resolved color value is an rgba color, with currentcolor resolved. - type ResolvedValue = AbsoluteColor; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - context.style.resolve_color(self) - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - generics::Color::Absolute(resolved) - } -} - -impl ToResolvedValue for computed::CaretColor { - // A resolved caret-color value is an rgba color, with auto resolving to - // currentcolor. - type ResolvedValue = AbsoluteColor; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - let color = match self.0 { - generics::ColorOrAuto::Color(color) => color, - generics::ColorOrAuto::Auto => generics::Color::currentcolor(), - }; - color.to_resolved_value(context) - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - generics::CaretColor(generics::ColorOrAuto::Color( - computed::Color::from_resolved_value(resolved), - )) - } -} diff --git a/components/style/values/resolved/counters.rs b/components/style/values/resolved/counters.rs deleted file mode 100644 index c1332449ad1..00000000000 --- a/components/style/values/resolved/counters.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* 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/. */ - -//! Resolved values for counter properties - -use super::{Context, ToResolvedValue}; -use crate::values::computed; - -/// https://drafts.csswg.org/css-content/#content-property -/// -/// We implement this at resolved value time because otherwise it causes us to -/// allocate a bunch of useless initial structs for ::before / ::after, which is -/// a bit unfortunate. -/// -/// Though these should be temporary, mostly, so if this causes complexity in -/// other places, it should be fine to move to `StyleAdjuster`. -/// -/// See https://github.com/w3c/csswg-drafts/issues/4632 for where some related -/// issues are being discussed. -impl ToResolvedValue for computed::Content { - type ResolvedValue = Self; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self { - let (is_pseudo, is_before_or_after, is_marker) = match context.style.pseudo() { - Some(ref pseudo) => (true, pseudo.is_before_or_after(), pseudo.is_marker()), - None => (false, false, false), - }; - match self { - Self::Normal if is_before_or_after => Self::None, - // For now, make `content: none` compute to `normal` for pseudos - // other than ::before, ::after and ::marker, as we don't respect it. - // https://github.com/w3c/csswg-drafts/issues/6124 - // Ditto for non-pseudo elements if the pref is disabled. - Self::None - if (is_pseudo && !is_before_or_after && !is_marker) || - (!is_pseudo && - !static_prefs::pref!("layout.css.element-content-none.enabled")) => - { - Self::Normal - }, - other => other, - } - } - - #[inline] - fn from_resolved_value(resolved: Self) -> Self { - resolved - } -} diff --git a/components/style/values/resolved/mod.rs b/components/style/values/resolved/mod.rs deleted file mode 100644 index 7f15ee452f5..00000000000 --- a/components/style/values/resolved/mod.rs +++ /dev/null @@ -1,277 +0,0 @@ -/* 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/. */ - -//! Resolved values. These are almost always computed values, but in some cases -//! there are used values. - -#[cfg(feature = "gecko")] -use crate::media_queries::Device; -use crate::properties::ComputedValues; -use crate::ArcSlice; -use servo_arc::Arc; -use smallvec::SmallVec; - -mod color; -mod counters; - -use crate::values::computed; - -/// Element-specific information needed to resolve property values. -#[cfg(feature = "gecko")] -pub struct ResolvedElementInfo<'a> { - /// Element we're resolving line-height against. - pub element: crate::gecko::wrapper::GeckoElement<'a>, -} - -/// Information needed to resolve a given value. -pub struct Context<'a> { - /// The style we're resolving for. This is useful to resolve currentColor. - pub style: &'a ComputedValues, - /// The device / document we're resolving style for. Useful to do font metrics stuff needed for - /// line-height. - #[cfg(feature = "gecko")] - pub device: &'a Device, - /// The element-specific information to resolve the value. - #[cfg(feature = "gecko")] - pub element_info: ResolvedElementInfo<'a>, -} - -/// A trait to represent the conversion between resolved and resolved values. -/// -/// This trait is derivable with `#[derive(ToResolvedValue)]`. -/// -/// The deriving code assumes that if the type isn't generic, then the trait can -/// be implemented as simple move. This means that a manual implementation with -/// `ResolvedValue = Self` is bogus if it returns anything else than a clone. -pub trait ToResolvedValue { - /// The resolved value type we're going to be converted to. - type ResolvedValue; - - /// Convert a resolved value to a resolved value. - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue; - - /// Convert a resolved value to resolved value form. - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self; -} - -macro_rules! trivial_to_resolved_value { - ($ty:ty) => { - impl $crate::values::resolved::ToResolvedValue for $ty { - type ResolvedValue = Self; - - #[inline] - fn to_resolved_value(self, _: &Context) -> Self { - self - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - resolved - } - } - }; -} - -trivial_to_resolved_value!(()); -trivial_to_resolved_value!(bool); -trivial_to_resolved_value!(f32); -trivial_to_resolved_value!(u8); -trivial_to_resolved_value!(i8); -trivial_to_resolved_value!(u16); -trivial_to_resolved_value!(i16); -trivial_to_resolved_value!(u32); -trivial_to_resolved_value!(i32); -trivial_to_resolved_value!(usize); -trivial_to_resolved_value!(String); -trivial_to_resolved_value!(Box<str>); -trivial_to_resolved_value!(crate::OwnedStr); -trivial_to_resolved_value!(crate::color::AbsoluteColor); -trivial_to_resolved_value!(crate::Atom); -trivial_to_resolved_value!(crate::values::AtomIdent); -trivial_to_resolved_value!(app_units::Au); -trivial_to_resolved_value!(computed::url::ComputedUrl); -#[cfg(feature = "gecko")] -trivial_to_resolved_value!(computed::url::ComputedImageUrl); -#[cfg(feature = "servo")] -trivial_to_resolved_value!(crate::Namespace); -#[cfg(feature = "servo")] -trivial_to_resolved_value!(crate::Prefix); -trivial_to_resolved_value!(computed::LengthPercentage); -trivial_to_resolved_value!(style_traits::values::specified::AllowedNumericType); -trivial_to_resolved_value!(computed::TimingFunction); - -impl<A, B> ToResolvedValue for (A, B) -where - A: ToResolvedValue, - B: ToResolvedValue, -{ - type ResolvedValue = ( - <A as ToResolvedValue>::ResolvedValue, - <B as ToResolvedValue>::ResolvedValue, - ); - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - ( - self.0.to_resolved_value(context), - self.1.to_resolved_value(context), - ) - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - ( - A::from_resolved_value(resolved.0), - B::from_resolved_value(resolved.1), - ) - } -} - -impl<T> ToResolvedValue for Option<T> -where - T: ToResolvedValue, -{ - type ResolvedValue = Option<<T as ToResolvedValue>::ResolvedValue>; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - self.map(|item| item.to_resolved_value(context)) - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - resolved.map(T::from_resolved_value) - } -} - -impl<T> ToResolvedValue for SmallVec<[T; 1]> -where - T: ToResolvedValue, -{ - type ResolvedValue = SmallVec<[<T as ToResolvedValue>::ResolvedValue; 1]>; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - self.into_iter() - .map(|item| item.to_resolved_value(context)) - .collect() - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - resolved.into_iter().map(T::from_resolved_value).collect() - } -} - -impl<T> ToResolvedValue for Vec<T> -where - T: ToResolvedValue, -{ - type ResolvedValue = Vec<<T as ToResolvedValue>::ResolvedValue>; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - self.into_iter() - .map(|item| item.to_resolved_value(context)) - .collect() - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - resolved.into_iter().map(T::from_resolved_value).collect() - } -} - -impl<T> ToResolvedValue for Box<T> -where - T: ToResolvedValue, -{ - type ResolvedValue = Box<<T as ToResolvedValue>::ResolvedValue>; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - Box::new(T::to_resolved_value(*self, context)) - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - Box::new(T::from_resolved_value(*resolved)) - } -} - -impl<T> ToResolvedValue for Box<[T]> -where - T: ToResolvedValue, -{ - type ResolvedValue = Box<[<T as ToResolvedValue>::ResolvedValue]>; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - Vec::from(self) - .to_resolved_value(context) - .into_boxed_slice() - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - Vec::from_resolved_value(Vec::from(resolved)).into_boxed_slice() - } -} - -impl<T> ToResolvedValue for crate::OwnedSlice<T> -where - T: ToResolvedValue, -{ - type ResolvedValue = crate::OwnedSlice<<T as ToResolvedValue>::ResolvedValue>; - - #[inline] - fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - self.into_box().to_resolved_value(context).into() - } - - #[inline] - fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - Self::from(Box::from_resolved_value(resolved.into_box())) - } -} - -// NOTE(emilio): This is implementable more generically, but it's unlikely what -// you want there, as it forces you to have an extra allocation. -// -// We could do that if needed, ideally with specialization for the case where -// ResolvedValue = T. But we don't need it for now. -impl<T> ToResolvedValue for Arc<T> -where - T: ToResolvedValue<ResolvedValue = T>, -{ - type ResolvedValue = Self; - - #[inline] - fn to_resolved_value(self, _: &Context) -> Self { - self - } - - #[inline] - fn from_resolved_value(resolved: Self) -> Self { - resolved - } -} - -// Same caveat as above applies. -impl<T> ToResolvedValue for ArcSlice<T> -where - T: ToResolvedValue<ResolvedValue = T>, -{ - type ResolvedValue = Self; - - #[inline] - fn to_resolved_value(self, _: &Context) -> Self { - self - } - - #[inline] - fn from_resolved_value(resolved: Self) -> Self { - resolved - } -} diff --git a/components/style/values/specified/align.rs b/components/style/values/specified/align.rs deleted file mode 100644 index ebb6f638340..00000000000 --- a/components/style/values/specified/align.rs +++ /dev/null @@ -1,817 +0,0 @@ -/* 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/. */ - -//! Values for CSS Box Alignment properties -//! -//! https://drafts.csswg.org/css-align/ - -use crate::parser::{Parse, ParserContext}; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, ToCss}; - -bitflags! { - /// Constants shared by multiple CSS Box Alignment properties - #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] - #[repr(C)] - pub struct AlignFlags: u8 { - // Enumeration stored in the lower 5 bits: - /// {align,justify}-{content,items,self}: 'auto' - const AUTO = 0; - /// 'normal' - const NORMAL = 1; - /// 'start' - const START = 2; - /// 'end' - const END = 3; - /// 'flex-start' - const FLEX_START = 4; - /// 'flex-end' - const FLEX_END = 5; - /// 'center' - const CENTER = 6; - /// 'left' - const LEFT = 7; - /// 'right' - const RIGHT = 8; - /// 'baseline' - const BASELINE = 9; - /// 'last-baseline' - const LAST_BASELINE = 10; - /// 'stretch' - const STRETCH = 11; - /// 'self-start' - const SELF_START = 12; - /// 'self-end' - const SELF_END = 13; - /// 'space-between' - const SPACE_BETWEEN = 14; - /// 'space-around' - const SPACE_AROUND = 15; - /// 'space-evenly' - const SPACE_EVENLY = 16; - - // Additional flags stored in the upper bits: - /// 'legacy' (mutually exclusive w. SAFE & UNSAFE) - const LEGACY = 1 << 5; - /// 'safe' - const SAFE = 1 << 6; - /// 'unsafe' (mutually exclusive w. SAFE) - const UNSAFE = 1 << 7; - - /// Mask for the additional flags above. - const FLAG_BITS = 0b11100000; - } -} - -impl AlignFlags { - /// Returns the enumeration value stored in the lower 5 bits. - #[inline] - fn value(&self) -> Self { - *self & !AlignFlags::FLAG_BITS - } -} - -impl ToCss for AlignFlags { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let extra_flags = *self & AlignFlags::FLAG_BITS; - let value = self.value(); - - match extra_flags { - AlignFlags::LEGACY => { - dest.write_str("legacy")?; - if value.is_empty() { - return Ok(()); - } - dest.write_char(' ')?; - }, - AlignFlags::SAFE => dest.write_str("safe ")?, - AlignFlags::UNSAFE => dest.write_str("unsafe ")?, - _ => { - debug_assert_eq!(extra_flags, AlignFlags::empty()); - }, - } - - dest.write_str(match value { - AlignFlags::AUTO => "auto", - AlignFlags::NORMAL => "normal", - AlignFlags::START => "start", - AlignFlags::END => "end", - AlignFlags::FLEX_START => "flex-start", - AlignFlags::FLEX_END => "flex-end", - AlignFlags::CENTER => "center", - AlignFlags::LEFT => "left", - AlignFlags::RIGHT => "right", - AlignFlags::BASELINE => "baseline", - AlignFlags::LAST_BASELINE => "last baseline", - AlignFlags::STRETCH => "stretch", - AlignFlags::SELF_START => "self-start", - AlignFlags::SELF_END => "self-end", - AlignFlags::SPACE_BETWEEN => "space-between", - AlignFlags::SPACE_AROUND => "space-around", - AlignFlags::SPACE_EVENLY => "space-evenly", - _ => unreachable!(), - }) - } -} - -/// An axis direction, either inline (for the `justify` properties) or block, -/// (for the `align` properties). -#[derive(Clone, Copy, PartialEq)] -pub enum AxisDirection { - /// Block direction. - Block, - /// Inline direction. - Inline, -} - -/// Shared value for the `align-content` and `justify-content` properties. -/// -/// <https://drafts.csswg.org/css-align/#content-distribution> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -pub struct ContentDistribution { - primary: AlignFlags, - // FIXME(https://github.com/w3c/csswg-drafts/issues/1002): This will need to - // accept fallback alignment, eventually. -} - -impl ContentDistribution { - /// The initial value 'normal' - #[inline] - pub fn normal() -> Self { - Self::new(AlignFlags::NORMAL) - } - - /// `start` - #[inline] - pub fn start() -> Self { - Self::new(AlignFlags::START) - } - - /// The initial value 'normal' - #[inline] - pub fn new(primary: AlignFlags) -> Self { - Self { primary } - } - - /// Returns whether this value is a <baseline-position>. - pub fn is_baseline_position(&self) -> bool { - matches!( - self.primary.value(), - AlignFlags::BASELINE | AlignFlags::LAST_BASELINE - ) - } - - /// The primary alignment - #[inline] - pub fn primary(self) -> AlignFlags { - self.primary - } - - /// Parse a value for align-content / justify-content. - pub fn parse<'i, 't>( - input: &mut Parser<'i, 't>, - axis: AxisDirection, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update the `list_keywords` function below - // when this function is updated. - - // Try to parse normal first - if input - .try_parse(|i| i.expect_ident_matching("normal")) - .is_ok() - { - return Ok(ContentDistribution::normal()); - } - - // Parse <baseline-position>, but only on the block axis. - if axis == AxisDirection::Block { - if let Ok(value) = input.try_parse(parse_baseline) { - return Ok(ContentDistribution::new(value)); - } - } - - // <content-distribution> - if let Ok(value) = input.try_parse(parse_content_distribution) { - return Ok(ContentDistribution::new(value)); - } - - // <overflow-position>? <content-position> - let overflow_position = input - .try_parse(parse_overflow_position) - .unwrap_or(AlignFlags::empty()); - - let content_position = try_match_ident_ignore_ascii_case! { input, - "start" => AlignFlags::START, - "end" => AlignFlags::END, - "flex-start" => AlignFlags::FLEX_START, - "flex-end" => AlignFlags::FLEX_END, - "center" => AlignFlags::CENTER, - "left" if axis == AxisDirection::Inline => AlignFlags::LEFT, - "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT, - }; - - Ok(ContentDistribution::new( - content_position | overflow_position, - )) - } - - fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) { - f(&["normal"]); - if axis == AxisDirection::Block { - list_baseline_keywords(f); - } - list_content_distribution_keywords(f); - list_overflow_position_keywords(f); - f(&["start", "end", "flex-start", "flex-end", "center"]); - if axis == AxisDirection::Inline { - f(&["left", "right"]); - } - } -} - -/// Value for the `align-content` property. -/// -/// <https://drafts.csswg.org/css-align/#propdef-align-content> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct AlignContent(pub ContentDistribution); - -impl Parse for AlignContent { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update `impl SpecifiedValueInfo` below when - // this function is updated. - Ok(AlignContent(ContentDistribution::parse( - input, - AxisDirection::Block, - )?)) - } -} - -impl SpecifiedValueInfo for AlignContent { - fn collect_completion_keywords(f: KeywordsCollectFn) { - ContentDistribution::list_keywords(f, AxisDirection::Block); - } -} - -/// Value for the `align-tracks` property. -/// -/// <https://github.com/w3c/csswg-drafts/issues/4650> -#[derive( - Clone, - Debug, - Default, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -#[css(comma)] -pub struct AlignTracks(#[css(iterable, if_empty = "normal")] pub crate::OwnedSlice<AlignContent>); - -impl Parse for AlignTracks { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let values = input.parse_comma_separated(|input| AlignContent::parse(context, input))?; - Ok(AlignTracks(values.into())) - } -} - -/// Value for the `justify-content` property. -/// -/// <https://drafts.csswg.org/css-align/#propdef-justify-content> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct JustifyContent(pub ContentDistribution); - -impl Parse for JustifyContent { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update `impl SpecifiedValueInfo` below when - // this function is updated. - Ok(JustifyContent(ContentDistribution::parse( - input, - AxisDirection::Inline, - )?)) - } -} - -impl SpecifiedValueInfo for JustifyContent { - fn collect_completion_keywords(f: KeywordsCollectFn) { - ContentDistribution::list_keywords(f, AxisDirection::Inline); - } -} -/// Value for the `justify-tracks` property. -/// -/// <https://github.com/w3c/csswg-drafts/issues/4650> -#[derive( - Clone, - Debug, - Default, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -#[css(comma)] -pub struct JustifyTracks( - #[css(iterable, if_empty = "normal")] pub crate::OwnedSlice<JustifyContent>, -); - -impl Parse for JustifyTracks { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let values = input.parse_comma_separated(|input| JustifyContent::parse(context, input))?; - Ok(JustifyTracks(values.into())) - } -} - -/// <https://drafts.csswg.org/css-align/#self-alignment> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct SelfAlignment(pub AlignFlags); - -impl SelfAlignment { - /// The initial value 'auto' - #[inline] - pub fn auto() -> Self { - SelfAlignment(AlignFlags::AUTO) - } - - /// Returns whether this value is valid for both axis directions. - pub fn is_valid_on_both_axes(&self) -> bool { - match self.0.value() { - // left | right are only allowed on the inline axis. - AlignFlags::LEFT | AlignFlags::RIGHT => false, - - _ => true, - } - } - - /// Parse a self-alignment value on one of the axis. - pub fn parse<'i, 't>( - input: &mut Parser<'i, 't>, - axis: AxisDirection, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update the `list_keywords` function below - // when this function is updated. - - // <baseline-position> - // - // It's weird that this accepts <baseline-position>, but not - // justify-content... - if let Ok(value) = input.try_parse(parse_baseline) { - return Ok(SelfAlignment(value)); - } - - // auto | normal | stretch - if let Ok(value) = input.try_parse(parse_auto_normal_stretch) { - return Ok(SelfAlignment(value)); - } - - // <overflow-position>? <self-position> - let overflow_position = input - .try_parse(parse_overflow_position) - .unwrap_or(AlignFlags::empty()); - let self_position = parse_self_position(input, axis)?; - Ok(SelfAlignment(overflow_position | self_position)) - } - - fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) { - list_baseline_keywords(f); - list_auto_normal_stretch(f); - list_overflow_position_keywords(f); - list_self_position_keywords(f, axis); - } -} - -/// The specified value of the align-self property. -/// -/// <https://drafts.csswg.org/css-align/#propdef-align-self> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct AlignSelf(pub SelfAlignment); - -impl Parse for AlignSelf { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update `impl SpecifiedValueInfo` below when - // this function is updated. - Ok(AlignSelf(SelfAlignment::parse( - input, - AxisDirection::Block, - )?)) - } -} - -impl SpecifiedValueInfo for AlignSelf { - fn collect_completion_keywords(f: KeywordsCollectFn) { - SelfAlignment::list_keywords(f, AxisDirection::Block); - } -} - -/// The specified value of the justify-self property. -/// -/// <https://drafts.csswg.org/css-align/#propdef-justify-self> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct JustifySelf(pub SelfAlignment); - -impl Parse for JustifySelf { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update `impl SpecifiedValueInfo` below when - // this function is updated. - Ok(JustifySelf(SelfAlignment::parse( - input, - AxisDirection::Inline, - )?)) - } -} - -impl SpecifiedValueInfo for JustifySelf { - fn collect_completion_keywords(f: KeywordsCollectFn) { - SelfAlignment::list_keywords(f, AxisDirection::Inline); - } -} - -/// Value of the `align-items` property -/// -/// <https://drafts.csswg.org/css-align/#propdef-align-items> -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct AlignItems(pub AlignFlags); - -impl AlignItems { - /// The initial value 'normal' - #[inline] - pub fn normal() -> Self { - AlignItems(AlignFlags::NORMAL) - } -} - -impl Parse for AlignItems { - // normal | stretch | <baseline-position> | - // <overflow-position>? <self-position> - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update `impl SpecifiedValueInfo` below when - // this function is updated. - - // <baseline-position> - if let Ok(baseline) = input.try_parse(parse_baseline) { - return Ok(AlignItems(baseline)); - } - - // normal | stretch - if let Ok(value) = input.try_parse(parse_normal_stretch) { - return Ok(AlignItems(value)); - } - // <overflow-position>? <self-position> - let overflow = input - .try_parse(parse_overflow_position) - .unwrap_or(AlignFlags::empty()); - let self_position = parse_self_position(input, AxisDirection::Block)?; - Ok(AlignItems(self_position | overflow)) - } -} - -impl SpecifiedValueInfo for AlignItems { - fn collect_completion_keywords(f: KeywordsCollectFn) { - list_baseline_keywords(f); - list_normal_stretch(f); - list_overflow_position_keywords(f); - list_self_position_keywords(f, AxisDirection::Block); - } -} - -/// Value of the `justify-items` property -/// -/// <https://drafts.csswg.org/css-align/#justify-items-property> -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem)] -#[repr(C)] -pub struct JustifyItems(pub AlignFlags); - -impl JustifyItems { - /// The initial value 'legacy' - #[inline] - pub fn legacy() -> Self { - JustifyItems(AlignFlags::LEGACY) - } - - /// The value 'normal' - #[inline] - pub fn normal() -> Self { - JustifyItems(AlignFlags::NORMAL) - } -} - -impl Parse for JustifyItems { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // NOTE Please also update `impl SpecifiedValueInfo` below when - // this function is updated. - - // <baseline-position> - // - // It's weird that this accepts <baseline-position>, but not - // justify-content... - if let Ok(baseline) = input.try_parse(parse_baseline) { - return Ok(JustifyItems(baseline)); - } - - // normal | stretch - if let Ok(value) = input.try_parse(parse_normal_stretch) { - return Ok(JustifyItems(value)); - } - - // legacy | [ legacy && [ left | right | center ] ] - if let Ok(value) = input.try_parse(parse_legacy) { - return Ok(JustifyItems(value)); - } - - // <overflow-position>? <self-position> - let overflow = input - .try_parse(parse_overflow_position) - .unwrap_or(AlignFlags::empty()); - let self_position = parse_self_position(input, AxisDirection::Inline)?; - Ok(JustifyItems(overflow | self_position)) - } -} - -impl SpecifiedValueInfo for JustifyItems { - fn collect_completion_keywords(f: KeywordsCollectFn) { - list_baseline_keywords(f); - list_normal_stretch(f); - list_legacy_keywords(f); - list_overflow_position_keywords(f); - list_self_position_keywords(f, AxisDirection::Inline); - } -} - -// auto | normal | stretch -fn parse_auto_normal_stretch<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_auto_normal_stretch` function - // below when this function is updated. - try_match_ident_ignore_ascii_case! { input, - "auto" => Ok(AlignFlags::AUTO), - "normal" => Ok(AlignFlags::NORMAL), - "stretch" => Ok(AlignFlags::STRETCH), - } -} - -fn list_auto_normal_stretch(f: KeywordsCollectFn) { - f(&["auto", "normal", "stretch"]); -} - -// normal | stretch -fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_normal_stretch` function below - // when this function is updated. - try_match_ident_ignore_ascii_case! { input, - "normal" => Ok(AlignFlags::NORMAL), - "stretch" => Ok(AlignFlags::STRETCH), - } -} - -fn list_normal_stretch(f: KeywordsCollectFn) { - f(&["normal", "stretch"]); -} - -// <baseline-position> -fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_baseline_keywords` function - // below when this function is updated. - try_match_ident_ignore_ascii_case! { input, - "baseline" => Ok(AlignFlags::BASELINE), - "first" => { - input.expect_ident_matching("baseline")?; - Ok(AlignFlags::BASELINE) - }, - "last" => { - input.expect_ident_matching("baseline")?; - Ok(AlignFlags::LAST_BASELINE) - }, - } -} - -fn list_baseline_keywords(f: KeywordsCollectFn) { - f(&["baseline", "first baseline", "last baseline"]); -} - -// <content-distribution> -fn parse_content_distribution<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_content_distribution_keywords` - // function below when this function is updated. - try_match_ident_ignore_ascii_case! { input, - "stretch" => Ok(AlignFlags::STRETCH), - "space-between" => Ok(AlignFlags::SPACE_BETWEEN), - "space-around" => Ok(AlignFlags::SPACE_AROUND), - "space-evenly" => Ok(AlignFlags::SPACE_EVENLY), - } -} - -fn list_content_distribution_keywords(f: KeywordsCollectFn) { - f(&["stretch", "space-between", "space-around", "space-evenly"]); -} - -// <overflow-position> -fn parse_overflow_position<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_overflow_position_keywords` - // function below when this function is updated. - try_match_ident_ignore_ascii_case! { input, - "safe" => Ok(AlignFlags::SAFE), - "unsafe" => Ok(AlignFlags::UNSAFE), - } -} - -fn list_overflow_position_keywords(f: KeywordsCollectFn) { - f(&["safe", "unsafe"]); -} - -// <self-position> | left | right in the inline axis. -fn parse_self_position<'i, 't>( - input: &mut Parser<'i, 't>, - axis: AxisDirection, -) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_self_position_keywords` - // function below when this function is updated. - Ok(try_match_ident_ignore_ascii_case! { input, - "start" => AlignFlags::START, - "end" => AlignFlags::END, - "flex-start" => AlignFlags::FLEX_START, - "flex-end" => AlignFlags::FLEX_END, - "center" => AlignFlags::CENTER, - "self-start" => AlignFlags::SELF_START, - "self-end" => AlignFlags::SELF_END, - "left" if axis == AxisDirection::Inline => AlignFlags::LEFT, - "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT, - }) -} - -fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) { - f(&[ - "start", - "end", - "flex-start", - "flex-end", - "center", - "self-start", - "self-end", - ]); - if axis == AxisDirection::Inline { - f(&["left", "right"]); - } -} - -fn parse_left_right_center<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_legacy_keywords` function below - // when this function is updated. - Ok(try_match_ident_ignore_ascii_case! { input, - "left" => AlignFlags::LEFT, - "right" => AlignFlags::RIGHT, - "center" => AlignFlags::CENTER, - }) -} - -// legacy | [ legacy && [ left | right | center ] ] -fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> { - // NOTE Please also update the `list_legacy_keywords` function below - // when this function is updated. - let flags = try_match_ident_ignore_ascii_case! { input, - "legacy" => { - let flags = input.try_parse(parse_left_right_center) - .unwrap_or(AlignFlags::empty()); - - return Ok(AlignFlags::LEGACY | flags) - }, - "left" => AlignFlags::LEFT, - "right" => AlignFlags::RIGHT, - "center" => AlignFlags::CENTER, - }; - - input.expect_ident_matching("legacy")?; - Ok(AlignFlags::LEGACY | flags) -} - -fn list_legacy_keywords(f: KeywordsCollectFn) { - f(&["legacy", "left", "right", "center"]); -} diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs deleted file mode 100644 index fb4554eb852..00000000000 --- a/components/style/values/specified/angle.rs +++ /dev/null @@ -1,276 +0,0 @@ -/* 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/. */ - -//! Specified angles. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::angle::Angle as ComputedAngle; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::specified::calc::CalcNode; -use crate::values::CSSFloat; -use crate::Zero; -use cssparser::{Parser, Token}; -use std::f32::consts::PI; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss}; - -/// A specified angle dimension. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToCss, ToShmem)] -pub enum AngleDimension { - /// An angle with degree unit. - #[css(dimension)] - Deg(CSSFloat), - /// An angle with gradian unit. - #[css(dimension)] - Grad(CSSFloat), - /// An angle with radian unit. - #[css(dimension)] - Rad(CSSFloat), - /// An angle with turn unit. - #[css(dimension)] - Turn(CSSFloat), -} - -impl Zero for AngleDimension { - fn zero() -> Self { - AngleDimension::Deg(0.) - } - - fn is_zero(&self) -> bool { - self.unitless_value() == 0.0 - } -} - -impl AngleDimension { - /// Returns the amount of degrees this angle represents. - #[inline] - fn degrees(&self) -> CSSFloat { - const DEG_PER_RAD: f32 = 180.0 / PI; - const DEG_PER_TURN: f32 = 360.0; - const DEG_PER_GRAD: f32 = 180.0 / 200.0; - - match *self { - AngleDimension::Deg(d) => d, - AngleDimension::Rad(rad) => rad * DEG_PER_RAD, - AngleDimension::Turn(turns) => turns * DEG_PER_TURN, - AngleDimension::Grad(gradians) => gradians * DEG_PER_GRAD, - } - } - - fn unitless_value(&self) -> CSSFloat { - match *self { - AngleDimension::Deg(v) | - AngleDimension::Rad(v) | - AngleDimension::Turn(v) | - AngleDimension::Grad(v) => v, - } - } - - fn unit(&self) -> &'static str { - match *self { - AngleDimension::Deg(_) => "deg", - AngleDimension::Rad(_) => "rad", - AngleDimension::Turn(_) => "turn", - AngleDimension::Grad(_) => "grad", - } - } -} - -/// A specified Angle value, which is just the angle dimension, plus whether it -/// was specified as `calc()` or not. -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct Angle { - value: AngleDimension, - was_calc: bool, -} - -impl Zero for Angle { - fn zero() -> Self { - Self { - value: Zero::zero(), - was_calc: false, - } - } - - fn is_zero(&self) -> bool { - self.value.is_zero() - } -} - -impl ToCss for Angle { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - crate::values::serialize_specified_dimension( - self.value.unitless_value(), - self.value.unit(), - self.was_calc, - dest, - ) - } -} - -impl ToComputedValue for Angle { - type ComputedValue = ComputedAngle; - - #[inline] - fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { - let degrees = self.degrees(); - - // NaN and +-infinity should degenerate to 0: https://github.com/w3c/csswg-drafts/issues/6105 - ComputedAngle::from_degrees(if degrees.is_finite() { degrees } else { 0.0 }) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Angle { - value: AngleDimension::Deg(computed.degrees()), - was_calc: false, - } - } -} - -impl Angle { - /// Creates an angle with the given value in degrees. - #[inline] - pub fn from_degrees(value: CSSFloat, was_calc: bool) -> Self { - Angle { - value: AngleDimension::Deg(value), - was_calc, - } - } - - /// Creates an angle with the given value in radians. - #[inline] - pub fn from_radians(value: CSSFloat) -> Self { - Angle { - value: AngleDimension::Rad(value), - was_calc: false, - } - } - - /// Return `0deg`. - pub fn zero() -> Self { - Self::from_degrees(0.0, false) - } - - /// Returns the value of the angle in degrees, mostly for `calc()`. - #[inline] - pub fn degrees(&self) -> CSSFloat { - self.value.degrees() - } - - /// Returns the value of the angle in radians. - #[inline] - pub fn radians(&self) -> CSSFloat { - const RAD_PER_DEG: f32 = PI / 180.0; - self.value.degrees() * RAD_PER_DEG - } - - /// Whether this specified angle came from a `calc()` expression. - #[inline] - pub fn was_calc(&self) -> bool { - self.was_calc - } - - /// Returns an `Angle` parsed from a `calc()` expression. - pub fn from_calc(degrees: CSSFloat) -> Self { - Angle { - value: AngleDimension::Deg(degrees), - was_calc: true, - } - } - - /// Returns the unit of the angle. - #[inline] - pub fn unit(&self) -> &'static str { - self.value.unit() - } -} - -/// Whether to allow parsing an unitless zero as a valid angle. -/// -/// This should always be `No`, except for exceptions like: -/// -/// https://github.com/w3c/fxtf-drafts/issues/228 -/// -/// See also: https://github.com/w3c/csswg-drafts/issues/1162. -#[allow(missing_docs)] -pub enum AllowUnitlessZeroAngle { - Yes, - No, -} - -impl Parse for Angle { - /// Parses an angle according to CSS-VALUES § 6.1. - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, AllowUnitlessZeroAngle::No) - } -} - -impl Angle { - /// Parse an `<angle>` value given a value and an unit. - pub fn parse_dimension(value: CSSFloat, unit: &str, was_calc: bool) -> Result<Angle, ()> { - let value = match_ignore_ascii_case! { unit, - "deg" => AngleDimension::Deg(value), - "grad" => AngleDimension::Grad(value), - "turn" => AngleDimension::Turn(value), - "rad" => AngleDimension::Rad(value), - _ => return Err(()) - }; - - Ok(Self { value, was_calc }) - } - - /// Parse an `<angle>` allowing unitless zero to represent a zero angle. - /// - /// See the comment in `AllowUnitlessZeroAngle` for why. - #[inline] - pub fn parse_with_unitless<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) - } - - pub(super) fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_unitless_zero: AllowUnitlessZeroAngle, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let t = input.next()?; - let allow_unitless_zero = matches!(allow_unitless_zero, AllowUnitlessZeroAngle::Yes); - match *t { - Token::Dimension { - value, ref unit, .. - } => { - match Angle::parse_dimension(value, unit, /* from_calc = */ false) { - Ok(angle) => Ok(angle), - Err(()) => { - let t = t.clone(); - Err(input.new_unexpected_token_error(t)) - }, - } - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - CalcNode::parse_angle(context, input, function) - }, - Token::Number { value, .. } if value == 0. && allow_unitless_zero => Ok(Angle::zero()), - ref t => { - let t = t.clone(); - Err(input.new_unexpected_token_error(t)) - }, - } - } -} - -impl SpecifiedValueInfo for Angle {} diff --git a/components/style/values/specified/animation.rs b/components/style/values/specified/animation.rs deleted file mode 100644 index ad4fbc587c1..00000000000 --- a/components/style/values/specified/animation.rs +++ /dev/null @@ -1,420 +0,0 @@ -/* 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/. */ - -//! Specified types for properties related to animations and transitions. - -use crate::custom_properties::Name as CustomPropertyName; -use crate::parser::{Parse, ParserContext}; -use crate::properties::{LonghandId, PropertyDeclarationId, PropertyId, ShorthandId}; -use crate::values::generics::animation as generics; -use crate::values::specified::{LengthPercentage, NonNegativeNumber}; -use crate::values::{CustomIdent, KeyframesName, TimelineName}; -use crate::Atom; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{ - CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, -}; - -/// A given transition property, that is either `All`, a longhand or shorthand -/// property, or an unsupported or custom property. -#[derive( - Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] -pub enum TransitionProperty { - /// A shorthand. - Shorthand(ShorthandId), - /// A longhand transitionable property. - Longhand(LonghandId), - /// A custom property. - Custom(CustomPropertyName), - /// Unrecognized property which could be any non-transitionable, custom property, or - /// unknown property. - Unsupported(CustomIdent), -} - -impl ToCss for TransitionProperty { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - use crate::values::serialize_atom_name; - match *self { - TransitionProperty::Shorthand(ref s) => s.to_css(dest), - TransitionProperty::Longhand(ref l) => l.to_css(dest), - TransitionProperty::Custom(ref name) => { - dest.write_str("--")?; - serialize_atom_name(name, dest) - }, - TransitionProperty::Unsupported(ref i) => i.to_css(dest), - } - } -} - -impl Parse for TransitionProperty { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - - let id = match PropertyId::parse_ignoring_rule_type(&ident, context) { - Ok(id) => id, - Err(..) => { - return Ok(TransitionProperty::Unsupported(CustomIdent::from_ident( - location, - ident, - &["none"], - )?)); - }, - }; - - Ok(match id.as_shorthand() { - Ok(s) => TransitionProperty::Shorthand(s), - Err(longhand_or_custom) => match longhand_or_custom { - PropertyDeclarationId::Longhand(id) => TransitionProperty::Longhand(id), - PropertyDeclarationId::Custom(custom) => TransitionProperty::Custom(custom.clone()), - }, - }) - } -} - -impl SpecifiedValueInfo for TransitionProperty { - fn collect_completion_keywords(f: KeywordsCollectFn) { - // `transition-property` can actually accept all properties and - // arbitrary identifiers, but `all` is a special one we'd like - // to list. - f(&["all"]); - } -} - -impl TransitionProperty { - /// Returns `all`. - #[inline] - pub fn all() -> Self { - TransitionProperty::Shorthand(ShorthandId::All) - } - - /// Convert TransitionProperty to nsCSSPropertyID. - #[cfg(feature = "gecko")] - pub fn to_nscsspropertyid( - &self, - ) -> Result<crate::gecko_bindings::structs::nsCSSPropertyID, ()> { - Ok(match *self { - TransitionProperty::Shorthand(ShorthandId::All) => { - crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties - }, - TransitionProperty::Shorthand(ref id) => id.to_nscsspropertyid(), - TransitionProperty::Longhand(ref id) => id.to_nscsspropertyid(), - TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => return Err(()), - }) - } -} - -/// https://drafts.csswg.org/css-animations/#animation-iteration-count -#[derive(Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum AnimationIterationCount { - /// A `<number>` value. - Number(NonNegativeNumber), - /// The `infinite` keyword. - Infinite, -} - -impl AnimationIterationCount { - /// Returns the value `1.0`. - #[inline] - pub fn one() -> Self { - Self::Number(NonNegativeNumber::new(1.0)) - } -} - -/// A value for the `animation-name` property. -#[derive( - Clone, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[value_info(other_values = "none")] -#[repr(C)] -pub struct AnimationName(pub KeyframesName); - -impl AnimationName { - /// Get the name of the animation as an `Atom`. - pub fn as_atom(&self) -> Option<&Atom> { - if self.is_none() { - return None; - } - Some(self.0.as_atom()) - } - - /// Returns the `none` value. - pub fn none() -> Self { - AnimationName(KeyframesName::none()) - } - - /// Returns whether this is the none value. - pub fn is_none(&self) -> bool { - self.0.is_none() - } -} - -impl Parse for AnimationName { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) { - return Ok(AnimationName(name)); - } - - input.expect_ident_matching("none")?; - Ok(AnimationName(KeyframesName::none())) - } -} - -/// A value for the <Scroller> used in scroll(). -/// -/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller -#[derive( - Clone, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum Scroller { - /// The nearest ancestor scroll container. (Default.) - Nearest, - /// The document viewport as the scroll container. - Root, - /// Specifies to use the element’s own principal box as the scroll container. - #[css(keyword = "self")] - SelfElement, -} - -impl Scroller { - /// Returns true if it is default. - #[inline] - fn is_default(&self) -> bool { - matches!(*self, Self::Nearest) - } -} - -impl Default for Scroller { - fn default() -> Self { - Self::Nearest - } -} - -/// A value for the <Axis> used in scroll(), or a value for {scroll|view}-timeline-axis. -/// -/// https://drafts.csswg.org/scroll-animations-1/#typedef-axis -/// https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-axis -/// https://drafts.csswg.org/scroll-animations-1/#view-timeline-axis -#[derive( - Clone, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ScrollAxis { - /// The block axis of the scroll container. (Default.) - Block = 0, - /// The inline axis of the scroll container. - Inline = 1, - /// The vertical block axis of the scroll container. - Vertical = 2, - /// The horizontal axis of the scroll container. - Horizontal = 3, -} - -impl ScrollAxis { - /// Returns true if it is default. - #[inline] - pub fn is_default(&self) -> bool { - matches!(*self, Self::Block) - } -} - -impl Default for ScrollAxis { - fn default() -> Self { - Self::Block - } -} - -/// The scroll() notation. -/// https://drafts.csswg.org/scroll-animations-1/#scroll-notation -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(function = "scroll")] -#[repr(C)] -pub struct ScrollFunction { - /// The scroll container element whose scroll position drives the progress of the timeline. - #[css(skip_if = "Scroller::is_default")] - pub scroller: Scroller, - /// The axis of scrolling that drives the progress of the timeline. - #[css(skip_if = "ScrollAxis::is_default")] - pub axis: ScrollAxis, -} - -impl ScrollFunction { - /// Parse the inner function arguments of `scroll()`. - fn parse_arguments<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - // <scroll()> = scroll( [ <scroller> || <axis> ]? ) - // https://drafts.csswg.org/scroll-animations-1/#funcdef-scroll - let mut scroller = None; - let mut axis = None; - loop { - if scroller.is_none() { - scroller = input.try_parse(Scroller::parse).ok(); - } - - if axis.is_none() { - axis = input.try_parse(ScrollAxis::parse).ok(); - if axis.is_some() { - continue; - } - } - break; - } - - Ok(Self { - scroller: scroller.unwrap_or_default(), - axis: axis.unwrap_or_default(), - }) - } -} - -impl generics::ViewFunction<LengthPercentage> { - /// Parse the inner function arguments of `view()`. - fn parse_arguments<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // <view()> = view( [ <axis> || <'view-timeline-inset'> ]? ) - // https://drafts.csswg.org/scroll-animations-1/#funcdef-view - let mut axis = None; - let mut inset = None; - loop { - if axis.is_none() { - axis = input.try_parse(ScrollAxis::parse).ok(); - } - - if inset.is_none() { - inset = input - .try_parse(|i| ViewTimelineInset::parse(context, i)) - .ok(); - if inset.is_some() { - continue; - } - } - break; - } - - Ok(Self { - inset: inset.unwrap_or_default(), - axis: axis.unwrap_or_default(), - }) - } -} - -/// A specified value for the `animation-timeline` property. -pub type AnimationTimeline = generics::GenericAnimationTimeline<LengthPercentage>; - -impl Parse for AnimationTimeline { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::generics::animation::ViewFunction; - - // <single-animation-timeline> = auto | none | <custom-ident> | <scroll()> | <view()> - // https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline - - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(Self::Auto); - } - - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(AnimationTimeline::Timeline(TimelineName::none())); - } - - if let Ok(name) = input.try_parse(|i| TimelineName::parse(context, i)) { - return Ok(AnimationTimeline::Timeline(name)); - } - - // Parse possible functions - let location = input.current_source_location(); - let function = input.expect_function()?.clone(); - input.parse_nested_block(move |i| { - match_ignore_ascii_case! { &function, - "scroll" => ScrollFunction::parse_arguments(i).map(Self::Scroll), - "view" => ViewFunction::parse_arguments(context, i).map(Self::View), - _ => { - Err(location.new_custom_error( - StyleParseErrorKind::UnexpectedFunction(function.clone()) - )) - }, - } - }) - } -} - -/// A value for the scroll-timeline-name or view-timeline-name. -pub type ScrollTimelineName = AnimationName; - -/// A specified value for the `view-timeline-inset` property. -pub type ViewTimelineInset = generics::GenericViewTimelineInset<LengthPercentage>; - -impl Parse for ViewTimelineInset { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::specified::LengthPercentageOrAuto; - - let start = LengthPercentageOrAuto::parse(context, input)?; - let end = match input.try_parse(|input| LengthPercentageOrAuto::parse(context, input)) { - Ok(end) => end, - Err(_) => start.clone(), - }; - - Ok(Self { start, end }) - } -} diff --git a/components/style/values/specified/background.rs b/components/style/values/specified/background.rs deleted file mode 100644 index 39a5a85193d..00000000000 --- a/components/style/values/specified/background.rs +++ /dev/null @@ -1,143 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values related to backgrounds. - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::background::BackgroundSize as GenericBackgroundSize; -use crate::values::specified::length::{ - NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, -}; -use cssparser::Parser; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss}; - -/// A specified value for the `background-size` property. -pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthPercentage>; - -impl Parse for BackgroundSize { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(width) = input.try_parse(|i| NonNegativeLengthPercentageOrAuto::parse(context, i)) - { - let height = input - .try_parse(|i| NonNegativeLengthPercentageOrAuto::parse(context, i)) - .unwrap_or(NonNegativeLengthPercentageOrAuto::auto()); - return Ok(GenericBackgroundSize::ExplicitSize { width, height }); - } - Ok(try_match_ident_ignore_ascii_case! { input, - "cover" => GenericBackgroundSize::Cover, - "contain" => GenericBackgroundSize::Contain, - }) - } -} - -/// One of the keywords for `background-repeat`. -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[value_info(other_values = "repeat-x,repeat-y")] -pub enum BackgroundRepeatKeyword { - Repeat, - Space, - Round, - NoRepeat, -} - -/// The value of the `background-repeat` property, with `repeat-x` / `repeat-y` -/// represented as the combination of `no-repeat` and `repeat` in the opposite -/// axes. -/// -/// https://drafts.csswg.org/css-backgrounds/#the-background-repeat -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct BackgroundRepeat(pub BackgroundRepeatKeyword, pub BackgroundRepeatKeyword); - -impl BackgroundRepeat { - /// Returns the `repeat repeat` value. - pub fn repeat() -> Self { - BackgroundRepeat( - BackgroundRepeatKeyword::Repeat, - BackgroundRepeatKeyword::Repeat, - ) - } -} - -impl ToCss for BackgroundRepeat { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match (self.0, self.1) { - (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => { - dest.write_str("repeat-x") - }, - (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => { - dest.write_str("repeat-y") - }, - (horizontal, vertical) => { - horizontal.to_css(dest)?; - if horizontal != vertical { - dest.write_char(' ')?; - vertical.to_css(dest)?; - } - Ok(()) - }, - } - } -} - -impl Parse for BackgroundRepeat { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let ident = input.expect_ident_cloned()?; - - match_ignore_ascii_case! { &ident, - "repeat-x" => { - return Ok(BackgroundRepeat(BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat)); - }, - "repeat-y" => { - return Ok(BackgroundRepeat(BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat)); - }, - _ => {}, - } - - let horizontal = match BackgroundRepeatKeyword::from_ident(&ident) { - Ok(h) => h, - Err(()) => { - return Err( - input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) - ); - }, - }; - - let vertical = input.try_parse(BackgroundRepeatKeyword::parse).ok(); - Ok(BackgroundRepeat(horizontal, vertical.unwrap_or(horizontal))) - } -} diff --git a/components/style/values/specified/basic_shape.rs b/components/style/values/specified/basic_shape.rs deleted file mode 100644 index c085446c642..00000000000 --- a/components/style/values/specified/basic_shape.rs +++ /dev/null @@ -1,321 +0,0 @@ -/* 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/. */ - -//! CSS handling for the specified value of -//! [`basic-shape`][basic-shape]s -//! -//! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::basic_shape as generic; -use crate::values::generics::basic_shape::{Path, PolygonCoord}; -use crate::values::generics::rect::Rect; -use crate::values::specified::border::BorderRadius; -use crate::values::specified::image::Image; -use crate::values::specified::position::{HorizontalPosition, Position, VerticalPosition}; -use crate::values::specified::url::SpecifiedUrl; -use crate::values::specified::{LengthPercentage, NonNegativeLengthPercentage, SVGPathData}; -use crate::Zero; -use cssparser::Parser; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// A specified alias for FillRule. -pub use crate::values::generics::basic_shape::FillRule; - -/// A specified `clip-path` value. -pub type ClipPath = generic::GenericClipPath<BasicShape, SpecifiedUrl>; - -/// A specified `shape-outside` value. -pub type ShapeOutside = generic::GenericShapeOutside<BasicShape, Image>; - -/// A specified basic shape. -pub type BasicShape = generic::GenericBasicShape< - HorizontalPosition, - VerticalPosition, - LengthPercentage, - NonNegativeLengthPercentage, ->; - -/// The specified value of `inset()` -pub type InsetRect = generic::InsetRect<LengthPercentage, NonNegativeLengthPercentage>; - -/// A specified circle. -pub type Circle = - generic::Circle<HorizontalPosition, VerticalPosition, NonNegativeLengthPercentage>; - -/// A specified ellipse. -pub type Ellipse = - generic::Ellipse<HorizontalPosition, VerticalPosition, NonNegativeLengthPercentage>; - -/// The specified value of `ShapeRadius` -pub type ShapeRadius = generic::ShapeRadius<NonNegativeLengthPercentage>; - -/// The specified value of `Polygon` -pub type Polygon = generic::GenericPolygon<LengthPercentage>; - -/// A helper for both clip-path and shape-outside parsing of shapes. -fn parse_shape_or_box<'i, 't, R, ReferenceBox>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - to_shape: impl FnOnce(Box<BasicShape>, ReferenceBox) -> R, - to_reference_box: impl FnOnce(ReferenceBox) -> R, -) -> Result<R, ParseError<'i>> -where - ReferenceBox: Default + Parse, -{ - fn parse_component<U: Parse>( - context: &ParserContext, - input: &mut Parser, - component: &mut Option<U>, - ) -> bool { - if component.is_some() { - return false; // already parsed this component - } - - *component = input.try_parse(|i| U::parse(context, i)).ok(); - component.is_some() - } - - let mut shape = None; - let mut ref_box = None; - - while parse_component(context, input, &mut shape) || - parse_component(context, input, &mut ref_box) - { - // - } - - if let Some(shp) = shape { - return Ok(to_shape(Box::new(shp), ref_box.unwrap_or_default())); - } - - match ref_box { - Some(r) => Ok(to_reference_box(r)), - None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } -} - -impl Parse for ClipPath { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(ClipPath::None); - } - - if let Ok(p) = input.try_parse(|i| Path::parse(context, i)) { - return Ok(ClipPath::Path(p)); - } - - if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) { - return Ok(ClipPath::Url(url)); - } - - parse_shape_or_box(context, input, ClipPath::Shape, ClipPath::Box) - } -} - -impl Parse for ShapeOutside { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // Need to parse this here so that `Image::parse_with_cors_anonymous` - // doesn't parse it. - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(ShapeOutside::None); - } - - if let Ok(image) = input.try_parse(|i| Image::parse_with_cors_anonymous(context, i)) { - debug_assert_ne!(image, Image::None); - return Ok(ShapeOutside::Image(image)); - } - - parse_shape_or_box(context, input, ShapeOutside::Shape, ShapeOutside::Box) - } -} - -impl Parse for BasicShape { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let function = input.expect_function()?.clone(); - input.parse_nested_block(move |i| { - (match_ignore_ascii_case! { &function, - "inset" => return InsetRect::parse_function_arguments(context, i).map(generic::BasicShape::Inset), - "circle" => return Circle::parse_function_arguments(context, i).map(generic::BasicShape::Circle), - "ellipse" => return Ellipse::parse_function_arguments(context, i).map(generic::BasicShape::Ellipse), - "polygon" => return Polygon::parse_function_arguments(context, i).map(generic::BasicShape::Polygon), - _ => Err(()) - }).map_err(|()| { - location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone())) - }) - }) - } -} - -impl Parse for InsetRect { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("inset")?; - input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) - } -} - -impl InsetRect { - /// Parse the inner function arguments of `inset()` - fn parse_function_arguments<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let rect = Rect::parse_with(context, input, LengthPercentage::parse)?; - let round = if input - .try_parse(|i| i.expect_ident_matching("round")) - .is_ok() - { - BorderRadius::parse(context, input)? - } else { - BorderRadius::zero() - }; - Ok(generic::InsetRect { rect, round }) - } -} - -impl Parse for Circle { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("circle")?; - input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) - } -} - -impl Circle { - fn parse_function_arguments<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let radius = input - .try_parse(|i| ShapeRadius::parse(context, i)) - .unwrap_or_default(); - let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() { - Position::parse(context, input)? - } else { - Position::center() - }; - - Ok(generic::Circle { radius, position }) - } -} - -impl Parse for Ellipse { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("ellipse")?; - input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) - } -} - -impl Ellipse { - fn parse_function_arguments<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let (a, b) = input - .try_parse(|i| -> Result<_, ParseError> { - Ok(( - ShapeRadius::parse(context, i)?, - ShapeRadius::parse(context, i)?, - )) - }) - .unwrap_or_default(); - let position = if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() { - Position::parse(context, input)? - } else { - Position::center() - }; - - Ok(generic::Ellipse { - semiaxis_x: a, - semiaxis_y: b, - position: position, - }) - } -} - -impl Parse for Polygon { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("polygon")?; - input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) - } -} - -impl Polygon { - /// Parse the inner arguments of a `polygon` function. - fn parse_function_arguments<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let fill = input - .try_parse(|i| -> Result<_, ParseError> { - let fill = FillRule::parse(i)?; - i.expect_comma()?; // only eat the comma if there is something before it - Ok(fill) - }) - .unwrap_or_default(); - - let coordinates = input - .parse_comma_separated(|i| { - Ok(PolygonCoord( - LengthPercentage::parse(context, i)?, - LengthPercentage::parse(context, i)?, - )) - })? - .into(); - - Ok(Polygon { fill, coordinates }) - } -} - -impl Parse for Path { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("path")?; - input.parse_nested_block(Self::parse_function_arguments) - } -} - -impl Path { - /// Parse the inner arguments of a `path` function. - fn parse_function_arguments<'i, 't>( - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::specified::svg_path::AllowEmpty; - - let fill = input - .try_parse(|i| -> Result<_, ParseError> { - let fill = FillRule::parse(i)?; - i.expect_comma()?; - Ok(fill) - }) - .unwrap_or_default(); - let path = SVGPathData::parse(input, AllowEmpty::No)?; - Ok(Path { fill, path }) - } -} diff --git a/components/style/values/specified/border.rs b/components/style/values/specified/border.rs deleted file mode 100644 index 35722bb8482..00000000000 --- a/components/style/values/specified/border.rs +++ /dev/null @@ -1,398 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values related to borders. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics::border::BorderCornerRadius as GenericBorderCornerRadius; -use crate::values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth; -use crate::values::generics::border::BorderImageSlice as GenericBorderImageSlice; -use crate::values::generics::border::BorderRadius as GenericBorderRadius; -use crate::values::generics::border::BorderSpacing as GenericBorderSpacing; -use crate::values::generics::rect::Rect; -use crate::values::generics::size::Size2D; -use crate::values::specified::length::{Length, NonNegativeLength, NonNegativeLengthPercentage}; -use crate::values::specified::{AllowQuirks, NonNegativeNumber, NonNegativeNumberOrPercentage}; -use crate::values::specified::Color; -use crate::Zero; -use app_units::Au; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, ToCss, values::SequenceWriter}; - -/// A specified value for a single side of a `border-style` property. -/// -/// The order here corresponds to the integer values from the border conflict -/// resolution rules in CSS 2.1 § 17.6.2.1. Higher values override lower values. -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - MallocSizeOf, - Ord, - Parse, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum BorderStyle { - Hidden, - None, - Inset, - Groove, - Outset, - Ridge, - Dotted, - Dashed, - Solid, - Double, -} - -impl BorderStyle { - /// Whether this border style is either none or hidden. - #[inline] - pub fn none_or_hidden(&self) -> bool { - matches!(*self, BorderStyle::None | BorderStyle::Hidden) - } -} - -/// A specified value for the `border-image-width` property. -pub type BorderImageWidth = Rect<BorderImageSideWidth>; - -/// A specified value for a single side of a `border-image-width` property. -pub type BorderImageSideWidth = - GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>; - -/// A specified value for the `border-image-slice` property. -pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>; - -/// A specified value for the `border-radius` property. -pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>; - -/// A specified value for the `border-*-radius` longhand properties. -pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>; - -/// A specified value for the `border-spacing` longhand properties. -pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>; - -impl BorderImageSlice { - /// Returns the `100%` value. - #[inline] - pub fn hundred_percent() -> Self { - GenericBorderImageSlice { - offsets: Rect::all(NonNegativeNumberOrPercentage::hundred_percent()), - fill: false, - } - } -} - -/// https://drafts.csswg.org/css-backgrounds-3/#typedef-line-width -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum LineWidth { - /// `thin` - Thin, - /// `medium` - Medium, - /// `thick` - Thick, - /// `<length>` - Length(NonNegativeLength), -} - -impl LineWidth { - /// Returns the `0px` value. - #[inline] - pub fn zero() -> Self { - Self::Length(NonNegativeLength::zero()) - } - - fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - if let Ok(length) = - input.try_parse(|i| NonNegativeLength::parse_quirky(context, i, allow_quirks)) - { - return Ok(Self::Length(length)); - } - Ok(try_match_ident_ignore_ascii_case! { input, - "thin" => Self::Thin, - "medium" => Self::Medium, - "thick" => Self::Thick, - }) - } -} - -impl Parse for LineWidth { - fn parse<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl ToComputedValue for LineWidth { - type ComputedValue = app_units::Au; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - // https://drafts.csswg.org/css-backgrounds-3/#line-width - Self::Thin => Au::from_px(1), - Self::Medium => Au::from_px(3), - Self::Thick => Au::from_px(5), - Self::Length(ref length) => Au::from_f32_px(length.to_computed_value(context).px()), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self::Length(NonNegativeLength::from_px(computed.to_f32_px())) - } -} - -/// A specified value for a single side of the `border-width` property. The difference between this -/// and LineWidth is whether we snap to device pixels or not. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct BorderSideWidth(LineWidth); - -impl BorderSideWidth { - /// Returns the `medium` value. - pub fn medium() -> Self { - Self(LineWidth::Medium) - } - - /// Returns a bare px value from the argument. - pub fn from_px(px: f32) -> Self { - Self(LineWidth::Length(Length::from_px(px).into())) - } - - /// Parses, with quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Ok(Self(LineWidth::parse_quirky(context, input, allow_quirks)?)) - } -} - -impl Parse for BorderSideWidth { - fn parse<'i>( - context: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl ToComputedValue for BorderSideWidth { - type ComputedValue = app_units::Au; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - let width = self.0.to_computed_value(context); - // Round `width` down to the nearest device pixel, but any non-zero value that would round - // down to zero is clamped to 1 device pixel. - if width == Au(0) { - return width; - } - - let au_per_dev_px = context.device().app_units_per_device_pixel(); - std::cmp::max( - Au(au_per_dev_px), - Au(width.0 / au_per_dev_px * au_per_dev_px), - ) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self(LineWidth::from_computed_value(computed)) - } -} - -impl BorderImageSideWidth { - /// Returns `1`. - #[inline] - pub fn one() -> Self { - GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.)) - } -} - -impl Parse for BorderImageSlice { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok(); - let offsets = Rect::parse_with(context, input, NonNegativeNumberOrPercentage::parse)?; - if !fill { - fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok(); - } - Ok(GenericBorderImageSlice { offsets, fill }) - } -} - -impl Parse for BorderRadius { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let widths = Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?; - let heights = if input.try_parse(|i| i.expect_delim('/')).is_ok() { - Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)? - } else { - widths.clone() - }; - - Ok(GenericBorderRadius { - top_left: BorderCornerRadius::new(widths.0, heights.0), - top_right: BorderCornerRadius::new(widths.1, heights.1), - bottom_right: BorderCornerRadius::new(widths.2, heights.2), - bottom_left: BorderCornerRadius::new(widths.3, heights.3), - }) - } -} - -impl Parse for BorderCornerRadius { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Size2D::parse_with(context, input, NonNegativeLengthPercentage::parse) - .map(GenericBorderCornerRadius) - } -} - -impl Parse for BorderSpacing { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Size2D::parse_with(context, input, |context, input| { - NonNegativeLength::parse_quirky(context, input, AllowQuirks::Yes) - }) - .map(GenericBorderSpacing) - } -} - -/// A single border-image-repeat keyword. -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum BorderImageRepeatKeyword { - Stretch, - Repeat, - Round, - Space, -} - -/// The specified value for the `border-image-repeat` property. -/// -/// https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword); - -impl ToCss for BorderImageRepeat { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.0.to_css(dest)?; - if self.0 != self.1 { - dest.write_char(' ')?; - self.1.to_css(dest)?; - } - Ok(()) - } -} - -impl BorderImageRepeat { - /// Returns the `stretch` value. - #[inline] - pub fn stretch() -> Self { - BorderImageRepeat( - BorderImageRepeatKeyword::Stretch, - BorderImageRepeatKeyword::Stretch, - ) - } -} - -impl Parse for BorderImageRepeat { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let horizontal = BorderImageRepeatKeyword::parse(input)?; - let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok(); - Ok(BorderImageRepeat( - horizontal, - vertical.unwrap_or(horizontal), - )) - } -} - -/// Serializes a border shorthand value composed of width/style/color. -pub fn serialize_directional_border<W>( - dest: &mut CssWriter<W>, - width: &BorderSideWidth, - style: &BorderStyle, - color: &Color, -) -> fmt::Result -where - W: Write, -{ - let has_style = *style != BorderStyle::None; - let has_color = *color != Color::CurrentColor; - let has_width = *width != BorderSideWidth::medium(); - if !has_style && !has_color && !has_width { - return width.to_css(dest) - } - let mut writer = SequenceWriter::new(dest, " "); - if has_width { - writer.item(width)?; - } - if has_style { - writer.item(style)?; - } - if has_color { - writer.item(color)?; - } - Ok(()) -} diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs deleted file mode 100644 index 7c41f1ead25..00000000000 --- a/components/style/values/specified/box.rs +++ /dev/null @@ -1,1879 +0,0 @@ -/* 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/. */ - -//! Specified types for box properties. - -use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] -use crate::properties::{LonghandId, PropertyDeclarationId, PropertyId}; -use crate::values::generics::box_::{ - GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, - VerticalAlignKeyword, -}; -use crate::values::specified::length::{LengthPercentage, NonNegativeLength}; -use crate::values::specified::{AllowQuirks, Integer}; -use crate::values::CustomIdent; -use cssparser::Parser; -use num_traits::FromPrimitive; -use std::fmt::{self, Debug, Formatter, Write}; -use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; -use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -#[cfg(not(feature = "servo"))] -fn flexbox_enabled() -> bool { - true -} - -#[cfg(feature = "servo")] -fn flexbox_enabled() -> bool { - style_config::get_bool("layout.flexbox.enabled") -} - -/// Defines an element’s display type, which consists of -/// the two basic qualities of how an element generates boxes -/// <https://drafts.csswg.org/css-display/#propdef-display> -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)] -#[repr(u8)] -pub enum DisplayOutside { - None = 0, - Inline, - Block, - TableCaption, - InternalTable, - #[cfg(feature = "gecko")] - InternalRuby, -} - -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, Eq, FromPrimitive, Hash, MallocSizeOf, PartialEq, ToCss, ToShmem)] -#[repr(u8)] -pub enum DisplayInside { - None = 0, - Contents, - Flow, - FlowRoot, - Flex, - #[cfg(feature = "gecko")] - Grid, - Table, - TableRowGroup, - TableColumn, - TableColumnGroup, - TableHeaderGroup, - TableFooterGroup, - TableRow, - TableCell, - #[cfg(feature = "gecko")] - Ruby, - #[cfg(feature = "gecko")] - RubyBase, - #[cfg(feature = "gecko")] - RubyBaseContainer, - #[cfg(feature = "gecko")] - RubyText, - #[cfg(feature = "gecko")] - RubyTextContainer, - #[cfg(feature = "gecko")] - WebkitBox, -} - -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Eq, - FromPrimitive, - Hash, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct Display(u16); - -/// Gecko-only impl block for Display (shared stuff later in this file): -#[allow(missing_docs)] -#[allow(non_upper_case_globals)] -impl Display { - // Our u16 bits are used as follows: LOOOOOOOIIIIIIII - const LIST_ITEM_BIT: u16 = 0x8000; //^ - const DISPLAY_OUTSIDE_BITS: u16 = 7; // ^^^^^^^ - const DISPLAY_INSIDE_BITS: u16 = 8; // ^^^^^^^^ - - /// https://drafts.csswg.org/css-display/#the-display-properties - pub const None: Self = Self::new(DisplayOutside::None, DisplayInside::None); - pub const Contents: Self = Self::new(DisplayOutside::None, DisplayInside::Contents); - pub const Inline: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flow); - pub const InlineBlock: Self = Self::new(DisplayOutside::Inline, DisplayInside::FlowRoot); - pub const Block: Self = Self::new(DisplayOutside::Block, DisplayInside::Flow); - #[cfg(feature = "gecko")] - pub const FlowRoot: Self = Self::new(DisplayOutside::Block, DisplayInside::FlowRoot); - pub const Flex: Self = Self::new(DisplayOutside::Block, DisplayInside::Flex); - pub const InlineFlex: Self = Self::new(DisplayOutside::Inline, DisplayInside::Flex); - #[cfg(feature = "gecko")] - pub const Grid: Self = Self::new(DisplayOutside::Block, DisplayInside::Grid); - #[cfg(feature = "gecko")] - pub const InlineGrid: Self = Self::new(DisplayOutside::Inline, DisplayInside::Grid); - pub const Table: Self = Self::new(DisplayOutside::Block, DisplayInside::Table); - pub const InlineTable: Self = Self::new(DisplayOutside::Inline, DisplayInside::Table); - pub const TableCaption: Self = Self::new(DisplayOutside::TableCaption, DisplayInside::Flow); - #[cfg(feature = "gecko")] - pub const Ruby: Self = Self::new(DisplayOutside::Inline, DisplayInside::Ruby); - #[cfg(feature = "gecko")] - pub const WebkitBox: Self = Self::new(DisplayOutside::Block, DisplayInside::WebkitBox); - #[cfg(feature = "gecko")] - pub const WebkitInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::WebkitBox); - - // Internal table boxes. - - pub const TableRowGroup: Self = - Self::new(DisplayOutside::InternalTable, DisplayInside::TableRowGroup); - - pub const TableHeaderGroup: Self = Self::new( - DisplayOutside::InternalTable, - DisplayInside::TableHeaderGroup, - ); - - pub const TableFooterGroup: Self = Self::new( - DisplayOutside::InternalTable, - DisplayInside::TableFooterGroup, - ); - - pub const TableColumn: Self = - Self::new(DisplayOutside::InternalTable, DisplayInside::TableColumn); - - pub const TableColumnGroup: Self = Self::new( - DisplayOutside::InternalTable, - DisplayInside::TableColumnGroup, - ); - - pub const TableRow: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableRow); - - pub const TableCell: Self = Self::new(DisplayOutside::InternalTable, DisplayInside::TableCell); - - /// Internal ruby boxes. - #[cfg(feature = "gecko")] - pub const RubyBase: Self = Self::new(DisplayOutside::InternalRuby, DisplayInside::RubyBase); - #[cfg(feature = "gecko")] - pub const RubyBaseContainer: Self = Self::new( - DisplayOutside::InternalRuby, - DisplayInside::RubyBaseContainer, - ); - #[cfg(feature = "gecko")] - pub const RubyText: Self = Self::new(DisplayOutside::InternalRuby, DisplayInside::RubyText); - #[cfg(feature = "gecko")] - pub const RubyTextContainer: Self = Self::new( - DisplayOutside::InternalRuby, - DisplayInside::RubyTextContainer, - ); - - /// Make a raw display value from <display-outside> and <display-inside> values. - #[inline] - const fn new(outside: DisplayOutside, inside: DisplayInside) -> Self { - let o: u16 = ((outside as u8) as u16) << Self::DISPLAY_INSIDE_BITS; - let i: u16 = (inside as u8) as u16; - Self(o | i) - } - - /// Make a display enum value from <display-outside> and <display-inside> values. - #[inline] - fn from3(outside: DisplayOutside, inside: DisplayInside, list_item: bool) -> Self { - let v = Self::new(outside, inside); - if !list_item { - return v; - } - Self(v.0 | Self::LIST_ITEM_BIT) - } - - /// Accessor for the <display-inside> value. - #[inline] - pub fn inside(&self) -> DisplayInside { - DisplayInside::from_u16(self.0 & ((1 << Self::DISPLAY_INSIDE_BITS) - 1)).unwrap() - } - - /// Accessor for the <display-outside> value. - #[inline] - pub fn outside(&self) -> DisplayOutside { - DisplayOutside::from_u16( - (self.0 >> Self::DISPLAY_INSIDE_BITS) & ((1 << Self::DISPLAY_OUTSIDE_BITS) - 1), - ) - .unwrap() - } - - /// Returns the raw underlying u16 value. - #[inline] - pub const fn to_u16(&self) -> u16 { - self.0 - } - - /// Whether this is `display: inline` (or `inline list-item`). - #[inline] - pub fn is_inline_flow(&self) -> bool { - self.outside() == DisplayOutside::Inline && self.inside() == DisplayInside::Flow - } - - /// Returns whether this `display` value is some kind of list-item. - #[inline] - pub const fn is_list_item(&self) -> bool { - (self.0 & Self::LIST_ITEM_BIT) != 0 - } - - /// Returns whether this `display` value is a ruby level container. - pub fn is_ruby_level_container(&self) -> bool { - match *self { - #[cfg(feature = "gecko")] - Display::RubyBaseContainer | Display::RubyTextContainer => true, - _ => false, - } - } - - /// Returns whether this `display` value is one of the types for ruby. - pub fn is_ruby_type(&self) -> bool { - match self.inside() { - #[cfg(feature = "gecko")] - DisplayInside::Ruby | - DisplayInside::RubyBase | - DisplayInside::RubyText | - DisplayInside::RubyBaseContainer | - DisplayInside::RubyTextContainer => true, - _ => false, - } - } -} - -/// Shared Display impl for both Gecko and Servo. -#[allow(non_upper_case_globals)] -impl Display { - /// The initial display value. - #[inline] - pub fn inline() -> Self { - Display::Inline - } - - /// <https://drafts.csswg.org/css2/visuren.html#x13> - #[cfg(feature = "servo")] - #[inline] - pub fn is_atomic_inline_level(&self) -> bool { - match *self { - Display::InlineBlock | Display::InlineFlex => true, - Display::InlineTable => true, - _ => false, - } - } - - /// Returns whether this `display` value is the display of a flex or - /// grid container. - /// - /// This is used to implement various style fixups. - pub fn is_item_container(&self) -> bool { - match self.inside() { - DisplayInside::Flex => true, - #[cfg(feature = "gecko")] - DisplayInside::Grid => true, - _ => false, - } - } - - /// Returns whether an element with this display type is a line - /// participant, which means it may lay its children on the same - /// line as itself. - pub fn is_line_participant(&self) -> bool { - if self.is_inline_flow() { - return true; - } - match *self { - #[cfg(feature = "gecko")] - Display::Contents | Display::Ruby | Display::RubyBaseContainer => true, - _ => false, - } - } - - /// Convert this display into an equivalent block display. - /// - /// Also used for :root style adjustments. - pub fn equivalent_block_display(&self, _is_root_element: bool) -> Self { - { - // Special handling for `contents` and `list-item`s on the root element. - if _is_root_element && (self.is_contents() || self.is_list_item()) { - return Display::Block; - } - } - - match self.outside() { - DisplayOutside::Inline => { - let inside = match self.inside() { - // `inline-block` blockifies to `block` rather than - // `flow-root`, for legacy reasons. - DisplayInside::FlowRoot => DisplayInside::Flow, - inside => inside, - }; - Display::from3(DisplayOutside::Block, inside, self.is_list_item()) - }, - DisplayOutside::Block | DisplayOutside::None => *self, - _ => Display::Block, - } - } - - /// Convert this display into an equivalent inline-outside display. - /// https://drafts.csswg.org/css-display/#inlinify - #[cfg(feature = "gecko")] - pub fn inlinify(&self) -> Self { - match self.outside() { - DisplayOutside::Block => { - let inside = match self.inside() { - // `display: block` inlinifies to `display: inline-block`, - // rather than `inline`, for legacy reasons. - DisplayInside::Flow => DisplayInside::FlowRoot, - inside => inside, - }; - Display::from3(DisplayOutside::Inline, inside, self.is_list_item()) - }, - _ => *self, - } - } - - /// Returns true if the value is `Contents` - #[inline] - pub fn is_contents(&self) -> bool { - match *self { - Display::Contents => true, - _ => false, - } - } - - /// Returns true if the value is `None` - #[inline] - pub fn is_none(&self) -> bool { - *self == Display::None - } -} - -impl ToCss for Display { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - let outside = self.outside(); - let inside = self.inside(); - match *self { - Display::Block | Display::Inline => outside.to_css(dest), - Display::InlineBlock => dest.write_str("inline-block"), - #[cfg(feature = "gecko")] - Display::WebkitInlineBox => dest.write_str("-webkit-inline-box"), - Display::TableCaption => dest.write_str("table-caption"), - _ => match (outside, inside) { - #[cfg(feature = "gecko")] - (DisplayOutside::Inline, DisplayInside::Grid) => dest.write_str("inline-grid"), - (DisplayOutside::Inline, DisplayInside::Flex) => dest.write_str("inline-flex"), - (DisplayOutside::Inline, DisplayInside::Table) => dest.write_str("inline-table"), - #[cfg(feature = "gecko")] - (DisplayOutside::Block, DisplayInside::Ruby) => dest.write_str("block ruby"), - (_, inside) => { - if self.is_list_item() { - if outside != DisplayOutside::Block { - outside.to_css(dest)?; - dest.write_char(' ')?; - } - if inside != DisplayInside::Flow { - inside.to_css(dest)?; - dest.write_char(' ')?; - } - dest.write_str("list-item") - } else { - inside.to_css(dest) - } - }, - }, - } - } -} - -/// <display-inside> = flow | flow-root | table | flex | grid | ruby -/// https://drafts.csswg.org/css-display/#typedef-display-inside -fn parse_display_inside<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<DisplayInside, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "flow" => DisplayInside::Flow, - "flex" if flexbox_enabled() => DisplayInside::Flex, - "flow-root" => DisplayInside::FlowRoot, - "table" => DisplayInside::Table, - #[cfg(feature = "gecko")] - "grid" => DisplayInside::Grid, - #[cfg(feature = "gecko")] - "ruby" => DisplayInside::Ruby, - }) -} - -/// <display-outside> = block | inline | run-in -/// https://drafts.csswg.org/css-display/#typedef-display-outside -fn parse_display_outside<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<DisplayOutside, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "block" => DisplayOutside::Block, - "inline" => DisplayOutside::Inline, - // FIXME(bug 2056): not supported in layout yet: - //"run-in" => DisplayOutside::RunIn, - }) -} - -/// (flow | flow-root)? -fn parse_display_inside_for_list_item<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<DisplayInside, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "flow" => DisplayInside::Flow, - #[cfg(feature = "gecko")] - "flow-root" => DisplayInside::FlowRoot, - }) -} -/// Test a <display-inside> Result for same values as above. -fn is_valid_inside_for_list_item<'i>(inside: &Result<DisplayInside, ParseError<'i>>) -> bool { - match inside { - Ok(DisplayInside::Flow) => true, - #[cfg(feature = "gecko")] - Ok(DisplayInside::FlowRoot) => true, - _ => false, - } -} - -/// Parse `list-item`. -fn parse_list_item<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), ParseError<'i>> { - Ok(input.expect_ident_matching("list-item")?) -} - -impl Parse for Display { - #[allow(unused)] // `context` isn't used for servo-2020 for now - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Display, ParseError<'i>> { - // Parse all combinations of <display-inside/outside>? and `list-item`? first. - let mut got_list_item = input.try_parse(parse_list_item).is_ok(); - let mut inside = if got_list_item { - input.try_parse(parse_display_inside_for_list_item) - } else { - input.try_parse(parse_display_inside) - }; - // <display-listitem> = <display-outside>? && [ flow | flow-root ]? && list-item - // https://drafts.csswg.org/css-display/#typedef-display-listitem - if !got_list_item && is_valid_inside_for_list_item(&inside) { - got_list_item = input.try_parse(parse_list_item).is_ok(); - } - let outside = input.try_parse(parse_display_outside); - if outside.is_ok() { - if !got_list_item && (inside.is_err() || is_valid_inside_for_list_item(&inside)) { - got_list_item = input.try_parse(parse_list_item).is_ok(); - } - if inside.is_err() { - inside = if got_list_item { - input.try_parse(parse_display_inside_for_list_item) - } else { - input.try_parse(parse_display_inside) - }; - if !got_list_item && is_valid_inside_for_list_item(&inside) { - got_list_item = input.try_parse(parse_list_item).is_ok(); - } - } - } - if got_list_item || inside.is_ok() || outside.is_ok() { - let inside = inside.unwrap_or(DisplayInside::Flow); - let outside = outside.unwrap_or(match inside { - // "If <display-outside> is omitted, the element’s outside display type - // defaults to block — except for ruby, which defaults to inline." - // https://drafts.csswg.org/css-display/#inside-model - #[cfg(feature = "gecko")] - DisplayInside::Ruby => DisplayOutside::Inline, - _ => DisplayOutside::Block, - }); - return Ok(Display::from3(outside, inside, got_list_item)); - } - - // Now parse the single-keyword `display` values. - Ok(try_match_ident_ignore_ascii_case! { input, - "none" => Display::None, - "contents" => Display::Contents, - "inline-block" => Display::InlineBlock, - "inline-table" => Display::InlineTable, - "-webkit-flex" if flexbox_enabled() => Display::Flex, - "inline-flex" | "-webkit-inline-flex" if flexbox_enabled() => Display::InlineFlex, - #[cfg(feature = "gecko")] - "inline-grid" => Display::InlineGrid, - "table-caption" => Display::TableCaption, - "table-row-group" => Display::TableRowGroup, - "table-header-group" => Display::TableHeaderGroup, - "table-footer-group" => Display::TableFooterGroup, - "table-column" => Display::TableColumn, - "table-column-group" => Display::TableColumnGroup, - "table-row" => Display::TableRow, - "table-cell" => Display::TableCell, - #[cfg(feature = "gecko")] - "ruby-base" => Display::RubyBase, - #[cfg(feature = "gecko")] - "ruby-base-container" => Display::RubyBaseContainer, - #[cfg(feature = "gecko")] - "ruby-text" => Display::RubyText, - #[cfg(feature = "gecko")] - "ruby-text-container" => Display::RubyTextContainer, - #[cfg(feature = "gecko")] - "-webkit-box" => Display::WebkitBox, - #[cfg(feature = "gecko")] - "-webkit-inline-box" => Display::WebkitInlineBox, - }) - } -} - -impl SpecifiedValueInfo for Display { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&[ - "block", - "contents", - "flex", - "flow-root", - "flow-root list-item", - "grid", - "inline", - "inline-block", - "inline-flex", - "inline-grid", - "inline-table", - "inline list-item", - "inline flow-root list-item", - "list-item", - "none", - "block ruby", - "ruby", - "ruby-base", - "ruby-base-container", - "ruby-text", - "ruby-text-container", - "table", - "table-caption", - "table-cell", - "table-column", - "table-column-group", - "table-footer-group", - "table-header-group", - "table-row", - "table-row-group", - "-webkit-box", - "-webkit-inline-box", - ]); - } -} - -impl Debug for Display { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Display") - .field("List Item", &self.is_list_item()) - .field("Inside", &self.inside()) - .field("Outside", &self.outside()) - .finish() - } -} - -/// A specified value for the `contain-intrinsic-size` property. -pub type ContainIntrinsicSize = GenericContainIntrinsicSize<NonNegativeLength>; - -/// A specified value for the `line-clamp` property. -pub type LineClamp = GenericLineClamp<Integer>; - -/// A specified value for the `vertical-align` property. -pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>; - -impl Parse for VerticalAlign { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(lp) = - input.try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes)) - { - return Ok(GenericVerticalAlign::Length(lp)); - } - - Ok(GenericVerticalAlign::Keyword(VerticalAlignKeyword::parse( - input, - )?)) - } -} - -/// A specified value for the `baseline-source` property. -/// https://drafts.csswg.org/css-inline-3/#baseline-source -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToShmem, - ToComputedValue, - ToResolvedValue, -)] -#[repr(u8)] -pub enum BaselineSource { - /// `Last` for `inline-block`, `First` otherwise. - Auto, - /// Use first baseline for alignment. - First, - /// Use last baseline for alignment. - Last, -} - -/// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ScrollSnapAxis { - X, - Y, - Block, - Inline, - Both, -} - -/// https://drafts.csswg.org/css-scroll-snap-1/#snap-strictness -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ScrollSnapStrictness { - #[css(skip)] - None, // Used to represent scroll-snap-type: none. It's not parsed. - Mandatory, - Proximity, -} - -/// https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-type -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct ScrollSnapType { - axis: ScrollSnapAxis, - strictness: ScrollSnapStrictness, -} - -impl ScrollSnapType { - /// Returns `none`. - #[inline] - pub fn none() -> Self { - Self { - axis: ScrollSnapAxis::Both, - strictness: ScrollSnapStrictness::None, - } - } -} - -impl Parse for ScrollSnapType { - /// none | [ x | y | block | inline | both ] [ mandatory | proximity ]? - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(ScrollSnapType::none()); - } - - let axis = ScrollSnapAxis::parse(input)?; - let strictness = input - .try_parse(ScrollSnapStrictness::parse) - .unwrap_or(ScrollSnapStrictness::Proximity); - Ok(Self { axis, strictness }) - } -} - -impl ToCss for ScrollSnapType { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.strictness == ScrollSnapStrictness::None { - return dest.write_str("none"); - } - self.axis.to_css(dest)?; - if self.strictness != ScrollSnapStrictness::Proximity { - dest.write_char(' ')?; - self.strictness.to_css(dest)?; - } - Ok(()) - } -} - -/// Specified value of scroll-snap-align keyword value. -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ScrollSnapAlignKeyword { - None, - Start, - End, - Center, -} - -/// https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-align -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct ScrollSnapAlign { - block: ScrollSnapAlignKeyword, - inline: ScrollSnapAlignKeyword, -} - -impl ScrollSnapAlign { - /// Returns `none`. - #[inline] - pub fn none() -> Self { - ScrollSnapAlign { - block: ScrollSnapAlignKeyword::None, - inline: ScrollSnapAlignKeyword::None, - } - } -} - -impl Parse for ScrollSnapAlign { - /// [ none | start | end | center ]{1,2} - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<ScrollSnapAlign, ParseError<'i>> { - let block = ScrollSnapAlignKeyword::parse(input)?; - let inline = input - .try_parse(ScrollSnapAlignKeyword::parse) - .unwrap_or(block); - Ok(ScrollSnapAlign { block, inline }) - } -} - -impl ToCss for ScrollSnapAlign { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.block.to_css(dest)?; - if self.block != self.inline { - dest.write_char(' ')?; - self.inline.to_css(dest)?; - } - Ok(()) - } -} - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ScrollSnapStop { - Normal, - Always, -} - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum OverscrollBehavior { - Auto, - Contain, - None, -} - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum OverflowAnchor { - Auto, - None, -} - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum OverflowClipBox { - PaddingBox, - ContentBox, -} - -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[css(comma)] -#[repr(C)] -/// Provides a rendering hint to the user agent, stating what kinds of changes -/// the author expects to perform on the element. -/// -/// `auto` is represented by an empty `features` list. -/// -/// <https://drafts.csswg.org/css-will-change/#will-change> -pub struct WillChange { - /// The features that are supposed to change. - /// - /// TODO(emilio): Consider using ArcSlice since we just clone them from the - /// specified value? That'd save an allocation, which could be worth it. - #[css(iterable, if_empty = "auto")] - features: crate::OwnedSlice<CustomIdent>, - /// A bitfield with the kind of change that the value will create, based - /// on the above field. - #[css(skip)] - bits: WillChangeBits, -} - -impl WillChange { - #[inline] - /// Get default value of `will-change` as `auto` - pub fn auto() -> Self { - Self::default() - } -} - -bitflags! { - /// The change bits that we care about. - #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[repr(C)] - pub struct WillChangeBits: u16 { - /// Whether a property which can create a stacking context **on any - /// box** will change. - const STACKING_CONTEXT_UNCONDITIONAL = 1 << 0; - /// Whether `transform` or related properties will change. - const TRANSFORM = 1 << 1; - /// Whether `scroll-position` will change. - const SCROLL = 1 << 2; - /// Whether `contain` will change. - const CONTAIN = 1 << 3; - /// Whether `opacity` will change. - const OPACITY = 1 << 4; - /// Whether `perspective` will change. - const PERSPECTIVE = 1 << 5; - /// Whether `z-index` will change. - const Z_INDEX = 1 << 6; - /// Whether any property which creates a containing block for non-svg - /// text frames will change. - const FIXPOS_CB_NON_SVG = 1 << 7; - /// Whether the position property will change. - const POSITION = 1 << 8; - } -} - -#[cfg(feature = "gecko")] -fn change_bits_for_longhand(longhand: LonghandId) -> WillChangeBits { - match longhand { - LonghandId::Opacity => WillChangeBits::OPACITY, - LonghandId::Contain => WillChangeBits::CONTAIN, - LonghandId::Perspective => WillChangeBits::PERSPECTIVE, - LonghandId::Position => { - WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::POSITION - }, - LonghandId::ZIndex => WillChangeBits::Z_INDEX, - LonghandId::Transform | - LonghandId::TransformStyle | - LonghandId::Translate | - LonghandId::Rotate | - LonghandId::Scale | - LonghandId::OffsetPath => WillChangeBits::TRANSFORM, - LonghandId::BackdropFilter | LonghandId::Filter => { - WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::FIXPOS_CB_NON_SVG - }, - LonghandId::MixBlendMode | - LonghandId::Isolation | - LonghandId::MaskImage | - LonghandId::ClipPath => WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL, - _ => WillChangeBits::empty(), - } -} - -#[cfg(feature = "gecko")] -fn change_bits_for_maybe_property(ident: &str, context: &ParserContext) -> WillChangeBits { - let id = match PropertyId::parse_ignoring_rule_type(ident, context) { - Ok(id) => id, - Err(..) => return WillChangeBits::empty(), - }; - - match id.as_shorthand() { - Ok(shorthand) => shorthand - .longhands() - .fold(WillChangeBits::empty(), |flags, p| { - flags | change_bits_for_longhand(p) - }), - Err(PropertyDeclarationId::Longhand(longhand)) => change_bits_for_longhand(longhand), - Err(PropertyDeclarationId::Custom(..)) => WillChangeBits::empty(), - } -} - -#[cfg(feature = "gecko")] -impl Parse for WillChange { - /// auto | <animateable-feature># - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("auto")) - .is_ok() - { - return Ok(Self::default()); - } - - let mut bits = WillChangeBits::empty(); - let custom_idents = input.parse_comma_separated(|i| { - let location = i.current_source_location(); - let parser_ident = i.expect_ident()?; - let ident = CustomIdent::from_ident( - location, - parser_ident, - &["will-change", "none", "all", "auto"], - )?; - - if context.in_ua_sheet() && ident.0 == atom!("-moz-fixed-pos-containing-block") { - bits |= WillChangeBits::FIXPOS_CB_NON_SVG; - } else if ident.0 == atom!("scroll-position") { - bits |= WillChangeBits::SCROLL; - } else { - bits |= change_bits_for_maybe_property(&parser_ident, context); - } - Ok(ident) - })?; - - Ok(Self { - features: custom_idents.into(), - bits, - }) - } -} - -bitflags! { - /// Values for the `touch-action` property. - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)] - #[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))] - #[repr(C)] - pub struct TouchAction: u8 { - /// `none` variant - const NONE = 1 << 0; - /// `auto` variant - const AUTO = 1 << 1; - /// `pan-x` variant - const PAN_X = 1 << 2; - /// `pan-y` variant - const PAN_Y = 1 << 3; - /// `manipulation` variant - const MANIPULATION = 1 << 4; - /// `pinch-zoom` variant - const PINCH_ZOOM = 1 << 5; - } -} - -impl TouchAction { - #[inline] - /// Get default `touch-action` as `auto` - pub fn auto() -> TouchAction { - TouchAction::AUTO - } -} - -bitflags! { - #[derive(MallocSizeOf, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] - #[css(bitflags(single = "none,strict,content", mixed="size,layout,style,paint,inline-size", overlapping_bits))] - #[repr(C)] - /// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property - pub struct Contain: u8 { - /// `none` variant, just for convenience. - const NONE = 0; - /// `inline-size` variant, turns on single-axis inline size containment - const INLINE_SIZE = 1 << 0; - /// `block-size` variant, turns on single-axis block size containment, internal only - const BLOCK_SIZE = 1 << 1; - /// `layout` variant, turns on layout containment - const LAYOUT = 1 << 2; - /// `style` variant, turns on style containment - const STYLE = 1 << 3; - /// `paint` variant, turns on paint containment - const PAINT = 1 << 4; - /// 'size' variant, turns on size containment - const SIZE = 1 << 5 | Contain::INLINE_SIZE.bits | Contain::BLOCK_SIZE.bits; - /// `content` variant, turns on layout and paint containment - const CONTENT = 1 << 6 | Contain::LAYOUT.bits | Contain::STYLE.bits | Contain::PAINT.bits; - /// `strict` variant, turns on all types of containment - const STRICT = 1 << 7 | Contain::LAYOUT.bits | Contain::STYLE.bits | Contain::PAINT.bits | Contain::SIZE.bits; - } -} - -impl Parse for ContainIntrinsicSize { - /// none | <length> | auto <length> - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(l) = input.try_parse(|i| NonNegativeLength::parse(context, i)) { - return Ok(Self::Length(l)); - } - - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - let l = NonNegativeLength::parse(context, input)?; - return Ok(Self::AutoLength(l)); - } - - input.expect_ident_matching("none")?; - Ok(Self::None) - } -} - -impl Parse for LineClamp { - /// none | <positive-integer> - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(i) = - input.try_parse(|i| crate::values::specified::PositiveInteger::parse(context, i)) - { - return Ok(Self(i.0)); - } - input.expect_ident_matching("none")?; - Ok(Self::none()) - } -} - -/// https://drafts.csswg.org/css-contain-2/#content-visibility -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ContentVisibility { - /// `auto` variant, the element turns on layout containment, style containment, and paint - /// containment. In addition, if the element is not relevant to the user (such as by being - /// offscreen) it also skips its content - Auto, - /// `hidden` variant, the element skips its content - Hidden, - /// 'visible' variant, no effect - Visible, -} - -#[derive( - Clone, - Copy, - Debug, - PartialEq, - Eq, - MallocSizeOf, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - Parse, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -#[allow(missing_docs)] -/// https://drafts.csswg.org/css-contain-3/#container-type -pub enum ContainerType { - /// The `normal` variant. - Normal, - /// The `inline-size` variant. - InlineSize, - /// The `size` variant. - Size, -} - -impl ContainerType { - /// Is this container-type: normal? - pub fn is_normal(self) -> bool { - self == Self::Normal - } - - /// Is this type containing size in any way? - pub fn is_size_container_type(self) -> bool { - !self.is_normal() - } -} - -/// https://drafts.csswg.org/css-contain-3/#container-name -#[repr(transparent)] -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub struct ContainerName(#[css(iterable, if_empty = "none")] pub crate::OwnedSlice<CustomIdent>); - -impl ContainerName { - /// Return the `none` value. - pub fn none() -> Self { - Self(Default::default()) - } - - /// Returns whether this is the `none` value. - pub fn is_none(&self) -> bool { - self.0.is_empty() - } - - fn parse_internal<'i>( - input: &mut Parser<'i, '_>, - for_query: bool, - ) -> Result<Self, ParseError<'i>> { - let mut idents = vec![]; - let location = input.current_source_location(); - let first = input.expect_ident()?; - if !for_query && first.eq_ignore_ascii_case("none") { - return Ok(Self::none()); - } - const DISALLOWED_CONTAINER_NAMES: &'static [&'static str] = &["none", "not", "or", "and"]; - idents.push(CustomIdent::from_ident( - location, - first, - DISALLOWED_CONTAINER_NAMES, - )?); - if !for_query { - while let Ok(name) = input.try_parse(|input| { - let ident = input.expect_ident()?; - CustomIdent::from_ident(location, &ident, DISALLOWED_CONTAINER_NAMES) - }) { - idents.push(name); - } - } - Ok(ContainerName(idents.into())) - } - - /// https://github.com/w3c/csswg-drafts/issues/7203 - /// Only a single name allowed in @container rule. - /// Disallow none for container-name in @container rule. - pub fn parse_for_query<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(input, /* for_query = */ true) - } -} - -impl Parse for ContainerName { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(input, /* for_query = */ false) - } -} - -/// A specified value for the `perspective` property. -pub type Perspective = GenericPerspective<NonNegativeLength>; - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -/// https://drafts.csswg.org/css-box/#propdef-float -pub enum Float { - Left, - Right, - None, - // https://drafts.csswg.org/css-logical-props/#float-clear - InlineStart, - InlineEnd, -} - -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -/// https://drafts.csswg.org/css2/#propdef-clear -pub enum Clear { - None, - Left, - Right, - Both, - // https://drafts.csswg.org/css-logical-props/#float-clear - InlineStart, - InlineEnd, -} - -/// https://drafts.csswg.org/css-ui/#propdef-resize -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -pub enum Resize { - None, - Both, - Horizontal, - Vertical, - // https://drafts.csswg.org/css-logical-1/#resize - Inline, - Block, -} - -/// The value for the `appearance` property. -/// -/// https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-appearance -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum Appearance { - /// No appearance at all. - None, - /// Default appearance for the element. - /// - /// This value doesn't make sense for -moz-default-appearance, but we don't bother to guard - /// against parsing it. - Auto, - /// A searchfield. - Searchfield, - /// A multi-line text field, e.g. HTML <textarea>. - Textarea, - /// A checkbox element. - Checkbox, - /// A radio element within a radio group. - Radio, - /// A dropdown list. - Menulist, - /// List boxes. - Listbox, - /// A horizontal meter bar. - Meter, - /// A horizontal progress bar. - ProgressBar, - /// A typical dialog button. - Button, - /// A single-line text field, e.g. HTML <input type=text>. - Textfield, - /// The dropdown button(s) that open up a dropdown list. - MenulistButton, - /// Various arrows that go in buttons - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ButtonArrowDown, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ButtonArrowNext, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ButtonArrowPrevious, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ButtonArrowUp, - /// A dual toolbar button (e.g., a Back button with a dropdown) - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Dualbutton, - /// A groupbox. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Groupbox, - /// Menu Bar background - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menubar, - /// <menu> and <menuitem> appearances - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menuitem, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Checkmenuitem, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Radiomenuitem, - /// For text on non-iconic menuitems only - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menuitemtext, - /// The text part of a dropdown list, to left of button. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MenulistText, - /// Menu Popup background. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menupopup, - /// menu checkbox/radio appearances - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menucheckbox, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menuradio, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menuseparator, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menuarrow, - /// An image in the menu gutter, like in bookmarks or history. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Menuimage, - /// The meter bar's meter indicator. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Meterchunk, - /// The "arrowed" part of the dropdown button that open up a dropdown list. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMenulistArrowButton, - /// For HTML's <input type=number> - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - NumberInput, - /// The progress bar's progress indicator - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Progresschunk, - /// A generic container that always repaints on state changes. This is a - /// hack to make XUL checkboxes and radio buttons work. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - CheckboxContainer, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - RadioContainer, - /// The label part of a checkbox or radio button, used for painting a focus - /// outline. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - CheckboxLabel, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - RadioLabel, - /// nsRangeFrame and its subparts - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Range, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - RangeThumb, - /// The scrollbar slider - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarHorizontal, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarVertical, - /// A scrollbar button (up/down/left/right). - /// Keep these in order (some code casts these values to `int` in order to - /// compare them against each other). - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarbuttonUp, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarbuttonDown, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarbuttonLeft, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarbuttonRight, - /// The scrollbar thumb. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarthumbHorizontal, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbarthumbVertical, - /// The scrollbar track. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbartrackHorizontal, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ScrollbartrackVertical, - /// The scroll corner - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Scrollcorner, - /// A separator. Can be horizontal or vertical. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Separator, - /// A spin control (up/down control for time/date pickers). - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Spinner, - /// The up button of a spin control. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - SpinnerUpbutton, - /// The down button of a spin control. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - SpinnerDownbutton, - /// The textfield of a spin control - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - SpinnerTextfield, - /// A splitter. Can be horizontal or vertical. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Splitter, - /// A status bar in a main application window. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Statusbar, - /// A single tab in a tab widget. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Tab, - /// A single pane (inside the tabpanels container). - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Tabpanel, - /// The tab panels container. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Tabpanels, - /// The tabs scroll arrows (left/right). - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TabScrollArrowBack, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TabScrollArrowForward, - /// A toolbar in an application window. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Toolbar, - /// A single toolbar button (with no associated dropdown). - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Toolbarbutton, - /// The dropdown portion of a toolbar button - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ToolbarbuttonDropdown, - /// The gripper for a toolbar. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Toolbargripper, - /// The toolbox that contains the toolbars. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Toolbox, - /// A tooltip. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Tooltip, - /// A listbox or tree widget header - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treeheader, - /// An individual header cell - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treeheadercell, - /// The sort arrow for a header. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treeheadersortarrow, - /// A tree item. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treeitem, - /// A tree widget branch line - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treeline, - /// A tree widget twisty. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treetwisty, - /// Open tree widget twisty. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treetwistyopen, - /// A tree widget. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Treeview, - /// Window and dialog backgrounds. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Window, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - Dialog, - - /// Vista Rebars. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWinCommunicationsToolbox, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWinMediaToolbox, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWinBrowsertabbarToolbox, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWinBorderlessGlass, - /// -moz-apperance style used in setting proper glass margins. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWinExcludeGlass, - - /// Mac help button. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacHelpButton, - - /// Windows themed window frame elements. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowButtonBox, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowButtonBoxMaximized, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowButtonClose, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowButtonMaximize, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowButtonMinimize, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowButtonRestore, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowTitlebar, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozWindowTitlebarMaximized, - - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacActiveSourceListSelection, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacDisclosureButtonClosed, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacDisclosureButtonOpen, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacSourceList, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacSourceListSelection, - - /// A themed focus outline (for outline:auto). - /// - /// This isn't exposed to CSS at all, just here for convenience. - #[css(skip)] - FocusOutline, - - /// A dummy variant that should be last to let the GTK widget do hackery. - #[css(skip)] - Count, -} - -/// A kind of break between two boxes. -/// -/// https://drafts.csswg.org/css-break/#break-between -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum BreakBetween { - Always, - Auto, - Page, - Avoid, - Left, - Right, -} - -impl BreakBetween { - /// Parse a legacy break-between value for `page-break-{before,after}`. - /// - /// See https://drafts.csswg.org/css-break/#page-break-properties. - #[cfg(feature = "gecko")] - #[inline] - pub(crate) fn parse_legacy<'i>( - _: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Self, ParseError<'i>> { - let break_value = BreakBetween::parse(input)?; - match break_value { - BreakBetween::Always => Ok(BreakBetween::Page), - BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => { - Ok(break_value) - }, - BreakBetween::Page => { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - } - } - - /// Serialize a legacy break-between value for `page-break-*`. - /// - /// See https://drafts.csswg.org/css-break/#page-break-properties. - #[cfg(feature = "gecko")] - pub(crate) fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => { - self.to_css(dest) - }, - BreakBetween::Page => dest.write_str("always"), - BreakBetween::Always => Ok(()), - } - } -} - -/// A kind of break within a box. -/// -/// https://drafts.csswg.org/css-break/#break-within -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum BreakWithin { - Auto, - Avoid, - AvoidPage, - AvoidColumn, -} - -impl BreakWithin { - /// Parse a legacy break-between value for `page-break-inside`. - /// - /// See https://drafts.csswg.org/css-break/#page-break-properties. - #[cfg(feature = "gecko")] - #[inline] - pub(crate) fn parse_legacy<'i>( - _: &ParserContext, - input: &mut Parser<'i, '_>, - ) -> Result<Self, ParseError<'i>> { - let break_value = BreakWithin::parse(input)?; - match break_value { - BreakWithin::Auto | BreakWithin::Avoid => Ok(break_value), - BreakWithin::AvoidPage | BreakWithin::AvoidColumn => { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - } - } - - /// Serialize a legacy break-between value for `page-break-inside`. - /// - /// See https://drafts.csswg.org/css-break/#page-break-properties. - #[cfg(feature = "gecko")] - pub(crate) fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - BreakWithin::Auto | BreakWithin::Avoid => self.to_css(dest), - BreakWithin::AvoidPage | BreakWithin::AvoidColumn => Ok(()), - } - } -} - -/// The value for the `overflow-x` / `overflow-y` properties. -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum Overflow { - Visible, - Hidden, - Scroll, - Auto, - #[cfg(feature = "gecko")] - Clip, -} - -// This can be derived once we remove or keep `-moz-hidden-unscrollable` -// indefinitely. -impl Parse for Overflow { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "visible" => Self::Visible, - "hidden" => Self::Hidden, - "scroll" => Self::Scroll, - "auto" => Self::Auto, - #[cfg(feature = "gecko")] - "clip" => Self::Clip, - #[cfg(feature = "gecko")] - "-moz-hidden-unscrollable" if static_prefs::pref!("layout.css.overflow-moz-hidden-unscrollable.enabled") => { - Overflow::Clip - }, - "overlay" if static_prefs::pref!("layout.css.overflow-overlay.enabled") => { - Overflow::Auto - }, - }) - } -} - -impl Overflow { - /// Return true if the value will create a scrollable box. - #[inline] - pub fn is_scrollable(&self) -> bool { - matches!(*self, Self::Hidden | Self::Scroll | Self::Auto) - } - /// Convert the value to a scrollable value if it's not already scrollable. - /// This maps `visible` to `auto` and `clip` to `hidden`. - #[inline] - pub fn to_scrollable(&self) -> Self { - match *self { - Self::Hidden | Self::Scroll | Self::Auto => *self, - Self::Visible => Self::Auto, - #[cfg(feature = "gecko")] - Self::Clip => Self::Hidden, - } - } -} - -bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)] - #[repr(C)] - #[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))] - /// Values for scrollbar-gutter: - /// <https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property> - pub struct ScrollbarGutter: u8 { - /// `auto` variant. Just for convenience if there is no flag set. - const AUTO = 0; - /// `stable` variant. - const STABLE = 1 << 0; - /// `both-edges` variant. - const BOTH_EDGES = 1 << 1; - } -} - -impl ScrollbarGutter { - #[inline] - fn has_stable(&self) -> bool { - self.intersects(Self::STABLE) - } -} diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs deleted file mode 100644 index d4cc64c2823..00000000000 --- a/components/style/values/specified/calc.rs +++ /dev/null @@ -1,1047 +0,0 @@ -/* 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/. */ - -//! [Calc expressions][calc]. -//! -//! [calc]: https://drafts.csswg.org/css-values/#calc-notation - -use crate::parser::ParserContext; -use crate::values::generics::calc as generic; -use crate::values::generics::calc::{MinMaxOp, ModRemOp, RoundingStrategy, SortKey}; -use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength}; -use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength}; -use crate::values::specified::{self, Angle, Resolution, Time}; -use crate::values::{serialize_number, serialize_percentage, CSSFloat, CSSInteger}; -use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token}; -use smallvec::SmallVec; -use std::cmp; -use std::fmt::{self, Write}; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -fn trig_enabled() -> bool { - static_prefs::pref!("layout.css.trig.enabled") -} - -fn nan_inf_enabled() -> bool { - static_prefs::pref!("layout.css.nan-inf.enabled") -} - -/// The name of the mathematical function that we're parsing. -#[derive(Clone, Copy, Debug, Parse)] -pub enum MathFunction { - /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc - Calc, - /// `min()`: https://drafts.csswg.org/css-values-4/#funcdef-min - Min, - /// `max()`: https://drafts.csswg.org/css-values-4/#funcdef-max - Max, - /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp - Clamp, - /// `round()`: https://drafts.csswg.org/css-values-4/#funcdef-round - Round, - /// `mod()`: https://drafts.csswg.org/css-values-4/#funcdef-mod - Mod, - /// `rem()`: https://drafts.csswg.org/css-values-4/#funcdef-rem - Rem, - /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin - Sin, - /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos - Cos, - /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan - Tan, - /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin - Asin, - /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos - Acos, - /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan - Atan, - /// `atan2()`: https://drafts.csswg.org/css-values-4/#funcdef-atan2 - Atan2, - /// `pow()`: https://drafts.csswg.org/css-values-4/#funcdef-pow - Pow, - /// `sqrt()`: https://drafts.csswg.org/css-values-4/#funcdef-sqrt - Sqrt, - /// `hypot()`: https://drafts.csswg.org/css-values-4/#funcdef-hypot - Hypot, - /// `log()`: https://drafts.csswg.org/css-values-4/#funcdef-log - Log, - /// `exp()`: https://drafts.csswg.org/css-values-4/#funcdef-exp - Exp, -} - -/// A leaf node inside a `Calc` expression's AST. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum Leaf { - /// `<length>` - Length(NoCalcLength), - /// `<angle>` - Angle(Angle), - /// `<time>` - Time(Time), - /// `<resolution>` - Resolution(Resolution), - /// `<percentage>` - Percentage(CSSFloat), - /// `<number>` - Number(CSSFloat), -} - -impl Leaf { - fn as_length(&self) -> Option<&NoCalcLength> { - match *self { - Self::Length(ref l) => Some(l), - _ => None, - } - } -} - -impl ToCss for Leaf { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - Self::Length(ref l) => l.to_css(dest), - Self::Number(n) => serialize_number(n, /* was_calc = */ false, dest), - Self::Resolution(ref r) => r.to_css(dest), - Self::Percentage(p) => serialize_percentage(p, dest), - Self::Angle(ref a) => a.to_css(dest), - Self::Time(ref t) => t.to_css(dest), - } - } -} - -bitflags! { - /// Expected units we allow parsing within a `calc()` expression. - /// - /// This is used as a hint for the parser to fast-reject invalid - /// expressions. Numbers are always allowed because they multiply other - /// units. - struct CalcUnits: u8 { - const LENGTH = 1 << 0; - const PERCENTAGE = 1 << 1; - const ANGLE = 1 << 2; - const TIME = 1 << 3; - const RESOLUTION = 1 << 3; - - const LENGTH_PERCENTAGE = Self::LENGTH.bits | Self::PERCENTAGE.bits; - // NOTE: When you add to this, make sure to make Atan2 deal with these. - const ALL = Self::LENGTH.bits | Self::PERCENTAGE.bits | Self::ANGLE.bits | Self::TIME.bits | Self::RESOLUTION.bits; - } -} - -/// A struct to hold a simplified `<length>` or `<percentage>` expression. -/// -/// In some cases, e.g. DOMMatrix, we support calc(), but reject all the -/// relative lengths, and to_computed_pixel_length_without_context() handles -/// this case. Therefore, if you want to add a new field, please make sure this -/// function work properly. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] -#[allow(missing_docs)] -pub struct CalcLengthPercentage { - #[css(skip)] - pub clamping_mode: AllowedNumericType, - pub node: CalcNode, -} - -impl CalcLengthPercentage { - fn same_unit_length_as(a: &Self, b: &Self) -> Option<(CSSFloat, CSSFloat)> { - use generic::CalcNodeLeaf; - - debug_assert_eq!(a.clamping_mode, b.clamping_mode); - debug_assert_eq!(a.clamping_mode, AllowedNumericType::All); - - let a = a.node.as_leaf()?; - let b = b.node.as_leaf()?; - - if a.sort_key() != b.sort_key() { - return None; - } - - let a = a.as_length()?.unitless_value(); - let b = b.as_length()?.unitless_value(); - return Some((a, b)); - } -} - -impl SpecifiedValueInfo for CalcLengthPercentage {} - -impl PartialOrd for Leaf { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - use self::Leaf::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return None; - } - - match (self, other) { - // NOTE: Percentages can't be compared reasonably here because the - // percentage basis might be negative, see bug 1709018. - // Conveniently, we only use this for <length-percentage> (for raw - // percentages, we go through resolve()). - (&Percentage(..), &Percentage(..)) => None, - (&Length(ref one), &Length(ref other)) => one.partial_cmp(other), - (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()), - (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), - (&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()), - (&Number(ref one), &Number(ref other)) => one.partial_cmp(other), - _ => { - match *self { - Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) | - Resolution(..) => {}, - } - unsafe { - debug_unreachable!("Forgot a branch?"); - } - }, - } - } -} - -impl generic::CalcNodeLeaf for Leaf { - fn unitless_value(&self) -> f32 { - match *self { - Self::Length(ref l) => l.unitless_value(), - Self::Percentage(n) | Self::Number(n) => n, - Self::Resolution(ref r) => r.dppx(), - Self::Angle(ref a) => a.degrees(), - Self::Time(ref t) => t.seconds(), - } - } - - fn sort_key(&self) -> SortKey { - match *self { - Self::Number(..) => SortKey::Number, - Self::Percentage(..) => SortKey::Percentage, - Self::Time(..) => SortKey::Sec, - Self::Resolution(..) => SortKey::Dppx, - Self::Angle(..) => SortKey::Deg, - Self::Length(ref l) => match *l { - NoCalcLength::Absolute(..) => SortKey::Px, - NoCalcLength::FontRelative(ref relative) => match *relative { - FontRelativeLength::Ch(..) => SortKey::Ch, - FontRelativeLength::Em(..) => SortKey::Em, - FontRelativeLength::Ex(..) => SortKey::Ex, - FontRelativeLength::Cap(..) => SortKey::Cap, - FontRelativeLength::Ic(..) => SortKey::Ic, - FontRelativeLength::Rem(..) => SortKey::Rem, - }, - NoCalcLength::ViewportPercentage(ref vp) => match *vp { - ViewportPercentageLength::Vh(..) => SortKey::Vh, - ViewportPercentageLength::Svh(..) => SortKey::Svh, - ViewportPercentageLength::Lvh(..) => SortKey::Lvh, - ViewportPercentageLength::Dvh(..) => SortKey::Dvh, - ViewportPercentageLength::Vw(..) => SortKey::Vw, - ViewportPercentageLength::Svw(..) => SortKey::Svw, - ViewportPercentageLength::Lvw(..) => SortKey::Lvw, - ViewportPercentageLength::Dvw(..) => SortKey::Dvw, - ViewportPercentageLength::Vmax(..) => SortKey::Vmax, - ViewportPercentageLength::Svmax(..) => SortKey::Svmax, - ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax, - ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax, - ViewportPercentageLength::Vmin(..) => SortKey::Vmin, - ViewportPercentageLength::Svmin(..) => SortKey::Svmin, - ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin, - ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin, - ViewportPercentageLength::Vb(..) => SortKey::Vb, - ViewportPercentageLength::Svb(..) => SortKey::Svb, - ViewportPercentageLength::Lvb(..) => SortKey::Lvb, - ViewportPercentageLength::Dvb(..) => SortKey::Dvb, - ViewportPercentageLength::Vi(..) => SortKey::Vi, - ViewportPercentageLength::Svi(..) => SortKey::Svi, - ViewportPercentageLength::Lvi(..) => SortKey::Lvi, - ViewportPercentageLength::Dvi(..) => SortKey::Dvi, - }, - NoCalcLength::ContainerRelative(ref cq) => match *cq { - ContainerRelativeLength::Cqw(..) => SortKey::Cqw, - ContainerRelativeLength::Cqh(..) => SortKey::Cqh, - ContainerRelativeLength::Cqi(..) => SortKey::Cqi, - ContainerRelativeLength::Cqb(..) => SortKey::Cqb, - ContainerRelativeLength::Cqmin(..) => SortKey::Cqmin, - ContainerRelativeLength::Cqmax(..) => SortKey::Cqmax, - }, - NoCalcLength::ServoCharacterWidth(..) => unreachable!(), - }, - } - } - - fn simplify(&mut self) { - if let Self::Length(NoCalcLength::Absolute(ref mut abs)) = *self { - *abs = AbsoluteLength::Px(abs.to_px()); - } - } - - /// Tries to merge one sum to another, that is, perform `x` + `y`. - /// - /// Only handles leaf nodes, it's the caller's responsibility to simplify - /// them before calling this if needed. - fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { - use self::Leaf::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - - match (self, other) { - (&mut Number(ref mut one), &Number(ref other)) | - (&mut Percentage(ref mut one), &Percentage(ref other)) => { - *one += *other; - }, - (&mut Angle(ref mut one), &Angle(ref other)) => { - *one = specified::Angle::from_calc(one.degrees() + other.degrees()); - }, - (&mut Time(ref mut one), &Time(ref other)) => { - *one = specified::Time::from_seconds(one.seconds() + other.seconds()); - }, - (&mut Resolution(ref mut one), &Resolution(ref other)) => { - *one = specified::Resolution::from_dppx(one.dppx() + other.dppx()); - }, - (&mut Length(ref mut one), &Length(ref other)) => { - *one = one.try_op(other, std::ops::Add::add)?; - }, - _ => { - match *other { - Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) | - Length(..) => {}, - } - unsafe { - debug_unreachable!(); - } - }, - } - - Ok(()) - } - - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - use self::Leaf::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - - match (self, other) { - (&Number(one), &Number(other)) => { - return Ok(Leaf::Number(op(one, other))); - }, - (&Percentage(one), &Percentage(other)) => { - return Ok(Leaf::Percentage(op(one, other))); - }, - (&Angle(ref one), &Angle(ref other)) => { - return Ok(Leaf::Angle(specified::Angle::from_calc(op( - one.degrees(), - other.degrees(), - )))); - }, - (&Resolution(ref one), &Resolution(ref other)) => { - return Ok(Leaf::Resolution(specified::Resolution::from_dppx(op( - one.dppx(), - other.dppx(), - )))); - }, - (&Time(ref one), &Time(ref other)) => { - return Ok(Leaf::Time(specified::Time::from_seconds(op( - one.seconds(), - other.seconds(), - )))); - }, - (&Length(ref one), &Length(ref other)) => { - return Ok(Leaf::Length(one.try_op(other, op)?)); - }, - _ => { - match *other { - Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) | - Resolution(..) => {}, - } - unsafe { - debug_unreachable!(); - } - }, - } - } - - fn map(&mut self, mut op: impl FnMut(f32) -> f32) { - match self { - Leaf::Length(one) => *one = one.map(op), - Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())), - Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())), - Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())), - Leaf::Percentage(one) => *one = op(*one), - Leaf::Number(one) => *one = op(*one), - } - } -} - -/// A calc node representation for specified values. -pub type CalcNode = generic::GenericCalcNode<Leaf>; - -impl CalcNode { - /// Tries to parse a single element in the expression, that is, a - /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to - /// `allowed_units`. - /// - /// May return a "complex" `CalcNode`, in the presence of a parenthesized - /// expression, for example. - fn parse_one<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allowed_units: CalcUnits, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - match input.next()? { - &Token::Number { value, .. } => Ok(CalcNode::Leaf(Leaf::Number(value))), - &Token::Dimension { - value, ref unit, .. - } => { - if allowed_units.intersects(CalcUnits::LENGTH) { - if let Ok(l) = NoCalcLength::parse_dimension(context, value, unit) { - return Ok(CalcNode::Leaf(Leaf::Length(l))); - } - } - if allowed_units.intersects(CalcUnits::ANGLE) { - if let Ok(a) = Angle::parse_dimension(value, unit, /* from_calc = */ true) { - return Ok(CalcNode::Leaf(Leaf::Angle(a))); - } - } - if allowed_units.intersects(CalcUnits::TIME) { - if let Ok(t) = Time::parse_dimension(value, unit) { - return Ok(CalcNode::Leaf(Leaf::Time(t))); - } - } - if allowed_units.intersects(CalcUnits::RESOLUTION) { - if let Ok(t) = Resolution::parse_dimension(value, unit) { - return Ok(CalcNode::Leaf(Leaf::Resolution(t))); - } - } - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }, - &Token::Percentage { unit_value, .. } - if allowed_units.intersects(CalcUnits::PERCENTAGE) => - { - Ok(CalcNode::Leaf(Leaf::Percentage(unit_value))) - }, - &Token::ParenthesisBlock => input.parse_nested_block(|input| { - CalcNode::parse_argument(context, input, allowed_units) - }), - &Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - CalcNode::parse(context, input, function, allowed_units) - }, - &Token::Ident(ref ident) => { - let number = match_ignore_ascii_case! { &**ident, - "e" if trig_enabled() => std::f32::consts::E, - "pi" if trig_enabled() => std::f32::consts::PI, - "infinity" if nan_inf_enabled() => f32::INFINITY, - "-infinity" if nan_inf_enabled() => f32::NEG_INFINITY, - "nan" if nan_inf_enabled() => f32::NAN, - _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))), - }; - Ok(CalcNode::Leaf(Leaf::Number(number))) - }, - t => Err(location.new_unexpected_token_error(t.clone())), - } - } - - /// Parse a top-level `calc` expression, with all nested sub-expressions. - /// - /// This is in charge of parsing, for example, `2 + 3 * 100%`. - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - allowed_units: CalcUnits, - ) -> Result<Self, ParseError<'i>> { - // TODO: Do something different based on the function name. In - // particular, for non-calc function we need to take a list of - // comma-separated arguments and such. - input.parse_nested_block(|input| { - match function { - MathFunction::Calc => Self::parse_argument(context, input, allowed_units), - MathFunction::Clamp => { - let min = Self::parse_argument(context, input, allowed_units)?; - input.expect_comma()?; - let center = Self::parse_argument(context, input, allowed_units)?; - input.expect_comma()?; - let max = Self::parse_argument(context, input, allowed_units)?; - Ok(Self::Clamp { - min: Box::new(min), - center: Box::new(center), - max: Box::new(max), - }) - }, - MathFunction::Round => { - let strategy = input.try_parse(parse_rounding_strategy); - - // <rounding-strategy> = nearest | up | down | to-zero - // https://drafts.csswg.org/css-values-4/#calc-syntax - fn parse_rounding_strategy<'i, 't>( - input: &mut Parser<'i, 't>, - ) -> Result<RoundingStrategy, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "nearest" => RoundingStrategy::Nearest, - "up" => RoundingStrategy::Up, - "down" => RoundingStrategy::Down, - "to-zero" => RoundingStrategy::ToZero, - }) - } - - if strategy.is_ok() { - input.expect_comma()?; - } - - let value = Self::parse_argument(context, input, allowed_units)?; - input.expect_comma()?; - let step = Self::parse_argument(context, input, allowed_units)?; - - Ok(Self::Round { - strategy: strategy.unwrap_or(RoundingStrategy::Nearest), - value: Box::new(value), - step: Box::new(step), - }) - }, - MathFunction::Mod | MathFunction::Rem => { - let dividend = Self::parse_argument(context, input, allowed_units)?; - input.expect_comma()?; - let divisor = Self::parse_argument(context, input, allowed_units)?; - - let op = match function { - MathFunction::Mod => ModRemOp::Mod, - MathFunction::Rem => ModRemOp::Rem, - _ => unreachable!(), - }; - Ok(Self::ModRem { - dividend: Box::new(dividend), - divisor: Box::new(divisor), - op, - }) - }, - MathFunction::Min | MathFunction::Max => { - // TODO(emilio): The common case for parse_comma_separated - // is just one element, but for min / max is two, really... - // - // Consider adding an API to cssparser to specify the - // initial vector capacity? - let arguments = input.parse_comma_separated(|input| { - Self::parse_argument(context, input, allowed_units) - })?; - - let op = match function { - MathFunction::Min => MinMaxOp::Min, - MathFunction::Max => MinMaxOp::Max, - _ => unreachable!(), - }; - - Ok(Self::MinMax(arguments.into(), op)) - }, - MathFunction::Sin | MathFunction::Cos | MathFunction::Tan => { - let a = Self::parse_angle_argument(context, input)?; - - let number = match function { - MathFunction::Sin => a.sin(), - MathFunction::Cos => a.cos(), - MathFunction::Tan => a.tan(), - _ => unsafe { - debug_unreachable!("We just checked!"); - }, - }; - - Ok(Self::Leaf(Leaf::Number(number))) - }, - MathFunction::Asin | MathFunction::Acos | MathFunction::Atan => { - let a = Self::parse_number_argument(context, input)?; - - let radians = match function { - MathFunction::Asin => a.asin(), - MathFunction::Acos => a.acos(), - MathFunction::Atan => a.atan(), - _ => unsafe { - debug_unreachable!("We just checked!"); - }, - }; - - Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) - }, - MathFunction::Atan2 => { - let a = Self::parse_argument(context, input, CalcUnits::ALL)?; - input.expect_comma()?; - let b = Self::parse_argument(context, input, CalcUnits::ALL)?; - - let radians = Self::try_resolve(input, || { - if let Ok(a) = a.to_number() { - let b = b.to_number()?; - return Ok(a.atan2(b)); - } - - if let Ok(a) = a.to_percentage() { - let b = b.to_percentage()?; - return Ok(a.atan2(b)); - } - - if let Ok(a) = a.to_time(None) { - let b = b.to_time(None)?; - return Ok(a.seconds().atan2(b.seconds())); - } - - if let Ok(a) = a.to_angle() { - let b = b.to_angle()?; - return Ok(a.radians().atan2(b.radians())); - } - - if let Ok(a) = a.to_resolution() { - let b = b.to_resolution()?; - return Ok(a.dppx().atan2(b.dppx())); - } - - let a = a.into_length_or_percentage(AllowedNumericType::All)?; - let b = b.into_length_or_percentage(AllowedNumericType::All)?; - let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?; - - Ok(a.atan2(b)) - })?; - - Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) - }, - MathFunction::Pow => { - let a = Self::parse_number_argument(context, input)?; - input.expect_comma()?; - let b = Self::parse_number_argument(context, input)?; - - let number = a.powf(b); - - Ok(Self::Leaf(Leaf::Number(number))) - }, - MathFunction::Sqrt => { - let a = Self::parse_number_argument(context, input)?; - - let number = a.sqrt(); - - Ok(Self::Leaf(Leaf::Number(number))) - }, - MathFunction::Hypot => { - let arguments = input.parse_comma_separated(|input| { - Self::parse_argument(context, input, allowed_units) - })?; - - Ok(Self::Hypot(arguments.into())) - }, - MathFunction::Log => { - let a = Self::parse_number_argument(context, input)?; - let b = input - .try_parse(|input| { - input.expect_comma()?; - Self::parse_number_argument(context, input) - }) - .ok(); - - let number = match b { - Some(b) => a.log(b), - None => a.ln(), - }; - - Ok(Self::Leaf(Leaf::Number(number))) - }, - MathFunction::Exp => { - let a = Self::parse_number_argument(context, input)?; - - let number = a.exp(); - - Ok(Self::Leaf(Leaf::Number(number))) - }, - } - }) - } - - fn parse_angle_argument<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<CSSFloat, ParseError<'i>> { - let argument = Self::parse_argument(context, input, CalcUnits::ANGLE)?; - argument - .to_number() - .or_else(|()| Ok(argument.to_angle()?.radians())) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - fn parse_number_argument<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse_argument(context, input, CalcUnits::empty())? - .to_number() - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - fn parse_argument<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allowed_units: CalcUnits, - ) -> Result<Self, ParseError<'i>> { - let mut sum = SmallVec::<[CalcNode; 1]>::new(); - sum.push(Self::parse_product(context, input, allowed_units)?); - - loop { - let start = input.state(); - match input.next_including_whitespace() { - Ok(&Token::WhiteSpace(_)) => { - if input.is_exhausted() { - break; // allow trailing whitespace - } - match *input.next()? { - Token::Delim('+') => { - sum.push(Self::parse_product(context, input, allowed_units)?); - }, - Token::Delim('-') => { - let mut rhs = Self::parse_product(context, input, allowed_units)?; - rhs.negate(); - sum.push(rhs); - }, - _ => { - input.reset(&start); - break; - }, - } - }, - _ => { - input.reset(&start); - break; - }, - } - } - - Ok(if sum.len() == 1 { - sum.drain(..).next().unwrap() - } else { - Self::Sum(sum.into_boxed_slice().into()) - }) - } - - /// Parse a top-level `calc` expression, and all the products that may - /// follow, and stop as soon as a non-product expression is found. - /// - /// This should parse correctly: - /// - /// * `2` - /// * `2 * 2` - /// * `2 * 2 + 2` (but will leave the `+ 2` unparsed). - /// - fn parse_product<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allowed_units: CalcUnits, - ) -> Result<Self, ParseError<'i>> { - let mut node = Self::parse_one(context, input, allowed_units)?; - - loop { - let start = input.state(); - match input.next() { - Ok(&Token::Delim('*')) => { - let rhs = Self::parse_one(context, input, allowed_units)?; - if let Ok(rhs) = rhs.to_number() { - node.mul_by(rhs); - } else if let Ok(number) = node.to_number() { - node = rhs; - node.mul_by(number); - } else { - // One of the two parts of the multiplication has to be - // a number, at least until we implement unit math. - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - }, - Ok(&Token::Delim('/')) => { - let rhs = Self::parse_one(context, input, allowed_units)?; - // Dividing by units is not ok. - // - // TODO(emilio): Eventually it should be. - let number = match rhs.to_number() { - Ok(n) if n != 0. || nan_inf_enabled() => n, - _ => { - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ) - }, - }; - node.mul_by(1. / number); - }, - _ => { - input.reset(&start); - break; - }, - } - } - - Ok(node) - } - - fn try_resolve<'i, 't, F>( - input: &Parser<'i, 't>, - closure: F, - ) -> Result<CSSFloat, ParseError<'i>> - where - F: FnOnce() -> Result<CSSFloat, ()>, - { - closure().map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Tries to simplify this expression into a `<length>` or `<percentage>` - /// value. - fn into_length_or_percentage( - mut self, - clamping_mode: AllowedNumericType, - ) -> Result<CalcLengthPercentage, ()> { - // Keep track of whether there's any invalid member of the calculation, - // so as to reject the calculation properly at parse-time. - let mut any_invalid = false; - self.visit_depth_first(|node| { - if let CalcNode::Leaf(ref l) = *node { - any_invalid |= !matches!(*l, Leaf::Percentage(..) | Leaf::Length(..)); - } - node.simplify_and_sort_direct_children(); - }); - - if any_invalid { - return Err(()); - } - - Ok(CalcLengthPercentage { - clamping_mode, - node: self, - }) - } - - /// Tries to simplify this expression into a `<time>` value. - fn to_time(&self, clamping_mode: Option<AllowedNumericType>) -> Result<Time, ()> { - let seconds = self.resolve(|leaf| match *leaf { - Leaf::Time(ref time) => Ok(time.seconds()), - _ => Err(()), - })?; - - Ok(Time::from_seconds_with_calc_clamping_mode( - if nan_inf_enabled() { - seconds - } else { - crate::values::normalize(seconds) - }, - clamping_mode, - )) - } - - /// Tries to simplify the expression into a `<resolution>` value. - fn to_resolution(&self) -> Result<Resolution, ()> { - let dppx = self.resolve(|leaf| match *leaf { - Leaf::Resolution(ref r) => Ok(r.dppx()), - _ => Err(()), - })?; - - Ok(Resolution::from_dppx_calc(if nan_inf_enabled() { - dppx - } else { - crate::values::normalize(dppx) - })) - } - - /// Tries to simplify this expression into an `Angle` value. - fn to_angle(&self) -> Result<Angle, ()> { - let degrees = self.resolve(|leaf| match *leaf { - Leaf::Angle(ref angle) => Ok(angle.degrees()), - _ => Err(()), - })?; - let result = Angle::from_calc(if nan_inf_enabled() { - degrees - } else { - crate::values::normalize(degrees) - }); - Ok(result) - } - - /// Tries to simplify this expression into a `<number>` value. - fn to_number(&self) -> Result<CSSFloat, ()> { - let number = self.resolve(|leaf| match *leaf { - Leaf::Number(n) => Ok(n), - _ => Err(()), - })?; - let result = if nan_inf_enabled() { - number - } else { - crate::values::normalize(number) - }; - Ok(result) - } - - /// Tries to simplify this expression into a `<percentage>` value. - fn to_percentage(&self) -> Result<CSSFloat, ()> { - self.resolve(|leaf| match *leaf { - Leaf::Percentage(p) => Ok(p), - _ => Err(()), - }) - } - - /// Given a function name, and the location from where the token came from, - /// return a mathematical function corresponding to that name or an error. - #[inline] - pub fn math_function<'i>( - context: &ParserContext, - name: &CowRcStr<'i>, - location: cssparser::SourceLocation, - ) -> Result<MathFunction, ParseError<'i>> { - use self::MathFunction::*; - - let function = match MathFunction::from_ident(&*name) { - Ok(f) => f, - Err(()) => { - return Err(location.new_unexpected_token_error(Token::Function(name.clone()))) - }, - }; - - let enabled = if context.chrome_rules_enabled() { - true - } else if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan | Atan2) { - trig_enabled() - } else if matches!(function, Round) { - static_prefs::pref!("layout.css.round.enabled") - } else if matches!(function, Mod | Rem) { - static_prefs::pref!("layout.css.mod-rem.enabled") - } else if matches!(function, Pow | Sqrt | Hypot | Log | Exp) { - static_prefs::pref!("layout.css.exp.enabled") - } else { - true - }; - - if !enabled { - return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); - } - - Ok(function) - } - - /// Convenience parsing function for integers. - pub fn parse_integer<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<CSSInteger, ParseError<'i>> { - Self::parse_number(context, input, function).map(|n| n.round() as CSSInteger) - } - - /// Convenience parsing function for `<length> | <percentage>`. - pub fn parse_length_or_percentage<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - clamping_mode: AllowedNumericType, - function: MathFunction, - ) -> Result<CalcLengthPercentage, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::LENGTH_PERCENTAGE)? - .into_length_or_percentage(clamping_mode) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for percentages. - pub fn parse_percentage<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::PERCENTAGE)? - .to_percentage() - .map(crate::values::normalize) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for `<length>`. - pub fn parse_length<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - clamping_mode: AllowedNumericType, - function: MathFunction, - ) -> Result<CalcLengthPercentage, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::LENGTH)? - .into_length_or_percentage(clamping_mode) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for `<number>`. - pub fn parse_number<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<CSSFloat, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::empty())? - .to_number() - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for `<angle>`. - pub fn parse_angle<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<Angle, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::ANGLE)? - .to_angle() - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for `<time>`. - pub fn parse_time<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - clamping_mode: AllowedNumericType, - function: MathFunction, - ) -> Result<Time, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::TIME)? - .to_time(Some(clamping_mode)) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for `<resolution>`. - pub fn parse_resolution<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<Resolution, ParseError<'i>> { - Self::parse(context, input, function, CalcUnits::RESOLUTION)? - .to_resolution() - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - - /// Convenience parsing function for `<number>` or `<percentage>`. - pub fn parse_number_or_percentage<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<NumberOrPercentage, ParseError<'i>> { - let node = Self::parse(context, input, function, CalcUnits::PERCENTAGE)?; - - if let Ok(value) = node.to_number() { - return Ok(NumberOrPercentage::Number { value }); - } - - match node.to_percentage() { - Ok(unit_value) => Ok(NumberOrPercentage::Percentage { unit_value }), - Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } - - /// Convenience parsing function for `<number>` or `<angle>`. - pub fn parse_angle_or_number<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - function: MathFunction, - ) -> Result<AngleOrNumber, ParseError<'i>> { - let node = Self::parse(context, input, function, CalcUnits::ANGLE)?; - - if let Ok(angle) = node.to_angle() { - let degrees = angle.degrees(); - return Ok(AngleOrNumber::Angle { degrees }); - } - - match node.to_number() { - Ok(value) => Ok(AngleOrNumber::Number { value }), - Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } -} diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs deleted file mode 100644 index c84592a97b8..00000000000 --- a/components/style/values/specified/color.rs +++ /dev/null @@ -1,1184 +0,0 @@ -/* 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/. */ - -//! Specified color values. - -use super::AllowQuirks; -use crate::color::mix::ColorInterpolationMethod; -use crate::color::{AbsoluteColor, ColorComponents, ColorFlags, ColorSpace}; -use crate::media_queries::Device; -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; -use crate::values::generics::color::{GenericCaretColor, GenericColorMix, GenericColorOrAuto}; -use crate::values::specified::calc::CalcNode; -use crate::values::specified::Percentage; -use crate::values::CustomIdent; -use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token}; -use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind}; -use itoa; -use std::fmt::{self, Write}; -use std::io::Write as IoWrite; -use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; -use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; - -/// A specified color-mix(). -pub type ColorMix = GenericColorMix<Color, Percentage>; - -impl ColorMix { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - preserve_authored: PreserveAuthored, - ) -> Result<Self, ParseError<'i>> { - let enabled = - context.chrome_rules_enabled() || static_prefs::pref!("layout.css.color-mix.enabled"); - - if !enabled { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - input.expect_function_matching("color-mix")?; - - input.parse_nested_block(|input| { - let interpolation = ColorInterpolationMethod::parse(context, input)?; - input.expect_comma()?; - - let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> { - input - .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input)) - .ok() - }; - - let mut left_percentage = try_parse_percentage(input); - - let left = Color::parse_internal(context, input, preserve_authored)?; - if left_percentage.is_none() { - left_percentage = try_parse_percentage(input); - } - - input.expect_comma()?; - - let mut right_percentage = try_parse_percentage(input); - - let right = Color::parse(context, input)?; - - if right_percentage.is_none() { - right_percentage = try_parse_percentage(input); - } - - let right_percentage = right_percentage - .unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))); - - let left_percentage = - left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get())); - - if left_percentage.get() + right_percentage.get() <= 0.0 { - // If the percentages sum to zero, the function is invalid. - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(ColorMix { - interpolation, - left, - left_percentage, - right, - right_percentage, - normalize_weights: true, - }) - }) - } -} - -/// Container holding an absolute color and the text specified by an author. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct Absolute { - /// The specified color. - pub color: AbsoluteColor, - /// Authored representation. - pub authored: Option<Box<str>>, -} - -impl ToCss for Absolute { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if let Some(ref authored) = self.authored { - dest.write_str(authored) - } else { - self.color.to_css(dest) - } - } -} - -/// Specified color value -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum Color { - /// The 'currentColor' keyword - CurrentColor, - /// An absolute color. - /// https://w3c.github.io/csswg-drafts/css-color-4/#typedef-absolute-color-function - Absolute(Box<Absolute>), - /// A system color. - #[cfg(feature = "gecko")] - System(SystemColor), - /// A color mix. - ColorMix(Box<ColorMix>), - /// Quirksmode-only rule for inheriting color from the body - #[cfg(feature = "gecko")] - InheritFromBodyQuirk, -} - -impl From<AbsoluteColor> for Color { - #[inline] - fn from(value: AbsoluteColor) -> Self { - Self::from_absolute_color(value) - } -} - -/// System colors. A bunch of these are ad-hoc, others come from Windows: -/// -/// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor -/// -/// Others are HTML/CSS specific. Spec is: -/// -/// https://drafts.csswg.org/css-color/#css-system-colors -/// https://drafts.csswg.org/css-color/#deprecated-system-colors -#[allow(missing_docs)] -#[cfg(feature = "gecko")] -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -#[repr(u8)] -pub enum SystemColor { - Activeborder, - /// Background in the (active) titlebar. - Activecaption, - Appworkspace, - Background, - Buttonface, - Buttonhighlight, - Buttonshadow, - Buttontext, - Buttonborder, - /// Text color in the (active) titlebar. - Captiontext, - #[parse(aliases = "-moz-field")] - Field, - /// Used for disabled field backgrounds. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozDisabledfield, - #[parse(aliases = "-moz-fieldtext")] - Fieldtext, - - Mark, - Marktext, - - /// Combobox widgets - MozComboboxtext, - MozCombobox, - - Graytext, - Highlight, - Highlighttext, - Inactiveborder, - /// Background in the (inactive) titlebar. - Inactivecaption, - /// Text color in the (inactive) titlebar. - Inactivecaptiontext, - Infobackground, - Infotext, - Menu, - Menutext, - Scrollbar, - Threeddarkshadow, - Threedface, - Threedhighlight, - Threedlightshadow, - Threedshadow, - Window, - Windowframe, - Windowtext, - MozButtondefault, - #[parse(aliases = "-moz-default-color")] - Canvastext, - #[parse(aliases = "-moz-default-background-color")] - Canvas, - MozDialog, - MozDialogtext, - /// Used to highlight valid regions to drop something onto. - MozDragtargetzone, - /// Used for selected but not focused cell backgrounds. - #[parse(aliases = "-moz-html-cellhighlight")] - MozCellhighlight, - /// Used for selected but not focused cell text. - #[parse(aliases = "-moz-html-cellhighlighttext")] - MozCellhighlighttext, - /// Used for selected and focused html cell backgrounds. - Selecteditem, - /// Used for selected and focused html cell text. - Selecteditemtext, - /// Used to button text background when hovered. - MozButtonhoverface, - /// Used to button text color when hovered. - MozButtonhovertext, - /// Used for menu item backgrounds when hovered. - MozMenuhover, - /// Used for menu item backgrounds when hovered and disabled. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMenuhoverdisabled, - /// Used for menu item text when hovered. - MozMenuhovertext, - /// Used for menubar item text. - MozMenubartext, - /// Used for menubar item text when hovered. - MozMenubarhovertext, - - /// On platforms where these colors are the same as -moz-field, use - /// -moz-fieldtext as foreground color - MozEventreerow, - MozOddtreerow, - - /// Used for button text when pressed. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozButtonactivetext, - - /// Used for button background when pressed. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozButtonactiveface, - - /// Used for button background when disabled. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozButtondisabledface, - - /// Background color of chrome toolbars in active windows. - MozMacChromeActive, - /// Background color of chrome toolbars in inactive windows. - MozMacChromeInactive, - /// Foreground color of default buttons. - MozMacDefaultbuttontext, - /// Ring color around text fields and lists. - MozMacFocusring, - /// Color used when mouse is over a menu item. - MozMacMenuselect, - /// Color used to do shadows on menu items. - MozMacMenushadow, - /// Color used to display text for disabled menu items. - MozMacMenutextdisable, - /// Color used to display text while mouse is over a menu item. - MozMacMenutextselect, - /// Text color of disabled text on toolbars. - MozMacDisabledtoolbartext, - /// Inactive light hightlight - MozMacSecondaryhighlight, - - MozMacMenupopup, - MozMacMenuitem, - MozMacActiveMenuitem, - MozMacSourceList, - MozMacSourceListSelection, - MozMacActiveSourceListSelection, - MozMacTooltip, - - /// Theme accent color. - /// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolor - Accentcolor, - - /// Foreground for the accent color. - /// https://drafts.csswg.org/css-color-4/#valdef-system-color-accentcolortext - Accentcolortext, - - /// The background-color for :autofill-ed inputs. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozAutofillBackground, - - /// Media rebar text. - MozWinMediatext, - /// Communications rebar text. - MozWinCommunicationstext, - - /// Hyperlink color extracted from the system, not affected by the - /// browser.anchor_color user pref. - /// - /// There is no OS-specified safe background color for this text, but it is - /// used regularly within Windows and the Gnome DE on Dialog and Window - /// colors. - MozNativehyperlinktext, - - /// As above, but visited link color. - #[css(skip)] - MozNativevisitedhyperlinktext, - - #[parse(aliases = "-moz-hyperlinktext")] - Linktext, - #[parse(aliases = "-moz-activehyperlinktext")] - Activetext, - #[parse(aliases = "-moz-visitedhyperlinktext")] - Visitedtext, - - /// Color of tree column headers - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozColheadertext, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozColheaderhovertext, - - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectDisabledBackground, - #[css(skip)] - TextSelectAttentionBackground, - #[css(skip)] - TextSelectAttentionForeground, - #[css(skip)] - TextHighlightBackground, - #[css(skip)] - TextHighlightForeground, - #[css(skip)] - IMERawInputBackground, - #[css(skip)] - IMERawInputForeground, - #[css(skip)] - IMERawInputUnderline, - #[css(skip)] - IMESelectedRawTextBackground, - #[css(skip)] - IMESelectedRawTextForeground, - #[css(skip)] - IMESelectedRawTextUnderline, - #[css(skip)] - IMEConvertedTextBackground, - #[css(skip)] - IMEConvertedTextForeground, - #[css(skip)] - IMEConvertedTextUnderline, - #[css(skip)] - IMESelectedConvertedTextBackground, - #[css(skip)] - IMESelectedConvertedTextForeground, - #[css(skip)] - IMESelectedConvertedTextUnderline, - #[css(skip)] - SpellCheckerUnderline, - #[css(skip)] - ThemedScrollbar, - #[css(skip)] - ThemedScrollbarInactive, - #[css(skip)] - ThemedScrollbarThumb, - #[css(skip)] - ThemedScrollbarThumbHover, - #[css(skip)] - ThemedScrollbarThumbActive, - #[css(skip)] - ThemedScrollbarThumbInactive, - - #[css(skip)] - End, // Just for array-indexing purposes. -} - -#[cfg(feature = "gecko")] -impl SystemColor { - #[inline] - fn compute(&self, cx: &Context) -> ComputedColor { - use crate::gecko::values::convert_nscolor_to_absolute_color; - use crate::gecko_bindings::bindings; - - // TODO: We should avoid cloning here most likely, though it's - // cheap-ish. - let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); - let color = cx.device().system_nscolor(*self, &style_color_scheme); - if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { - return ComputedColor::currentcolor(); - } - ComputedColor::Absolute(convert_nscolor_to_absolute_color(color)) - } -} - -#[inline] -fn new_absolute( - color_space: ColorSpace, - c1: Option<f32>, - c2: Option<f32>, - c3: Option<f32>, - alpha: Option<f32>, -) -> Color { - let mut flags = ColorFlags::empty(); - - macro_rules! c { - ($v:expr,$flag:tt) => {{ - if let Some(value) = $v { - value - } else { - flags |= ColorFlags::$flag; - 0.0 - } - }}; - } - - let c1 = c!(c1, C1_IS_NONE); - let c2 = c!(c2, C2_IS_NONE); - let c3 = c!(c3, C3_IS_NONE); - let alpha = c!(alpha, ALPHA_IS_NONE); - - let mut color = AbsoluteColor::new(color_space, ColorComponents(c1, c2, c3), alpha); - color.flags |= flags; - Color::Absolute(Box::new(Absolute { - color, - authored: None, - })) -} - -impl cssparser::FromParsedColor for Color { - fn from_current_color() -> Self { - Color::CurrentColor - } - - fn from_rgba(red: Option<u8>, green: Option<u8>, blue: Option<u8>, alpha: Option<f32>) -> Self { - new_absolute( - ColorSpace::Srgb, - red.map(|r| r as f32 / 255.0), - green.map(|g| g as f32 / 255.0), - blue.map(|b| b as f32 / 255.0), - alpha, - ) - } - - fn from_hsl( - hue: Option<f32>, - saturation: Option<f32>, - lightness: Option<f32>, - alpha: Option<f32>, - ) -> Self { - new_absolute(ColorSpace::Hsl, hue, saturation, lightness, alpha) - } - - fn from_hwb( - hue: Option<f32>, - whiteness: Option<f32>, - blackness: Option<f32>, - alpha: Option<f32>, - ) -> Self { - new_absolute(ColorSpace::Hwb, hue, whiteness, blackness, alpha) - } - - fn from_lab( - lightness: Option<f32>, - a: Option<f32>, - b: Option<f32>, - alpha: Option<f32>, - ) -> Self { - new_absolute(ColorSpace::Lab, lightness, a, b, alpha) - } - - fn from_lch( - lightness: Option<f32>, - chroma: Option<f32>, - hue: Option<f32>, - alpha: Option<f32>, - ) -> Self { - new_absolute(ColorSpace::Lch, lightness, chroma, hue, alpha) - } - - fn from_oklab( - lightness: Option<f32>, - a: Option<f32>, - b: Option<f32>, - alpha: Option<f32>, - ) -> Self { - new_absolute(ColorSpace::Oklab, lightness, a, b, alpha) - } - - fn from_oklch( - lightness: Option<f32>, - chroma: Option<f32>, - hue: Option<f32>, - alpha: Option<f32>, - ) -> Self { - new_absolute(ColorSpace::Oklch, lightness, chroma, hue, alpha) - } - - fn from_color_function( - color_space: cssparser::PredefinedColorSpace, - c1: Option<f32>, - c2: Option<f32>, - c3: Option<f32>, - alpha: Option<f32>, - ) -> Self { - let mut result = new_absolute(color_space.into(), c1, c2, c3, alpha); - if let Color::Absolute(ref mut absolute) = result { - if matches!(absolute.color.color_space, ColorSpace::Srgb) { - absolute.color.flags |= ColorFlags::AS_COLOR_FUNCTION; - } - } - result - } -} - -struct ColorParser<'a, 'b: 'a>(&'a ParserContext<'b>); -impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorParser<'i> for ColorParser<'a, 'b> { - type Output = Color; - type Error = StyleParseErrorKind<'i>; - - fn parse_angle_or_number<'t>( - &self, - input: &mut Parser<'i, 't>, - ) -> Result<AngleOrNumber, ParseError<'i>> { - use crate::values::specified::Angle; - - let location = input.current_source_location(); - let token = input.next()?.clone(); - match token { - Token::Dimension { - value, ref unit, .. - } => { - let angle = Angle::parse_dimension(value, unit, /* from_calc = */ false); - - let degrees = match angle { - Ok(angle) => angle.degrees(), - Err(()) => return Err(location.new_unexpected_token_error(token.clone())), - }; - - Ok(AngleOrNumber::Angle { degrees }) - }, - Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }), - Token::Function(ref name) => { - let function = CalcNode::math_function(self.0, name, location)?; - CalcNode::parse_angle_or_number(self.0, input, function) - }, - t => return Err(location.new_unexpected_token_error(t)), - } - } - - fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> { - Ok(Percentage::parse(self.0, input)?.get()) - } - - fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> { - use crate::values::specified::Number; - - Ok(Number::parse(self.0, input)?.get()) - } - - fn parse_number_or_percentage<'t>( - &self, - input: &mut Parser<'i, 't>, - ) -> Result<NumberOrPercentage, ParseError<'i>> { - let location = input.current_source_location(); - - match *input.next()? { - Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }), - Token::Percentage { unit_value, .. } => { - Ok(NumberOrPercentage::Percentage { unit_value }) - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(self.0, name, location)?; - CalcNode::parse_number_or_percentage(self.0, input, function) - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - } - } -} - -/// Whether to preserve authored colors during parsing. That's useful only if we -/// plan to serialize the color back. -enum PreserveAuthored { - No, - Yes, -} - -impl Parse for Color { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, PreserveAuthored::Yes) - } -} - -impl Color { - fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - preserve_authored: PreserveAuthored, - ) -> Result<Self, ParseError<'i>> { - let authored = match preserve_authored { - PreserveAuthored::No => None, - PreserveAuthored::Yes => { - // Currently we only store authored value for color keywords, - // because all browsers serialize those values as keywords for - // specified value. - let start = input.state(); - let authored = input.expect_ident_cloned().ok(); - input.reset(&start); - authored - }, - }; - - let color_parser = ColorParser(&*context); - match input.try_parse(|i| cssparser::parse_color_with(&color_parser, i)) { - Ok(mut color) => { - if let Color::Absolute(ref mut absolute) = color { - let enabled = { - let is_legacy_color = matches!( - absolute.color.color_space, - ColorSpace::Srgb | ColorSpace::Hsl - ); - let is_color_function = - absolute.color.flags.contains(ColorFlags::AS_COLOR_FUNCTION); - let pref_enabled = static_prefs::pref!("layout.css.more_color_4.enabled"); - - (is_legacy_color && !is_color_function) || pref_enabled - }; - if !enabled { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // Because we can't set the `authored` value at construction time, we have to set it - // here. - absolute.authored = authored.map(|s| s.to_ascii_lowercase().into_boxed_str()); - } - Ok(color) - }, - Err(e) => { - #[cfg(feature = "gecko")] - { - if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) { - return Ok(Color::System(system)); - } - } - - if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i, preserve_authored)) - { - return Ok(Color::ColorMix(Box::new(mix))); - } - - match e.kind { - ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { - Err(e.location.new_custom_error(StyleParseErrorKind::ValueError( - ValueParseErrorKind::InvalidColor(t), - ))) - }, - _ => Err(e), - } - }, - } - } - - /// Returns whether a given color is valid for authors. - pub fn is_valid(context: &ParserContext, input: &mut Parser) -> bool { - input - .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)) - .is_ok() - } - - /// Tries to parse a color and compute it with a given device. - pub fn parse_and_compute( - context: &ParserContext, - input: &mut Parser, - device: Option<&Device>, - ) -> Option<ComputedColor> { - use crate::error_reporting::ContextualParseError; - let start = input.position(); - let result = input - .parse_entirely(|input| Self::parse_internal(context, input, PreserveAuthored::No)); - - let specified = match result { - Ok(s) => s, - Err(e) => { - if !context.error_reporting_enabled() { - return None; - } - // Ignore other kinds of errors that might be reported, such as - // ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken), - // since Gecko didn't use to report those to the error console. - // - // TODO(emilio): Revise whether we want to keep this at all, we - // use this only for canvas, this warnings are disabled by - // default and not available on OffscreenCanvas anyways... - if let ParseErrorKind::Custom(StyleParseErrorKind::ValueError(..)) = e.kind { - let location = e.location.clone(); - let error = ContextualParseError::UnsupportedValue(input.slice_from(start), e); - context.log_css_error(location, error); - } - return None; - }, - }; - - match device { - Some(device) => { - Context::for_media_query_evaluation(device, device.quirks_mode(), |context| { - specified.to_computed_color(Some(&context)) - }) - }, - None => specified.to_computed_color(None), - } - } -} - -impl ToCss for Color { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest), - Color::Absolute(ref absolute) => absolute.to_css(dest), - Color::ColorMix(ref mix) => mix.to_css(dest), - #[cfg(feature = "gecko")] - Color::System(system) => system.to_css(dest), - #[cfg(feature = "gecko")] - Color::InheritFromBodyQuirk => Ok(()), - } - } -} - -impl Color { - /// Returns whether this color is allowed in forced-colors mode. - pub fn honored_in_forced_colors_mode(&self, allow_transparent: bool) -> bool { - match *self { - #[cfg(feature = "gecko")] - Self::InheritFromBodyQuirk => false, - Self::CurrentColor => true, - #[cfg(feature = "gecko")] - Self::System(..) => true, - Self::Absolute(ref absolute) => allow_transparent && absolute.color.alpha() == 0.0, - Self::ColorMix(ref mix) => { - mix.left.honored_in_forced_colors_mode(allow_transparent) && - mix.right.honored_in_forced_colors_mode(allow_transparent) - }, - } - } - - /// Returns currentcolor value. - #[inline] - pub fn currentcolor() -> Self { - Self::CurrentColor - } - - /// Returns transparent value. - #[inline] - pub fn transparent() -> Self { - // We should probably set authored to "transparent", but maybe it doesn't matter. - Self::from_absolute_color(AbsoluteColor::transparent()) - } - - /// Create a color from an [`AbsoluteColor`]. - pub fn from_absolute_color(color: AbsoluteColor) -> Self { - Color::Absolute(Box::new(Absolute { - color, - authored: None, - })) - } - - /// Parse a color, with quirks. - /// - /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk> - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - input.try_parse(|i| Self::parse(context, i)).or_else(|e| { - if !allow_quirks.allowed(context.quirks_mode) { - return Err(e); - } - Color::parse_quirky_color(input).map_err(|_| e) - }) - } - - /// Parse a <quirky-color> value. - /// - /// <https://quirks.spec.whatwg.org/#the-hashless-hex-color-quirk> - fn parse_quirky_color<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let (value, unit) = match *input.next()? { - Token::Number { - int_value: Some(integer), - .. - } => (integer, None), - Token::Dimension { - int_value: Some(integer), - ref unit, - .. - } => (integer, Some(unit)), - Token::Ident(ref ident) => { - if ident.len() != 3 && ident.len() != 6 { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - return cssparser::parse_hash_color(ident.as_bytes()).map_err(|()| { - location.new_custom_error(StyleParseErrorKind::UnspecifiedError) - }); - }, - ref t => { - return Err(location.new_unexpected_token_error(t.clone())); - }, - }; - if value < 0 { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - let length = if value <= 9 { - 1 - } else if value <= 99 { - 2 - } else if value <= 999 { - 3 - } else if value <= 9999 { - 4 - } else if value <= 99999 { - 5 - } else if value <= 999999 { - 6 - } else { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }; - let total = length + unit.as_ref().map_or(0, |d| d.len()); - if total > 6 { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - let mut serialization = [b'0'; 6]; - let space_padding = 6 - total; - let mut written = space_padding; - let mut buf = itoa::Buffer::new(); - let s = buf.format(value); - (&mut serialization[written..]) - .write_all(s.as_bytes()) - .unwrap(); - written += s.len(); - if let Some(unit) = unit { - written += (&mut serialization[written..]) - .write(unit.as_bytes()) - .unwrap(); - } - debug_assert_eq!(written, 6); - cssparser::parse_hash_color(&serialization) - .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -impl Color { - /// Converts this Color into a ComputedColor. - /// - /// If `context` is `None`, and the specified color requires data from - /// the context to resolve, then `None` is returned. - pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> { - Some(match *self { - Color::CurrentColor => ComputedColor::CurrentColor, - Color::Absolute(ref absolute) => ComputedColor::Absolute(absolute.color), - Color::ColorMix(ref mix) => { - use crate::values::computed::percentage::Percentage; - - let left = mix.left.to_computed_color(context)?; - let right = mix.right.to_computed_color(context)?; - - ComputedColor::from_color_mix(GenericColorMix { - interpolation: mix.interpolation, - left, - left_percentage: Percentage(mix.left_percentage.get()), - right, - right_percentage: Percentage(mix.right_percentage.get()), - normalize_weights: mix.normalize_weights, - }) - }, - #[cfg(feature = "gecko")] - Color::System(system) => system.compute(context?), - #[cfg(feature = "gecko")] - Color::InheritFromBodyQuirk => { - ComputedColor::Absolute(context?.device().body_text_color()) - }, - }) - } -} - -impl ToComputedValue for Color { - type ComputedValue = ComputedColor; - - fn to_computed_value(&self, context: &Context) -> ComputedColor { - self.to_computed_color(Some(context)).unwrap() - } - - fn from_computed_value(computed: &ComputedColor) -> Self { - match *computed { - ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()), - ComputedColor::CurrentColor => Color::CurrentColor, - ComputedColor::ColorMix(ref mix) => { - Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix))) - }, - } - } -} - -/// Specified color value for `-moz-font-smoothing-background-color`. -/// -/// This property does not support `currentcolor`. We could drop it at -/// parse-time, but it's not exposed to the web so it doesn't really matter. -/// -/// We resolve it to `transparent` instead. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct MozFontSmoothingBackgroundColor(pub Color); - -impl Parse for MozFontSmoothingBackgroundColor { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Color::parse(context, input).map(MozFontSmoothingBackgroundColor) - } -} - -impl ToComputedValue for MozFontSmoothingBackgroundColor { - type ComputedValue = AbsoluteColor; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - self.0 - .to_computed_value(context) - .resolve_to_absolute(&AbsoluteColor::transparent()) - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - MozFontSmoothingBackgroundColor(Color::from_absolute_color(*computed)) - } -} - -impl SpecifiedValueInfo for Color { - const SUPPORTED_TYPES: u8 = CssType::COLOR; - - fn collect_completion_keywords(f: KeywordsCollectFn) { - // We are not going to insert all the color names here. Caller and - // devtools should take care of them. XXX Actually, transparent - // should probably be handled that way as well. - // XXX `currentColor` should really be `currentcolor`. But let's - // keep it consistent with the old system for now. - f(&[ - "rgb", - "rgba", - "hsl", - "hsla", - "hwb", - "currentColor", - "transparent", - ]); - if static_prefs::pref!("layout.css.color-mix.enabled") { - f(&["color-mix"]); - } - if static_prefs::pref!("layout.css.more_color_4.enabled") { - f(&["color", "lab", "lch", "oklab", "oklch"]); - } - } -} - -/// Specified value for the "color" property, which resolves the `currentcolor` -/// keyword to the parent color instead of self's color. -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct ColorPropertyValue(pub Color); - -impl ToComputedValue for ColorPropertyValue { - type ComputedValue = AbsoluteColor; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - let current_color = context.builder.get_parent_inherited_text().clone_color(); - self.0 - .to_computed_value(context) - .resolve_to_absolute(¤t_color) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - ColorPropertyValue(Color::from_absolute_color(*computed).into()) - } -} - -impl Parse for ColorPropertyValue { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Color::parse_quirky(context, input, AllowQuirks::Yes).map(ColorPropertyValue) - } -} - -/// auto | <color> -pub type ColorOrAuto = GenericColorOrAuto<Color>; - -/// caret-color -pub type CaretColor = GenericCaretColor<Color>; - -impl Parse for CaretColor { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - ColorOrAuto::parse(context, input).map(GenericCaretColor) - } -} - -bitflags! { - /// Various flags to represent the color-scheme property in an efficient - /// way. - #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[repr(C)] - #[value_info(other_values = "light,dark,only")] - pub struct ColorSchemeFlags: u8 { - /// Whether the author specified `light`. - const LIGHT = 1 << 0; - /// Whether the author specified `dark`. - const DARK = 1 << 1; - /// Whether the author specified `only`. - const ONLY = 1 << 2; - } -} - -/// <https://drafts.csswg.org/css-color-adjust/#color-scheme-prop> -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -#[value_info(other_values = "normal")] -pub struct ColorScheme { - #[ignore_malloc_size_of = "Arc"] - idents: crate::ArcSlice<CustomIdent>, - bits: ColorSchemeFlags, -} - -impl ColorScheme { - /// Returns the `normal` value. - pub fn normal() -> Self { - Self { - idents: Default::default(), - bits: ColorSchemeFlags::empty(), - } - } - - /// Returns the raw bitfield. - pub fn raw_bits(&self) -> u8 { - self.bits.bits - } -} - -impl Parse for ColorScheme { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut idents = vec![]; - let mut bits = ColorSchemeFlags::empty(); - - let mut location = input.current_source_location(); - while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { - let mut is_only = false; - match_ignore_ascii_case! { &ident, - "normal" => { - if idents.is_empty() && bits.is_empty() { - return Ok(Self::normal()); - } - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }, - "light" => bits.insert(ColorSchemeFlags::LIGHT), - "dark" => bits.insert(ColorSchemeFlags::DARK), - "only" => { - if bits.intersects(ColorSchemeFlags::ONLY) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - bits.insert(ColorSchemeFlags::ONLY); - is_only = true; - }, - _ => {}, - }; - - if is_only { - if !idents.is_empty() { - // Only is allowed either at the beginning or at the end, - // but not in the middle. - break; - } - } else { - idents.push(CustomIdent::from_ident(location, &ident, &[])?); - } - location = input.current_source_location(); - } - - if idents.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(Self { - idents: crate::ArcSlice::from_iter(idents.into_iter()), - bits, - }) - } -} - -impl ToCss for ColorScheme { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.idents.is_empty() { - debug_assert!(self.bits.is_empty()); - return dest.write_str("normal"); - } - let mut first = true; - for ident in self.idents.iter() { - if !first { - dest.write_char(' ')?; - } - first = false; - ident.to_css(dest)?; - } - if self.bits.intersects(ColorSchemeFlags::ONLY) { - dest.write_str(" only")?; - } - Ok(()) - } -} - -/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum PrintColorAdjust { - /// Ignore backgrounds and darken text. - Economy, - /// Respect specified colors. - Exact, -} - -/// https://drafts.csswg.org/css-color-adjust-1/#forced-color-adjust-prop -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ForcedColorAdjust { - /// Adjust colors if needed. - Auto, - /// Respect specified colors. - None, -} diff --git a/components/style/values/specified/column.rs b/components/style/values/specified/column.rs deleted file mode 100644 index 2dd7bb0144d..00000000000 --- a/components/style/values/specified/column.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* 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/. */ - -//! Specified types for the column properties. - -use crate::values::generics::column::ColumnCount as GenericColumnCount; -use crate::values::specified::PositiveInteger; - -/// A specified type for `column-count` values. -pub type ColumnCount = GenericColumnCount<PositiveInteger>; diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs deleted file mode 100644 index 4dd949b6b1e..00000000000 --- a/components/style/values/specified/counters.rs +++ /dev/null @@ -1,281 +0,0 @@ -/* 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/. */ - -//! Specified types for counter properties. - -#[cfg(feature = "servo")] -use crate::computed_values::list_style_type::T as ListStyleType; -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::counters as generics; -use crate::values::generics::counters::CounterPair; -#[cfg(feature = "gecko")] -use crate::values::generics::CounterStyle; -use crate::values::specified::image::Image; -use crate::values::specified::Attr; -use crate::values::specified::Integer; -use crate::values::CustomIdent; -use cssparser::{Parser, Token}; -use selectors::parser::SelectorParseErrorKind; -use style_traits::{ParseError, StyleParseErrorKind}; - -#[derive(PartialEq)] -enum CounterType { - Increment, - Set, - Reset, -} - -impl CounterType { - fn default_value(&self) -> i32 { - match *self { - Self::Increment => 1, - Self::Reset | Self::Set => 0, - } - } -} - -/// A specified value for the `counter-increment` property. -pub type CounterIncrement = generics::GenericCounterIncrement<Integer>; - -impl Parse for CounterIncrement { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Self::new(parse_counters( - context, - input, - CounterType::Increment, - )?)) - } -} - -/// A specified value for the `counter-set` property. -pub type CounterSet = generics::GenericCounterSet<Integer>; - -impl Parse for CounterSet { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Self::new(parse_counters(context, input, CounterType::Set)?)) - } -} - -/// A specified value for the `counter-reset` property. -pub type CounterReset = generics::GenericCounterReset<Integer>; - -impl Parse for CounterReset { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(Self::new(parse_counters( - context, - input, - CounterType::Reset, - )?)) - } -} - -fn parse_counters<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - counter_type: CounterType, -) -> Result<Vec<CounterPair<Integer>>, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(vec![]); - } - - let mut counters = Vec::new(); - loop { - let location = input.current_source_location(); - let (name, is_reversed) = match input.next() { - Ok(&Token::Ident(ref ident)) => { - (CustomIdent::from_ident(location, ident, &["none"])?, false) - }, - Ok(&Token::Function(ref name)) - if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => - { - input.parse_nested_block(|input| { - let location = input.current_source_location(); - Ok(( - CustomIdent::from_ident(location, input.expect_ident()?, &["none"])?, - true, - )) - })? - }, - Ok(t) => { - let t = t.clone(); - return Err(location.new_unexpected_token_error(t)); - }, - Err(_) => break, - }; - - let value = match input.try_parse(|input| Integer::parse(context, input)) { - Ok(start) => { - if start.value == i32::min_value() { - // The spec says that values must be clamped to the valid range, - // and we reserve i32::min_value() as an internal magic value. - // https://drafts.csswg.org/css-lists/#auto-numbering - Integer::new(i32::min_value() + 1) - } else { - start - } - }, - _ => Integer::new(if is_reversed { - i32::min_value() - } else { - counter_type.default_value() - }), - }; - counters.push(CounterPair { - name, - value, - is_reversed, - }); - } - - if !counters.is_empty() { - Ok(counters) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -/// The specified value for the `content` property. -pub type Content = generics::GenericContent<Image>; - -/// The specified value for a content item in the `content` property. -pub type ContentItem = generics::GenericContentItem<Image>; - -impl Content { - #[cfg(feature = "servo")] - fn parse_counter_style(_: &ParserContext, input: &mut Parser) -> ListStyleType { - input - .try_parse(|input| { - input.expect_comma()?; - ListStyleType::parse(input) - }) - .unwrap_or(ListStyleType::Decimal) - } - - #[cfg(feature = "gecko")] - fn parse_counter_style(context: &ParserContext, input: &mut Parser) -> CounterStyle { - input - .try_parse(|input| { - input.expect_comma()?; - CounterStyle::parse(context, input) - }) - .unwrap_or(CounterStyle::decimal()) - } -} - -impl Parse for Content { - // normal | none | [ <string> | <counter> | open-quote | close-quote | no-open-quote | - // no-close-quote ]+ - // TODO: <uri>, attr(<identifier>) - #[cfg_attr(feature = "servo", allow(unused_mut))] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("normal")) - .is_ok() - { - return Ok(generics::Content::Normal); - } - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(generics::Content::None); - } - - let mut content = vec![]; - let mut has_alt_content = false; - loop { - { - if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) { - content.push(generics::ContentItem::Image(image)); - continue; - } - } - match input.next() { - Ok(&Token::QuotedString(ref value)) => { - content.push(generics::ContentItem::String( - value.as_ref().to_owned().into(), - )); - }, - Ok(&Token::Function(ref name)) => { - let result = match_ignore_ascii_case! { &name, - "counter" => input.parse_nested_block(|input| { - let location = input.current_source_location(); - let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?; - let style = Content::parse_counter_style(context, input); - Ok(generics::ContentItem::Counter(name, style)) - }), - "counters" => input.parse_nested_block(|input| { - let location = input.current_source_location(); - let name = CustomIdent::from_ident(location, input.expect_ident()?, &[])?; - input.expect_comma()?; - let separator = input.expect_string()?.as_ref().to_owned().into(); - let style = Content::parse_counter_style(context, input); - Ok(generics::ContentItem::Counters(name, separator, style)) - }), - "attr" => input.parse_nested_block(|input| { - Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?)) - }), - _ => { - use style_traits::StyleParseErrorKind; - let name = name.clone(); - return Err(input.new_custom_error( - StyleParseErrorKind::UnexpectedFunction(name), - )) - } - }?; - content.push(result); - }, - Ok(&Token::Ident(ref ident)) => { - content.push(match_ignore_ascii_case! { &ident, - "open-quote" => generics::ContentItem::OpenQuote, - "close-quote" => generics::ContentItem::CloseQuote, - "no-open-quote" => generics::ContentItem::NoOpenQuote, - "no-close-quote" => generics::ContentItem::NoCloseQuote, - #[cfg(feature = "gecko")] - "-moz-alt-content" => { - has_alt_content = true; - generics::ContentItem::MozAltContent - }, - #[cfg(feature = "gecko")] - "-moz-label-content" if context.chrome_rules_enabled() => { - has_alt_content = true; - generics::ContentItem::MozLabelContent - }, - _ =>{ - let ident = ident.clone(); - return Err(input.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent(ident) - )); - } - }); - }, - Err(_) => break, - Ok(t) => { - let t = t.clone(); - return Err(input.new_unexpected_token_error(t)); - }, - } - } - // We don't allow to parse `-moz-alt-content` in multiple positions. - if content.is_empty() || (has_alt_content && content.len() != 1) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(generics::Content::Items(content.into())) - } -} diff --git a/components/style/values/specified/easing.rs b/components/style/values/specified/easing.rs deleted file mode 100644 index 8021e3b7dcb..00000000000 --- a/components/style/values/specified/easing.rs +++ /dev/null @@ -1,252 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS Easing functions. -use crate::parser::{Parse, ParserContext}; -use crate::piecewise_linear::{PiecewiseLinearFunction, PiecewiseLinearFunctionBuildParameters}; -use crate::values::computed::easing::TimingFunction as ComputedTimingFunction; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics::easing::TimingFunction as GenericTimingFunction; -use crate::values::generics::easing::{StepPosition, TimingKeyword}; -use crate::values::specified::{Integer, Number, Percentage}; -use cssparser::{Delimiter, Parser, Token}; -use selectors::parser::SelectorParseErrorKind; -use std::iter::FromIterator; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// An entry for linear easing function. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct LinearStop { - /// Output of the function at the given point. - pub output: Number, - /// Playback progress at which this output is given. - #[css(skip_if = "Option::is_none")] - pub input: Option<Percentage>, -} - -/// A list of specified linear stops. -#[derive(Clone, Default, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -#[css(comma)] -pub struct LinearStops { - #[css(iterable)] - entries: crate::OwnedSlice<LinearStop>, -} - -impl LinearStops { - fn new(list: crate::OwnedSlice<LinearStop>) -> Self { - LinearStops { entries: list } - } -} - -/// A specified timing function. -pub type TimingFunction = GenericTimingFunction<Integer, Number, LinearStops>; - -#[cfg(feature = "gecko")] -fn linear_timing_function_enabled() -> bool { - static_prefs::pref!("layout.css.linear-easing-function.enabled") -} - -#[cfg(feature = "servo")] -fn linear_timing_function_enabled() -> bool { - false -} - -impl Parse for TimingFunction { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(keyword) = input.try_parse(TimingKeyword::parse) { - return Ok(GenericTimingFunction::Keyword(keyword)); - } - if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { - let position = match_ignore_ascii_case! { &ident, - "step-start" => StepPosition::Start, - "step-end" => StepPosition::End, - _ => { - return Err(input.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent(ident.clone()) - )); - }, - }; - return Ok(GenericTimingFunction::Steps(Integer::new(1), position)); - } - let location = input.current_source_location(); - let function = input.expect_function()?.clone(); - input.parse_nested_block(move |i| { - match_ignore_ascii_case! { &function, - "cubic-bezier" => Self::parse_cubic_bezier(context, i), - "steps" => Self::parse_steps(context, i), - "linear" => Self::parse_linear_function(context, i), - _ => Err(location.new_custom_error(StyleParseErrorKind::UnexpectedFunction(function.clone()))), - } - }) - } -} - -impl TimingFunction { - fn parse_cubic_bezier<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let x1 = Number::parse(context, input)?; - input.expect_comma()?; - let y1 = Number::parse(context, input)?; - input.expect_comma()?; - let x2 = Number::parse(context, input)?; - input.expect_comma()?; - let y2 = Number::parse(context, input)?; - - if x1.get() < 0.0 || x1.get() > 1.0 || x2.get() < 0.0 || x2.get() > 1.0 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(GenericTimingFunction::CubicBezier { x1, y1, x2, y2 }) - } - - fn parse_steps<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let steps = Integer::parse_positive(context, input)?; - let position = input - .try_parse(|i| { - i.expect_comma()?; - StepPosition::parse(context, i) - }) - .unwrap_or(StepPosition::End); - - // jump-none accepts a positive integer greater than 1. - // FIXME(emilio): The spec asks us to avoid rejecting it at parse - // time except until computed value time. - // - // It's not totally clear it's worth it though, and no other browser - // does this. - if position == StepPosition::JumpNone && steps.value() <= 1 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(GenericTimingFunction::Steps(steps, position)) - } - - fn parse_linear_function<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if !linear_timing_function_enabled() { - return Err(input.new_custom_error(StyleParseErrorKind::ExperimentalProperty)); - } - let mut result = vec![]; - // Closely follows `parse_comma_separated`, but can generate multiple entries for one comma-separated entry. - loop { - input.parse_until_before(Delimiter::Comma, |i| { - let mut input_start = i.try_parse(|i| Percentage::parse(context, i)).ok(); - let mut input_end = i.try_parse(|i| Percentage::parse(context, i)).ok(); - - let output = Number::parse(context, i)?; - if input_start.is_none() { - debug_assert!(input_end.is_none(), "Input end parsed without input start?"); - input_start = i.try_parse(|i| Percentage::parse(context, i)).ok(); - input_end = i.try_parse(|i| Percentage::parse(context, i)).ok(); - } - result.push(LinearStop { - output, - input: input_start.into(), - }); - if input_end.is_some() { - debug_assert!( - input_start.is_some(), - "Input end valid but not input start?" - ); - result.push(LinearStop { - output, - input: input_end.into(), - }); - } - - Ok(()) - })?; - - match input.next() { - Err(_) => break, - Ok(&Token::Comma) => continue, - Ok(_) => unreachable!(), - } - } - if result.len() < 2 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(GenericTimingFunction::LinearFunction(LinearStops::new( - crate::OwnedSlice::from(result), - ))) - } -} - -impl LinearStop { - /// Convert this type to entries that can be used to build PiecewiseLinearFunction. - pub fn to_piecewise_linear_build_parameters( - x: &LinearStop, - ) -> PiecewiseLinearFunctionBuildParameters { - (x.output.get(), x.input.map(|x| x.get())) - } -} - -// We need this for converting the specified TimingFunction into computed TimingFunction without -// Context (for some FFIs in glue.rs). In fact, we don't really need Context to get the computed -// value of TimingFunction. -impl TimingFunction { - /// Generate the ComputedTimingFunction without Context. - pub fn to_computed_value_without_context(&self) -> ComputedTimingFunction { - match &self { - GenericTimingFunction::Steps(steps, pos) => { - GenericTimingFunction::Steps(steps.value(), *pos) - }, - GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => { - GenericTimingFunction::CubicBezier { - x1: x1.get(), - y1: y1.get(), - x2: x2.get(), - y2: y2.get(), - } - }, - GenericTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword), - GenericTimingFunction::LinearFunction(steps) => { - GenericTimingFunction::LinearFunction(PiecewiseLinearFunction::from_iter( - steps - .entries - .iter() - .map(|e| LinearStop::to_piecewise_linear_build_parameters(e)), - )) - }, - } - } -} - -impl ToComputedValue for TimingFunction { - type ComputedValue = ComputedTimingFunction; - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - self.to_computed_value_without_context() - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match &computed { - ComputedTimingFunction::Steps(steps, pos) => Self::Steps(Integer::new(*steps), *pos), - ComputedTimingFunction::CubicBezier { x1, y1, x2, y2 } => Self::CubicBezier { - x1: Number::new(*x1), - y1: Number::new(*y1), - x2: Number::new(*x2), - y2: Number::new(*y2), - }, - ComputedTimingFunction::Keyword(keyword) => GenericTimingFunction::Keyword(*keyword), - ComputedTimingFunction::LinearFunction(function) => { - GenericTimingFunction::LinearFunction(LinearStops { - entries: crate::OwnedSlice::from_iter(function.iter().map(|e| LinearStop { - output: Number::new(e.y), - input: Some(Percentage::new(e.x)).into(), - })), - }) - }, - } - } -} diff --git a/components/style/values/specified/effects.rs b/components/style/values/specified/effects.rs deleted file mode 100644 index 79a19d3209c..00000000000 --- a/components/style/values/specified/effects.rs +++ /dev/null @@ -1,450 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values related to effects. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::effects::BoxShadow as ComputedBoxShadow; -use crate::values::computed::effects::SimpleShadow as ComputedSimpleShadow; -#[cfg(feature = "gecko")] -use crate::values::computed::url::ComputedUrl; -use crate::values::computed::Angle as ComputedAngle; -use crate::values::computed::CSSPixelLength as ComputedCSSPixelLength; -use crate::values::computed::Filter as ComputedFilter; -use crate::values::computed::NonNegativeLength as ComputedNonNegativeLength; -use crate::values::computed::NonNegativeNumber as ComputedNonNegativeNumber; -use crate::values::computed::ZeroToOneNumber as ComputedZeroToOneNumber; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics::effects::BoxShadow as GenericBoxShadow; -use crate::values::generics::effects::Filter as GenericFilter; -use crate::values::generics::effects::SimpleShadow as GenericSimpleShadow; -use crate::values::generics::NonNegative; -use crate::values::specified::color::Color; -use crate::values::specified::length::{Length, NonNegativeLength}; -#[cfg(feature = "gecko")] -use crate::values::specified::url::SpecifiedUrl; -use crate::values::specified::{Angle, Number, NumberOrPercentage}; -#[cfg(feature = "servo")] -use crate::values::Impossible; -use crate::Zero; -use cssparser::{self, BasicParseErrorKind, Parser, Token}; -use style_traits::{ParseError, StyleParseErrorKind, ValueParseErrorKind}; - -/// A specified value for a single shadow of the `box-shadow` property. -pub type BoxShadow = - GenericBoxShadow<Option<Color>, Length, Option<NonNegativeLength>, Option<Length>>; - -/// A specified value for a single `filter`. -#[cfg(feature = "gecko")] -pub type SpecifiedFilter = GenericFilter< - Angle, - NonNegativeFactor, - ZeroToOneFactor, - NonNegativeLength, - SimpleShadow, - SpecifiedUrl, ->; - -/// A specified value for a single `filter`. -#[cfg(feature = "servo")] -pub type SpecifiedFilter = GenericFilter< - Angle, - NonNegativeFactor, - ZeroToOneFactor, - NonNegativeLength, - SimpleShadow, - Impossible, ->; - -pub use self::SpecifiedFilter as Filter; - -/// A value for the `<factor>` parts in `Filter`. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct NonNegativeFactor(NumberOrPercentage); - -/// A value for the `<factor>` parts in `Filter` which clamps to one. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct ZeroToOneFactor(NumberOrPercentage); - -/// Clamp the value to 1 if the value is over 100%. -#[inline] -fn clamp_to_one(number: NumberOrPercentage) -> NumberOrPercentage { - match number { - NumberOrPercentage::Percentage(percent) => { - NumberOrPercentage::Percentage(percent.clamp_to_hundred()) - }, - NumberOrPercentage::Number(number) => NumberOrPercentage::Number(number.clamp_to_one()), - } -} - -macro_rules! factor_impl_common { - ($ty:ty, $computed_ty:ty) => { - impl $ty { - #[inline] - fn one() -> Self { - Self(NumberOrPercentage::Number(Number::new(1.))) - } - } - - impl ToComputedValue for $ty { - type ComputedValue = $computed_ty; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - use crate::values::computed::NumberOrPercentage; - match self.0.to_computed_value(context) { - NumberOrPercentage::Number(n) => n.into(), - NumberOrPercentage::Percentage(p) => p.0.into(), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self(NumberOrPercentage::Number( - ToComputedValue::from_computed_value(&computed.0), - )) - } - } - }; -} -factor_impl_common!(NonNegativeFactor, ComputedNonNegativeNumber); -factor_impl_common!(ZeroToOneFactor, ComputedZeroToOneNumber); - -impl Parse for NonNegativeFactor { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - NumberOrPercentage::parse_non_negative(context, input).map(Self) - } -} - -impl Parse for ZeroToOneFactor { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - NumberOrPercentage::parse_non_negative(context, input) - .map(clamp_to_one) - .map(Self) - } -} - -/// A specified value for the `drop-shadow()` filter. -pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<NonNegativeLength>>; - -impl Parse for BoxShadow { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut lengths = None; - let mut color = None; - let mut inset = false; - - loop { - if !inset { - if input - .try_parse(|input| input.expect_ident_matching("inset")) - .is_ok() - { - inset = true; - continue; - } - } - if lengths.is_none() { - let value = input.try_parse::<_, _, ParseError>(|i| { - let horizontal = Length::parse(context, i)?; - let vertical = Length::parse(context, i)?; - let (blur, spread) = - match i.try_parse(|i| Length::parse_non_negative(context, i)) { - Ok(blur) => { - let spread = i.try_parse(|i| Length::parse(context, i)).ok(); - (Some(blur.into()), spread) - }, - Err(_) => (None, None), - }; - Ok((horizontal, vertical, blur, spread)) - }); - if let Ok(value) = value { - lengths = Some(value); - continue; - } - } - if color.is_none() { - if let Ok(value) = input.try_parse(|i| Color::parse(context, i)) { - color = Some(value); - continue; - } - } - break; - } - - let lengths = - lengths.ok_or(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))?; - Ok(BoxShadow { - base: SimpleShadow { - color: color, - horizontal: lengths.0, - vertical: lengths.1, - blur: lengths.2, - }, - spread: lengths.3, - inset: inset, - }) - } -} - -impl ToComputedValue for BoxShadow { - type ComputedValue = ComputedBoxShadow; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - ComputedBoxShadow { - base: self.base.to_computed_value(context), - spread: self - .spread - .as_ref() - .unwrap_or(&Length::zero()) - .to_computed_value(context), - inset: self.inset, - } - } - - #[inline] - fn from_computed_value(computed: &ComputedBoxShadow) -> Self { - BoxShadow { - base: ToComputedValue::from_computed_value(&computed.base), - spread: Some(ToComputedValue::from_computed_value(&computed.spread)), - inset: computed.inset, - } - } -} - -// We need this for converting the specified Filter into computed Filter without Context (for -// some FFIs in glue.rs). This can fail because in some circumstances, we still need Context to -// determine the computed value. -impl Filter { - /// Generate the ComputedFilter without Context. - pub fn to_computed_value_without_context(&self) -> Result<ComputedFilter, ()> { - match *self { - Filter::Blur(ref length) => Ok(ComputedFilter::Blur(ComputedNonNegativeLength::new( - length.0.to_computed_pixel_length_without_context()?, - ))), - Filter::Brightness(ref factor) => Ok(ComputedFilter::Brightness( - ComputedNonNegativeNumber::from(factor.0.to_number().get()), - )), - Filter::Contrast(ref factor) => Ok(ComputedFilter::Contrast( - ComputedNonNegativeNumber::from(factor.0.to_number().get()), - )), - Filter::Grayscale(ref factor) => Ok(ComputedFilter::Grayscale( - ComputedZeroToOneNumber::from(factor.0.to_number().get()), - )), - Filter::HueRotate(ref angle) => Ok(ComputedFilter::HueRotate( - ComputedAngle::from_degrees(angle.degrees()), - )), - Filter::Invert(ref factor) => Ok(ComputedFilter::Invert( - ComputedZeroToOneNumber::from(factor.0.to_number().get()), - )), - Filter::Opacity(ref factor) => Ok(ComputedFilter::Opacity( - ComputedZeroToOneNumber::from(factor.0.to_number().get()), - )), - Filter::Saturate(ref factor) => Ok(ComputedFilter::Saturate( - ComputedNonNegativeNumber::from(factor.0.to_number().get()), - )), - Filter::Sepia(ref factor) => Ok(ComputedFilter::Sepia(ComputedZeroToOneNumber::from( - factor.0.to_number().get(), - ))), - Filter::DropShadow(ref shadow) => { - if cfg!(feature = "gecko") { - let color = match shadow - .color - .as_ref() - .unwrap_or(&Color::currentcolor()) - .to_computed_color(None) - { - Some(c) => c, - None => return Err(()), - }; - - let horizontal = ComputedCSSPixelLength::new( - shadow - .horizontal - .to_computed_pixel_length_without_context()?, - ); - let vertical = ComputedCSSPixelLength::new( - shadow.vertical.to_computed_pixel_length_without_context()?, - ); - let blur = ComputedNonNegativeLength::new( - shadow - .blur - .as_ref() - .unwrap_or(&NonNegativeLength::zero()) - .0 - .to_computed_pixel_length_without_context()?, - ); - - Ok(ComputedFilter::DropShadow(ComputedSimpleShadow { - color, - horizontal, - vertical, - blur, - })) - } else { - Err(()) - } - }, - #[cfg(feature = "gecko")] - Filter::Url(ref url) => Ok(ComputedFilter::Url(ComputedUrl(url.clone()))), - #[cfg(feature = "servo")] - Filter::Url(_) => Err(()), - } - } -} - -impl Parse for Filter { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - #[cfg(feature = "gecko")] - { - if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) { - return Ok(GenericFilter::Url(url)); - } - } - let location = input.current_source_location(); - let function = match input.expect_function() { - Ok(f) => f.clone(), - Err(cssparser::BasicParseError { - kind: BasicParseErrorKind::UnexpectedToken(t), - location, - }) => return Err(location.new_custom_error(ValueParseErrorKind::InvalidFilter(t))), - Err(e) => return Err(e.into()), - }; - input.parse_nested_block(|i| { - match_ignore_ascii_case! { &*function, - "blur" => Ok(GenericFilter::Blur( - i.try_parse(|i| NonNegativeLength::parse(context, i)) - .unwrap_or(Zero::zero()), - )), - "brightness" => Ok(GenericFilter::Brightness( - i.try_parse(|i| NonNegativeFactor::parse(context, i)) - .unwrap_or(NonNegativeFactor::one()), - )), - "contrast" => Ok(GenericFilter::Contrast( - i.try_parse(|i| NonNegativeFactor::parse(context, i)) - .unwrap_or(NonNegativeFactor::one()), - )), - "grayscale" => { - // Values of amount over 100% are allowed but UAs must clamp the values to 1. - // https://drafts.fxtf.org/filter-effects/#funcdef-filter-grayscale - Ok(GenericFilter::Grayscale( - i.try_parse(|i| ZeroToOneFactor::parse(context, i)) - .unwrap_or(ZeroToOneFactor::one()), - )) - }, - "hue-rotate" => { - // We allow unitless zero here, see: - // https://github.com/w3c/fxtf-drafts/issues/228 - Ok(GenericFilter::HueRotate( - i.try_parse(|i| Angle::parse_with_unitless(context, i)) - .unwrap_or(Zero::zero()), - )) - }, - "invert" => { - // Values of amount over 100% are allowed but UAs must clamp the values to 1. - // https://drafts.fxtf.org/filter-effects/#funcdef-filter-invert - Ok(GenericFilter::Invert( - i.try_parse(|i| ZeroToOneFactor::parse(context, i)) - .unwrap_or(ZeroToOneFactor::one()), - )) - }, - "opacity" => { - // Values of amount over 100% are allowed but UAs must clamp the values to 1. - // https://drafts.fxtf.org/filter-effects/#funcdef-filter-opacity - Ok(GenericFilter::Opacity( - i.try_parse(|i| ZeroToOneFactor::parse(context, i)) - .unwrap_or(ZeroToOneFactor::one()), - )) - }, - "saturate" => Ok(GenericFilter::Saturate( - i.try_parse(|i| NonNegativeFactor::parse(context, i)) - .unwrap_or(NonNegativeFactor::one()), - )), - "sepia" => { - // Values of amount over 100% are allowed but UAs must clamp the values to 1. - // https://drafts.fxtf.org/filter-effects/#funcdef-filter-sepia - Ok(GenericFilter::Sepia( - i.try_parse(|i| ZeroToOneFactor::parse(context, i)) - .unwrap_or(ZeroToOneFactor::one()), - )) - }, - "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)), - _ => Err(location.new_custom_error( - ValueParseErrorKind::InvalidFilter(Token::Function(function.clone())) - )), - } - }) - } -} - -impl Parse for SimpleShadow { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let color = input.try_parse(|i| Color::parse(context, i)).ok(); - let horizontal = Length::parse(context, input)?; - let vertical = Length::parse(context, input)?; - let blur = input - .try_parse(|i| Length::parse_non_negative(context, i)) - .ok(); - let blur = blur.map(NonNegative::<Length>); - let color = color.or_else(|| input.try_parse(|i| Color::parse(context, i)).ok()); - - Ok(SimpleShadow { - color, - horizontal, - vertical, - blur, - }) - } -} - -impl ToComputedValue for SimpleShadow { - type ComputedValue = ComputedSimpleShadow; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - ComputedSimpleShadow { - color: self - .color - .as_ref() - .unwrap_or(&Color::currentcolor()) - .to_computed_value(context), - horizontal: self.horizontal.to_computed_value(context), - vertical: self.vertical.to_computed_value(context), - blur: self - .blur - .as_ref() - .unwrap_or(&NonNegativeLength::zero()) - .to_computed_value(context), - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - SimpleShadow { - color: Some(ToComputedValue::from_computed_value(&computed.color)), - horizontal: ToComputedValue::from_computed_value(&computed.horizontal), - vertical: ToComputedValue::from_computed_value(&computed.vertical), - blur: Some(ToComputedValue::from_computed_value(&computed.blur)), - } - } -} diff --git a/components/style/values/specified/flex.rs b/components/style/values/specified/flex.rs deleted file mode 100644 index 7c767cdf34b..00000000000 --- a/components/style/values/specified/flex.rs +++ /dev/null @@ -1,25 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values related to flexbox. - -use crate::values::generics::flex::FlexBasis as GenericFlexBasis; -use crate::values::specified::Size; - -/// A specified value for the `flex-basis` property. -pub type FlexBasis = GenericFlexBasis<Size>; - -impl FlexBasis { - /// `auto` - #[inline] - pub fn auto() -> Self { - GenericFlexBasis::Size(Size::auto()) - } - - /// `0%` - #[inline] - pub fn zero_percent() -> Self { - GenericFlexBasis::Size(Size::zero_percent()) - } -} diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs deleted file mode 100644 index abb5da2c1c3..00000000000 --- a/components/style/values/specified/font.rs +++ /dev/null @@ -1,2125 +0,0 @@ -/* 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/. */ - -//! Specified values for font properties - -#[cfg(feature = "gecko")] -use crate::context::QuirksMode; -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::font::{FamilyName, FontFamilyList, SingleFontFamily}; -use crate::values::computed::Percentage as ComputedPercentage; -use crate::values::computed::{font as computed, Length, NonNegativeLength}; -use crate::values::computed::{CSSPixelLength, Context, ToComputedValue}; -use crate::values::generics::font::VariationValue; -use crate::values::generics::font::{ - self as generics, FeatureTagValue, FontSettings, FontTag, GenericFontSizeAdjust, -}; -use crate::values::generics::NonNegative; -use crate::values::specified::length::{FontBaseSize, PX_PER_PT}; -use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage}; -use crate::values::specified::{NoCalcLength, NonNegativeNumber, NonNegativePercentage, Number}; -use crate::values::{serialize_atom_identifier, CustomIdent, SelectorParseErrorKind}; -use crate::Atom; -use cssparser::{Parser, Token}; -#[cfg(feature = "gecko")] -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf}; -use std::fmt::{self, Write}; -use style_traits::values::SequenceWriter; -use style_traits::{CssWriter, KeywordsCollectFn, ParseError}; -use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -// FIXME(emilio): The system font code is copy-pasta, and should be cleaned up. -macro_rules! system_font_methods { - ($ty:ident, $field:ident) => { - system_font_methods!($ty); - - fn compute_system(&self, _context: &Context) -> <$ty as ToComputedValue>::ComputedValue { - debug_assert!(matches!(*self, $ty::System(..))); - #[cfg(feature = "gecko")] - { - _context.cached_system_font.as_ref().unwrap().$field.clone() - } - #[cfg(feature = "servo")] - { - unreachable!() - } - } - }; - - ($ty:ident) => { - /// Get a specified value that represents a system font. - pub fn system_font(f: SystemFont) -> Self { - $ty::System(f) - } - - /// Retreive a SystemFont from the specified value. - pub fn get_system(&self) -> Option<SystemFont> { - if let $ty::System(s) = *self { - Some(s) - } else { - None - } - } - }; -} - -/// System fonts. -#[repr(u8)] -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem -)] -#[allow(missing_docs)] -#[cfg(feature = "gecko")] -pub enum SystemFont { - /// https://drafts.csswg.org/css-fonts/#valdef-font-caption - Caption, - /// https://drafts.csswg.org/css-fonts/#valdef-font-icon - Icon, - /// https://drafts.csswg.org/css-fonts/#valdef-font-menu - Menu, - /// https://drafts.csswg.org/css-fonts/#valdef-font-message-box - MessageBox, - /// https://drafts.csswg.org/css-fonts/#valdef-font-small-caption - SmallCaption, - /// https://drafts.csswg.org/css-fonts/#valdef-font-status-bar - StatusBar, - /// Internal system font, used by the `<menupopup>`s on macOS. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozPullDownMenu, - /// Internal system font, used for `<button>` elements. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozButton, - /// Internal font, used by `<select>` elements. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozList, - /// Internal font, used by `<input>` elements. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozField, - #[css(skip)] - End, // Just for indexing purposes. -} - -// We don't parse system fonts in servo, but in the interest of not -// littering a lot of code with `if engine == "gecko"` conditionals, -// we have a dummy system font module that does nothing - -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem -)] -#[allow(missing_docs)] -#[cfg(feature = "servo")] -/// void enum for system font, can never exist -pub enum SystemFont {} - -#[allow(missing_docs)] -#[cfg(feature = "servo")] -impl SystemFont { - pub fn parse(_: &mut Parser) -> Result<Self, ()> { - Err(()) - } -} - -const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8; -const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71; - -/// The minimum font-weight value per: -/// -/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values -pub const MIN_FONT_WEIGHT: f32 = 1.; - -/// The maximum font-weight value per: -/// -/// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values -pub const MAX_FONT_WEIGHT: f32 = 1000.; - -/// A specified font-weight value. -/// -/// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight -#[derive( - Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -pub enum FontWeight { - /// `<font-weight-absolute>` - Absolute(AbsoluteFontWeight), - /// Bolder variant - Bolder, - /// Lighter variant - Lighter, - /// System font variant. - #[css(skip)] - System(SystemFont), -} - -impl FontWeight { - system_font_methods!(FontWeight, font_weight); - - /// `normal` - #[inline] - pub fn normal() -> Self { - FontWeight::Absolute(AbsoluteFontWeight::Normal) - } - - /// Get a specified FontWeight from a gecko keyword - pub fn from_gecko_keyword(kw: u32) -> Self { - debug_assert!(kw % 100 == 0); - debug_assert!(kw as f32 <= MAX_FONT_WEIGHT); - FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::new(kw as f32))) - } -} - -impl ToComputedValue for FontWeight { - type ComputedValue = computed::FontWeight; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - FontWeight::Absolute(ref abs) => abs.compute(), - FontWeight::Bolder => context - .builder - .get_parent_font() - .clone_font_weight() - .bolder(), - FontWeight::Lighter => context - .builder - .get_parent_font() - .clone_font_weight() - .lighter(), - FontWeight::System(_) => self.compute_system(context), - } - } - - #[inline] - fn from_computed_value(computed: &computed::FontWeight) -> Self { - FontWeight::Absolute(AbsoluteFontWeight::Weight(Number::from_computed_value( - &computed.value(), - ))) - } -} - -/// An absolute font-weight value for a @font-face rule. -/// -/// https://drafts.csswg.org/css-fonts-4/#font-weight-absolute-values -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum AbsoluteFontWeight { - /// A `<number>`, with the additional constraints specified in: - /// - /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values - Weight(Number), - /// Normal font weight. Same as 400. - Normal, - /// Bold font weight. Same as 700. - Bold, -} - -impl AbsoluteFontWeight { - /// Returns the computed value for this absolute font weight. - pub fn compute(&self) -> computed::FontWeight { - match *self { - AbsoluteFontWeight::Weight(weight) => computed::FontWeight::from_float(weight.get()), - AbsoluteFontWeight::Normal => computed::FontWeight::NORMAL, - AbsoluteFontWeight::Bold => computed::FontWeight::BOLD, - } - } -} - -impl Parse for AbsoluteFontWeight { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(number) = input.try_parse(|input| Number::parse(context, input)) { - // We could add another AllowedNumericType value, but it doesn't - // seem worth it just for a single property with such a weird range, - // so we do the clamping here manually. - if !number.was_calc() && - (number.get() < MIN_FONT_WEIGHT || number.get() > MAX_FONT_WEIGHT) - { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - return Ok(AbsoluteFontWeight::Weight(number)); - } - - Ok(try_match_ident_ignore_ascii_case! { input, - "normal" => AbsoluteFontWeight::Normal, - "bold" => AbsoluteFontWeight::Bold, - }) - } -} - -/// The specified value of the `font-style` property, without the system font -/// crap. -pub type SpecifiedFontStyle = generics::FontStyle<Angle>; - -impl ToCss for SpecifiedFontStyle { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match *self { - generics::FontStyle::Normal => dest.write_str("normal"), - generics::FontStyle::Italic => dest.write_str("italic"), - generics::FontStyle::Oblique(ref angle) => { - dest.write_str("oblique")?; - if *angle != Self::default_angle() { - dest.write_char(' ')?; - angle.to_css(dest)?; - } - Ok(()) - }, - } - } -} - -impl Parse for SpecifiedFontStyle { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(try_match_ident_ignore_ascii_case! { input, - "normal" => generics::FontStyle::Normal, - "italic" => generics::FontStyle::Italic, - "oblique" => { - let angle = input.try_parse(|input| Self::parse_angle(context, input)) - .unwrap_or_else(|_| Self::default_angle()); - - generics::FontStyle::Oblique(angle) - }, - }) - } -} - -impl ToComputedValue for SpecifiedFontStyle { - type ComputedValue = computed::FontStyle; - - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - match *self { - Self::Normal => computed::FontStyle::NORMAL, - Self::Italic => computed::FontStyle::ITALIC, - Self::Oblique(ref angle) => computed::FontStyle::oblique(angle.degrees()), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - if *computed == computed::FontStyle::NORMAL { - return Self::Normal; - } - if *computed == computed::FontStyle::ITALIC { - return Self::Italic; - } - let degrees = computed.oblique_degrees(); - generics::FontStyle::Oblique(Angle::from_degrees(degrees, /* was_calc = */ false)) - } -} - -/// From https://drafts.csswg.org/css-fonts-4/#valdef-font-style-oblique-angle: -/// -/// Values less than -90deg or values greater than 90deg are -/// invalid and are treated as parse errors. -/// -/// The maximum angle value that `font-style: oblique` should compute to. -pub const FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES: f32 = 90.; - -/// The minimum angle value that `font-style: oblique` should compute to. -pub const FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES: f32 = -90.; - -impl SpecifiedFontStyle { - /// Gets a clamped angle in degrees from a specified Angle. - pub fn compute_angle_degrees(angle: &Angle) -> f32 { - angle - .degrees() - .max(FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES) - .min(FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES) - } - - /// Parse a suitable angle for font-style: oblique. - pub fn parse_angle<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Angle, ParseError<'i>> { - let angle = Angle::parse(context, input)?; - if angle.was_calc() { - return Ok(angle); - } - - let degrees = angle.degrees(); - if degrees < FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES || - degrees > FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES - { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - return Ok(angle); - } - - /// The default angle for `font-style: oblique`. - pub fn default_angle() -> Angle { - Angle::from_degrees( - computed::FontStyle::DEFAULT_OBLIQUE_DEGREES as f32, - /* was_calc = */ false, - ) - } -} - -/// The specified value of the `font-style` property. -#[derive( - Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -#[allow(missing_docs)] -pub enum FontStyle { - Specified(SpecifiedFontStyle), - #[css(skip)] - System(SystemFont), -} - -impl FontStyle { - /// Return the `normal` value. - #[inline] - pub fn normal() -> Self { - FontStyle::Specified(generics::FontStyle::Normal) - } - - system_font_methods!(FontStyle, font_style); -} - -impl ToComputedValue for FontStyle { - type ComputedValue = computed::FontStyle; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - FontStyle::Specified(ref specified) => specified.to_computed_value(context), - FontStyle::System(..) => self.compute_system(context), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - FontStyle::Specified(SpecifiedFontStyle::from_computed_value(computed)) - } -} - -/// A value for the `font-stretch` property. -/// -/// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop -#[allow(missing_docs)] -#[derive( - Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -pub enum FontStretch { - Stretch(NonNegativePercentage), - Keyword(FontStretchKeyword), - #[css(skip)] - System(SystemFont), -} - -/// A keyword value for `font-stretch`. -#[derive( - Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -#[allow(missing_docs)] -pub enum FontStretchKeyword { - Normal, - Condensed, - UltraCondensed, - ExtraCondensed, - SemiCondensed, - SemiExpanded, - Expanded, - ExtraExpanded, - UltraExpanded, -} - -impl FontStretchKeyword { - /// Turns the keyword into a computed value. - pub fn compute(&self) -> computed::FontStretch { - computed::FontStretch::from_keyword(*self) - } - - /// Does the opposite operation to `compute`, in order to serialize keywords - /// if possible. - pub fn from_percentage(p: f32) -> Option<Self> { - computed::FontStretch::from_percentage(p).as_keyword() - } -} - -impl FontStretch { - /// `normal`. - pub fn normal() -> Self { - FontStretch::Keyword(FontStretchKeyword::Normal) - } - - system_font_methods!(FontStretch, font_stretch); -} - -impl ToComputedValue for FontStretch { - type ComputedValue = computed::FontStretch; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - FontStretch::Stretch(ref percentage) => { - let percentage = percentage.to_computed_value(context).0; - computed::FontStretch::from_percentage(percentage.0) - }, - FontStretch::Keyword(ref kw) => kw.compute(), - FontStretch::System(_) => self.compute_system(context), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative( - computed.to_percentage(), - ))) - } -} - -/// CSS font keywords -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, - Serialize, - Deserialize, -)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum FontSizeKeyword { - #[css(keyword = "xx-small")] - XXSmall, - XSmall, - Small, - Medium, - Large, - XLarge, - #[css(keyword = "xx-large")] - XXLarge, - #[css(keyword = "xxx-large")] - XXXLarge, - #[css(skip)] - None, -} - -impl FontSizeKeyword { - /// Convert to an HTML <font size> value - #[inline] - pub fn html_size(self) -> u8 { - self as u8 - } -} - -impl Default for FontSizeKeyword { - fn default() -> Self { - FontSizeKeyword::Medium - } -} - -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] -/// Additional information for keyword-derived font sizes. -pub struct KeywordInfo { - /// The keyword used - pub kw: FontSizeKeyword, - /// A factor to be multiplied by the computed size of the keyword - #[css(skip)] - pub factor: f32, - /// An additional fixed offset to add to the kw * factor in the case of - /// `calc()`. - #[css(skip)] - pub offset: CSSPixelLength, -} - -impl KeywordInfo { - /// KeywordInfo value for font-size: medium - pub fn medium() -> Self { - Self::new(FontSizeKeyword::Medium) - } - - /// KeywordInfo value for font-size: none - pub fn none() -> Self { - Self::new(FontSizeKeyword::None) - } - - fn new(kw: FontSizeKeyword) -> Self { - KeywordInfo { - kw, - factor: 1., - offset: CSSPixelLength::new(0.), - } - } - - /// Computes the final size for this font-size keyword, accounting for - /// text-zoom. - fn to_computed_value(&self, context: &Context) -> CSSPixelLength { - debug_assert_ne!(self.kw, FontSizeKeyword::None); - let base = context.maybe_zoom_text(self.kw.to_length(context).0); - base * self.factor + context.maybe_zoom_text(self.offset) - } - - /// Given a parent keyword info (self), apply an additional factor/offset to - /// it. - fn compose(self, factor: f32) -> Self { - if self.kw == FontSizeKeyword::None { - return self; - } - KeywordInfo { - kw: self.kw, - factor: self.factor * factor, - offset: self.offset * factor, - } - } -} - -impl SpecifiedValueInfo for KeywordInfo { - fn collect_completion_keywords(f: KeywordsCollectFn) { - <FontSizeKeyword as SpecifiedValueInfo>::collect_completion_keywords(f); - } -} - -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -/// A specified font-size value -pub enum FontSize { - /// A length; e.g. 10px. - Length(LengthPercentage), - /// A keyword value, along with a ratio and absolute offset. - /// The ratio in any specified keyword value - /// will be 1 (with offset 0), but we cascade keywordness even - /// after font-relative (percent and em) values - /// have been applied, which is where the ratio - /// comes in. The offset comes in if we cascaded a calc value, - /// where the font-relative portion (em and percentage) will - /// go into the ratio, and the remaining units all computed together - /// will go into the offset. - /// See bug 1355707. - Keyword(KeywordInfo), - /// font-size: smaller - Smaller, - /// font-size: larger - Larger, - /// Derived from a specified system font. - #[css(skip)] - System(SystemFont), -} - -/// Specifies a prioritized list of font family names or generic family names. -#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)] -#[cfg_attr(feature = "servo", derive(Hash))] -pub enum FontFamily { - /// List of `font-family` - #[css(comma)] - Values(#[css(iterable)] FontFamilyList), - /// System font - #[css(skip)] - System(SystemFont), -} - -impl FontFamily { - system_font_methods!(FontFamily, font_family); -} - -impl ToComputedValue for FontFamily { - type ComputedValue = computed::FontFamily; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - FontFamily::Values(ref list) => computed::FontFamily { - families: list.clone(), - is_system_font: false, - is_initial: false, - }, - FontFamily::System(_) => self.compute_system(context), - } - } - - fn from_computed_value(other: &computed::FontFamily) -> Self { - FontFamily::Values(other.families.clone()) - } -} - -#[cfg(feature = "gecko")] -impl MallocSizeOf for FontFamily { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - match *self { - FontFamily::Values(ref v) => { - // Although the family list is refcounted, we always attribute - // its size to the specified value. - v.list.unconditional_size_of(ops) - }, - FontFamily::System(_) => 0, - } - } -} - -impl Parse for FontFamily { - /// <family-name># - /// <family-name> = <string> | [ <ident>+ ] - /// TODO: <generic-family> - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<FontFamily, ParseError<'i>> { - let values = - input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?; - Ok(FontFamily::Values(FontFamilyList { - #[cfg(feature = "gecko")] - list: crate::ArcSlice::from_iter(values.into_iter()), - #[cfg(feature = "servo")] - list: values.into_boxed_slice(), - })) - } -} - -impl SpecifiedValueInfo for FontFamily {} - -/// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other -/// way around because we want the former to exclude generic family keywords. -impl Parse for FamilyName { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - match SingleFontFamily::parse(context, input) { - Ok(SingleFontFamily::FamilyName(name)) => Ok(name), - Ok(SingleFontFamily::Generic(_)) => { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - Err(e) => Err(e), - } - } -} - -/// Preserve the readability of text when font fallback occurs -pub type FontSizeAdjust = GenericFontSizeAdjust<NonNegativeNumber>; - -impl Parse for FontSizeAdjust { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - if let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { - let basis_enabled = static_prefs::pref!("layout.css.font-size-adjust.basis.enabled"); - let basis = match_ignore_ascii_case! { &ident, - "none" => return Ok(Self::None), - // Check for size adjustment basis keywords if enabled. - "ex-height" if basis_enabled => Self::ExHeight, - "cap-height" if basis_enabled => Self::CapHeight, - "ch-width" if basis_enabled => Self::ChWidth, - "ic-width" if basis_enabled => Self::IcWidth, - "ic-height" if basis_enabled => Self::IcHeight, - // Unknown (or disabled) keyword. - _ => return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent(ident) - )), - }; - let value = NonNegativeNumber::parse(context, input)?; - return Ok(basis(value)); - } - // Without a basis keyword, the number refers to the 'ex-height' metric. - let value = NonNegativeNumber::parse(context, input)?; - Ok(Self::ExHeight(value)) - } -} - -/// This is the ratio applied for font-size: larger -/// and smaller by both Firefox and Chrome -const LARGER_FONT_SIZE_RATIO: f32 = 1.2; - -/// The default font size. -pub const FONT_MEDIUM_PX: f32 = 16.0; - -impl FontSizeKeyword { - #[inline] - #[cfg(feature = "servo")] - fn to_length(&self, _: &Context) -> NonNegativeLength { - let medium = Length::new(FONT_MEDIUM_PX); - // https://drafts.csswg.org/css-fonts-3/#font-size-prop - NonNegative(match *self { - FontSizeKeyword::XXSmall => medium * 3.0 / 5.0, - FontSizeKeyword::XSmall => medium * 3.0 / 4.0, - FontSizeKeyword::Small => medium * 8.0 / 9.0, - FontSizeKeyword::Medium => medium, - FontSizeKeyword::Large => medium * 6.0 / 5.0, - FontSizeKeyword::XLarge => medium * 3.0 / 2.0, - FontSizeKeyword::XXLarge => medium * 2.0, - FontSizeKeyword::XXXLarge => medium * 3.0, - FontSizeKeyword::None => unreachable!(), - }) - } - - #[cfg(feature = "gecko")] - #[inline] - fn to_length(&self, cx: &Context) -> NonNegativeLength { - let font = cx.style().get_font(); - let family = &font.mFont.family.families; - let generic = family - .single_generic() - .unwrap_or(computed::GenericFontFamily::None); - let base_size = unsafe { - Atom::with(font.mLanguage.mRawPtr, |language| { - cx.device().base_size_for_generic(language, generic) - }) - }; - self.to_length_without_context(cx.quirks_mode, base_size) - } - - /// Resolve a keyword length without any context, with explicit arguments. - #[cfg(feature = "gecko")] - #[inline] - pub fn to_length_without_context( - &self, - quirks_mode: QuirksMode, - base_size: Length, - ) -> NonNegativeLength { - // The tables in this function are originally from - // nsRuleNode::CalcFontPointSize in Gecko: - // - // https://searchfox.org/mozilla-central/rev/c05d9d61188d32b8/layout/style/nsRuleNode.cpp#3150 - // - // Mapping from base size and HTML size to pixels - // The first index is (base_size - 9), the second is the - // HTML size. "0" is CSS keyword xx-small, not HTML size 0, - // since HTML size 0 is the same as 1. - // - // xxs xs s m l xl xxl - - // - 0/1 2 3 4 5 6 7 - static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [ - [9, 9, 9, 9, 11, 14, 18, 27], - [9, 9, 9, 10, 12, 15, 20, 30], - [9, 9, 10, 11, 13, 17, 22, 33], - [9, 9, 10, 12, 14, 18, 24, 36], - [9, 10, 12, 13, 16, 20, 26, 39], - [9, 10, 12, 14, 17, 21, 28, 42], - [9, 10, 13, 15, 18, 23, 30, 45], - [9, 10, 13, 16, 18, 24, 32, 48], - ]; - - // This table gives us compatibility with WinNav4 for the default fonts only. - // In WinNav4, the default fonts were: - // - // Times/12pt == Times/16px at 96ppi - // Courier/10pt == Courier/13px at 96ppi - // - // xxs xs s m l xl xxl - - // - 1 2 3 4 5 6 7 - static QUIRKS_FONT_SIZE_MAPPING: [[i32; 8]; 8] = [ - [9, 9, 9, 9, 11, 14, 18, 28], - [9, 9, 9, 10, 12, 15, 20, 31], - [9, 9, 9, 11, 13, 17, 22, 34], - [9, 9, 10, 12, 14, 18, 24, 37], - [9, 9, 10, 13, 16, 20, 26, 40], - [9, 9, 11, 14, 17, 21, 28, 42], - [9, 10, 12, 15, 17, 23, 30, 45], - [9, 10, 13, 16, 18, 24, 32, 48], - ]; - - static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300]; - let base_size_px = base_size.px().round() as i32; - let html_size = self.html_size() as usize; - NonNegative(if base_size_px >= 9 && base_size_px <= 16 { - let mapping = if quirks_mode == QuirksMode::Quirks { - QUIRKS_FONT_SIZE_MAPPING - } else { - FONT_SIZE_MAPPING - }; - Length::new(mapping[(base_size_px - 9) as usize][html_size] as f32) - } else { - base_size * FONT_SIZE_FACTORS[html_size] as f32 / 100.0 - }) - } -} - -impl FontSize { - /// <https://html.spec.whatwg.org/multipage/#rules-for-parsing-a-legacy-font-size> - pub fn from_html_size(size: u8) -> Self { - FontSize::Keyword(KeywordInfo::new(match size { - // If value is less than 1, let it be 1. - 0 | 1 => FontSizeKeyword::XSmall, - 2 => FontSizeKeyword::Small, - 3 => FontSizeKeyword::Medium, - 4 => FontSizeKeyword::Large, - 5 => FontSizeKeyword::XLarge, - 6 => FontSizeKeyword::XXLarge, - // If value is greater than 7, let it be 7. - _ => FontSizeKeyword::XXXLarge, - })) - } - - /// Compute it against a given base font size - pub fn to_computed_value_against( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> computed::FontSize { - use crate::values::specified::length::FontRelativeLength; - - let compose_keyword = |factor| { - context - .style() - .get_parent_font() - .clone_font_size() - .keyword_info - .compose(factor) - }; - let mut info = KeywordInfo::none(); - let size = match *self { - FontSize::Length(LengthPercentage::Length(ref l)) => { - if let NoCalcLength::FontRelative(ref value) = *l { - if let FontRelativeLength::Em(em) = *value { - // If the parent font was keyword-derived, this is - // too. Tack the em unit onto the factor - info = compose_keyword(em); - } - } - let result = l.to_computed_value_with_base_size(context, base_size); - if l.should_zoom_text() { - context.maybe_zoom_text(result) - } else { - result - } - }, - FontSize::Length(LengthPercentage::Percentage(pc)) => { - // If the parent font was keyword-derived, this is too. - // Tack the % onto the factor - info = compose_keyword(pc.0); - (base_size.resolve(context).computed_size() * pc.0).normalized() - }, - FontSize::Length(LengthPercentage::Calc(ref calc)) => { - let calc = calc.to_computed_value_zoomed(context, base_size); - calc.resolve(base_size.resolve(context).computed_size()) - }, - FontSize::Keyword(i) => { - // As a specified keyword, this is keyword derived - info = i; - i.to_computed_value(context).clamp_to_non_negative() - }, - FontSize::Smaller => { - info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO); - FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO) - .to_computed_value(context, base_size) - }, - FontSize::Larger => { - info = compose_keyword(LARGER_FONT_SIZE_RATIO); - FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO).to_computed_value(context, base_size) - }, - - FontSize::System(_) => { - #[cfg(feature = "servo")] - { - unreachable!() - } - #[cfg(feature = "gecko")] - { - context - .cached_system_font - .as_ref() - .unwrap() - .font_size - .computed_size() - } - }, - }; - computed::FontSize { - computed_size: NonNegative(size), - used_size: NonNegative(size), - keyword_info: info, - } - } -} - -impl ToComputedValue for FontSize { - type ComputedValue = computed::FontSize; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed::FontSize { - self.to_computed_value_against(context, FontBaseSize::InheritedStyle) - } - - #[inline] - fn from_computed_value(computed: &computed::FontSize) -> Self { - FontSize::Length(LengthPercentage::Length( - ToComputedValue::from_computed_value(&computed.computed_size()), - )) - } -} - -impl FontSize { - system_font_methods!(FontSize); - - /// Get initial value for specified font size. - #[inline] - pub fn medium() -> Self { - FontSize::Keyword(KeywordInfo::medium()) - } - - /// Parses a font-size, with quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<FontSize, ParseError<'i>> { - if let Ok(lp) = input - .try_parse(|i| LengthPercentage::parse_non_negative_quirky(context, i, allow_quirks)) - { - return Ok(FontSize::Length(lp)); - } - - if let Ok(kw) = input.try_parse(FontSizeKeyword::parse) { - return Ok(FontSize::Keyword(KeywordInfo::new(kw))); - } - - try_match_ident_ignore_ascii_case! { input, - "smaller" => Ok(FontSize::Smaller), - "larger" => Ok(FontSize::Larger), - } - } -} - -impl Parse for FontSize { - /// <length> | <percentage> | <absolute-size> | <relative-size> - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<FontSize, ParseError<'i>> { - FontSize::parse_quirky(context, input, AllowQuirks::No) - } -} - -bitflags! { - #[cfg_attr(feature = "servo", derive(MallocSizeOf))] - /// Flags of variant alternates in bit - struct VariantAlternatesParsingFlags: u8 { - /// None of variant alternates enabled - const NORMAL = 0; - /// Historical forms - const HISTORICAL_FORMS = 0x01; - /// Stylistic Alternates - const STYLISTIC = 0x02; - /// Stylistic Sets - const STYLESET = 0x04; - /// Character Variant - const CHARACTER_VARIANT = 0x08; - /// Swash glyphs - const SWASH = 0x10; - /// Ornaments glyphs - const ORNAMENTS = 0x20; - /// Annotation forms - const ANNOTATION = 0x40; - } -} - -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -/// Set of variant alternates -pub enum VariantAlternates { - /// Enables display of stylistic alternates - #[css(function)] - Stylistic(CustomIdent), - /// Enables display with stylistic sets - #[css(comma, function)] - Styleset(#[css(iterable)] crate::OwnedSlice<CustomIdent>), - /// Enables display of specific character variants - #[css(comma, function)] - CharacterVariant(#[css(iterable)] crate::OwnedSlice<CustomIdent>), - /// Enables display of swash glyphs - #[css(function)] - Swash(CustomIdent), - /// Enables replacement of default glyphs with ornaments - #[css(function)] - Ornaments(CustomIdent), - /// Enables display of alternate annotation forms - #[css(function)] - Annotation(CustomIdent), - /// Enables display of historical forms - HistoricalForms, -} - -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -/// List of Variant Alternates -pub struct FontVariantAlternates( - #[css(if_empty = "normal", iterable)] crate::OwnedSlice<VariantAlternates>, -); - -impl FontVariantAlternates { - /// Returns the length of all variant alternates. - pub fn len(&self) -> usize { - self.0.iter().fold(0, |acc, alternate| match *alternate { - VariantAlternates::Swash(_) | - VariantAlternates::Stylistic(_) | - VariantAlternates::Ornaments(_) | - VariantAlternates::Annotation(_) => acc + 1, - VariantAlternates::Styleset(ref slice) | - VariantAlternates::CharacterVariant(ref slice) => acc + slice.len(), - _ => acc, - }) - } -} - -impl FontVariantAlternates { - #[inline] - /// Get initial specified value with VariantAlternatesList - pub fn get_initial_specified_value() -> Self { - Default::default() - } -} - -impl Parse for FontVariantAlternates { - /// normal | - /// [ stylistic(<feature-value-name>) || - /// historical-forms || - /// styleset(<feature-value-name> #) || - /// character-variant(<feature-value-name> #) || - /// swash(<feature-value-name>) || - /// ornaments(<feature-value-name>) || - /// annotation(<feature-value-name>) ] - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<FontVariantAlternates, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("normal")) - .is_ok() - { - return Ok(Default::default()); - } - - let mut stylistic = None; - let mut historical = None; - let mut styleset = None; - let mut character_variant = None; - let mut swash = None; - let mut ornaments = None; - let mut annotation = None; - - // Parse values for the various alternate types in any order. - let mut parsed_alternates = VariantAlternatesParsingFlags::empty(); - macro_rules! check_if_parsed( - ($input:expr, $flag:path) => ( - if parsed_alternates.contains($flag) { - return Err($input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - parsed_alternates |= $flag; - ) - ); - while let Ok(_) = input.try_parse(|input| match *input.next()? { - Token::Ident(ref value) if value.eq_ignore_ascii_case("historical-forms") => { - check_if_parsed!(input, VariantAlternatesParsingFlags::HISTORICAL_FORMS); - historical = Some(VariantAlternates::HistoricalForms); - Ok(()) - }, - Token::Function(ref name) => { - let name = name.clone(); - input.parse_nested_block(|i| { - match_ignore_ascii_case! { &name, - "swash" => { - check_if_parsed!(i, VariantAlternatesParsingFlags::SWASH); - let location = i.current_source_location(); - let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; - swash = Some(VariantAlternates::Swash(ident)); - Ok(()) - }, - "stylistic" => { - check_if_parsed!(i, VariantAlternatesParsingFlags::STYLISTIC); - let location = i.current_source_location(); - let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; - stylistic = Some(VariantAlternates::Stylistic(ident)); - Ok(()) - }, - "ornaments" => { - check_if_parsed!(i, VariantAlternatesParsingFlags::ORNAMENTS); - let location = i.current_source_location(); - let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; - ornaments = Some(VariantAlternates::Ornaments(ident)); - Ok(()) - }, - "annotation" => { - check_if_parsed!(i, VariantAlternatesParsingFlags::ANNOTATION); - let location = i.current_source_location(); - let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; - annotation = Some(VariantAlternates::Annotation(ident)); - Ok(()) - }, - "styleset" => { - check_if_parsed!(i, VariantAlternatesParsingFlags::STYLESET); - let idents = i.parse_comma_separated(|i| { - let location = i.current_source_location(); - CustomIdent::from_ident(location, i.expect_ident()?, &[]) - })?; - styleset = Some(VariantAlternates::Styleset(idents.into())); - Ok(()) - }, - "character-variant" => { - check_if_parsed!(i, VariantAlternatesParsingFlags::CHARACTER_VARIANT); - let idents = i.parse_comma_separated(|i| { - let location = i.current_source_location(); - CustomIdent::from_ident(location, i.expect_ident()?, &[]) - })?; - character_variant = Some(VariantAlternates::CharacterVariant(idents.into())); - Ok(()) - }, - _ => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - }) - }, - _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - }) {} - - if parsed_alternates.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // Collect the parsed values in canonical order, so that we'll serialize correctly. - let mut alternates = Vec::new(); - macro_rules! push_if_some( - ($value:expr) => ( - if let Some(v) = $value { - alternates.push(v); - } - ) - ); - push_if_some!(stylistic); - push_if_some!(historical); - push_if_some!(styleset); - push_if_some!(character_variant); - push_if_some!(swash); - push_if_some!(ornaments); - push_if_some!(annotation); - - Ok(FontVariantAlternates(alternates.into())) - } -} - -macro_rules! impl_variant_east_asian { - { - $( - $(#[$($meta:tt)+])* - $ident:ident / $css:expr => $gecko:ident = $value:expr, - )+ - } => { - bitflags! { - #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] - /// Vairants for east asian variant - pub struct FontVariantEastAsian: u16 { - /// None of the features - const NORMAL = 0; - $( - $(#[$($meta)+])* - const $ident = $value; - )+ - } - } - - impl ToCss for FontVariantEastAsian { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("normal"); - } - - let mut writer = SequenceWriter::new(dest, " "); - $( - if self.intersects(Self::$ident) { - writer.raw_item($css)?; - } - )+ - Ok(()) - } - } - - /// Asserts that all variant-east-asian matches its NS_FONT_VARIANT_EAST_ASIAN_* value. - #[cfg(feature = "gecko")] - #[inline] - pub fn assert_variant_east_asian_matches() { - use crate::gecko_bindings::structs; - $( - debug_assert_eq!(structs::$gecko as u16, FontVariantEastAsian::$ident.bits()); - )+ - } - - impl SpecifiedValueInfo for FontVariantEastAsian { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&["normal", $($css,)+]); - } - } - } -} - -impl_variant_east_asian! { - /// Enables rendering of JIS78 forms (OpenType feature: jp78) - JIS78 / "jis78" => NS_FONT_VARIANT_EAST_ASIAN_JIS78 = 0x01, - /// Enables rendering of JIS83 forms (OpenType feature: jp83). - JIS83 / "jis83" => NS_FONT_VARIANT_EAST_ASIAN_JIS83 = 0x02, - /// Enables rendering of JIS90 forms (OpenType feature: jp90). - JIS90 / "jis90" => NS_FONT_VARIANT_EAST_ASIAN_JIS90 = 0x04, - /// Enables rendering of JIS2004 forms (OpenType feature: jp04). - JIS04 / "jis04" => NS_FONT_VARIANT_EAST_ASIAN_JIS04 = 0x08, - /// Enables rendering of simplified forms (OpenType feature: smpl). - SIMPLIFIED / "simplified" => NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED = 0x10, - /// Enables rendering of traditional forms (OpenType feature: trad). - TRADITIONAL / "traditional" => NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL = 0x20, - /// Enables rendering of full-width variants (OpenType feature: fwid). - FULL_WIDTH / "full-width" => NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH = 0x40, - /// Enables rendering of proportionally-spaced variants (OpenType feature: pwid). - PROPORTIONAL_WIDTH / "proportional-width" => NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH = 0x80, - /// Enables display of ruby variant glyphs (OpenType feature: ruby). - RUBY / "ruby" => NS_FONT_VARIANT_EAST_ASIAN_RUBY = 0x100, -} - -#[cfg(feature = "gecko")] -impl FontVariantEastAsian { - /// Obtain a specified value from a Gecko keyword value - /// - /// Intended for use with presentation attributes, not style structs - pub fn from_gecko_keyword(kw: u16) -> Self { - Self::from_bits_truncate(kw) - } - - /// Transform into gecko keyword - pub fn to_gecko_keyword(self) -> u16 { - self.bits() - } -} - -#[cfg(feature = "gecko")] -impl_gecko_keyword_conversions!(FontVariantEastAsian, u16); - -impl Parse for FontVariantEastAsian { - /// normal | [ <east-asian-variant-values> || <east-asian-width-values> || ruby ] - /// <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] - /// <east-asian-width-values> = [ full-width | proportional-width ] - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut result = Self::empty(); - - if input - .try_parse(|input| input.expect_ident_matching("normal")) - .is_ok() - { - return Ok(result); - } - - while let Ok(flag) = input.try_parse(|input| { - Ok( - match_ignore_ascii_case! { &input.expect_ident().map_err(|_| ())?, - "jis78" => - exclusive_value!((result, Self::JIS78 | Self::JIS83 | - Self::JIS90 | Self::JIS04 | - Self::SIMPLIFIED | Self::TRADITIONAL - ) => Self::JIS78), - "jis83" => - exclusive_value!((result, Self::JIS78 | Self::JIS83 | - Self::JIS90 | Self::JIS04 | - Self::SIMPLIFIED | Self::TRADITIONAL - ) => Self::JIS83), - "jis90" => - exclusive_value!((result, Self::JIS78 | Self::JIS83 | - Self::JIS90 | Self::JIS04 | - Self::SIMPLIFIED | Self::TRADITIONAL - ) => Self::JIS90), - "jis04" => - exclusive_value!((result, Self::JIS78 | Self::JIS83 | - Self::JIS90 | Self::JIS04 | - Self::SIMPLIFIED | Self::TRADITIONAL - ) => Self::JIS04), - "simplified" => - exclusive_value!((result, Self::JIS78 | Self::JIS83 | - Self::JIS90 | Self::JIS04 | - Self::SIMPLIFIED | Self::TRADITIONAL - ) => Self::SIMPLIFIED), - "traditional" => - exclusive_value!((result, Self::JIS78 | Self::JIS83 | - Self::JIS90 | Self::JIS04 | - Self::SIMPLIFIED | Self::TRADITIONAL - ) => Self::TRADITIONAL), - "full-width" => - exclusive_value!((result, Self::FULL_WIDTH | - Self::PROPORTIONAL_WIDTH - ) => Self::FULL_WIDTH), - "proportional-width" => - exclusive_value!((result, Self::FULL_WIDTH | - Self::PROPORTIONAL_WIDTH - ) => Self::PROPORTIONAL_WIDTH), - "ruby" => - exclusive_value!((result, Self::RUBY) => Self::RUBY), - _ => return Err(()), - }, - ) - }) { - result.insert(flag); - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -macro_rules! impl_variant_ligatures { - { - $( - $(#[$($meta:tt)+])* - $ident:ident / $css:expr => $gecko:ident = $value:expr, - )+ - } => { - bitflags! { - #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] - /// Variants of ligatures - pub struct FontVariantLigatures: u16 { - /// Specifies that common default features are enabled - const NORMAL = 0; - $( - $(#[$($meta)+])* - const $ident = $value; - )+ - } - } - - impl ToCss for FontVariantLigatures { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("normal"); - } - if self.contains(FontVariantLigatures::NONE) { - return dest.write_str("none"); - } - - let mut writer = SequenceWriter::new(dest, " "); - $( - if self.intersects(FontVariantLigatures::$ident) { - writer.raw_item($css)?; - } - )+ - Ok(()) - } - } - - /// Asserts that all variant-east-asian matches its NS_FONT_VARIANT_EAST_ASIAN_* value. - #[cfg(feature = "gecko")] - #[inline] - pub fn assert_variant_ligatures_matches() { - use crate::gecko_bindings::structs; - $( - debug_assert_eq!(structs::$gecko as u16, FontVariantLigatures::$ident.bits()); - )+ - } - - impl SpecifiedValueInfo for FontVariantLigatures { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&["normal", $($css,)+]); - } - } - } -} - -impl_variant_ligatures! { - /// Specifies that all types of ligatures and contextual forms - /// covered by this property are explicitly disabled - NONE / "none" => NS_FONT_VARIANT_LIGATURES_NONE = 0x01, - /// Enables display of common ligatures - COMMON_LIGATURES / "common-ligatures" => NS_FONT_VARIANT_LIGATURES_COMMON = 0x02, - /// Disables display of common ligatures - NO_COMMON_LIGATURES / "no-common-ligatures" => NS_FONT_VARIANT_LIGATURES_NO_COMMON = 0x04, - /// Enables display of discretionary ligatures - DISCRETIONARY_LIGATURES / "discretionary-ligatures" => NS_FONT_VARIANT_LIGATURES_DISCRETIONARY = 0x08, - /// Disables display of discretionary ligatures - NO_DISCRETIONARY_LIGATURES / "no-discretionary-ligatures" => NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY = 0x10, - /// Enables display of historical ligatures - HISTORICAL_LIGATURES / "historical-ligatures" => NS_FONT_VARIANT_LIGATURES_HISTORICAL = 0x20, - /// Disables display of historical ligatures - NO_HISTORICAL_LIGATURES / "no-historical-ligatures" => NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL = 0x40, - /// Enables display of contextual alternates - CONTEXTUAL / "contextual" => NS_FONT_VARIANT_LIGATURES_CONTEXTUAL = 0x80, - /// Disables display of contextual alternates - NO_CONTEXTUAL / "no-contextual" => NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL = 0x100, -} - -#[cfg(feature = "gecko")] -impl FontVariantLigatures { - /// Obtain a specified value from a Gecko keyword value - /// - /// Intended for use with presentation attributes, not style structs - pub fn from_gecko_keyword(kw: u16) -> Self { - Self::from_bits_truncate(kw) - } - - /// Transform into gecko keyword - pub fn to_gecko_keyword(self) -> u16 { - self.bits() - } -} - -#[cfg(feature = "gecko")] -impl_gecko_keyword_conversions!(FontVariantLigatures, u16); - -impl Parse for FontVariantLigatures { - /// normal | none | - /// [ <common-lig-values> || - /// <discretionary-lig-values> || - /// <historical-lig-values> || - /// <contextual-alt-values> ] - /// <common-lig-values> = [ common-ligatures | no-common-ligatures ] - /// <discretionary-lig-values> = [ discretionary-ligatures | no-discretionary-ligatures ] - /// <historical-lig-values> = [ historical-ligatures | no-historical-ligatures ] - /// <contextual-alt-values> = [ contextual | no-contextual ] - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut result = Self::empty(); - if input - .try_parse(|input| input.expect_ident_matching("normal")) - .is_ok() - { - return Ok(result); - } - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(Self::NONE); - } - - while let Ok(flag) = input.try_parse(|input| { - Ok( - match_ignore_ascii_case! { &input.expect_ident().map_err(|_| ())?, - "common-ligatures" => - exclusive_value!((result, Self::COMMON_LIGATURES | - Self::NO_COMMON_LIGATURES - ) => Self::COMMON_LIGATURES), - "no-common-ligatures" => - exclusive_value!((result, Self::COMMON_LIGATURES | - Self::NO_COMMON_LIGATURES - ) => Self::NO_COMMON_LIGATURES), - "discretionary-ligatures" => - exclusive_value!((result, Self::DISCRETIONARY_LIGATURES | - Self::NO_DISCRETIONARY_LIGATURES - ) => Self::DISCRETIONARY_LIGATURES), - "no-discretionary-ligatures" => - exclusive_value!((result, Self::DISCRETIONARY_LIGATURES | - Self::NO_DISCRETIONARY_LIGATURES - ) => Self::NO_DISCRETIONARY_LIGATURES), - "historical-ligatures" => - exclusive_value!((result, Self::HISTORICAL_LIGATURES | - Self::NO_HISTORICAL_LIGATURES - ) => Self::HISTORICAL_LIGATURES), - "no-historical-ligatures" => - exclusive_value!((result, Self::HISTORICAL_LIGATURES | - Self::NO_HISTORICAL_LIGATURES - ) => Self::NO_HISTORICAL_LIGATURES), - "contextual" => - exclusive_value!((result, Self::CONTEXTUAL | - Self::NO_CONTEXTUAL - ) => Self::CONTEXTUAL), - "no-contextual" => - exclusive_value!((result, Self::CONTEXTUAL | - Self::NO_CONTEXTUAL - ) => Self::NO_CONTEXTUAL), - _ => return Err(()), - }, - ) - }) { - result.insert(flag); - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -macro_rules! impl_variant_numeric { - { - $( - $(#[$($meta:tt)+])* - $ident:ident / $css:expr => $gecko:ident = $value:expr, - )+ - } => { - bitflags! { - #[derive(MallocSizeOf, ToComputedValue, ToResolvedValue, ToShmem)] - /// Variants of numeric values - pub struct FontVariantNumeric: u8 { - /// None of other variants are enabled. - const NORMAL = 0; - $( - $(#[$($meta)+])* - const $ident = $value; - )+ - } - } - - impl ToCss for FontVariantNumeric { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("normal"); - } - - let mut writer = SequenceWriter::new(dest, " "); - $( - if self.intersects(FontVariantNumeric::$ident) { - writer.raw_item($css)?; - } - )+ - Ok(()) - } - } - - /// Asserts that all variant-east-asian matches its NS_FONT_VARIANT_EAST_ASIAN_* value. - #[cfg(feature = "gecko")] - #[inline] - pub fn assert_variant_numeric_matches() { - use crate::gecko_bindings::structs; - $( - debug_assert_eq!(structs::$gecko as u8, FontVariantNumeric::$ident.bits()); - )+ - } - - impl SpecifiedValueInfo for FontVariantNumeric { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&["normal", $($css,)+]); - } - } - } -} - -impl_variant_numeric! { - /// Enables display of lining numerals. - LINING_NUMS / "lining-nums" => NS_FONT_VARIANT_NUMERIC_LINING = 0x01, - /// Enables display of old-style numerals. - OLDSTYLE_NUMS / "oldstyle-nums" => NS_FONT_VARIANT_NUMERIC_OLDSTYLE = 0x02, - /// Enables display of proportional numerals. - PROPORTIONAL_NUMS / "proportional-nums" => NS_FONT_VARIANT_NUMERIC_PROPORTIONAL = 0x04, - /// Enables display of tabular numerals. - TABULAR_NUMS / "tabular-nums" => NS_FONT_VARIANT_NUMERIC_TABULAR = 0x08, - /// Enables display of lining diagonal fractions. - DIAGONAL_FRACTIONS / "diagonal-fractions" => NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS = 0x10, - /// Enables display of lining stacked fractions. - STACKED_FRACTIONS / "stacked-fractions" => NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS = 0x20, - /// Enables display of letter forms used with ordinal numbers. - ORDINAL / "ordinal" => NS_FONT_VARIANT_NUMERIC_ORDINAL = 0x80, - /// Enables display of slashed zeros. - SLASHED_ZERO / "slashed-zero" => NS_FONT_VARIANT_NUMERIC_SLASHZERO = 0x40, -} - -#[cfg(feature = "gecko")] -impl FontVariantNumeric { - /// Obtain a specified value from a Gecko keyword value - /// - /// Intended for use with presentation attributes, not style structs - pub fn from_gecko_keyword(kw: u8) -> Self { - Self::from_bits_truncate(kw) - } - - /// Transform into gecko keyword - pub fn to_gecko_keyword(self) -> u8 { - self.bits() - } -} - -#[cfg(feature = "gecko")] -impl_gecko_keyword_conversions!(FontVariantNumeric, u8); - -impl Parse for FontVariantNumeric { - /// normal | - /// [ <numeric-figure-values> || - /// <numeric-spacing-values> || - /// <numeric-fraction-values> || - /// ordinal || - /// slashed-zero ] - /// <numeric-figure-values> = [ lining-nums | oldstyle-nums ] - /// <numeric-spacing-values> = [ proportional-nums | tabular-nums ] - /// <numeric-fraction-values> = [ diagonal-fractions | stacked-fractions ] - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut result = Self::empty(); - - if input - .try_parse(|input| input.expect_ident_matching("normal")) - .is_ok() - { - return Ok(result); - } - - while let Ok(flag) = input.try_parse(|input| { - Ok( - match_ignore_ascii_case! { &input.expect_ident().map_err(|_| ())?, - "ordinal" => - exclusive_value!((result, Self::ORDINAL) => Self::ORDINAL), - "slashed-zero" => - exclusive_value!((result, Self::SLASHED_ZERO) => Self::SLASHED_ZERO), - "lining-nums" => - exclusive_value!((result, Self::LINING_NUMS | - Self::OLDSTYLE_NUMS - ) => Self::LINING_NUMS), - "oldstyle-nums" => - exclusive_value!((result, Self::LINING_NUMS | - Self::OLDSTYLE_NUMS - ) => Self::OLDSTYLE_NUMS), - "proportional-nums" => - exclusive_value!((result, Self::PROPORTIONAL_NUMS | - Self::TABULAR_NUMS - ) => Self::PROPORTIONAL_NUMS), - "tabular-nums" => - exclusive_value!((result, Self::PROPORTIONAL_NUMS | - Self::TABULAR_NUMS - ) => Self::TABULAR_NUMS), - "diagonal-fractions" => - exclusive_value!((result, Self::DIAGONAL_FRACTIONS | - Self::STACKED_FRACTIONS - ) => Self::DIAGONAL_FRACTIONS), - "stacked-fractions" => - exclusive_value!((result, Self::DIAGONAL_FRACTIONS | - Self::STACKED_FRACTIONS - ) => Self::STACKED_FRACTIONS), - _ => return Err(()), - }, - ) - }) { - result.insert(flag); - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -/// This property provides low-level control over OpenType or TrueType font features. -pub type FontFeatureSettings = FontSettings<FeatureTagValue<Integer>>; - -/// For font-language-override, use the same representation as the computed value. -pub use crate::values::computed::font::FontLanguageOverride; - -impl Parse for FontLanguageOverride { - /// normal | <string> - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<FontLanguageOverride, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("normal")) - .is_ok() - { - return Ok(FontLanguageOverride::normal()); - } - - let string = input.expect_string()?; - - // The OpenType spec requires tags to be 1 to 4 ASCII characters: - // https://learn.microsoft.com/en-gb/typography/opentype/spec/otff#data-types - if string.is_empty() || string.len() > 4 || !string.is_ascii() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - let mut bytes = [b' '; 4]; - for (byte, str_byte) in bytes.iter_mut().zip(string.as_bytes()) { - *byte = *str_byte; - } - - Ok(FontLanguageOverride(u32::from_be_bytes(bytes))) - } -} - -/// A value for any of the font-synthesis-{weight,style,small-caps} properties. -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum FontSynthesis { - /// This attribute may be synthesized if not supported by a face. - Auto, - /// Do not attempt to synthesis this style attribute. - None, -} - -#[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// Allows authors to choose a palette from those supported by a color font -/// (and potentially @font-palette-values overrides). -pub struct FontPalette(Atom); - -#[allow(missing_docs)] -impl FontPalette { - pub fn normal() -> Self { - Self(atom!("normal")) - } - pub fn light() -> Self { - Self(atom!("light")) - } - pub fn dark() -> Self { - Self(atom!("dark")) - } -} - -impl Parse for FontPalette { - /// normal | light | dark | dashed-ident - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<FontPalette, ParseError<'i>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &ident, - "normal" => Ok(Self::normal()), - "light" => Ok(Self::light()), - "dark" => Ok(Self::dark()), - _ => if ident.starts_with("--") { - Ok(Self(Atom::from(ident.as_ref()))) - } else { - Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) - }, - } - } -} - -impl ToCss for FontPalette { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_atom_identifier(&self.0, dest) - } -} - -/// This property provides low-level control over OpenType or TrueType font -/// variations. -pub type FontVariationSettings = FontSettings<VariationValue<Number>>; - -fn parse_one_feature_value<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<Integer, ParseError<'i>> { - if let Ok(integer) = input.try_parse(|i| Integer::parse_non_negative(context, i)) { - return Ok(integer); - } - - try_match_ident_ignore_ascii_case! { input, - "on" => Ok(Integer::new(1)), - "off" => Ok(Integer::new(0)), - } -} - -impl Parse for FeatureTagValue<Integer> { - /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let tag = FontTag::parse(context, input)?; - let value = input - .try_parse(|i| parse_one_feature_value(context, i)) - .unwrap_or_else(|_| Integer::new(1)); - - Ok(Self { tag, value }) - } -} - -impl Parse for VariationValue<Number> { - /// This is the `<string> <number>` part of the font-variation-settings - /// syntax. - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let tag = FontTag::parse(context, input)?; - let value = Number::parse(context, input)?; - Ok(Self { tag, value }) - } -} - -/// A metrics override value for a @font-face descriptor -/// -/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc -#[derive( - Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -pub enum MetricsOverride { - /// A non-negative `<percentage>` of the computed font size - Override(NonNegativePercentage), - /// Normal metrics from the font. - Normal, -} - -impl MetricsOverride { - #[inline] - /// Get default value with `normal` - pub fn normal() -> MetricsOverride { - MetricsOverride::Normal - } - - /// The ToComputedValue implementation, used for @font-face descriptors. - /// - /// Valid override percentages must be non-negative; we return -1.0 to indicate - /// the absence of an override (i.e. 'normal'). - #[inline] - pub fn compute(&self) -> ComputedPercentage { - match *self { - MetricsOverride::Normal => ComputedPercentage(-1.0), - MetricsOverride::Override(percent) => ComputedPercentage(percent.0.get()), - } - } -} - -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -/// How to do font-size scaling. -pub enum XTextScale { - /// Both min-font-size and text zoom are enabled. - All, - /// Text-only zoom is enabled, but min-font-size is not honored. - ZoomOnly, - /// Neither of them is enabled. - None, -} - -impl XTextScale { - /// Returns whether text zoom is enabled. - #[inline] - pub fn text_zoom_enabled(self) -> bool { - self != Self::None - } -} - -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -/// Internal property that reflects the lang attribute -pub struct XLang(#[css(skip)] pub Atom); - -impl XLang { - #[inline] - /// Get default value for `-x-lang` - pub fn get_initial_value() -> XLang { - XLang(atom!("")) - } -} - -impl Parse for XLang { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<XLang, ParseError<'i>> { - debug_assert!( - false, - "Should be set directly by presentation attributes only." - ); - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -/// Specifies the minimum font size allowed due to changes in scriptlevel. -/// Ref: https://wiki.mozilla.org/MathML:mstyle -pub struct MozScriptMinSize(pub NoCalcLength); - -impl MozScriptMinSize { - #[inline] - /// Calculate initial value of -moz-script-min-size. - pub fn get_initial_value() -> Length { - Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * PX_PER_PT) - } -} - -impl Parse for MozScriptMinSize { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<MozScriptMinSize, ParseError<'i>> { - debug_assert!( - false, - "Should be set directly by presentation attributes only." - ); - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -/// A value for the `math-depth` property. -/// https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum MathDepth { - /// Increment math-depth if math-style is compact. - AutoAdd, - - /// Add the function's argument to math-depth. - #[css(function)] - Add(Integer), - - /// Set math-depth to the specified value. - Absolute(Integer), -} - -impl Parse for MathDepth { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<MathDepth, ParseError<'i>> { - if input - .try_parse(|i| i.expect_ident_matching("auto-add")) - .is_ok() - { - return Ok(MathDepth::AutoAdd); - } - if let Ok(math_depth_value) = input.try_parse(|input| Integer::parse(context, input)) { - return Ok(MathDepth::Absolute(math_depth_value)); - } - input.expect_function_matching("add")?; - let math_depth_delta_value = - input.parse_nested_block(|input| Integer::parse(context, input))?; - Ok(MathDepth::Add(math_depth_delta_value)) - } -} - -#[cfg_attr(feature = "gecko", derive(MallocSizeOf))] -#[derive( - Clone, - Copy, - Debug, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -/// Specifies the multiplier to be used to adjust font size -/// due to changes in scriptlevel. -/// -/// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs -pub struct MozScriptSizeMultiplier(pub f32); - -impl MozScriptSizeMultiplier { - #[inline] - /// Get default value of `-moz-script-size-multiplier` - pub fn get_initial_value() -> MozScriptSizeMultiplier { - MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32) - } -} - -impl Parse for MozScriptSizeMultiplier { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<MozScriptSizeMultiplier, ParseError<'i>> { - debug_assert!( - false, - "Should be set directly by presentation attributes only." - ); - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -impl From<f32> for MozScriptSizeMultiplier { - fn from(v: f32) -> Self { - MozScriptSizeMultiplier(v) - } -} - -impl From<MozScriptSizeMultiplier> for f32 { - fn from(v: MozScriptSizeMultiplier) -> f32 { - v.0 - } -} diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs deleted file mode 100644 index e721add59cf..00000000000 --- a/components/style/values/specified/gecko.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* 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/. */ - -//! Specified types for legacy Gecko-only properties. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::{self, Length, LengthPercentage}; -use crate::values::generics::rect::Rect; -use cssparser::{Parser, Token}; -use std::fmt; -use style_traits::values::SequenceWriter; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -fn parse_pixel_or_percent<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<LengthPercentage, ParseError<'i>> { - let location = input.current_source_location(); - let token = input.next()?; - let value = match *token { - Token::Dimension { - value, ref unit, .. - } => { - match_ignore_ascii_case! { unit, - "px" => Ok(LengthPercentage::new_length(Length::new(value))), - _ => Err(()), - } - }, - Token::Percentage { unit_value, .. } => Ok(LengthPercentage::new_percent( - computed::Percentage(unit_value), - )), - _ => Err(()), - }; - value.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) -} - -/// The value of an IntersectionObserver's rootMargin property. -/// -/// Only bare px or percentage values are allowed. Other length units and -/// calc() values are not allowed. -/// -/// <https://w3c.github.io/IntersectionObserver/#parse-a-root-margin> -#[repr(transparent)] -pub struct IntersectionObserverRootMargin(pub Rect<LengthPercentage>); - -impl Parse for IntersectionObserverRootMargin { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::Zero; - if input.is_exhausted() { - // If there are zero elements in tokens, set tokens to ["0px"]. - return Ok(IntersectionObserverRootMargin(Rect::all( - LengthPercentage::zero(), - ))); - } - let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?; - Ok(IntersectionObserverRootMargin(rect)) - } -} - -// Strictly speaking this is not ToCss. It's serializing for DOM. But -// we can just reuse the infrastructure of this. -// -// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-rootmargin> -impl ToCss for IntersectionObserverRootMargin { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - // We cannot use the ToCss impl of Rect, because that would - // merge items when they are equal. We want to list them all. - let mut writer = SequenceWriter::new(dest, " "); - let rect = &self.0; - writer.item(&rect.0)?; - writer.item(&rect.1)?; - writer.item(&rect.2)?; - writer.item(&rect.3) - } -} diff --git a/components/style/values/specified/grid.rs b/components/style/values/specified/grid.rs deleted file mode 100644 index 8bfa4a363bc..00000000000 --- a/components/style/values/specified/grid.rs +++ /dev/null @@ -1,349 +0,0 @@ -/* 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/. */ - -//! CSS handling for the computed value of -//! [grids](https://drafts.csswg.org/css-grid/) - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::grid::{GridTemplateComponent, ImplicitGridTracks, RepeatCount}; -use crate::values::generics::grid::{LineNameList, TrackBreadth, TrackRepeat, TrackSize}; -use crate::values::generics::grid::{TrackList, TrackListValue}; -use crate::values::specified::{Integer, LengthPercentage}; -use crate::values::{CSSFloat, CustomIdent}; -use cssparser::{ParseError as CssParseError, Parser, Token}; -use std::mem; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// Parse a single flexible length. -pub fn parse_flex<'i, 't>(input: &mut Parser<'i, 't>) -> Result<CSSFloat, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::Dimension { - value, ref unit, .. - } if unit.eq_ignore_ascii_case("fr") && value.is_sign_positive() => Ok(value), - ref t => Err(location.new_unexpected_token_error(t.clone())), - } -} - -impl<L> TrackBreadth<L> { - fn parse_keyword<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - #[derive(Parse)] - enum TrackKeyword { - Auto, - MaxContent, - MinContent, - } - - Ok(match TrackKeyword::parse(input)? { - TrackKeyword::Auto => TrackBreadth::Auto, - TrackKeyword::MaxContent => TrackBreadth::MaxContent, - TrackKeyword::MinContent => TrackBreadth::MinContent, - }) - } -} - -impl Parse for TrackBreadth<LengthPercentage> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // FIXME: This and other callers in this file should use - // NonNegativeLengthPercentage instead. - // - // Though it seems these cannot be animated so it's ~ok. - if let Ok(lp) = input.try_parse(|i| LengthPercentage::parse_non_negative(context, i)) { - return Ok(TrackBreadth::Breadth(lp)); - } - - if let Ok(f) = input.try_parse(parse_flex) { - return Ok(TrackBreadth::Fr(f)); - } - - Self::parse_keyword(input) - } -} - -impl Parse for TrackSize<LengthPercentage> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(b) = input.try_parse(|i| TrackBreadth::parse(context, i)) { - return Ok(TrackSize::Breadth(b)); - } - - if input - .try_parse(|i| i.expect_function_matching("minmax")) - .is_ok() - { - return input.parse_nested_block(|input| { - let inflexible_breadth = - match input.try_parse(|i| LengthPercentage::parse_non_negative(context, i)) { - Ok(lp) => TrackBreadth::Breadth(lp), - Err(..) => TrackBreadth::parse_keyword(input)?, - }; - - input.expect_comma()?; - Ok(TrackSize::Minmax( - inflexible_breadth, - TrackBreadth::parse(context, input)?, - )) - }); - } - - input.expect_function_matching("fit-content")?; - let lp = input.parse_nested_block(|i| LengthPercentage::parse_non_negative(context, i))?; - Ok(TrackSize::FitContent(TrackBreadth::Breadth(lp))) - } -} - -impl Parse for ImplicitGridTracks<TrackSize<LengthPercentage>> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use style_traits::{Separator, Space}; - let track_sizes = Space::parse(input, |i| TrackSize::parse(context, i))?; - if track_sizes.len() == 1 && track_sizes[0].is_initial() { - // A single track with the initial value is always represented by an empty slice. - return Ok(Default::default()); - } - return Ok(ImplicitGridTracks(track_sizes.into())); - } -} - -/// Parse the grid line names into a vector of owned strings. -/// -/// <https://drafts.csswg.org/css-grid/#typedef-line-names> -pub fn parse_line_names<'i, 't>( - input: &mut Parser<'i, 't>, -) -> Result<crate::OwnedSlice<CustomIdent>, ParseError<'i>> { - input.expect_square_bracket_block()?; - input.parse_nested_block(|input| { - let mut values = vec![]; - while let Ok((loc, ident)) = input.try_parse(|i| -> Result<_, CssParseError<()>> { - Ok((i.current_source_location(), i.expect_ident_cloned()?)) - }) { - let ident = CustomIdent::from_ident(loc, &ident, &["span", "auto"])?; - values.push(ident); - } - - Ok(values.into()) - }) -} - -/// The type of `repeat` function (only used in parsing). -/// -/// <https://drafts.csswg.org/css-grid/#typedef-track-repeat> -#[derive(Clone, Copy, Debug, PartialEq, SpecifiedValueInfo)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -enum RepeatType { - /// [`<auto-repeat>`](https://drafts.csswg.org/css-grid/#typedef-auto-repeat) - Auto, - /// [`<track-repeat>`](https://drafts.csswg.org/css-grid/#typedef-track-repeat) - Normal, - /// [`<fixed-repeat>`](https://drafts.csswg.org/css-grid/#typedef-fixed-repeat) - Fixed, -} - -impl TrackRepeat<LengthPercentage, Integer> { - fn parse_with_repeat_type<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<(Self, RepeatType), ParseError<'i>> { - input - .try_parse(|i| i.expect_function_matching("repeat").map_err(|e| e.into())) - .and_then(|_| { - input.parse_nested_block(|input| { - let count = RepeatCount::parse(context, input)?; - input.expect_comma()?; - - let is_auto = count == RepeatCount::AutoFit || count == RepeatCount::AutoFill; - let mut repeat_type = if is_auto { - RepeatType::Auto - } else { - // <fixed-size> is a subset of <track-size>, so it should work for both - RepeatType::Fixed - }; - - let mut names = vec![]; - let mut values = vec![]; - let mut current_names; - - loop { - current_names = input.try_parse(parse_line_names).unwrap_or_default(); - if let Ok(track_size) = input.try_parse(|i| TrackSize::parse(context, i)) { - if !track_size.is_fixed() { - if is_auto { - // should be <fixed-size> for <auto-repeat> - return Err(input - .new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - if repeat_type == RepeatType::Fixed { - repeat_type = RepeatType::Normal // <track-size> for sure - } - } - - values.push(track_size); - names.push(current_names); - } else { - if values.is_empty() { - // expecting at least one <track-size> - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ); - } - - names.push(current_names); // final `<line-names>` - break; // no more <track-size>, breaking - } - } - - let repeat = TrackRepeat { - count, - track_sizes: values.into(), - line_names: names.into(), - }; - - Ok((repeat, repeat_type)) - }) - }) - } -} - -impl Parse for TrackList<LengthPercentage, Integer> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut current_names = vec![]; - let mut names = vec![]; - let mut values = vec![]; - - // Whether we've parsed an `<auto-repeat>` value. - let mut auto_repeat_index = None; - // assume that everything is <fixed-size>. This flag is useful when we encounter <auto-repeat> - let mut at_least_one_not_fixed = false; - loop { - current_names - .extend_from_slice(&mut input.try_parse(parse_line_names).unwrap_or_default()); - if let Ok(track_size) = input.try_parse(|i| TrackSize::parse(context, i)) { - if !track_size.is_fixed() { - at_least_one_not_fixed = true; - if auto_repeat_index.is_some() { - // <auto-track-list> only accepts <fixed-size> and <fixed-repeat> - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - - let vec = mem::replace(&mut current_names, vec![]); - names.push(vec.into()); - values.push(TrackListValue::TrackSize(track_size)); - } else if let Ok((repeat, type_)) = - input.try_parse(|i| TrackRepeat::parse_with_repeat_type(context, i)) - { - match type_ { - RepeatType::Normal => { - at_least_one_not_fixed = true; - if auto_repeat_index.is_some() { - // only <fixed-repeat> - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ); - } - }, - RepeatType::Auto => { - if auto_repeat_index.is_some() || at_least_one_not_fixed { - // We've either seen <auto-repeat> earlier, or there's at least one non-fixed value - return Err( - input.new_custom_error(StyleParseErrorKind::UnspecifiedError) - ); - } - auto_repeat_index = Some(values.len()); - }, - RepeatType::Fixed => {}, - } - - let vec = mem::replace(&mut current_names, vec![]); - names.push(vec.into()); - values.push(TrackListValue::TrackRepeat(repeat)); - } else { - if values.is_empty() && auto_repeat_index.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - names.push(current_names.into()); - break; - } - } - - Ok(TrackList { - auto_repeat_index: auto_repeat_index.unwrap_or(std::usize::MAX), - values: values.into(), - line_names: names.into(), - }) - } -} - -#[cfg(feature = "gecko")] -#[inline] -fn allow_grid_template_subgrids() -> bool { - true -} - -#[cfg(feature = "servo")] -#[inline] -fn allow_grid_template_subgrids() -> bool { - false -} - -#[cfg(feature = "gecko")] -#[inline] -fn allow_grid_template_masonry() -> bool { - static_prefs::pref!("layout.css.grid-template-masonry-value.enabled") -} - -#[cfg(feature = "servo")] -#[inline] -fn allow_grid_template_masonry() -> bool { - false -} - -impl Parse for GridTemplateComponent<LengthPercentage, Integer> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(GridTemplateComponent::None); - } - - Self::parse_without_none(context, input) - } -} - -impl GridTemplateComponent<LengthPercentage, Integer> { - /// Parses a `GridTemplateComponent<LengthPercentage>` except `none` keyword. - pub fn parse_without_none<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if allow_grid_template_subgrids() { - if let Ok(t) = input.try_parse(|i| LineNameList::parse(context, i)) { - return Ok(GridTemplateComponent::Subgrid(Box::new(t))); - } - } - if allow_grid_template_masonry() { - if input - .try_parse(|i| i.expect_ident_matching("masonry")) - .is_ok() - { - return Ok(GridTemplateComponent::Masonry); - } - } - let track_list = TrackList::parse(context, input)?; - Ok(GridTemplateComponent::TrackList(Box::new(track_list))) - } -} diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs deleted file mode 100644 index 26f43adf0a5..00000000000 --- a/components/style/values/specified/image.rs +++ /dev/null @@ -1,1308 +0,0 @@ -/* 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/. */ - -//! CSS handling for the specified value of -//! [`image`][image]s -//! -//! [image]: https://drafts.csswg.org/css-images/#image-values - -use crate::custom_properties::SpecifiedValue; -use crate::parser::{Parse, ParserContext}; -use crate::stylesheets::CorsMode; -use crate::values::generics::image::PaintWorklet; -use crate::values::generics::image::{ - self as generic, Circle, Ellipse, GradientCompatMode, ShapeExtent, -}; -use crate::values::generics::position::Position as GenericPosition; -use crate::values::generics::NonNegative; -use crate::values::specified::position::{HorizontalPositionKeyword, VerticalPositionKeyword}; -use crate::values::specified::position::{Position, PositionComponent, Side}; -use crate::values::specified::url::SpecifiedImageUrl; -use crate::values::specified::{ - Angle, AngleOrPercentage, Color, Length, LengthPercentage, NonNegativeLength, - NonNegativeLengthPercentage, Resolution, -}; -use crate::values::specified::{Number, NumberOrPercentage, Percentage}; -use crate::Atom; -use cssparser::{Delimiter, Parser, Token}; -use selectors::parser::SelectorParseErrorKind; -use std::cmp::Ordering; -use std::fmt::{self, Write}; -use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError}; -use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -/// Specified values for an image according to CSS-IMAGES. -/// <https://drafts.csswg.org/css-images/#image-values> -pub type Image = - generic::Image<Gradient, MozImageRect, SpecifiedImageUrl, Color, Percentage, Resolution>; - -// Images should remain small, see https://github.com/servo/servo/pull/18430 -size_of_test!(Image, 40); - -/// Specified values for a CSS gradient. -/// <https://drafts.csswg.org/css-images/#gradients> -pub type Gradient = generic::Gradient< - LineDirection, - LengthPercentage, - NonNegativeLength, - NonNegativeLengthPercentage, - Position, - Angle, - AngleOrPercentage, - Color, ->; - -/// Specified values for CSS cross-fade -/// cross-fade( CrossFadeElement, ...) -/// <https://drafts.csswg.org/css-images-4/#cross-fade-function> -pub type CrossFade = generic::CrossFade<Image, Color, Percentage>; -/// CrossFadeElement = percent? CrossFadeImage -pub type CrossFadeElement = generic::CrossFadeElement<Image, Color, Percentage>; -/// CrossFadeImage = image | color -pub type CrossFadeImage = generic::CrossFadeImage<Image, Color>; - -/// `image-set()` -pub type ImageSet = generic::ImageSet<Image, Resolution>; - -/// Each of the arguments to `image-set()` -pub type ImageSetItem = generic::ImageSetItem<Image, Resolution>; - -type LengthPercentageItemList = crate::OwnedSlice<generic::GradientItem<Color, LengthPercentage>>; - -#[cfg(feature = "gecko")] -fn cross_fade_enabled() -> bool { - static_prefs::pref!("layout.css.cross-fade.enabled") -} - -#[cfg(feature = "servo")] -fn cross_fade_enabled() -> bool { - false -} - -#[cfg(feature = "gecko")] -fn image_set_enabled() -> bool { - true -} - -#[cfg(feature = "servo")] -fn image_set_enabled() -> bool { - false -} - -impl SpecifiedValueInfo for Gradient { - const SUPPORTED_TYPES: u8 = CssType::GRADIENT; - - fn collect_completion_keywords(f: KeywordsCollectFn) { - // This list here should keep sync with that in Gradient::parse. - f(&[ - "linear-gradient", - "-webkit-linear-gradient", - "-moz-linear-gradient", - "repeating-linear-gradient", - "-webkit-repeating-linear-gradient", - "-moz-repeating-linear-gradient", - "radial-gradient", - "-webkit-radial-gradient", - "-moz-radial-gradient", - "repeating-radial-gradient", - "-webkit-repeating-radial-gradient", - "-moz-repeating-radial-gradient", - "-webkit-gradient", - "conic-gradient", - "repeating-conic-gradient", - ]); - } -} - -// Need to manually implement as whether or not cross-fade shows up in -// completions & etc is dependent on it being enabled. -impl<Image, Color, Percentage> SpecifiedValueInfo for generic::CrossFade<Image, Color, Percentage> { - const SUPPORTED_TYPES: u8 = 0; - - fn collect_completion_keywords(f: KeywordsCollectFn) { - if cross_fade_enabled() { - f(&["cross-fade"]); - } - } -} - -impl<Image, Resolution> SpecifiedValueInfo for generic::ImageSet<Image, Resolution> { - const SUPPORTED_TYPES: u8 = 0; - - fn collect_completion_keywords(f: KeywordsCollectFn) { - if image_set_enabled() { - f(&["image-set"]); - } - } -} - -/// A specified gradient line direction. -/// -/// FIXME(emilio): This should be generic over Angle. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum LineDirection { - /// An angular direction. - Angle(Angle), - /// A horizontal direction. - Horizontal(HorizontalPositionKeyword), - /// A vertical direction. - Vertical(VerticalPositionKeyword), - /// A direction towards a corner of a box. - Corner(HorizontalPositionKeyword, VerticalPositionKeyword), -} - -/// A specified ending shape. -pub type EndingShape = generic::EndingShape<NonNegativeLength, NonNegativeLengthPercentage>; - -/// Specified values for `moz-image-rect` -/// -moz-image-rect(<uri>, top, right, bottom, left); -#[cfg(all(feature = "gecko", not(feature = "cbindgen")))] -pub type MozImageRect = generic::GenericMozImageRect<NumberOrPercentage, SpecifiedImageUrl>; - -#[cfg(not(feature = "gecko"))] -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -/// Empty enum on non-Gecko -pub enum MozImageRect {} - -bitflags! { - struct ParseImageFlags: u8 { - const FORBID_NONE = 1 << 0; - const FORBID_IMAGE_SET = 1 << 1; - const FORBID_NON_URL = 1 << 2; - } -} - -impl Parse for Image { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Image, ParseError<'i>> { - Image::parse_with_cors_mode(context, input, CorsMode::None, ParseImageFlags::empty()) - } -} - -impl Image { - fn parse_with_cors_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - flags: ParseImageFlags, - ) -> Result<Image, ParseError<'i>> { - if !flags.contains(ParseImageFlags::FORBID_NONE) && - input.try_parse(|i| i.expect_ident_matching("none")).is_ok() - { - return Ok(generic::Image::None); - } - - if let Ok(url) = input - .try_parse(|input| SpecifiedImageUrl::parse_with_cors_mode(context, input, cors_mode)) - { - return Ok(generic::Image::Url(url)); - } - - if !flags.contains(ParseImageFlags::FORBID_IMAGE_SET) && image_set_enabled() { - if let Ok(is) = - input.try_parse(|input| ImageSet::parse(context, input, cors_mode, flags)) - { - return Ok(generic::Image::ImageSet(Box::new(is))); - } - } - - if flags.contains(ParseImageFlags::FORBID_NON_URL) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - if let Ok(gradient) = input.try_parse(|i| Gradient::parse(context, i)) { - return Ok(generic::Image::Gradient(Box::new(gradient))); - } - - if cross_fade_enabled() { - if let Ok(cf) = - input.try_parse(|input| CrossFade::parse(context, input, cors_mode, flags)) - { - return Ok(generic::Image::CrossFade(Box::new(cf))); - } - } - #[cfg(feature = "servo")] - { - if let Ok(paint_worklet) = input.try_parse(|i| PaintWorklet::parse(context, i)) { - return Ok(generic::Image::PaintWorklet(paint_worklet)); - } - } - #[cfg(feature = "gecko")] - { - if let Ok(image_rect) = - input.try_parse(|input| MozImageRect::parse(context, input, cors_mode)) - { - return Ok(generic::Image::Rect(Box::new(image_rect))); - } - Ok(generic::Image::Element(Image::parse_element(input)?)) - } - #[cfg(not(feature = "gecko"))] - Err(input.new_error_for_next_token()) - } -} - -impl Image { - /// Creates an already specified image value from an already resolved URL - /// for insertion in the cascade. - #[cfg(feature = "servo")] - pub fn for_cascade(url: ::servo_arc::Arc<::url::Url>) -> Self { - use crate::values::CssUrl; - generic::Image::Url(CssUrl::for_cascade(url)) - } - - /// Parses a `-moz-element(# <element-id>)`. - #[cfg(feature = "gecko")] - fn parse_element<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Atom, ParseError<'i>> { - input.try_parse(|i| i.expect_function_matching("-moz-element"))?; - let location = input.current_source_location(); - input.parse_nested_block(|i| match *i.next()? { - Token::IDHash(ref id) => Ok(Atom::from(id.as_ref())), - ref t => Err(location.new_unexpected_token_error(t.clone())), - }) - } - - /// Provides an alternate method for parsing that associates the URL with - /// anonymous CORS headers. - pub fn parse_with_cors_anonymous<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Image, ParseError<'i>> { - Self::parse_with_cors_mode( - context, - input, - CorsMode::Anonymous, - ParseImageFlags::empty(), - ) - } - - /// Provides an alternate method for parsing, but forbidding `none` - pub fn parse_forbid_none<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Image, ParseError<'i>> { - Self::parse_with_cors_mode(context, input, CorsMode::None, ParseImageFlags::FORBID_NONE) - } - - /// Provides an alternate method for parsing, but only for urls. - pub fn parse_only_url<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Image, ParseError<'i>> { - Self::parse_with_cors_mode( - context, - input, - CorsMode::None, - ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_NON_URL, - ) - } -} - -impl CrossFade { - /// cross-fade() = cross-fade( <cf-image># ) - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - flags: ParseImageFlags, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("cross-fade")?; - let elements = input.parse_nested_block(|input| { - input.parse_comma_separated(|input| { - CrossFadeElement::parse(context, input, cors_mode, flags) - }) - })?; - let elements = crate::OwnedSlice::from(elements); - Ok(Self { elements }) - } -} - -impl CrossFadeElement { - fn parse_percentage<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Option<Percentage> { - // We clamp our values here as this is the way that Safari and Chrome's - // implementation handle out-of-bounds percentages but whether or not - // this behavior follows the specification is still being discussed. - // See: <https://github.com/w3c/csswg-drafts/issues/5333> - input - .try_parse(|input| Percentage::parse_non_negative(context, input)) - .ok() - .map(|p| p.clamp_to_hundred()) - } - - /// <cf-image> = <percentage>? && [ <image> | <color> ] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - flags: ParseImageFlags, - ) -> Result<Self, ParseError<'i>> { - // Try and parse a leading percent sign. - let mut percent = Self::parse_percentage(context, input); - // Parse the image - let image = CrossFadeImage::parse(context, input, cors_mode, flags)?; - // Try and parse a trailing percent sign. - if percent.is_none() { - percent = Self::parse_percentage(context, input); - } - Ok(Self { - percent: percent.into(), - image, - }) - } -} - -impl CrossFadeImage { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - flags: ParseImageFlags, - ) -> Result<Self, ParseError<'i>> { - if let Ok(image) = input.try_parse(|input| { - Image::parse_with_cors_mode( - context, - input, - cors_mode, - flags | ParseImageFlags::FORBID_NONE, - ) - }) { - return Ok(Self::Image(image)); - } - Ok(Self::Color(Color::parse(context, input)?)) - } -} - -impl ImageSet { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - flags: ParseImageFlags, - ) -> Result<Self, ParseError<'i>> { - let function = input.expect_function()?; - match_ignore_ascii_case! { &function, - "-webkit-image-set" | "image-set" => {}, - _ => { - let func = function.clone(); - return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func))); - } - } - let items = input.parse_nested_block(|input| { - input.parse_comma_separated(|input| { - ImageSetItem::parse(context, input, cors_mode, flags) - }) - })?; - Ok(Self { - selected_index: std::usize::MAX, - items: items.into(), - }) - } -} - -impl ImageSetItem { - fn parse_type<'i>(p: &mut Parser<'i, '_>) -> Result<crate::OwnedStr, ParseError<'i>> { - p.expect_function_matching("type")?; - p.parse_nested_block(|input| Ok(input.expect_string()?.as_ref().to_owned().into())) - } - - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - flags: ParseImageFlags, - ) -> Result<Self, ParseError<'i>> { - let image = match input.try_parse(|i| i.expect_url_or_string()) { - Ok(url) => Image::Url(SpecifiedImageUrl::parse_from_string( - url.as_ref().into(), - context, - cors_mode, - )), - Err(..) => Image::parse_with_cors_mode( - context, - input, - cors_mode, - flags | ParseImageFlags::FORBID_NONE | ParseImageFlags::FORBID_IMAGE_SET, - )?, - }; - - let mut resolution = input - .try_parse(|input| Resolution::parse(context, input)) - .ok(); - let mime_type = input.try_parse(Self::parse_type).ok(); - - // Try to parse resolution after type(). - if mime_type.is_some() && resolution.is_none() { - resolution = input - .try_parse(|input| Resolution::parse(context, input)) - .ok(); - } - - let resolution = resolution.unwrap_or_else(|| Resolution::from_x(1.0)); - let has_mime_type = mime_type.is_some(); - let mime_type = mime_type.unwrap_or_default(); - - Ok(Self { - image, - resolution, - has_mime_type, - mime_type, - }) - } -} - -impl Parse for Gradient { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - enum Shape { - Linear, - Radial, - Conic, - } - - let func = input.expect_function()?; - let (shape, repeating, compat_mode) = match_ignore_ascii_case! { &func, - "linear-gradient" => { - (Shape::Linear, false, GradientCompatMode::Modern) - }, - "-webkit-linear-gradient" => { - (Shape::Linear, false, GradientCompatMode::WebKit) - }, - #[cfg(feature = "gecko")] - "-moz-linear-gradient" => { - (Shape::Linear, false, GradientCompatMode::Moz) - }, - "repeating-linear-gradient" => { - (Shape::Linear, true, GradientCompatMode::Modern) - }, - "-webkit-repeating-linear-gradient" => { - (Shape::Linear, true, GradientCompatMode::WebKit) - }, - #[cfg(feature = "gecko")] - "-moz-repeating-linear-gradient" => { - (Shape::Linear, true, GradientCompatMode::Moz) - }, - "radial-gradient" => { - (Shape::Radial, false, GradientCompatMode::Modern) - }, - "-webkit-radial-gradient" => { - (Shape::Radial, false, GradientCompatMode::WebKit) - }, - #[cfg(feature = "gecko")] - "-moz-radial-gradient" => { - (Shape::Radial, false, GradientCompatMode::Moz) - }, - "repeating-radial-gradient" => { - (Shape::Radial, true, GradientCompatMode::Modern) - }, - "-webkit-repeating-radial-gradient" => { - (Shape::Radial, true, GradientCompatMode::WebKit) - }, - #[cfg(feature = "gecko")] - "-moz-repeating-radial-gradient" => { - (Shape::Radial, true, GradientCompatMode::Moz) - }, - "conic-gradient" => { - (Shape::Conic, false, GradientCompatMode::Modern) - }, - "repeating-conic-gradient" => { - (Shape::Conic, true, GradientCompatMode::Modern) - }, - "-webkit-gradient" => { - return input.parse_nested_block(|i| { - Self::parse_webkit_gradient_argument(context, i) - }); - }, - _ => { - let func = func.clone(); - return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedFunction(func))); - } - }; - - Ok(input.parse_nested_block(|i| { - Ok(match shape { - Shape::Linear => Self::parse_linear(context, i, repeating, compat_mode)?, - Shape::Radial => Self::parse_radial(context, i, repeating, compat_mode)?, - Shape::Conic => Self::parse_conic(context, i, repeating)?, - }) - })?) - } -} - -impl Gradient { - fn parse_webkit_gradient_argument<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::specified::position::{ - HorizontalPositionKeyword as X, VerticalPositionKeyword as Y, - }; - type Point = GenericPosition<Component<X>, Component<Y>>; - - #[derive(Clone, Copy, Parse)] - enum Component<S> { - Center, - Number(NumberOrPercentage), - Side(S), - } - - impl LineDirection { - fn from_points(first: Point, second: Point) -> Self { - let h_ord = first.horizontal.partial_cmp(&second.horizontal); - let v_ord = first.vertical.partial_cmp(&second.vertical); - let (h, v) = match (h_ord, v_ord) { - (Some(h), Some(v)) => (h, v), - _ => return LineDirection::Vertical(Y::Bottom), - }; - match (h, v) { - (Ordering::Less, Ordering::Less) => LineDirection::Corner(X::Right, Y::Bottom), - (Ordering::Less, Ordering::Equal) => LineDirection::Horizontal(X::Right), - (Ordering::Less, Ordering::Greater) => LineDirection::Corner(X::Right, Y::Top), - (Ordering::Equal, Ordering::Greater) => LineDirection::Vertical(Y::Top), - (Ordering::Equal, Ordering::Equal) | (Ordering::Equal, Ordering::Less) => { - LineDirection::Vertical(Y::Bottom) - }, - (Ordering::Greater, Ordering::Less) => { - LineDirection::Corner(X::Left, Y::Bottom) - }, - (Ordering::Greater, Ordering::Equal) => LineDirection::Horizontal(X::Left), - (Ordering::Greater, Ordering::Greater) => { - LineDirection::Corner(X::Left, Y::Top) - }, - } - } - } - - impl From<Point> for Position { - fn from(point: Point) -> Self { - Self::new(point.horizontal.into(), point.vertical.into()) - } - } - - impl Parse for Point { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.try_parse(|i| { - let x = Component::parse(context, i)?; - let y = Component::parse(context, i)?; - - Ok(Self::new(x, y)) - }) - } - } - - impl<S: Side> From<Component<S>> for NumberOrPercentage { - fn from(component: Component<S>) -> Self { - match component { - Component::Center => NumberOrPercentage::Percentage(Percentage::new(0.5)), - Component::Number(number) => number, - Component::Side(side) => { - let p = if side.is_start() { - Percentage::zero() - } else { - Percentage::hundred() - }; - NumberOrPercentage::Percentage(p) - }, - } - } - } - - impl<S: Side> From<Component<S>> for PositionComponent<S> { - fn from(component: Component<S>) -> Self { - match component { - Component::Center => PositionComponent::Center, - Component::Number(NumberOrPercentage::Number(number)) => { - PositionComponent::Length(Length::from_px(number.value).into()) - }, - Component::Number(NumberOrPercentage::Percentage(p)) => { - PositionComponent::Length(p.into()) - }, - Component::Side(side) => PositionComponent::Side(side, None), - } - } - } - - impl<S: Copy + Side> Component<S> { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - match ( - NumberOrPercentage::from(*self), - NumberOrPercentage::from(*other), - ) { - (NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => { - a.get().partial_cmp(&b.get()) - }, - (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => { - a.value.partial_cmp(&b.value) - }, - (_, _) => None, - } - } - } - - let ident = input.expect_ident_cloned()?; - input.expect_comma()?; - - Ok(match_ignore_ascii_case! { &ident, - "linear" => { - let first = Point::parse(context, input)?; - input.expect_comma()?; - let second = Point::parse(context, input)?; - - let direction = LineDirection::from_points(first, second); - let items = Gradient::parse_webkit_gradient_stops(context, input, false)?; - - generic::Gradient::Linear { - direction, - items, - repeating: false, - compat_mode: GradientCompatMode::Modern, - } - }, - "radial" => { - let first_point = Point::parse(context, input)?; - input.expect_comma()?; - let first_radius = Number::parse_non_negative(context, input)?; - input.expect_comma()?; - let second_point = Point::parse(context, input)?; - input.expect_comma()?; - let second_radius = Number::parse_non_negative(context, input)?; - - let (reverse_stops, point, radius) = if second_radius.value >= first_radius.value { - (false, second_point, second_radius) - } else { - (true, first_point, first_radius) - }; - - let rad = Circle::Radius(NonNegative(Length::from_px(radius.value))); - let shape = generic::EndingShape::Circle(rad); - let position: Position = point.into(); - let items = Gradient::parse_webkit_gradient_stops(context, input, reverse_stops)?; - - generic::Gradient::Radial { - shape, - position, - items, - repeating: false, - compat_mode: GradientCompatMode::Modern, - } - }, - _ => { - let e = SelectorParseErrorKind::UnexpectedIdent(ident.clone()); - return Err(input.new_custom_error(e)); - }, - }) - } - - fn parse_webkit_gradient_stops<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - reverse_stops: bool, - ) -> Result<LengthPercentageItemList, ParseError<'i>> { - let mut items = input - .try_parse(|i| { - i.expect_comma()?; - i.parse_comma_separated(|i| { - let function = i.expect_function()?.clone(); - let (color, mut p) = i.parse_nested_block(|i| { - let p = match_ignore_ascii_case! { &function, - "color-stop" => { - let p = NumberOrPercentage::parse(context, i)?.to_percentage(); - i.expect_comma()?; - p - }, - "from" => Percentage::zero(), - "to" => Percentage::hundred(), - _ => { - return Err(i.new_custom_error( - StyleParseErrorKind::UnexpectedFunction(function.clone()) - )) - }, - }; - let color = Color::parse(context, i)?; - if color == Color::CurrentColor { - return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok((color.into(), p)) - })?; - if reverse_stops { - p.reverse(); - } - Ok(generic::GradientItem::ComplexColorStop { - color, - position: p.into(), - }) - }) - }) - .unwrap_or(vec![]); - - if items.is_empty() { - items = vec![ - generic::GradientItem::ComplexColorStop { - color: Color::transparent(), - position: LengthPercentage::zero_percent(), - }, - generic::GradientItem::ComplexColorStop { - color: Color::transparent(), - position: LengthPercentage::hundred_percent(), - }, - ]; - } else if items.len() == 1 { - let first = items[0].clone(); - items.push(first); - } else { - items.sort_by(|a, b| { - match (a, b) { - ( - &generic::GradientItem::ComplexColorStop { - position: ref a_position, - .. - }, - &generic::GradientItem::ComplexColorStop { - position: ref b_position, - .. - }, - ) => match (a_position, b_position) { - (&LengthPercentage::Percentage(a), &LengthPercentage::Percentage(b)) => { - return a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal); - }, - _ => {}, - }, - _ => {}, - } - if reverse_stops { - Ordering::Greater - } else { - Ordering::Less - } - }) - } - Ok(items.into()) - } - - /// Not used for -webkit-gradient syntax and conic-gradient - fn parse_stops<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<LengthPercentageItemList, ParseError<'i>> { - let items = - generic::GradientItem::parse_comma_separated(context, input, LengthPercentage::parse)?; - if items.len() < 2 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(items) - } - - /// Parses a linear gradient. - /// GradientCompatMode can change during `-moz-` prefixed gradient parsing if it come across a `to` keyword. - fn parse_linear<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - repeating: bool, - mut compat_mode: GradientCompatMode, - ) -> Result<Self, ParseError<'i>> { - let direction = if let Ok(d) = - input.try_parse(|i| LineDirection::parse(context, i, &mut compat_mode)) - { - input.expect_comma()?; - d - } else { - match compat_mode { - GradientCompatMode::Modern => { - LineDirection::Vertical(VerticalPositionKeyword::Bottom) - }, - _ => LineDirection::Vertical(VerticalPositionKeyword::Top), - } - }; - let items = Gradient::parse_stops(context, input)?; - - Ok(Gradient::Linear { - direction, - items, - repeating, - compat_mode, - }) - } - - /// Parses a radial gradient. - fn parse_radial<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - repeating: bool, - compat_mode: GradientCompatMode, - ) -> Result<Self, ParseError<'i>> { - let (shape, position) = match compat_mode { - GradientCompatMode::Modern => { - let shape = input.try_parse(|i| EndingShape::parse(context, i, compat_mode)); - let position = input.try_parse(|i| { - i.expect_ident_matching("at")?; - Position::parse(context, i) - }); - (shape, position.ok()) - }, - _ => { - let position = input.try_parse(|i| Position::parse(context, i)); - let shape = input.try_parse(|i| { - if position.is_ok() { - i.expect_comma()?; - } - EndingShape::parse(context, i, compat_mode) - }); - (shape, position.ok()) - }, - }; - - if shape.is_ok() || position.is_some() { - input.expect_comma()?; - } - - let shape = shape.unwrap_or({ - generic::EndingShape::Ellipse(Ellipse::Extent(ShapeExtent::FarthestCorner)) - }); - - let position = position.unwrap_or(Position::center()); - - let items = Gradient::parse_stops(context, input)?; - - Ok(Gradient::Radial { - shape, - position, - items, - repeating, - compat_mode, - }) - } - fn parse_conic<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - repeating: bool, - ) -> Result<Self, ParseError<'i>> { - let angle = input.try_parse(|i| { - i.expect_ident_matching("from")?; - // Spec allows unitless zero start angles - // https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle - Angle::parse_with_unitless(context, i) - }); - let position = input.try_parse(|i| { - i.expect_ident_matching("at")?; - Position::parse(context, i) - }); - if angle.is_ok() || position.is_ok() { - input.expect_comma()?; - } - - let angle = angle.unwrap_or(Angle::zero()); - let position = position.unwrap_or(Position::center()); - let items = generic::GradientItem::parse_comma_separated( - context, - input, - AngleOrPercentage::parse_with_unitless, - )?; - - if cfg!(feature = "servo") || items.len() < 2 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(Gradient::Conic { - angle, - position, - items, - repeating, - }) - } -} - -impl generic::LineDirection for LineDirection { - fn points_downwards(&self, compat_mode: GradientCompatMode) -> bool { - match *self { - LineDirection::Angle(ref angle) => angle.degrees() == 180.0, - LineDirection::Vertical(VerticalPositionKeyword::Bottom) => { - compat_mode == GradientCompatMode::Modern - }, - LineDirection::Vertical(VerticalPositionKeyword::Top) => { - compat_mode != GradientCompatMode::Modern - }, - _ => false, - } - } - - fn to_css<W>(&self, dest: &mut CssWriter<W>, compat_mode: GradientCompatMode) -> fmt::Result - where - W: Write, - { - match *self { - LineDirection::Angle(angle) => angle.to_css(dest), - LineDirection::Horizontal(x) => { - if compat_mode == GradientCompatMode::Modern { - dest.write_str("to ")?; - } - x.to_css(dest) - }, - LineDirection::Vertical(y) => { - if compat_mode == GradientCompatMode::Modern { - dest.write_str("to ")?; - } - y.to_css(dest) - }, - LineDirection::Corner(x, y) => { - if compat_mode == GradientCompatMode::Modern { - dest.write_str("to ")?; - } - x.to_css(dest)?; - dest.write_char(' ')?; - y.to_css(dest) - }, - } - } -} - -impl LineDirection { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - compat_mode: &mut GradientCompatMode, - ) -> Result<Self, ParseError<'i>> { - // Gradients allow unitless zero angles as an exception, see: - // https://github.com/w3c/csswg-drafts/issues/1162 - if let Ok(angle) = input.try_parse(|i| Angle::parse_with_unitless(context, i)) { - return Ok(LineDirection::Angle(angle)); - } - - input.try_parse(|i| { - let to_ident = i.try_parse(|i| i.expect_ident_matching("to")); - match *compat_mode { - // `to` keyword is mandatory in modern syntax. - GradientCompatMode::Modern => to_ident?, - // Fall back to Modern compatibility mode in case there is a `to` keyword. - // According to Gecko, `-moz-linear-gradient(to ...)` should serialize like - // `linear-gradient(to ...)`. - GradientCompatMode::Moz if to_ident.is_ok() => { - *compat_mode = GradientCompatMode::Modern - }, - // There is no `to` keyword in webkit prefixed syntax. If it's consumed, - // parsing should throw an error. - GradientCompatMode::WebKit if to_ident.is_ok() => { - return Err( - i.new_custom_error(SelectorParseErrorKind::UnexpectedIdent("to".into())) - ); - }, - _ => {}, - } - - if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) { - if let Ok(y) = i.try_parse(VerticalPositionKeyword::parse) { - return Ok(LineDirection::Corner(x, y)); - } - return Ok(LineDirection::Horizontal(x)); - } - let y = VerticalPositionKeyword::parse(i)?; - if let Ok(x) = i.try_parse(HorizontalPositionKeyword::parse) { - return Ok(LineDirection::Corner(x, y)); - } - Ok(LineDirection::Vertical(y)) - }) - } -} - -impl EndingShape { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - compat_mode: GradientCompatMode, - ) -> Result<Self, ParseError<'i>> { - if let Ok(extent) = input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) - { - if input - .try_parse(|i| i.expect_ident_matching("circle")) - .is_ok() - { - return Ok(generic::EndingShape::Circle(Circle::Extent(extent))); - } - let _ = input.try_parse(|i| i.expect_ident_matching("ellipse")); - return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent))); - } - if input - .try_parse(|i| i.expect_ident_matching("circle")) - .is_ok() - { - if let Ok(extent) = - input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) - { - return Ok(generic::EndingShape::Circle(Circle::Extent(extent))); - } - if compat_mode == GradientCompatMode::Modern { - if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) { - return Ok(generic::EndingShape::Circle(Circle::Radius(length))); - } - } - return Ok(generic::EndingShape::Circle(Circle::Extent( - ShapeExtent::FarthestCorner, - ))); - } - if input - .try_parse(|i| i.expect_ident_matching("ellipse")) - .is_ok() - { - if let Ok(extent) = - input.try_parse(|i| ShapeExtent::parse_with_compat_mode(i, compat_mode)) - { - return Ok(generic::EndingShape::Ellipse(Ellipse::Extent(extent))); - } - if compat_mode == GradientCompatMode::Modern { - let pair: Result<_, ParseError> = input.try_parse(|i| { - let x = NonNegativeLengthPercentage::parse(context, i)?; - let y = NonNegativeLengthPercentage::parse(context, i)?; - Ok((x, y)) - }); - if let Ok((x, y)) = pair { - return Ok(generic::EndingShape::Ellipse(Ellipse::Radii(x, y))); - } - } - return Ok(generic::EndingShape::Ellipse(Ellipse::Extent( - ShapeExtent::FarthestCorner, - ))); - } - if let Ok(length) = input.try_parse(|i| NonNegativeLength::parse(context, i)) { - if let Ok(y) = input.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) { - if compat_mode == GradientCompatMode::Modern { - let _ = input.try_parse(|i| i.expect_ident_matching("ellipse")); - } - return Ok(generic::EndingShape::Ellipse(Ellipse::Radii( - NonNegative(LengthPercentage::from(length.0)), - y, - ))); - } - if compat_mode == GradientCompatMode::Modern { - let y = input.try_parse(|i| { - i.expect_ident_matching("ellipse")?; - NonNegativeLengthPercentage::parse(context, i) - }); - if let Ok(y) = y { - return Ok(generic::EndingShape::Ellipse(Ellipse::Radii( - NonNegative(LengthPercentage::from(length.0)), - y, - ))); - } - let _ = input.try_parse(|i| i.expect_ident_matching("circle")); - } - - return Ok(generic::EndingShape::Circle(Circle::Radius(length))); - } - input.try_parse(|i| { - let x = Percentage::parse_non_negative(context, i)?; - let y = if let Ok(y) = i.try_parse(|i| NonNegativeLengthPercentage::parse(context, i)) { - if compat_mode == GradientCompatMode::Modern { - let _ = i.try_parse(|i| i.expect_ident_matching("ellipse")); - } - y - } else { - if compat_mode == GradientCompatMode::Modern { - i.expect_ident_matching("ellipse")?; - } - NonNegativeLengthPercentage::parse(context, i)? - }; - Ok(generic::EndingShape::Ellipse(Ellipse::Radii( - NonNegative(LengthPercentage::from(x)), - y, - ))) - }) - } -} - -impl ShapeExtent { - fn parse_with_compat_mode<'i, 't>( - input: &mut Parser<'i, 't>, - compat_mode: GradientCompatMode, - ) -> Result<Self, ParseError<'i>> { - match Self::parse(input)? { - ShapeExtent::Contain | ShapeExtent::Cover - if compat_mode == GradientCompatMode::Modern => - { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - ShapeExtent::Contain => Ok(ShapeExtent::ClosestSide), - ShapeExtent::Cover => Ok(ShapeExtent::FarthestCorner), - keyword => Ok(keyword), - } - } -} - -impl<T> generic::GradientItem<Color, T> { - fn parse_comma_separated<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - parse_position: impl for<'i1, 't1> Fn(&ParserContext, &mut Parser<'i1, 't1>) -> Result<T, ParseError<'i1>> - + Copy, - ) -> Result<crate::OwnedSlice<Self>, ParseError<'i>> { - let mut items = Vec::new(); - let mut seen_stop = false; - - loop { - input.parse_until_before(Delimiter::Comma, |input| { - if seen_stop { - if let Ok(hint) = input.try_parse(|i| parse_position(context, i)) { - seen_stop = false; - items.push(generic::GradientItem::InterpolationHint(hint)); - return Ok(()); - } - } - - let stop = generic::ColorStop::parse(context, input, parse_position)?; - - if let Ok(multi_position) = input.try_parse(|i| parse_position(context, i)) { - let stop_color = stop.color.clone(); - items.push(stop.into_item()); - items.push( - generic::ColorStop { - color: stop_color, - position: Some(multi_position), - } - .into_item(), - ); - } else { - items.push(stop.into_item()); - } - - seen_stop = true; - Ok(()) - })?; - - match input.next() { - Err(_) => break, - Ok(&Token::Comma) => continue, - Ok(_) => unreachable!(), - } - } - - if !seen_stop || items.len() < 2 { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(items.into()) - } -} - -impl<T> generic::ColorStop<Color, T> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - parse_position: impl for<'i1, 't1> Fn( - &ParserContext, - &mut Parser<'i1, 't1>, - ) -> Result<T, ParseError<'i1>>, - ) -> Result<Self, ParseError<'i>> { - Ok(generic::ColorStop { - color: Color::parse(context, input)?, - position: input.try_parse(|i| parse_position(context, i)).ok(), - }) - } -} - -impl Parse for PaintWorklet { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("paint")?; - input.parse_nested_block(|input| { - let name = Atom::from(&**input.expect_ident()?); - let arguments = input - .try_parse(|input| { - input.expect_comma()?; - input.parse_comma_separated(|input| SpecifiedValue::parse(input)) - }) - .unwrap_or(vec![]); - Ok(PaintWorklet { name, arguments }) - }) - } -} - -impl MozImageRect { - #[cfg(feature = "gecko")] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - cors_mode: CorsMode, - ) -> Result<Self, ParseError<'i>> { - input.try_parse(|i| i.expect_function_matching("-moz-image-rect"))?; - input.parse_nested_block(|i| { - let string = i.expect_url_or_string()?; - let url = SpecifiedImageUrl::parse_from_string( - string.as_ref().to_owned(), - context, - cors_mode, - ); - i.expect_comma()?; - let top = NumberOrPercentage::parse_non_negative(context, i)?; - i.expect_comma()?; - let right = NumberOrPercentage::parse_non_negative(context, i)?; - i.expect_comma()?; - let bottom = NumberOrPercentage::parse_non_negative(context, i)?; - i.expect_comma()?; - let left = NumberOrPercentage::parse_non_negative(context, i)?; - Ok(MozImageRect { - url, - top, - right, - bottom, - left, - }) - }) - } -} - -/// https://drafts.csswg.org/css-images/#propdef-image-rendering -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ImageRendering { - Auto, - #[cfg(feature = "gecko")] - Smooth, - #[parse(aliases = "-moz-crisp-edges")] - CrispEdges, - Pixelated, - // From the spec: - // - // This property previously accepted the values optimizeSpeed and - // optimizeQuality. These are now deprecated; a user agent must accept - // them as valid values but must treat them as having the same behavior - // as crisp-edges and smooth respectively, and authors must not use - // them. - // - #[cfg(feature = "gecko")] - Optimizespeed, - #[cfg(feature = "gecko")] - Optimizequality, -} diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs deleted file mode 100644 index 48928a8576d..00000000000 --- a/components/style/values/specified/length.rs +++ /dev/null @@ -1,1849 +0,0 @@ -/* 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/. */ - -//! [Length values][length]. -//! -//! [length]: https://drafts.csswg.org/css-values/#lengths - -use super::{AllowQuirks, Number, Percentage, ToComputedValue}; -use crate::computed_value_flags::ComputedValueFlags; -use crate::font_metrics::{FontMetrics, FontMetricsOrientation}; -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::{self, CSSPixelLength, Context}; -use crate::values::generics::length as generics; -use crate::values::generics::length::{ - GenericLengthOrNumber, GenericLengthPercentageOrNormal, GenericMaxSize, GenericSize, -}; -use crate::values::generics::NonNegative; -use crate::values::specified::calc::{self, CalcNode}; -use crate::values::specified::NonNegativeNumber; -use crate::values::CSSFloat; -use crate::{Zero, ZeroNoPercent}; -use app_units::Au; -use cssparser::{Parser, Token}; -use std::cmp; -use std::fmt::{self, Write}; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -pub use super::image::Image; -pub use super::image::{EndingShape as GradientEndingShape, Gradient}; -pub use crate::values::specified::calc::CalcLengthPercentage; - -/// Number of pixels per inch -pub const PX_PER_IN: CSSFloat = 96.; -/// Number of pixels per centimeter -pub const PX_PER_CM: CSSFloat = PX_PER_IN / 2.54; -/// Number of pixels per millimeter -pub const PX_PER_MM: CSSFloat = PX_PER_IN / 25.4; -/// Number of pixels per quarter -pub const PX_PER_Q: CSSFloat = PX_PER_MM / 4.; -/// Number of pixels per point -pub const PX_PER_PT: CSSFloat = PX_PER_IN / 72.; -/// Number of pixels per pica -pub const PX_PER_PC: CSSFloat = PX_PER_PT * 12.; - -/// A font relative length. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] -pub enum FontRelativeLength { - /// A "em" value: https://drafts.csswg.org/css-values/#em - #[css(dimension)] - Em(CSSFloat), - /// A "ex" value: https://drafts.csswg.org/css-values/#ex - #[css(dimension)] - Ex(CSSFloat), - /// A "ch" value: https://drafts.csswg.org/css-values/#ch - #[css(dimension)] - Ch(CSSFloat), - /// A "cap" value: https://drafts.csswg.org/css-values/#cap - #[css(dimension)] - Cap(CSSFloat), - /// An "ic" value: https://drafts.csswg.org/css-values/#ic - #[css(dimension)] - Ic(CSSFloat), - /// A "rem" value: https://drafts.csswg.org/css-values/#rem - #[css(dimension)] - Rem(CSSFloat), -} - -/// A source to resolve font-relative units against -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum FontBaseSize { - /// Use the font-size of the current element. - CurrentStyle, - /// Use the inherited font-size. - InheritedStyle, -} - -impl FontBaseSize { - /// Calculate the actual size for a given context - pub fn resolve(&self, context: &Context) -> computed::FontSize { - match *self { - Self::CurrentStyle => context.style().get_font().clone_font_size(), - Self::InheritedStyle => context.style().get_parent_font().clone_font_size(), - } - } -} - -impl FontRelativeLength { - /// Return the unitless, raw value. - fn unitless_value(&self) -> CSSFloat { - match *self { - Self::Em(v) | Self::Ex(v) | Self::Ch(v) | Self::Cap(v) | Self::Ic(v) | Self::Rem(v) => { - v - }, - } - } - - // Return the unit, as a string. - fn unit(&self) -> &'static str { - match *self { - Self::Em(_) => "em", - Self::Ex(_) => "ex", - Self::Ch(_) => "ch", - Self::Cap(_) => "cap", - Self::Ic(_) => "ic", - Self::Rem(_) => "rem", - } - } - - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - use self::FontRelativeLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - - Ok(match (self, other) { - (&Em(one), &Em(other)) => Em(op(one, other)), - (&Ex(one), &Ex(other)) => Ex(op(one, other)), - (&Ch(one), &Ch(other)) => Ch(op(one, other)), - (&Cap(one), &Cap(other)) => Cap(op(one, other)), - (&Ic(one), &Ic(other)) => Ic(op(one, other)), - (&Rem(one), &Rem(other)) => Rem(op(one, other)), - // See https://github.com/rust-lang/rust/issues/68867. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, - } - debug_unreachable!("Forgot to handle unit in try_op()") - }, - }) - } - - fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self { - match self { - Self::Em(x) => Self::Em(op(*x)), - Self::Ex(x) => Self::Ex(op(*x)), - Self::Ch(x) => Self::Ch(op(*x)), - Self::Cap(x) => Self::Cap(op(*x)), - Self::Ic(x) => Self::Ic(op(*x)), - Self::Rem(x) => Self::Rem(op(*x)), - } - } - - /// Computes the font-relative length. - pub fn to_computed_value( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> computed::Length { - let (reference_size, length) = self.reference_font_size_and_length(context, base_size); - (reference_size * length).finite() - } - - /// Return reference font size. - /// - /// We use the base_size flag to pass a different size for computing - /// font-size and unconstrained font-size. - /// - /// This returns a pair, the first one is the reference font size, and the - /// second one is the unpacked relative length. - fn reference_font_size_and_length( - &self, - context: &Context, - base_size: FontBaseSize, - ) -> (computed::Length, CSSFloat) { - fn query_font_metrics( - context: &Context, - base_size: FontBaseSize, - orientation: FontMetricsOrientation, - ) -> FontMetrics { - let retrieve_math_scales = false; - context.query_font_metrics(base_size, orientation, retrieve_math_scales) - } - - let reference_font_size = base_size.resolve(context); - match *self { - Self::Em(length) => { - if context.for_non_inherited_property && base_size == FontBaseSize::CurrentStyle { - context - .rule_cache_conditions - .borrow_mut() - .set_font_size_dependency(reference_font_size.computed_size); - } - - (reference_font_size.computed_size(), length) - }, - Self::Ex(length) => { - // The x-height is an intrinsically horizontal metric. - let metrics = - query_font_metrics(context, base_size, FontMetricsOrientation::Horizontal); - let reference_size = metrics.x_height.unwrap_or_else(|| { - // https://drafts.csswg.org/css-values/#ex - // - // In the cases where it is impossible or impractical to - // determine the x-height, a value of 0.5em must be - // assumed. - // - // (But note we use 0.5em of the used, not computed - // font-size) - reference_font_size.used_size() * 0.5 - }); - (reference_size, length) - }, - Self::Ch(length) => { - // https://drafts.csswg.org/css-values/#ch: - // - // Equal to the used advance measure of the “0” (ZERO, - // U+0030) glyph in the font used to render it. (The advance - // measure of a glyph is its advance width or height, - // whichever is in the inline axis of the element.) - // - let metrics = query_font_metrics( - context, - base_size, - FontMetricsOrientation::MatchContextPreferHorizontal, - ); - let reference_size = metrics.zero_advance_measure.unwrap_or_else(|| { - // https://drafts.csswg.org/css-values/#ch - // - // In the cases where it is impossible or impractical to - // determine the measure of the “0” glyph, it must be - // assumed to be 0.5em wide by 1em tall. Thus, the ch - // unit falls back to 0.5em in the general case, and to - // 1em when it would be typeset upright (i.e. - // writing-mode is vertical-rl or vertical-lr and - // text-orientation is upright). - // - // Same caveat about computed vs. used font-size applies - // above. - let wm = context.style().writing_mode; - if wm.is_vertical() && wm.is_upright() { - reference_font_size.used_size() - } else { - reference_font_size.used_size() * 0.5 - } - }); - (reference_size, length) - }, - Self::Cap(length) => { - let metrics = - query_font_metrics(context, base_size, FontMetricsOrientation::Horizontal); - let reference_size = metrics.cap_height.unwrap_or_else(|| { - // https://drafts.csswg.org/css-values/#cap - // - // In the cases where it is impossible or impractical to - // determine the cap-height, the font’s ascent must be - // used. - // - metrics.ascent - }); - (reference_size, length) - }, - Self::Ic(length) => { - let metrics = query_font_metrics( - context, - base_size, - FontMetricsOrientation::MatchContextPreferVertical, - ); - let reference_size = metrics.ic_width.unwrap_or_else(|| { - // https://drafts.csswg.org/css-values/#ic - // - // In the cases where it is impossible or impractical to - // determine the ideographic advance measure, it must be - // assumed to be 1em. - // - // Same caveat about computed vs. used as for other - // metric-dependent units. - reference_font_size.used_size() - }); - (reference_size, length) - }, - Self::Rem(length) => { - // https://drafts.csswg.org/css-values/#rem: - // - // When specified on the font-size property of the root - // element, the rem units refer to the property's initial - // value. - // - let reference_size = if context.builder.is_root_element || context.in_media_query { - reference_font_size.computed_size() - } else { - context.device().root_font_size() - }; - (reference_size, length) - }, - } - } -} - -/// https://drafts.csswg.org/css-values/#viewport-variants -pub enum ViewportVariant { - /// https://drafts.csswg.org/css-values/#ua-default-viewport-size - UADefault, - /// https://drafts.csswg.org/css-values/#small-viewport-percentage-units - Small, - /// https://drafts.csswg.org/css-values/#large-viewport-percentage-units - Large, - /// https://drafts.csswg.org/css-values/#dynamic-viewport-percentage-units - Dynamic, -} - -/// https://drafts.csswg.org/css-values/#viewport-relative-units -#[derive(PartialEq)] -enum ViewportUnit { - /// *vw units. - Vw, - /// *vh units. - Vh, - /// *vmin units. - Vmin, - /// *vmax units. - Vmax, - /// *vb units. - Vb, - /// *vi units. - Vi, -} - -/// A viewport-relative length. -/// -/// <https://drafts.csswg.org/css-values/#viewport-relative-lengths> -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] -pub enum ViewportPercentageLength { - /// <https://drafts.csswg.org/css-values/#valdef-length-vw> - #[css(dimension)] - Vw(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-svw> - #[css(dimension)] - Svw(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-lvw> - #[css(dimension)] - Lvw(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-dvw> - #[css(dimension)] - Dvw(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-vh> - #[css(dimension)] - Vh(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-svh> - #[css(dimension)] - Svh(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-lvh> - #[css(dimension)] - Lvh(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-dvh> - #[css(dimension)] - Dvh(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-vmin> - #[css(dimension)] - Vmin(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-svmin> - #[css(dimension)] - Svmin(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-lvmin> - #[css(dimension)] - Lvmin(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-dvmin> - #[css(dimension)] - Dvmin(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-vmax> - #[css(dimension)] - Vmax(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-svmax> - #[css(dimension)] - Svmax(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-lvmax> - #[css(dimension)] - Lvmax(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-dvmax> - #[css(dimension)] - Dvmax(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-vb> - #[css(dimension)] - Vb(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-svb> - #[css(dimension)] - Svb(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-lvb> - #[css(dimension)] - Lvb(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-dvb> - #[css(dimension)] - Dvb(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-vi> - #[css(dimension)] - Vi(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-svi> - #[css(dimension)] - Svi(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-lvi> - #[css(dimension)] - Lvi(CSSFloat), - /// <https://drafts.csswg.org/css-values/#valdef-length-dvi> - #[css(dimension)] - Dvi(CSSFloat), -} - -impl ViewportPercentageLength { - /// Return the unitless, raw value. - fn unitless_value(&self) -> CSSFloat { - self.unpack().2 - } - - // Return the unit, as a string. - fn unit(&self) -> &'static str { - match *self { - Self::Vw(_) => "vw", - Self::Lvw(_) => "lvw", - Self::Svw(_) => "svw", - Self::Dvw(_) => "dvw", - Self::Vh(_) => "vh", - Self::Svh(_) => "svh", - Self::Lvh(_) => "lvh", - Self::Dvh(_) => "dvh", - Self::Vmin(_) => "vmin", - Self::Svmin(_) => "svmin", - Self::Lvmin(_) => "lvmin", - Self::Dvmin(_) => "dvmin", - Self::Vmax(_) => "vmax", - Self::Svmax(_) => "svmax", - Self::Lvmax(_) => "lvmax", - Self::Dvmax(_) => "dvmax", - Self::Vb(_) => "vb", - Self::Svb(_) => "svb", - Self::Lvb(_) => "lvb", - Self::Dvb(_) => "dvb", - Self::Vi(_) => "vi", - Self::Svi(_) => "svi", - Self::Lvi(_) => "lvi", - Self::Dvi(_) => "dvi", - } - } - - fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) { - match *self { - Self::Vw(v) => (ViewportVariant::UADefault, ViewportUnit::Vw, v), - Self::Svw(v) => (ViewportVariant::Small, ViewportUnit::Vw, v), - Self::Lvw(v) => (ViewportVariant::Large, ViewportUnit::Vw, v), - Self::Dvw(v) => (ViewportVariant::Dynamic, ViewportUnit::Vw, v), - Self::Vh(v) => (ViewportVariant::UADefault, ViewportUnit::Vh, v), - Self::Svh(v) => (ViewportVariant::Small, ViewportUnit::Vh, v), - Self::Lvh(v) => (ViewportVariant::Large, ViewportUnit::Vh, v), - Self::Dvh(v) => (ViewportVariant::Dynamic, ViewportUnit::Vh, v), - Self::Vmin(v) => (ViewportVariant::UADefault, ViewportUnit::Vmin, v), - Self::Svmin(v) => (ViewportVariant::Small, ViewportUnit::Vmin, v), - Self::Lvmin(v) => (ViewportVariant::Large, ViewportUnit::Vmin, v), - Self::Dvmin(v) => (ViewportVariant::Dynamic, ViewportUnit::Vmin, v), - Self::Vmax(v) => (ViewportVariant::UADefault, ViewportUnit::Vmax, v), - Self::Svmax(v) => (ViewportVariant::Small, ViewportUnit::Vmax, v), - Self::Lvmax(v) => (ViewportVariant::Large, ViewportUnit::Vmax, v), - Self::Dvmax(v) => (ViewportVariant::Dynamic, ViewportUnit::Vmax, v), - Self::Vb(v) => (ViewportVariant::UADefault, ViewportUnit::Vb, v), - Self::Svb(v) => (ViewportVariant::Small, ViewportUnit::Vb, v), - Self::Lvb(v) => (ViewportVariant::Large, ViewportUnit::Vb, v), - Self::Dvb(v) => (ViewportVariant::Dynamic, ViewportUnit::Vb, v), - Self::Vi(v) => (ViewportVariant::UADefault, ViewportUnit::Vi, v), - Self::Svi(v) => (ViewportVariant::Small, ViewportUnit::Vi, v), - Self::Lvi(v) => (ViewportVariant::Large, ViewportUnit::Vi, v), - Self::Dvi(v) => (ViewportVariant::Dynamic, ViewportUnit::Vi, v), - } - } - - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - use self::ViewportPercentageLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - - Ok(match (self, other) { - (&Vw(one), &Vw(other)) => Vw(op(one, other)), - (&Svw(one), &Svw(other)) => Svw(op(one, other)), - (&Lvw(one), &Lvw(other)) => Lvw(op(one, other)), - (&Dvw(one), &Dvw(other)) => Dvw(op(one, other)), - (&Vh(one), &Vh(other)) => Vh(op(one, other)), - (&Svh(one), &Svh(other)) => Svh(op(one, other)), - (&Lvh(one), &Lvh(other)) => Lvh(op(one, other)), - (&Dvh(one), &Dvh(other)) => Dvh(op(one, other)), - (&Vmin(one), &Vmin(other)) => Vmin(op(one, other)), - (&Svmin(one), &Svmin(other)) => Svmin(op(one, other)), - (&Lvmin(one), &Lvmin(other)) => Lvmin(op(one, other)), - (&Dvmin(one), &Dvmin(other)) => Dvmin(op(one, other)), - (&Vmax(one), &Vmax(other)) => Vmax(op(one, other)), - (&Svmax(one), &Svmax(other)) => Svmax(op(one, other)), - (&Lvmax(one), &Lvmax(other)) => Lvmax(op(one, other)), - (&Dvmax(one), &Dvmax(other)) => Dvmax(op(one, other)), - (&Vb(one), &Vb(other)) => Vb(op(one, other)), - (&Svb(one), &Svb(other)) => Svb(op(one, other)), - (&Lvb(one), &Lvb(other)) => Lvb(op(one, other)), - (&Dvb(one), &Dvb(other)) => Dvb(op(one, other)), - (&Vi(one), &Vi(other)) => Vi(op(one, other)), - (&Svi(one), &Svi(other)) => Svi(op(one, other)), - (&Lvi(one), &Lvi(other)) => Lvi(op(one, other)), - (&Dvi(one), &Dvi(other)) => Dvi(op(one, other)), - // See https://github.com/rust-lang/rust/issues/68867. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | Vh(..) | Svh(..) | Lvh(..) | - Dvh(..) | Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | Vmax(..) | - Svmax(..) | Lvmax(..) | Dvmax(..) | Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | - Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, - } - debug_unreachable!("Forgot to handle unit in try_op()") - }, - }) - } - - fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self { - match self { - Self::Vw(x) => Self::Vw(op(*x)), - Self::Svw(x) => Self::Svw(op(*x)), - Self::Lvw(x) => Self::Lvw(op(*x)), - Self::Dvw(x) => Self::Dvw(op(*x)), - Self::Vh(x) => Self::Vh(op(*x)), - Self::Svh(x) => Self::Svh(op(*x)), - Self::Lvh(x) => Self::Lvh(op(*x)), - Self::Dvh(x) => Self::Dvh(op(*x)), - Self::Vmin(x) => Self::Vmin(op(*x)), - Self::Svmin(x) => Self::Svmin(op(*x)), - Self::Lvmin(x) => Self::Lvmin(op(*x)), - Self::Dvmin(x) => Self::Dvmin(op(*x)), - Self::Vmax(x) => Self::Vmax(op(*x)), - Self::Svmax(x) => Self::Svmax(op(*x)), - Self::Lvmax(x) => Self::Lvmax(op(*x)), - Self::Dvmax(x) => Self::Dvmax(op(*x)), - Self::Vb(x) => Self::Vb(op(*x)), - Self::Svb(x) => Self::Svb(op(*x)), - Self::Lvb(x) => Self::Lvb(op(*x)), - Self::Dvb(x) => Self::Dvb(op(*x)), - Self::Vi(x) => Self::Vi(op(*x)), - Self::Svi(x) => Self::Svi(op(*x)), - Self::Lvi(x) => Self::Lvi(op(*x)), - Self::Dvi(x) => Self::Dvi(op(*x)), - } - } - - /// Computes the given viewport-relative length for the given viewport size. - pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength { - let (variant, unit, factor) = self.unpack(); - let size = context.viewport_size_for_viewport_unit_resolution(variant); - let length = match unit { - ViewportUnit::Vw => size.width, - ViewportUnit::Vh => size.height, - ViewportUnit::Vmin => cmp::min(size.width, size.height), - ViewportUnit::Vmax => cmp::max(size.width, size.height), - ViewportUnit::Vi | ViewportUnit::Vb => { - context - .rule_cache_conditions - .borrow_mut() - .set_writing_mode_dependency(context.builder.writing_mode); - if (unit == ViewportUnit::Vb) == context.style().writing_mode.is_vertical() { - size.width - } else { - size.height - } - }, - }; - - // FIXME: Bug 1396535, we need to fix the extremely small viewport length for transform. - // See bug 989802. We truncate so that adding multiple viewport units - // that add up to 100 does not overflow due to rounding differences - let trunc_scaled = ((length.0 as f64) * factor as f64 / 100.).trunc(); - Au::from_f64_au(if trunc_scaled.is_nan() { - 0.0f64 - } else { - trunc_scaled - }) - .into() - } -} - -/// HTML5 "character width", as defined in HTML5 § 14.5.4. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] -pub struct CharacterWidth(pub i32); - -impl CharacterWidth { - /// Computes the given character width. - pub fn to_computed_value(&self, reference_font_size: computed::Length) -> computed::Length { - // This applies the *converting a character width to pixels* algorithm - // as specified in HTML5 § 14.5.4. - // - // TODO(pcwalton): Find these from the font. - let average_advance = reference_font_size * 0.5; - let max_advance = reference_font_size; - (average_advance * (self.0 as CSSFloat - 1.0) + max_advance).finite() - } -} - -/// Represents an absolute length with its unit -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] -pub enum AbsoluteLength { - /// An absolute length in pixels (px) - #[css(dimension)] - Px(CSSFloat), - /// An absolute length in inches (in) - #[css(dimension)] - In(CSSFloat), - /// An absolute length in centimeters (cm) - #[css(dimension)] - Cm(CSSFloat), - /// An absolute length in millimeters (mm) - #[css(dimension)] - Mm(CSSFloat), - /// An absolute length in quarter-millimeters (q) - #[css(dimension)] - Q(CSSFloat), - /// An absolute length in points (pt) - #[css(dimension)] - Pt(CSSFloat), - /// An absolute length in pica (pc) - #[css(dimension)] - Pc(CSSFloat), -} - -impl AbsoluteLength { - /// Return the unitless, raw value. - fn unitless_value(&self) -> CSSFloat { - match *self { - Self::Px(v) | - Self::In(v) | - Self::Cm(v) | - Self::Mm(v) | - Self::Q(v) | - Self::Pt(v) | - Self::Pc(v) => v, - } - } - - // Return the unit, as a string. - fn unit(&self) -> &'static str { - match *self { - Self::Px(_) => "px", - Self::In(_) => "in", - Self::Cm(_) => "cm", - Self::Mm(_) => "mm", - Self::Q(_) => "q", - Self::Pt(_) => "pt", - Self::Pc(_) => "pc", - } - } - - /// Convert this into a pixel value. - #[inline] - pub fn to_px(&self) -> CSSFloat { - match *self { - Self::Px(value) => value, - Self::In(value) => value * PX_PER_IN, - Self::Cm(value) => value * PX_PER_CM, - Self::Mm(value) => value * PX_PER_MM, - Self::Q(value) => value * PX_PER_Q, - Self::Pt(value) => value * PX_PER_PT, - Self::Pc(value) => value * PX_PER_PC, - } - } - - fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - Ok(Self::Px(op(self.to_px(), other.to_px()))) - } - - fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self { - Self::Px(op(self.to_px())) - } -} - -impl ToComputedValue for AbsoluteLength { - type ComputedValue = CSSPixelLength; - - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - CSSPixelLength::new(self.to_px()).finite() - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Self::Px(computed.px()) - } -} - -impl PartialOrd for AbsoluteLength { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - self.to_px().partial_cmp(&other.to_px()) - } -} - -/// A container query length. -/// -/// <https://drafts.csswg.org/css-contain-3/#container-lengths> -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] -pub enum ContainerRelativeLength { - /// 1% of query container's width - #[css(dimension)] - Cqw(CSSFloat), - /// 1% of query container's height - #[css(dimension)] - Cqh(CSSFloat), - /// 1% of query container's inline size - #[css(dimension)] - Cqi(CSSFloat), - /// 1% of query container's block size - #[css(dimension)] - Cqb(CSSFloat), - /// The smaller value of `cqi` or `cqb` - #[css(dimension)] - Cqmin(CSSFloat), - /// The larger value of `cqi` or `cqb` - #[css(dimension)] - Cqmax(CSSFloat), -} - -impl ContainerRelativeLength { - fn unitless_value(&self) -> CSSFloat { - match *self { - Self::Cqw(v) | - Self::Cqh(v) | - Self::Cqi(v) | - Self::Cqb(v) | - Self::Cqmin(v) | - Self::Cqmax(v) => v, - } - } - - // Return the unit, as a string. - fn unit(&self) -> &'static str { - match *self { - Self::Cqw(_) => "cqw", - Self::Cqh(_) => "cqh", - Self::Cqi(_) => "cqi", - Self::Cqb(_) => "cqb", - Self::Cqmin(_) => "cqmin", - Self::Cqmax(_) => "cqmax", - } - } - - pub(crate) fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - use self::ContainerRelativeLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - - Ok(match (self, other) { - (&Cqw(one), &Cqw(other)) => Cqw(op(one, other)), - (&Cqh(one), &Cqh(other)) => Cqh(op(one, other)), - (&Cqi(one), &Cqi(other)) => Cqi(op(one, other)), - (&Cqb(one), &Cqb(other)) => Cqb(op(one, other)), - (&Cqmin(one), &Cqmin(other)) => Cqmin(op(one, other)), - (&Cqmax(one), &Cqmax(other)) => Cqmax(op(one, other)), - - // See https://github.com/rust-lang/rust/issues/68867, then - // https://github.com/rust-lang/rust/pull/95161. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Cqw(..) | Cqh(..) | Cqi(..) | Cqb(..) | Cqmin(..) | Cqmax(..) => {}, - } - debug_unreachable!("Forgot to handle unit in try_op()") - }, - }) - } - - pub(crate) fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self { - match self { - Self::Cqw(x) => Self::Cqw(op(*x)), - Self::Cqh(x) => Self::Cqh(op(*x)), - Self::Cqi(x) => Self::Cqi(op(*x)), - Self::Cqb(x) => Self::Cqb(op(*x)), - Self::Cqmin(x) => Self::Cqmin(op(*x)), - Self::Cqmax(x) => Self::Cqmax(op(*x)), - } - } - - /// Computes the given container-relative length. - pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength { - if context.for_non_inherited_property { - context.rule_cache_conditions.borrow_mut().set_uncacheable(); - } - context.builder.add_flags(ComputedValueFlags::USES_CONTAINER_UNITS); - - let size = context.get_container_size_query(); - let (factor, container_length) = match *self { - Self::Cqw(v) => (v, size.get_container_width(context)), - Self::Cqh(v) => (v, size.get_container_height(context)), - Self::Cqi(v) => (v, size.get_container_inline_size(context)), - Self::Cqb(v) => (v, size.get_container_block_size(context)), - Self::Cqmin(v) => ( - v, - cmp::min( - size.get_container_inline_size(context), - size.get_container_block_size(context), - ), - ), - Self::Cqmax(v) => ( - v, - cmp::max( - size.get_container_inline_size(context), - size.get_container_block_size(context), - ), - ), - }; - CSSPixelLength::new((container_length.to_f64_px() * factor as f64 / 100.0) as f32).finite() - } -} - -#[cfg(feature = "gecko")] -fn are_container_queries_enabled() -> bool { - static_prefs::pref!("layout.css.container-queries.enabled") -} -#[cfg(feature = "servo")] -fn are_container_queries_enabled() -> bool { - false -} - -/// A `<length>` without taking `calc` expressions into account -/// -/// <https://drafts.csswg.org/css-values/#lengths> -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum NoCalcLength { - /// An absolute length - /// - /// <https://drafts.csswg.org/css-values/#absolute-length> - Absolute(AbsoluteLength), - - /// A font-relative length: - /// - /// <https://drafts.csswg.org/css-values/#font-relative-lengths> - FontRelative(FontRelativeLength), - - /// A viewport-relative length. - /// - /// <https://drafts.csswg.org/css-values/#viewport-relative-lengths> - ViewportPercentage(ViewportPercentageLength), - - /// A container query length. - /// - /// <https://drafts.csswg.org/css-contain-3/#container-lengths> - ContainerRelative(ContainerRelativeLength), - /// HTML5 "character width", as defined in HTML5 § 14.5.4. - /// - /// This cannot be specified by the user directly and is only generated by - /// `Stylist::synthesize_rules_for_legacy_attributes()`. - ServoCharacterWidth(CharacterWidth), -} - -impl NoCalcLength { - /// Return the unitless, raw value. - pub fn unitless_value(&self) -> CSSFloat { - match *self { - Self::Absolute(v) => v.unitless_value(), - Self::FontRelative(v) => v.unitless_value(), - Self::ViewportPercentage(v) => v.unitless_value(), - Self::ContainerRelative(v) => v.unitless_value(), - Self::ServoCharacterWidth(c) => c.0 as f32, - } - } - - // Return the unit, as a string. - fn unit(&self) -> &'static str { - match *self { - Self::Absolute(v) => v.unit(), - Self::FontRelative(v) => v.unit(), - Self::ViewportPercentage(v) => v.unit(), - Self::ContainerRelative(v) => v.unit(), - Self::ServoCharacterWidth(_) => "", - } - } - - /// Returns whether the value of this length without unit is less than zero. - pub fn is_negative(&self) -> bool { - self.unitless_value().is_sign_negative() - } - - /// Returns whether the value of this length without unit is equal to zero. - pub fn is_zero(&self) -> bool { - self.unitless_value() == 0.0 - } - - /// Returns whether the value of this length without unit is infinite. - pub fn is_infinite(&self) -> bool { - self.unitless_value().is_infinite() - } - - /// Returns whether the value of this length without unit is NaN. - pub fn is_nan(&self) -> bool { - self.unitless_value().is_nan() - } - - /// Whether text-only zoom should be applied to this length. - /// - /// Generally, font-dependent/relative units don't get text-only-zoomed, - /// because the font they're relative to should be zoomed already. - pub fn should_zoom_text(&self) -> bool { - match *self { - Self::Absolute(..) | Self::ViewportPercentage(..) | Self::ContainerRelative(..) => true, - Self::ServoCharacterWidth(..) | Self::FontRelative(..) => false, - } - } - - /// Parse a given absolute or relative dimension. - pub fn parse_dimension( - context: &ParserContext, - value: CSSFloat, - unit: &str, - ) -> Result<Self, ()> { - Ok(match_ignore_ascii_case! { unit, - "px" => Self::Absolute(AbsoluteLength::Px(value)), - "in" => Self::Absolute(AbsoluteLength::In(value)), - "cm" => Self::Absolute(AbsoluteLength::Cm(value)), - "mm" => Self::Absolute(AbsoluteLength::Mm(value)), - "q" => Self::Absolute(AbsoluteLength::Q(value)), - "pt" => Self::Absolute(AbsoluteLength::Pt(value)), - "pc" => Self::Absolute(AbsoluteLength::Pc(value)), - // font-relative - "em" => Self::FontRelative(FontRelativeLength::Em(value)), - "ex" => Self::FontRelative(FontRelativeLength::Ex(value)), - "ch" => Self::FontRelative(FontRelativeLength::Ch(value)), - "cap" => Self::FontRelative(FontRelativeLength::Cap(value)), - "ic" => Self::FontRelative(FontRelativeLength::Ic(value)), - "rem" => Self::FontRelative(FontRelativeLength::Rem(value)), - // viewport percentages - "vw" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Vw(value)) - }, - "svw" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Svw(value)) - }, - "lvw" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Lvw(value)) - }, - "dvw" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Dvw(value)) - }, - "vh" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Vh(value)) - }, - "svh" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Svh(value)) - }, - "lvh" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Lvh(value)) - }, - "dvh" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Dvh(value)) - }, - "vmin" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Vmin(value)) - }, - "svmin" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Svmin(value)) - }, - "lvmin" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Lvmin(value)) - }, - "dvmin" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Dvmin(value)) - }, - "vmax" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Vmax(value)) - }, - "svmax" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Svmax(value)) - }, - "lvmax" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Lvmax(value)) - }, - "dvmax" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Dvmax(value)) - }, - "vb" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Vb(value)) - }, - "svb" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Svb(value)) - }, - "lvb" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Lvb(value)) - }, - "dvb" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Dvb(value)) - }, - "vi" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Vi(value)) - }, - "svi" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Svi(value)) - }, - "lvi" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Lvi(value)) - }, - "dvi" if !context.in_page_rule() => { - Self::ViewportPercentage(ViewportPercentageLength::Dvi(value)) - }, - // Container query lengths. Inherit the limitation from viewport units since - // we may fall back to them. - "cqw" if !context.in_page_rule() && are_container_queries_enabled() => { - Self::ContainerRelative(ContainerRelativeLength::Cqw(value)) - }, - "cqh" if !context.in_page_rule() && are_container_queries_enabled() => { - Self::ContainerRelative(ContainerRelativeLength::Cqh(value)) - }, - "cqi" if !context.in_page_rule() && are_container_queries_enabled() => { - Self::ContainerRelative(ContainerRelativeLength::Cqi(value)) - }, - "cqb" if !context.in_page_rule() && are_container_queries_enabled() => { - Self::ContainerRelative(ContainerRelativeLength::Cqb(value)) - }, - "cqmin" if !context.in_page_rule() && are_container_queries_enabled() => { - Self::ContainerRelative(ContainerRelativeLength::Cqmin(value)) - }, - "cqmax" if !context.in_page_rule() && are_container_queries_enabled() => { - Self::ContainerRelative(ContainerRelativeLength::Cqmax(value)) - }, - _ => return Err(()), - }) - } - - pub(crate) fn try_op<O>(&self, other: &Self, op: O) -> Result<Self, ()> - where - O: Fn(f32, f32) -> f32, - { - use self::NoCalcLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - - Ok(match (self, other) { - (&Absolute(ref one), &Absolute(ref other)) => Absolute(one.try_op(other, op)?), - (&FontRelative(ref one), &FontRelative(ref other)) => { - FontRelative(one.try_op(other, op)?) - }, - (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { - ViewportPercentage(one.try_op(other, op)?) - }, - (&ContainerRelative(ref one), &ContainerRelative(ref other)) => { - ContainerRelative(one.try_op(other, op)?) - }, - (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { - ServoCharacterWidth(CharacterWidth(op(one.0 as f32, other.0 as f32) as i32)) - }, - // See https://github.com/rust-lang/rust/issues/68867. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Absolute(..) | - FontRelative(..) | - ViewportPercentage(..) | - ContainerRelative(..) | - ServoCharacterWidth(..) => {}, - } - debug_unreachable!("Forgot to handle unit in try_op()") - }, - }) - } - - pub(crate) fn map(&self, mut op: impl FnMut(f32) -> f32) -> Self { - use self::NoCalcLength::*; - - match self { - Absolute(ref one) => Absolute(one.map(op)), - FontRelative(ref one) => FontRelative(one.map(op)), - ViewportPercentage(ref one) => ViewportPercentage(one.map(op)), - ContainerRelative(ref one) => ContainerRelative(one.map(op)), - ServoCharacterWidth(ref one) => { - ServoCharacterWidth(CharacterWidth(op(one.0 as f32) as i32)) - }, - } - } - - /// Get a px value without context. - #[inline] - pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { - match *self { - Self::Absolute(len) => Ok(CSSPixelLength::new(len.to_px()).finite().px()), - _ => Err(()), - } - } - - /// Get an absolute length from a px value. - #[inline] - pub fn from_px(px_value: CSSFloat) -> NoCalcLength { - NoCalcLength::Absolute(AbsoluteLength::Px(px_value)) - } -} - -impl ToCss for NoCalcLength { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - crate::values::serialize_specified_dimension( - self.unitless_value(), - self.unit(), - false, - dest, - ) - } -} - -impl SpecifiedValueInfo for NoCalcLength {} - -impl PartialOrd for NoCalcLength { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - use self::NoCalcLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return None; - } - - match (self, other) { - (&Absolute(ref one), &Absolute(ref other)) => one.to_px().partial_cmp(&other.to_px()), - (&FontRelative(ref one), &FontRelative(ref other)) => one.partial_cmp(other), - (&ViewportPercentage(ref one), &ViewportPercentage(ref other)) => { - one.partial_cmp(other) - }, - (&ContainerRelative(ref one), &ContainerRelative(ref other)) => one.partial_cmp(other), - (&ServoCharacterWidth(ref one), &ServoCharacterWidth(ref other)) => { - one.0.partial_cmp(&other.0) - }, - // See https://github.com/rust-lang/rust/issues/68867. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Absolute(..) | - FontRelative(..) | - ViewportPercentage(..) | - ContainerRelative(..) | - ServoCharacterWidth(..) => {}, - } - debug_unreachable!("Forgot an arm in partial_cmp?") - }, - } - } -} - -impl Zero for NoCalcLength { - fn zero() -> Self { - NoCalcLength::Absolute(AbsoluteLength::Px(0.)) - } - - fn is_zero(&self) -> bool { - NoCalcLength::is_zero(self) - } -} - -/// An extension to `NoCalcLength` to parse `calc` expressions. -/// This is commonly used for the `<length>` values. -/// -/// <https://drafts.csswg.org/css-values/#lengths> -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum Length { - /// The internal length type that cannot parse `calc` - NoCalc(NoCalcLength), - /// A calc expression. - /// - /// <https://drafts.csswg.org/css-values/#calc-notation> - Calc(Box<CalcLengthPercentage>), -} - -impl From<NoCalcLength> for Length { - #[inline] - fn from(len: NoCalcLength) -> Self { - Length::NoCalc(len) - } -} - -impl PartialOrd for FontRelativeLength { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - use self::FontRelativeLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return None; - } - - match (self, other) { - (&Em(ref one), &Em(ref other)) => one.partial_cmp(other), - (&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other), - (&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other), - (&Cap(ref one), &Cap(ref other)) => one.partial_cmp(other), - (&Ic(ref one), &Ic(ref other)) => one.partial_cmp(other), - (&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other), - // See https://github.com/rust-lang/rust/issues/68867. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, - } - debug_unreachable!("Forgot an arm in partial_cmp?") - }, - } - } -} - -impl PartialOrd for ContainerRelativeLength { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - use self::ContainerRelativeLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return None; - } - - match (self, other) { - (&Cqw(ref one), &Cqw(ref other)) => one.partial_cmp(other), - (&Cqh(ref one), &Cqh(ref other)) => one.partial_cmp(other), - (&Cqi(ref one), &Cqi(ref other)) => one.partial_cmp(other), - (&Cqb(ref one), &Cqb(ref other)) => one.partial_cmp(other), - (&Cqmin(ref one), &Cqmin(ref other)) => one.partial_cmp(other), - (&Cqmax(ref one), &Cqmax(ref other)) => one.partial_cmp(other), - - // See https://github.com/rust-lang/rust/issues/68867, then - // https://github.com/rust-lang/rust/pull/95161. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Cqw(..) | Cqh(..) | Cqi(..) | Cqb(..) | Cqmin(..) | Cqmax(..) => {}, - } - debug_unreachable!("Forgot to handle unit in partial_cmp()") - }, - } - } -} - -impl PartialOrd for ViewportPercentageLength { - fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { - use self::ViewportPercentageLength::*; - - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return None; - } - - match (self, other) { - (&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other), - (&Svw(ref one), &Svw(ref other)) => one.partial_cmp(other), - (&Lvw(ref one), &Lvw(ref other)) => one.partial_cmp(other), - (&Dvw(ref one), &Dvw(ref other)) => one.partial_cmp(other), - (&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other), - (&Svh(ref one), &Svh(ref other)) => one.partial_cmp(other), - (&Lvh(ref one), &Lvh(ref other)) => one.partial_cmp(other), - (&Dvh(ref one), &Dvh(ref other)) => one.partial_cmp(other), - (&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other), - (&Svmin(ref one), &Svmin(ref other)) => one.partial_cmp(other), - (&Lvmin(ref one), &Lvmin(ref other)) => one.partial_cmp(other), - (&Dvmin(ref one), &Dvmin(ref other)) => one.partial_cmp(other), - (&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other), - (&Svmax(ref one), &Svmax(ref other)) => one.partial_cmp(other), - (&Lvmax(ref one), &Lvmax(ref other)) => one.partial_cmp(other), - (&Dvmax(ref one), &Dvmax(ref other)) => one.partial_cmp(other), - (&Vb(ref one), &Vb(ref other)) => one.partial_cmp(other), - (&Svb(ref one), &Svb(ref other)) => one.partial_cmp(other), - (&Lvb(ref one), &Lvb(ref other)) => one.partial_cmp(other), - (&Dvb(ref one), &Dvb(ref other)) => one.partial_cmp(other), - (&Vi(ref one), &Vi(ref other)) => one.partial_cmp(other), - (&Svi(ref one), &Svi(ref other)) => one.partial_cmp(other), - (&Lvi(ref one), &Lvi(ref other)) => one.partial_cmp(other), - (&Dvi(ref one), &Dvi(ref other)) => one.partial_cmp(other), - // See https://github.com/rust-lang/rust/issues/68867. rustc isn't - // able to figure it own on its own so we help. - _ => unsafe { - match *self { - Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | Vh(..) | Svh(..) | Lvh(..) | - Dvh(..) | Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | Vmax(..) | - Svmax(..) | Lvmax(..) | Dvmax(..) | Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | - Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, - } - debug_unreachable!("Forgot an arm in partial_cmp?") - }, - } - } -} - -impl Length { - #[inline] - fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - num_context: AllowedNumericType, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let token = input.next()?; - match *token { - Token::Dimension { - value, ref unit, .. - } if num_context.is_ok(context.parsing_mode, value) => { - NoCalcLength::parse_dimension(context, value, unit) - .map(Length::NoCalc) - .map_err(|()| location.new_unexpected_token_error(token.clone())) - }, - Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => { - if value != 0. && - !context.parsing_mode.allows_unitless_lengths() && - !allow_quirks.allowed(context.quirks_mode) - { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(Length::NoCalc(NoCalcLength::Absolute(AbsoluteLength::Px( - value, - )))) - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - let calc = CalcNode::parse_length(context, input, num_context, function)?; - Ok(Length::Calc(Box::new(calc))) - }, - ref token => return Err(location.new_unexpected_token_error(token.clone())), - } - } - - /// Parse a non-negative length - #[inline] - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_non_negative_quirky(context, input, AllowQuirks::No) - } - - /// Parse a non-negative length, allowing quirks. - #[inline] - pub fn parse_non_negative_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal( - context, - input, - AllowedNumericType::NonNegative, - allow_quirks, - ) - } - - /// Get an absolute length from a px value. - #[inline] - pub fn from_px(px_value: CSSFloat) -> Length { - Length::NoCalc(NoCalcLength::from_px(px_value)) - } - - /// Get a px value without context. - pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> { - match *self { - Self::NoCalc(ref l) => l.to_computed_pixel_length_without_context(), - Self::Calc(ref l) => l.to_computed_pixel_length_without_context(), - } - } -} - -impl Parse for Length { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl Zero for Length { - fn zero() -> Self { - Length::NoCalc(NoCalcLength::zero()) - } - - fn is_zero(&self) -> bool { - // FIXME(emilio): Seems a bit weird to treat calc() unconditionally as - // non-zero here? - match *self { - Length::NoCalc(ref l) => l.is_zero(), - Length::Calc(..) => false, - } - } -} - -impl Length { - /// Parses a length, with quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, AllowedNumericType::All, allow_quirks) - } -} - -/// A wrapper of Length, whose value must be >= 0. -pub type NonNegativeLength = NonNegative<Length>; - -impl Parse for NonNegativeLength { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(NonNegative(Length::parse_non_negative(context, input)?)) - } -} - -impl From<NoCalcLength> for NonNegativeLength { - #[inline] - fn from(len: NoCalcLength) -> Self { - NonNegative(Length::NoCalc(len)) - } -} - -impl From<Length> for NonNegativeLength { - #[inline] - fn from(len: Length) -> Self { - NonNegative(len) - } -} - -impl NonNegativeLength { - /// Get an absolute length from a px value. - #[inline] - pub fn from_px(px_value: CSSFloat) -> Self { - Length::from_px(px_value.max(0.)).into() - } - - /// Parses a non-negative length, optionally with quirks. - #[inline] - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Ok(NonNegative(Length::parse_non_negative_quirky( - context, - input, - allow_quirks, - )?)) - } -} - -/// A `<length-percentage>` value. This can be either a `<length>`, a -/// `<percentage>`, or a combination of both via `calc()`. -/// -/// https://drafts.csswg.org/css-values-4/#typedef-length-percentage -#[allow(missing_docs)] -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum LengthPercentage { - Length(NoCalcLength), - Percentage(computed::Percentage), - Calc(Box<CalcLengthPercentage>), -} - -impl From<Length> for LengthPercentage { - fn from(len: Length) -> LengthPercentage { - match len { - Length::NoCalc(l) => LengthPercentage::Length(l), - Length::Calc(l) => LengthPercentage::Calc(l), - } - } -} - -impl From<NoCalcLength> for LengthPercentage { - #[inline] - fn from(len: NoCalcLength) -> Self { - LengthPercentage::Length(len) - } -} - -impl From<Percentage> for LengthPercentage { - #[inline] - fn from(pc: Percentage) -> Self { - if let Some(clamping_mode) = pc.calc_clamping_mode() { - LengthPercentage::Calc(Box::new(CalcLengthPercentage { - clamping_mode, - node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())), - })) - } else { - LengthPercentage::Percentage(computed::Percentage(pc.get())) - } - } -} - -impl From<computed::Percentage> for LengthPercentage { - #[inline] - fn from(pc: computed::Percentage) -> Self { - LengthPercentage::Percentage(pc) - } -} - -impl Parse for LengthPercentage { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl LengthPercentage { - #[inline] - /// Returns a `0%` value. - pub fn zero_percent() -> LengthPercentage { - LengthPercentage::Percentage(computed::Percentage::zero()) - } - - #[inline] - /// Returns a `100%` value. - pub fn hundred_percent() -> LengthPercentage { - LengthPercentage::Percentage(computed::Percentage::hundred()) - } - - fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - num_context: AllowedNumericType, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let token = input.next()?; - match *token { - Token::Dimension { - value, ref unit, .. - } if num_context.is_ok(context.parsing_mode, value) => { - return NoCalcLength::parse_dimension(context, value, unit) - .map(LengthPercentage::Length) - .map_err(|()| location.new_unexpected_token_error(token.clone())); - }, - Token::Percentage { unit_value, .. } - if num_context.is_ok(context.parsing_mode, unit_value) => - { - return Ok(LengthPercentage::Percentage(computed::Percentage( - unit_value, - ))); - }, - Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => { - if value != 0. && - !context.parsing_mode.allows_unitless_lengths() && - !allow_quirks.allowed(context.quirks_mode) - { - return Err(location.new_unexpected_token_error(token.clone())); - } else { - return Ok(LengthPercentage::Length(NoCalcLength::from_px(value))); - } - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - let calc = - CalcNode::parse_length_or_percentage(context, input, num_context, function)?; - Ok(LengthPercentage::Calc(Box::new(calc))) - }, - _ => return Err(location.new_unexpected_token_error(token.clone())), - } - } - - /// Parses allowing the unitless length quirk. - /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk> - #[inline] - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal(context, input, AllowedNumericType::All, allow_quirks) - } - - /// Parse a non-negative length. - /// - /// FIXME(emilio): This should be not public and we should use - /// NonNegativeLengthPercentage instead. - #[inline] - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_non_negative_quirky(context, input, AllowQuirks::No) - } - - /// Parse a non-negative length, with quirks. - #[inline] - pub fn parse_non_negative_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_internal( - context, - input, - AllowedNumericType::NonNegative, - allow_quirks, - ) - } -} - -impl Zero for LengthPercentage { - fn zero() -> Self { - LengthPercentage::Length(NoCalcLength::zero()) - } - - fn is_zero(&self) -> bool { - match *self { - LengthPercentage::Length(l) => l.is_zero(), - LengthPercentage::Percentage(p) => p.0 == 0.0, - LengthPercentage::Calc(_) => false, - } - } -} - -impl ZeroNoPercent for LengthPercentage { - fn is_zero_no_percent(&self) -> bool { - match *self { - LengthPercentage::Percentage(_) => false, - _ => self.is_zero(), - } - } -} - -/// A specified type for `<length-percentage> | auto`. -pub type LengthPercentageOrAuto = generics::LengthPercentageOrAuto<LengthPercentage>; - -impl LengthPercentageOrAuto { - /// Returns a value representing `0%`. - #[inline] - pub fn zero_percent() -> Self { - generics::LengthPercentageOrAuto::LengthPercentage(LengthPercentage::zero_percent()) - } - - /// Parses a length or a percentage, allowing the unitless length quirk. - /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk> - #[inline] - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with(context, input, |context, input| { - LengthPercentage::parse_quirky(context, input, allow_quirks) - }) - } -} - -/// A wrapper of LengthPercentageOrAuto, whose value must be >= 0. -pub type NonNegativeLengthPercentageOrAuto = - generics::LengthPercentageOrAuto<NonNegativeLengthPercentage>; - -impl NonNegativeLengthPercentageOrAuto { - /// Returns a value representing `0%`. - #[inline] - pub fn zero_percent() -> Self { - generics::LengthPercentageOrAuto::LengthPercentage( - NonNegativeLengthPercentage::zero_percent(), - ) - } - - /// Parses a non-negative length-percentage, allowing the unitless length - /// quirk. - #[inline] - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with(context, input, |context, input| { - NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks) - }) - } -} - -/// A wrapper of LengthPercentage, whose value must be >= 0. -pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>; - -/// Either a NonNegativeLengthPercentage or the `normal` keyword. -pub type NonNegativeLengthPercentageOrNormal = - GenericLengthPercentageOrNormal<NonNegativeLengthPercentage>; - -impl From<NoCalcLength> for NonNegativeLengthPercentage { - #[inline] - fn from(len: NoCalcLength) -> Self { - NonNegative(LengthPercentage::from(len)) - } -} - -impl Parse for NonNegativeLengthPercentage { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl NonNegativeLengthPercentage { - #[inline] - /// Returns a `0%` value. - pub fn zero_percent() -> Self { - NonNegative(LengthPercentage::zero_percent()) - } - - /// Parses a length or a percentage, allowing the unitless length quirk. - /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk> - #[inline] - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - LengthPercentage::parse_non_negative_quirky(context, input, allow_quirks).map(NonNegative) - } -} - -/// Either a `<length>` or the `auto` keyword. -/// -/// Note that we use LengthPercentage just for convenience, since it pretty much -/// is everything we care about, but we could just add a similar LengthOrAuto -/// instead if we think getting rid of this weirdness is worth it. -pub type LengthOrAuto = generics::LengthPercentageOrAuto<Length>; - -impl LengthOrAuto { - /// Parses a length, allowing the unitless length quirk. - /// <https://quirks.spec.whatwg.org/#the-unitless-length-quirk> - #[inline] - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with(context, input, |context, input| { - Length::parse_quirky(context, input, allow_quirks) - }) - } -} - -/// Either a non-negative `<length>` or the `auto` keyword. -pub type NonNegativeLengthOrAuto = generics::LengthPercentageOrAuto<NonNegativeLength>; - -/// Either a `<length>` or a `<number>`. -pub type LengthOrNumber = GenericLengthOrNumber<Length, Number>; - -/// A specified value for `min-width`, `min-height`, `width` or `height` property. -pub type Size = GenericSize<NonNegativeLengthPercentage>; - -impl Parse for Size { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Size::parse_quirky(context, input, AllowQuirks::No) - } -} - -macro_rules! parse_size_non_length { - ($size:ident, $input:expr, $auto_or_none:expr => $auto_or_none_ident:ident) => {{ - let size = $input.try_parse(|input| { - Ok(try_match_ident_ignore_ascii_case! { input, - #[cfg(feature = "gecko")] - "min-content" | "-moz-min-content" => $size::MinContent, - #[cfg(feature = "gecko")] - "max-content" | "-moz-max-content" => $size::MaxContent, - #[cfg(feature = "gecko")] - "fit-content" | "-moz-fit-content" => $size::FitContent, - #[cfg(feature = "gecko")] - "-moz-available" => $size::MozAvailable, - $auto_or_none => $size::$auto_or_none_ident, - }) - }); - if size.is_ok() { - return size; - } - }}; -} - -#[cfg(feature = "gecko")] -fn is_fit_content_function_enabled() -> bool { - static_prefs::pref!("layout.css.fit-content-function.enabled") -} - -#[cfg(feature = "gecko")] -macro_rules! parse_fit_content_function { - ($size:ident, $input:expr, $context:expr, $allow_quirks:expr) => { - if is_fit_content_function_enabled() { - if let Ok(length) = $input.try_parse(|input| { - input.expect_function_matching("fit-content")?; - input.parse_nested_block(|i| { - NonNegativeLengthPercentage::parse_quirky($context, i, $allow_quirks) - }) - }) { - return Ok($size::FitContentFunction(length)); - } - } - }; -} - -impl Size { - /// Parses, with quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - parse_size_non_length!(Size, input, "auto" => Auto); - #[cfg(feature = "gecko")] - parse_fit_content_function!(Size, input, context, allow_quirks); - - let length = NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)?; - Ok(GenericSize::LengthPercentage(length)) - } - - /// Returns `0%`. - #[inline] - pub fn zero_percent() -> Self { - GenericSize::LengthPercentage(NonNegativeLengthPercentage::zero_percent()) - } -} - -/// A specified value for `max-width` or `max-height` property. -pub type MaxSize = GenericMaxSize<NonNegativeLengthPercentage>; - -impl Parse for MaxSize { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - MaxSize::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl MaxSize { - /// Parses, with quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - parse_size_non_length!(MaxSize, input, "none" => None); - #[cfg(feature = "gecko")] - parse_fit_content_function!(MaxSize, input, context, allow_quirks); - - let length = NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)?; - Ok(GenericMaxSize::LengthPercentage(length)) - } -} - -/// A specified non-negative `<length>` | `<number>`. -pub type NonNegativeLengthOrNumber = GenericLengthOrNumber<NonNegativeLength, NonNegativeNumber>; diff --git a/components/style/values/specified/list.rs b/components/style/values/specified/list.rs deleted file mode 100644 index 693471e4782..00000000000 --- a/components/style/values/specified/list.rs +++ /dev/null @@ -1,202 +0,0 @@ -/* 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/. */ - -//! `list` specified values. - -use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] -use crate::values::generics::CounterStyle; -#[cfg(feature = "gecko")] -use crate::values::CustomIdent; -use cssparser::{Parser, Token}; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// Specified and computed `list-style-type` property. -#[cfg(feature = "gecko")] -#[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum ListStyleType { - /// `none` - None, - /// <counter-style> - CounterStyle(CounterStyle), - /// <string> - String(String), -} - -#[cfg(feature = "gecko")] -impl ListStyleType { - /// Initial specified value for `list-style-type`. - #[inline] - pub fn disc() -> Self { - ListStyleType::CounterStyle(CounterStyle::disc()) - } - - /// Convert from gecko keyword to list-style-type. - /// - /// This should only be used for mapping type attribute to - /// list-style-type, and thus only values possible in that - /// attribute is considered here. - pub fn from_gecko_keyword(value: u32) -> Self { - use crate::gecko_bindings::structs; - let v8 = value as u8; - - if v8 == structs::ListStyle_None { - return ListStyleType::None; - } - - ListStyleType::CounterStyle(CounterStyle::Name(CustomIdent(match v8 { - structs::ListStyle_Disc => atom!("disc"), - structs::ListStyle_Circle => atom!("circle"), - structs::ListStyle_Square => atom!("square"), - structs::ListStyle_Decimal => atom!("decimal"), - structs::ListStyle_LowerRoman => atom!("lower-roman"), - structs::ListStyle_UpperRoman => atom!("upper-roman"), - structs::ListStyle_LowerAlpha => atom!("lower-alpha"), - structs::ListStyle_UpperAlpha => atom!("upper-alpha"), - _ => unreachable!("Unknown counter style keyword value"), - }))) - } - - /// Is this a bullet? (i.e. `list-style-type: disc|circle|square|disclosure-closed|disclosure-open`) - #[inline] - pub fn is_bullet(&self) -> bool { - match self { - ListStyleType::CounterStyle(ref style) => style.is_bullet(), - _ => false, - } - } -} - -#[cfg(feature = "gecko")] -impl Parse for ListStyleType { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(style) = input.try_parse(|i| CounterStyle::parse(context, i)) { - return Ok(ListStyleType::CounterStyle(style)); - } - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(ListStyleType::None); - } - Ok(ListStyleType::String( - input.expect_string()?.as_ref().to_owned(), - )) - } -} - -/// A quote pair. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct QuotePair { - /// The opening quote. - pub opening: crate::OwnedStr, - - /// The closing quote. - pub closing: crate::OwnedStr, -} - -/// List of quote pairs for the specified/computed value of `quotes` property. -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct QuoteList( - #[css(iterable, if_empty = "none")] - #[ignore_malloc_size_of = "Arc"] - pub crate::ArcSlice<QuotePair>, -); - -/// Specified and computed `quotes` property: `auto`, `none`, or a list -/// of characters. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub enum Quotes { - /// list of quote pairs - QuoteList(QuoteList), - /// auto (use lang-dependent quote marks) - Auto, -} - -impl Parse for Quotes { - fn parse<'i, 't>( - _: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Quotes, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("auto")) - .is_ok() - { - return Ok(Quotes::Auto); - } - - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(Quotes::QuoteList(QuoteList::default())); - } - - let mut quotes = Vec::new(); - loop { - let location = input.current_source_location(); - let opening = match input.next() { - Ok(&Token::QuotedString(ref value)) => value.as_ref().to_owned().into(), - Ok(t) => return Err(location.new_unexpected_token_error(t.clone())), - Err(_) => break, - }; - - let closing = input.expect_string()?.as_ref().to_owned().into(); - quotes.push(QuotePair { opening, closing }); - } - - if !quotes.is_empty() { - Ok(Quotes::QuoteList(QuoteList(crate::ArcSlice::from_iter( - quotes.into_iter(), - )))) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs deleted file mode 100644 index da7fcea9b46..00000000000 --- a/components/style/values/specified/mod.rs +++ /dev/null @@ -1,953 +0,0 @@ -/* 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/. */ - -//! Specified values. -//! -//! TODO(emilio): Enhance docs. - -use super::computed::transform::DirectionVector; -use super::computed::{Context, ToComputedValue}; -use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks; -use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth}; -use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize}; -use super::generics::transform::IsParallelTo; -use super::generics::{self, GreaterThanOrEqualToOne, NonNegative}; -use super::{CSSFloat, CSSInteger}; -use crate::context::QuirksMode; -use crate::parser::{Parse, ParserContext}; -use crate::values::specified::calc::CalcNode; -use crate::values::{serialize_atom_identifier, serialize_number}; -use crate::{Atom, Namespace, One, Prefix, Zero}; -use cssparser::{Parser, Token}; -use std::fmt::{self, Write}; -use std::ops::Add; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -#[cfg(feature = "gecko")] -pub use self::align::{AlignContent, AlignItems, AlignSelf, AlignTracks, ContentDistribution}; -#[cfg(feature = "gecko")] -pub use self::align::{JustifyContent, JustifyItems, JustifySelf, JustifyTracks, SelfAlignment}; -pub use self::angle::{AllowUnitlessZeroAngle, Angle}; -pub use self::animation::{AnimationIterationCount, AnimationName, AnimationTimeline}; -pub use self::animation::{ScrollAxis, ScrollTimelineName, TransitionProperty, ViewTimelineInset}; -pub use self::background::{BackgroundRepeat, BackgroundSize}; -pub use self::basic_shape::FillRule; -pub use self::border::{ - BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice, - BorderImageWidth, BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle, LineWidth, -}; -pub use self::box_::{ - Appearance, BreakBetween, BaselineSource, BreakWithin, Contain, ContainerName, ContainerType, - Clear, ContainIntrinsicSize, ContentVisibility, Display, Float, LineClamp, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter, - ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, - TouchAction, VerticalAlign, WillChange, -}; -pub use self::color::{ - Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust, -}; -pub use self::column::ColumnCount; -pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; -pub use self::easing::TimingFunction; -pub use self::effects::{BoxShadow, Filter, SimpleShadow}; -pub use self::flex::FlexBasis; -pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle}; -pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric}; -pub use self::font::{FontSize, FontSizeAdjust, FontSizeKeyword, FontStretch, FontSynthesis}; -pub use self::font::{FontVariantAlternates, FontWeight}; -pub use self::font::{FontVariantEastAsian, FontVariationSettings}; -pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale}; -pub use self::image::{EndingShape as GradientEndingShape, Gradient}; -pub use self::image::{Image, ImageRendering, MozImageRect}; -pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; -pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; -pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; -pub use self::length::{MaxSize, Size}; -pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant}; -pub use self::length::{ - NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, -}; -#[cfg(feature = "gecko")] -pub use self::list::ListStyleType; -pub use self::list::Quotes; -pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate}; -pub use self::outline::OutlineStyle; -pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize}; -pub use self::percentage::{NonNegativePercentage, Percentage}; -pub use self::position::AspectRatio; -pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto}; -pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement}; -pub use self::position::{PositionComponent, ZIndex}; -pub use self::ratio::Ratio; -pub use self::rect::NonNegativeLengthOrNumberRect; -pub use self::resolution::Resolution; -pub use self::svg::{DProperty, MozContextProperties}; -pub use self::svg::{SVGLength, SVGOpacity, SVGPaint}; -pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; -pub use self::svg_path::SVGPathData; -pub use self::text::HyphenateCharacter; -pub use self::text::RubyPosition; -pub use self::text::TextAlignLast; -pub use self::text::TextUnderlinePosition; -pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAlign}; -pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; -pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; -pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform}; -pub use self::time::Time; -pub use self::transform::{Rotate, Scale, Transform}; -pub use self::transform::{TransformOrigin, TransformStyle, Translate}; -#[cfg(feature = "gecko")] -pub use self::ui::CursorImage; -pub use self::ui::{BoolInteger, Cursor, UserSelect}; -pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent; - -#[cfg(feature = "gecko")] -pub mod align; -pub mod angle; -pub mod animation; -pub mod background; -pub mod basic_shape; -pub mod border; -#[path = "box.rs"] -pub mod box_; -pub mod calc; -pub mod color; -pub mod column; -pub mod counters; -pub mod easing; -pub mod effects; -pub mod flex; -pub mod font; -#[cfg(feature = "gecko")] -pub mod gecko; -pub mod grid; -pub mod image; -pub mod length; -pub mod list; -pub mod motion; -pub mod outline; -pub mod page; -pub mod percentage; -pub mod position; -pub mod ratio; -pub mod rect; -pub mod resolution; -pub mod source_size_list; -pub mod svg; -pub mod svg_path; -pub mod table; -pub mod text; -pub mod time; -pub mod transform; -pub mod ui; -pub mod url; - -/// <angle> | <percentage> -/// https://drafts.csswg.org/css-values/#typedef-angle-percentage -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum AngleOrPercentage { - Percentage(Percentage), - Angle(Angle), -} - -impl AngleOrPercentage { - fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_unitless_zero: AllowUnitlessZeroAngle, - ) -> Result<Self, ParseError<'i>> { - if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) { - return Ok(AngleOrPercentage::Percentage(per)); - } - - Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle) - } - - /// Allow unitless angles, used for conic-gradients as specified by the spec. - /// https://drafts.csswg.org/css-images-4/#valdef-conic-gradient-angle - pub fn parse_with_unitless<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes) - } -} - -impl Parse for AngleOrPercentage { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No) - } -} - -/// Parse a `<number>` value, with a given clamping mode. -fn parse_number_with_clamping_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - clamping_mode: AllowedNumericType, -) -> Result<Number, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => { - Ok(Number { - value, - calc_clamping_mode: None, - }) - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - let value = CalcNode::parse_number(context, input, function)?; - Ok(Number { - value, - calc_clamping_mode: Some(clamping_mode), - }) - }, - ref t => Err(location.new_unexpected_token_error(t.clone())), - } -} - -/// A CSS `<number>` specified value. -/// -/// https://drafts.csswg.org/css-values-3/#number-value -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] -pub struct Number { - /// The numeric value itself. - value: CSSFloat, - /// If this number came from a calc() expression, this tells how clamping - /// should be done on the value. - calc_clamping_mode: Option<AllowedNumericType>, -} - -impl Parse for Number { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - parse_number_with_clamping_mode(context, input, AllowedNumericType::All) - } -} - -impl Number { - /// Returns a new number with the value `val`. - #[inline] - fn new_with_clamping_mode( - value: CSSFloat, - calc_clamping_mode: Option<AllowedNumericType>, - ) -> Self { - Self { - value, - calc_clamping_mode, - } - } - - /// Returns this percentage as a number. - pub fn to_percentage(&self) -> Percentage { - Percentage::new_with_clamping_mode(self.value, self.calc_clamping_mode) - } - - /// Returns a new number with the value `val`. - #[inline] - pub fn new(val: CSSFloat) -> Self { - Self::new_with_clamping_mode(val, None) - } - - /// Returns whether this number came from a `calc()` expression. - #[inline] - pub fn was_calc(&self) -> bool { - self.calc_clamping_mode.is_some() - } - - /// Returns the numeric value, clamped if needed. - #[inline] - pub fn get(&self) -> f32 { - crate::values::normalize( - self.calc_clamping_mode - .map_or(self.value, |mode| mode.clamp(self.value)), - ) - .min(f32::MAX) - .max(f32::MIN) - } - - #[allow(missing_docs)] - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Number, ParseError<'i>> { - parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative) - } - - #[allow(missing_docs)] - pub fn parse_at_least_one<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Number, ParseError<'i>> { - parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne) - } - - /// Clamp to 1.0 if the value is over 1.0. - #[inline] - pub fn clamp_to_one(self) -> Self { - Number { - value: self.value.min(1.), - calc_clamping_mode: self.calc_clamping_mode, - } - } -} - -impl ToComputedValue for Number { - type ComputedValue = CSSFloat; - - #[inline] - fn to_computed_value(&self, _: &Context) -> CSSFloat { - self.get() - } - - #[inline] - fn from_computed_value(computed: &CSSFloat) -> Self { - Number { - value: *computed, - calc_clamping_mode: None, - } - } -} - -impl ToCss for Number { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_number(self.value, self.calc_clamping_mode.is_some(), dest) - } -} - -impl IsParallelTo for (Number, Number, Number) { - fn is_parallel_to(&self, vector: &DirectionVector) -> bool { - use euclid::approxeq::ApproxEq; - // If a and b is parallel, the angle between them is 0deg, so - // a x b = |a|*|b|*sin(0)*n = 0 * n, |a x b| == 0. - let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get()); - self_vector - .cross(*vector) - .square_length() - .approx_eq(&0.0f32) - } -} - -impl SpecifiedValueInfo for Number {} - -impl Add for Number { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self::new(self.get() + other.get()) - } -} - -impl Zero for Number { - #[inline] - fn zero() -> Self { - Self::new(0.) - } - - #[inline] - fn is_zero(&self) -> bool { - self.get() == 0. - } -} - -impl From<Number> for f32 { - #[inline] - fn from(n: Number) -> Self { - n.get() - } -} - -impl From<Number> for f64 { - #[inline] - fn from(n: Number) -> Self { - n.get() as f64 - } -} - -/// A Number which is >= 0.0. -pub type NonNegativeNumber = NonNegative<Number>; - -impl Parse for NonNegativeNumber { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative) - .map(NonNegative::<Number>) - } -} - -impl One for NonNegativeNumber { - #[inline] - fn one() -> Self { - NonNegativeNumber::new(1.0) - } - - #[inline] - fn is_one(&self) -> bool { - self.get() == 1.0 - } -} - -impl NonNegativeNumber { - /// Returns a new non-negative number with the value `val`. - pub fn new(val: CSSFloat) -> Self { - NonNegative::<Number>(Number::new(val.max(0.))) - } - - /// Returns the numeric value. - #[inline] - pub fn get(&self) -> f32 { - self.0.get() - } -} - -/// An Integer which is >= 0. -pub type NonNegativeInteger = NonNegative<Integer>; - -impl Parse for NonNegativeInteger { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(NonNegative(Integer::parse_non_negative(context, input)?)) - } -} - -/// A Number which is >= 1.0. -pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>; - -impl Parse for GreaterThanOrEqualToOneNumber { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne) - .map(GreaterThanOrEqualToOne::<Number>) - } -} - -/// <number> | <percentage> -/// -/// Accepts only non-negative numbers. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum NumberOrPercentage { - Percentage(Percentage), - Number(Number), -} - -impl NumberOrPercentage { - fn parse_with_clamping_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - type_: AllowedNumericType, - ) -> Result<Self, ParseError<'i>> { - if let Ok(per) = - input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_)) - { - return Ok(NumberOrPercentage::Percentage(per)); - } - - parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number) - } - - /// Parse a non-negative number or percentage. - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) - } - - /// Convert the number or the percentage to a number. - pub fn to_percentage(self) -> Percentage { - match self { - Self::Percentage(p) => p, - Self::Number(n) => n.to_percentage(), - } - } - - /// Convert the number or the percentage to a number. - pub fn to_number(self) -> Number { - match self { - Self::Percentage(p) => p.to_number(), - Self::Number(n) => n, - } - } -} - -impl Parse for NumberOrPercentage { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::All) - } -} - -/// A non-negative <number> | <percentage>. -pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>; - -impl NonNegativeNumberOrPercentage { - /// Returns the `100%` value. - #[inline] - pub fn hundred_percent() -> Self { - NonNegative(NumberOrPercentage::Percentage(Percentage::hundred())) - } -} - -impl Parse for NonNegativeNumberOrPercentage { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(NonNegative(NumberOrPercentage::parse_non_negative( - context, input, - )?)) - } -} - -/// The value of Opacity is <alpha-value>, which is "<number> | <percentage>". -/// However, we serialize the specified value as number, so it's ok to store -/// the Opacity as Number. -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, SpecifiedValueInfo, ToCss, ToShmem, -)] -pub struct Opacity(Number); - -impl Parse for Opacity { - /// Opacity accepts <number> | <percentage>, so we parse it as NumberOrPercentage, - /// and then convert into an Number if it's a Percentage. - /// https://drafts.csswg.org/cssom/#serializing-css-values - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let number = NumberOrPercentage::parse(context, input)?.to_number(); - Ok(Opacity(number)) - } -} - -impl ToComputedValue for Opacity { - type ComputedValue = CSSFloat; - - #[inline] - fn to_computed_value(&self, context: &Context) -> CSSFloat { - let value = self.0.to_computed_value(context); - if context.for_smil_animation { - // SMIL expects to be able to interpolate between out-of-range - // opacity values. - value - } else { - value.min(1.0).max(0.0) - } - } - - #[inline] - fn from_computed_value(computed: &CSSFloat) -> Self { - Opacity(Number::from_computed_value(computed)) - } -} - -/// A specified `<integer>`, optionally coming from a `calc()` expression. -/// -/// <https://drafts.csswg.org/css-values/#integers> -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, ToShmem)] -pub struct Integer { - value: CSSInteger, - was_calc: bool, -} - -impl Zero for Integer { - #[inline] - fn zero() -> Self { - Self::new(0) - } - - #[inline] - fn is_zero(&self) -> bool { - self.value() == 0 - } -} - -impl One for Integer { - #[inline] - fn one() -> Self { - Self::new(1) - } - - #[inline] - fn is_one(&self) -> bool { - self.value() == 1 - } -} - -impl PartialEq<i32> for Integer { - fn eq(&self, value: &i32) -> bool { - self.value() == *value - } -} - -impl Integer { - /// Trivially constructs a new `Integer` value. - pub fn new(val: CSSInteger) -> Self { - Integer { - value: val, - was_calc: false, - } - } - - /// Returns the integer value associated with this value. - pub fn value(&self) -> CSSInteger { - self.value - } - - /// Trivially constructs a new integer value from a `calc()` expression. - fn from_calc(val: CSSInteger) -> Self { - Integer { - value: val, - was_calc: true, - } - } -} - -impl Parse for Integer { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::Number { - int_value: Some(v), .. - } => Ok(Integer::new(v)), - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - let result = CalcNode::parse_integer(context, input, function)?; - Ok(Integer::from_calc(result)) - }, - ref t => Err(location.new_unexpected_token_error(t.clone())), - } - } -} - -impl Integer { - /// Parse an integer value which is at least `min`. - pub fn parse_with_minimum<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - min: i32, - ) -> Result<Integer, ParseError<'i>> { - let value = Integer::parse(context, input)?; - // FIXME(emilio): The spec asks us to avoid rejecting it at parse - // time except until computed value time. - // - // It's not totally clear it's worth it though, and no other browser - // does this. - if value.value() < min { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(value) - } - - /// Parse a non-negative integer. - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Integer, ParseError<'i>> { - Integer::parse_with_minimum(context, input, 0) - } - - /// Parse a positive integer (>= 1). - pub fn parse_positive<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Integer, ParseError<'i>> { - Integer::parse_with_minimum(context, input, 1) - } -} - -impl ToComputedValue for Integer { - type ComputedValue = i32; - - #[inline] - fn to_computed_value(&self, _: &Context) -> i32 { - self.value - } - - #[inline] - fn from_computed_value(computed: &i32) -> Self { - Integer::new(*computed) - } -} - -impl ToCss for Integer { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.was_calc { - dest.write_str("calc(")?; - } - self.value.to_css(dest)?; - if self.was_calc { - dest.write_char(')')?; - } - Ok(()) - } -} - -impl SpecifiedValueInfo for Integer {} - -/// A wrapper of Integer, with value >= 1. -pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>; - -impl Parse for PositiveInteger { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne) - } -} - -/// The specified value of a grid `<track-breadth>` -pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>; - -/// The specified value of a grid `<track-size>` -pub type TrackSize = GenericTrackSize<LengthPercentage>; - -/// The specified value of a grid `<track-size>+` -pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>; - -/// The specified value of a grid `<track-list>` -/// (could also be `<auto-track-list>` or `<explicit-track-list>`) -pub type TrackList = GenericTrackList<LengthPercentage, Integer>; - -/// The specified value of a `<grid-line>`. -pub type GridLine = GenericGridLine<Integer>; - -/// `<grid-template-rows> | <grid-template-columns>` -pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>; - -/// rect(...) -pub type ClipRect = generics::GenericClipRect<LengthOrAuto>; - -impl Parse for ClipRect { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl ClipRect { - /// Parses a rect(<top>, <left>, <bottom>, <right>), allowing quirks. - fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - input.expect_function_matching("rect")?; - - fn parse_argument<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<LengthOrAuto, ParseError<'i>> { - LengthOrAuto::parse_quirky(context, input, allow_quirks) - } - - input.parse_nested_block(|input| { - let top = parse_argument(context, input, allow_quirks)?; - let right; - let bottom; - let left; - - if input.try_parse(|input| input.expect_comma()).is_ok() { - right = parse_argument(context, input, allow_quirks)?; - input.expect_comma()?; - bottom = parse_argument(context, input, allow_quirks)?; - input.expect_comma()?; - left = parse_argument(context, input, allow_quirks)?; - } else { - right = parse_argument(context, input, allow_quirks)?; - bottom = parse_argument(context, input, allow_quirks)?; - left = parse_argument(context, input, allow_quirks)?; - } - - Ok(ClipRect { - top, - right, - bottom, - left, - }) - }) - } -} - -/// rect(...) | auto -pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>; - -impl ClipRectOrAuto { - /// Parses a ClipRect or Auto, allowing quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) { - return Ok(generics::GenericClipRectOrAuto::Rect(v)); - } - input.expect_ident_matching("auto")?; - Ok(generics::GenericClipRectOrAuto::Auto) - } -} - -/// Whether quirks are allowed in this context. -#[derive(Clone, Copy, PartialEq)] -pub enum AllowQuirks { - /// Quirks are not allowed. - No, - /// Quirks are allowed, in quirks mode. - Yes, - /// Quirks are always allowed, used for SVG lengths. - Always, -} - -impl AllowQuirks { - /// Returns `true` if quirks are allowed in this context. - pub fn allowed(self, quirks_mode: QuirksMode) -> bool { - match self { - AllowQuirks::Always => true, - AllowQuirks::No => false, - AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks, - } - } -} - -/// An attr(...) rule -/// -/// `[namespace? `|`]? ident` -#[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[css(function)] -#[repr(C)] -pub struct Attr { - /// Optional namespace prefix. - pub namespace_prefix: Prefix, - /// Optional namespace URL. - pub namespace_url: Namespace, - /// Attribute name - pub attribute: Atom, -} - -impl Parse for Attr { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Attr, ParseError<'i>> { - input.expect_function_matching("attr")?; - input.parse_nested_block(|i| Attr::parse_function(context, i)) - } -} - -/// Get the Namespace for a given prefix from the namespace map. -fn get_namespace_for_prefix(prefix: &Prefix, context: &ParserContext) -> Option<Namespace> { - context.namespaces.prefixes.get(prefix).cloned() -} - -impl Attr { - /// Parse contents of attr() assuming we have already parsed `attr` and are - /// within a parse_nested_block() - pub fn parse_function<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Attr, ParseError<'i>> { - // Syntax is `[namespace? `|`]? ident` - // no spaces allowed - let first = input.try_parse(|i| i.expect_ident_cloned()).ok(); - if let Ok(token) = input.try_parse(|i| i.next_including_whitespace().map(|t| t.clone())) { - match token { - Token::Delim('|') => { - let location = input.current_source_location(); - // must be followed by an ident - let second_token = match *input.next_including_whitespace()? { - Token::Ident(ref second) => second, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - }; - - let (namespace_prefix, namespace_url) = if let Some(ns) = first { - let prefix = Prefix::from(ns.as_ref()); - let ns = match get_namespace_for_prefix(&prefix, context) { - Some(ns) => ns, - None => { - return Err(location - .new_custom_error(StyleParseErrorKind::UnspecifiedError)); - }, - }; - (prefix, ns) - } else { - (Prefix::default(), Namespace::default()) - }; - return Ok(Attr { - namespace_prefix, - namespace_url, - attribute: Atom::from(second_token.as_ref()), - }); - }, - // In the case of attr(foobar ) we don't want to error out - // because of the trailing whitespace. - Token::WhiteSpace(..) => {}, - ref t => return Err(input.new_unexpected_token_error(t.clone())), - } - } - - if let Some(first) = first { - Ok(Attr { - namespace_prefix: Prefix::default(), - namespace_url: Namespace::default(), - attribute: Atom::from(first.as_ref()), - }) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -impl ToCss for Attr { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str("attr(")?; - if !self.namespace_prefix.is_empty() { - serialize_atom_identifier(&self.namespace_prefix, dest)?; - dest.write_char('|')?; - } - serialize_atom_identifier(&self.attribute, dest)?; - dest.write_char(')') - } -} diff --git a/components/style/values/specified/motion.rs b/components/style/values/specified/motion.rs deleted file mode 100644 index c1ace2cea60..00000000000 --- a/components/style/values/specified/motion.rs +++ /dev/null @@ -1,238 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values that are related to motion path. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics::motion as generics; -use crate::values::specified::position::{HorizontalPosition, VerticalPosition}; -use crate::values::specified::{Angle, Position}; -use crate::Zero; -use cssparser::Parser; -use style_traits::{ParseError, StyleParseErrorKind}; - -/// The specified value of ray() function. -pub type RayFunction = generics::GenericRayFunction<Angle, Position>; - -/// The specified value of `offset-path`. -pub type OffsetPath = generics::GenericOffsetPath<RayFunction>; - -/// The specified value of `offset-position`. -pub type OffsetPosition = generics::GenericOffsetPosition<HorizontalPosition, VerticalPosition>; - -impl Parse for RayFunction { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::specified::PositionOrAuto; - - if !static_prefs::pref!("layout.css.motion-path-ray.enabled") { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - let mut angle = None; - let mut size = None; - let mut contain = false; - let mut position = None; - loop { - if angle.is_none() { - angle = input.try_parse(|i| Angle::parse(context, i)).ok(); - } - - if size.is_none() { - size = input.try_parse(generics::RaySize::parse).ok(); - if size.is_some() { - continue; - } - } - - if !contain { - contain = input - .try_parse(|i| i.expect_ident_matching("contain")) - .is_ok(); - if contain { - continue; - } - } - - if position.is_none() { - if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() { - let pos = Position::parse(context, input)?; - position = Some(PositionOrAuto::Position(pos)); - } - - if position.is_some() { - continue; - } - } - break; - } - - if angle.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(RayFunction { - angle: angle.unwrap(), - // If no <ray-size> is specified it defaults to closest-side. - size: size.unwrap_or(generics::RaySize::ClosestSide), - contain, - position: position.unwrap_or(PositionOrAuto::auto()), - }) - } -} - -impl Parse for OffsetPath { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::specified::svg_path::{AllowEmpty, SVGPathData}; - - // Parse none. - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(OffsetPath::none()); - } - - // Parse possible functions. - let location = input.current_source_location(); - let function = input.expect_function()?.clone(); - input.parse_nested_block(move |i| { - match_ignore_ascii_case! { &function, - // Bug 1186329: Implement the parser for <basic-shape>, <geometry-box>, - // and <url>. - "path" => SVGPathData::parse(i, AllowEmpty::No).map(OffsetPath::Path), - "ray" => RayFunction::parse(context, i).map(|v| OffsetPath::Ray(Box::new(v))), - _ => { - Err(location.new_custom_error( - StyleParseErrorKind::UnexpectedFunction(function.clone()) - )) - }, - } - }) - } -} - -/// The direction of offset-rotate. -#[derive( - Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -#[repr(u8)] -pub enum OffsetRotateDirection { - /// Unspecified direction keyword. - #[css(skip)] - None, - /// 0deg offset (face forward). - Auto, - /// 180deg offset (face backward). - Reverse, -} - -impl OffsetRotateDirection { - /// Returns true if it is none (i.e. the keyword is not specified). - #[inline] - fn is_none(&self) -> bool { - *self == OffsetRotateDirection::None - } -} - -#[inline] -fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool { - !direction.is_none() && angle.is_zero() -} - -/// The specified offset-rotate. -/// The syntax is: "[ auto | reverse ] || <angle>" -/// -/// https://drafts.fxtf.org/motion-1/#offset-rotate-property -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub struct OffsetRotate { - /// [auto | reverse]. - #[css(skip_if = "OffsetRotateDirection::is_none")] - direction: OffsetRotateDirection, - /// <angle>. - /// If direction is None, this is a fixed angle which indicates a - /// constant clockwise rotation transformation applied to it by this - /// specified rotation angle. Otherwise, the angle will be added to - /// the angle of the direction in layout. - #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")] - angle: Angle, -} - -impl OffsetRotate { - /// Returns the initial value, auto. - #[inline] - pub fn auto() -> Self { - OffsetRotate { - direction: OffsetRotateDirection::Auto, - angle: Angle::zero(), - } - } - - /// Returns true if self is auto 0deg. - #[inline] - pub fn is_auto(&self) -> bool { - self.direction == OffsetRotateDirection::Auto && self.angle.is_zero() - } -} - -impl Parse for OffsetRotate { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let mut direction = input.try_parse(OffsetRotateDirection::parse); - let angle = input.try_parse(|i| Angle::parse(context, i)); - if direction.is_err() { - // The direction and angle could be any order, so give it a change to parse - // direction again. - direction = input.try_parse(OffsetRotateDirection::parse); - } - - if direction.is_err() && angle.is_err() { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(OffsetRotate { - direction: direction.unwrap_or(OffsetRotateDirection::None), - angle: angle.unwrap_or(Zero::zero()), - }) - } -} - -impl ToComputedValue for OffsetRotate { - type ComputedValue = ComputedOffsetRotate; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - use crate::values::computed::Angle as ComputedAngle; - - ComputedOffsetRotate { - auto: !self.direction.is_none(), - angle: if self.direction == OffsetRotateDirection::Reverse { - // The computed value should always convert "reverse" into "auto". - // e.g. "reverse calc(20deg + 10deg)" => "auto 210deg" - self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0) - } else { - self.angle.to_computed_value(context) - }, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - OffsetRotate { - direction: if computed.auto { - OffsetRotateDirection::Auto - } else { - OffsetRotateDirection::None - }, - angle: ToComputedValue::from_computed_value(&computed.angle), - } - } -} diff --git a/components/style/values/specified/outline.rs b/components/style/values/specified/outline.rs deleted file mode 100644 index 6e5382d4c2b..00000000000 --- a/components/style/values/specified/outline.rs +++ /dev/null @@ -1,71 +0,0 @@ -/* 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/. */ - -//! Specified values for outline properties - -use crate::parser::{Parse, ParserContext}; -use crate::values::specified::BorderStyle; -use cssparser::Parser; -use selectors::parser::SelectorParseErrorKind; -use style_traits::ParseError; - -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Ord, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -/// <https://drafts.csswg.org/css-ui/#propdef-outline-style> -pub enum OutlineStyle { - /// auto - Auto, - /// <border-style> - BorderStyle(BorderStyle), -} - -impl OutlineStyle { - #[inline] - /// Get default value as None - pub fn none() -> OutlineStyle { - OutlineStyle::BorderStyle(BorderStyle::None) - } - - #[inline] - /// Get value for None or Hidden - pub fn none_or_hidden(&self) -> bool { - match *self { - OutlineStyle::Auto => false, - OutlineStyle::BorderStyle(ref style) => style.none_or_hidden(), - } - } -} - -impl Parse for OutlineStyle { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<OutlineStyle, ParseError<'i>> { - if let Ok(border_style) = input.try_parse(BorderStyle::parse) { - if let BorderStyle::Hidden = border_style { - return Err(input - .new_custom_error(SelectorParseErrorKind::UnexpectedIdent("hidden".into()))); - } - - return Ok(OutlineStyle::BorderStyle(border_style)); - } - - input.expect_ident_matching("auto")?; - Ok(OutlineStyle::Auto) - } -} diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs deleted file mode 100644 index 76d9105e8f3..00000000000 --- a/components/style/values/specified/page.rs +++ /dev/null @@ -1,99 +0,0 @@ -/* 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/. */ - -//! Specified @page at-rule properties and named-page style properties - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::size::Size2D; -use crate::values::specified::length::NonNegativeLength; -use crate::values::{generics, CustomIdent}; -use cssparser::Parser; -use style_traits::ParseError; - -pub use generics::page::PageOrientation; -pub use generics::page::PageSizeOrientation; -pub use generics::page::PaperSize; -/// Specified value of the @page size descriptor -pub type PageSize = generics::page::PageSize<Size2D<NonNegativeLength>>; - -impl Parse for PageSize { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // Try to parse as <page-size> [ <orientation> ] - if let Ok(paper_size) = input.try_parse(PaperSize::parse) { - let orientation = input - .try_parse(PageSizeOrientation::parse) - .unwrap_or(PageSizeOrientation::Portrait); - return Ok(PageSize::PaperSize(paper_size, orientation)); - } - // Try to parse as <orientation> [ <page-size> ] - if let Ok(orientation) = input.try_parse(PageSizeOrientation::parse) { - if let Ok(paper_size) = input.try_parse(PaperSize::parse) { - return Ok(PageSize::PaperSize(paper_size, orientation)); - } - return Ok(PageSize::Orientation(orientation)); - } - // Try to parse dimensions - if let Ok(size) = - input.try_parse(|i| Size2D::parse_with(context, i, NonNegativeLength::parse)) - { - return Ok(PageSize::Size(size)); - } - // auto value - input.expect_ident_matching("auto")?; - Ok(PageSize::Auto) - } -} - -/// Page name value. -/// -/// https://drafts.csswg.org/css-page-3/#using-named-pages -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum PageName { - /// `auto` value. - Auto, - /// Page name value - PageName(CustomIdent), -} - -impl Parse for PageName { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - Ok(match_ignore_ascii_case! { ident, - "auto" => PageName::auto(), - _ => PageName::PageName(CustomIdent::from_ident(location, ident, &[])?), - }) - } -} - -impl PageName { - /// `auto` value. - #[inline] - pub fn auto() -> Self { - PageName::Auto - } - - /// Whether this is the `auto` value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, PageName::Auto) - } -} diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs deleted file mode 100644 index ccf16d64639..00000000000 --- a/components/style/values/specified/percentage.rs +++ /dev/null @@ -1,225 +0,0 @@ -/* 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/. */ - -//! Specified percentages. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::percentage::Percentage as ComputedPercentage; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics::NonNegative; -use crate::values::specified::calc::CalcNode; -use crate::values::specified::Number; -use crate::values::{normalize, serialize_percentage, CSSFloat}; -use cssparser::{Parser, Token}; -use std::fmt::{self, Write}; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, ToCss}; - -/// A percentage value. -#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq, ToShmem)] -pub struct Percentage { - /// The percentage value as a float. - /// - /// [0 .. 100%] maps to [0.0 .. 1.0] - value: CSSFloat, - /// If this percentage came from a calc() expression, this tells how - /// clamping should be done on the value. - calc_clamping_mode: Option<AllowedNumericType>, -} - -impl ToCss for Percentage { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.calc_clamping_mode.is_some() { - dest.write_str("calc(")?; - } - - serialize_percentage(self.value, dest)?; - - if self.calc_clamping_mode.is_some() { - dest.write_char(')')?; - } - Ok(()) - } -} - -impl Percentage { - /// Creates a percentage from a numeric value. - pub(super) fn new_with_clamping_mode( - value: CSSFloat, - calc_clamping_mode: Option<AllowedNumericType>, - ) -> Self { - Self { - value, - calc_clamping_mode, - } - } - - /// Creates a percentage from a numeric value. - pub fn new(value: CSSFloat) -> Self { - Self::new_with_clamping_mode(value, None) - } - - /// `0%` - #[inline] - pub fn zero() -> Self { - Percentage { - value: 0., - calc_clamping_mode: None, - } - } - - /// `100%` - #[inline] - pub fn hundred() -> Self { - Percentage { - value: 1., - calc_clamping_mode: None, - } - } - - /// Gets the underlying value for this float. - pub fn get(&self) -> CSSFloat { - self.calc_clamping_mode - .map_or(self.value, |mode| mode.clamp(self.value)) - } - - /// Returns this percentage as a number. - pub fn to_number(&self) -> Number { - Number::new_with_clamping_mode(self.value, self.calc_clamping_mode) - } - - /// Returns the calc() clamping mode for this percentage. - pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> { - self.calc_clamping_mode - } - - /// Reverses this percentage, preserving calc-ness. - /// - /// For example: If it was 20%, convert it into 80%. - pub fn reverse(&mut self) { - let new_value = 1. - self.value; - self.value = new_value; - } - - /// Parses a specific kind of percentage. - pub fn parse_with_clamping_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - num_context: AllowedNumericType, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::Percentage { unit_value, .. } - if num_context.is_ok(context.parsing_mode, unit_value) => - { - Ok(Percentage::new(unit_value)) - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - let value = CalcNode::parse_percentage(context, input, function)?; - Ok(Percentage { - value, - calc_clamping_mode: Some(num_context), - }) - }, - ref t => Err(location.new_unexpected_token_error(t.clone())), - } - } - - /// Parses a percentage token, but rejects it if it's negative. - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) - } - - /// Parses a percentage token, but rejects it if it's negative or more than - /// 100%. - pub fn parse_zero_to_a_hundred<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne) - } - - /// Clamp to 100% if the value is over 100%. - #[inline] - pub fn clamp_to_hundred(self) -> Self { - Percentage { - value: self.value.min(1.), - calc_clamping_mode: self.calc_clamping_mode, - } - } -} - -impl Parse for Percentage { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::All) - } -} - -impl ToComputedValue for Percentage { - type ComputedValue = ComputedPercentage; - - #[inline] - fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { - ComputedPercentage(normalize(self.get())) - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Percentage::new(computed.0) - } -} - -impl SpecifiedValueInfo for Percentage {} - -/// Turns the percentage into a plain float. -pub trait ToPercentage { - /// Returns whether this percentage used to be a calc(). - fn is_calc(&self) -> bool { - false - } - /// Turns the percentage into a plain float. - fn to_percentage(&self) -> CSSFloat; -} - -impl ToPercentage for Percentage { - fn is_calc(&self) -> bool { - self.calc_clamping_mode.is_some() - } - - fn to_percentage(&self) -> CSSFloat { - self.get() - } -} - -/// A wrapper of Percentage, whose value must be >= 0. -pub type NonNegativePercentage = NonNegative<Percentage>; - -impl Parse for NonNegativePercentage { - #[inline] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Ok(NonNegative(Percentage::parse_non_negative(context, input)?)) - } -} - -impl NonNegativePercentage { - /// Convert to ComputedPercentage, for FontFaceRule size-adjust getter. - #[inline] - pub fn compute(&self) -> ComputedPercentage { - ComputedPercentage(self.0.get()) - } -} diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs deleted file mode 100644 index 8f4b495d3f6..00000000000 --- a/components/style/values/specified/position.rs +++ /dev/null @@ -1,905 +0,0 @@ -/* 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/. */ - -//! CSS handling for the specified value of -//! [`position`][position]s -//! -//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position - -use crate::parser::{Parse, ParserContext}; -use crate::selector_map::PrecomputedHashMap; -use crate::str::HTML_SPACE_CHARACTERS; -use crate::values::computed::LengthPercentage as ComputedLengthPercentage; -use crate::values::computed::{Context, Percentage, ToComputedValue}; -use crate::values::generics::position::AspectRatio as GenericAspectRatio; -use crate::values::generics::position::Position as GenericPosition; -use crate::values::generics::position::PositionComponent as GenericPositionComponent; -use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; -use crate::values::generics::position::ZIndex as GenericZIndex; -use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber}; -use crate::{Atom, Zero}; -use cssparser::Parser; -use selectors::parser::SelectorParseErrorKind; -use servo_arc::Arc; -use std::fmt::{self, Write}; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// The specified value of a CSS `<position>` -pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>; - -/// The specified value of an `auto | <position>`. -pub type PositionOrAuto = GenericPositionOrAuto<Position>; - -/// The specified value of a horizontal position. -pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>; - -/// The specified value of a vertical position. -pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>; - -/// The specified value of a component of a CSS `<position>`. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum PositionComponent<S> { - /// `center` - Center, - /// `<length-percentage>` - Length(LengthPercentage), - /// `<side> <length-percentage>?` - Side(S, Option<LengthPercentage>), -} - -/// A keyword for the X direction. -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum HorizontalPositionKeyword { - Left, - Right, -} - -/// A keyword for the Y direction. -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum VerticalPositionKeyword { - Top, - Bottom, -} - -impl Parse for Position { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?; - if position.is_three_value_syntax() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - Ok(position) - } -} - -impl Position { - /// Parses a `<bg-position>`, with quirks. - pub fn parse_three_value_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) { - Ok(x_pos @ PositionComponent::Center) => { - if let Ok(y_pos) = - input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) - { - return Ok(Self::new(x_pos, y_pos)); - } - let x_pos = input - .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) - .unwrap_or(x_pos); - let y_pos = PositionComponent::Center; - return Ok(Self::new(x_pos, y_pos)); - }, - Ok(PositionComponent::Side(x_keyword, lp)) => { - if input - .try_parse(|i| i.expect_ident_matching("center")) - .is_ok() - { - let x_pos = PositionComponent::Side(x_keyword, lp); - let y_pos = PositionComponent::Center; - return Ok(Self::new(x_pos, y_pos)); - } - if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) { - let y_lp = input - .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) - .ok(); - let x_pos = PositionComponent::Side(x_keyword, lp); - let y_pos = PositionComponent::Side(y_keyword, y_lp); - return Ok(Self::new(x_pos, y_pos)); - } - let x_pos = PositionComponent::Side(x_keyword, None); - let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length); - return Ok(Self::new(x_pos, y_pos)); - }, - Ok(x_pos @ PositionComponent::Length(_)) => { - if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) { - let y_pos = PositionComponent::Side(y_keyword, None); - return Ok(Self::new(x_pos, y_pos)); - } - if let Ok(y_lp) = - input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) - { - let y_pos = PositionComponent::Length(y_lp); - return Ok(Self::new(x_pos, y_pos)); - } - let y_pos = PositionComponent::Center; - let _ = input.try_parse(|i| i.expect_ident_matching("center")); - return Ok(Self::new(x_pos, y_pos)); - }, - Err(_) => {}, - } - let y_keyword = VerticalPositionKeyword::parse(input)?; - let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| { - let y_lp = i - .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) - .ok(); - if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) { - let x_lp = i - .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) - .ok(); - let x_pos = PositionComponent::Side(x_keyword, x_lp); - return Ok((y_lp, x_pos)); - }; - i.expect_ident_matching("center")?; - let x_pos = PositionComponent::Center; - Ok((y_lp, x_pos)) - }); - if let Ok((y_lp, x_pos)) = lp_and_x_pos { - let y_pos = PositionComponent::Side(y_keyword, y_lp); - return Ok(Self::new(x_pos, y_pos)); - } - let x_pos = PositionComponent::Center; - let y_pos = PositionComponent::Side(y_keyword, None); - Ok(Self::new(x_pos, y_pos)) - } - - /// `center center` - #[inline] - pub fn center() -> Self { - Self::new(PositionComponent::Center, PositionComponent::Center) - } - - /// Returns true if this uses a 3 value syntax. - #[inline] - fn is_three_value_syntax(&self) -> bool { - self.horizontal.component_count() != self.vertical.component_count() - } -} - -impl ToCss for Position { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - match (&self.horizontal, &self.vertical) { - ( - x_pos @ &PositionComponent::Side(_, Some(_)), - &PositionComponent::Length(ref y_lp), - ) => { - x_pos.to_css(dest)?; - dest.write_str(" top ")?; - y_lp.to_css(dest) - }, - ( - &PositionComponent::Length(ref x_lp), - y_pos @ &PositionComponent::Side(_, Some(_)), - ) => { - dest.write_str("left ")?; - x_lp.to_css(dest)?; - dest.write_char(' ')?; - y_pos.to_css(dest) - }, - (x_pos, y_pos) => { - x_pos.to_css(dest)?; - dest.write_char(' ')?; - y_pos.to_css(dest) - }, - } - } -} - -impl<S: Parse> Parse for PositionComponent<S> { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_quirky(context, input, AllowQuirks::No) - } -} - -impl<S: Parse> PositionComponent<S> { - /// Parses a component of a CSS position, with quirks. - pub fn parse_quirky<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - allow_quirks: AllowQuirks, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|i| i.expect_ident_matching("center")) - .is_ok() - { - return Ok(PositionComponent::Center); - } - if let Ok(lp) = - input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) - { - return Ok(PositionComponent::Length(lp)); - } - let keyword = S::parse(context, input)?; - let lp = input - .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks)) - .ok(); - Ok(PositionComponent::Side(keyword, lp)) - } -} - -impl<S> GenericPositionComponent for PositionComponent<S> { - fn is_center(&self) -> bool { - match *self { - PositionComponent::Center => true, - PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.0 == 0.5, - // 50% from any side is still the center. - PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => per.0 == 0.5, - _ => false, - } - } -} - -impl<S> PositionComponent<S> { - /// `0%` - pub fn zero() -> Self { - PositionComponent::Length(LengthPercentage::Percentage(Percentage::zero())) - } - - /// Returns the count of this component. - fn component_count(&self) -> usize { - match *self { - PositionComponent::Length(..) | PositionComponent::Center => 1, - PositionComponent::Side(_, ref lp) => { - if lp.is_some() { - 2 - } else { - 1 - } - }, - } - } -} - -impl<S: Side> ToComputedValue for PositionComponent<S> { - type ComputedValue = ComputedLengthPercentage; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)), - PositionComponent::Side(ref keyword, None) => { - let p = Percentage(if keyword.is_start() { 0. } else { 1. }); - ComputedLengthPercentage::new_percent(p) - }, - PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => { - let length = length.to_computed_value(context); - // We represent `<end-side> <length>` as `calc(100% - <length>)`. - ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All) - }, - PositionComponent::Side(_, Some(ref length)) | - PositionComponent::Length(ref length) => length.to_computed_value(context), - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - PositionComponent::Length(ToComputedValue::from_computed_value(computed)) - } -} - -impl<S: Side> PositionComponent<S> { - /// The initial specified value of a position component, i.e. the start side. - pub fn initial_specified_value() -> Self { - PositionComponent::Side(S::start(), None) - } -} - -/// Represents a side, either horizontal or vertical, of a CSS position. -pub trait Side { - /// Returns the start side. - fn start() -> Self; - - /// Returns whether this side is the start side. - fn is_start(&self) -> bool; -} - -impl Side for HorizontalPositionKeyword { - #[inline] - fn start() -> Self { - HorizontalPositionKeyword::Left - } - - #[inline] - fn is_start(&self) -> bool { - *self == Self::start() - } -} - -impl Side for VerticalPositionKeyword { - #[inline] - fn start() -> Self { - VerticalPositionKeyword::Top - } - - #[inline] - fn is_start(&self) -> bool { - *self == Self::start() - } -} - -bitflags! { - /// Controls how the auto-placement algorithm works specifying exactly how auto-placed items - /// get flowed into the grid. - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "row,column,dense")] - #[repr(C)] - pub struct GridAutoFlow: u8 { - /// 'row' - mutually exclusive with 'column' - const ROW = 1 << 0; - /// 'column' - mutually exclusive with 'row' - const COLUMN = 1 << 1; - /// 'dense' - const DENSE = 1 << 2; - } -} - -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -/// Masonry auto-placement algorithm packing. -pub enum MasonryPlacement { - /// Place the item in the track(s) with the smallest extent so far. - Pack, - /// Place the item after the last item, from start to end. - Next, -} - -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -/// Masonry auto-placement algorithm item sorting option. -pub enum MasonryItemOrder { - /// Place all items with a definite placement before auto-placed items. - DefiniteFirst, - /// Place items in `order-modified document order`. - Ordered, -} - -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// Controls how the Masonry layout algorithm works -/// specifying exactly how auto-placed items get flowed in the masonry axis. -pub struct MasonryAutoFlow { - /// Specify how to pick a auto-placement track. - #[css(contextual_skip_if = "is_pack_with_non_default_order")] - pub placement: MasonryPlacement, - /// Specify how to pick an item to place. - #[css(skip_if = "is_item_order_definite_first")] - pub order: MasonryItemOrder, -} - -#[inline] -fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool { - *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst -} - -#[inline] -fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool { - *order == MasonryItemOrder::DefiniteFirst -} - -impl MasonryAutoFlow { - #[inline] - /// Get initial `masonry-auto-flow` value. - pub fn initial() -> MasonryAutoFlow { - MasonryAutoFlow { - placement: MasonryPlacement::Pack, - order: MasonryItemOrder::DefiniteFirst, - } - } -} - -impl Parse for MasonryAutoFlow { - /// [ definite-first | ordered ] || [ pack | next ] - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<MasonryAutoFlow, ParseError<'i>> { - let mut value = MasonryAutoFlow::initial(); - let mut got_placement = false; - let mut got_order = false; - while !input.is_exhausted() { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - let success = match_ignore_ascii_case! { &ident, - "pack" if !got_placement => { - got_placement = true; - true - }, - "next" if !got_placement => { - value.placement = MasonryPlacement::Next; - got_placement = true; - true - }, - "definite-first" if !got_order => { - got_order = true; - true - }, - "ordered" if !got_order => { - value.order = MasonryItemOrder::Ordered; - got_order = true; - true - }, - _ => false - }; - if !success { - return Err(location - .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))); - } - } - - if got_placement || got_order { - Ok(value) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -// TODO: Can be derived with some care. -impl Parse for GridAutoFlow { - /// [ row | column ] || dense - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<GridAutoFlow, ParseError<'i>> { - let mut track = None; - let mut dense = GridAutoFlow::empty(); - - while !input.is_exhausted() { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - let success = match_ignore_ascii_case! { &ident, - "row" if track.is_none() => { - track = Some(GridAutoFlow::ROW); - true - }, - "column" if track.is_none() => { - track = Some(GridAutoFlow::COLUMN); - true - }, - "dense" if dense.is_empty() => { - dense = GridAutoFlow::DENSE; - true - }, - _ => false, - }; - if !success { - return Err(location - .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))); - } - } - - if track.is_some() || !dense.is_empty() { - Ok(track.unwrap_or(GridAutoFlow::ROW) | dense) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -impl ToCss for GridAutoFlow { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if *self == GridAutoFlow::ROW { - return dest.write_str("row"); - } - - if *self == GridAutoFlow::COLUMN { - return dest.write_str("column"); - } - - if *self == GridAutoFlow::ROW | GridAutoFlow::DENSE { - return dest.write_str("dense"); - } - - if *self == GridAutoFlow::COLUMN | GridAutoFlow::DENSE { - return dest.write_str("column dense"); - } - - debug_assert!(false, "Unknown or invalid grid-autoflow value"); - Ok(()) - } -} - -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// https://drafts.csswg.org/css-grid/#named-grid-area -pub struct TemplateAreas { - /// `named area` containing for each template area - #[css(skip)] - pub areas: crate::OwnedSlice<NamedArea>, - /// The original CSS string value of each template area - #[css(iterable)] - pub strings: crate::OwnedSlice<crate::OwnedStr>, - /// The number of columns of the grid. - #[css(skip)] - pub width: u32, -} - -impl TemplateAreas { - /// Transform `vector` of str into `template area` - pub fn from_vec(strings: Vec<crate::OwnedStr>) -> Result<Self, ()> { - if strings.is_empty() { - return Err(()); - } - let mut areas: Vec<NamedArea> = vec![]; - let mut width = 0; - { - let mut row = 0u32; - let mut area_indices = PrecomputedHashMap::<Atom, usize>::default(); - for string in &strings { - let mut current_area_index: Option<usize> = None; - row += 1; - let mut column = 0u32; - for token in TemplateAreasTokenizer(string) { - column += 1; - let name = if let Some(token) = token? { - Atom::from(token) - } else { - if let Some(index) = current_area_index.take() { - if areas[index].columns.end != column { - return Err(()); - } - } - continue; - }; - if let Some(index) = current_area_index { - if areas[index].name == name { - if areas[index].rows.start == row { - areas[index].columns.end += 1; - } - continue; - } - if areas[index].columns.end != column { - return Err(()); - } - } - if let Some(index) = area_indices.get(&name).cloned() { - if areas[index].columns.start != column || areas[index].rows.end != row { - return Err(()); - } - areas[index].rows.end += 1; - current_area_index = Some(index); - continue; - } - let index = areas.len(); - assert!(area_indices.insert(name.clone(), index).is_none()); - areas.push(NamedArea { - name, - columns: UnsignedRange { - start: column, - end: column + 1, - }, - rows: UnsignedRange { - start: row, - end: row + 1, - }, - }); - current_area_index = Some(index); - } - if column == 0 { - // Each string must produce a valid token. - // https://github.com/w3c/csswg-drafts/issues/5110 - return Err(()); - } - if let Some(index) = current_area_index { - if areas[index].columns.end != column + 1 { - assert_ne!(areas[index].rows.start, row); - return Err(()); - } - } - if row == 1 { - width = column; - } else if width != column { - return Err(()); - } - } - } - Ok(TemplateAreas { - areas: areas.into(), - strings: strings.into(), - width, - }) - } -} - -impl Parse for TemplateAreas { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut strings = vec![]; - while let Ok(string) = - input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned().into())) - { - strings.push(string); - } - - TemplateAreas::from_vec(strings) - .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } -} - -/// Arc type for `Arc<TemplateAreas>` -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>); - -impl Parse for TemplateAreasArc { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let parsed = TemplateAreas::parse(context, input)?; - Ok(TemplateAreasArc(Arc::new(parsed))) - } -} - -/// A range of rows or columns. Using this instead of std::ops::Range for FFI -/// purposes. -#[repr(C)] -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -pub struct UnsignedRange { - /// The start of the range. - pub start: u32, - /// The end of the range. - pub end: u32, -} - -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// Not associated with any particular grid item, but can be referenced from the -/// grid-placement properties. -pub struct NamedArea { - /// Name of the `named area` - pub name: Atom, - /// Rows of the `named area` - pub rows: UnsignedRange, - /// Columns of the `named area` - pub columns: UnsignedRange, -} - -/// Tokenize the string into a list of the tokens, -/// using longest-match semantics -struct TemplateAreasTokenizer<'a>(&'a str); - -impl<'a> Iterator for TemplateAreasTokenizer<'a> { - type Item = Result<Option<&'a str>, ()>; - - fn next(&mut self) -> Option<Self::Item> { - let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS); - if rest.is_empty() { - return None; - } - if rest.starts_with('.') { - self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..]; - return Some(Ok(None)); - } - if !rest.starts_with(is_name_code_point) { - return Some(Err(())); - } - let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len()); - let token = &rest[..token_len]; - self.0 = &rest[token_len..]; - Some(Ok(Some(token))) - } -} - -fn is_name_code_point(c: char) -> bool { - c >= 'A' && c <= 'Z' || - c >= 'a' && c <= 'z' || - c >= '\u{80}' || - c == '_' || - c >= '0' && c <= '9' || - c == '-' -} - -/// This property specifies named grid areas. -/// -/// The syntax of this property also provides a visualization of the structure -/// of the grid, making the overall layout of the grid container easier to -/// understand. -#[repr(C, u8)] -#[derive( - Clone, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -pub enum GridTemplateAreas { - /// The `none` value. - None, - /// The actual value. - Areas(TemplateAreasArc), -} - -impl GridTemplateAreas { - #[inline] - /// Get default value as `none` - pub fn none() -> GridTemplateAreas { - GridTemplateAreas::None - } -} - -/// A specified value for the `z-index` property. -pub type ZIndex = GenericZIndex<Integer>; - -/// A specified value for the `aspect-ratio` property. -pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>; - -impl Parse for AspectRatio { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::values::generics::position::PreferredRatio; - use crate::values::specified::Ratio; - - let location = input.current_source_location(); - let mut auto = input.try_parse(|i| i.expect_ident_matching("auto")); - let ratio = input.try_parse(|i| Ratio::parse(context, i)); - if auto.is_err() { - auto = input.try_parse(|i| i.expect_ident_matching("auto")); - } - - if auto.is_err() && ratio.is_err() { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(AspectRatio { - auto: auto.is_ok(), - ratio: match ratio { - Ok(ratio) => PreferredRatio::Ratio(ratio), - Err(..) => PreferredRatio::None, - }, - }) - } -} - -impl AspectRatio { - /// Returns Self by a valid ratio. - pub fn from_mapped_ratio(w: f32, h: f32) -> Self { - use crate::values::generics::position::PreferredRatio; - use crate::values::generics::ratio::Ratio; - AspectRatio { - auto: true, - ratio: PreferredRatio::Ratio(Ratio( - NonNegativeNumber::new(w), - NonNegativeNumber::new(h), - )), - } - } -} diff --git a/components/style/values/specified/ratio.rs b/components/style/values/specified/ratio.rs deleted file mode 100644 index 4cdddd452ed..00000000000 --- a/components/style/values/specified/ratio.rs +++ /dev/null @@ -1,32 +0,0 @@ -/* 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/. */ - -//! Specified types for <ratio>. -//! -//! [ratio]: https://drafts.csswg.org/css-values/#ratios - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::ratio::Ratio as GenericRatio; -use crate::values::specified::NonNegativeNumber; -use crate::One; -use cssparser::Parser; -use style_traits::ParseError; - -/// A specified <ratio> value. -pub type Ratio = GenericRatio<NonNegativeNumber>; - -impl Parse for Ratio { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let a = NonNegativeNumber::parse(context, input)?; - let b = match input.try_parse(|input| input.expect_delim('/')) { - Ok(()) => NonNegativeNumber::parse(context, input)?, - _ => One::one(), - }; - - Ok(GenericRatio(a, b)) - } -} diff --git a/components/style/values/specified/rect.rs b/components/style/values/specified/rect.rs deleted file mode 100644 index 7955ecaa48d..00000000000 --- a/components/style/values/specified/rect.rs +++ /dev/null @@ -1,11 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS borders. - -use crate::values::generics::rect::Rect; -use crate::values::specified::length::NonNegativeLengthOrNumber; - -/// A specified rectangle made of four `<length-or-number>` values. -pub type NonNegativeLengthOrNumberRect = Rect<NonNegativeLengthOrNumber>; diff --git a/components/style/values/specified/resolution.rs b/components/style/values/specified/resolution.rs deleted file mode 100644 index 74f100972a7..00000000000 --- a/components/style/values/specified/resolution.rs +++ /dev/null @@ -1,141 +0,0 @@ -/* 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/. */ - -//! Resolution values: -//! -//! https://drafts.csswg.org/css-values/#resolution - -use crate::parser::{Parse, ParserContext}; -use crate::values::specified::CalcNode; -use crate::values::CSSFloat; -use cssparser::{Parser, Token}; -use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// A specified resolution. -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)] -pub struct Resolution { - value: CSSFloat, - unit: ResolutionUnit, - was_calc: bool, -} - -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -enum ResolutionUnit { - /// Dots per inch. - Dpi, - /// An alias unit for dots per pixel. - X, - /// Dots per pixel. - Dppx, - /// Dots per centimeter. - Dpcm, -} - -impl ResolutionUnit { - fn as_str(self) -> &'static str { - match self { - Self::Dpi => "dpi", - Self::X => "x", - Self::Dppx => "dppx", - Self::Dpcm => "dpcm", - } - } -} - -impl Resolution { - /// Returns a resolution value from dppx units. - pub fn from_dppx(value: CSSFloat) -> Self { - Self { - value, - unit: ResolutionUnit::Dppx, - was_calc: false, - } - } - - /// Returns a resolution value from dppx units. - pub fn from_x(value: CSSFloat) -> Self { - Self { - value, - unit: ResolutionUnit::X, - was_calc: false, - } - } - - /// Returns a resolution value from dppx units. - pub fn from_dppx_calc(value: CSSFloat) -> Self { - Self { - value, - unit: ResolutionUnit::Dppx, - was_calc: true, - } - } - - /// Convert this resolution value to dppx units. - pub fn dppx(&self) -> CSSFloat { - match self.unit { - ResolutionUnit::X | ResolutionUnit::Dppx => self.value, - _ => self.dpi() / 96.0, - } - } - - /// Convert this resolution value to dpi units. - pub fn dpi(&self) -> CSSFloat { - match self.unit { - ResolutionUnit::Dpi => self.value, - ResolutionUnit::X | ResolutionUnit::Dppx => self.value * 96.0, - ResolutionUnit::Dpcm => self.value * 2.54, - } - } - - /// Parse a resolution given a value and unit. - pub fn parse_dimension<'i, 't>(value: CSSFloat, unit: &str) -> Result<Self, ()> { - let unit = match_ignore_ascii_case! { &unit, - "dpi" => ResolutionUnit::Dpi, - "dppx" => ResolutionUnit::Dppx, - "dpcm" => ResolutionUnit::Dpcm, - "x" => ResolutionUnit::X, - _ => return Err(()) - }; - Ok(Self { - value, - unit, - was_calc: false, - }) - } -} - -impl ToCss for Resolution { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - crate::values::serialize_specified_dimension( - self.value, - self.unit.as_str(), - self.was_calc, - dest, - ) - } -} - -impl Parse for Resolution { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - match *input.next()? { - Token::Dimension { - value, ref unit, .. - } if value >= 0. => Self::parse_dimension(value, unit) - .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - CalcNode::parse_resolution(context, input, function) - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - } - } -} diff --git a/components/style/values/specified/source_size_list.rs b/components/style/values/specified/source_size_list.rs deleted file mode 100644 index ac47461cc48..00000000000 --- a/components/style/values/specified/source_size_list.rs +++ /dev/null @@ -1,136 +0,0 @@ -/* 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/. */ - -//! https://html.spec.whatwg.org/multipage/#source-size-list - -use crate::media_queries::Device; -use crate::parser::{Parse, ParserContext}; -use crate::queries::{FeatureType, QueryCondition}; -use crate::values::computed::{self, ToComputedValue}; -use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength}; -use app_units::Au; -use cssparser::{Delimiter, Parser, Token}; -use selectors::context::QuirksMode; -use style_traits::ParseError; - -/// A value for a `<source-size>`: -/// -/// https://html.spec.whatwg.org/multipage/#source-size -#[derive(Debug)] -pub struct SourceSize { - condition: QueryCondition, - value: Length, -} - -impl Parse for SourceSize { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let condition = QueryCondition::parse(context, input, FeatureType::Media)?; - let value = Length::parse_non_negative(context, input)?; - Ok(Self { condition, value }) - } -} - -/// A value for a `<source-size-list>`: -/// -/// https://html.spec.whatwg.org/multipage/#source-size-list -#[derive(Debug)] -pub struct SourceSizeList { - source_sizes: Vec<SourceSize>, - value: Option<Length>, -} - -impl SourceSizeList { - /// Create an empty `SourceSizeList`, which can be used as a fall-back. - pub fn empty() -> Self { - Self { - source_sizes: vec![], - value: None, - } - } - - /// Evaluate this <source-size-list> to get the final viewport length. - pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au { - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - let matching_source_size = self.source_sizes.iter().find(|source_size| { - source_size - .condition - .matches(context) - .to_bool(/* unknown = */ false) - }); - - match matching_source_size { - Some(source_size) => source_size.value.to_computed_value(context), - None => match self.value { - Some(ref v) => v.to_computed_value(context), - None => Length::NoCalc(NoCalcLength::ViewportPercentage( - ViewportPercentageLength::Vw(100.), - )) - .to_computed_value(context), - }, - } - }) - .into() - } -} - -enum SourceSizeOrLength { - SourceSize(SourceSize), - Length(Length), -} - -impl Parse for SourceSizeOrLength { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(size) = input.try_parse(|input| SourceSize::parse(context, input)) { - return Ok(SourceSizeOrLength::SourceSize(size)); - } - - let length = Length::parse_non_negative(context, input)?; - Ok(SourceSizeOrLength::Length(length)) - } -} - -impl SourceSizeList { - /// NOTE(emilio): This doesn't match the grammar in the spec, see: - /// - /// https://html.spec.whatwg.org/multipage/#parsing-a-sizes-attribute - pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Self { - let mut source_sizes = vec![]; - - loop { - let result = input.parse_until_before(Delimiter::Comma, |input| { - SourceSizeOrLength::parse(context, input) - }); - - match result { - Ok(SourceSizeOrLength::Length(value)) => { - return Self { - source_sizes, - value: Some(value), - }; - }, - Ok(SourceSizeOrLength::SourceSize(source_size)) => { - source_sizes.push(source_size); - }, - Err(..) => {}, - } - - match input.next() { - Ok(&Token::Comma) => {}, - Err(..) => break, - _ => unreachable!(), - } - } - - SourceSizeList { - source_sizes, - value: None, - } - } -} diff --git a/components/style/values/specified/svg.rs b/components/style/values/specified/svg.rs deleted file mode 100644 index 24b7d3f3aec..00000000000 --- a/components/style/values/specified/svg.rs +++ /dev/null @@ -1,391 +0,0 @@ -/* 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/. */ - -//! Specified types for SVG properties. - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::svg as generic; -use crate::values::specified::color::Color; -use crate::values::specified::url::SpecifiedUrl; -use crate::values::specified::AllowQuirks; -use crate::values::specified::LengthPercentage; -use crate::values::specified::SVGPathData; -use crate::values::specified::{NonNegativeLengthPercentage, Opacity}; -use crate::values::CustomIdent; -use cssparser::{Parser, Token}; -use std::fmt::{self, Write}; -use style_traits::{CommaWithSpace, CssWriter, ParseError, Separator}; -use style_traits::{StyleParseErrorKind, ToCss}; - -/// Specified SVG Paint value -pub type SVGPaint = generic::GenericSVGPaint<Color, SpecifiedUrl>; - -/// <length> | <percentage> | <number> | context-value -pub type SVGLength = generic::GenericSVGLength<LengthPercentage>; - -/// A non-negative version of SVGLength. -pub type SVGWidth = generic::GenericSVGLength<NonNegativeLengthPercentage>; - -/// [ <length> | <percentage> | <number> ]# | context-value -pub type SVGStrokeDashArray = generic::GenericSVGStrokeDashArray<NonNegativeLengthPercentage>; - -/// Whether the `context-value` value is enabled. -#[cfg(feature = "gecko")] -pub fn is_context_value_enabled() -> bool { - static_prefs::pref!("gfx.font_rendering.opentype_svg.enabled") -} - -/// Whether the `context-value` value is enabled. -#[cfg(not(feature = "gecko"))] -pub fn is_context_value_enabled() -> bool { - false -} - -macro_rules! parse_svg_length { - ($ty:ty, $lp:ty) => { - impl Parse for $ty { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(lp) = - input.try_parse(|i| <$lp>::parse_quirky(context, i, AllowQuirks::Always)) - { - return Ok(generic::SVGLength::LengthPercentage(lp)); - } - - try_match_ident_ignore_ascii_case! { input, - "context-value" if is_context_value_enabled() => { - Ok(generic::SVGLength::ContextValue) - }, - } - } - } - }; -} - -parse_svg_length!(SVGLength, LengthPercentage); -parse_svg_length!(SVGWidth, NonNegativeLengthPercentage); - -impl Parse for SVGStrokeDashArray { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if let Ok(values) = input.try_parse(|i| { - CommaWithSpace::parse(i, |i| { - NonNegativeLengthPercentage::parse_quirky(context, i, AllowQuirks::Always) - }) - }) { - return Ok(generic::SVGStrokeDashArray::Values(values.into())); - } - - try_match_ident_ignore_ascii_case! { input, - "context-value" if is_context_value_enabled() => { - Ok(generic::SVGStrokeDashArray::ContextValue) - }, - "none" => Ok(generic::SVGStrokeDashArray::Values(Default::default())), - } - } -} - -/// <opacity-value> | context-fill-opacity | context-stroke-opacity -pub type SVGOpacity = generic::SVGOpacity<Opacity>; - -/// The specified value for a single CSS paint-order property. -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, ToCss)] -pub enum PaintOrder { - /// `normal` variant - Normal = 0, - /// `fill` variant - Fill = 1, - /// `stroke` variant - Stroke = 2, - /// `markers` variant - Markers = 3, -} - -/// Number of non-normal components -pub const PAINT_ORDER_COUNT: u8 = 3; - -/// Number of bits for each component -pub const PAINT_ORDER_SHIFT: u8 = 2; - -/// Mask with above bits set -pub const PAINT_ORDER_MASK: u8 = 0b11; - -/// The specified value is tree `PaintOrder` values packed into the -/// bitfields below, as a six-bit field, of 3 two-bit pairs -/// -/// Each pair can be set to FILL, STROKE, or MARKERS -/// Lowest significant bit pairs are highest priority. -/// `normal` is the empty bitfield. The three pairs are -/// never zero in any case other than `normal`. -/// -/// Higher priority values, i.e. the values specified first, -/// will be painted first (and may be covered by paintings of lower priority) -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct SVGPaintOrder(pub u8); - -impl SVGPaintOrder { - /// Get default `paint-order` with `0` - pub fn normal() -> Self { - SVGPaintOrder(0) - } - - /// Get variant of `paint-order` - pub fn order_at(&self, pos: u8) -> PaintOrder { - match (self.0 >> pos * PAINT_ORDER_SHIFT) & PAINT_ORDER_MASK { - 0 => PaintOrder::Normal, - 1 => PaintOrder::Fill, - 2 => PaintOrder::Stroke, - 3 => PaintOrder::Markers, - _ => unreachable!("this cannot happen"), - } - } -} - -impl Parse for SVGPaintOrder { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<SVGPaintOrder, ParseError<'i>> { - if let Ok(()) = input.try_parse(|i| i.expect_ident_matching("normal")) { - return Ok(SVGPaintOrder::normal()); - } - - let mut value = 0; - // bitfield representing what we've seen so far - // bit 1 is fill, bit 2 is stroke, bit 3 is markers - let mut seen = 0; - let mut pos = 0; - - loop { - let result: Result<_, ParseError> = input.try_parse(|input| { - try_match_ident_ignore_ascii_case! { input, - "fill" => Ok(PaintOrder::Fill), - "stroke" => Ok(PaintOrder::Stroke), - "markers" => Ok(PaintOrder::Markers), - } - }); - - match result { - Ok(val) => { - if (seen & (1 << val as u8)) != 0 { - // don't parse the same ident twice - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - value |= (val as u8) << (pos * PAINT_ORDER_SHIFT); - seen |= 1 << (val as u8); - pos += 1; - }, - Err(_) => break, - } - } - - if value == 0 { - // Couldn't find any keyword - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // fill in rest - for i in pos..PAINT_ORDER_COUNT { - for paint in 1..(PAINT_ORDER_COUNT + 1) { - // if not seen, set bit at position, mark as seen - if (seen & (1 << paint)) == 0 { - seen |= 1 << paint; - value |= paint << (i * PAINT_ORDER_SHIFT); - break; - } - } - } - - Ok(SVGPaintOrder(value)) - } -} - -impl ToCss for SVGPaintOrder { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.0 == 0 { - return dest.write_str("normal"); - } - - let mut last_pos_to_serialize = 0; - for i in (1..PAINT_ORDER_COUNT).rev() { - let component = self.order_at(i); - let earlier_component = self.order_at(i - 1); - if component < earlier_component { - last_pos_to_serialize = i - 1; - break; - } - } - - for pos in 0..last_pos_to_serialize + 1 { - if pos != 0 { - dest.write_char(' ')? - } - self.order_at(pos).to_css(dest)?; - } - Ok(()) - } -} - -bitflags! { - /// The context properties we understand. - #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[repr(C)] - pub struct ContextPropertyBits: u8 { - /// `fill` - const FILL = 1 << 0; - /// `stroke` - const STROKE = 1 << 1; - /// `fill-opacity` - const FILL_OPACITY = 1 << 2; - /// `stroke-opacity` - const STROKE_OPACITY = 1 << 3; - } -} - -/// Specified MozContextProperties value. -/// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties) -#[derive( - Clone, - Debug, - Default, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct MozContextProperties { - #[css(iterable, if_empty = "none")] - #[ignore_malloc_size_of = "Arc"] - idents: crate::ArcSlice<CustomIdent>, - #[css(skip)] - bits: ContextPropertyBits, -} - -impl Parse for MozContextProperties { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<MozContextProperties, ParseError<'i>> { - let mut values = vec![]; - let mut bits = ContextPropertyBits::empty(); - loop { - { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - - if ident.eq_ignore_ascii_case("none") && values.is_empty() { - return Ok(Self::default()); - } - - let ident = CustomIdent::from_ident(location, ident, &["all", "none", "auto"])?; - - if ident.0 == atom!("fill") { - bits.insert(ContextPropertyBits::FILL); - } else if ident.0 == atom!("stroke") { - bits.insert(ContextPropertyBits::STROKE); - } else if ident.0 == atom!("fill-opacity") { - bits.insert(ContextPropertyBits::FILL_OPACITY); - } else if ident.0 == atom!("stroke-opacity") { - bits.insert(ContextPropertyBits::STROKE_OPACITY); - } - - values.push(ident); - } - - let location = input.current_source_location(); - match input.next() { - Ok(&Token::Comma) => continue, - Err(..) => break, - Ok(other) => return Err(location.new_unexpected_token_error(other.clone())), - } - } - - if values.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(MozContextProperties { - idents: crate::ArcSlice::from_iter(values.into_iter()), - bits, - }) - } -} - -/// The svg d property type. -/// -/// https://svgwg.org/svg2-draft/paths.html#TheDProperty -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum DProperty { - /// Path value for path(<string>) or just a <string>. - #[css(function)] - Path(SVGPathData), - /// None value. - #[animation(error)] - None, -} - -impl DProperty { - /// return none. - #[inline] - pub fn none() -> Self { - DProperty::None - } -} - -impl Parse for DProperty { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // Parse none. - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(DProperty::none()); - } - - // Parse possible functions. - input.expect_function_matching("path")?; - let path_data = input.parse_nested_block(|i| Parse::parse(context, i))?; - Ok(DProperty::Path(path_data)) - } -} diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs deleted file mode 100644 index 3b33a188fec..00000000000 --- a/components/style/values/specified/svg_path.rs +++ /dev/null @@ -1,1030 +0,0 @@ -/* 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/. */ - -//! Specified types for SVG Path. - -use crate::parser::{Parse, ParserContext}; -use crate::values::animated::{lists, Animate, Procedure, ToAnimatedZero}; -use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::CSSFloat; -use cssparser::Parser; -use std::fmt::{self, Write}; -use std::iter::{Cloned, Peekable}; -use std::slice; -use style_traits::values::SequenceWriter; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; - -/// Whether to allow empty string in the parser. -#[derive(Clone, Debug, Eq, PartialEq)] -#[allow(missing_docs)] -pub enum AllowEmpty { - Yes, - No, -} - -/// The SVG path data. -/// -/// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive( - Clone, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct SVGPathData( - // TODO(emilio): Should probably measure this somehow only from the - // specified values. - #[ignore_malloc_size_of = "Arc"] pub crate::ArcSlice<PathCommand>, -); - -impl SVGPathData { - /// Get the array of PathCommand. - #[inline] - pub fn commands(&self) -> &[PathCommand] { - &self.0 - } - - /// Create a normalized copy of this path by converting each relative - /// command to an absolute command. - pub fn normalize(&self) -> Self { - let mut state = PathTraversalState { - subpath_start: CoordPair::new(0.0, 0.0), - pos: CoordPair::new(0.0, 0.0), - }; - let iter = self.0.iter().map(|seg| seg.normalize(&mut state)); - SVGPathData(crate::ArcSlice::from_iter(iter)) - } - - // FIXME: Bug 1714238, we may drop this once we use the same data structure for both SVG and - // CSS. - /// Decode the svg path raw data from Gecko. - #[cfg(feature = "gecko")] - pub fn decode_from_f32_array(path: &[f32]) -> Result<Self, ()> { - use crate::gecko_bindings::structs::dom::SVGPathSeg_Binding::*; - - let mut result: Vec<PathCommand> = Vec::new(); - let mut i: usize = 0; - while i < path.len() { - // See EncodeType() and DecodeType() in SVGPathSegUtils.h. - // We are using reinterpret_cast<> to encode and decode between u32 and f32, so here we - // use to_bits() to decode the type. - let seg_type = path[i].to_bits() as u16; - i = i + 1; - match seg_type { - PATHSEG_CLOSEPATH => result.push(PathCommand::ClosePath), - PATHSEG_MOVETO_ABS | PATHSEG_MOVETO_REL => { - debug_assert!(i + 1 < path.len()); - result.push(PathCommand::MoveTo { - point: CoordPair::new(path[i], path[i + 1]), - absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS), - }); - i = i + 2; - }, - PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => { - debug_assert!(i + 1 < path.len()); - result.push(PathCommand::LineTo { - point: CoordPair::new(path[i], path[i + 1]), - absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS), - }); - i = i + 2; - }, - PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => { - debug_assert!(i + 5 < path.len()); - result.push(PathCommand::CurveTo { - control1: CoordPair::new(path[i], path[i + 1]), - control2: CoordPair::new(path[i + 2], path[i + 3]), - point: CoordPair::new(path[i + 4], path[i + 5]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS), - }); - i = i + 6; - }, - PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => { - debug_assert!(i + 3 < path.len()); - result.push(PathCommand::QuadBezierCurveTo { - control1: CoordPair::new(path[i], path[i + 1]), - point: CoordPair::new(path[i + 2], path[i + 3]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS), - }); - i = i + 4; - }, - PATHSEG_ARC_ABS | PATHSEG_ARC_REL => { - debug_assert!(i + 6 < path.len()); - result.push(PathCommand::EllipticalArc { - rx: path[i], - ry: path[i + 1], - angle: path[i + 2], - large_arc_flag: ArcFlag(path[i + 3] != 0.0f32), - sweep_flag: ArcFlag(path[i + 4] != 0.0f32), - point: CoordPair::new(path[i + 5], path[i + 6]), - absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS), - }); - i = i + 7; - }, - PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => { - debug_assert!(i < path.len()); - result.push(PathCommand::HorizontalLineTo { - x: path[i], - absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS), - }); - i = i + 1; - }, - PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => { - debug_assert!(i < path.len()); - result.push(PathCommand::VerticalLineTo { - y: path[i], - absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS), - }); - i = i + 1; - }, - PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => { - debug_assert!(i + 3 < path.len()); - result.push(PathCommand::SmoothCurveTo { - control2: CoordPair::new(path[i], path[i + 1]), - point: CoordPair::new(path[i + 2], path[i + 3]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS), - }); - i = i + 4; - }, - PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => { - debug_assert!(i + 1 < path.len()); - result.push(PathCommand::SmoothQuadBezierCurveTo { - point: CoordPair::new(path[i], path[i + 1]), - absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS), - }); - i = i + 2; - }, - PATHSEG_UNKNOWN | _ => return Err(()), - } - } - - Ok(SVGPathData(crate::ArcSlice::from_iter(result.into_iter()))) - } - - /// Parse this SVG path string with the argument that indicates whether we should allow the - /// empty string. - // We cannot use cssparser::Parser to parse a SVG path string because the spec wants to make - // the SVG path string as compact as possible. (i.e. The whitespaces may be dropped.) - // e.g. "M100 200L100 200" is a valid SVG path string. If we use tokenizer, the first ident - // is "M100", instead of "M", and this is not correct. Therefore, we use a Peekable - // str::Char iterator to check each character. - pub fn parse<'i, 't>( - input: &mut Parser<'i, 't>, - allow_empty: AllowEmpty, - ) -> Result<Self, ParseError<'i>> { - let location = input.current_source_location(); - let path_string = input.expect_string()?.as_ref(); - - // Parse the svg path string as multiple sub-paths. - let mut path_parser = PathParser::new(path_string); - while skip_wsp(&mut path_parser.chars) { - if path_parser.parse_subpath().is_err() { - return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - } - - // The css-shapes-1 says a path data string that does conform but defines an empty path is - // invalid and causes the entire path() to be invalid, so we use the argement to decide - // whether we should allow the empty string. - // https://drafts.csswg.org/css-shapes-1/#typedef-basic-shape - if matches!(allow_empty, AllowEmpty::No) && path_parser.path.is_empty() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - Ok(SVGPathData(crate::ArcSlice::from_iter( - path_parser.path.into_iter(), - ))) - } -} - -impl ToCss for SVGPathData { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - dest.write_char('"')?; - { - let mut writer = SequenceWriter::new(dest, " "); - for command in self.commands() { - writer.item(command)?; - } - } - dest.write_char('"') - } -} - -impl Parse for SVGPathData { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // Note that the EBNF allows the path data string in the d property to be empty, so we - // don't reject empty SVG path data. - // https://svgwg.org/svg2-draft/single-page.html#paths-PathDataBNF - SVGPathData::parse(input, AllowEmpty::Yes) - } -} - -impl Animate for SVGPathData { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if self.0.len() != other.0.len() { - return Err(()); - } - - // FIXME(emilio): This allocates three copies of the path, that's not - // great! Specially, once we're normalized once, we don't need to - // re-normalize again. - let left = self.normalize(); - let right = other.normalize(); - - let items: Vec<_> = lists::by_computed_value::animate(&left.0, &right.0, procedure)?; - Ok(SVGPathData(crate::ArcSlice::from_iter(items.into_iter()))) - } -} - -impl ComputeSquaredDistance for SVGPathData { - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - if self.0.len() != other.0.len() { - return Err(()); - } - let left = self.normalize(); - let right = other.normalize(); - lists::by_computed_value::squared_distance(&left.0, &right.0) - } -} - -/// The SVG path command. -/// The fields of these commands are self-explanatory, so we skip the documents. -/// Note: the index of the control points, e.g. control1, control2, are mapping to the control -/// points of the Bézier curve in the spec. -/// -/// https://www.w3.org/TR/SVG11/paths.html#PathData -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(C, u8)] -pub enum PathCommand { - /// The unknown type. - /// https://www.w3.org/TR/SVG/paths.html#__svg__SVGPathSeg__PATHSEG_UNKNOWN - Unknown, - /// The "moveto" command. - MoveTo { - point: CoordPair, - absolute: IsAbsolute, - }, - /// The "lineto" command. - LineTo { - point: CoordPair, - absolute: IsAbsolute, - }, - /// The horizontal "lineto" command. - HorizontalLineTo { x: CSSFloat, absolute: IsAbsolute }, - /// The vertical "lineto" command. - VerticalLineTo { y: CSSFloat, absolute: IsAbsolute }, - /// The cubic Bézier curve command. - CurveTo { - control1: CoordPair, - control2: CoordPair, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The smooth curve command. - SmoothCurveTo { - control2: CoordPair, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The quadratic Bézier curve command. - QuadBezierCurveTo { - control1: CoordPair, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The smooth quadratic Bézier curve command. - SmoothQuadBezierCurveTo { - point: CoordPair, - absolute: IsAbsolute, - }, - /// The elliptical arc curve command. - EllipticalArc { - rx: CSSFloat, - ry: CSSFloat, - angle: CSSFloat, - large_arc_flag: ArcFlag, - sweep_flag: ArcFlag, - point: CoordPair, - absolute: IsAbsolute, - }, - /// The "closepath" command. - ClosePath, -} - -/// For internal SVGPath normalization. -#[allow(missing_docs)] -struct PathTraversalState { - subpath_start: CoordPair, - pos: CoordPair, -} - -impl PathCommand { - /// Create a normalized copy of this PathCommand. Absolute commands will be copied as-is while - /// for relative commands an equivalent absolute command will be returned. - /// - /// See discussion: https://github.com/w3c/svgwg/issues/321 - fn normalize(&self, state: &mut PathTraversalState) -> Self { - use self::PathCommand::*; - match *self { - Unknown => Unknown, - ClosePath => { - state.pos = state.subpath_start; - ClosePath - }, - MoveTo { - mut point, - absolute, - } => { - if !absolute.is_yes() { - point += state.pos; - } - state.pos = point; - state.subpath_start = point; - MoveTo { - point, - absolute: IsAbsolute::Yes, - } - }, - LineTo { - mut point, - absolute, - } => { - if !absolute.is_yes() { - point += state.pos; - } - state.pos = point; - LineTo { - point, - absolute: IsAbsolute::Yes, - } - }, - HorizontalLineTo { mut x, absolute } => { - if !absolute.is_yes() { - x += state.pos.x; - } - state.pos.x = x; - HorizontalLineTo { - x, - absolute: IsAbsolute::Yes, - } - }, - VerticalLineTo { mut y, absolute } => { - if !absolute.is_yes() { - y += state.pos.y; - } - state.pos.y = y; - VerticalLineTo { - y, - absolute: IsAbsolute::Yes, - } - }, - CurveTo { - mut control1, - mut control2, - mut point, - absolute, - } => { - if !absolute.is_yes() { - control1 += state.pos; - control2 += state.pos; - point += state.pos; - } - state.pos = point; - CurveTo { - control1, - control2, - point, - absolute: IsAbsolute::Yes, - } - }, - SmoothCurveTo { - mut control2, - mut point, - absolute, - } => { - if !absolute.is_yes() { - control2 += state.pos; - point += state.pos; - } - state.pos = point; - SmoothCurveTo { - control2, - point, - absolute: IsAbsolute::Yes, - } - }, - QuadBezierCurveTo { - mut control1, - mut point, - absolute, - } => { - if !absolute.is_yes() { - control1 += state.pos; - point += state.pos; - } - state.pos = point; - QuadBezierCurveTo { - control1, - point, - absolute: IsAbsolute::Yes, - } - }, - SmoothQuadBezierCurveTo { - mut point, - absolute, - } => { - if !absolute.is_yes() { - point += state.pos; - } - state.pos = point; - SmoothQuadBezierCurveTo { - point, - absolute: IsAbsolute::Yes, - } - }, - EllipticalArc { - rx, - ry, - angle, - large_arc_flag, - sweep_flag, - mut point, - absolute, - } => { - if !absolute.is_yes() { - point += state.pos; - } - state.pos = point; - EllipticalArc { - rx, - ry, - angle, - large_arc_flag, - sweep_flag, - point, - absolute: IsAbsolute::Yes, - } - }, - } - } -} - -impl ToCss for PathCommand { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - use self::PathCommand::*; - match *self { - Unknown => dest.write_char('X'), - ClosePath => dest.write_char('Z'), - MoveTo { point, absolute } => { - dest.write_char(if absolute.is_yes() { 'M' } else { 'm' })?; - dest.write_char(' ')?; - point.to_css(dest) - }, - LineTo { point, absolute } => { - dest.write_char(if absolute.is_yes() { 'L' } else { 'l' })?; - dest.write_char(' ')?; - point.to_css(dest) - }, - CurveTo { - control1, - control2, - point, - absolute, - } => { - dest.write_char(if absolute.is_yes() { 'C' } else { 'c' })?; - dest.write_char(' ')?; - control1.to_css(dest)?; - dest.write_char(' ')?; - control2.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - QuadBezierCurveTo { - control1, - point, - absolute, - } => { - dest.write_char(if absolute.is_yes() { 'Q' } else { 'q' })?; - dest.write_char(' ')?; - control1.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - EllipticalArc { - rx, - ry, - angle, - large_arc_flag, - sweep_flag, - point, - absolute, - } => { - dest.write_char(if absolute.is_yes() { 'A' } else { 'a' })?; - dest.write_char(' ')?; - rx.to_css(dest)?; - dest.write_char(' ')?; - ry.to_css(dest)?; - dest.write_char(' ')?; - angle.to_css(dest)?; - dest.write_char(' ')?; - large_arc_flag.to_css(dest)?; - dest.write_char(' ')?; - sweep_flag.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - HorizontalLineTo { x, absolute } => { - dest.write_char(if absolute.is_yes() { 'H' } else { 'h' })?; - dest.write_char(' ')?; - x.to_css(dest) - }, - VerticalLineTo { y, absolute } => { - dest.write_char(if absolute.is_yes() { 'V' } else { 'v' })?; - dest.write_char(' ')?; - y.to_css(dest) - }, - SmoothCurveTo { - control2, - point, - absolute, - } => { - dest.write_char(if absolute.is_yes() { 'S' } else { 's' })?; - dest.write_char(' ')?; - control2.to_css(dest)?; - dest.write_char(' ')?; - point.to_css(dest) - }, - SmoothQuadBezierCurveTo { point, absolute } => { - dest.write_char(if absolute.is_yes() { 'T' } else { 't' })?; - dest.write_char(' ')?; - point.to_css(dest) - }, - } - } -} - -/// The path command absolute type. -#[allow(missing_docs)] -#[derive( - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum IsAbsolute { - Yes, - No, -} - -impl IsAbsolute { - /// Return true if this is IsAbsolute::Yes. - #[inline] - pub fn is_yes(&self) -> bool { - *self == IsAbsolute::Yes - } - - /// Return Yes if value is true. Otherwise, return No. - #[inline] - #[cfg(feature = "gecko")] - fn new(value: bool) -> Self { - if value { - IsAbsolute::Yes - } else { - IsAbsolute::No - } - } -} - -/// The path coord type. -#[allow(missing_docs)] -#[derive( - AddAssign, - Animate, - Clone, - ComputeSquaredDistance, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct CoordPair { - x: CSSFloat, - y: CSSFloat, -} - -impl CoordPair { - /// Create a CoordPair. - #[inline] - pub fn new(x: CSSFloat, y: CSSFloat) -> Self { - CoordPair { x, y } - } -} - -/// The EllipticalArc flag type. -#[derive( - Clone, - Copy, - Debug, - Deserialize, - MallocSizeOf, - PartialEq, - Serialize, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -pub struct ArcFlag(bool); - -impl ToCss for ArcFlag { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: fmt::Write, - { - (self.0 as i32).to_css(dest) - } -} - -impl Animate for ArcFlag { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - (self.0 as i32) - .animate(&(other.0 as i32), procedure) - .map(|v| ArcFlag(v > 0)) - } -} - -impl ComputeSquaredDistance for ArcFlag { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - (self.0 as i32).compute_squared_distance(&(other.0 as i32)) - } -} - -impl ToAnimatedZero for ArcFlag { - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - // The 2 ArcFlags in EllipticalArc determine which one of the 4 different arcs will be - // used. (i.e. From 4 combinations). In other words, if we change the flag, we get a - // different arc. Therefore, we return *self. - // https://svgwg.org/svg2-draft/paths.html#PathDataEllipticalArcCommands - Ok(*self) - } -} - -/// SVG Path parser. -struct PathParser<'a> { - chars: Peekable<Cloned<slice::Iter<'a, u8>>>, - path: Vec<PathCommand>, -} - -macro_rules! parse_arguments { - ( - $parser:ident, - $abs:ident, - $enum:ident, - [ $para:ident => $func:ident $(, $other_para:ident => $other_func:ident)* ] - ) => { - { - loop { - let $para = $func(&mut $parser.chars)?; - $( - skip_comma_wsp(&mut $parser.chars); - let $other_para = $other_func(&mut $parser.chars)?; - )* - $parser.path.push(PathCommand::$enum { $para $(, $other_para)*, $abs }); - - // End of string or the next character is a possible new command. - if !skip_wsp(&mut $parser.chars) || - $parser.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) { - break; - } - skip_comma_wsp(&mut $parser.chars); - } - Ok(()) - } - } -} - -impl<'a> PathParser<'a> { - /// Return a PathParser. - #[inline] - fn new(string: &'a str) -> Self { - PathParser { - chars: string.as_bytes().iter().cloned().peekable(), - path: Vec::new(), - } - } - - /// Parse a sub-path. - fn parse_subpath(&mut self) -> Result<(), ()> { - // Handle "moveto" Command first. If there is no "moveto", this is not a valid sub-path - // (i.e. not a valid moveto-drawto-command-group). - self.parse_moveto()?; - - // Handle other commands. - loop { - skip_wsp(&mut self.chars); - if self.chars.peek().map_or(true, |&m| m == b'M' || m == b'm') { - break; - } - - let command = self.chars.next().unwrap(); - let abs = if command.is_ascii_uppercase() { - IsAbsolute::Yes - } else { - IsAbsolute::No - }; - - skip_wsp(&mut self.chars); - match command { - b'Z' | b'z' => self.parse_closepath(), - b'L' | b'l' => self.parse_lineto(abs), - b'H' | b'h' => self.parse_h_lineto(abs), - b'V' | b'v' => self.parse_v_lineto(abs), - b'C' | b'c' => self.parse_curveto(abs), - b'S' | b's' => self.parse_smooth_curveto(abs), - b'Q' | b'q' => self.parse_quadratic_bezier_curveto(abs), - b'T' | b't' => self.parse_smooth_quadratic_bezier_curveto(abs), - b'A' | b'a' => self.parse_elliptical_arc(abs), - _ => return Err(()), - }?; - } - Ok(()) - } - - /// Parse "moveto" command. - fn parse_moveto(&mut self) -> Result<(), ()> { - let command = match self.chars.next() { - Some(c) if c == b'M' || c == b'm' => c, - _ => return Err(()), - }; - - skip_wsp(&mut self.chars); - let point = parse_coord(&mut self.chars)?; - let absolute = if command == b'M' { - IsAbsolute::Yes - } else { - IsAbsolute::No - }; - self.path.push(PathCommand::MoveTo { point, absolute }); - - // End of string or the next character is a possible new command. - if !skip_wsp(&mut self.chars) || self.chars.peek().map_or(true, |c| c.is_ascii_alphabetic()) - { - return Ok(()); - } - skip_comma_wsp(&mut self.chars); - - // If a moveto is followed by multiple pairs of coordinates, the subsequent - // pairs are treated as implicit lineto commands. - self.parse_lineto(absolute) - } - - /// Parse "closepath" command. - fn parse_closepath(&mut self) -> Result<(), ()> { - self.path.push(PathCommand::ClosePath); - Ok(()) - } - - /// Parse "lineto" command. - fn parse_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, LineTo, [ point => parse_coord ]) - } - - /// Parse horizontal "lineto" command. - fn parse_h_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, HorizontalLineTo, [ x => parse_number ]) - } - - /// Parse vertical "lineto" command. - fn parse_v_lineto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, VerticalLineTo, [ y => parse_number ]) - } - - /// Parse cubic Bézier curve command. - fn parse_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, CurveTo, [ - control1 => parse_coord, control2 => parse_coord, point => parse_coord - ]) - } - - /// Parse smooth "curveto" command. - fn parse_smooth_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, SmoothCurveTo, [ - control2 => parse_coord, point => parse_coord - ]) - } - - /// Parse quadratic Bézier curve command. - fn parse_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, QuadBezierCurveTo, [ - control1 => parse_coord, point => parse_coord - ]) - } - - /// Parse smooth quadratic Bézier curveto command. - fn parse_smooth_quadratic_bezier_curveto(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - parse_arguments!(self, absolute, SmoothQuadBezierCurveTo, [ point => parse_coord ]) - } - - /// Parse elliptical arc curve command. - fn parse_elliptical_arc(&mut self, absolute: IsAbsolute) -> Result<(), ()> { - // Parse a flag whose value is '0' or '1'; otherwise, return Err(()). - let parse_flag = |iter: &mut Peekable<Cloned<slice::Iter<u8>>>| match iter.next() { - Some(c) if c == b'0' || c == b'1' => Ok(ArcFlag(c == b'1')), - _ => Err(()), - }; - parse_arguments!(self, absolute, EllipticalArc, [ - rx => parse_number, - ry => parse_number, - angle => parse_number, - large_arc_flag => parse_flag, - sweep_flag => parse_flag, - point => parse_coord - ]) - } -} - -/// Parse a pair of numbers into CoordPair. -fn parse_coord(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CoordPair, ()> { - let x = parse_number(iter)?; - skip_comma_wsp(iter); - let y = parse_number(iter)?; - Ok(CoordPair::new(x, y)) -} - -/// This is a special version which parses the number for SVG Path. e.g. "M 0.6.5" should be parsed -/// as MoveTo with a coordinate of ("0.6", ".5"), instead of treating 0.6.5 as a non-valid floating -/// point number. In other words, the logic here is similar with that of -/// tokenizer::consume_numeric, which also consumes the number as many as possible, but here the -/// input is a Peekable and we only accept an integer of a floating point number. -/// -/// The "number" syntax in https://www.w3.org/TR/SVG/paths.html#PathDataBNF -fn parse_number(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> Result<CSSFloat, ()> { - // 1. Check optional sign. - let sign = if iter - .peek() - .map_or(false, |&sign| sign == b'+' || sign == b'-') - { - if iter.next().unwrap() == b'-' { - -1. - } else { - 1. - } - } else { - 1. - }; - - // 2. Check integer part. - let mut integral_part: f64 = 0.; - let got_dot = if !iter.peek().map_or(false, |&n| n == b'.') { - // If the first digit in integer part is neither a dot nor a digit, this is not a number. - if iter.peek().map_or(true, |n| !n.is_ascii_digit()) { - return Err(()); - } - - while iter.peek().map_or(false, |n| n.is_ascii_digit()) { - integral_part = integral_part * 10. + (iter.next().unwrap() - b'0') as f64; - } - - iter.peek().map_or(false, |&n| n == b'.') - } else { - true - }; - - // 3. Check fractional part. - let mut fractional_part: f64 = 0.; - if got_dot { - // Consume '.'. - iter.next(); - // If the first digit in fractional part is not a digit, this is not a number. - if iter.peek().map_or(true, |n| !n.is_ascii_digit()) { - return Err(()); - } - - let mut factor = 0.1; - while iter.peek().map_or(false, |n| n.is_ascii_digit()) { - fractional_part += (iter.next().unwrap() - b'0') as f64 * factor; - factor *= 0.1; - } - } - - let mut value = sign * (integral_part + fractional_part); - - // 4. Check exp part. The segment name of SVG Path doesn't include 'E' or 'e', so it's ok to - // treat the numbers after 'E' or 'e' are in the exponential part. - if iter.peek().map_or(false, |&exp| exp == b'E' || exp == b'e') { - // Consume 'E' or 'e'. - iter.next(); - let exp_sign = if iter - .peek() - .map_or(false, |&sign| sign == b'+' || sign == b'-') - { - if iter.next().unwrap() == b'-' { - -1. - } else { - 1. - } - } else { - 1. - }; - - let mut exp: f64 = 0.; - while iter.peek().map_or(false, |n| n.is_ascii_digit()) { - exp = exp * 10. + (iter.next().unwrap() - b'0') as f64; - } - - value *= f64::powf(10., exp * exp_sign); - } - - if value.is_finite() { - Ok(value.min(f32::MAX as f64).max(f32::MIN as f64) as CSSFloat) - } else { - Err(()) - } -} - -/// Skip all svg whitespaces, and return true if |iter| hasn't finished. -#[inline] -fn skip_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool { - // Note: SVG 1.1 defines the whitespaces as \u{9}, \u{20}, \u{A}, \u{D}. - // However, SVG 2 has one extra whitespace: \u{C}. - // Therefore, we follow the newest spec for the definition of whitespace, - // i.e. \u{9}, \u{20}, \u{A}, \u{C}, \u{D}. - while iter.peek().map_or(false, |c| c.is_ascii_whitespace()) { - iter.next(); - } - iter.peek().is_some() -} - -/// Skip all svg whitespaces and one comma, and return true if |iter| hasn't finished. -#[inline] -fn skip_comma_wsp(iter: &mut Peekable<Cloned<slice::Iter<u8>>>) -> bool { - if !skip_wsp(iter) { - return false; - } - - if *iter.peek().unwrap() != b',' { - return true; - } - iter.next(); - - skip_wsp(iter) -} diff --git a/components/style/values/specified/table.rs b/components/style/values/specified/table.rs deleted file mode 100644 index 88f917ac78d..00000000000 --- a/components/style/values/specified/table.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values related to tables. - -/// Specified values for the `caption-side` property. -/// -/// Note that despite having "physical" names, these are actually interpreted -/// according to the table's writing-mode: Top and Bottom are treated as -/// block-start and -end respectively. -/// -/// https://drafts.csswg.org/css-tables/#propdef-caption-side -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - MallocSizeOf, - Ord, - Parse, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum CaptionSide { - Top, - Bottom, -} diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs deleted file mode 100644 index 8545a4fc01a..00000000000 --- a/components/style/values/specified/text.rs +++ /dev/null @@ -1,1154 +0,0 @@ -/* 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/. */ - -//! Specified types for text properties. - -use crate::parser::{Parse, ParserContext}; -use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode; -use crate::values::computed::text::LineHeight as ComputedLineHeight; -use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle; -use crate::values::computed::text::TextOverflow as ComputedTextOverflow; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::generics::text::InitialLetter as GenericInitialLetter; -use crate::values::generics::text::LineHeight as GenericLineHeight; -use crate::values::generics::text::{GenericTextDecorationLength, Spacing}; -use crate::values::specified::length::NonNegativeLengthPercentage; -use crate::values::specified::length::{FontRelativeLength, Length}; -use crate::values::specified::length::{LengthPercentage, NoCalcLength}; -use crate::values::specified::{AllowQuirks, Integer, NonNegativeNumber, Number}; -use cssparser::{Parser, Token}; -use selectors::parser::SelectorParseErrorKind; -use std::fmt::{self, Write}; -use style_traits::values::SequenceWriter; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; -use style_traits::{KeywordsCollectFn, SpecifiedValueInfo}; -use unicode_segmentation::UnicodeSegmentation; - -/// A specified type for the `initial-letter` property. -pub type InitialLetter = GenericInitialLetter<Number, Integer>; - -/// A specified value for the `letter-spacing` property. -pub type LetterSpacing = Spacing<Length>; - -/// A specified value for the `word-spacing` property. -pub type WordSpacing = Spacing<LengthPercentage>; - -/// A specified value for the `line-height` property. -pub type LineHeight = GenericLineHeight<NonNegativeNumber, NonNegativeLengthPercentage>; - -/// A value for the `hyphenate-character` property. -#[derive( - Clone, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum HyphenateCharacter { - /// `auto` - Auto, - /// `<string>` - String(crate::OwnedStr), -} - -impl Parse for InitialLetter { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|i| i.expect_ident_matching("normal")) - .is_ok() - { - return Ok(GenericInitialLetter::Normal); - } - let size = Number::parse_at_least_one(context, input)?; - let sink = input - .try_parse(|i| Integer::parse_positive(context, i)) - .ok(); - Ok(GenericInitialLetter::Specified(size, sink)) - } -} - -impl Parse for LetterSpacing { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Spacing::parse_with(context, input, |c, i| { - Length::parse_quirky(c, i, AllowQuirks::Yes) - }) - } -} - -impl Parse for WordSpacing { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Spacing::parse_with(context, input, |c, i| { - LengthPercentage::parse_quirky(c, i, AllowQuirks::Yes) - }) - } -} - -impl ToComputedValue for LineHeight { - type ComputedValue = ComputedLineHeight; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - use crate::values::specified::length::FontBaseSize; - match *self { - GenericLineHeight::Normal => GenericLineHeight::Normal, - #[cfg(feature = "gecko")] - GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight, - GenericLineHeight::Number(number) => { - GenericLineHeight::Number(number.to_computed_value(context)) - }, - GenericLineHeight::Length(ref non_negative_lp) => { - let result = match non_negative_lp.0 { - LengthPercentage::Length(NoCalcLength::Absolute(ref abs)) => { - context.maybe_zoom_text(abs.to_computed_value(context)) - }, - LengthPercentage::Length(ref length) => length.to_computed_value(context), - LengthPercentage::Percentage(ref p) => FontRelativeLength::Em(p.0) - .to_computed_value(context, FontBaseSize::CurrentStyle), - LengthPercentage::Calc(ref calc) => { - let computed_calc = - calc.to_computed_value_zoomed(context, FontBaseSize::CurrentStyle); - let base = context.style().get_font().clone_font_size().computed_size(); - computed_calc.resolve(base) - }, - }; - GenericLineHeight::Length(result.into()) - }, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - GenericLineHeight::Normal => GenericLineHeight::Normal, - #[cfg(feature = "gecko")] - GenericLineHeight::MozBlockHeight => GenericLineHeight::MozBlockHeight, - GenericLineHeight::Number(ref number) => { - GenericLineHeight::Number(NonNegativeNumber::from_computed_value(number)) - }, - GenericLineHeight::Length(ref length) => { - GenericLineHeight::Length(NoCalcLength::from_computed_value(&length.0).into()) - }, - } - } -} - -/// A generic value for the `text-overflow` property. -#[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - Parse, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C, u8)] -pub enum TextOverflowSide { - /// Clip inline content. - Clip, - /// Render ellipsis to represent clipped inline content. - Ellipsis, - /// Render a given string to represent clipped inline content. - String(crate::OwnedStr), -} - -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -/// text-overflow. Specifies rendering when inline content overflows its line box edge. -pub struct TextOverflow { - /// First value. Applies to end line box edge if no second is supplied; line-left edge otherwise. - pub first: TextOverflowSide, - /// Second value. Applies to the line-right edge if supplied. - pub second: Option<TextOverflowSide>, -} - -impl Parse for TextOverflow { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<TextOverflow, ParseError<'i>> { - let first = TextOverflowSide::parse(context, input)?; - let second = input - .try_parse(|input| TextOverflowSide::parse(context, input)) - .ok(); - Ok(TextOverflow { first, second }) - } -} - -impl ToComputedValue for TextOverflow { - type ComputedValue = ComputedTextOverflow; - - #[inline] - fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { - if let Some(ref second) = self.second { - Self::ComputedValue { - first: self.first.clone(), - second: second.clone(), - sides_are_logical: false, - } - } else { - Self::ComputedValue { - first: TextOverflowSide::Clip, - second: self.first.clone(), - sides_are_logical: true, - } - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - if computed.sides_are_logical { - assert_eq!(computed.first, TextOverflowSide::Clip); - TextOverflow { - first: computed.second.clone(), - second: None, - } - } else { - TextOverflow { - first: computed.first.clone(), - second: Some(computed.second.clone()), - } - } - } -} - -bitflags! { - #[derive(MallocSizeOf, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] - #[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))] - #[repr(C)] - /// Specified keyword values for the text-decoration-line property. - pub struct TextDecorationLine: u8 { - /// No text decoration line is specified. - const NONE = 0; - /// underline - const UNDERLINE = 1 << 0; - /// overline - const OVERLINE = 1 << 1; - /// line-through - const LINE_THROUGH = 1 << 2; - /// blink - const BLINK = 1 << 3; - /// Only set by presentation attributes - /// - /// Setting this will mean that text-decorations use the color - /// specified by `color` in quirks mode. - /// - /// For example, this gives <a href=foo><font color="red">text</font></a> - /// a red text decoration - #[cfg(feature = "gecko")] - const COLOR_OVERRIDE = 0x10; - } -} - -impl Default for TextDecorationLine { - fn default() -> Self { - TextDecorationLine::NONE - } -} - -impl TextDecorationLine { - #[inline] - /// Returns the initial value of text-decoration-line - pub fn none() -> Self { - TextDecorationLine::NONE - } -} - -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// Specified value of the text-transform property, stored in two parts: -/// the case-related transforms (mutually exclusive, only one may be in effect), and others (non-exclusive). -pub struct TextTransform { - /// Case transform, if any. - pub case_: TextTransformCase, - /// Non-case transforms. - pub other_: TextTransformOther, -} - -impl TextTransform { - #[inline] - /// Returns the initial value of text-transform - pub fn none() -> Self { - TextTransform { - case_: TextTransformCase::None, - other_: TextTransformOther::empty(), - } - } - #[inline] - /// Returns whether the value is 'none' - pub fn is_none(&self) -> bool { - self.case_ == TextTransformCase::None && self.other_.is_empty() - } -} - -// TODO: This can be simplified by deriving it. -impl Parse for TextTransform { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut result = TextTransform::none(); - - // Case keywords are mutually exclusive; other transforms may co-occur. - loop { - let location = input.current_source_location(); - let ident = match input.next() { - Ok(&Token::Ident(ref ident)) => ident, - Ok(other) => return Err(location.new_unexpected_token_error(other.clone())), - Err(..) => break, - }; - - match_ignore_ascii_case! { ident, - "none" if result.is_none() => { - return Ok(result); - }, - "uppercase" if result.case_ == TextTransformCase::None => { - result.case_ = TextTransformCase::Uppercase - }, - "lowercase" if result.case_ == TextTransformCase::None => { - result.case_ = TextTransformCase::Lowercase - }, - "capitalize" if result.case_ == TextTransformCase::None => { - result.case_ = TextTransformCase::Capitalize - }, - "full-width" if !result.other_.intersects(TextTransformOther::FULL_WIDTH) => { - result.other_.insert(TextTransformOther::FULL_WIDTH) - }, - "full-size-kana" if !result.other_.intersects(TextTransformOther::FULL_SIZE_KANA) => { - result.other_.insert(TextTransformOther::FULL_SIZE_KANA) - }, - _ => return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent(ident.clone()) - )), - } - } - - if result.is_none() { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } else { - Ok(result) - } - } -} - -impl ToCss for TextTransform { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_none() { - return dest.write_str("none"); - } - - if self.case_ != TextTransformCase::None { - self.case_.to_css(dest)?; - if !self.other_.is_empty() { - dest.write_char(' ')?; - } - } - - self.other_.to_css(dest) - } -} - -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(C)] -/// Specified keyword values for case transforms in the text-transform property. (These are exclusive.) -pub enum TextTransformCase { - /// No case transform. - None, - /// All uppercase. - Uppercase, - /// All lowercase. - Lowercase, - /// Capitalize each word. - Capitalize, -} - -bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "none,full-width,full-size-kana")] - #[repr(C)] - /// Specified keyword values for non-case transforms in the text-transform property. (Non-exclusive.) - pub struct TextTransformOther: u8 { - /// full-width - const FULL_WIDTH = 1 << 0; - /// full-size-kana - const FULL_SIZE_KANA = 1 << 1; - } -} - -impl ToCss for TextTransformOther { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let mut writer = SequenceWriter::new(dest, " "); - let mut any = false; - macro_rules! maybe_write { - ($ident:ident => $str:expr) => { - if self.contains(TextTransformOther::$ident) { - writer.raw_item($str)?; - any = true; - } - }; - } - - maybe_write!(FULL_WIDTH => "full-width"); - maybe_write!(FULL_SIZE_KANA => "full-size-kana"); - - debug_assert!(any || self.is_empty()); - - Ok(()) - } -} - -/// Specified and computed value of text-align-last. -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum TextAlignLast { - Auto, - Start, - End, - Left, - Right, - Center, - Justify, -} - -/// Specified value of text-align keyword value. -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum TextAlignKeyword { - Start, - Left, - Right, - Center, - Justify, - #[css(skip)] - #[cfg(feature = "gecko")] - Char, - End, - #[cfg(feature = "gecko")] - MozCenter, - #[cfg(feature = "gecko")] - MozLeft, - #[cfg(feature = "gecko")] - MozRight, - #[cfg(feature = "servo")] - ServoCenter, - #[cfg(feature = "servo")] - ServoLeft, - #[cfg(feature = "servo")] - ServoRight, -} - -/// Specified value of text-align property. -#[derive( - Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] -pub enum TextAlign { - /// Keyword value of text-align property. - Keyword(TextAlignKeyword), - /// `match-parent` value of text-align property. It has a different handling - /// unlike other keywords. - #[cfg(feature = "gecko")] - MatchParent, - /// This is how we implement the following HTML behavior from - /// https://html.spec.whatwg.org/#tables-2: - /// - /// User agents are expected to have a rule in their user agent style sheet - /// that matches th elements that have a parent node whose computed value - /// for the 'text-align' property is its initial value, whose declaration - /// block consists of just a single declaration that sets the 'text-align' - /// property to the value 'center'. - /// - /// Since selectors can't depend on the ancestor styles, we implement it with a - /// magic value that computes to the right thing. Since this is an - /// implementation detail, it shouldn't be exposed to web content. - #[cfg(feature = "gecko")] - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozCenterOrInherit, -} - -impl ToComputedValue for TextAlign { - type ComputedValue = TextAlignKeyword; - - #[inline] - fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { - match *self { - TextAlign::Keyword(key) => key, - #[cfg(feature = "gecko")] - TextAlign::MatchParent => { - // on the root <html> element we should still respect the dir - // but the parent dir of that element is LTR even if it's <html dir=rtl> - // and will only be RTL if certain prefs have been set. - // In that case, the default behavior here will set it to left, - // but we want to set it to right -- instead set it to the default (`start`), - // which will do the right thing in this case (but not the general case) - if _context.builder.is_root_element { - return TextAlignKeyword::Start; - } - let parent = _context - .builder - .get_parent_inherited_text() - .clone_text_align(); - let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr(); - match (parent, ltr) { - (TextAlignKeyword::Start, true) => TextAlignKeyword::Left, - (TextAlignKeyword::Start, false) => TextAlignKeyword::Right, - (TextAlignKeyword::End, true) => TextAlignKeyword::Right, - (TextAlignKeyword::End, false) => TextAlignKeyword::Left, - _ => parent, - } - }, - #[cfg(feature = "gecko")] - TextAlign::MozCenterOrInherit => { - let parent = _context - .builder - .get_parent_inherited_text() - .clone_text_align(); - if parent == TextAlignKeyword::Start { - TextAlignKeyword::Center - } else { - parent - } - }, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - TextAlign::Keyword(*computed) - } -} - -fn fill_mode_is_default_and_shape_exists( - fill: &TextEmphasisFillMode, - shape: &Option<TextEmphasisShapeKeyword>, -) -> bool { - shape.is_some() && fill.is_filled() -} - -/// Specified value of text-emphasis-style property. -/// -/// https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -#[allow(missing_docs)] -pub enum TextEmphasisStyle { - /// [ <fill> || <shape> ] - Keyword { - #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")] - fill: TextEmphasisFillMode, - shape: Option<TextEmphasisShapeKeyword>, - }, - /// `none` - None, - /// `<string>` (of which only the first grapheme cluster will be used). - String(crate::OwnedStr), -} - -/// Fill mode for the text-emphasis-style property -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum TextEmphasisFillMode { - /// `filled` - Filled, - /// `open` - Open, -} - -impl TextEmphasisFillMode { - /// Whether the value is `filled`. - #[inline] - pub fn is_filled(&self) -> bool { - matches!(*self, TextEmphasisFillMode::Filled) - } -} - -/// Shape keyword for the text-emphasis-style property -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum TextEmphasisShapeKeyword { - /// `dot` - Dot, - /// `circle` - Circle, - /// `double-circle` - DoubleCircle, - /// `triangle` - Triangle, - /// `sesame` - Sesame, -} - -impl ToComputedValue for TextEmphasisStyle { - type ComputedValue = ComputedTextEmphasisStyle; - - #[inline] - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - TextEmphasisStyle::Keyword { fill, shape } => { - let shape = shape.unwrap_or_else(|| { - // FIXME(emilio, bug 1572958): This should set the - // rule_cache_conditions properly. - // - // Also should probably use WritingMode::is_vertical rather - // than the computed value of the `writing-mode` property. - if context.style().get_inherited_box().clone_writing_mode() == - SpecifiedWritingMode::HorizontalTb - { - TextEmphasisShapeKeyword::Circle - } else { - TextEmphasisShapeKeyword::Sesame - } - }); - ComputedTextEmphasisStyle::Keyword { fill, shape } - }, - TextEmphasisStyle::None => ComputedTextEmphasisStyle::None, - TextEmphasisStyle::String(ref s) => { - // Passing `true` to iterate over extended grapheme clusters, following - // recommendation at http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries - // - // FIXME(emilio): Doing this at computed value time seems wrong. - // The spec doesn't say that this should be a computed-value - // time operation. This is observable from getComputedStyle(). - let s = s.graphemes(true).next().unwrap_or("").to_string(); - ComputedTextEmphasisStyle::String(s.into()) - }, - } - } - - #[inline] - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - match *computed { - ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword { - fill, - shape: Some(shape), - }, - ComputedTextEmphasisStyle::None => TextEmphasisStyle::None, - ComputedTextEmphasisStyle::String(ref string) => { - TextEmphasisStyle::String(string.clone()) - }, - } - } -} - -impl Parse for TextEmphasisStyle { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(TextEmphasisStyle::None); - } - - if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) { - // Handle <string> - return Ok(TextEmphasisStyle::String(s.into())); - } - - // Handle a pair of keywords - let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok(); - let fill = input.try_parse(TextEmphasisFillMode::parse).ok(); - if shape.is_none() { - shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok(); - } - - if shape.is_none() && fill.is_none() { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - // If a shape keyword is specified but neither filled nor open is - // specified, filled is assumed. - let fill = fill.unwrap_or(TextEmphasisFillMode::Filled); - - // We cannot do the same because the default `<shape>` depends on the - // computed writing-mode. - Ok(TextEmphasisStyle::Keyword { fill, shape }) - } -} - -bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, Parse, ToCss)] - #[repr(C)] - #[css(bitflags(mixed="over,under,left,right", validate_mixed="Self::validate_and_simplify"))] - /// Values for text-emphasis-position: - /// <https://drafts.csswg.org/css-text-decor/#text-emphasis-position-property> - pub struct TextEmphasisPosition: u8 { - /// Draws marks to the right of the text in vertical writing mode. - const OVER = 1 << 0; - /// Draw marks under the text in horizontal writing mode. - const UNDER = 1 << 1; - /// Draw marks to the left of the text in vertical writing mode. - const LEFT = 1 << 2; - /// Draws marks to the right of the text in vertical writing mode. - const RIGHT = 1 << 3; - } -} - -impl TextEmphasisPosition { - fn validate_and_simplify(&mut self) -> bool { - if self.intersects(Self::OVER) == self.intersects(Self::UNDER) { - return false; - } - - if self.intersects(Self::LEFT) { - return !self.intersects(Self::RIGHT); - } - - self.remove(Self::RIGHT); // Right is the default - true - } -} - -/// Values for the `word-break` property. -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum WordBreak { - Normal, - BreakAll, - KeepAll, - /// The break-word value, needed for compat. - /// - /// Specifying `word-break: break-word` makes `overflow-wrap` behave as - /// `anywhere`, and `word-break` behave like `normal`. - #[cfg(feature = "gecko")] - BreakWord, -} - -/// Values for the `text-justify` CSS property. -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum TextJustify { - Auto, - None, - InterWord, - // See https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute - // and https://github.com/w3c/csswg-drafts/issues/6156 for the alias. - #[parse(aliases = "distribute")] - InterCharacter, -} - -/// Values for the `-moz-control-character-visibility` CSS property. -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum MozControlCharacterVisibility { - Hidden, - Visible, -} - -#[cfg(feature = "gecko")] -impl Default for MozControlCharacterVisibility { - fn default() -> Self { - if static_prefs::pref!("layout.css.control-characters.visible") { - Self::Visible - } else { - Self::Hidden - } - } -} - -/// Values for the `line-break` property. -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum LineBreak { - Auto, - Loose, - Normal, - Strict, - Anywhere, -} - -/// Values for the `overflow-wrap` property. -#[repr(u8)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum OverflowWrap { - Normal, - BreakWord, - Anywhere, -} - -/// Implements text-decoration-skip-ink which takes the keywords auto | none | all -/// -/// https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property -#[repr(u8)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[allow(missing_docs)] -pub enum TextDecorationSkipInk { - Auto, - None, - All, -} - -/// Implements type for `text-decoration-thickness` property -pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>; - -impl TextDecorationLength { - /// `Auto` value. - #[inline] - pub fn auto() -> Self { - GenericTextDecorationLength::Auto - } - - /// Whether this is the `Auto` value. - #[inline] - pub fn is_auto(&self) -> bool { - matches!(*self, GenericTextDecorationLength::Auto) - } -} - -bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "auto,from-font,under,left,right")] - #[repr(C)] - /// Specified keyword values for the text-underline-position property. - /// (Non-exclusive, but not all combinations are allowed: the spec grammar gives - /// `auto | [ from-font | under ] || [ left | right ]`.) - /// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property - pub struct TextUnderlinePosition: u8 { - /// Use automatic positioning below the alphabetic baseline. - const AUTO = 0; - /// Use underline position from the first available font. - const FROM_FONT = 1 << 0; - /// Below the glyph box. - const UNDER = 1 << 1; - /// In vertical mode, place to the left of the text. - const LEFT = 1 << 2; - /// In vertical mode, place to the right of the text. - const RIGHT = 1 << 3; - } -} - -// TODO: This can be derived with some care. -impl Parse for TextUnderlinePosition { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<TextUnderlinePosition, ParseError<'i>> { - let mut result = TextUnderlinePosition::empty(); - - loop { - let location = input.current_source_location(); - let ident = match input.next() { - Ok(&Token::Ident(ref ident)) => ident, - Ok(other) => return Err(location.new_unexpected_token_error(other.clone())), - Err(..) => break, - }; - - match_ignore_ascii_case! { ident, - "auto" if result.is_empty() => { - return Ok(result); - }, - "from-font" if !result.intersects(TextUnderlinePosition::FROM_FONT | - TextUnderlinePosition::UNDER) => { - result.insert(TextUnderlinePosition::FROM_FONT); - }, - "under" if !result.intersects(TextUnderlinePosition::FROM_FONT | - TextUnderlinePosition::UNDER) => { - result.insert(TextUnderlinePosition::UNDER); - }, - "left" if !result.intersects(TextUnderlinePosition::LEFT | - TextUnderlinePosition::RIGHT) => { - result.insert(TextUnderlinePosition::LEFT); - }, - "right" if !result.intersects(TextUnderlinePosition::LEFT | - TextUnderlinePosition::RIGHT) => { - result.insert(TextUnderlinePosition::RIGHT); - }, - _ => return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedIdent(ident.clone()) - )), - } - } - - if !result.is_empty() { - Ok(result) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - } - } -} - -impl ToCss for TextUnderlinePosition { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - if self.is_empty() { - return dest.write_str("auto"); - } - - let mut writer = SequenceWriter::new(dest, " "); - let mut any = false; - - macro_rules! maybe_write { - ($ident:ident => $str:expr) => { - if self.contains(TextUnderlinePosition::$ident) { - any = true; - writer.raw_item($str)?; - } - }; - } - - maybe_write!(FROM_FONT => "from-font"); - maybe_write!(UNDER => "under"); - maybe_write!(LEFT => "left"); - maybe_write!(RIGHT => "right"); - - debug_assert!(any); - - Ok(()) - } -} - -/// Values for `ruby-position` property -#[repr(u8)] -#[derive( - Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] -#[allow(missing_docs)] -pub enum RubyPosition { - AlternateOver, - AlternateUnder, - Over, - Under, -} - -impl Parse for RubyPosition { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<RubyPosition, ParseError<'i>> { - // Parse alternate before - let alternate = input - .try_parse(|i| i.expect_ident_matching("alternate")) - .is_ok(); - if alternate && input.is_exhausted() { - return Ok(RubyPosition::AlternateOver); - } - // Parse over / under - let over = try_match_ident_ignore_ascii_case! { input, - "over" => true, - "under" => false, - }; - // Parse alternate after - let alternate = alternate || - input - .try_parse(|i| i.expect_ident_matching("alternate")) - .is_ok(); - - Ok(match (over, alternate) { - (true, true) => RubyPosition::AlternateOver, - (false, true) => RubyPosition::AlternateUnder, - (true, false) => RubyPosition::Over, - (false, false) => RubyPosition::Under, - }) - } -} - -impl ToCss for RubyPosition { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str(match self { - RubyPosition::AlternateOver => "alternate", - RubyPosition::AlternateUnder => "alternate under", - RubyPosition::Over => "over", - RubyPosition::Under => "under", - }) - } -} - -impl SpecifiedValueInfo for RubyPosition { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&["alternate", "over", "under"]) - } -} diff --git a/components/style/values/specified/time.rs b/components/style/values/specified/time.rs deleted file mode 100644 index e8410c7f43d..00000000000 --- a/components/style/values/specified/time.rs +++ /dev/null @@ -1,174 +0,0 @@ -/* 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/. */ - -//! Specified time values. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::time::Time as ComputedTime; -use crate::values::computed::{Context, ToComputedValue}; -use crate::values::specified::calc::CalcNode; -use crate::values::CSSFloat; -use cssparser::{Parser, Token}; -use std::fmt::{self, Write}; -use style_traits::values::specified::AllowedNumericType; -use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; - -/// A time value according to CSS-VALUES § 6.2. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub struct Time { - seconds: CSSFloat, - unit: TimeUnit, - calc_clamping_mode: Option<AllowedNumericType>, -} - -/// A time unit. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub enum TimeUnit { - /// `s` - Second, - /// `ms` - Millisecond, -} - -impl Time { - /// Returns a time value that represents `seconds` seconds. - pub fn from_seconds_with_calc_clamping_mode( - seconds: CSSFloat, - calc_clamping_mode: Option<AllowedNumericType>, - ) -> Self { - Time { - seconds, - unit: TimeUnit::Second, - calc_clamping_mode, - } - } - - /// Returns a time value that represents `seconds` seconds. - pub fn from_seconds(seconds: CSSFloat) -> Self { - Self::from_seconds_with_calc_clamping_mode(seconds, None) - } - - /// Returns `0s`. - pub fn zero() -> Self { - Self::from_seconds(0.0) - } - - /// Returns the time in fractional seconds. - pub fn seconds(self) -> CSSFloat { - self.seconds - } - - /// Returns the unit of the time. - #[inline] - pub fn unit(&self) -> &'static str { - match self.unit { - TimeUnit::Second => "s", - TimeUnit::Millisecond => "ms", - } - } - - #[inline] - fn unitless_value(&self) -> CSSFloat { - match self.unit { - TimeUnit::Second => self.seconds, - TimeUnit::Millisecond => self.seconds * 1000., - } - } - - /// Parses a time according to CSS-VALUES § 6.2. - pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Time, ()> { - let (seconds, unit) = match_ignore_ascii_case! { unit, - "s" => (value, TimeUnit::Second), - "ms" => (value / 1000.0, TimeUnit::Millisecond), - _ => return Err(()) - }; - - Ok(Time { - seconds, - unit, - calc_clamping_mode: None, - }) - } - - fn parse_with_clamping_mode<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - clamping_mode: AllowedNumericType, - ) -> Result<Self, ParseError<'i>> { - use style_traits::ParsingMode; - - let location = input.current_source_location(); - match *input.next()? { - // Note that we generally pass ParserContext to is_ok() to check - // that the ParserMode of the ParserContext allows all numeric - // values for SMIL regardless of clamping_mode, but in this Time - // value case, the value does not animate for SMIL at all, so we use - // ParsingMode::DEFAULT directly. - Token::Dimension { - value, ref unit, .. - } if clamping_mode.is_ok(ParsingMode::DEFAULT, value) => { - Time::parse_dimension(value, unit) - .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) - }, - Token::Function(ref name) => { - let function = CalcNode::math_function(context, name, location)?; - CalcNode::parse_time(context, input, clamping_mode, function) - }, - ref t => return Err(location.new_unexpected_token_error(t.clone())), - } - } - - /// Parses a non-negative time value. - pub fn parse_non_negative<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative) - } -} - -impl ToComputedValue for Time { - type ComputedValue = ComputedTime; - - fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue { - let seconds = self - .calc_clamping_mode - .map_or(self.seconds(), |mode| mode.clamp(self.seconds())); - - ComputedTime::from_seconds(crate::values::normalize(seconds)) - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - Time { - seconds: computed.seconds(), - unit: TimeUnit::Second, - calc_clamping_mode: None, - } - } -} - -impl Parse for Time { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Self::parse_with_clamping_mode(context, input, AllowedNumericType::All) - } -} - -impl ToCss for Time { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - crate::values::serialize_specified_dimension( - self.unitless_value(), - self.unit(), - self.calc_clamping_mode.is_some(), - dest, - ) - } -} - -impl SpecifiedValueInfo for Time {} diff --git a/components/style/values/specified/transform.rs b/components/style/values/specified/transform.rs deleted file mode 100644 index c10b79a089b..00000000000 --- a/components/style/values/specified/transform.rs +++ /dev/null @@ -1,487 +0,0 @@ -/* 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/. */ - -//! Specified types for CSS values that are related to transformations. - -use crate::parser::{Parse, ParserContext}; -use crate::values::computed::{Context, LengthPercentage as ComputedLengthPercentage}; -use crate::values::computed::{Percentage as ComputedPercentage, ToComputedValue}; -use crate::values::generics::transform as generic; -use crate::values::generics::transform::{Matrix, Matrix3D}; -use crate::values::specified::position::{ - HorizontalPositionKeyword, Side, VerticalPositionKeyword, -}; -use crate::values::specified::{ - self, Angle, Integer, Length, LengthPercentage, Number, NumberOrPercentage, -}; -use crate::Zero; -use cssparser::Parser; -use style_traits::{ParseError, StyleParseErrorKind}; - -pub use crate::values::generics::transform::TransformStyle; - -/// A single operation in a specified CSS `transform` -pub type TransformOperation = - generic::TransformOperation<Angle, Number, Length, Integer, LengthPercentage>; - -/// A specified CSS `transform` -pub type Transform = generic::Transform<TransformOperation>; - -/// The specified value of a CSS `<transform-origin>` -pub type TransformOrigin = generic::TransformOrigin< - OriginComponent<HorizontalPositionKeyword>, - OriginComponent<VerticalPositionKeyword>, - Length, ->; - -impl TransformOrigin { - /// Returns the initial specified value for `transform-origin`. - #[inline] - pub fn initial_value() -> Self { - Self::new( - OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))), - OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage(0.5))), - Length::zero(), - ) - } - - /// Returns the `0 0` value. - pub fn zero_zero() -> Self { - Self::new( - OriginComponent::Length(LengthPercentage::zero()), - OriginComponent::Length(LengthPercentage::zero()), - Length::zero(), - ) - } -} - -impl Transform { - /// Internal parse function for deciding if we wish to accept prefixed values or not - /// - /// `transform` allows unitless zero angles as an exception, see: - /// https://github.com/w3c/csswg-drafts/issues/1162 - fn parse_internal<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use style_traits::{Separator, Space}; - - if input - .try_parse(|input| input.expect_ident_matching("none")) - .is_ok() - { - return Ok(generic::Transform::none()); - } - - Ok(generic::Transform( - Space::parse(input, |input| { - let function = input.expect_function()?.clone(); - input.parse_nested_block(|input| { - let location = input.current_source_location(); - let result = match_ignore_ascii_case! { &function, - "matrix" => { - let a = Number::parse(context, input)?; - input.expect_comma()?; - let b = Number::parse(context, input)?; - input.expect_comma()?; - let c = Number::parse(context, input)?; - input.expect_comma()?; - let d = Number::parse(context, input)?; - input.expect_comma()?; - // Standard matrix parsing. - let e = Number::parse(context, input)?; - input.expect_comma()?; - let f = Number::parse(context, input)?; - Ok(generic::TransformOperation::Matrix(Matrix { a, b, c, d, e, f })) - }, - "matrix3d" => { - let m11 = Number::parse(context, input)?; - input.expect_comma()?; - let m12 = Number::parse(context, input)?; - input.expect_comma()?; - let m13 = Number::parse(context, input)?; - input.expect_comma()?; - let m14 = Number::parse(context, input)?; - input.expect_comma()?; - let m21 = Number::parse(context, input)?; - input.expect_comma()?; - let m22 = Number::parse(context, input)?; - input.expect_comma()?; - let m23 = Number::parse(context, input)?; - input.expect_comma()?; - let m24 = Number::parse(context, input)?; - input.expect_comma()?; - let m31 = Number::parse(context, input)?; - input.expect_comma()?; - let m32 = Number::parse(context, input)?; - input.expect_comma()?; - let m33 = Number::parse(context, input)?; - input.expect_comma()?; - let m34 = Number::parse(context, input)?; - input.expect_comma()?; - // Standard matrix3d parsing. - let m41 = Number::parse(context, input)?; - input.expect_comma()?; - let m42 = Number::parse(context, input)?; - input.expect_comma()?; - let m43 = Number::parse(context, input)?; - input.expect_comma()?; - let m44 = Number::parse(context, input)?; - Ok(generic::TransformOperation::Matrix3D(Matrix3D { - m11, m12, m13, m14, - m21, m22, m23, m24, - m31, m32, m33, m34, - m41, m42, m43, m44, - })) - }, - "translate" => { - let sx = specified::LengthPercentage::parse(context, input)?; - if input.try_parse(|input| input.expect_comma()).is_ok() { - let sy = specified::LengthPercentage::parse(context, input)?; - Ok(generic::TransformOperation::Translate(sx, sy)) - } else { - Ok(generic::TransformOperation::Translate(sx, Zero::zero())) - } - }, - "translatex" => { - let tx = specified::LengthPercentage::parse(context, input)?; - Ok(generic::TransformOperation::TranslateX(tx)) - }, - "translatey" => { - let ty = specified::LengthPercentage::parse(context, input)?; - Ok(generic::TransformOperation::TranslateY(ty)) - }, - "translatez" => { - let tz = specified::Length::parse(context, input)?; - Ok(generic::TransformOperation::TranslateZ(tz)) - }, - "translate3d" => { - let tx = specified::LengthPercentage::parse(context, input)?; - input.expect_comma()?; - let ty = specified::LengthPercentage::parse(context, input)?; - input.expect_comma()?; - let tz = specified::Length::parse(context, input)?; - Ok(generic::TransformOperation::Translate3D(tx, ty, tz)) - }, - "scale" => { - let sx = NumberOrPercentage::parse(context, input)?.to_number(); - if input.try_parse(|input| input.expect_comma()).is_ok() { - let sy = NumberOrPercentage::parse(context, input)?.to_number(); - Ok(generic::TransformOperation::Scale(sx, sy)) - } else { - Ok(generic::TransformOperation::Scale(sx, sx)) - } - }, - "scalex" => { - let sx = NumberOrPercentage::parse(context, input)?.to_number(); - Ok(generic::TransformOperation::ScaleX(sx)) - }, - "scaley" => { - let sy = NumberOrPercentage::parse(context, input)?.to_number(); - Ok(generic::TransformOperation::ScaleY(sy)) - }, - "scalez" => { - let sz = NumberOrPercentage::parse(context, input)?.to_number(); - Ok(generic::TransformOperation::ScaleZ(sz)) - }, - "scale3d" => { - let sx = NumberOrPercentage::parse(context, input)?.to_number(); - input.expect_comma()?; - let sy = NumberOrPercentage::parse(context, input)?.to_number(); - input.expect_comma()?; - let sz = NumberOrPercentage::parse(context, input)?.to_number(); - Ok(generic::TransformOperation::Scale3D(sx, sy, sz)) - }, - "rotate" => { - let theta = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::Rotate(theta)) - }, - "rotatex" => { - let theta = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::RotateX(theta)) - }, - "rotatey" => { - let theta = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::RotateY(theta)) - }, - "rotatez" => { - let theta = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::RotateZ(theta)) - }, - "rotate3d" => { - let ax = Number::parse(context, input)?; - input.expect_comma()?; - let ay = Number::parse(context, input)?; - input.expect_comma()?; - let az = Number::parse(context, input)?; - input.expect_comma()?; - let theta = specified::Angle::parse_with_unitless(context, input)?; - // TODO(gw): Check that the axis can be normalized. - Ok(generic::TransformOperation::Rotate3D(ax, ay, az, theta)) - }, - "skew" => { - let ax = specified::Angle::parse_with_unitless(context, input)?; - if input.try_parse(|input| input.expect_comma()).is_ok() { - let ay = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::Skew(ax, ay)) - } else { - Ok(generic::TransformOperation::Skew(ax, Zero::zero())) - } - }, - "skewx" => { - let theta = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::SkewX(theta)) - }, - "skewy" => { - let theta = specified::Angle::parse_with_unitless(context, input)?; - Ok(generic::TransformOperation::SkewY(theta)) - }, - "perspective" => { - let p = match input.try_parse(|input| specified::Length::parse_non_negative(context, input)) { - Ok(p) => generic::PerspectiveFunction::Length(p), - Err(..) => { - input.expect_ident_matching("none")?; - generic::PerspectiveFunction::None - } - }; - Ok(generic::TransformOperation::Perspective(p)) - }, - _ => Err(()), - }; - result.map_err(|()| { - location.new_custom_error(StyleParseErrorKind::UnexpectedFunction( - function.clone(), - )) - }) - }) - })? - .into(), - )) - } -} - -impl Parse for Transform { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - Transform::parse_internal(context, input) - } -} - -/// The specified value of a component of a CSS `<transform-origin>`. -#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] -pub enum OriginComponent<S> { - /// `center` - Center, - /// `<length-percentage>` - Length(LengthPercentage), - /// `<side>` - Side(S), -} - -impl Parse for TransformOrigin { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let parse_depth = |input: &mut Parser| { - input - .try_parse(|i| Length::parse(context, i)) - .unwrap_or(Length::zero()) - }; - match input.try_parse(|i| OriginComponent::parse(context, i)) { - Ok(x_origin @ OriginComponent::Center) => { - if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) { - let depth = parse_depth(input); - return Ok(Self::new(x_origin, y_origin, depth)); - } - let y_origin = OriginComponent::Center; - if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) { - let x_origin = OriginComponent::Side(x_keyword); - let depth = parse_depth(input); - return Ok(Self::new(x_origin, y_origin, depth)); - } - let depth = Length::from_px(0.); - return Ok(Self::new(x_origin, y_origin, depth)); - }, - Ok(x_origin) => { - if let Ok(y_origin) = input.try_parse(|i| OriginComponent::parse(context, i)) { - let depth = parse_depth(input); - return Ok(Self::new(x_origin, y_origin, depth)); - } - let y_origin = OriginComponent::Center; - let depth = Length::from_px(0.); - return Ok(Self::new(x_origin, y_origin, depth)); - }, - Err(_) => {}, - } - let y_keyword = VerticalPositionKeyword::parse(input)?; - let y_origin = OriginComponent::Side(y_keyword); - if let Ok(x_keyword) = input.try_parse(HorizontalPositionKeyword::parse) { - let x_origin = OriginComponent::Side(x_keyword); - let depth = parse_depth(input); - return Ok(Self::new(x_origin, y_origin, depth)); - } - if input - .try_parse(|i| i.expect_ident_matching("center")) - .is_ok() - { - let x_origin = OriginComponent::Center; - let depth = parse_depth(input); - return Ok(Self::new(x_origin, y_origin, depth)); - } - let x_origin = OriginComponent::Center; - let depth = Length::from_px(0.); - Ok(Self::new(x_origin, y_origin, depth)) - } -} - -impl<S> ToComputedValue for OriginComponent<S> -where - S: Side, -{ - type ComputedValue = ComputedLengthPercentage; - - fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { - match *self { - OriginComponent::Center => { - ComputedLengthPercentage::new_percent(ComputedPercentage(0.5)) - }, - OriginComponent::Length(ref length) => length.to_computed_value(context), - OriginComponent::Side(ref keyword) => { - let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. }); - ComputedLengthPercentage::new_percent(p) - }, - } - } - - fn from_computed_value(computed: &Self::ComputedValue) -> Self { - OriginComponent::Length(ToComputedValue::from_computed_value(computed)) - } -} - -impl<S> OriginComponent<S> { - /// `0%` - pub fn zero() -> Self { - OriginComponent::Length(LengthPercentage::Percentage(ComputedPercentage::zero())) - } -} - -/// A specified CSS `rotate` -pub type Rotate = generic::Rotate<Number, Angle>; - -impl Parse for Rotate { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(generic::Rotate::None); - } - - // Parse <angle> or [ x | y | z | <number>{3} ] && <angle>. - // - // The rotate axis and angle could be in any order, so we parse angle twice to cover - // two cases. i.e. `<number>{3} <angle>` or `<angle> <number>{3}` - let angle = input - .try_parse(|i| specified::Angle::parse(context, i)) - .ok(); - let axis = input - .try_parse(|i| { - Ok(try_match_ident_ignore_ascii_case! { i, - "x" => (Number::new(1.), Number::new(0.), Number::new(0.)), - "y" => (Number::new(0.), Number::new(1.), Number::new(0.)), - "z" => (Number::new(0.), Number::new(0.), Number::new(1.)), - }) - }) - .or_else(|_: ParseError| -> Result<_, ParseError> { - input.try_parse(|i| { - Ok(( - Number::parse(context, i)?, - Number::parse(context, i)?, - Number::parse(context, i)?, - )) - }) - }) - .ok(); - let angle = match angle { - Some(a) => a, - None => specified::Angle::parse(context, input)?, - }; - - Ok(match axis { - Some((x, y, z)) => generic::Rotate::Rotate3D(x, y, z, angle), - None => generic::Rotate::Rotate(angle), - }) - } -} - -/// A specified CSS `translate` -pub type Translate = generic::Translate<LengthPercentage, Length>; - -impl Parse for Translate { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(generic::Translate::None); - } - - let tx = specified::LengthPercentage::parse(context, input)?; - if let Ok(ty) = input.try_parse(|i| specified::LengthPercentage::parse(context, i)) { - if let Ok(tz) = input.try_parse(|i| specified::Length::parse(context, i)) { - // 'translate: <length-percentage> <length-percentage> <length>' - return Ok(generic::Translate::Translate(tx, ty, tz)); - } - - // translate: <length-percentage> <length-percentage>' - return Ok(generic::Translate::Translate( - tx, - ty, - specified::Length::zero(), - )); - } - - // 'translate: <length-percentage> ' - Ok(generic::Translate::Translate( - tx, - specified::LengthPercentage::zero(), - specified::Length::zero(), - )) - } -} - -/// A specified CSS `scale` -pub type Scale = generic::Scale<Number>; - -impl Parse for Scale { - /// Scale accepts <number> | <percentage>, so we parse it as NumberOrPercentage, - /// and then convert into an Number if it's a Percentage. - /// https://github.com/w3c/csswg-drafts/pull/4396 - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(generic::Scale::None); - } - - let sx = NumberOrPercentage::parse(context, input)?.to_number(); - if let Ok(sy) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) { - let sy = sy.to_number(); - if let Ok(sz) = input.try_parse(|i| NumberOrPercentage::parse(context, i)) { - // 'scale: <number> <number> <number>' - return Ok(generic::Scale::Scale(sx, sy, sz.to_number())); - } - - // 'scale: <number> <number>' - return Ok(generic::Scale::Scale(sx, sy, Number::new(1.0))); - } - - // 'scale: <number>' - Ok(generic::Scale::Scale(sx, sx, Number::new(1.0))) - } -} diff --git a/components/style/values/specified/ui.rs b/components/style/values/specified/ui.rs deleted file mode 100644 index 0c656faab20..00000000000 --- a/components/style/values/specified/ui.rs +++ /dev/null @@ -1,232 +0,0 @@ -/* 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/. */ - -//! Specified types for UI properties. - -use crate::parser::{Parse, ParserContext}; -use crate::values::generics::ui as generics; -use crate::values::specified::color::Color; -use crate::values::specified::image::Image; -use crate::values::specified::Number; -use cssparser::Parser; -use std::fmt::{self, Write}; -use style_traits::{ - CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, -}; - -/// A specified value for the `cursor` property. -pub type Cursor = generics::GenericCursor<CursorImage>; - -/// A specified value for item of `image cursors`. -pub type CursorImage = generics::GenericCursorImage<Image, Number>; - -impl Parse for Cursor { - /// cursor: [<url> [<number> <number>]?]# [auto | default | ...] - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - let mut images = vec![]; - loop { - match input.try_parse(|input| CursorImage::parse(context, input)) { - Ok(image) => images.push(image), - Err(_) => break, - } - input.expect_comma()?; - } - Ok(Self { - images: images.into(), - keyword: CursorKind::parse(input)?, - }) - } -} - -impl Parse for CursorImage { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - use crate::Zero; - - let image = Image::parse_only_url(context, input)?; - let mut has_hotspot = false; - let mut hotspot_x = Number::zero(); - let mut hotspot_y = Number::zero(); - - if let Ok(x) = input.try_parse(|input| Number::parse(context, input)) { - has_hotspot = true; - hotspot_x = x; - hotspot_y = Number::parse(context, input)?; - } - - Ok(Self { - image, - has_hotspot, - hotspot_x, - hotspot_y, - }) - } -} - -// This trait is manually implemented because we don't support the whole <image> -// syntax for cursors -impl SpecifiedValueInfo for CursorImage { - fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&["url", "image-set"]); - } -} -/// Specified value of `-moz-force-broken-image-icon` -#[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] -#[repr(transparent)] -pub struct BoolInteger(pub bool); - -impl BoolInteger { - /// Returns 0 - #[inline] - pub fn zero() -> Self { - Self(false) - } -} - -impl Parse for BoolInteger { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // We intentionally don't support calc values here. - match input.expect_integer()? { - 0 => Ok(Self(false)), - 1 => Ok(Self(true)), - _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), - } - } -} - -impl ToCss for BoolInteger { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - dest.write_str(if self.0 { "1" } else { "0" }) - } -} - -/// A specified value for `scrollbar-color` property -pub type ScrollbarColor = generics::ScrollbarColor<Color>; - -impl Parse for ScrollbarColor { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(generics::ScrollbarColor::Auto); - } - Ok(generics::ScrollbarColor::Colors { - thumb: Color::parse(context, input)?, - track: Color::parse(context, input)?, - }) - } -} - -/// The specified value for the `user-select` property. -/// -/// https://drafts.csswg.org/css-ui-4/#propdef-user-select -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum UserSelect { - Auto, - Text, - #[parse(aliases = "-moz-none")] - None, - /// Force selection of all children. - All, -} - -/// The keywords allowed in the Cursor property. -/// -/// https://drafts.csswg.org/css-ui-4/#propdef-cursor -#[allow(missing_docs)] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum CursorKind { - None, - Default, - Pointer, - ContextMenu, - Help, - Progress, - Wait, - Cell, - Crosshair, - Text, - VerticalText, - Alias, - Copy, - Move, - NoDrop, - NotAllowed, - #[parse(aliases = "-moz-grab")] - Grab, - #[parse(aliases = "-moz-grabbing")] - Grabbing, - EResize, - NResize, - NeResize, - NwResize, - SResize, - SeResize, - SwResize, - WResize, - EwResize, - NsResize, - NeswResize, - NwseResize, - ColResize, - RowResize, - AllScroll, - #[parse(aliases = "-moz-zoom-in")] - ZoomIn, - #[parse(aliases = "-moz-zoom-out")] - ZoomOut, - Auto, -} diff --git a/components/style/values/specified/url.rs b/components/style/values/specified/url.rs deleted file mode 100644 index 17ecbe0d5e8..00000000000 --- a/components/style/values/specified/url.rs +++ /dev/null @@ -1,15 +0,0 @@ -/* 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/. */ - -//! Common handling for the specified value CSS url() values. - -use crate::values::generics::url::GenericUrlOrNone; - -#[cfg(feature = "gecko")] -pub use crate::gecko::url::{SpecifiedImageUrl, SpecifiedUrl}; -#[cfg(feature = "servo")] -pub use crate::servo::url::{SpecifiedImageUrl, SpecifiedUrl}; - -/// Specified <url> | <none> -pub type UrlOrNone = GenericUrlOrNone<SpecifiedUrl>; diff --git a/components/style_config/Cargo.toml b/components/style_config/Cargo.toml deleted file mode 100644 index 02507dacf0c..00000000000 --- a/components/style_config/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "style_config" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2021" -publish = false - -[lib] -name = "style_config" -path = "lib.rs" - -[dependencies] -lazy_static = { workspace = true } diff --git a/components/style_config/lib.rs b/components/style_config/lib.rs deleted file mode 100644 index 8b2c2fc4528..00000000000 --- a/components/style_config/lib.rs +++ /dev/null @@ -1,97 +0,0 @@ -/* 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/. */ - -use std::collections::HashMap; -use std::sync::RwLock; - -use lazy_static::lazy_static; - -lazy_static! { - static ref PREFS: Preferences = Preferences::default(); -} - -#[derive(Debug, Default)] -pub struct Preferences { - // When adding a new pref type, be sure to update the TryFrom<&PrefValue> in - // servo_config, to plumb the values in from Servo. - bool_prefs: RwLock<HashMap<String, bool>>, - i32_prefs: RwLock<HashMap<String, i32>>, -} - -impl Preferences { - pub fn get_bool(&self, key: &str) -> bool { - let prefs = self.bool_prefs.read().expect("RwLock is poisoned"); - *prefs.get(key).unwrap_or(&false) - } - - pub fn get_i32(&self, key: &str) -> i32 { - let prefs = self.i32_prefs.read().expect("RwLock is poisoned"); - *prefs.get(key).unwrap_or(&0) - } - - pub fn set_bool(&self, key: &str, value: bool) { - let mut prefs = self.bool_prefs.write().expect("RwLock is poisoned"); - - // Avoid cloning the key if it exists. - if let Some(pref) = prefs.get_mut(key) { - *pref = value; - } else { - prefs.insert(key.to_owned(), value); - } - } - - pub fn set_i32(&self, key: &str, value: i32) { - let mut prefs = self.i32_prefs.write().expect("RwLock is poisoned"); - - // Avoid cloning the key if it exists. - if let Some(pref) = prefs.get_mut(key) { - *pref = value; - } else { - prefs.insert(key.to_owned(), value); - } - } -} - -pub fn get_bool(key: &str) -> bool { - PREFS.get_bool(key) -} - -pub fn get_i32(key: &str) -> i32 { - PREFS.get_i32(key) -} - -pub fn set_bool(key: &str, value: bool) { - PREFS.set_bool(key, value) -} - -pub fn set_i32(key: &str, value: i32) { - PREFS.set_i32(key, value) -} - -#[test] -fn test() { - let mut prefs = Preferences::default(); - - // Prefs have default values when unset. - assert_eq!(prefs.get_bool("foo"), false); - assert_eq!(prefs.get_i32("bar"), 0); - - // Prefs can be set and retrieved. - prefs.set_bool("foo", true); - prefs.set_i32("bar", 1); - assert_eq!(prefs.get_bool("foo"), true); - assert_eq!(prefs.get_i32("bar"), 1); - prefs.set_bool("foo", false); - prefs.set_i32("bar", 2); - assert_eq!(prefs.get_bool("foo"), false); - assert_eq!(prefs.get_i32("bar"), 2); - - // Each value type currently has an independent namespace. - prefs.set_i32("foo", 3); - prefs.set_bool("bar", true); - assert_eq!(prefs.get_i32("foo"), 3); - assert_eq!(prefs.get_bool("foo"), false); - assert_eq!(prefs.get_bool("bar"), true); - assert_eq!(prefs.get_i32("bar"), 2); -} diff --git a/components/style_derive/Cargo.toml b/components/style_derive/Cargo.toml deleted file mode 100644 index 98dc1852e8c..00000000000 --- a/components/style_derive/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "style_derive" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -[lib] -path = "lib.rs" -proc-macro = true - -[dependencies] -darling = { workspace = true, default-features = false } -derive_common = { path = "../derive_common" } -proc-macro2 = "1" -quote = "1" -syn = { workspace = true } -synstructure = { workspace = true } diff --git a/components/style_derive/animate.rs b/components/style_derive/animate.rs deleted file mode 100644 index 9549100ad08..00000000000 --- a/components/style_derive/animate.rs +++ /dev/null @@ -1,135 +0,0 @@ -/* 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/. */ - -use darling::util::PathList; -use derive_common::cg; -use proc_macro2::TokenStream; -use quote::TokenStreamExt; -use syn::{DeriveInput, WhereClause}; -use synstructure::{Structure, VariantInfo}; - -pub fn derive(mut input: DeriveInput) -> TokenStream { - let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input); - - let no_bound = animation_input_attrs.no_bound.unwrap_or_default(); - let mut where_clause = input.generics.where_clause.take(); - for param in input.generics.type_params() { - if !no_bound.iter().any(|name| name.is_ident(¶m.ident)) { - cg::add_predicate( - &mut where_clause, - parse_quote!(#param: crate::values::animated::Animate), - ); - } - } - let (mut match_body, needs_catchall_branch) = { - let s = Structure::new(&input); - let needs_catchall_branch = s.variants().len() > 1; - let match_body = s.variants().iter().fold(quote!(), |body, variant| { - let arm = derive_variant_arm(variant, &mut where_clause); - quote! { #body #arm } - }); - (match_body, needs_catchall_branch) - }; - - input.generics.where_clause = where_clause; - - if needs_catchall_branch { - // This ideally shouldn't be needed, but see - // https://github.com/rust-lang/rust/issues/68867 - match_body.append_all(quote! { _ => unsafe { debug_unreachable!() } }); - } - - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - quote! { - impl #impl_generics crate::values::animated::Animate for #name #ty_generics #where_clause { - #[allow(unused_variables, unused_imports)] - #[inline] - fn animate( - &self, - other: &Self, - procedure: crate::values::animated::Procedure, - ) -> Result<Self, ()> { - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - match (self, other) { - #match_body - } - } - } - } -} - -fn derive_variant_arm( - variant: &VariantInfo, - where_clause: &mut Option<WhereClause>, -) -> TokenStream { - let variant_attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast()); - let (this_pattern, this_info) = cg::ref_pattern(&variant, "this"); - let (other_pattern, other_info) = cg::ref_pattern(&variant, "other"); - - if variant_attrs.error { - return quote! { - (&#this_pattern, &#other_pattern) => Err(()), - }; - } - - let (result_value, result_info) = cg::value(&variant, "result"); - let mut computations = quote!(); - let iter = result_info.iter().zip(this_info.iter().zip(&other_info)); - computations.append_all(iter.map(|(result, (this, other))| { - let field_attrs = cg::parse_field_attrs::<AnimationFieldAttrs>(&result.ast()); - if field_attrs.field_bound { - let ty = &this.ast().ty; - cg::add_predicate( - where_clause, - parse_quote!(#ty: crate::values::animated::Animate), - ); - } - if field_attrs.constant { - quote! { - if #this != #other { - return Err(()); - } - let #result = std::clone::Clone::clone(#this); - } - } else { - quote! { - let #result = - crate::values::animated::Animate::animate(#this, #other, procedure)?; - } - } - })); - - quote! { - (&#this_pattern, &#other_pattern) => { - #computations - Ok(#result_value) - } - } -} - -#[derive(Default, FromDeriveInput)] -#[darling(attributes(animation), default)] -pub struct AnimationInputAttrs { - pub no_bound: Option<PathList>, -} - -#[derive(Default, FromVariant)] -#[darling(attributes(animation), default)] -pub struct AnimationVariantAttrs { - pub error: bool, - // Only here because of structs, where the struct definition acts as a - // variant itself. - pub no_bound: Option<PathList>, -} - -#[derive(Default, FromField)] -#[darling(attributes(animation), default)] -pub struct AnimationFieldAttrs { - pub constant: bool, - pub field_bound: bool, -} diff --git a/components/style_derive/compute_squared_distance.rs b/components/style_derive/compute_squared_distance.rs deleted file mode 100644 index 022ab115eea..00000000000 --- a/components/style_derive/compute_squared_distance.rs +++ /dev/null @@ -1,125 +0,0 @@ -/* 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/. */ - -use crate::animate::{AnimationFieldAttrs, AnimationInputAttrs, AnimationVariantAttrs}; -use derive_common::cg; -use proc_macro2::TokenStream; -use quote::TokenStreamExt; -use syn::{DeriveInput, WhereClause}; -use synstructure; - -pub fn derive(mut input: DeriveInput) -> TokenStream { - let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input); - let no_bound = animation_input_attrs.no_bound.unwrap_or_default(); - let mut where_clause = input.generics.where_clause.take(); - for param in input.generics.type_params() { - if !no_bound.iter().any(|name| name.is_ident(¶m.ident)) { - cg::add_predicate( - &mut where_clause, - parse_quote!(#param: crate::values::distance::ComputeSquaredDistance), - ); - } - } - - let (mut match_body, needs_catchall_branch) = { - let s = synstructure::Structure::new(&input); - let needs_catchall_branch = s.variants().len() > 1; - - let match_body = s.variants().iter().fold(quote!(), |body, variant| { - let arm = derive_variant_arm(variant, &mut where_clause); - quote! { #body #arm } - }); - - (match_body, needs_catchall_branch) - }; - - input.generics.where_clause = where_clause; - - if needs_catchall_branch { - // This ideally shouldn't be needed, but see: - // https://github.com/rust-lang/rust/issues/68867 - match_body.append_all(quote! { _ => unsafe { debug_unreachable!() } }); - } - - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - quote! { - impl #impl_generics crate::values::distance::ComputeSquaredDistance for #name #ty_generics #where_clause { - #[allow(unused_variables, unused_imports)] - #[inline] - fn compute_squared_distance( - &self, - other: &Self, - ) -> Result<crate::values::distance::SquaredDistance, ()> { - if std::mem::discriminant(self) != std::mem::discriminant(other) { - return Err(()); - } - match (self, other) { - #match_body - } - } - } - } -} - -fn derive_variant_arm( - variant: &synstructure::VariantInfo, - mut where_clause: &mut Option<WhereClause>, -) -> TokenStream { - let variant_attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast()); - let (this_pattern, this_info) = cg::ref_pattern(&variant, "this"); - let (other_pattern, other_info) = cg::ref_pattern(&variant, "other"); - - if variant_attrs.error { - return quote! { - (&#this_pattern, &#other_pattern) => Err(()), - }; - } - - let sum = if this_info.is_empty() { - quote! { crate::values::distance::SquaredDistance::from_sqrt(0.) } - } else { - let mut sum = quote!(); - sum.append_separated(this_info.iter().zip(&other_info).map(|(this, other)| { - let field_attrs = cg::parse_field_attrs::<DistanceFieldAttrs>(&this.ast()); - if field_attrs.field_bound { - let ty = &this.ast().ty; - cg::add_predicate( - &mut where_clause, - parse_quote!(#ty: crate::values::distance::ComputeSquaredDistance), - ); - } - - let animation_field_attrs = - cg::parse_field_attrs::<AnimationFieldAttrs>(&this.ast()); - - if animation_field_attrs.constant { - quote! { - { - if #this != #other { - return Err(()); - } - crate::values::distance::SquaredDistance::from_sqrt(0.) - } - } - } else { - quote! { - crate::values::distance::ComputeSquaredDistance::compute_squared_distance(#this, #other)? - } - } - }), quote!(+)); - sum - }; - - return quote! { - (&#this_pattern, &#other_pattern) => Ok(#sum), - }; -} - -#[derive(Default, FromField)] -#[darling(attributes(distance), default)] -struct DistanceFieldAttrs { - field_bound: bool, -} diff --git a/components/style_derive/lib.rs b/components/style_derive/lib.rs deleted file mode 100644 index 079db00c5a3..00000000000 --- a/components/style_derive/lib.rs +++ /dev/null @@ -1,82 +0,0 @@ -/* 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/. */ - -#![recursion_limit = "128"] - -#[macro_use] -extern crate darling; -extern crate derive_common; -extern crate proc_macro; -extern crate proc_macro2; -#[macro_use] -extern crate quote; -#[macro_use] -extern crate syn; -extern crate synstructure; - -use proc_macro::TokenStream; - -mod animate; -mod compute_squared_distance; -mod parse; -mod specified_value_info; -mod to_animated_value; -mod to_animated_zero; -mod to_computed_value; -mod to_css; -mod to_resolved_value; - -#[proc_macro_derive(Animate, attributes(animate, animation))] -pub fn derive_animate(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - animate::derive(input).into() -} - -#[proc_macro_derive(ComputeSquaredDistance, attributes(animation, distance))] -pub fn derive_compute_squared_distance(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - compute_squared_distance::derive(input).into() -} - -#[proc_macro_derive(ToAnimatedValue)] -pub fn derive_to_animated_value(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - to_animated_value::derive(input).into() -} - -#[proc_macro_derive(Parse, attributes(css, parse))] -pub fn derive_parse(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - parse::derive(input).into() -} - -#[proc_macro_derive(ToAnimatedZero, attributes(animation, zero))] -pub fn derive_to_animated_zero(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - to_animated_zero::derive(input).into() -} - -#[proc_macro_derive(ToComputedValue, attributes(compute))] -pub fn derive_to_computed_value(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - to_computed_value::derive(input).into() -} - -#[proc_macro_derive(ToResolvedValue, attributes(resolve))] -pub fn derive_to_resolved_value(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - to_resolved_value::derive(input).into() -} - -#[proc_macro_derive(ToCss, attributes(css))] -pub fn derive_to_css(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - to_css::derive(input).into() -} - -#[proc_macro_derive(SpecifiedValueInfo, attributes(css, parse, value_info))] -pub fn derive_specified_value_info(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - specified_value_info::derive(input).into() -} diff --git a/components/style_derive/parse.rs b/components/style_derive/parse.rs deleted file mode 100644 index b1a1213435c..00000000000 --- a/components/style_derive/parse.rs +++ /dev/null @@ -1,323 +0,0 @@ -/* 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/. */ - -use crate::to_css::{CssBitflagAttrs, CssVariantAttrs}; -use derive_common::cg; -use proc_macro2::{Span, TokenStream}; -use quote::TokenStreamExt; -use syn::{self, DeriveInput, Ident, Path}; -use synstructure::{Structure, VariantInfo}; - -#[derive(Default, FromVariant)] -#[darling(attributes(parse), default)] -pub struct ParseVariantAttrs { - pub aliases: Option<String>, - pub condition: Option<Path>, -} - -#[derive(Default, FromField)] -#[darling(attributes(parse), default)] -pub struct ParseFieldAttrs { - field_bound: bool, -} - -fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream { - let mut match_arms = TokenStream::new(); - for (rust_name, css_name) in bitflags.single_flags() { - let rust_ident = Ident::new(&rust_name, Span::call_site()); - match_arms.append_all(quote! { - #css_name if result.is_empty() => { - single_flag = true; - Self::#rust_ident - }, - }); - } - - for (rust_name, css_name) in bitflags.mixed_flags() { - let rust_ident = Ident::new(&rust_name, Span::call_site()); - match_arms.append_all(quote! { - #css_name => Self::#rust_ident, - }); - } - - let mut validate_condition = quote! { !result.is_empty() }; - if let Some(ref function) = bitflags.validate_mixed { - validate_condition.append_all(quote! { - && #function(&mut result) - }); - } - - // NOTE(emilio): this loop has this weird structure because we run this code - // to parse stuff like text-decoration-line in the text-decoration - // shorthand, so we need to be a bit careful that we don't error if we don't - // consume the whole thing because we find an invalid identifier or other - // kind of token. Instead, we should leave it unconsumed. - quote! { - let mut result = Self::empty(); - loop { - let mut single_flag = false; - let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| { - Ok(try_match_ident_ignore_ascii_case! { input, - #match_arms - }) - }); - - let flag = match flag { - Ok(flag) => flag, - Err(..) => break, - }; - - if single_flag { - return Ok(flag); - } - - if result.intersects(flag) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); - } - - result.insert(flag); - } - if #validate_condition { - Ok(result) - } else { - Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError)) - } - } -} - -fn parse_non_keyword_variant( - where_clause: &mut Option<syn::WhereClause>, - name: &syn::Ident, - variant: &VariantInfo, - variant_attrs: &CssVariantAttrs, - parse_attrs: &ParseVariantAttrs, - skip_try: bool, -) -> TokenStream { - let bindings = variant.bindings(); - assert!(parse_attrs.aliases.is_none()); - assert!(variant_attrs.function.is_none()); - assert!(variant_attrs.keyword.is_none()); - assert_eq!( - bindings.len(), - 1, - "We only support deriving parse for simple variants" - ); - let variant_name = &variant.ast().ident; - let binding_ast = &bindings[0].ast(); - let ty = &binding_ast.ty; - - if let Some(ref bitflags) = variant_attrs.bitflags { - assert!(skip_try, "Should be the only variant"); - assert!( - parse_attrs.condition.is_none(), - "Should be the only variant" - ); - assert!(where_clause.is_none(), "Generic bitflags?"); - return parse_bitflags(bitflags); - } - - let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast); - if field_attrs.field_bound { - cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse)); - } - - let mut parse = if skip_try { - quote! { - let v = <#ty as crate::parser::Parse>::parse(context, input)?; - return Ok(#name::#variant_name(v)); - } - } else { - quote! { - if let Ok(v) = input.try(|i| <#ty as crate::parser::Parse>::parse(context, i)) { - return Ok(#name::#variant_name(v)); - } - } - }; - - if let Some(ref condition) = parse_attrs.condition { - parse = quote! { - if #condition(context) { - #parse - } - }; - - if skip_try { - // We're the last variant and we can fail to parse due to the - // condition clause. If that happens, we need to return an error. - parse = quote! { - #parse - Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError)) - }; - } - } - - parse -} - -pub fn derive(mut input: DeriveInput) -> TokenStream { - let mut where_clause = input.generics.where_clause.take(); - for param in input.generics.type_params() { - cg::add_predicate( - &mut where_clause, - parse_quote!(#param: crate::parser::Parse), - ); - } - - let name = &input.ident; - let s = Structure::new(&input); - - let mut saw_condition = false; - let mut match_keywords = quote! {}; - let mut non_keywords = vec![]; - - let mut effective_variants = 0; - for variant in s.variants().iter() { - let css_variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&variant.ast()); - if css_variant_attrs.skip { - continue; - } - effective_variants += 1; - - let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast()); - - saw_condition |= parse_attrs.condition.is_some(); - - if !variant.bindings().is_empty() { - non_keywords.push((variant, css_variant_attrs, parse_attrs)); - continue; - } - - let identifier = cg::to_css_identifier( - &css_variant_attrs - .keyword - .unwrap_or_else(|| variant.ast().ident.to_string()), - ); - let ident = &variant.ast().ident; - - let condition = match parse_attrs.condition { - Some(ref p) => quote! { if #p(context) }, - None => quote! {}, - }; - - match_keywords.extend(quote! { - #identifier #condition => Ok(#name::#ident), - }); - - let aliases = match parse_attrs.aliases { - Some(aliases) => aliases, - None => continue, - }; - - for alias in aliases.split(',') { - match_keywords.extend(quote! { - #alias #condition => Ok(#name::#ident), - }); - } - } - - let needs_context = saw_condition || !non_keywords.is_empty(); - - let context_ident = if needs_context { - quote! { context } - } else { - quote! { _ } - }; - - let has_keywords = non_keywords.len() != effective_variants; - - let mut parse_non_keywords = quote! {}; - for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() { - let skip_try = !has_keywords && i == non_keywords.len() - 1; - let parse_variant = parse_non_keyword_variant( - &mut where_clause, - name, - variant, - css_attrs, - parse_attrs, - skip_try, - ); - parse_non_keywords.extend(parse_variant); - } - - let parse_body = if needs_context { - let parse_keywords = if has_keywords { - quote! { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - match_ignore_ascii_case! { &ident, - #match_keywords - _ => Err(location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - )) - } - } - } else { - quote! {} - }; - - quote! { - #parse_non_keywords - #parse_keywords - } - } else { - quote! { Self::parse(input) } - }; - - let has_non_keywords = !non_keywords.is_empty(); - - input.generics.where_clause = where_clause; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let parse_trait_impl = quote! { - impl #impl_generics crate::parser::Parse for #name #ty_generics #where_clause { - #[inline] - fn parse<'i, 't>( - #context_ident: &crate::parser::ParserContext, - input: &mut cssparser::Parser<'i, 't>, - ) -> Result<Self, style_traits::ParseError<'i>> { - #parse_body - } - } - }; - - if needs_context { - return parse_trait_impl; - } - - assert!(!has_non_keywords); - - // TODO(emilio): It'd be nice to get rid of these, but that makes the - // conversion harder... - let methods_impl = quote! { - impl #name { - /// Parse this keyword. - #[inline] - pub fn parse<'i, 't>( - input: &mut cssparser::Parser<'i, 't>, - ) -> Result<Self, style_traits::ParseError<'i>> { - let location = input.current_source_location(); - let ident = input.expect_ident()?; - Self::from_ident(ident.as_ref()).map_err(|()| { - location.new_unexpected_token_error( - cssparser::Token::Ident(ident.clone()) - ) - }) - } - - /// Parse this keyword from a string slice. - #[inline] - pub fn from_ident(ident: &str) -> Result<Self, ()> { - match_ignore_ascii_case! { ident, - #match_keywords - _ => Err(()), - } - } - } - }; - - quote! { - #parse_trait_impl - #methods_impl - } -} diff --git a/components/style_derive/rustfmt.toml b/components/style_derive/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/style_derive/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/style_derive/specified_value_info.rs b/components/style_derive/specified_value_info.rs deleted file mode 100644 index e29f2bc416e..00000000000 --- a/components/style_derive/specified_value_info.rs +++ /dev/null @@ -1,195 +0,0 @@ -/* 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/. */ - -use crate::parse::ParseVariantAttrs; -use crate::to_css::{CssFieldAttrs, CssInputAttrs, CssVariantAttrs}; -use derive_common::cg; -use proc_macro2::TokenStream; -use quote::TokenStreamExt; -use syn::{Data, DeriveInput, Fields, Ident, Type}; - -pub fn derive(mut input: DeriveInput) -> TokenStream { - let css_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input); - let mut types = vec![]; - let mut values = vec![]; - - let input_ident = &input.ident; - let input_name = || cg::to_css_identifier(&input_ident.to_string()); - if let Some(function) = css_attrs.function { - values.push(function.explicit().unwrap_or_else(input_name)); - // If the whole value is wrapped in a function, value types of - // its fields should not be propagated. - } else { - let mut where_clause = input.generics.where_clause.take(); - for param in input.generics.type_params() { - cg::add_predicate( - &mut where_clause, - parse_quote!(#param: style_traits::SpecifiedValueInfo), - ); - } - input.generics.where_clause = where_clause; - - match input.data { - Data::Enum(ref e) => { - for v in e.variants.iter() { - let css_attrs = cg::parse_variant_attrs::<CssVariantAttrs>(&v); - let info_attrs = cg::parse_variant_attrs::<ValueInfoVariantAttrs>(&v); - let parse_attrs = cg::parse_variant_attrs::<ParseVariantAttrs>(&v); - if css_attrs.skip { - continue; - } - if let Some(aliases) = parse_attrs.aliases { - for alias in aliases.split(',') { - values.push(alias.to_string()); - } - } - if let Some(other_values) = info_attrs.other_values { - for value in other_values.split(',') { - values.push(value.to_string()); - } - } - let ident = &v.ident; - let variant_name = || cg::to_css_identifier(&ident.to_string()); - if info_attrs.starts_with_keyword { - values.push(variant_name()); - continue; - } - if let Some(keyword) = css_attrs.keyword { - values.push(keyword); - continue; - } - if let Some(function) = css_attrs.function { - values.push(function.explicit().unwrap_or_else(variant_name)); - } else if !derive_struct_fields(&v.fields, &mut types, &mut values) { - values.push(variant_name()); - } - } - }, - Data::Struct(ref s) => { - if let Some(ref bitflags) = css_attrs.bitflags { - for (_rust_name, css_name) in bitflags.single_flags() { - values.push(css_name) - } - for (_rust_name, css_name) in bitflags.mixed_flags() { - values.push(css_name) - } - } else if !derive_struct_fields(&s.fields, &mut types, &mut values) { - values.push(input_name()); - } - }, - Data::Union(_) => unreachable!("union is not supported"), - } - } - - let info_attrs = cg::parse_input_attrs::<ValueInfoInputAttrs>(&input); - if let Some(other_values) = info_attrs.other_values { - for value in other_values.split(',') { - values.push(value.to_string()); - } - } - - let mut types_value = quote!(0); - types_value.append_all(types.iter().map(|ty| { - quote! { - | <#ty as style_traits::SpecifiedValueInfo>::SUPPORTED_TYPES - } - })); - - let mut nested_collects = quote!(); - nested_collects.append_all(types.iter().map(|ty| { - quote! { - <#ty as style_traits::SpecifiedValueInfo>::collect_completion_keywords(_f); - } - })); - - if let Some(ty) = info_attrs.ty { - types_value.append_all(quote! { - | style_traits::CssType::#ty - }); - } - - let append_values = if values.is_empty() { - quote!() - } else { - let mut value_list = quote!(); - value_list.append_separated(values.iter(), quote! { , }); - quote! { _f(&[#value_list]); } - }; - - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - quote! { - impl #impl_generics style_traits::SpecifiedValueInfo for #name #ty_generics - #where_clause - { - const SUPPORTED_TYPES: u8 = #types_value; - - fn collect_completion_keywords(_f: &mut FnMut(&[&'static str])) { - #nested_collects - #append_values - } - } - } -} - -/// Derive from the given fields. Return false if the fields is a Unit, -/// true otherwise. -fn derive_struct_fields<'a>( - fields: &'a Fields, - types: &mut Vec<&'a Type>, - values: &mut Vec<String>, -) -> bool { - let fields = match *fields { - Fields::Unit => return false, - Fields::Named(ref fields) => fields.named.iter(), - Fields::Unnamed(ref fields) => fields.unnamed.iter(), - }; - types.extend(fields.filter_map(|field| { - let info_attrs = cg::parse_field_attrs::<ValueInfoFieldAttrs>(field); - if let Some(other_values) = info_attrs.other_values { - for value in other_values.split(',') { - values.push(value.to_string()); - } - } - let css_attrs = cg::parse_field_attrs::<CssFieldAttrs>(field); - if css_attrs.represents_keyword { - let ident = field - .ident - .as_ref() - .expect("only named field should use represents_keyword"); - values.push(cg::to_css_identifier(&ident.to_string())); - return None; - } - if let Some(if_empty) = css_attrs.if_empty { - values.push(if_empty); - } - if !css_attrs.skip { - Some(&field.ty) - } else { - None - } - })); - true -} - -#[derive(Default, FromDeriveInput)] -#[darling(attributes(value_info), default)] -struct ValueInfoInputAttrs { - ty: Option<Ident>, - other_values: Option<String>, -} - -#[derive(Default, FromVariant)] -#[darling(attributes(value_info), default)] -struct ValueInfoVariantAttrs { - starts_with_keyword: bool, - other_values: Option<String>, -} - -#[derive(Default, FromField)] -#[darling(attributes(value_info), default)] -struct ValueInfoFieldAttrs { - other_values: Option<String>, -} diff --git a/components/style_derive/to_animated_value.rs b/components/style_derive/to_animated_value.rs deleted file mode 100644 index 45282f0c448..00000000000 --- a/components/style_derive/to_animated_value.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* 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/. */ - -use proc_macro2::TokenStream; -use syn::DeriveInput; -use synstructure::BindStyle; -use to_computed_value; - -pub fn derive(input: DeriveInput) -> TokenStream { - let trait_impl = |from_body, to_body| { - quote! { - #[inline] - fn from_animated_value(from: Self::AnimatedValue) -> Self { - #from_body - } - - #[inline] - fn to_animated_value(self) -> Self::AnimatedValue { - #to_body - } - } - }; - - to_computed_value::derive_to_value( - input, - parse_quote!(crate::values::animated::ToAnimatedValue), - parse_quote!(AnimatedValue), - BindStyle::Move, - |_| Default::default(), - |binding| quote!(crate::values::animated::ToAnimatedValue::from_animated_value(#binding)), - |binding| quote!(crate::values::animated::ToAnimatedValue::to_animated_value(#binding)), - trait_impl, - ) -} diff --git a/components/style_derive/to_animated_zero.rs b/components/style_derive/to_animated_zero.rs deleted file mode 100644 index 008e94cbcfb..00000000000 --- a/components/style_derive/to_animated_zero.rs +++ /dev/null @@ -1,65 +0,0 @@ -/* 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/. */ - -use crate::animate::{AnimationFieldAttrs, AnimationInputAttrs, AnimationVariantAttrs}; -use derive_common::cg; -use proc_macro2::TokenStream; -use quote::TokenStreamExt; -use syn; -use synstructure; - -pub fn derive(mut input: syn::DeriveInput) -> TokenStream { - let animation_input_attrs = cg::parse_input_attrs::<AnimationInputAttrs>(&input); - let no_bound = animation_input_attrs.no_bound.unwrap_or_default(); - let mut where_clause = input.generics.where_clause.take(); - for param in input.generics.type_params() { - if !no_bound.iter().any(|name| name.is_ident(¶m.ident)) { - cg::add_predicate( - &mut where_clause, - parse_quote!(#param: crate::values::animated::ToAnimatedZero), - ); - } - } - - let to_body = synstructure::Structure::new(&input).each_variant(|variant| { - let attrs = cg::parse_variant_attrs_from_ast::<AnimationVariantAttrs>(&variant.ast()); - if attrs.error { - return Some(quote! { Err(()) }); - } - let (mapped, mapped_bindings) = cg::value(variant, "mapped"); - let bindings_pairs = variant.bindings().iter().zip(mapped_bindings); - let mut computations = quote!(); - computations.append_all(bindings_pairs.map(|(binding, mapped_binding)| { - let field_attrs = cg::parse_field_attrs::<AnimationFieldAttrs>(&binding.ast()); - if field_attrs.constant { - quote! { - let #mapped_binding = std::clone::Clone::clone(#binding); - } - } else { - quote! { - let #mapped_binding = - crate::values::animated::ToAnimatedZero::to_animated_zero(#binding)?; - } - } - })); - computations.append_all(quote! { Ok(#mapped) }); - Some(computations) - }); - input.generics.where_clause = where_clause; - - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - quote! { - impl #impl_generics crate::values::animated::ToAnimatedZero for #name #ty_generics #where_clause { - #[allow(unused_variables)] - #[inline] - fn to_animated_zero(&self) -> Result<Self, ()> { - match *self { - #to_body - } - } - } - } -} diff --git a/components/style_derive/to_computed_value.rs b/components/style_derive/to_computed_value.rs deleted file mode 100644 index 5e0f595c6b9..00000000000 --- a/components/style_derive/to_computed_value.rs +++ /dev/null @@ -1,205 +0,0 @@ -/* 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/. */ - -use derive_common::cg; -use proc_macro2::TokenStream; -use syn::{DeriveInput, Ident, Path}; -use synstructure::{BindStyle, BindingInfo}; - -pub fn derive_to_value( - mut input: DeriveInput, - trait_path: Path, - output_type_name: Ident, - bind_style: BindStyle, - // Returns whether to apply the field bound for a given item. - mut binding_attrs: impl FnMut(&BindingInfo) -> ToValueAttrs, - // Returns a token stream of the form: trait_path::from_foo(#binding) - mut call_from: impl FnMut(&BindingInfo) -> TokenStream, - mut call_to: impl FnMut(&BindingInfo) -> TokenStream, - // Returns a tokenstream of the form: - // fn from_function_syntax(foobar) -> Baz { - // #first_arg - // } - // - // fn to_function_syntax(foobar) -> Baz { - // #second_arg - // } - mut trait_impl: impl FnMut(TokenStream, TokenStream) -> TokenStream, -) -> TokenStream { - let name = &input.ident; - - let mut where_clause = input.generics.where_clause.take(); - cg::propagate_clauses_to_output_type( - &mut where_clause, - &input.generics, - &trait_path, - &output_type_name, - ); - - let moves = match bind_style { - BindStyle::Move | BindStyle::MoveMut => true, - BindStyle::Ref | BindStyle::RefMut => false, - }; - - let params = input.generics.type_params().collect::<Vec<_>>(); - for param in ¶ms { - cg::add_predicate(&mut where_clause, parse_quote!(#param: #trait_path)); - } - - let computed_value_type = cg::fmap_trait_output(&input, &trait_path, &output_type_name); - - let mut add_field_bound = |binding: &BindingInfo| { - let ty = &binding.ast().ty; - - let output_type = cg::map_type_params( - ty, - ¶ms, - &computed_value_type, - &mut |ident| parse_quote!(<#ident as #trait_path>::#output_type_name), - ); - - cg::add_predicate( - &mut where_clause, - parse_quote!( - #ty: #trait_path<#output_type_name = #output_type> - ), - ); - }; - - let (to_body, from_body) = if params.is_empty() { - let mut s = synstructure::Structure::new(&input); - s.variants_mut().iter_mut().for_each(|v| { - v.bind_with(|_| bind_style); - }); - - for variant in s.variants() { - for binding in variant.bindings() { - let attrs = binding_attrs(&binding); - assert!( - !attrs.field_bound, - "It is default on a non-generic implementation", - ); - if !attrs.no_field_bound { - // Add field bounds to all bindings except the manually - // excluded. This ensures the correctness of the clone() / - // move based implementation. - add_field_bound(binding); - } - } - } - - let to_body = if moves { - quote! { self } - } else { - quote! { std::clone::Clone::clone(self) } - }; - - let from_body = if moves { - quote! { from } - } else { - quote! { std::clone::Clone::clone(from) } - }; - - (to_body, from_body) - } else { - let to_body = cg::fmap_match(&input, bind_style, |binding| { - let attrs = binding_attrs(&binding); - assert!( - !attrs.no_field_bound, - "It doesn't make sense on a generic implementation" - ); - if attrs.field_bound { - add_field_bound(&binding); - } - call_to(&binding) - }); - - let from_body = cg::fmap_match(&input, bind_style, |binding| call_from(&binding)); - - let self_ = if moves { - quote! { self } - } else { - quote! { *self } - }; - let from_ = if moves { - quote! { from } - } else { - quote! { *from } - }; - - let to_body = quote! { - match #self_ { - #to_body - } - }; - - let from_body = quote! { - match #from_ { - #from_body - } - }; - - (to_body, from_body) - }; - - input.generics.where_clause = where_clause; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let impl_ = trait_impl(from_body, to_body); - - quote! { - impl #impl_generics #trait_path for #name #ty_generics #where_clause { - type #output_type_name = #computed_value_type; - - #impl_ - } - } -} - -pub fn derive(input: DeriveInput) -> TokenStream { - let trait_impl = |from_body, to_body| { - quote! { - #[inline] - fn from_computed_value(from: &Self::ComputedValue) -> Self { - #from_body - } - - #[allow(unused_variables)] - #[inline] - fn to_computed_value(&self, context: &crate::values::computed::Context) -> Self::ComputedValue { - #to_body - } - } - }; - - derive_to_value( - input, - parse_quote!(crate::values::computed::ToComputedValue), - parse_quote!(ComputedValue), - BindStyle::Ref, - |binding| { - let attrs = cg::parse_field_attrs::<ComputedValueAttrs>(&binding.ast()); - ToValueAttrs { - field_bound: attrs.field_bound, - no_field_bound: attrs.no_field_bound, - } - }, - |binding| quote!(crate::values::computed::ToComputedValue::from_computed_value(#binding)), - |binding| quote!(crate::values::computed::ToComputedValue::to_computed_value(#binding, context)), - trait_impl, - ) -} - -#[derive(Default)] -pub struct ToValueAttrs { - pub field_bound: bool, - pub no_field_bound: bool, -} - -#[derive(Default, FromField)] -#[darling(attributes(compute), default)] -struct ComputedValueAttrs { - field_bound: bool, - no_field_bound: bool, -} diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs deleted file mode 100644 index aa335366485..00000000000 --- a/components/style_derive/to_css.rs +++ /dev/null @@ -1,396 +0,0 @@ -/* 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/. */ - -use darling::util::Override; -use derive_common::cg; -use proc_macro2::{Span, TokenStream}; -use quote::{ToTokens, TokenStreamExt}; -use syn::{self, Data, Ident, Path, WhereClause}; -use synstructure::{BindingInfo, Structure, VariantInfo}; - -fn derive_bitflags(input: &syn::DeriveInput, bitflags: &CssBitflagAttrs) -> TokenStream { - let name = &input.ident; - let mut body = TokenStream::new(); - for (rust_name, css_name) in bitflags.single_flags() { - let rust_ident = Ident::new(&rust_name, Span::call_site()); - body.append_all(quote! { - if *self == Self::#rust_ident { - return dest.write_str(#css_name); - } - }); - } - - body.append_all(quote! { - let mut has_any = false; - }); - - if bitflags.overlapping_bits { - body.append_all(quote! { - let mut serialized = Self::empty(); - }); - } - - for (rust_name, css_name) in bitflags.mixed_flags() { - let rust_ident = Ident::new(&rust_name, Span::call_site()); - let serialize = quote! { - if has_any { - dest.write_char(' ')?; - } - has_any = true; - dest.write_str(#css_name)?; - }; - if bitflags.overlapping_bits { - body.append_all(quote! { - if self.contains(Self::#rust_ident) && !serialized.intersects(Self::#rust_ident) { - #serialize - serialized.insert(Self::#rust_ident); - } - }); - } else { - body.append_all(quote! { - if self.intersects(Self::#rust_ident) { - #serialize - } - }); - } - } - - body.append_all(quote! { - Ok(()) - }); - - quote! { - impl style_traits::ToCss for #name { - #[allow(unused_variables)] - #[inline] - fn to_css<W>( - &self, - dest: &mut style_traits::CssWriter<W>, - ) -> std::fmt::Result - where - W: std::fmt::Write, - { - #body - } - } - } -} - -pub fn derive(mut input: syn::DeriveInput) -> TokenStream { - let mut where_clause = input.generics.where_clause.take(); - for param in input.generics.type_params() { - cg::add_predicate(&mut where_clause, parse_quote!(#param: style_traits::ToCss)); - } - - let input_attrs = cg::parse_input_attrs::<CssInputAttrs>(&input); - if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() { - assert!( - input_attrs.function.is_none(), - "#[css(function)] is not allowed on enums or bitflags" - ); - assert!( - !input_attrs.comma, - "#[css(comma)] is not allowed on enums or bitflags" - ); - } - - if let Some(ref bitflags) = input_attrs.bitflags { - assert!( - !input_attrs.derive_debug, - "Bitflags can derive debug on their own" - ); - assert!(where_clause.is_none(), "Generic bitflags?"); - return derive_bitflags(&input, bitflags); - } - - let match_body = { - let s = Structure::new(&input); - s.each_variant(|variant| derive_variant_arm(variant, &mut where_clause)) - }; - input.generics.where_clause = where_clause; - - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let mut impls = quote! { - impl #impl_generics style_traits::ToCss for #name #ty_generics #where_clause { - #[allow(unused_variables)] - #[inline] - fn to_css<W>( - &self, - dest: &mut style_traits::CssWriter<W>, - ) -> std::fmt::Result - where - W: std::fmt::Write, - { - match *self { - #match_body - } - } - } - }; - - if input_attrs.derive_debug { - impls.append_all(quote! { - impl #impl_generics std::fmt::Debug for #name #ty_generics #where_clause { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - style_traits::ToCss::to_css( - self, - &mut style_traits::CssWriter::new(f), - ) - } - } - }); - } - - impls -} - -fn derive_variant_arm(variant: &VariantInfo, generics: &mut Option<WhereClause>) -> TokenStream { - let bindings = variant.bindings(); - let identifier = cg::to_css_identifier(&variant.ast().ident.to_string()); - let ast = variant.ast(); - let variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&ast); - let separator = if variant_attrs.comma { ", " } else { " " }; - - if variant_attrs.skip { - return quote!(Ok(())); - } - if variant_attrs.dimension { - assert_eq!(bindings.len(), 1); - assert!( - variant_attrs.function.is_none() && variant_attrs.keyword.is_none(), - "That makes no sense" - ); - } - - let mut expr = if let Some(keyword) = variant_attrs.keyword { - assert!(bindings.is_empty()); - quote! { - std::fmt::Write::write_str(dest, #keyword) - } - } else if !bindings.is_empty() { - derive_variant_fields_expr(bindings, generics, separator) - } else { - quote! { - std::fmt::Write::write_str(dest, #identifier) - } - }; - - if variant_attrs.dimension { - expr = quote! { - #expr?; - std::fmt::Write::write_str(dest, #identifier) - } - } else if let Some(function) = variant_attrs.function { - let mut identifier = function.explicit().map_or(identifier, |name| name); - identifier.push('('); - expr = quote! { - std::fmt::Write::write_str(dest, #identifier)?; - #expr?; - std::fmt::Write::write_str(dest, ")") - } - } - expr -} - -fn derive_variant_fields_expr( - bindings: &[BindingInfo], - where_clause: &mut Option<WhereClause>, - separator: &str, -) -> TokenStream { - let mut iter = bindings - .iter() - .filter_map(|binding| { - let attrs = cg::parse_field_attrs::<CssFieldAttrs>(&binding.ast()); - if attrs.skip { - return None; - } - Some((binding, attrs)) - }) - .peekable(); - - let (first, attrs) = match iter.next() { - Some(pair) => pair, - None => return quote! { Ok(()) }, - }; - if attrs.field_bound { - let ty = &first.ast().ty; - // TODO(emilio): IntoIterator might not be enough for every type of - // iterable thing (like ArcSlice<> or what not). We might want to expose - // an `item = "T"` attribute to handle that in the future. - let predicate = if attrs.iterable { - parse_quote!(<#ty as IntoIterator>::Item: style_traits::ToCss) - } else { - parse_quote!(#ty: style_traits::ToCss) - }; - cg::add_predicate(where_clause, predicate); - } - if !attrs.iterable && iter.peek().is_none() { - let mut expr = quote! { style_traits::ToCss::to_css(#first, dest) }; - if let Some(condition) = attrs.skip_if { - expr = quote! { - if !#condition(#first) { - #expr - } - } - } - - if let Some(condition) = attrs.contextual_skip_if { - expr = quote! { - if !#condition(#(#bindings), *) { - #expr - } - } - } - return expr; - } - - let mut expr = derive_single_field_expr(first, attrs, where_clause, bindings); - for (binding, attrs) in iter { - derive_single_field_expr(binding, attrs, where_clause, bindings).to_tokens(&mut expr) - } - - quote! {{ - let mut writer = style_traits::values::SequenceWriter::new(dest, #separator); - #expr - Ok(()) - }} -} - -fn derive_single_field_expr( - field: &BindingInfo, - attrs: CssFieldAttrs, - where_clause: &mut Option<WhereClause>, - bindings: &[BindingInfo], -) -> TokenStream { - let mut expr = if attrs.iterable { - if let Some(if_empty) = attrs.if_empty { - return quote! { - { - let mut iter = #field.iter().peekable(); - if iter.peek().is_none() { - writer.raw_item(#if_empty)?; - } else { - for item in iter { - writer.item(&item)?; - } - } - } - }; - } - quote! { - for item in #field.iter() { - writer.item(&item)?; - } - } - } else if attrs.represents_keyword { - let ident = field - .ast() - .ident - .as_ref() - .expect("Unnamed field with represents_keyword?"); - let ident = cg::to_css_identifier(&ident.to_string()).replace("_", "-"); - quote! { - if *#field { - writer.raw_item(#ident)?; - } - } - } else { - if attrs.field_bound { - let ty = &field.ast().ty; - cg::add_predicate(where_clause, parse_quote!(#ty: style_traits::ToCss)); - } - quote! { writer.item(#field)?; } - }; - - if let Some(condition) = attrs.skip_if { - expr = quote! { - if !#condition(#field) { - #expr - } - } - } - - if let Some(condition) = attrs.contextual_skip_if { - expr = quote! { - if !#condition(#(#bindings), *) { - #expr - } - } - } - - expr -} - -#[derive(Default, FromMeta)] -#[darling(default)] -pub struct CssBitflagAttrs { - /// Flags that can only go on their own, comma-separated. - pub single: Option<String>, - /// Flags that can go mixed with each other, comma-separated. - pub mixed: Option<String>, - /// Extra validation of the resulting mixed flags. - pub validate_mixed: Option<Path>, - /// Whether there are overlapping bits we need to take care of when - /// serializing. - pub overlapping_bits: bool, -} - -impl CssBitflagAttrs { - /// Returns a vector of (rust_name, css_name) of a given flag list. - fn names(s: &Option<String>) -> Vec<(String, String)> { - let s = match s { - Some(s) => s, - None => return vec![], - }; - s.split(',') - .map(|css_name| (cg::to_scream_case(css_name), css_name.to_owned())) - .collect() - } - - pub fn single_flags(&self) -> Vec<(String, String)> { - Self::names(&self.single) - } - - pub fn mixed_flags(&self) -> Vec<(String, String)> { - Self::names(&self.mixed) - } -} - -#[derive(Default, FromDeriveInput)] -#[darling(attributes(css), default)] -pub struct CssInputAttrs { - pub derive_debug: bool, - // Here because structs variants are also their whole type definition. - pub function: Option<Override<String>>, - // Here because structs variants are also their whole type definition. - pub comma: bool, - pub bitflags: Option<CssBitflagAttrs>, -} - -#[derive(Default, FromVariant)] -#[darling(attributes(css), default)] -pub struct CssVariantAttrs { - pub function: Option<Override<String>>, - // Here because structs variants are also their whole type definition. - pub derive_debug: bool, - pub comma: bool, - pub bitflags: Option<CssBitflagAttrs>, - pub dimension: bool, - pub keyword: Option<String>, - pub skip: bool, -} - -#[derive(Default, FromField)] -#[darling(attributes(css), default)] -pub struct CssFieldAttrs { - pub if_empty: Option<String>, - pub field_bound: bool, - pub iterable: bool, - pub skip: bool, - pub represents_keyword: bool, - pub contextual_skip_if: Option<Path>, - pub skip_if: Option<Path>, -} diff --git a/components/style_derive/to_resolved_value.rs b/components/style_derive/to_resolved_value.rs deleted file mode 100644 index e049f91152a..00000000000 --- a/components/style_derive/to_resolved_value.rs +++ /dev/null @@ -1,52 +0,0 @@ -/* 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/. */ - -use derive_common::cg; -use proc_macro2::TokenStream; -use syn::DeriveInput; -use synstructure::BindStyle; -use to_computed_value; - -pub fn derive(input: DeriveInput) -> TokenStream { - let trait_impl = |from_body, to_body| { - quote! { - #[inline] - fn from_resolved_value(from: Self::ResolvedValue) -> Self { - #from_body - } - - #[inline] - fn to_resolved_value( - self, - context: &crate::values::resolved::Context, - ) -> Self::ResolvedValue { - #to_body - } - } - }; - - to_computed_value::derive_to_value( - input, - parse_quote!(crate::values::resolved::ToResolvedValue), - parse_quote!(ResolvedValue), - BindStyle::Move, - |binding| { - let attrs = cg::parse_field_attrs::<ResolvedValueAttrs>(&binding.ast()); - to_computed_value::ToValueAttrs { - field_bound: attrs.field_bound, - no_field_bound: attrs.no_field_bound, - } - }, - |binding| quote!(crate::values::resolved::ToResolvedValue::from_resolved_value(#binding)), - |binding| quote!(crate::values::resolved::ToResolvedValue::to_resolved_value(#binding, context)), - trait_impl, - ) -} - -#[derive(Default, FromField)] -#[darling(attributes(resolve), default)] -struct ResolvedValueAttrs { - field_bound: bool, - no_field_bound: bool, -} diff --git a/components/style_static_prefs/Cargo.toml b/components/style_static_prefs/Cargo.toml deleted file mode 100644 index 7a42225d8e9..00000000000 --- a/components/style_static_prefs/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "static_prefs" -version = "0.1.0" -edition = "2021" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false diff --git a/components/style_static_prefs/src/lib.rs b/components/style_static_prefs/src/lib.rs deleted file mode 100644 index 818d80226c1..00000000000 --- a/components/style_static_prefs/src/lib.rs +++ /dev/null @@ -1,162 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A list of static preferences exposed to the style crate. These should -//! be kept sync with the preferences used by the style. -#[macro_export] -macro_rules! pref { - ("browser.display.permit_backplate") => { - false - }; - ("browser.display.use_document_fonts") => { - false - }; - ("dom.customHighlightAPI.enabled") => { - false - }; - ("dom.element.popover.enabled") => { - false - }; - ("gfx.font_rendering.opentype_svg.enabled") => { - false - }; - ("layout.css.color-mix.enabled") => { - true - }; - ("layout.css.contain-intrinsic-size.enabled") => { - false - }; - ("layout.css.container-queries.enabled") => { - false - }; - ("layout.css.content-visibility.enabled") => { - false - }; - ("layout.css.control-characters.visible") => { - false - }; - ("layout.css.cross-fade.enabled") => { - false - }; - ("layout.css.element-content-none.enabled") => { - false - }; - ("layout.css.fit-content-function.enabled") => { - false - }; - ("layout.css.font-palette.enabled") => { - false - }; - ("layout.css.font-tech.enabled") => { - false - }; - ("layout.css.font-variant-emoji.enabled") => { - false - }; - ("layout.css.font-variations.enabled") => { - false - }; - ("layout.css.forced-color-adjust.enabled") => { - false - }; - ("layout.css.forced-colors.enabled") => { - false - }; - ("layout.css.grid-template-masonry-value.enabled") => { - false - }; - ("layout.css.has-selector.enabled") => { - false - }; - ("layout.css.import-supports.enabled") => { - false - }; - ("layout.css.inverted-colors.enabled") => { - false - }; - ("layout.css.marker.restricted") => { - false - }; - ("layout.css.math-depth.enabled") => { - false - }; - ("layout.css.math-style.enabled") => { - false - }; - ("layout.css.more_color_4.enabled") => { - true - }; - ("layout.css.motion-path-offset-position.enabled") => { - false - }; - ("layout.css.motion-path-ray.enabled") => { - false - }; - ("layout.css.moz-control-character-visibility.enabled") => { - false - }; - ("layout.css.nesting.enabled") => { - false - }; - ("layout.css.overflow-moz-hidden-unscrollable.enabled") => { - false - }; - ("layout.css.overflow-overlay.enabled") => { - false - }; - ("layout.css.page-orientation.enabled") => { - false - }; - ("layout.css.prefers-contrast.enabled") => { - false - }; - ("layout.css.prefers-reduced-transparency.enabled") => { - false - }; - ("layout.css.properties-and-values.enabled") => { - false - }; - ("layout.css.scroll-driven-animations.enabled") => { - false - }; - ("layout.css.size-adjust.enabled") => { - false - }; - ("layout.css.stylo-local-work-queue.in-main-thread") => { - 32 - }; - ("layout.css.stylo-local-work-queue.in-worker") => { - 0 - }; - ("layout.css.stylo-threads") => { - false - }; - ("layout.css.stylo-work-unit-size") => { - 16 - }; - ("layout.css.system-ui.enabled") => { - false - }; - ("layout.css.nan-inf.enabled") => { - false - }; - ("layout.css.trig.enabled") => { - false - }; - ("layout.css.round.enabled") => { - false - }; - ("layout.css.mod-rem.enabled") => { - false - }; - ("layout.css.exp.enabled") => { - false - }; - ("layout.css.bucket-attribute-names.enabled") => { - false - }; - ("layout.css.font-size-adjust.basis.enabled") => { - false - }; -} diff --git a/components/style_traits/Cargo.toml b/components/style_traits/Cargo.toml deleted file mode 100644 index 3b9ecd7aaed..00000000000 --- a/components/style_traits/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "style_traits" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -[lib] -name = "style_traits" -path = "lib.rs" - -[features] -servo = ["servo_atoms", "cssparser/serde", "webrender_api", "url", "euclid/serde"] -gecko = [] - -[dependencies] -app_units = "0.7" -bitflags = "1.0" -cssparser = { workspace = true } -euclid = "0.22" -lazy_static = "1" -malloc_size_of = { path = "../malloc_size_of" } -malloc_size_of_derive = "0.1" -selectors = { path = "../selectors" } -serde = "1.0" -servo_arc = { path = "../servo_arc" } -servo_atoms = { path = "../atoms", optional = true } -size_of_test = { path = "../size_of_test" } -to_shmem = { path = "../to_shmem" } -to_shmem_derive = { path = "../to_shmem_derive" } -url = { workspace = true, optional = true } -webrender_api = { workspace = true, optional = true } diff --git a/components/style_traits/arc_slice.rs b/components/style_traits/arc_slice.rs deleted file mode 100644 index 1f10ed9455c..00000000000 --- a/components/style_traits/arc_slice.rs +++ /dev/null @@ -1,163 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -//! A thin atomically-reference-counted slice. - -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use servo_arc::ThinArc; -use std::ops::Deref; -use std::ptr::NonNull; -use std::{iter, mem}; - -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalSizeOf}; - -/// A canary that we stash in ArcSlices. -/// -/// Given we cannot use a zero-sized-type for the header, since well, C++ -/// doesn't have zsts, and we want to use cbindgen for this type, we may as well -/// assert some sanity at runtime. -/// -/// We use an u64, to guarantee that we can use a single singleton for every -/// empty slice, even if the types they hold are aligned differently. -const ARC_SLICE_CANARY: u64 = 0xf3f3f3f3f3f3f3f3; - -/// A wrapper type for a refcounted slice using ThinArc. -/// -/// cbindgen:derive-eq=false -/// cbindgen:derive-neq=false -#[repr(C)] -#[derive(Debug, Eq, PartialEq, ToShmem)] -pub struct ArcSlice<T>(#[shmem(field_bound)] ThinArc<u64, T>); - -impl<T> Deref for ArcSlice<T> { - type Target = [T]; - - #[inline] - fn deref(&self) -> &Self::Target { - debug_assert_eq!(self.0.header.header, ARC_SLICE_CANARY); - &self.0.slice - } -} - -impl<T> Clone for ArcSlice<T> { - fn clone(&self) -> Self { - ArcSlice(self.0.clone()) - } -} - -lazy_static! { - // ThinArc doesn't support alignments greater than align_of::<u64>. - static ref EMPTY_ARC_SLICE: ArcSlice<u64> = { - ArcSlice::from_iter_leaked(iter::empty()) - }; -} - -impl<T> Default for ArcSlice<T> { - #[allow(unsafe_code)] - fn default() -> Self { - debug_assert!( - mem::align_of::<T>() <= mem::align_of::<u64>(), - "Need to increase the alignment of EMPTY_ARC_SLICE" - ); - unsafe { - let empty: ArcSlice<_> = EMPTY_ARC_SLICE.clone(); - let empty: Self = mem::transmute(empty); - debug_assert_eq!(empty.len(), 0); - empty - } - } -} - -impl<T: Serialize> Serialize for ArcSlice<T> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - self.deref().serialize(serializer) - } -} - -impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcSlice<T> { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let r = Vec::deserialize(deserializer)?; - Ok(ArcSlice::from_iter(r.into_iter())) - } -} - -impl<T> ArcSlice<T> { - /// Creates an Arc for a slice using the given iterator to generate the - /// slice. - #[inline] - pub fn from_iter<I>(items: I) -> Self - where - I: Iterator<Item = T> + ExactSizeIterator, - { - if items.len() == 0 { - return Self::default(); - } - ArcSlice(ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items)) - } - - /// Creates an Arc for a slice using the given iterator to generate the - /// slice, and marks the arc as intentionally leaked from the refcount - /// logging point of view. - #[inline] - pub fn from_iter_leaked<I>(items: I) -> Self - where - I: Iterator<Item = T> + ExactSizeIterator, - { - let thin_arc = ThinArc::from_header_and_iter(ARC_SLICE_CANARY, items); - thin_arc.with_arc(|a| a.mark_as_intentionally_leaked()); - ArcSlice(thin_arc) - } - - /// Creates a value that can be passed via FFI, and forgets this value - /// altogether. - #[inline] - #[allow(unsafe_code)] - pub fn forget(self) -> ForgottenArcSlicePtr<T> { - let ret = unsafe { - ForgottenArcSlicePtr(NonNull::new_unchecked(self.0.ptr() as *const _ as *mut _)) - }; - mem::forget(self); - ret - } - - /// Leaks an empty arc slice pointer, and returns it. Only to be used to - /// construct ArcSlices from FFI. - #[inline] - pub fn leaked_empty_ptr() -> *mut std::os::raw::c_void { - let empty: ArcSlice<_> = EMPTY_ARC_SLICE.clone(); - let ptr = empty.0.ptr(); - std::mem::forget(empty); - ptr as *mut _ - } - - /// Returns whether there's only one reference to this ArcSlice. - pub fn is_unique(&self) -> bool { - self.0.with_arc(|arc| arc.is_unique()) - } -} - -impl<T: MallocSizeOf> MallocUnconditionalSizeOf for ArcSlice<T> { - #[allow(unsafe_code)] - fn unconditional_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut size = unsafe { ops.malloc_size_of(self.0.heap_ptr()) }; - for el in self.iter() { - size += el.size_of(ops); - } - size - } -} - -/// The inner pointer of an ArcSlice<T>, to be sent via FFI. -/// The type of the pointer is a bit of a lie, we just want to preserve the type -/// but these pointers cannot be constructed outside of this crate, so we're -/// good. -#[repr(C)] -pub struct ForgottenArcSlicePtr<T>(NonNull<T>); diff --git a/components/style_traits/dom.rs b/components/style_traits/dom.rs deleted file mode 100644 index d8c660d2be9..00000000000 --- a/components/style_traits/dom.rs +++ /dev/null @@ -1,216 +0,0 @@ -/* 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/. */ - -//! Types used to access the DOM from style calculation. - -use malloc_size_of::malloc_size_of_is_0; - -/// An opaque handle to a node, which, unlike UnsafeNode, cannot be transformed -/// back into a non-opaque representation. The only safe operation that can be -/// performed on this node is to compare it to another opaque handle or to another -/// OpaqueNode. -/// -/// Layout and Graphics use this to safely represent nodes for comparison purposes. -/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout -/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for -/// locality reasons. Using `OpaqueNode` enforces this invariant. -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -pub struct OpaqueNode(pub usize); - -impl OpaqueNode { - /// Returns the address of this node, for debugging purposes. - #[inline] - pub fn id(&self) -> usize { - self.0 - } -} - -// DOM types to be shared between Rust and C++. - -bitflags! { - /// Event-based element states. - #[repr(C)] - pub struct ElementState: u64 { - /// The mouse is down on this element. - /// <https://html.spec.whatwg.org/multipage/#selector-active> - /// FIXME(#7333): set/unset this when appropriate - const ACTIVE = 1 << 0; - /// This element has focus. - /// <https://html.spec.whatwg.org/multipage/#selector-focus> - const FOCUS = 1 << 1; - /// The mouse is hovering over this element. - /// <https://html.spec.whatwg.org/multipage/#selector-hover> - const HOVER = 1 << 2; - /// Content is enabled (and can be disabled). - /// <http://www.whatwg.org/html/#selector-enabled> - const ENABLED = 1 << 3; - /// Content is disabled. - /// <http://www.whatwg.org/html/#selector-disabled> - const DISABLED = 1 << 4; - /// Content is checked. - /// <https://html.spec.whatwg.org/multipage/#selector-checked> - const CHECKED = 1 << 5; - /// <https://html.spec.whatwg.org/multipage/#selector-indeterminate> - const INDETERMINATE = 1 << 6; - /// <https://html.spec.whatwg.org/multipage/#selector-placeholder-shown> - const PLACEHOLDER_SHOWN = 1 << 7; - /// <https://html.spec.whatwg.org/multipage/#selector-target> - const URLTARGET = 1 << 8; - /// <https://fullscreen.spec.whatwg.org/#%3Afullscreen-pseudo-class> - const FULLSCREEN = 1 << 9; - /// <https://html.spec.whatwg.org/multipage/#selector-valid> - const VALID = 1 << 10; - /// <https://html.spec.whatwg.org/multipage/#selector-invalid> - const INVALID = 1 << 11; - /// <https://drafts.csswg.org/selectors-4/#user-valid-pseudo> - const USER_VALID = 1 << 12; - /// <https://drafts.csswg.org/selectors-4/#user-invalid-pseudo> - const USER_INVALID = 1 << 13; - /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-broken - const BROKEN = 1 << 14; - /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-loading - const LOADING = 1 << 15; - /// <https://html.spec.whatwg.org/multipage/#selector-required> - const REQUIRED = 1 << 16; - /// <https://html.spec.whatwg.org/multipage/#selector-optional> - /// We use an underscore to workaround a silly windows.h define. - const OPTIONAL_ = 1 << 17; - /// <https://html.spec.whatwg.org/multipage/#selector-defined> - const DEFINED = 1 << 18; - /// <https://html.spec.whatwg.org/multipage/#selector-visited> - const VISITED = 1 << 19; - /// <https://html.spec.whatwg.org/multipage/#selector-link> - const UNVISITED = 1 << 20; - /// <https://drafts.csswg.org/selectors-4/#the-any-link-pseudo> - const VISITED_OR_UNVISITED = Self::VISITED.bits | Self::UNVISITED.bits; - /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-drag-over - const DRAGOVER = 1 << 21; - /// <https://html.spec.whatwg.org/multipage/#selector-in-range> - const INRANGE = 1 << 22; - /// <https://html.spec.whatwg.org/multipage/#selector-out-of-range> - const OUTOFRANGE = 1 << 23; - /// <https://html.spec.whatwg.org/multipage/#selector-read-only> - const READONLY = 1 << 24; - /// <https://html.spec.whatwg.org/multipage/#selector-read-write> - const READWRITE = 1 << 25; - /// <https://html.spec.whatwg.org/multipage/#selector-default> - const DEFAULT = 1 << 26; - /// Non-standard & undocumented. - const OPTIMUM = 1 << 28; - /// Non-standard & undocumented. - const SUB_OPTIMUM = 1 << 29; - /// Non-standard & undocumented. - const SUB_SUB_OPTIMUM = 1 << 30; - /// Non-standard & undocumented. - const INCREMENT_SCRIPT_LEVEL = 1u64 << 31; - /// <https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo> - const FOCUSRING = 1u64 << 32; - /// <https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo> - const FOCUS_WITHIN = 1u64 << 33; - /// :dir matching; the states are used for dynamic change detection. - /// State that elements that match :dir(ltr) are in. - const LTR = 1u64 << 34; - /// State that elements that match :dir(rtl) are in. - const RTL = 1u64 << 35; - /// State that HTML elements that have a "dir" attr are in. - const HAS_DIR_ATTR = 1u64 << 36; - /// State that HTML elements with dir="ltr" (or something - /// case-insensitively equal to "ltr") are in. - const HAS_DIR_ATTR_LTR = 1u64 << 37; - /// State that HTML elements with dir="rtl" (or something - /// case-insensitively equal to "rtl") are in. - const HAS_DIR_ATTR_RTL = 1u64 << 38; - /// State that HTML <bdi> elements without a valid-valued "dir" attr or - /// any HTML elements (including <bdi>) with dir="auto" (or something - /// case-insensitively equal to "auto") are in. - const HAS_DIR_ATTR_LIKE_AUTO = 1u64 << 39; - /// Non-standard & undocumented. - const AUTOFILL = 1u64 << 40; - /// Non-standard & undocumented. - const AUTOFILL_PREVIEW = 1u64 << 41; - /// State that dialog element is modal, for centered alignment - /// <https://html.spec.whatwg.org/multipage/#centered-alignment> - const MODAL_DIALOG = 1u64 << 42; - /// <https://html.spec.whatwg.org/multipage/#inert-subtrees> - const INERT = 1u64 << 43; - /// State for the topmost modal element in top layer - const TOPMOST_MODAL = 1u64 << 44; - /// Initially used for the devtools highlighter, but now somehow only - /// used for the devtools accessibility inspector. - const DEVTOOLS_HIGHLIGHTED = 1u64 << 45; - /// Used for the devtools style editor. Probably should go away. - const STYLEEDITOR_TRANSITIONING = 1u64 << 46; - /// For :-moz-value-empty (to show widgets like the reveal password - /// button or the clear button). - const VALUE_EMPTY = 1u64 << 47; - /// For :-moz-revealed. - const REVEALED = 1u64 << 48; - - /// Some convenience unions. - const DIR_STATES = Self::LTR.bits | Self::RTL.bits; - - const DIR_ATTR_STATES = Self::HAS_DIR_ATTR.bits | - Self::HAS_DIR_ATTR_LTR.bits | - Self::HAS_DIR_ATTR_RTL.bits | - Self::HAS_DIR_ATTR_LIKE_AUTO.bits; - - const DISABLED_STATES = Self::DISABLED.bits | Self::ENABLED.bits; - - const REQUIRED_STATES = Self::REQUIRED.bits | Self::OPTIONAL_.bits; - - /// Event states that can be added and removed through - /// Element::{Add,Remove}ManuallyManagedStates. - /// - /// Take care when manually managing state bits. You are responsible - /// for setting or clearing the bit when an Element is added or removed - /// from a document (e.g. in BindToTree and UnbindFromTree), if that is - /// an appropriate thing to do for your state bit. - const MANUALLY_MANAGED_STATES = Self::AUTOFILL.bits | Self::AUTOFILL_PREVIEW.bits; - - /// Event states that are managed externally to an element (by the - /// EventStateManager, or by other code). As opposed to those in - /// INTRINSIC_STATES, which are are computed by the element itself - /// and returned from Element::IntrinsicState. - const EXTERNALLY_MANAGED_STATES = - Self::MANUALLY_MANAGED_STATES.bits | - Self::DIR_ATTR_STATES.bits | - Self::DISABLED_STATES.bits | - Self::REQUIRED_STATES.bits | - Self::ACTIVE.bits | - Self::DEFINED.bits | - Self::DRAGOVER.bits | - Self::FOCUS.bits | - Self::FOCUSRING.bits | - Self::FOCUS_WITHIN.bits | - Self::FULLSCREEN.bits | - Self::HOVER.bits | - Self::URLTARGET.bits | - Self::MODAL_DIALOG.bits | - Self::INERT.bits | - Self::TOPMOST_MODAL.bits | - Self::REVEALED.bits; - - const INTRINSIC_STATES = !Self::EXTERNALLY_MANAGED_STATES.bits; - } -} - -bitflags! { - /// Event-based document states. - #[repr(C)] - pub struct DocumentState: u64 { - /// Window activation status - const WINDOW_INACTIVE = 1 << 0; - /// RTL locale: specific to the XUL localedir attribute - const RTL_LOCALE = 1 << 1; - /// LTR locale: specific to the XUL localedir attribute - const LTR_LOCALE = 1 << 2; - /// LWTheme status - const LWTHEME = 1 << 3; - - const ALL_LOCALEDIR_BITS = Self::LTR_LOCALE.bits | Self::RTL_LOCALE.bits; - } -} - -malloc_size_of_is_0!(ElementState, DocumentState); diff --git a/components/style_traits/lib.rs b/components/style_traits/lib.rs deleted file mode 100644 index 334b3a891bd..00000000000 --- a/components/style_traits/lib.rs +++ /dev/null @@ -1,281 +0,0 @@ -/* 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 module contains shared types and messages for use by devtools/script. -//! The traits are here instead of in script so that the devtools crate can be -//! modified independently of the rest of Servo. - -#![crate_name = "style_traits"] -#![crate_type = "rlib"] - -extern crate app_units; -#[macro_use] -extern crate bitflags; -extern crate cssparser; -extern crate euclid; -#[macro_use] -extern crate lazy_static; -extern crate malloc_size_of; -#[macro_use] -extern crate malloc_size_of_derive; -extern crate selectors; -#[macro_use] -extern crate serde; -extern crate servo_arc; -#[cfg(feature = "servo")] -extern crate servo_atoms; -extern crate to_shmem; -#[macro_use] -extern crate to_shmem_derive; -#[cfg(feature = "servo")] -extern crate webrender_api; -#[cfg(feature = "servo")] -extern crate url; -#[cfg(feature = "servo")] -pub use webrender_api::units::DevicePixel; - -use cssparser::{CowRcStr, Token}; -use selectors::parser::SelectorParseErrorKind; -#[cfg(feature = "servo")] -use servo_atoms::Atom; - -/// One hardware pixel. -/// -/// This unit corresponds to the smallest addressable element of the display hardware. -#[cfg(not(feature = "servo"))] -#[derive(Clone, Copy, Debug)] -pub enum DevicePixel {} - -/// Represents a mobile style pinch zoom factor. -/// TODO(gw): Once WR supports pinch zoom, use a type directly from webrender_api. -#[derive(Clone, Copy, Debug, PartialEq)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize, MallocSizeOf))] -pub struct PinchZoomFactor(f32); - -impl PinchZoomFactor { - /// Construct a new pinch zoom factor. - pub fn new(scale: f32) -> PinchZoomFactor { - PinchZoomFactor(scale) - } - - /// Get the pinch zoom factor as an untyped float. - pub fn get(&self) -> f32 { - self.0 - } -} - -/// One CSS "px" in the coordinate system of the "initial viewport": -/// <http://www.w3.org/TR/css-device-adapt/#initial-viewport> -/// -/// `CSSPixel` is equal to `DeviceIndependentPixel` times a "page zoom" factor controlled by the user. This is -/// the desktop-style "full page" zoom that enlarges content but then reflows the layout viewport -/// so it still exactly fits the visible area. -/// -/// At the default zoom level of 100%, one `CSSPixel` is equal to one `DeviceIndependentPixel`. However, if the -/// document is zoomed in or out then this scale may be larger or smaller. -#[derive(Clone, Copy, Debug)] -pub enum CSSPixel {} - -// In summary, the hierarchy of pixel units and the factors to convert from one to the next: -// -// DevicePixel -// / hidpi_ratio => DeviceIndependentPixel -// / desktop_zoom => CSSPixel - -pub mod arc_slice; -pub mod dom; -pub mod specified_value_info; -#[macro_use] -pub mod values; -pub mod owned_slice; -pub mod owned_str; - -pub use crate::specified_value_info::{CssType, KeywordsCollectFn, SpecifiedValueInfo}; -pub use crate::values::{ - Comma, CommaWithSpace, CssWriter, OneOrMoreSeparated, Separator, Space, ToCss, -}; - -/// The error type for all CSS parsing routines. -pub type ParseError<'i> = cssparser::ParseError<'i, StyleParseErrorKind<'i>>; - -/// Error in property value parsing -pub type ValueParseError<'i> = cssparser::ParseError<'i, ValueParseErrorKind<'i>>; - -#[derive(Clone, Debug, PartialEq)] -/// Errors that can be encountered while parsing CSS values. -pub enum StyleParseErrorKind<'i> { - /// A bad URL token in a DVB. - BadUrlInDeclarationValueBlock(CowRcStr<'i>), - /// A bad string token in a DVB. - BadStringInDeclarationValueBlock(CowRcStr<'i>), - /// Unexpected closing parenthesis in a DVB. - UnbalancedCloseParenthesisInDeclarationValueBlock, - /// Unexpected closing bracket in a DVB. - UnbalancedCloseSquareBracketInDeclarationValueBlock, - /// Unexpected closing curly bracket in a DVB. - UnbalancedCloseCurlyBracketInDeclarationValueBlock, - /// A property declaration value had input remaining after successfully parsing. - PropertyDeclarationValueNotExhausted, - /// An unexpected dimension token was encountered. - UnexpectedDimension(CowRcStr<'i>), - /// Missing or invalid media feature name. - MediaQueryExpectedFeatureName(CowRcStr<'i>), - /// Missing or invalid media feature value. - MediaQueryExpectedFeatureValue, - /// A media feature range operator was not expected. - MediaQueryUnexpectedOperator, - /// min- or max- properties must have a value. - RangedExpressionWithNoValue, - /// A function was encountered that was not expected. - UnexpectedFunction(CowRcStr<'i>), - /// Error encountered parsing a @property's `syntax` descriptor - PropertySyntaxField(PropertySyntaxParseError), - /// @namespace must be before any rule but @charset and @import - UnexpectedNamespaceRule, - /// @import must be before any rule but @charset - UnexpectedImportRule, - /// @import rules are disallowed in the parser. - DisallowedImportRule, - /// Unexpected @charset rule encountered. - UnexpectedCharsetRule, - /// The @property `<custom-property-name>` must start with `--` - UnexpectedIdent(CowRcStr<'i>), - /// A placeholder for many sources of errors that require more specific variants. - UnspecifiedError, - /// An unexpected token was found within a namespace rule. - UnexpectedTokenWithinNamespace(Token<'i>), - /// An error was encountered while parsing a property value. - ValueError(ValueParseErrorKind<'i>), - /// An error was encountered while parsing a selector - SelectorError(SelectorParseErrorKind<'i>), - /// The property declaration was for an unknown property. - UnknownProperty(CowRcStr<'i>), - /// The property declaration was for a disabled experimental property. - ExperimentalProperty, - /// The property declaration contained an invalid color value. - InvalidColor(CowRcStr<'i>, Token<'i>), - /// The property declaration contained an invalid filter value. - InvalidFilter(CowRcStr<'i>, Token<'i>), - /// The property declaration contained an invalid value. - OtherInvalidValue(CowRcStr<'i>), - /// The declaration contained an animation property, and we were parsing - /// this as a keyframe block (so that property should be ignored). - /// - /// See: https://drafts.csswg.org/css-animations/#keyframes - AnimationPropertyInKeyframeBlock, - /// The property is not allowed within a page rule. - NotAllowedInPageRule, -} - -impl<'i> From<ValueParseErrorKind<'i>> for StyleParseErrorKind<'i> { - fn from(this: ValueParseErrorKind<'i>) -> Self { - StyleParseErrorKind::ValueError(this) - } -} - -impl<'i> From<SelectorParseErrorKind<'i>> for StyleParseErrorKind<'i> { - fn from(this: SelectorParseErrorKind<'i>) -> Self { - StyleParseErrorKind::SelectorError(this) - } -} - -/// Specific errors that can be encountered while parsing property values. -#[derive(Clone, Debug, PartialEq)] -pub enum ValueParseErrorKind<'i> { - /// An invalid token was encountered while parsing a color value. - InvalidColor(Token<'i>), - /// An invalid filter value was encountered. - InvalidFilter(Token<'i>), -} - -impl<'i> StyleParseErrorKind<'i> { - /// Create an InvalidValue parse error - pub fn new_invalid<S>(name: S, value_error: ParseError<'i>) -> ParseError<'i> - where - S: Into<CowRcStr<'i>>, - { - let name = name.into(); - let variant = match value_error.kind { - cssparser::ParseErrorKind::Custom(StyleParseErrorKind::ValueError(e)) => match e { - ValueParseErrorKind::InvalidColor(token) => { - StyleParseErrorKind::InvalidColor(name, token) - }, - ValueParseErrorKind::InvalidFilter(token) => { - StyleParseErrorKind::InvalidFilter(name, token) - }, - }, - _ => StyleParseErrorKind::OtherInvalidValue(name), - }; - cssparser::ParseError { - kind: cssparser::ParseErrorKind::Custom(variant), - location: value_error.location, - } - } -} - -/// Errors that can be encountered while parsing the @property rule's syntax descriptor. -#[derive(Clone, Debug, PartialEq)] -pub enum PropertySyntaxParseError { - /// The string's length was 0. - EmptyInput, - /// A non-whitespace, non-pipe character was fount after parsing a component. - ExpectedPipeBetweenComponents, - /// The start of an identifier was expected but not found. - /// - /// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point> - InvalidNameStart, - /// The name is not a valid `<ident>`. - InvalidName, - /// The data type name was not closed. - /// - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name> - UnclosedDataTypeName, - /// The next byte was expected while parsing, but EOF was found instead. - UnexpectedEOF, - /// The data type is not a supported syntax component name. - /// - /// <https://drafts.css-houdini.org/css-properties-values-api-1/#supported-names> - UnknownDataTypeName, -} - -bitflags! { - /// The mode to use when parsing values. - pub struct ParsingMode: u8 { - /// In CSS; lengths must have units, except for zero values, where the unit can be omitted. - /// <https://www.w3.org/TR/css3-values/#lengths> - const DEFAULT = 0x00; - /// In SVG; a coordinate or length value without a unit identifier (e.g., "25") is assumed - /// to be in user units (px). - /// <https://www.w3.org/TR/SVG/coords.html#Units> - const ALLOW_UNITLESS_LENGTH = 0x01; - /// In SVG; out-of-range values are not treated as an error in parsing. - /// <https://www.w3.org/TR/SVG/implnote.html#RangeClamping> - const ALLOW_ALL_NUMERIC_VALUES = 0x02; - } -} - -impl ParsingMode { - /// Whether the parsing mode allows unitless lengths for non-zero values to be intpreted as px. - #[inline] - pub fn allows_unitless_lengths(&self) -> bool { - self.intersects(ParsingMode::ALLOW_UNITLESS_LENGTH) - } - - /// Whether the parsing mode allows all numeric values. - #[inline] - pub fn allows_all_numeric_values(&self) -> bool { - self.intersects(ParsingMode::ALLOW_ALL_NUMERIC_VALUES) - } -} - -#[cfg(feature = "servo")] -/// Speculatively execute paint code in the worklet thread pool. -pub trait SpeculativePainter: Send + Sync { - /// <https://drafts.css-houdini.org/css-paint-api/#draw-a-paint-image> - fn speculatively_draw_a_paint_image( - &self, - properties: Vec<(Atom, String)>, - arguments: Vec<String>, - ); -} diff --git a/components/style_traits/owned_slice.rs b/components/style_traits/owned_slice.rs deleted file mode 100644 index 36ba3162e59..00000000000 --- a/components/style_traits/owned_slice.rs +++ /dev/null @@ -1,198 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] - -//! A replacement for `Box<[T]>` that cbindgen can understand. - -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; -use serde::de::{Deserialize, Deserializer}; -use serde::ser::{Serialize, Serializer}; -use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use std::ptr::NonNull; -use std::{fmt, iter, mem, slice}; -use to_shmem::{self, SharedMemoryBuilder, ToShmem}; - -/// A struct that basically replaces a `Box<[T]>`, but which cbindgen can -/// understand. -/// -/// We could rely on the struct layout of `Box<[T]>` per: -/// -/// https://github.com/rust-lang/unsafe-code-guidelines/blob/master/reference/src/layout/pointers.md -/// -/// But handling fat pointers with cbindgen both in structs and argument -/// positions more generally is a bit tricky. -/// -/// cbindgen:derive-eq=false -/// cbindgen:derive-neq=false -#[repr(C)] -pub struct OwnedSlice<T: Sized> { - ptr: NonNull<T>, - len: usize, - _phantom: PhantomData<T>, -} - -impl<T: Sized> Default for OwnedSlice<T> { - #[inline] - fn default() -> Self { - Self { - len: 0, - ptr: NonNull::dangling(), - _phantom: PhantomData, - } - } -} - -impl<T: Sized> Drop for OwnedSlice<T> { - #[inline] - fn drop(&mut self) { - if self.len != 0 { - let _ = mem::replace(self, Self::default()).into_vec(); - } - } -} - -unsafe impl<T: Sized + Send> Send for OwnedSlice<T> {} -unsafe impl<T: Sized + Sync> Sync for OwnedSlice<T> {} - -impl<T: Clone> Clone for OwnedSlice<T> { - #[inline] - fn clone(&self) -> Self { - Self::from_slice(&**self) - } -} - -impl<T: fmt::Debug> fmt::Debug for OwnedSlice<T> { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - self.deref().fmt(formatter) - } -} - -impl<T: PartialEq> PartialEq for OwnedSlice<T> { - fn eq(&self, other: &Self) -> bool { - self.deref().eq(other.deref()) - } -} - -impl<T: Eq> Eq for OwnedSlice<T> {} - -impl<T: Sized> OwnedSlice<T> { - /// Convert the OwnedSlice into a boxed slice. - #[inline] - pub fn into_box(self) -> Box<[T]> { - self.into_vec().into_boxed_slice() - } - - /// Convert the OwnedSlice into a Vec. - #[inline] - pub fn into_vec(self) -> Vec<T> { - let ret = unsafe { Vec::from_raw_parts(self.ptr.as_ptr(), self.len, self.len) }; - mem::forget(self); - ret - } - - /// Convert the regular slice into an owned slice. - #[inline] - pub fn from_slice(s: &[T]) -> Self - where - T: Clone, - { - Self::from(s.to_vec()) - } -} - -impl<T> IntoIterator for OwnedSlice<T> { - type Item = T; - type IntoIter = <Vec<T> as IntoIterator>::IntoIter; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.into_vec().into_iter() - } -} - -impl<T> Deref for OwnedSlice<T> { - type Target = [T]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - unsafe { slice::from_raw_parts(self.ptr.as_ptr(), self.len) } - } -} - -impl<T> DerefMut for OwnedSlice<T> { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) } - } -} - -impl<T> From<Box<[T]>> for OwnedSlice<T> { - #[inline] - fn from(mut b: Box<[T]>) -> Self { - let len = b.len(); - let ptr = unsafe { NonNull::new_unchecked(b.as_mut_ptr()) }; - mem::forget(b); - Self { - len, - ptr, - _phantom: PhantomData, - } - } -} - -impl<T> From<Vec<T>> for OwnedSlice<T> { - #[inline] - fn from(b: Vec<T>) -> Self { - Self::from(b.into_boxed_slice()) - } -} - -impl<T: Sized> MallocShallowSizeOf for OwnedSlice<T> { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - unsafe { ops.malloc_size_of(self.ptr.as_ptr()) } - } -} - -impl<T: MallocSizeOf + Sized> MallocSizeOf for OwnedSlice<T> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.shallow_size_of(ops) + (**self).size_of(ops) - } -} - -impl<T: ToShmem + Sized> ToShmem for OwnedSlice<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> { - unsafe { - let dest = to_shmem::to_shmem_slice(self.iter(), builder)?; - Ok(mem::ManuallyDrop::new(Self::from(Box::from_raw(dest)))) - } - } -} - -impl<T> iter::FromIterator<T> for OwnedSlice<T> { - #[inline] - fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { - Vec::from_iter(iter).into() - } -} - -impl<T: Serialize> Serialize for OwnedSlice<T> { - fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> - where - S: Serializer, - { - self.deref().serialize(serializer) - } -} - -impl<'de, T: Deserialize<'de>> Deserialize<'de> for OwnedSlice<T> { - fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> - where - D: Deserializer<'de>, - { - let r = Box::<[T]>::deserialize(deserializer)?; - Ok(r.into()) - } -} diff --git a/components/style_traits/owned_str.rs b/components/style_traits/owned_str.rs deleted file mode 100644 index ebfdcd5e066..00000000000 --- a/components/style_traits/owned_str.rs +++ /dev/null @@ -1,81 +0,0 @@ -/* 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/. */ - -#![allow(unsafe_code)] - -//! A replacement for `Box<str>` that has a defined layout for FFI. - -use crate::owned_slice::OwnedSlice; -use std::fmt; -use std::ops::{Deref, DerefMut}; - -/// A struct that basically replaces a Box<str>, but with a defined layout, -/// suitable for FFI. -#[repr(C)] -#[derive(Clone, Default, Eq, MallocSizeOf, PartialEq, ToShmem)] -pub struct OwnedStr(OwnedSlice<u8>); - -impl fmt::Debug for OwnedStr { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - self.deref().fmt(formatter) - } -} - -impl Deref for OwnedStr { - type Target = str; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - unsafe { std::str::from_utf8_unchecked(&*self.0) } - } -} - -impl DerefMut for OwnedStr { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { std::str::from_utf8_unchecked_mut(&mut *self.0) } - } -} - -impl OwnedStr { - /// Convert the OwnedStr into a boxed str. - #[inline] - pub fn into_box(self) -> Box<str> { - self.into_string().into_boxed_str() - } - - /// Convert the OwnedStr into a `String`. - #[inline] - pub fn into_string(self) -> String { - unsafe { String::from_utf8_unchecked(self.0.into_vec()) } - } -} - -impl From<OwnedStr> for String { - #[inline] - fn from(b: OwnedStr) -> Self { - b.into_string() - } -} - -impl From<OwnedStr> for Box<str> { - #[inline] - fn from(b: OwnedStr) -> Self { - b.into_box() - } -} - -impl From<Box<str>> for OwnedStr { - #[inline] - fn from(b: Box<str>) -> Self { - Self::from(b.into_string()) - } -} - -impl From<String> for OwnedStr { - #[inline] - fn from(s: String) -> Self { - OwnedStr(s.into_bytes().into()) - } -} diff --git a/components/style_traits/rustfmt.toml b/components/style_traits/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/style_traits/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/style_traits/specified_value_info.rs b/components/style_traits/specified_value_info.rs deleted file mode 100644 index b73d576715a..00000000000 --- a/components/style_traits/specified_value_info.rs +++ /dev/null @@ -1,138 +0,0 @@ -/* 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/. */ - -//! Value information for devtools. - -use crate::arc_slice::ArcSlice; -use crate::owned_slice::OwnedSlice; -use servo_arc::Arc; -use std::ops::Range; -use std::sync::Arc as StdArc; - -/// Type of value that a property supports. This is used by Gecko's -/// devtools to make sense about value it parses, and types listed -/// here should match InspectorPropertyType in InspectorUtils.webidl. -/// -/// XXX This should really be a bitflags rather than a namespace mod, -/// but currently we cannot use bitflags in const. -#[allow(non_snake_case)] -pub mod CssType { - /// <color> - pub const COLOR: u8 = 1 << 0; - /// <gradient> - pub const GRADIENT: u8 = 1 << 1; - /// <timing-function> - pub const TIMING_FUNCTION: u8 = 1 << 2; -} - -/// See SpecifiedValueInfo::collect_completion_keywords. -pub type KeywordsCollectFn<'a> = &'a mut dyn FnMut(&[&'static str]); - -/// Information of values of a given specified value type. -/// -/// This trait is derivable with `#[derive(SpecifiedValueInfo)]`. -/// -/// The algorithm traverses the type definition. For `SUPPORTED_TYPES`, -/// it puts an or'ed value of `SUPPORTED_TYPES` of all types it finds. -/// For `collect_completion_keywords`, it recursively invokes this -/// method on types found, and lists all keyword values and function -/// names following the same rule as `ToCss` in that method. -/// -/// Some attributes of `ToCss` can affect the behavior, specifically: -/// * If `#[css(function)]` is found, the content inside the annotated -/// variant (or the whole type) isn't traversed, only the function -/// name is listed in `collect_completion_keywords`. -/// * If `#[css(skip)]` is found, the content inside the variant or -/// field is ignored. -/// * Values listed in `#[css(if_empty)]`, `#[parse(aliases)]`, and -/// `#[css(keyword)]` are added into `collect_completion_keywords`. -/// -/// In addition to `css` attributes, it also has `value_info` helper -/// attributes, including: -/// * `#[value_info(ty = "TYPE")]` can be used to specify a constant -/// from `CssType` to `SUPPORTED_TYPES`. -/// * `#[value_info(other_values = "value1,value2")]` can be used to -/// add other values related to a field, variant, or the type itself -/// into `collect_completion_keywords`. -/// * `#[value_info(starts_with_keyword)]` can be used on variants to -/// add the name of a non-unit variant (serialized like `ToCss`) into -/// `collect_completion_keywords`. -pub trait SpecifiedValueInfo { - /// Supported CssTypes by the given value type. - /// - /// XXX This should be typed CssType when that becomes a bitflags. - /// Currently we cannot do so since bitflags cannot be used in constant. - const SUPPORTED_TYPES: u8 = 0; - - /// Collect value starting words for the given specified value type. - /// This includes keyword and function names which can appear at the - /// beginning of a value of this type. - /// - /// Caller should pass in a callback function to accept the list of - /// values. The callback function can be called multiple times, and - /// some values passed to the callback may be duplicate. - fn collect_completion_keywords(_f: KeywordsCollectFn) {} -} - -impl SpecifiedValueInfo for bool {} -impl SpecifiedValueInfo for f32 {} -impl SpecifiedValueInfo for i8 {} -impl SpecifiedValueInfo for i32 {} -impl SpecifiedValueInfo for u8 {} -impl SpecifiedValueInfo for u16 {} -impl SpecifiedValueInfo for u32 {} -impl SpecifiedValueInfo for usize {} -impl SpecifiedValueInfo for str {} -impl SpecifiedValueInfo for String {} -impl SpecifiedValueInfo for crate::owned_str::OwnedStr {} - -#[cfg(feature = "servo")] -impl SpecifiedValueInfo for ::servo_atoms::Atom {} -#[cfg(feature = "servo")] -impl SpecifiedValueInfo for ::url::Url {} - -impl<T: SpecifiedValueInfo + ?Sized> SpecifiedValueInfo for Box<T> { - const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES; - fn collect_completion_keywords(f: KeywordsCollectFn) { - T::collect_completion_keywords(f); - } -} - -impl<T: SpecifiedValueInfo> SpecifiedValueInfo for [T] { - const SUPPORTED_TYPES: u8 = T::SUPPORTED_TYPES; - fn collect_completion_keywords(f: KeywordsCollectFn) { - T::collect_completion_keywords(f); - } -} - -macro_rules! impl_generic_specified_value_info { - ($ty:ident<$param:ident>) => { - impl<$param: SpecifiedValueInfo> SpecifiedValueInfo for $ty<$param> { - const SUPPORTED_TYPES: u8 = $param::SUPPORTED_TYPES; - fn collect_completion_keywords(f: KeywordsCollectFn) { - $param::collect_completion_keywords(f); - } - } - }; -} -impl_generic_specified_value_info!(Option<T>); -impl_generic_specified_value_info!(OwnedSlice<T>); -impl_generic_specified_value_info!(Vec<T>); -impl_generic_specified_value_info!(Arc<T>); -impl_generic_specified_value_info!(StdArc<T>); -impl_generic_specified_value_info!(ArcSlice<T>); -impl_generic_specified_value_info!(Range<Idx>); - -impl<T1, T2> SpecifiedValueInfo for (T1, T2) -where - T1: SpecifiedValueInfo, - T2: SpecifiedValueInfo, -{ - const SUPPORTED_TYPES: u8 = T1::SUPPORTED_TYPES | T2::SUPPORTED_TYPES; - - fn collect_completion_keywords(f: KeywordsCollectFn) { - T1::collect_completion_keywords(f); - T2::collect_completion_keywords(f); - } -} diff --git a/components/style_traits/values.rs b/components/style_traits/values.rs deleted file mode 100644 index 14025023cb3..00000000000 --- a/components/style_traits/values.rs +++ /dev/null @@ -1,567 +0,0 @@ -/* 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/. */ - -//! Helper types and traits for the handling of CSS values. - -use app_units::Au; -use cssparser::ToCss as CssparserToCss; -use cssparser::{serialize_string, ParseError, Parser, Token, UnicodeRange}; -use servo_arc::Arc; -use std::fmt::{self, Write}; - -/// Serialises a value according to its CSS representation. -/// -/// This trait is implemented for `str` and its friends, serialising the string -/// contents as a CSS quoted string. -/// -/// This trait is derivable with `#[derive(ToCss)]`, with the following behaviour: -/// * unit variants get serialised as the `snake-case` representation -/// of their name; -/// * unit variants whose name starts with "Moz" or "Webkit" are prepended -/// with a "-"; -/// * if `#[css(comma)]` is found on a variant, its fields are separated by -/// commas, otherwise, by spaces; -/// * if `#[css(function)]` is found on a variant, the variant name gets -/// serialised like unit variants and its fields are surrounded by parentheses; -/// * if `#[css(iterable)]` is found on a function variant, that variant needs -/// to have a single member, and that member needs to be iterable. The -/// iterable will be serialized as the arguments for the function; -/// * an iterable field can also be annotated with `#[css(if_empty = "foo")]` -/// to print `"foo"` if the iterator is empty; -/// * if `#[css(dimension)]` is found on a variant, that variant needs -/// to have a single member. The variant would be serialized as a CSS -/// dimension token, like: <member><identifier>; -/// * if `#[css(skip)]` is found on a field, the `ToCss` call for that field -/// is skipped; -/// * if `#[css(skip_if = "function")]` is found on a field, the `ToCss` call -/// for that field is skipped if `function` returns true. This function is -/// provided the field as an argument; -/// * if `#[css(contextual_skip_if = "function")]` is found on a field, the -/// `ToCss` call for that field is skipped if `function` returns true. This -/// function is given all the fields in the current struct or variant as an -/// argument; -/// * `#[css(represents_keyword)]` can be used on bool fields in order to -/// serialize the field name if the field is true, or nothing otherwise. It -/// also collects those keywords for `SpecifiedValueInfo`. -/// * `#[css(bitflags(single="", mixed="", validate="", overlapping_bits)]` can -/// be used to derive parse / serialize / etc on bitflags. The rules for parsing -/// bitflags are the following: -/// -/// * `single` flags can only appear on their own. It's common that bitflags -/// properties at least have one such value like `none` or `auto`. -/// * `mixed` properties can appear mixed together, but not along any other -/// flag that shares a bit with itself. For example, if you have three -/// bitflags like: -/// -/// FOO = 1 << 0; -/// BAR = 1 << 1; -/// BAZ = 1 << 2; -/// BAZZ = BAR | BAZ; -/// -/// Then the following combinations won't be valid: -/// -/// * foo foo: (every flag shares a bit with itself) -/// * bar bazz: (bazz shares a bit with bar) -/// -/// But `bar baz` will be valid, as they don't share bits, and so would -/// `foo` with any other flag, or `bazz` on its own. -/// * `overlapping_bits` enables some tracking during serialization of mixed -/// flags to avoid serializing variants that can subsume other variants. -/// In the example above, you could do: -/// mixed="foo,bazz,bar,baz", overlapping_bits -/// to ensure that if bazz is serialized, bar and baz aren't, even though -/// their bits are set. Note that the serialization order is canonical, -/// and thus depends on the order you specify the flags in. -/// -/// * finally, one can put `#[css(derive_debug)]` on the whole type, to -/// implement `Debug` by a single call to `ToCss::to_css`. -pub trait ToCss { - /// Serialize `self` in CSS syntax, writing to `dest`. - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write; - - /// Serialize `self` in CSS syntax and return a string. - /// - /// (This is a convenience wrapper for `to_css` and probably should not be overridden.) - #[inline] - fn to_css_string(&self) -> String { - let mut s = String::new(); - self.to_css(&mut CssWriter::new(&mut s)).unwrap(); - s - } -} - -impl<'a, T> ToCss for &'a T -where - T: ToCss + ?Sized, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - (*self).to_css(dest) - } -} - -impl ToCss for crate::owned_str::OwnedStr { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_string(self, dest) - } -} - -impl ToCss for str { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_string(self, dest) - } -} - -impl ToCss for String { - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - serialize_string(self, dest) - } -} - -impl<T> ToCss for Option<T> -where - T: ToCss, -{ - #[inline] - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.as_ref().map_or(Ok(()), |value| value.to_css(dest)) - } -} - -impl ToCss for () { - #[inline] - fn to_css<W>(&self, _: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - Ok(()) - } -} - -/// A writer tailored for serialising CSS. -/// -/// Coupled with SequenceWriter, this allows callers to transparently handle -/// things like comma-separated values etc. -pub struct CssWriter<'w, W: 'w> { - inner: &'w mut W, - prefix: Option<&'static str>, -} - -impl<'w, W> CssWriter<'w, W> -where - W: Write, -{ - /// Creates a new `CssWriter`. - #[inline] - pub fn new(inner: &'w mut W) -> Self { - Self { - inner, - prefix: Some(""), - } - } -} - -impl<'w, W> Write for CssWriter<'w, W> -where - W: Write, -{ - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - if s.is_empty() { - return Ok(()); - } - if let Some(prefix) = self.prefix.take() { - // We are going to write things, but first we need to write - // the prefix that was set by `SequenceWriter::item`. - if !prefix.is_empty() { - self.inner.write_str(prefix)?; - } - } - self.inner.write_str(s) - } - - #[inline] - fn write_char(&mut self, c: char) -> fmt::Result { - if let Some(prefix) = self.prefix.take() { - // See comment in `write_str`. - if !prefix.is_empty() { - self.inner.write_str(prefix)?; - } - } - self.inner.write_char(c) - } -} - -/// Convenience wrapper to serialise CSS values separated by a given string. -pub struct SequenceWriter<'a, 'b: 'a, W: 'b> { - inner: &'a mut CssWriter<'b, W>, - separator: &'static str, -} - -impl<'a, 'b, W> SequenceWriter<'a, 'b, W> -where - W: Write + 'b, -{ - /// Create a new sequence writer. - #[inline] - pub fn new(inner: &'a mut CssWriter<'b, W>, separator: &'static str) -> Self { - if inner.prefix.is_none() { - // See comment in `item`. - inner.prefix = Some(""); - } - Self { inner, separator } - } - - #[inline] - fn write_item<F>(&mut self, f: F) -> fmt::Result - where - F: FnOnce(&mut CssWriter<'b, W>) -> fmt::Result, - { - // Separate non-generic functions so that this code is not repeated - // in every monomorphization with a different type `F` or `W`. - // https://github.com/servo/servo/issues/26713 - fn before( - prefix: &mut Option<&'static str>, - separator: &'static str, - ) -> Option<&'static str> { - let old_prefix = *prefix; - if old_prefix.is_none() { - // If there is no prefix in the inner writer, a previous - // call to this method produced output, which means we need - // to write the separator next time we produce output again. - *prefix = Some(separator); - } - old_prefix - } - fn after( - old_prefix: Option<&'static str>, - prefix: &mut Option<&'static str>, - separator: &'static str, - ) { - match (old_prefix, *prefix) { - (_, None) => { - // This call produced output and cleaned up after itself. - }, - (None, Some(p)) => { - // Some previous call to `item` produced output, - // but this one did not, prefix should be the same as - // the one we set. - debug_assert_eq!(separator, p); - // We clean up here even though it's not necessary just - // to be able to do all these assertion checks. - *prefix = None; - }, - (Some(old), Some(new)) => { - // No previous call to `item` produced output, and this one - // either. - debug_assert_eq!(old, new); - }, - } - } - - let old_prefix = before(&mut self.inner.prefix, self.separator); - f(self.inner)?; - after(old_prefix, &mut self.inner.prefix, self.separator); - Ok(()) - } - - /// Serialises a CSS value, writing any separator as necessary. - /// - /// The separator is never written before any `item` produces any output, - /// and is written in subsequent calls only if the `item` produces some - /// output on its own again. This lets us handle `Option<T>` fields by - /// just not printing anything on `None`. - #[inline] - pub fn item<T>(&mut self, item: &T) -> fmt::Result - where - T: ToCss, - { - self.write_item(|inner| item.to_css(inner)) - } - - /// Writes a string as-is (i.e. not escaped or wrapped in quotes) - /// with any separator as necessary. - /// - /// See SequenceWriter::item. - #[inline] - pub fn raw_item(&mut self, item: &str) -> fmt::Result { - self.write_item(|inner| inner.write_str(item)) - } -} - -/// Type used as the associated type in the `OneOrMoreSeparated` trait on a -/// type to indicate that a serialized list of elements of this type is -/// separated by commas. -pub struct Comma; - -/// Type used as the associated type in the `OneOrMoreSeparated` trait on a -/// type to indicate that a serialized list of elements of this type is -/// separated by spaces. -pub struct Space; - -/// Type used as the associated type in the `OneOrMoreSeparated` trait on a -/// type to indicate that a serialized list of elements of this type is -/// separated by commas, but spaces without commas are also allowed when -/// parsing. -pub struct CommaWithSpace; - -/// A trait satisfied by the types corresponding to separators. -pub trait Separator { - /// The separator string that the satisfying separator type corresponds to. - fn separator() -> &'static str; - - /// Parses a sequence of values separated by this separator. - /// - /// The given closure is called repeatedly for each item in the sequence. - /// - /// Successful results are accumulated in a vector. - /// - /// This method returns `Err(_)` the first time a closure does or if - /// the separators aren't correct. - fn parse<'i, 't, F, T, E>( - parser: &mut Parser<'i, 't>, - parse_one: F, - ) -> Result<Vec<T>, ParseError<'i, E>> - where - F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>; -} - -impl Separator for Comma { - fn separator() -> &'static str { - ", " - } - - fn parse<'i, 't, F, T, E>( - input: &mut Parser<'i, 't>, - parse_one: F, - ) -> Result<Vec<T>, ParseError<'i, E>> - where - F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, - { - input.parse_comma_separated(parse_one) - } -} - -impl Separator for Space { - fn separator() -> &'static str { - " " - } - - fn parse<'i, 't, F, T, E>( - input: &mut Parser<'i, 't>, - mut parse_one: F, - ) -> Result<Vec<T>, ParseError<'i, E>> - where - F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, - { - input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. - let mut results = vec![parse_one(input)?]; - loop { - input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. - if let Ok(item) = input.try(&mut parse_one) { - results.push(item); - } else { - return Ok(results); - } - } - } -} - -impl Separator for CommaWithSpace { - fn separator() -> &'static str { - ", " - } - - fn parse<'i, 't, F, T, E>( - input: &mut Parser<'i, 't>, - mut parse_one: F, - ) -> Result<Vec<T>, ParseError<'i, E>> - where - F: for<'tt> FnMut(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, E>>, - { - input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. - let mut results = vec![parse_one(input)?]; - loop { - input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. - let comma_location = input.current_source_location(); - let comma = input.try(|i| i.expect_comma()).is_ok(); - input.skip_whitespace(); // Unnecessary for correctness, but may help try() rewind less. - if let Ok(item) = input.try(&mut parse_one) { - results.push(item); - } else if comma { - return Err(comma_location.new_unexpected_token_error(Token::Comma)); - } else { - break; - } - } - Ok(results) - } -} - -/// Marker trait on T to automatically implement ToCss for Vec<T> when T's are -/// separated by some delimiter `delim`. -pub trait OneOrMoreSeparated { - /// Associated type indicating which separator is used. - type S: Separator; -} - -impl OneOrMoreSeparated for UnicodeRange { - type S = Comma; -} - -impl<T> ToCss for Vec<T> -where - T: ToCss + OneOrMoreSeparated, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - let mut iter = self.iter(); - iter.next().unwrap().to_css(dest)?; - for item in iter { - dest.write_str(<T as OneOrMoreSeparated>::S::separator())?; - item.to_css(dest)?; - } - Ok(()) - } -} - -impl<T> ToCss for Box<T> -where - T: ?Sized + ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - (**self).to_css(dest) - } -} - -impl<T> ToCss for Arc<T> -where - T: ?Sized + ToCss, -{ - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - (**self).to_css(dest) - } -} - -impl ToCss for Au { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - self.to_f64_px().to_css(dest)?; - dest.write_str("px") - } -} - -macro_rules! impl_to_css_for_predefined_type { - ($name: ty) => { - impl<'a> ToCss for $name { - fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result - where - W: Write, - { - ::cssparser::ToCss::to_css(self, dest) - } - } - }; -} - -impl_to_css_for_predefined_type!(f32); -impl_to_css_for_predefined_type!(i8); -impl_to_css_for_predefined_type!(i32); -impl_to_css_for_predefined_type!(u16); -impl_to_css_for_predefined_type!(u32); -impl_to_css_for_predefined_type!(::cssparser::Token<'a>); -impl_to_css_for_predefined_type!(::cssparser::UnicodeRange); - -/// Helper types for the handling of specified values. -pub mod specified { - use crate::ParsingMode; - - /// Whether to allow negative lengths or not. - #[repr(u8)] - #[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - MallocSizeOf, - PartialEq, - PartialOrd, - Serialize, - to_shmem_derive::ToShmem, - )] - pub enum AllowedNumericType { - /// Allow all kind of numeric values. - All, - /// Allow only non-negative numeric values. - NonNegative, - /// Allow only numeric values greater or equal to 1.0. - AtLeastOne, - /// Allow only numeric values from 0 to 1.0. - ZeroToOne, - } - - impl Default for AllowedNumericType { - #[inline] - fn default() -> Self { - AllowedNumericType::All - } - } - - impl AllowedNumericType { - /// Whether the value fits the rules of this numeric type. - #[inline] - pub fn is_ok(&self, parsing_mode: ParsingMode, val: f32) -> bool { - if parsing_mode.allows_all_numeric_values() { - return true; - } - match *self { - AllowedNumericType::All => true, - AllowedNumericType::NonNegative => val >= 0.0, - AllowedNumericType::AtLeastOne => val >= 1.0, - AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0, - } - } - - /// Clamp the value following the rules of this numeric type. - #[inline] - pub fn clamp(&self, val: f32) -> f32 { - match *self { - AllowedNumericType::All => val, - AllowedNumericType::NonNegative => val.max(0.), - AllowedNumericType::AtLeastOne => val.max(1.), - AllowedNumericType::ZeroToOne => val.max(0.).min(1.), - } - } - } -} diff --git a/components/to_shmem/Cargo.toml b/components/to_shmem/Cargo.toml deleted file mode 100644 index 5072f102dd4..00000000000 --- a/components/to_shmem/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "to_shmem" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -[lib] -name = "to_shmem" -path = "lib.rs" - -[features] -servo = ["cssparser/serde", "string_cache"] -gecko = [] - -[dependencies] -cssparser = { workspace = true } -servo_arc = { path = "../servo_arc" } -smallbitvec = { workspace = true } -smallvec = { workspace = true } -string_cache = { workspace = true, optional = true } -thin-vec = { workspace = true } diff --git a/components/to_shmem/lib.rs b/components/to_shmem/lib.rs deleted file mode 100644 index 0a708999d1d..00000000000 --- a/components/to_shmem/lib.rs +++ /dev/null @@ -1,596 +0,0 @@ -/* 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/. */ - -//! Trait for cloning data into a shared memory buffer. -//! -//! This module contains the SharedMemoryBuilder type and ToShmem trait. -//! -//! We put them here (and not in style_traits) so that we can derive ToShmem -//! from the selectors and style crates. - -#![crate_name = "to_shmem"] -#![crate_type = "rlib"] - -extern crate cssparser; -extern crate servo_arc; -extern crate smallbitvec; -extern crate smallvec; -#[cfg(feature = "string_cache")] -extern crate string_cache; -extern crate thin_vec; - -use servo_arc::{Arc, ThinArc}; -use smallbitvec::{InternalStorage, SmallBitVec}; -use smallvec::{Array, SmallVec}; -use std::alloc::Layout; -use std::collections::HashSet; -use std::ffi::CString; -use std::isize; -use std::marker::PhantomData; -use std::mem::{self, ManuallyDrop}; -use std::num::Wrapping; -use std::ops::Range; -use std::os::raw::c_char; -#[cfg(debug_assertions)] -use std::os::raw::c_void; -use std::ptr::{self, NonNull}; -use std::slice; -use std::str; -use thin_vec::ThinVec; - -/// Result type for ToShmem::to_shmem. -/// -/// The String is an error message describing why the call failed. -pub type Result<T> = std::result::Result<ManuallyDrop<T>, String>; - -// Various pointer arithmetic functions in this file can be replaced with -// functions on `Layout` once they have stabilized: -// -// https://github.com/rust-lang/rust/issues/55724 - -/// A builder object that transforms and copies values into a fixed size buffer. -pub struct SharedMemoryBuilder { - /// The buffer into which values will be copied. - buffer: *mut u8, - /// The size of the buffer. - capacity: usize, - /// The current position in the buffer, where the next value will be written - /// at. - index: usize, - /// Pointers to every shareable value that we store in the shared memory - /// buffer. We use this to assert against encountering the same value - /// twice, e.g. through another Arc reference, so that we don't - /// inadvertently store duplicate copies of values. - #[cfg(debug_assertions)] - shared_values: HashSet<*const c_void>, -} - -/// Amount of padding needed after `size` bytes to ensure that the following -/// address will satisfy `align`. -fn padding_needed_for(size: usize, align: usize) -> usize { - padded_size(size, align).wrapping_sub(size) -} - -/// Rounds up `size` so that the following address will satisfy `align`. -fn padded_size(size: usize, align: usize) -> usize { - size.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1) -} - -impl SharedMemoryBuilder { - /// Creates a new SharedMemoryBuilder using the specified buffer. - pub unsafe fn new(buffer: *mut u8, capacity: usize) -> SharedMemoryBuilder { - SharedMemoryBuilder { - buffer, - capacity, - index: 0, - #[cfg(debug_assertions)] - shared_values: HashSet::new(), - } - } - - /// Returns the number of bytes currently used in the buffer. - #[inline] - pub fn len(&self) -> usize { - self.index - } - - /// Writes a value into the shared memory buffer and returns a pointer to - /// it in the buffer. - /// - /// The value is cloned and converted into a form suitable for placing into - /// a shared memory buffer by calling ToShmem::to_shmem on it. - /// - /// Panics if there is insufficient space in the buffer. - pub fn write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String> { - // Reserve space for the value. - let dest: *mut T = self.alloc_value(); - - // Make a clone of the value with all of its heap allocations - // placed in the shared memory buffer. - let value = value.to_shmem(self)?; - - unsafe { - // Copy the value into the buffer. - ptr::write(dest, ManuallyDrop::into_inner(value)); - } - - // Return a pointer to the shared value. - Ok(dest) - } - - /// Reserves space in the shared memory buffer to fit a value of type T, - /// and returns a pointer to that reserved space. - /// - /// Panics if there is insufficient space in the buffer. - pub fn alloc_value<T>(&mut self) -> *mut T { - self.alloc(Layout::new::<T>()) - } - - /// Reserves space in the shared memory buffer to fit an array of values of - /// type T, and returns a pointer to that reserved space. - /// - /// Panics if there is insufficient space in the buffer. - pub fn alloc_array<T>(&mut self, len: usize) -> *mut T { - if len == 0 { - return NonNull::dangling().as_ptr(); - } - - let size = mem::size_of::<T>(); - let align = mem::align_of::<T>(); - - self.alloc(Layout::from_size_align(padded_size(size, align) * len, align).unwrap()) - } - - /// Reserves space in the shared memory buffer that conforms to the - /// specified layout, and returns a pointer to that reserved space. - /// - /// Panics if there is insufficient space in the buffer. - pub fn alloc<T>(&mut self, layout: Layout) -> *mut T { - // Amount of padding to align the value. - // - // The addition can't overflow, since self.index <= self.capacity, and - // for us to have successfully allocated the buffer, `buffer + capacity` - // can't overflow. - let padding = padding_needed_for(self.buffer as usize + self.index, layout.align()); - - // Reserve space for the padding. - let start = self.index.checked_add(padding).unwrap(); - assert!(start <= std::isize::MAX as usize); // for the cast below - - // Reserve space for the value. - let end = start.checked_add(layout.size()).unwrap(); - assert!(end <= self.capacity); - - self.index = end; - unsafe { self.buffer.add(start) as *mut T } - } -} - -/// A type that can be copied into a SharedMemoryBuilder. -pub trait ToShmem: Sized { - /// Clones this value into a form suitable for writing into a - /// SharedMemoryBuilder. - /// - /// If this value owns any heap allocations, they should be written into - /// `builder` so that the return value of this function can point to the - /// copy in the shared memory buffer. - /// - /// The return type is wrapped in ManuallyDrop to make it harder to - /// accidentally invoke the destructor of the value that is produced. - /// - /// Returns a Result so that we can gracefully recover from unexpected - /// content. - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>; -} - -#[macro_export] -macro_rules! impl_trivial_to_shmem { - ($($ty:ty),*) => { - $( - impl $crate::ToShmem for $ty { - fn to_shmem( - &self, - _builder: &mut $crate::SharedMemoryBuilder, - ) -> $crate::Result<Self> { - $crate::Result::Ok(::std::mem::ManuallyDrop::new(*self)) - } - } - )* - }; -} - -impl_trivial_to_shmem!( - (), - bool, - f32, - f64, - i8, - i16, - i32, - i64, - u8, - u16, - u32, - u64, - isize, - usize -); - -impl_trivial_to_shmem!(cssparser::SourceLocation); -impl_trivial_to_shmem!(cssparser::TokenSerializationType); - -impl<T> ToShmem for PhantomData<T> { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> { - Ok(ManuallyDrop::new(*self)) - } -} - -impl<T: ToShmem> ToShmem for Range<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - Ok(ManuallyDrop::new(Range { - start: ManuallyDrop::into_inner(self.start.to_shmem(builder)?), - end: ManuallyDrop::into_inner(self.end.to_shmem(builder)?), - })) - } -} - -impl ToShmem for cssparser::UnicodeRange { - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> { - Ok(ManuallyDrop::new(cssparser::UnicodeRange { - start: self.start, - end: self.end, - })) - } -} - -impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - Ok(ManuallyDrop::new(( - ManuallyDrop::into_inner(self.0.to_shmem(builder)?), - ManuallyDrop::into_inner(self.1.to_shmem(builder)?), - ))) - } -} - -impl<T: ToShmem> ToShmem for Wrapping<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner( - self.0.to_shmem(builder)?, - )))) - } -} - -impl<T: ToShmem> ToShmem for Box<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - // Reserve space for the boxed value. - let dest: *mut T = builder.alloc_value(); - - // Make a clone of the boxed value with all of its heap allocations - // placed in the shared memory buffer. - let value = (**self).to_shmem(builder)?; - - unsafe { - // Copy the value into the buffer. - ptr::write(dest, ManuallyDrop::into_inner(value)); - - Ok(ManuallyDrop::new(Box::from_raw(dest))) - } - } -} - -/// Converts all the items in `src` into shared memory form, writes them into -/// the specified buffer, and returns a pointer to the slice. -unsafe fn to_shmem_slice_ptr<'a, T, I>( - src: I, - dest: *mut T, - builder: &mut SharedMemoryBuilder, -) -> std::result::Result<*mut [T], String> -where - T: 'a + ToShmem, - I: ExactSizeIterator<Item = &'a T>, -{ - let dest = slice::from_raw_parts_mut(dest, src.len()); - - // Make a clone of each element from the iterator with its own heap - // allocations placed in the buffer, and copy that clone into the buffer. - for (src, dest) in src.zip(dest.iter_mut()) { - ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder)?)); - } - - Ok(dest) -} - -/// Writes all the items in `src` into a slice in the shared memory buffer and -/// returns a pointer to the slice. -pub unsafe fn to_shmem_slice<'a, T, I>( - src: I, - builder: &mut SharedMemoryBuilder, -) -> std::result::Result<*mut [T], String> -where - T: 'a + ToShmem, - I: ExactSizeIterator<Item = &'a T>, -{ - let dest = builder.alloc_array(src.len()); - to_shmem_slice_ptr(src, dest, builder) -} - -impl<T: ToShmem> ToShmem for Box<[T]> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - unsafe { - let dest = to_shmem_slice(self.iter(), builder)?; - Ok(ManuallyDrop::new(Box::from_raw(dest))) - } - } -} - -impl ToShmem for Box<str> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - // Reserve space for the string bytes. - let dest: *mut u8 = builder.alloc_array(self.len()); - - unsafe { - // Copy the value into the buffer. - ptr::copy(self.as_ptr(), dest, self.len()); - - Ok(ManuallyDrop::new(Box::from_raw( - str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(dest, self.len())), - ))) - } - } -} - -impl ToShmem for String { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - // Reserve space for the string bytes. - let dest: *mut u8 = builder.alloc_array(self.len()); - - unsafe { - // Copy the value into the buffer. - ptr::copy(self.as_ptr(), dest, self.len()); - - Ok(ManuallyDrop::new(String::from_raw_parts( - dest, - self.len(), - self.len(), - ))) - } - } -} - -impl ToShmem for CString { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - let len = self.as_bytes_with_nul().len(); - - // Reserve space for the string bytes. - let dest: *mut c_char = builder.alloc_array(len); - - unsafe { - // Copy the value into the buffer. - ptr::copy(self.as_ptr(), dest, len); - - Ok(ManuallyDrop::new(CString::from_raw(dest))) - } - } -} - -impl<T: ToShmem> ToShmem for Vec<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - unsafe { - let dest = to_shmem_slice(self.iter(), builder)? as *mut T; - let dest_vec = Vec::from_raw_parts(dest, self.len(), self.len()); - Ok(ManuallyDrop::new(dest_vec)) - } - } -} - -impl<T: ToShmem, A: Array<Item = T>> ToShmem for SmallVec<A> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - let dest_vec = unsafe { - if self.spilled() { - // Place the items in a separate allocation in the shared memory - // buffer. - let dest = to_shmem_slice(self.iter(), builder)? as *mut T; - SmallVec::from_raw_parts(dest, self.len(), self.len()) - } else { - // Place the items inline. - let mut s = SmallVec::new(); - to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder)?; - s.set_len(self.len()); - s - } - }; - - Ok(ManuallyDrop::new(dest_vec)) - } -} - -impl<T: ToShmem> ToShmem for Option<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - let v = match self { - Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)), - None => None, - }; - - Ok(ManuallyDrop::new(v)) - } -} - -impl<T: ToShmem, S> ToShmem for HashSet<T, S> -where - Self: Default, -{ - fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> { - if !self.is_empty() { - return Err(format!( - "ToShmem failed for HashSet: We only support empty sets \ - (we don't expect custom properties in UA sheets, they're observable by content)", - )); - } - Ok(ManuallyDrop::new(Self::default())) - } -} - -impl<T: ToShmem> ToShmem for Arc<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - // Assert that we don't encounter any shared references to values we - // don't expect. - #[cfg(debug_assertions)] - assert!( - !builder.shared_values.contains(&self.heap_ptr()), - "ToShmem failed for Arc<{}>: encountered a value with multiple \ - references.", - std::any::type_name::<T>() - ); - - // Make a clone of the Arc-owned value with all of its heap allocations - // placed in the shared memory buffer. - let value = (**self).to_shmem(builder)?; - - // Create a new Arc with the shared value and have it place its - // ArcInner in the shared memory buffer. - unsafe { - let static_arc = Arc::new_static( - |layout| builder.alloc(layout), - ManuallyDrop::into_inner(value), - ); - - #[cfg(debug_assertions)] - builder.shared_values.insert(self.heap_ptr()); - - Ok(ManuallyDrop::new(static_arc)) - } - } -} - -impl<H: ToShmem, T: ToShmem> ToShmem for ThinArc<H, T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - // We don't currently have any shared ThinArc values in stylesheets, - // so don't support them for now. - #[cfg(debug_assertions)] - assert!( - !builder.shared_values.contains(&self.heap_ptr()), - "ToShmem failed for ThinArc<T>: encountered a value with multiple references, which \ - is not currently supported", - ); - - // Make a clone of the Arc-owned header and slice values with all of - // their heap allocations placed in the shared memory buffer. - let header = self.header.header.to_shmem(builder)?; - let mut values = Vec::with_capacity(self.slice.len()); - for v in self.slice.iter() { - values.push(v.to_shmem(builder)?); - } - - // Create a new ThinArc with the shared value and have it place - // its ArcInner in the shared memory buffer. - unsafe { - let static_arc = ThinArc::static_from_header_and_iter( - |layout| builder.alloc(layout), - ManuallyDrop::into_inner(header), - values.into_iter().map(ManuallyDrop::into_inner), - ); - - #[cfg(debug_assertions)] - builder.shared_values.insert(self.heap_ptr()); - - Ok(ManuallyDrop::new(static_arc)) - } - } -} - -impl<T: ToShmem> ToShmem for ThinVec<T> { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - let len = self.len(); - if len == 0 { - return Ok(ManuallyDrop::new(Self::new())); - } - - assert_eq!(mem::size_of::<Self>(), mem::size_of::<*const ()>()); - - // nsTArrayHeader size. - // FIXME: Would be nice not to hard-code this, but in practice thin-vec crate also relies - // on this. - let header_size = 2 * mem::size_of::<u32>(); - let header_align = mem::size_of::<u32>(); - - let item_size = mem::size_of::<T>(); - let item_align = mem::align_of::<T>(); - - // We don't need to support underalignment for now, this could be supported if needed. - assert!(item_align >= header_align); - - // This is explicitly unsupported by ThinVec, see: - // https://searchfox.org/mozilla-central/rev/ad732108b073742d7324f998c085f459674a6846/third_party/rust/thin-vec/src/lib.rs#375-386 - assert!(item_align <= header_size); - let header_padding = 0; - - let layout = Layout::from_size_align( - header_size + header_padding + padded_size(item_size, item_align) * len, - item_align, - ) - .unwrap(); - - let shmem_header_ptr = builder.alloc::<u8>(layout); - let shmem_data_ptr = unsafe { shmem_header_ptr.add(header_size + header_padding) }; - - let data_ptr = self.as_ptr() as *const T as *const u8; - let header_ptr = unsafe { data_ptr.sub(header_size + header_padding) }; - - unsafe { - // Copy the header. Note this might copy a wrong capacity, but it doesn't matter, - // because shared memory ptrs are immutable anyways, and we can't relocate. - ptr::copy(header_ptr, shmem_header_ptr, header_size); - // ToShmem + copy the contents into the shared buffer. - to_shmem_slice_ptr(self.iter(), shmem_data_ptr as *mut T, builder)?; - // Return the new ThinVec, which is just a pointer to the shared memory buffer. - let shmem_thinvec: Self = mem::transmute(shmem_header_ptr); - - // Sanity-check that the ptr and length match. - debug_assert_eq!(shmem_thinvec.as_ptr(), shmem_data_ptr as *const T); - debug_assert_eq!(shmem_thinvec.len(), len); - - Ok(ManuallyDrop::new(shmem_thinvec)) - } - } -} - -impl ToShmem for SmallBitVec { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> { - let storage = match self.clone().into_storage() { - InternalStorage::Spilled(vs) => { - // Reserve space for the boxed slice values. - let len = vs.len(); - let dest: *mut usize = builder.alloc_array(len); - - unsafe { - // Copy the value into the buffer. - let src = vs.as_ptr() as *const usize; - ptr::copy(src, dest, len); - - let dest_slice = - Box::from_raw(slice::from_raw_parts_mut(dest, len) as *mut [usize]); - InternalStorage::Spilled(dest_slice) - } - }, - InternalStorage::Inline(x) => InternalStorage::Inline(x), - }; - Ok(ManuallyDrop::new(unsafe { - SmallBitVec::from_storage(storage) - })) - } -} - -#[cfg(feature = "string_cache")] -impl<Static: string_cache::StaticAtomSet> ToShmem for string_cache::Atom<Static> { - fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self> { - // NOTE(emilio): In practice, this can be implemented trivially if - // string_cache could expose the implementation detail of static atoms - // being an index into the static table (and panicking in the - // non-static, non-inline cases). - unimplemented!( - "If servo wants to share stylesheets across processes, \ - then ToShmem for Atom needs to be implemented" - ) - } -} diff --git a/components/to_shmem/rustfmt.toml b/components/to_shmem/rustfmt.toml deleted file mode 100644 index c7ad93bafe3..00000000000 --- a/components/to_shmem/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -disable_all_formatting = true diff --git a/components/to_shmem_derive/Cargo.toml b/components/to_shmem_derive/Cargo.toml deleted file mode 100644 index 2aa8b6b145f..00000000000 --- a/components/to_shmem_derive/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "to_shmem_derive" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -publish = false - -[lib] -path = "lib.rs" -proc-macro = true - -[dependencies] -darling = { workspace = true } -derive_common = { path = "../derive_common" } -proc-macro2 = { workspace = true } -quote = { workspace = true } -syn = { workspace = true } -synstructure = { workspace = true } diff --git a/components/to_shmem_derive/lib.rs b/components/to_shmem_derive/lib.rs deleted file mode 100644 index b820e7f85d0..00000000000 --- a/components/to_shmem_derive/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -/* 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/. */ - -#![recursion_limit = "128"] - -#[macro_use] -extern crate darling; -extern crate derive_common; -extern crate proc_macro; -extern crate proc_macro2; -#[macro_use] -extern crate quote; -#[macro_use] -extern crate syn; -extern crate synstructure; - -use proc_macro::TokenStream; - -mod to_shmem; - -#[proc_macro_derive(ToShmem, attributes(shmem))] -pub fn derive_to_shmem(stream: TokenStream) -> TokenStream { - let input = syn::parse(stream).unwrap(); - to_shmem::derive(input).into() -} diff --git a/components/to_shmem_derive/to_shmem.rs b/components/to_shmem_derive/to_shmem.rs deleted file mode 100644 index 157730c5a51..00000000000 --- a/components/to_shmem_derive/to_shmem.rs +++ /dev/null @@ -1,78 +0,0 @@ -/* 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/. */ - -use derive_common::cg; -use proc_macro2::TokenStream; -use syn; -use synstructure::{BindStyle, Structure}; - -pub fn derive(mut input: syn::DeriveInput) -> TokenStream { - let mut where_clause = input.generics.where_clause.take(); - let attrs = cg::parse_input_attrs::<ShmemInputAttrs>(&input); - if !attrs.no_bounds { - for param in input.generics.type_params() { - cg::add_predicate(&mut where_clause, parse_quote!(#param: ::to_shmem::ToShmem)); - } - } - for variant in Structure::new(&input).variants() { - for binding in variant.bindings() { - let attrs = cg::parse_field_attrs::<ShmemFieldAttrs>(&binding.ast()); - if attrs.field_bound { - let ty = &binding.ast().ty; - cg::add_predicate(&mut where_clause, parse_quote!(#ty: ::to_shmem::ToShmem)) - } - } - } - - input.generics.where_clause = where_clause; - - // Do all of the `to_shmem()?` calls before the `ManuallyDrop::into_inner()` - // calls, so that we don't drop a value in the shared memory buffer if one - // of the `to_shmem`s fails. - let match_body = cg::fmap2_match( - &input, - BindStyle::Ref, - |binding| { - quote! { - ::to_shmem::ToShmem::to_shmem(#binding, builder)? - } - }, - |binding| { - Some(quote! { - ::std::mem::ManuallyDrop::into_inner(#binding) - }) - }, - ); - - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - quote! { - impl #impl_generics ::to_shmem::ToShmem for #name #ty_generics #where_clause { - #[allow(unused_variables, unreachable_code)] - fn to_shmem( - &self, - builder: &mut ::to_shmem::SharedMemoryBuilder, - ) -> ::to_shmem::Result<Self> { - Ok(::std::mem::ManuallyDrop::new( - match *self { - #match_body - } - )) - } - } - } -} - -#[derive(Default, FromDeriveInput)] -#[darling(attributes(shmem), default)] -pub struct ShmemInputAttrs { - pub no_bounds: bool, -} - -#[derive(Default, FromField)] -#[darling(attributes(shmem), default)] -pub struct ShmemFieldAttrs { - pub field_bound: bool, -} diff --git a/components/url/Cargo.toml b/components/url/Cargo.toml index 69706b0e246..65c3fa75967 100644 --- a/components/url/Cargo.toml +++ b/components/url/Cargo.toml @@ -11,11 +11,11 @@ name = "servo_url" path = "lib.rs" [dependencies] -malloc_size_of = { path = "../malloc_size_of", features = ["servo"] } +malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } serde = { workspace = true, features = ["derive"] } -servo_arc = { path = "../servo_arc" } +servo_arc = { workspace = true } servo_rand = { path = "../rand" } -to_shmem = { path = "../to_shmem" } +to_shmem = { workspace = true } url = { workspace = true, features = ["serde"] } uuid = { workspace = true, features = ["serde"] } diff --git a/components/webgpu/Cargo.toml b/components/webgpu/Cargo.toml index efc978f8580..4e70928c6e2 100644 --- a/components/webgpu/Cargo.toml +++ b/components/webgpu/Cargo.toml @@ -15,7 +15,7 @@ arrayvec = { workspace = true, features = ["serde"] } euclid = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } -malloc_size_of = { path = "../malloc_size_of" } +malloc_size_of = { workspace = true } msg = { workspace = true } serde = { workspace = true, features = ["serde_derive"] } servo_config = { path = "../config" } diff --git a/etc/ci/generate_workflow.py b/etc/ci/generate_workflow.py deleted file mode 100644 index a3295e1c0a2..00000000000 --- a/etc/ci/generate_workflow.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2021 The Servo Project Developers. See the COPYRIGHT -# file at the top-level directory of this distribution. -# -# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or -# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -# option. This file may not be copied, modified, or distributed -# except according to those terms. - -import os.path -import sys - -BASE = os.path.dirname(__file__.replace('\\', '/')) -sys.path.insert(0, os.path.join( - BASE, "..", "..", "components", "style", "properties", "Mako-1.1.2-py2.py3-none-any.whl" -)) - -from mako import exceptions # noqa -from mako.lookup import TemplateLookup # noqa -from mako.template import Template # noqa - - -def abort(message): - print(message, file=sys.stderr) - sys.exit(1) - - -def render(filename, **context): - try: - lookup = TemplateLookup(directories=[BASE], - input_encoding="utf8", - strict_undefined=True) - template = Template(open(os.path.join(BASE, filename), "rb").read(), - filename=filename, - input_encoding="utf8", - lookup=lookup, - strict_undefined=True) - # Uncomment to debug generated Python code: - # write("/tmp", "mako_%s.py" % os.path.basename(filename), template.code) - return template.render(**context) - except Exception: - # Uncomment to see a traceback in generated Python code: - # raise - abort(exceptions.text_error_template().render()) - - -def main(): - with open(os.path.join(".github", "workflows", "main.yml"), 'w') as f: - f.write(render( - 'workflow.mako', - total_chunks=20, - REPOSITORY_NAME="${{ github.event.repository.name }}", - CODESIGN_CERT="${{ secrets.WINDOWS_CODESIGN_CERT }}", - )) - - -if __name__ == "__main__": - main() diff --git a/python/requirements.txt b/python/requirements.txt index 7c56901599f..dc04dd0eae0 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -36,3 +36,6 @@ notify-py == 0.3.42 flask requests types-requests + +# For mach package on macOS. +Mako == 1.1.2 diff --git a/python/servo/package_commands.py b/python/servo/package_commands.py index ab8cbacb2cd..54afdd6a9e4 100644 --- a/python/servo/package_commands.py +++ b/python/servo/package_commands.py @@ -39,10 +39,6 @@ from servo.build_commands import copy_dependencies from servo.gstreamer import macos_gst_root from servo.util import delete, get_target_dir -# Note: mako cannot be imported at the top level because it breaks mach bootstrap -sys.path.append(path.join(path.dirname(__file__), "..", "..", - "components", "style", "properties", "Mako-1.1.2-py2.py3-none-any.whl")) - PACKAGES = { 'android': [ 'android/armv7-linux-androideabi/production/servoapp.apk', diff --git a/servo-tidy.toml b/servo-tidy.toml index 25d533e56b6..184616cb5ff 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -66,11 +66,6 @@ packages = [ # Files that are ignored for all tidy and lint checks. files = [ "./components/net/tests/parsable_mime/text", - # Ignore selectors and style files to avoid diverging too much from upstream Gecko - "./components/selectors/", - "./components/style/", - "./components/style_derive/parse.rs", - "./components/to_shmem/lib.rs", "./resources/hsts_preload.json", "./tests/wpt/meta/MANIFEST.json", "./tests/wpt/meta-legacy-layout/MANIFEST.json", @@ -90,8 +85,6 @@ files = [ "./tests/wpt/mozilla/tests/css/fonts", "./tests/wpt/mozilla/tests/css/pre_with_tab.html", "./tests/wpt/mozilla/tests/mozilla/textarea_placeholder.html", - # Python 3 syntax causes "E901 SyntaxError" when flake8 runs in Python 2 - "./components/style/properties/build.py", # The tidy tests currently don't pass tidy. "./python/tidy/test.py", ] diff --git a/tests/unit/malloc_size_of/Cargo.toml b/tests/unit/malloc_size_of/Cargo.toml index 049c750972f..50051762a65 100644 --- a/tests/unit/malloc_size_of/Cargo.toml +++ b/tests/unit/malloc_size_of/Cargo.toml @@ -10,5 +10,5 @@ path = "lib.rs" test = false [dependencies] -malloc_size_of = {path = "../../../components/malloc_size_of"} -servo_arc = {path = "../../../components/servo_arc"} +malloc_size_of = { workspace = true } +servo_arc = { workspace = true } diff --git a/tests/unit/style/Cargo.toml b/tests/unit/style/Cargo.toml index ef02ffd00b3..d3d1bbd1c9d 100644 --- a/tests/unit/style/Cargo.toml +++ b/tests/unit/style/Cargo.toml @@ -16,9 +16,9 @@ euclid = { workspace = true } html5ever = { workspace = true } rayon = { workspace = true } serde_json = { workspace = true } -selectors = {path = "../../../components/selectors" } -servo_arc = {path = "../../../components/servo_arc"} -servo_atoms = {path = "../../../components/atoms"} -style = {path = "../../../components/style", features = ["servo"]} -style_traits = {path = "../../../components/style_traits"} +selectors = { workspace = true } +servo_arc = { workspace = true } +servo_atoms = { workspace = true } +style = { workspace = true } +style_traits = { workspace = true } url = { workspace = true } diff --git a/components/style/stylesheets/font_face_rule.rs b/tests/unit/style/build.rs index 78f3b338b22..8a25184fd27 100644 --- a/components/style/stylesheets/font_face_rule.rs +++ b/tests/unit/style/build.rs @@ -2,6 +2,7 @@ * 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/. */ -#![allow(missing_docs)] - -pub use crate::font_face::FontFaceRuleData as FontFaceRule; +fn main() { + // Dummy build script, just so the code can get env!("OUT_DIR"). + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tests/unit/style/properties/scaffolding.rs b/tests/unit/style/properties/scaffolding.rs index e9f7148e2e5..fc2a9f1405d 100644 --- a/tests/unit/style/properties/scaffolding.rs +++ b/tests/unit/style/properties/scaffolding.rs @@ -10,14 +10,17 @@ use serde_json::{self, Value}; #[test] fn properties_list_json() { - let top = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()) + // Four dotdots: /path/to/target(4)/debug(3)/build(2)/style_tests-*(1)/out + // Do not ascend above the target dir, because it may not be called target + // or even have a parent (see CARGO_TARGET_DIR). + let target_dir = Path::new(env!("OUT_DIR")) + .join("..") .join("..") .join("..") .join(".."); - let json = top - .join("target") + let json = target_dir .join("doc") - .join("servo") + .join("stylo") .join("css-properties.json"); let properties: Value = serde_json::from_reader(File::open(json).unwrap()).unwrap(); |