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 9e71bd6a70.

* 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
This commit is contained in:
Delan Azabani 2024-02-27 23:39:06 +08:00 committed by GitHub
parent b07505417e
commit faf754dfa6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
400 changed files with 112 additions and 123600 deletions

23
Cargo.lock generated
View file

@ -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",
@ -3700,12 +3702,6 @@ dependencies = [
"walkdir",
]
[[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"
@ -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",

View file

@ -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" }

View file

@ -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 }

View file

@ -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();
}

View file

@ -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"));

View file

@ -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

View file

@ -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 }

View file

@ -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]

View file

@ -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 }

View file

@ -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| &param.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)
}

View file

@ -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;

View file

@ -1 +0,0 @@
disable_all_formatting = true

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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.

View file

@ -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.

View file

@ -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)
}
}

View file

@ -1 +0,0 @@
disable_all_formatting = true

View file

@ -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 }

View file

@ -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"

View file

@ -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"] }

View file

@ -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 }

View file

@ -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"

View file

@ -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"

View file

@ -1,25 +0,0 @@
rust-selectors
==============
* [![Build Status](https://travis-ci.com/servo/rust-selectors.svg?branch=master)](
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 [Kuchikis `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 dont already have a tree data structure,
consider using [Kuchiki](https://github.com/kuchiki-rs/kuchiki) itself.

View file

@ -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
}
},
}
}
}

View file

@ -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())
}
});
}
}

View file

@ -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
"#;

View file

@ -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
// were 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 weve called SmallVec::set_len(0) above,
// so SmallVec::drop wont 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))
}

View file

@ -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
}
}

View file

@ -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};

File diff suppressed because it is too large Load diff

View file

@ -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()
}
}

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
disable_all_formatting = true

View file

@ -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);
}
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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 }

View file

@ -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"

View file

@ -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.

View file

@ -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.

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
disable_all_formatting = true

View file

@ -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"] }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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 }

View file

@ -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"

View file

@ -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);
};
}

View file

@ -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"

View file

@ -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).

File diff suppressed because it is too large Load diff

View file

@ -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);

View file

@ -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>,
}

View file

@ -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;
}
}
}

View file

@ -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
}
}

View file

@ -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.
}
}

View file

@ -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();
}

View file

@ -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());
}
}

View file

@ -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()
}
}

View file

@ -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)
}

View file

@ -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)
},
}
}
}

View file

@ -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>;
}

View file

@ -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 wed 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 dont 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)?)))
}
}

View file

@ -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",
}

View file

@ -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"))

File diff suppressed because it is too large Load diff

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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();
}
}
}
}

View file

@ -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
}

View file

@ -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,
)
}
}

View file

@ -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
)
}
}
}

View file

@ -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 wed 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 doesnt 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 = [
]
}

View file

@ -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,
}

View file

@ -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
);

View file

@ -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,
]
}
}

View file

@ -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>;

File diff suppressed because it is too large Load diff

View file

@ -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()
}
}

View file

@ -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;

View file

@ -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, _, _),
]
}
}
}

View file

@ -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
}
}

View file

@ -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(())
}
}

View file

@ -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])

View file

@ -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))
}
}

View file

@ -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);

View file

@ -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) }))
}
}
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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()
};
}

Some files were not shown because too many files have changed in this diff Show more