Auto merge of #26775 - servo:gecko-sync, r=emilio

style: sync changes from mozilla-central.
This commit is contained in:
bors-servo 2020-06-03 23:02:22 -04:00 committed by GitHub
commit 4d95efeebf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 2353 additions and 1325 deletions

4
Cargo.lock generated
View file

@ -5328,9 +5328,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]] [[package]]
name = "smallbitvec" name = "smallbitvec"
version = "2.3.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e" checksum = "797a4eaffb90d896f29698d45676f9f940a71936d7574996a7df54593ba209fa"
[[package]] [[package]]
name = "smallvec" name = "smallvec"

View file

@ -85,9 +85,22 @@ pub fn add_predicate(where_clause: &mut Option<syn::WhereClause>, pred: WherePre
.push(pred); .push(pred);
} }
pub fn fmap_match<F>(input: &DeriveInput, bind_style: BindStyle, mut f: F) -> TokenStream pub fn fmap_match<F>(input: &DeriveInput, bind_style: BindStyle, f: F) -> TokenStream
where where
F: FnMut(BindingInfo) -> TokenStream, 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); let mut s = synstructure::Structure::new(input);
s.variants_mut().iter_mut().for_each(|v| { s.variants_mut().iter_mut().for_each(|v| {
@ -95,12 +108,20 @@ where
}); });
s.each_variant(|variant| { s.each_variant(|variant| {
let (mapped, mapped_fields) = value(variant, "mapped"); let (mapped, mapped_fields) = value(variant, "mapped");
let fields_pairs = variant.bindings().iter().zip(mapped_fields); let fields_pairs = variant.bindings().iter().zip(mapped_fields.iter());
let mut computations = quote!(); let mut computations = quote!();
computations.append_all(fields_pairs.map(|(field, mapped_field)| { computations.append_all(fields_pairs.map(|(field, mapped_field)| {
let expr = f(field.clone()); let expr = f(field);
quote! { let #mapped_field = #expr; } 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); computations.append_all(mapped);
Some(computations) Some(computations)
}) })

View file

@ -3331,7 +3331,7 @@ impl Document {
pub fn element_state_will_change(&self, el: &Element) { pub fn element_state_will_change(&self, el: &Element) {
let mut entry = self.ensure_pending_restyle(el); let mut entry = self.ensure_pending_restyle(el);
if entry.snapshot.is_none() { if entry.snapshot.is_none() {
entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document())); entry.snapshot = Some(Snapshot::new());
} }
let snapshot = entry.snapshot.as_mut().unwrap(); let snapshot = entry.snapshot.as_mut().unwrap();
if snapshot.state.is_none() { if snapshot.state.is_none() {
@ -3347,7 +3347,7 @@ impl Document {
// could in theory do it in the DOM I think. // could in theory do it in the DOM I think.
let mut entry = self.ensure_pending_restyle(el); let mut entry = self.ensure_pending_restyle(el);
if entry.snapshot.is_none() { if entry.snapshot.is_none() {
entry.snapshot = Some(Snapshot::new(el.html_element_in_html_document())); entry.snapshot = Some(Snapshot::new());
} }
if attr.local_name() == &local_name!("style") { if attr.local_name() == &local_name!("style") {
entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE); entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
@ -3359,12 +3359,21 @@ impl Document {
let snapshot = entry.snapshot.as_mut().unwrap(); let snapshot = entry.snapshot.as_mut().unwrap();
if attr.local_name() == &local_name!("id") { if attr.local_name() == &local_name!("id") {
if snapshot.id_changed {
return;
}
snapshot.id_changed = true; snapshot.id_changed = true;
} else if attr.local_name() == &local_name!("class") { } else if attr.local_name() == &local_name!("class") {
if snapshot.class_changed {
return;
}
snapshot.class_changed = true; snapshot.class_changed = true;
} else { } else {
snapshot.other_attributes_changed = true; snapshot.other_attributes_changed = true;
} }
if !snapshot.changed_attrs.contains(attr.local_name()) {
snapshot.changed_attrs.push(attr.local_name().clone());
}
if snapshot.attrs.is_none() { if snapshot.attrs.is_none() {
let attrs = el let attrs = el
.attrs() .attrs()

View file

@ -322,13 +322,7 @@ where
}, },
} }
// The only other parser-allowed Component in this sequence is a state if !iter.matches_for_stateless_pseudo_element() {
// class. We just don't match in that case.
if let Some(s) = iter.next() {
debug_assert!(
matches!(*s, Component::NonTSPseudoClass(..)),
"Someone messed up pseudo-element parsing"
);
return false; return false;
} }

View file

@ -104,41 +104,57 @@ bitflags! {
/// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
/// as well. /// as well.
const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
/// Whether we are after any of the pseudo-like things. /// Whether we are after any of the pseudo-like things.
const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits; const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
/// Whether we explicitly disallow combinators.
const DISALLOW_COMBINATORS = 1 << 5;
/// Whether we explicitly disallow pseudo-element-like things.
const DISALLOW_PSEUDOS = 1 << 6;
} }
} }
impl SelectorParsingState { impl SelectorParsingState {
#[inline] #[inline]
fn allows_functional_pseudo_classes(self) -> bool { fn allows_pseudos(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) // NOTE(emilio): We allow pseudos after ::part and such.
!self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS)
} }
#[inline] #[inline]
fn allows_slotted(self) -> bool { fn allows_slotted(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
} }
// TODO(emilio): Should we allow other ::part()s after ::part()?
//
// See https://github.com/w3c/csswg-drafts/issues/3841
#[inline] #[inline]
fn allows_part(self) -> bool { fn allows_part(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS)
}
// TODO(emilio): Maybe some of these should be allowed, but this gets us on
// the safe side for now, matching previous behavior. Gotta be careful with
// the ones like :-moz-any, which allow nested selectors but don't carry the
// state, and so on.
#[inline]
fn allows_custom_functional_pseudo_classes(self) -> bool {
!self.intersects(Self::AFTER_PSEUDO)
} }
#[inline] #[inline]
fn allows_non_functional_pseudo_classes(self) -> bool { fn allows_non_functional_pseudo_classes(self) -> bool {
!self.intersects( !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
SelectorParsingState::AFTER_SLOTTED |
SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT,
)
} }
#[inline] #[inline]
fn allows_tree_structural_pseudo_classes(self) -> bool { fn allows_tree_structural_pseudo_classes(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO) !self.intersects(Self::AFTER_PSEUDO)
}
#[inline]
fn allows_combinators(self) -> bool {
!self.intersects(Self::DISALLOW_COMBINATORS)
} }
} }
@ -146,7 +162,6 @@ pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum SelectorParseErrorKind<'i> { pub enum SelectorParseErrorKind<'i> {
PseudoElementInComplexSelector,
NoQualifiedNameInAttributeSelector(Token<'i>), NoQualifiedNameInAttributeSelector(Token<'i>),
EmptySelector, EmptySelector,
DanglingCombinator, DanglingCombinator,
@ -321,15 +336,25 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
) -> Result<Self, ParseError<'i, P::Error>> ) -> Result<Self, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
{
Self::parse_with_state(parser, input, SelectorParsingState::empty())
}
fn parse_with_state<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Self, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
{ {
let mut values = SmallVec::new(); let mut values = SmallVec::new();
loop { loop {
values.push( values.push(input.parse_until_before(Delimiter::Comma, |input| {
input parse_selector(parser, input, state)
.parse_until_before(Delimiter::Comma, |input| parse_selector(parser, input))?, })?);
);
match input.next() { match input.next() {
Err(_) => return Ok(SelectorList(values)), Err(_) => return Ok(SelectorList(values)),
Ok(&Token::Comma) => continue, Ok(&Token::Comma) => continue,
@ -344,30 +369,21 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
} }
} }
/// Parses one compound selector suitable for nested stuff like ::-moz-any, etc. /// Parses one compound selector suitable for nested stuff like :-moz-any, etc.
fn parse_inner_compound_selector<'i, 't, P, Impl>( fn parse_inner_compound_selector<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>> ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
let location = input.current_source_location(); parse_selector(
let selector = parse_selector(parser, input)?; parser,
input,
// Ensure they're actually all compound selectors without pseudo-elements. state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS,
if selector.has_pseudo_element() { )
return Err(
location.new_custom_error(SelectorParseErrorKind::PseudoElementInComplexSelector)
);
}
if selector.iter_raw_match_order().any(|s| s.is_combinator()) {
return Err(location.new_custom_error(SelectorParseErrorKind::NonCompoundSelector));
}
Ok(selector)
} }
/// Parse a comma separated list of compound selectors. /// Parse a comma separated list of compound selectors.
@ -380,7 +396,9 @@ where
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
input input
.parse_comma_separated(|input| parse_inner_compound_selector(parser, input)) .parse_comma_separated(|input| {
parse_inner_compound_selector(parser, input, SelectorParsingState::empty())
})
.map(|selectors| selectors.into_boxed_slice()) .map(|selectors| selectors.into_boxed_slice())
} }
@ -404,18 +422,66 @@ pub struct AncestorHashes {
pub packed_hashes: [u32; 3], pub packed_hashes: [u32; 3],
} }
fn collect_ancestor_hashes<Impl: SelectorImpl>(
iter: SelectorIter<Impl>,
quirks_mode: QuirksMode,
hashes: &mut [u32; 4],
len: &mut usize,
) -> bool
where
Impl::Identifier: PrecomputedHash,
Impl::ClassName: PrecomputedHash,
Impl::LocalName: PrecomputedHash,
Impl::NamespaceUrl: PrecomputedHash,
{
for component in AncestorIter::new(iter) {
let hash = match *component {
Component::LocalName(LocalName {
ref name,
ref lower_name,
}) => {
// Only insert the local-name into the filter if it's all
// lowercase. Otherwise we would need to test both hashes, and
// our data structures aren't really set up for that.
if name != lower_name {
continue;
}
name.precomputed_hash()
},
Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => {
url.precomputed_hash()
},
// In quirks mode, class and id selectors should match
// case-insensitively, so just avoid inserting them into the filter.
Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(),
Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => {
class.precomputed_hash()
},
Component::Is(ref list) | Component::Where(ref list) => {
// :where and :is OR their selectors, so we can't put any hash
// in the filter if there's more than one selector, as that'd
// exclude elements that may match one of the other selectors.
if list.len() == 1 {
if !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) {
return false;
}
}
continue;
},
_ => continue,
};
hashes[*len] = hash & BLOOM_HASH_MASK;
*len += 1;
if *len == hashes.len() {
return false;
}
}
true
}
impl AncestorHashes { impl AncestorHashes {
pub fn new<Impl: SelectorImpl>(selector: &Selector<Impl>, quirks_mode: QuirksMode) -> Self pub fn new<Impl: SelectorImpl>(selector: &Selector<Impl>, quirks_mode: QuirksMode) -> Self
where
Impl::Identifier: PrecomputedHash,
Impl::ClassName: PrecomputedHash,
Impl::LocalName: PrecomputedHash,
Impl::NamespaceUrl: PrecomputedHash,
{
Self::from_iter(selector.iter(), quirks_mode)
}
fn from_iter<Impl: SelectorImpl>(iter: SelectorIter<Impl>, quirks_mode: QuirksMode) -> Self
where where
Impl::Identifier: PrecomputedHash, Impl::Identifier: PrecomputedHash,
Impl::ClassName: PrecomputedHash, Impl::ClassName: PrecomputedHash,
@ -424,18 +490,14 @@ impl AncestorHashes {
{ {
// Compute ancestor hashes for the bloom filter. // Compute ancestor hashes for the bloom filter.
let mut hashes = [0u32; 4]; let mut hashes = [0u32; 4];
let mut hash_iter = AncestorIter::new(iter).filter_map(|x| x.ancestor_hash(quirks_mode)); let mut len = 0;
for i in 0..4 { collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len);
hashes[i] = match hash_iter.next() { debug_assert!(len <= 4);
Some(x) => x & BLOOM_HASH_MASK,
None => break,
}
}
// Now, pack the fourth hash (if it exists) into the upper byte of each of // Now, pack the fourth hash (if it exists) into the upper byte of each of
// the other three hashes. // the other three hashes.
let fourth = hashes[3]; if len == 4 {
if fourth != 0 { let fourth = hashes[3];
hashes[0] |= (fourth & 0x000000ff) << 24; hashes[0] |= (fourth & 0x000000ff) << 24;
hashes[1] |= (fourth & 0x0000ff00) << 16; hashes[1] |= (fourth & 0x0000ff00) << 16;
hashes[2] |= (fourth & 0x00ff0000) << 8; hashes[2] |= (fourth & 0x00ff0000) << 8;
@ -741,6 +803,33 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
self.next_sequence().is_none() self.next_sequence().is_none()
} }
#[inline]
pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool {
let first = match self.next() {
Some(c) => c,
// Note that this is the common path that we keep inline: the
// pseudo-element not having anything to its right.
None => return true,
};
self.matches_for_stateless_pseudo_element_internal(first)
}
#[inline(never)]
fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<Impl>) -> bool {
if !first.matches_for_stateless_pseudo_element() {
return false;
}
for component in self {
// The only other parser-allowed Components in this sequence are
// state pseudo-classes, or one of the other things that can contain
// them.
if !component.matches_for_stateless_pseudo_element() {
return false;
}
}
true
}
/// Returns remaining count of the simple selectors and combinators in the Selector. /// Returns remaining count of the simple selectors and combinators in the Selector.
#[inline] #[inline]
pub fn selector_length(&self) -> usize { pub fn selector_length(&self) -> usize {
@ -980,43 +1069,6 @@ pub enum Component<Impl: SelectorImpl> {
} }
impl<Impl: SelectorImpl> Component<Impl> { impl<Impl: SelectorImpl> Component<Impl> {
/// Compute the ancestor hash to check against the bloom filter.
fn ancestor_hash(&self, quirks_mode: QuirksMode) -> Option<u32>
where
Impl::Identifier: PrecomputedHash,
Impl::ClassName: PrecomputedHash,
Impl::LocalName: PrecomputedHash,
Impl::NamespaceUrl: PrecomputedHash,
{
match *self {
Component::LocalName(LocalName {
ref name,
ref lower_name,
}) => {
// Only insert the local-name into the filter if it's all
// lowercase. Otherwise we would need to test both hashes, and
// our data structures aren't really set up for that.
if name == lower_name {
Some(name.precomputed_hash())
} else {
None
}
},
Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => {
Some(url.precomputed_hash())
},
// In quirks mode, class and id selectors should match
// case-insensitively, so just avoid inserting them into the filter.
Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => {
Some(id.precomputed_hash())
},
Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => {
Some(class.precomputed_hash())
},
_ => None,
}
}
/// Returns true if this is a combinator. /// Returns true if this is a combinator.
pub fn is_combinator(&self) -> bool { pub fn is_combinator(&self) -> bool {
matches!(*self, Component::Combinator(_)) matches!(*self, Component::Combinator(_))
@ -1030,6 +1082,52 @@ impl<Impl: SelectorImpl> Component<Impl> {
} }
} }
/// Whether this component is valid after a pseudo-element. Only intended
/// for sanity-checking.
pub fn maybe_allowed_after_pseudo_element(&self) -> bool {
match *self {
Component::NonTSPseudoClass(..) => true,
Component::Negation(ref components) => components
.iter()
.all(|c| c.maybe_allowed_after_pseudo_element()),
Component::Is(ref selectors) | Component::Where(ref selectors) => {
selectors.iter().all(|selector| {
selector
.iter_raw_match_order()
.all(|c| c.maybe_allowed_after_pseudo_element())
})
},
_ => false,
}
}
/// Whether a given selector should match for stateless pseudo-elements.
///
/// This is a bit subtle: Only selectors that return true in
/// `maybe_allowed_after_pseudo_element` should end up here, and
/// `NonTSPseudoClass` never matches (as it is a stateless pseudo after
/// all).
fn matches_for_stateless_pseudo_element(&self) -> bool {
debug_assert!(
self.maybe_allowed_after_pseudo_element(),
"Someone messed up pseudo-element parsing: {:?}",
*self
);
match *self {
Component::Negation(ref components) => !components
.iter()
.all(|c| c.matches_for_stateless_pseudo_element()),
Component::Is(ref selectors) | Component::Where(ref selectors) => {
selectors.iter().any(|selector| {
selector
.iter_raw_match_order()
.all(|c| c.matches_for_stateless_pseudo_element())
})
},
_ => false,
}
}
pub fn visit<V>(&self, visitor: &mut V) -> bool pub fn visit<V>(&self, visitor: &mut V) -> bool
where where
V: SelectorVisitor<Impl = Impl>, V: SelectorVisitor<Impl = Impl>,
@ -1533,6 +1631,7 @@ fn display_to_css_identifier<T: Display, W: fmt::Write>(x: &T, dest: &mut W) ->
fn parse_selector<'i, 't, P, Impl>( fn parse_selector<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
mut state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>> ) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
@ -1545,16 +1644,14 @@ where
let mut part = false; let mut part = false;
'outer_loop: loop { 'outer_loop: loop {
// Parse a sequence of simple selectors. // Parse a sequence of simple selectors.
let state = match parse_compound_selector(parser, input, &mut builder)? { let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
Some(state) => state, if empty {
None => { return Err(input.new_custom_error(if builder.has_combinators() {
return Err(input.new_custom_error(if builder.has_combinators() { SelectorParseErrorKind::DanglingCombinator
SelectorParseErrorKind::DanglingCombinator } else {
} else { SelectorParseErrorKind::EmptySelector
SelectorParseErrorKind::EmptySelector }));
})); }
},
};
if state.intersects(SelectorParsingState::AFTER_PSEUDO) { if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT); has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT);
@ -1595,6 +1692,11 @@ where
}, },
} }
} }
if !state.allows_combinators() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
builder.push_combinator(combinator); builder.push_combinator(combinator);
} }
@ -1611,7 +1713,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
{ {
parse_selector(parser, input) parse_selector(parser, input, SelectorParsingState::empty())
} }
} }
@ -1621,6 +1723,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
fn parse_type_selector<'i, 't, P, Impl, S>( fn parse_type_selector<'i, 't, P, Impl, S>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
sink: &mut S, sink: &mut S,
) -> Result<bool, ParseError<'i, P::Error>> ) -> Result<bool, ParseError<'i, P::Error>>
where where
@ -1635,6 +1738,9 @@ where
}) | }) |
Ok(OptionalQName::None(_)) => Ok(false), Ok(OptionalQName::None(_)) => Ok(false),
Ok(OptionalQName::Some(namespace, local_name)) => { Ok(OptionalQName::Some(namespace, local_name)) => {
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
match namespace { match namespace {
QNamePrefix::ImplicitAnyNamespace => {}, QNamePrefix::ImplicitAnyNamespace => {},
QNamePrefix::ImplicitDefaultNamespace(url) => { QNamePrefix::ImplicitDefaultNamespace(url) => {
@ -2006,11 +2112,14 @@ fn parse_attribute_flags<'i, 't>(
fn parse_negation<'i, 't, P, Impl>( fn parse_negation<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
let state = state | SelectorParsingState::INSIDE_NEGATION;
// We use a sequence because a type selector may be represented as two Components. // We use a sequence because a type selector may be represented as two Components.
let mut sequence = SmallVec::<[Component<Impl>; 2]>::new(); let mut sequence = SmallVec::<[Component<Impl>; 2]>::new();
@ -2018,7 +2127,7 @@ where
// Get exactly one simple selector. The parse logic in the caller will verify // Get exactly one simple selector. The parse logic in the caller will verify
// that there are no trailing tokens after we're done. // that there are no trailing tokens after we're done.
let is_type_sel = match parse_type_selector(parser, input, &mut sequence) { let is_type_sel = match parse_type_selector(parser, input, state, &mut sequence) {
Ok(result) => result, Ok(result) => result,
Err(ParseError { Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
@ -2027,7 +2136,7 @@ where
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
if !is_type_sel { if !is_type_sel {
match parse_one_simple_selector(parser, input, SelectorParsingState::INSIDE_NEGATION)? { match parse_one_simple_selector(parser, input, state)? {
Some(SimpleSelectorParseResult::SimpleSelector(s)) => { Some(SimpleSelectorParseResult::SimpleSelector(s)) => {
sequence.push(s); sequence.push(s);
}, },
@ -2054,12 +2163,13 @@ where
/// | [ HASH | class | attrib | pseudo | negation ]+ /// | [ HASH | class | attrib | pseudo | negation ]+
/// ///
/// `Err(())` means invalid selector. /// `Err(())` means invalid selector.
/// `Ok(None)` is an empty selector /// `Ok(true)` is an empty selector
fn parse_compound_selector<'i, 't, P, Impl>( fn parse_compound_selector<'i, 't, P, Impl>(
parser: &P, parser: &P,
state: &mut SelectorParsingState,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
builder: &mut SelectorBuilder<Impl>, builder: &mut SelectorBuilder<Impl>,
) -> Result<Option<SelectorParsingState>, ParseError<'i, P::Error>> ) -> Result<bool, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
@ -2067,13 +2177,12 @@ where
input.skip_whitespace(); input.skip_whitespace();
let mut empty = true; let mut empty = true;
if parse_type_selector(parser, input, builder)? { if parse_type_selector(parser, input, *state, builder)? {
empty = false; empty = false;
} }
let mut state = SelectorParsingState::empty();
loop { loop {
let result = match parse_one_simple_selector(parser, input, state)? { let result = match parse_one_simple_selector(parser, input, *state)? {
None => break, None => break,
Some(result) => result, Some(result) => result,
}; };
@ -2132,17 +2241,13 @@ where
}, },
} }
} }
if empty { Ok(empty)
// An empty selector is invalid.
Ok(None)
} else {
Ok(Some(state))
}
} }
fn parse_is_or_where<'i, 't, P, Impl>( fn parse_is_or_where<'i, 't, P, Impl>(
parser: &P, parser: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>, component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>,
) -> Result<Component<Impl>, ParseError<'i, P::Error>> ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where where
@ -2150,15 +2255,16 @@ where
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
debug_assert!(parser.parse_is_and_where()); debug_assert!(parser.parse_is_and_where());
let inner = SelectorList::parse(parser, input)?;
// https://drafts.csswg.org/selectors/#matches-pseudo: // https://drafts.csswg.org/selectors/#matches-pseudo:
// //
// Pseudo-elements cannot be represented by the matches-any // Pseudo-elements cannot be represented by the matches-any
// pseudo-class; they are not valid within :is(). // pseudo-class; they are not valid within :is().
// //
if inner.0.iter().any(|i| i.has_pseudo_element()) { let inner = SelectorList::parse_with_state(
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidPseudoElementInsideWhere)); parser,
} input,
state | SelectorParsingState::DISALLOW_PSEUDOS,
)?;
Ok(component(inner.0.into_vec().into_boxed_slice())) Ok(component(inner.0.into_vec().into_boxed_slice()))
} }
@ -2172,40 +2278,51 @@ where
P: Parser<'i, Impl = Impl>, P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
{ {
if !state.allows_functional_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
debug_assert!(state.allows_tree_structural_pseudo_classes());
match_ignore_ascii_case! { &name, match_ignore_ascii_case! { &name,
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?), "nth-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthChild),
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?), "nth-of-type" => return parse_nth_pseudo_class(parser, input, state, Component::NthOfType),
"nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?), "nth-last-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthLastChild),
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?), "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, state, Component::NthLastOfType),
"is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Is), "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Where), "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where),
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))), "host" => {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?)));
},
"not" => { "not" => {
if state.intersects(SelectorParsingState::INSIDE_NEGATION) { if state.intersects(SelectorParsingState::INSIDE_NEGATION) {
return Err(input.new_custom_error( return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent("not".into()) SelectorParseErrorKind::UnexpectedIdent("not".into())
)); ));
} }
debug_assert!(state.is_empty()); return parse_negation(parser, input, state)
return parse_negation(parser, input)
}, },
_ => {} _ => {}
} }
if !state.allows_custom_functional_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass)
} }
fn parse_nth_pseudo_class<'i, 't, Impl, F>( fn parse_nth_pseudo_class<'i, 't, P, Impl, F>(
_: &P,
input: &mut CssParser<'i, 't>, input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
selector: F, selector: F,
) -> Result<Component<Impl>, BasicParseError<'i>> ) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl, Impl: SelectorImpl,
F: FnOnce(i32, i32) -> Component<Impl>, F: FnOnce(i32, i32) -> Component<Impl>,
{ {
if !state.allows_tree_structural_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let (a, b) = parse_nth(input)?; let (a, b) = parse_nth(input)?;
Ok(selector(a, b)) Ok(selector(a, b))
} }
@ -2290,7 +2407,7 @@ where
}; };
let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name);
if is_pseudo_element { if is_pseudo_element {
if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { if !state.allows_pseudos() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
} }
let pseudo_element = if is_functional { let pseudo_element = if is_functional {
@ -2317,7 +2434,7 @@ where
); );
} }
let selector = input.parse_nested_block(|input| { let selector = input.parse_nested_block(|input| {
parse_inner_compound_selector(parser, input) parse_inner_compound_selector(parser, input, state)
})?; })?;
return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector)));
} }

View file

@ -978,7 +978,13 @@ where
} => { } => {
let guard = declarations.read_with(context.guards.author); let guard = declarations.read_with(context.guards.author);
let iter = || { // This currently ignores visited styles, which seems acceptable,
// as existing browsers don't appear to animate visited styles.
let computed = properties::apply_declarations::<E, _>(
context.stylist.device(),
/* pseudo = */ None,
previous_style.rules(),
&context.guards,
// It's possible to have !important properties in keyframes // It's possible to have !important properties in keyframes
// so we have to filter them out. // so we have to filter them out.
// See the spec issue https://github.com/w3c/csswg-drafts/issues/1824 // See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
@ -986,17 +992,7 @@ where
guard guard
.normal_declaration_iter() .normal_declaration_iter()
.filter(|declaration| declaration.is_animatable()) .filter(|declaration| declaration.is_animatable())
.map(|decl| (decl, Origin::Author)) .map(|decl| (decl, Origin::Author)),
};
// This currently ignores visited styles, which seems acceptable,
// as existing browsers don't appear to animate visited styles.
let computed = properties::apply_declarations::<E, _, _>(
context.stylist.device(),
/* pseudo = */ None,
previous_style.rules(),
&context.guards,
iter,
Some(previous_style), Some(previous_style),
Some(previous_style), Some(previous_style),
Some(previous_style), Some(previous_style),

View file

@ -131,6 +131,7 @@ impl BuilderExt for Builder {
// them. // them.
let mut builder = Builder::default() let mut builder = Builder::default()
.rust_target(RustTarget::Stable_1_25) .rust_target(RustTarget::Stable_1_25)
.size_t_is_usize(true)
.disable_untagged_union(); .disable_untagged_union();
let rustfmt_path = env::var_os("RUSTFMT") let rustfmt_path = env::var_os("RUSTFMT")

View file

@ -7,6 +7,7 @@
use crate::context::QuirksMode; use crate::context::QuirksMode;
use crate::dom::{TDocument, TElement, TNode, TShadowRoot}; 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::{DescendantInvalidationLists, Invalidation};
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector}; use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use crate::Atom; use crate::Atom;
@ -130,7 +131,7 @@ where
{ {
results: &'a mut Q::Output, results: &'a mut Q::Output,
matching_context: MatchingContext<'a, E::Impl>, matching_context: MatchingContext<'a, E::Impl>,
selector_list: &'a SelectorList<E::Impl>, dependencies: &'a [Dependency],
} }
impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q> impl<'a, E, Q> InvalidationProcessor<'a, E> for QuerySelectorProcessor<'a, E, Q>
@ -143,6 +144,14 @@ where
true 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( fn collect_invalidations(
&mut self, &mut self,
element: E, element: E,
@ -171,11 +180,10 @@ where
self_invalidations self_invalidations
}; };
for selector in self.selector_list.0.iter() { for dependency in self.dependencies.iter() {
target_vector.push(Invalidation::new( target_vector.push(Invalidation::new(
selector, dependency,
self.matching_context.current_host.clone(), self.matching_context.current_host.clone(),
0,
)) ))
} }
@ -642,10 +650,15 @@ pub fn query_selector<E, Q>(
if root_element.is_some() || !invalidation_may_be_useful { if root_element.is_some() || !invalidation_may_be_useful {
query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context); query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context);
} else { } 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> { let mut processor = QuerySelectorProcessor::<E, Q> {
results, results,
matching_context, matching_context,
selector_list, dependencies: &dependencies,
}; };
for node in root.dom_children() { for node in root.dom_children() {

View file

@ -87,9 +87,9 @@ bitflags! {
/// <https://html.spec.whatwg.org/multipage/#selector-out-of-range> /// <https://html.spec.whatwg.org/multipage/#selector-out-of-range>
const IN_OUTOFRANGE_STATE = 1 << 28; const IN_OUTOFRANGE_STATE = 1 << 28;
/// <https://html.spec.whatwg.org/multipage/#selector-read-only> /// <https://html.spec.whatwg.org/multipage/#selector-read-only>
const IN_MOZ_READONLY_STATE = 1 << 29; const IN_READONLY_STATE = 1 << 29;
/// <https://html.spec.whatwg.org/multipage/#selector-read-write> /// <https://html.spec.whatwg.org/multipage/#selector-read-write>
const IN_MOZ_READWRITE_STATE = 1 << 30; const IN_READWRITE_STATE = 1 << 30;
/// <https://html.spec.whatwg.org/multipage/#selector-default> /// <https://html.spec.whatwg.org/multipage/#selector-default>
const IN_DEFAULT_STATE = 1 << 31; const IN_DEFAULT_STATE = 1 << 31;
/// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-submit-invalid /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-submit-invalid
@ -141,6 +141,10 @@ bitflags! {
/// ///
/// https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo /// https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
const IN_FOCUS_VISIBLE_STATE = 1 << 52; const IN_FOCUS_VISIBLE_STATE = 1 << 52;
/// State that dialog element is modal, for centered alignment
///
/// https://html.spec.whatwg.org/multipage/#centered-alignment
const IN_MODAL_DIALOG_STATE = 1 << 53;
} }
} }

View file

@ -8,8 +8,9 @@ use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs; use crate::gecko_bindings::structs;
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements}; use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription}; use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
use crate::media_queries::media_feature_expression::{AspectRatio, RangeOrOperator}; use crate::media_queries::media_feature_expression::RangeOrOperator;
use crate::media_queries::{Device, MediaType}; use crate::media_queries::{Device, MediaType};
use crate::values::computed::position::Ratio;
use crate::values::computed::CSSPixelLength; use crate::values::computed::CSSPixelLength;
use crate::values::computed::Resolution; use crate::values::computed::Resolution;
use crate::Atom; use crate::Atom;
@ -91,7 +92,7 @@ fn eval_device_height(
fn eval_aspect_ratio_for<F>( fn eval_aspect_ratio_for<F>(
device: &Device, device: &Device,
query_value: Option<AspectRatio>, query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>, range_or_operator: Option<RangeOrOperator>,
get_size: F, get_size: F,
) -> bool ) -> bool
@ -104,14 +105,14 @@ where
}; };
let size = get_size(device); let size = get_size(device);
let value = AspectRatio(size.width.0 as f32, size.height.0 as f32); let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value) RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
} }
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
fn eval_aspect_ratio( fn eval_aspect_ratio(
device: &Device, device: &Device,
query_value: Option<AspectRatio>, query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>, range_or_operator: Option<RangeOrOperator>,
) -> bool { ) -> bool {
eval_aspect_ratio_for(device, query_value, range_or_operator, viewport_size) eval_aspect_ratio_for(device, query_value, range_or_operator, viewport_size)
@ -120,7 +121,7 @@ fn eval_aspect_ratio(
/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio /// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
fn eval_device_aspect_ratio( fn eval_device_aspect_ratio(
device: &Device, device: &Device,
query_value: Option<AspectRatio>, query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>, range_or_operator: Option<RangeOrOperator>,
) -> bool { ) -> bool {
eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
@ -537,225 +538,222 @@ macro_rules! system_metric_feature {
}}; }};
} }
lazy_static! { /// Adding new media features requires (1) adding the new feature to this
/// Adding new media features requires (1) adding the new feature to this /// array, with appropriate entries (and potentially any new code needed
/// array, with appropriate entries (and potentially any new code needed /// to support new types in these entries and (2) ensuring that either
/// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that
/// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change.
/// would be returned by the evaluator function could change. pub static MEDIA_FEATURES: [MediaFeatureDescription; 53] = [
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 53] = [ feature!(
feature!( atom!("width"),
atom!("width"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Length(eval_width),
Evaluator::Length(eval_width), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("height"),
atom!("height"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Length(eval_height),
Evaluator::Length(eval_height), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("aspect-ratio"),
atom!("aspect-ratio"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::NumberRatio(eval_aspect_ratio),
Evaluator::NumberRatio(eval_aspect_ratio), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("orientation"),
atom!("orientation"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_orientation, Orientation),
keyword_evaluator!(eval_orientation, Orientation), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("device-width"),
atom!("device-width"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Length(eval_device_width),
Evaluator::Length(eval_device_width), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("device-height"),
atom!("device-height"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Length(eval_device_height),
Evaluator::Length(eval_device_height), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("device-aspect-ratio"),
atom!("device-aspect-ratio"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::NumberRatio(eval_device_aspect_ratio),
Evaluator::NumberRatio(eval_device_aspect_ratio), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("-moz-device-orientation"),
atom!("-moz-device-orientation"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_device_orientation, Orientation),
keyword_evaluator!(eval_device_orientation, Orientation), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), // Webkit extensions that we support for de-facto web compatibility.
// Webkit extensions that we support for de-facto web compatibility. // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): feature!(
feature!( atom!("device-pixel-ratio"),
atom!("device-pixel-ratio"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio),
Evaluator::Float(eval_device_pixel_ratio), ParsingRequirements::WEBKIT_PREFIX,
ParsingRequirements::WEBKIT_PREFIX, ),
), // -webkit-transform-3d.
// -webkit-transform-3d. feature!(
feature!( atom!("transform-3d"),
atom!("transform-3d"), AllowsRanges::No,
AllowsRanges::No, Evaluator::BoolInteger(eval_transform_3d),
Evaluator::BoolInteger(eval_transform_3d), ParsingRequirements::WEBKIT_PREFIX,
ParsingRequirements::WEBKIT_PREFIX, ),
), feature!(
feature!( atom!("-moz-device-pixel-ratio"),
atom!("-moz-device-pixel-ratio"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio),
Evaluator::Float(eval_device_pixel_ratio), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("resolution"),
atom!("resolution"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Resolution(eval_resolution),
Evaluator::Resolution(eval_resolution), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("display-mode"),
atom!("display-mode"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_display_mode, DisplayMode),
keyword_evaluator!(eval_display_mode, DisplayMode), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("grid"),
atom!("grid"), AllowsRanges::No,
AllowsRanges::No, Evaluator::BoolInteger(eval_grid),
Evaluator::BoolInteger(eval_grid), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("scan"),
atom!("scan"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_scan, Scan),
keyword_evaluator!(eval_scan, Scan), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("color"),
atom!("color"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Integer(eval_color),
Evaluator::Integer(eval_color), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("color-index"),
atom!("color-index"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Integer(eval_color_index),
Evaluator::Integer(eval_color_index), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("monochrome"),
atom!("monochrome"), AllowsRanges::Yes,
AllowsRanges::Yes, Evaluator::Integer(eval_monochrome),
Evaluator::Integer(eval_monochrome), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("prefers-reduced-motion"),
atom!("prefers-reduced-motion"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("overflow-block"),
atom!("overflow-block"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_overflow_block, OverflowBlock),
keyword_evaluator!(eval_overflow_block, OverflowBlock), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("overflow-inline"),
atom!("overflow-inline"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_overflow_inline, OverflowInline),
keyword_evaluator!(eval_overflow_inline, OverflowInline), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("prefers-color-scheme"),
atom!("prefers-color-scheme"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("pointer"),
atom!("pointer"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_pointer, Pointer),
keyword_evaluator!(eval_pointer, Pointer), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("any-pointer"),
atom!("any-pointer"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_any_pointer, Pointer),
keyword_evaluator!(eval_any_pointer, Pointer), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("hover"),
atom!("hover"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_hover, Hover),
keyword_evaluator!(eval_hover, Hover), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), feature!(
feature!( atom!("any-hover"),
atom!("any-hover"), AllowsRanges::No,
AllowsRanges::No, keyword_evaluator!(eval_any_hover, Hover),
keyword_evaluator!(eval_any_hover, Hover), ParsingRequirements::empty(),
ParsingRequirements::empty(), ),
), // Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
// Internal because it is really only useful in the user agent anyway
// Internal -moz-is-glyph media feature: applies only inside SVG glyphs. // and therefore not worth standardizing.
// Internal because it is really only useful in the user agent anyway feature!(
// and therefore not worth standardizing. atom!("-moz-is-glyph"),
feature!( AllowsRanges::No,
atom!("-moz-is-glyph"), Evaluator::BoolInteger(eval_moz_is_glyph),
AllowsRanges::No, ParsingRequirements::CHROME_AND_UA_ONLY,
Evaluator::BoolInteger(eval_moz_is_glyph), ),
ParsingRequirements::CHROME_AND_UA_ONLY, feature!(
), atom!("-moz-is-resource-document"),
feature!( AllowsRanges::No,
atom!("-moz-is-resource-document"), Evaluator::BoolInteger(eval_moz_is_resource_document),
AllowsRanges::No, ParsingRequirements::CHROME_AND_UA_ONLY,
Evaluator::BoolInteger(eval_moz_is_resource_document), ),
ParsingRequirements::CHROME_AND_UA_ONLY, feature!(
), atom!("-moz-os-version"),
feature!( AllowsRanges::No,
atom!("-moz-os-version"), Evaluator::Ident(eval_moz_os_version),
AllowsRanges::No, ParsingRequirements::CHROME_AND_UA_ONLY,
Evaluator::Ident(eval_moz_os_version), ),
ParsingRequirements::CHROME_AND_UA_ONLY, system_metric_feature!(atom!("-moz-scrollbar-start-backward")),
), system_metric_feature!(atom!("-moz-scrollbar-start-forward")),
system_metric_feature!(atom!("-moz-scrollbar-start-backward")), system_metric_feature!(atom!("-moz-scrollbar-end-backward")),
system_metric_feature!(atom!("-moz-scrollbar-start-forward")), system_metric_feature!(atom!("-moz-scrollbar-end-forward")),
system_metric_feature!(atom!("-moz-scrollbar-end-backward")), system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")),
system_metric_feature!(atom!("-moz-scrollbar-end-forward")), system_metric_feature!(atom!("-moz-overlay-scrollbars")),
system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")), system_metric_feature!(atom!("-moz-windows-default-theme")),
system_metric_feature!(atom!("-moz-overlay-scrollbars")), system_metric_feature!(atom!("-moz-mac-graphite-theme")),
system_metric_feature!(atom!("-moz-windows-default-theme")), system_metric_feature!(atom!("-moz-mac-yosemite-theme")),
system_metric_feature!(atom!("-moz-mac-graphite-theme")), system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")),
system_metric_feature!(atom!("-moz-mac-yosemite-theme")), system_metric_feature!(atom!("-moz-windows-compositor")),
system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")), system_metric_feature!(atom!("-moz-windows-classic")),
system_metric_feature!(atom!("-moz-windows-compositor")), system_metric_feature!(atom!("-moz-windows-glass")),
system_metric_feature!(atom!("-moz-windows-classic")), system_metric_feature!(atom!("-moz-menubar-drag")),
system_metric_feature!(atom!("-moz-windows-glass")), system_metric_feature!(atom!("-moz-swipe-animation-enabled")),
system_metric_feature!(atom!("-moz-menubar-drag")), system_metric_feature!(atom!("-moz-gtk-csd-available")),
system_metric_feature!(atom!("-moz-swipe-animation-enabled")), system_metric_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default")),
system_metric_feature!(atom!("-moz-gtk-csd-available")), system_metric_feature!(atom!("-moz-gtk-csd-transparent-background")),
system_metric_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default")), system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")),
system_metric_feature!(atom!("-moz-gtk-csd-transparent-background")), system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")),
system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")), system_metric_feature!(atom!("-moz-gtk-csd-close-button")),
system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")), system_metric_feature!(atom!("-moz-gtk-csd-reversed-placement")),
system_metric_feature!(atom!("-moz-gtk-csd-close-button")), system_metric_feature!(atom!("-moz-system-dark-theme")),
system_metric_feature!(atom!("-moz-gtk-csd-reversed-placement")), // This is the only system-metric media feature that's accessible to
system_metric_feature!(atom!("-moz-system-dark-theme")), // content as of today.
// This is the only system-metric media feature that's accessible to // FIXME(emilio): Restrict (or remove?) when bug 1035774 lands.
// content as of today. feature!(
// FIXME(emilio): Restrict (or remove?) when bug 1035774 lands. atom!("-moz-touch-enabled"),
feature!( AllowsRanges::No,
atom!("-moz-touch-enabled"), Evaluator::BoolInteger(eval_moz_touch_enabled),
AllowsRanges::No, ParsingRequirements::empty(),
Evaluator::BoolInteger(eval_moz_touch_enabled), ),
ParsingRequirements::empty(), ];
),
];
}

View file

@ -50,6 +50,7 @@ macro_rules! apply_non_ts_list {
("-moz-devtools-highlighted", MozDevtoolsHighlighted, IN_DEVTOOLS_HIGHLIGHTED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-devtools-highlighted", MozDevtoolsHighlighted, IN_DEVTOOLS_HIGHLIGHTED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _), ("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
// TODO(emilio): This is inconsistently named (the capital R). // TODO(emilio): This is inconsistently named (the capital R).
("-moz-focusring", MozFocusRing, IN_FOCUSRING_STATE, _), ("-moz-focusring", MozFocusRing, IN_FOCUSRING_STATE, _),
("-moz-broken", MozBroken, IN_BROKEN_STATE, _), ("-moz-broken", MozBroken, IN_BROKEN_STATE, _),
@ -79,8 +80,8 @@ macro_rules! apply_non_ts_list {
("out-of-range", OutOfRange, IN_OUTOFRANGE_STATE, _), ("out-of-range", OutOfRange, IN_OUTOFRANGE_STATE, _),
("default", Default, IN_DEFAULT_STATE, _), ("default", Default, IN_DEFAULT_STATE, _),
("placeholder-shown", PlaceholderShown, IN_PLACEHOLDER_SHOWN_STATE, _), ("placeholder-shown", PlaceholderShown, IN_PLACEHOLDER_SHOWN_STATE, _),
("-moz-read-only", MozReadOnly, IN_MOZ_READONLY_STATE, _), ("read-only", ReadOnly, IN_READONLY_STATE, _),
("-moz-read-write", MozReadWrite, IN_MOZ_READWRITE_STATE, _), ("read-write", ReadWrite, IN_READWRITE_STATE, _),
("-moz-submit-invalid", MozSubmitInvalid, IN_MOZ_SUBMITINVALID_STATE, _), ("-moz-submit-invalid", MozSubmitInvalid, IN_MOZ_SUBMITINVALID_STATE, _),
("-moz-ui-valid", MozUIValid, IN_MOZ_UI_VALID_STATE, _), ("-moz-ui-valid", MozUIValid, IN_MOZ_UI_VALID_STATE, _),
("-moz-ui-invalid", MozUIInvalid, IN_MOZ_UI_INVALID_STATE, _), ("-moz-ui-invalid", MozUIInvalid, IN_MOZ_UI_INVALID_STATE, _),

View file

@ -37,7 +37,8 @@ impl ::selectors::parser::PseudoElement for PseudoElement {
PseudoElement::Before | PseudoElement::Before |
PseudoElement::After | PseudoElement::After |
PseudoElement::Marker | PseudoElement::Marker |
PseudoElement::Placeholder PseudoElement::Placeholder |
PseudoElement::FileChooserButton
) )
} }
@ -92,6 +93,12 @@ impl PseudoElement {
EAGER_PSEUDOS[i].clone() EAGER_PSEUDOS[i].clone()
} }
/// Whether the current pseudo element is animatable.
#[inline]
pub fn is_animatable(&self) -> bool {
matches!(*self, Self::Before | Self::After | Self::Marker)
}
/// Whether the current pseudo element is ::before or ::after. /// Whether the current pseudo element is ::before or ::after.
#[inline] #[inline]
pub fn is_before_or_after(&self) -> bool { pub fn is_before_or_after(&self) -> bool {
@ -153,7 +160,15 @@ impl PseudoElement {
/// Whether this pseudo-element is enabled for all content. /// Whether this pseudo-element is enabled for all content.
pub fn enabled_in_content(&self) -> bool { pub fn enabled_in_content(&self) -> bool {
(self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) == 0 match *self {
PseudoElement::MozFocusOuter => {
static_prefs::pref!("layout.css.moz-focus-outer.enabled")
},
PseudoElement::FileChooserButton => {
static_prefs::pref!("layout.css.file-chooser-button.enabled")
},
_ => (self.flags() & structs::CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) == 0,
}
} }
/// Whether this pseudo is enabled explicitly in UA sheets. /// Whether this pseudo is enabled explicitly in UA sheets.

View file

@ -119,6 +119,8 @@ impl NonTSPseudoClass {
match_ignore_ascii_case! { &name, match_ignore_ascii_case! { &name,
$($css => Some(NonTSPseudoClass::$name),)* $($css => Some(NonTSPseudoClass::$name),)*
"-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen), "-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen),
"-moz-read-only" => Some(NonTSPseudoClass::ReadOnly),
"-moz-read-write" => Some(NonTSPseudoClass::ReadWrite),
_ => None, _ => None,
} }
} }
@ -227,17 +229,6 @@ impl NonTSPseudoClass {
NonTSPseudoClass::MozLWThemeDarkText NonTSPseudoClass::MozLWThemeDarkText
) )
} }
/// Returns true if the evaluation of the pseudo-class depends on the
/// element's attributes.
pub fn is_attr_based(&self) -> bool {
matches!(
*self,
NonTSPseudoClass::MozTableBorderNonzero |
NonTSPseudoClass::MozBrowserFrame |
NonTSPseudoClass::Lang(..)
)
}
} }
impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass { impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
@ -352,7 +343,8 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
#[inline] #[inline]
fn parse_is_and_where(&self) -> bool { fn parse_is_and_where(&self) -> bool {
static_prefs::pref!("layout.css.is-where-selectors.enabled") self.in_user_agent_stylesheet() ||
static_prefs::pref!("layout.css.is-where-selectors.enabled")
} }
#[inline] #[inline]

View file

@ -70,11 +70,15 @@ impl GeckoElementSnapshot {
self.mClassAttributeChanged() self.mClassAttributeChanged()
} }
/// Returns true if the snapshot recorded an attribute change which isn't a /// Executes the callback once for each attribute that changed.
/// class / id
#[inline] #[inline]
pub fn other_attr_changed(&self) -> bool { pub fn each_attr_changed<F>(&self, mut callback: F)
self.mOtherAttributeChanged() where
F: FnMut(&Atom),
{
for attr in self.mChangedAttrNames.iter() {
unsafe { Atom::with(attr.mRawPtr, &mut callback) }
}
} }
/// selectors::Element::attr_matches /// selectors::Element::attr_matches

View file

@ -18,7 +18,7 @@ use std::fmt::{self, Write};
use std::mem::ManuallyDrop; use std::mem::ManuallyDrop;
use std::sync::RwLock; use std::sync::RwLock;
use style_traits::{CssWriter, ParseError, ToCss}; use style_traits::{CssWriter, ParseError, ToCss};
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
/// A CSS url() value for gecko. /// A CSS url() value for gecko.
#[css(function = "url")] #[css(function = "url")]
@ -241,11 +241,11 @@ impl LoadDataSource {
} }
impl ToShmem for LoadDataSource { impl ToShmem for LoadDataSource {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
ManuallyDrop::new(match self { Ok(ManuallyDrop::new(match self {
LoadDataSource::Owned(..) => LoadDataSource::Lazy, LoadDataSource::Owned(..) => LoadDataSource::Lazy,
LoadDataSource::Lazy => LoadDataSource::Lazy, LoadDataSource::Lazy => LoadDataSource::Lazy,
}) }))
} }
} }

View file

@ -1446,7 +1446,7 @@ impl<'le> TElement for GeckoElement<'le> {
#[inline] #[inline]
fn may_have_animations(&self) -> bool { fn may_have_animations(&self) -> bool {
if let Some(pseudo) = self.implemented_pseudo_element() { if let Some(pseudo) = self.implemented_pseudo_element() {
if !pseudo.is_before_or_after() { if !pseudo.is_animatable() {
return false; return false;
} }
// FIXME(emilio): When would the parent of a ::before / ::after // FIXME(emilio): When would the parent of a ::before / ::after
@ -2037,8 +2037,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozHandlerCrashed | NonTSPseudoClass::MozHandlerCrashed |
NonTSPseudoClass::Required | NonTSPseudoClass::Required |
NonTSPseudoClass::Optional | NonTSPseudoClass::Optional |
NonTSPseudoClass::MozReadOnly | NonTSPseudoClass::ReadOnly |
NonTSPseudoClass::MozReadWrite | NonTSPseudoClass::ReadWrite |
NonTSPseudoClass::FocusWithin | NonTSPseudoClass::FocusWithin |
NonTSPseudoClass::FocusVisible | NonTSPseudoClass::FocusVisible |
NonTSPseudoClass::MozDragOver | NonTSPseudoClass::MozDragOver |
@ -2062,6 +2062,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozDirAttrRTL | NonTSPseudoClass::MozDirAttrRTL |
NonTSPseudoClass::MozDirAttrLikeAuto | NonTSPseudoClass::MozDirAttrLikeAuto |
NonTSPseudoClass::MozAutofill | NonTSPseudoClass::MozAutofill |
NonTSPseudoClass::MozModalDialog |
NonTSPseudoClass::Active | NonTSPseudoClass::Active |
NonTSPseudoClass::Hover | NonTSPseudoClass::Hover |
NonTSPseudoClass::MozAutofillPreview => { NonTSPseudoClass::MozAutofillPreview => {

View file

@ -5,7 +5,7 @@
//! Rust helpers for Gecko's nsTArray. //! Rust helpers for Gecko's nsTArray.
use crate::gecko_bindings::bindings; use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs::{nsTArray, nsTArrayHeader}; use crate::gecko_bindings::structs::{nsTArray, nsTArrayHeader, CopyableTArray};
use std::mem; use std::mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::slice; use std::slice;
@ -128,3 +128,16 @@ impl<T> nsTArray<T> {
self.iter_mut().zip(iter).for_each(|(r, v)| *r = v); self.iter_mut().zip(iter).for_each(|(r, v)| *r = v);
} }
} }
impl<T> Deref for CopyableTArray<T> {
type Target = nsTArray<T>;
fn deref(&self) -> &Self::Target {
&self._base
}
}
impl<T> DerefMut for CopyableTArray<T> {
fn deref_mut(&mut self) -> &mut nsTArray<T> {
&mut self._base
}
}

View file

@ -4,6 +4,7 @@
//! Helpers for different FFI pointer kinds that Gecko's FFI layer uses. //! Helpers for different FFI pointer kinds that Gecko's FFI layer uses.
use gecko_bindings::structs::root::mozilla::detail::CopyablePtr;
use servo_arc::{Arc, RawOffsetArc}; use servo_arc::{Arc, RawOffsetArc};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem::{forget, transmute}; use std::mem::{forget, transmute};
@ -332,3 +333,16 @@ impl<GeckoType> OwnedOrNull<GeckoType> {
unsafe { transmute(self) } unsafe { transmute(self) }
} }
} }
impl<T> Deref for CopyablePtr<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.mPtr
}
}
impl<T> DerefMut for CopyablePtr<T> {
fn deref_mut<'a>(&'a mut self) -> &'a mut T {
&mut self.mPtr
}
}

View file

@ -3,7 +3,6 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#![allow(unsafe_code)] #![allow(unsafe_code)]
// This is needed for the constants in atom_macro.rs, because we have some // This is needed for the constants in atom_macro.rs, because we have some
// atoms whose names differ only by case, e.g. datetime and dateTime. // atoms whose names differ only by case, e.g. datetime and dateTime.
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
@ -30,7 +29,7 @@ use std::num::NonZeroUsize;
use std::ops::Deref; use std::ops::Deref;
use std::{slice, str}; use std::{slice, str};
use style_traits::SpecifiedValueInfo; use style_traits::SpecifiedValueInfo;
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
#[macro_use] #[macro_use]
#[allow(improper_ctypes, non_camel_case_types, missing_docs)] #[allow(improper_ctypes, non_camel_case_types, missing_docs)]
@ -132,14 +131,15 @@ impl Borrow<WeakAtom> for Atom {
} }
impl ToShmem for Atom { impl ToShmem for Atom {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
assert!( if !self.is_static() {
self.is_static(), return Err(format!(
"ToShmem failed for Atom: must be a static atom: {}", "ToShmem failed for Atom: must be a static atom: {}",
self self
); ));
}
ManuallyDrop::new(Atom(self.0)) Ok(ManuallyDrop::new(Atom(self.0)))
} }
} }

View file

@ -6,6 +6,7 @@
use crate::dom::TElement; use crate::dom::TElement;
use crate::element_state::DocumentState; use crate::element_state::DocumentState;
use crate::invalidation::element::invalidation_map::Dependency;
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector}; use crate::invalidation::element::invalidator::{DescendantInvalidationLists, InvalidationVector};
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor}; use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
use crate::invalidation::element::state_and_attributes; use crate::invalidation::element::state_and_attributes;
@ -65,6 +66,14 @@ where
E: TElement, E: TElement,
I: Iterator<Item = &'a CascadeData>, I: Iterator<Item = &'a CascadeData>,
{ {
fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool {
debug_assert!(
false,
"how, we should only have parent-less dependencies here!"
);
true
}
fn collect_invalidations( fn collect_invalidations(
&mut self, &mut self,
_element: E, _element: E,
@ -81,10 +90,14 @@ where
// We pass `None` as a scope, as document state selectors aren't // We pass `None` as a scope, as document state selectors aren't
// affected by the current scope. // affected by the current scope.
//
// FIXME(emilio): We should really pass the relevant host for
// self.rules, so that we invalidate correctly if the selector
// happens to have something like :host(:-moz-window-inactive)
// for example.
self_invalidations.push(Invalidation::new( self_invalidations.push(Invalidation::new(
&dependency.selector, &dependency.dependency,
/* scope = */ None, /* scope = */ None,
0,
)); ));
} }
} }

View file

@ -6,7 +6,9 @@
use crate::context::QuirksMode; use crate::context::QuirksMode;
use crate::element_state::{DocumentState, ElementState}; use crate::element_state::{DocumentState, ElementState};
use crate::selector_map::{MaybeCaseInsensitiveHashMap, SelectorMap, SelectorMapEntry}; use crate::selector_map::{
MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry,
};
use crate::selector_parser::SelectorImpl; use crate::selector_parser::SelectorImpl;
use crate::{Atom, LocalName, Namespace}; use crate::{Atom, LocalName, Namespace};
use fallible::FallibleVec; use fallible::FallibleVec;
@ -47,6 +49,23 @@ pub struct Dependency {
/// The offset into the selector that we should match on. /// The offset into the selector that we should match on.
pub selector_offset: usize, pub selector_offset: usize,
/// The parent dependency for an ancestor selector. For example, consider
/// the following:
///
/// .foo .bar:where(.baz span) .qux
/// ^ ^ ^
/// A B C
///
/// We'd generate:
///
/// * One dependency for .qux (offset: 0, parent: None)
/// * One dependency for .baz pointing to B with parent being a
/// dependency pointing to C.
/// * One dependency from .bar pointing to C (parent: None)
/// * One dependency from .foo pointing to A (parent: None)
///
pub parent: Option<Box<Dependency>>,
} }
/// The kind of elements down the tree this dependency may affect. /// The kind of elements down the tree this dependency may affect.
@ -71,6 +90,22 @@ pub enum DependencyInvalidationKind {
} }
impl Dependency { impl Dependency {
/// Creates a dummy dependency to invalidate the whole selector.
///
/// This is necessary because document state invalidation wants to
/// invalidate all elements in the document.
///
/// The offset is such as that Invalidation::new(self) returns a zero
/// offset. That is, it points to a virtual "combinator" outside of the
/// selector, so calling combinator() on such a dependency will panic.
pub fn for_full_selector_invalidation(selector: Selector<SelectorImpl>) -> Self {
Self {
selector_offset: selector.len() + 1,
selector,
parent: None,
}
}
/// Returns the combinator to the right of the partial selector this /// Returns the combinator to the right of the partial selector this
/// dependency represents. /// dependency represents.
/// ///
@ -130,31 +165,18 @@ impl SelectorMapEntry for StateDependency {
/// The same, but for document state selectors. /// The same, but for document state selectors.
#[derive(Clone, Debug, MallocSizeOf)] #[derive(Clone, Debug, MallocSizeOf)]
pub struct DocumentStateDependency { pub struct DocumentStateDependency {
/// The selector that is affected. We don't need to track an offset, since /// We track `Dependency` even though we don't need to track an offset,
/// when it changes it changes for the whole document anyway. /// since when it changes it changes for the whole document anyway.
#[cfg_attr( #[cfg_attr(
feature = "gecko", feature = "gecko",
ignore_malloc_size_of = "CssRules have primary refs, we measure there" ignore_malloc_size_of = "CssRules have primary refs, we measure there"
)] )]
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")] #[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
pub selector: Selector<SelectorImpl>, pub dependency: Dependency,
/// The state this dependency is affected by. /// The state this dependency is affected by.
pub state: DocumentState, pub state: DocumentState,
} }
bitflags! {
/// A set of flags that denote whether any invalidations have occurred
/// for a particular attribute selector.
#[derive(MallocSizeOf)]
#[repr(C)]
pub struct InvalidationMapFlags : u8 {
/// Whether [class] or such is used.
const HAS_CLASS_ATTR_SELECTOR = 1 << 0;
/// Whether [id] or such is used.
const HAS_ID_ATTR_SELECTOR = 1 << 1;
}
}
/// A map where we store invalidations. /// A map where we store invalidations.
/// ///
/// This is slightly different to a SelectorMap, in the sense of that the same /// This is slightly different to a SelectorMap, in the sense of that the same
@ -176,10 +198,8 @@ pub struct InvalidationMap {
/// A list of document state dependencies in the rules we represent. /// A list of document state dependencies in the rules we represent.
pub document_state_selectors: Vec<DocumentStateDependency>, pub document_state_selectors: Vec<DocumentStateDependency>,
/// A map of other attribute affecting selectors. /// A map of other attribute affecting selectors.
pub other_attribute_affecting_selectors: SelectorMap<Dependency>, pub other_attribute_affecting_selectors:
/// A set of flags that contain whether various special attributes are used PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>,
/// in this invalidation map.
pub flags: InvalidationMapFlags,
} }
impl InvalidationMap { impl InvalidationMap {
@ -190,8 +210,7 @@ impl InvalidationMap {
id_to_selector: MaybeCaseInsensitiveHashMap::new(), id_to_selector: MaybeCaseInsensitiveHashMap::new(),
state_affecting_selectors: SelectorMap::new(), state_affecting_selectors: SelectorMap::new(),
document_state_selectors: Vec::new(), document_state_selectors: Vec::new(),
other_attribute_affecting_selectors: SelectorMap::new(), other_attribute_affecting_selectors: PrecomputedHashMap::default(),
flags: InvalidationMapFlags::empty(),
} }
} }
@ -199,7 +218,9 @@ impl InvalidationMap {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.state_affecting_selectors.len() + self.state_affecting_selectors.len() +
self.document_state_selectors.len() + self.document_state_selectors.len() +
self.other_attribute_affecting_selectors.len() + self.other_attribute_affecting_selectors
.iter()
.fold(0, |accum, (_, ref v)| accum + v.len()) +
self.id_to_selector self.id_to_selector
.iter() .iter()
.fold(0, |accum, (_, ref v)| accum + v.len()) + .fold(0, |accum, (_, ref v)| accum + v.len()) +
@ -215,7 +236,6 @@ impl InvalidationMap {
self.state_affecting_selectors.clear(); self.state_affecting_selectors.clear();
self.document_state_selectors.clear(); self.document_state_selectors.clear();
self.other_attribute_affecting_selectors.clear(); self.other_attribute_affecting_selectors.clear();
self.flags = InvalidationMapFlags::empty();
} }
/// Adds a selector to this `InvalidationMap`. Returns Err(..) to /// Adds a selector to this `InvalidationMap`. Returns Err(..) to
@ -227,115 +247,60 @@ impl InvalidationMap {
) -> Result<(), FailedAllocationError> { ) -> Result<(), FailedAllocationError> {
debug!("InvalidationMap::note_selector({:?})", selector); debug!("InvalidationMap::note_selector({:?})", selector);
let mut iter = selector.iter();
let mut combinator;
let mut index = 0;
let mut document_state = DocumentState::empty(); let mut document_state = DocumentState::empty();
loop { {
let sequence_start = index; let mut parent_stack = SmallVec::new();
let mut alloc_error = None;
let mut compound_visitor = CompoundSelectorDependencyCollector { let mut collector = SelectorDependencyCollector {
classes: SmallVec::new(), map: self,
ids: SmallVec::new(),
state: ElementState::empty(),
document_state: &mut document_state, document_state: &mut document_state,
other_attributes: false, selector,
flags: &mut self.flags, parent_selectors: &mut parent_stack,
quirks_mode,
compound_state: PerCompoundState::new(0),
alloc_error: &mut alloc_error,
}; };
// Visit all the simple selectors in this sequence. let visit_result = collector.visit_whole_selector();
// debug_assert_eq!(!visit_result, alloc_error.is_some());
// Note that this works because we can't have combinators nested if let Some(alloc_error) = alloc_error {
// inside simple selectors (i.e. in :not() or :-moz-any()). return Err(alloc_error);
//
// If we ever support that we'll need to visit nested complex
// selectors as well, in order to mark them as affecting descendants
// at least.
for ss in &mut iter {
ss.visit(&mut compound_visitor);
index += 1; // Account for the simple selector.
} }
for class in compound_visitor.classes {
self.class_to_selector
.try_entry(class, quirks_mode)?
.or_insert_with(SmallVec::new)
.try_push(Dependency {
selector: selector.clone(),
selector_offset: sequence_start,
})?;
}
for id in compound_visitor.ids {
self.id_to_selector
.try_entry(id, quirks_mode)?
.or_insert_with(SmallVec::new)
.try_push(Dependency {
selector: selector.clone(),
selector_offset: sequence_start,
})?;
}
if !compound_visitor.state.is_empty() {
self.state_affecting_selectors.insert(
StateDependency {
dep: Dependency {
selector: selector.clone(),
selector_offset: sequence_start,
},
state: compound_visitor.state,
},
quirks_mode,
)?;
}
if compound_visitor.other_attributes {
self.other_attribute_affecting_selectors.insert(
Dependency {
selector: selector.clone(),
selector_offset: sequence_start,
},
quirks_mode,
)?;
}
combinator = iter.next_sequence();
if combinator.is_none() {
break;
}
index += 1; // Account for the combinator.
} }
if !document_state.is_empty() { if !document_state.is_empty() {
self.document_state_selectors let dep = DocumentStateDependency {
.try_push(DocumentStateDependency { state: document_state,
state: document_state, dependency: Dependency::for_full_selector_invalidation(selector.clone()),
selector: selector.clone(), };
})?; self.document_state_selectors.try_push(dep)?;
} }
Ok(()) Ok(())
} }
} }
/// A struct that collects invalidations for a given compound selector. struct PerCompoundState {
/// /// The offset at which our compound starts.
/// FIXME(emilio, bug 1509418): :where is mishandled, figure out the right way offset: usize,
/// to do invalidation for :where when combinators are inside.
///
/// Simplest feasible idea seems to be: For each :where branch, if there are
/// combinators in the branch, treat as its own selector (basically, call
/// `.note_selector` with the nested selector). That may over-invalidate, but
/// not too much. If there are no combinators, then behave like we do today and
/// use the outer selector as a whole. If we care a lot about that potential
/// over-invalidation where combinators are present then we need more complex
/// data-structures in `Dependency`.
struct CompoundSelectorDependencyCollector<'a> {
/// The state this compound selector is affected by. /// The state this compound selector is affected by.
state: ElementState, element_state: ElementState,
}
impl PerCompoundState {
fn new(offset: usize) -> Self {
Self {
offset,
element_state: ElementState::empty(),
}
}
}
/// A struct that collects invalidations for a given compound selector.
struct SelectorDependencyCollector<'a> {
map: &'a mut InvalidationMap,
/// The document this _complex_ selector is affected by. /// The document this _complex_ selector is affected by.
/// ///
@ -343,76 +308,226 @@ struct CompoundSelectorDependencyCollector<'a> {
/// state and it changes for everything. /// state and it changes for everything.
document_state: &'a mut DocumentState, document_state: &'a mut DocumentState,
/// The classes this compound selector is affected by. /// The current selector and offset we're iterating.
selector: &'a Selector<SelectorImpl>,
/// The stack of parent selectors that we have, and at which offset of the
/// sequence.
/// ///
/// NB: This will be often a single class, but could be multiple in /// This starts empty. It grows when we find nested :is and :where selector
/// presence of :not, :-moz-any, .foo.bar.baz, etc. /// lists.
classes: SmallVec<[Atom; 5]>, parent_selectors: &'a mut SmallVec<[(Selector<SelectorImpl>, usize); 5]>,
/// The IDs this compound selector is affected by. /// The quirks mode of the document where we're inserting dependencies.
/// quirks_mode: QuirksMode,
/// NB: This will be almost always a single id, but could be multiple in
/// presence of :not, :-moz-any, #foo#bar, etc.
ids: SmallVec<[Atom; 5]>,
/// Whether it affects other attribute-dependent selectors that aren't ID or /// State relevant to a given compound selector.
/// class selectors (NB: We still set this to true in presence of [class] or compound_state: PerCompoundState,
/// [id] attribute selectors).
other_attributes: bool,
/// The invalidation map flags, that we set when some attribute selectors are present. /// The allocation error, if we OOM.
flags: &'a mut InvalidationMapFlags, alloc_error: &'a mut Option<FailedAllocationError>,
} }
impl<'a> SelectorVisitor for CompoundSelectorDependencyCollector<'a> { impl<'a> SelectorDependencyCollector<'a> {
fn visit_whole_selector(&mut self) -> bool {
let iter = self.selector.iter();
self.visit_whole_selector_from(iter, 0)
}
fn visit_whole_selector_from(
&mut self,
mut iter: SelectorIter<SelectorImpl>,
mut index: usize,
) -> bool {
loop {
// Reset the compound state.
self.compound_state = PerCompoundState::new(index);
// Visit all the simple selectors in this sequence.
for ss in &mut iter {
if !ss.visit(self) {
return false;
}
index += 1; // Account for the simple selector.
}
if !self.compound_state.element_state.is_empty() {
let dependency = self.dependency();
let result = self.map.state_affecting_selectors.insert(
StateDependency {
dep: dependency,
state: self.compound_state.element_state,
},
self.quirks_mode,
);
if let Err(alloc_error) = result {
*self.alloc_error = Some(alloc_error);
return false;
}
}
let combinator = iter.next_sequence();
if combinator.is_none() {
return true;
}
index += 1; // account for the combinator
}
}
fn add_attr_dependency(&mut self, name: LocalName) -> bool {
let dependency = self.dependency();
let map = &mut self.map.other_attribute_affecting_selectors;
let entry = match map.try_entry(name) {
Ok(entry) => entry,
Err(err) => {
*self.alloc_error = Some(err);
return false;
},
};
match entry.or_insert_with(SmallVec::new).try_push(dependency) {
Ok(..) => true,
Err(err) => {
*self.alloc_error = Some(err);
return false;
},
}
}
fn dependency(&self) -> Dependency {
let mut parent = None;
// TODO(emilio): Maybe we should refcount the parent dependencies, or
// cache them or something.
for &(ref selector, ref selector_offset) in self.parent_selectors.iter() {
debug_assert_ne!(
self.compound_state.offset, 0,
"Shouldn't bother creating nested dependencies for the rightmost compound",
);
let new_parent = Dependency {
selector: selector.clone(),
selector_offset: *selector_offset,
parent,
};
parent = Some(Box::new(new_parent));
}
Dependency {
selector: self.selector.clone(),
selector_offset: self.compound_state.offset,
parent,
}
}
}
impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> {
type Impl = SelectorImpl; type Impl = SelectorImpl;
fn visit_selector_list(&mut self, list: &[Selector<SelectorImpl>]) -> bool {
for selector in list {
// Here we cheat a bit: We can visit the rightmost compound with
// the "outer" visitor, and it'd be fine. This reduces the amount of
// state and attribute invalidations, and we need to check the outer
// selector to the left anyway to avoid over-invalidation, so it
// avoids matching it twice uselessly.
let mut iter = selector.iter();
let mut index = 0;
for ss in &mut iter {
if !ss.visit(self) {
return false;
}
index += 1;
}
let combinator = iter.next_sequence();
if combinator.is_none() {
continue;
}
index += 1; // account for the combinator.
self.parent_selectors
.push((self.selector.clone(), self.compound_state.offset));
let mut nested = SelectorDependencyCollector {
map: &mut *self.map,
document_state: &mut *self.document_state,
selector,
parent_selectors: &mut *self.parent_selectors,
quirks_mode: self.quirks_mode,
compound_state: PerCompoundState::new(index),
alloc_error: &mut *self.alloc_error,
};
if !nested.visit_whole_selector_from(iter, index) {
return false;
}
self.parent_selectors.pop();
}
true
}
fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool { fn visit_simple_selector(&mut self, s: &Component<SelectorImpl>) -> bool {
#[cfg(feature = "gecko")]
use crate::selector_parser::NonTSPseudoClass; use crate::selector_parser::NonTSPseudoClass;
match *s { match *s {
Component::ID(ref id) => { Component::ID(ref atom) | Component::Class(ref atom) => {
self.ids.push(id.clone()); let dependency = self.dependency();
}, let map = match *s {
Component::Class(ref class) => { Component::ID(..) => &mut self.map.id_to_selector,
self.classes.push(class.clone()); Component::Class(..) => &mut self.map.class_to_selector,
_ => unreachable!(),
};
let entry = match map.try_entry(atom.clone(), self.quirks_mode) {
Ok(entry) => entry,
Err(err) => {
*self.alloc_error = Some(err);
return false;
},
};
match entry.or_insert_with(SmallVec::new).try_push(dependency) {
Ok(..) => true,
Err(err) => {
*self.alloc_error = Some(err);
return false;
},
}
}, },
Component::NonTSPseudoClass(ref pc) => { Component::NonTSPseudoClass(ref pc) => {
self.other_attributes |= pc.is_attr_based(); self.compound_state.element_state |= match *pc {
self.state |= match *pc {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
NonTSPseudoClass::Dir(ref dir) => dir.element_state(), NonTSPseudoClass::Dir(ref dir) => dir.element_state(),
_ => pc.state_flag(), _ => pc.state_flag(),
}; };
*self.document_state |= pc.document_state_flag(); *self.document_state |= pc.document_state_flag();
},
_ => {},
}
true let attr_name = match *pc {
#[cfg(feature = "gecko")]
NonTSPseudoClass::MozTableBorderNonzero => local_name!("border"),
#[cfg(feature = "gecko")]
NonTSPseudoClass::MozBrowserFrame => local_name!("mozbrowser"),
NonTSPseudoClass::Lang(..) => local_name!("lang"),
_ => return true,
};
self.add_attr_dependency(attr_name)
},
_ => true,
}
} }
fn visit_attribute_selector( fn visit_attribute_selector(
&mut self, &mut self,
constraint: &NamespaceConstraint<&Namespace>, _: &NamespaceConstraint<&Namespace>,
_local_name: &LocalName, local_name: &LocalName,
local_name_lower: &LocalName, local_name_lower: &LocalName,
) -> bool { ) -> bool {
self.other_attributes = true; if !self.add_attr_dependency(local_name.clone()) {
let may_match_in_no_namespace = match *constraint { return false;
NamespaceConstraint::Any => true, }
NamespaceConstraint::Specific(ref ns) => ns.is_empty(),
};
if may_match_in_no_namespace { if local_name != local_name_lower && !self.add_attr_dependency(local_name_lower.clone()) {
if *local_name_lower == local_name!("id") { return false;
self.flags
.insert(InvalidationMapFlags::HAS_ID_ATTR_SELECTOR)
} else if *local_name_lower == local_name!("class") {
self.flags
.insert(InvalidationMapFlags::HAS_CLASS_ATTR_SELECTOR)
}
} }
true true

View file

@ -7,10 +7,10 @@
use crate::context::StackLimitChecker; use crate::context::StackLimitChecker;
use crate::dom::{TElement, TNode, TShadowRoot}; use crate::dom::{TElement, TNode, TShadowRoot};
use crate::selector_parser::SelectorImpl; use crate::invalidation::element::invalidation_map::{Dependency, DependencyInvalidationKind};
use selectors::matching::matches_compound_selector_from; use selectors::matching::matches_compound_selector_from;
use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext}; use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
use selectors::parser::{Combinator, Component, Selector}; use selectors::parser::{Combinator, Component};
use selectors::OpaqueElement; use selectors::OpaqueElement;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::fmt; use std::fmt;
@ -34,6 +34,27 @@ where
false false
} }
/// When a dependency from a :where or :is selector matches, it may still be
/// the case that we don't need to invalidate the full style. Consider the
/// case of:
///
/// div .foo:where(.bar *, .baz) .qux
///
/// We can get to the `*` part after a .bar class change, but you only need
/// to restyle the element if it also matches .foo.
///
/// Similarly, you only need to restyle .baz if the whole result of matching
/// the selector changes.
///
/// This function is called to check the result of matching the "outer"
/// dependency that we generate for the parent of the `:where` selector,
/// that is, in the case above it should match
/// `div .foo:where(.bar *, .baz)`.
///
/// Returning true unconditionally here is over-optimistic and may
/// over-invalidate.
fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool;
/// The matching context that should be used to process invalidations. /// The matching context that should be used to process invalidations.
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>; fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>;
@ -127,7 +148,11 @@ enum InvalidationKind {
/// relative to a current element we are processing, must be restyled. /// relative to a current element we are processing, must be restyled.
#[derive(Clone)] #[derive(Clone)]
pub struct Invalidation<'a> { pub struct Invalidation<'a> {
selector: &'a Selector<SelectorImpl>, /// The dependency that generated this invalidation.
///
/// Note that the offset inside the dependency is not really useful after
/// construction.
dependency: &'a Dependency,
/// The right shadow host from where the rule came from, if any. /// The right shadow host from where the rule came from, if any.
/// ///
/// This is needed to ensure that we match the selector with the right /// This is needed to ensure that we match the selector with the right
@ -138,6 +163,8 @@ pub struct Invalidation<'a> {
/// ///
/// This order is a "parse order" offset, that is, zero is the leftmost part /// This order is a "parse order" offset, that is, zero is the leftmost part
/// of the selector written as parsed / serialized. /// of the selector written as parsed / serialized.
///
/// It is initialized from the offset from `dependency`.
offset: usize, offset: usize,
/// Whether the invalidation was already matched by any previous sibling or /// Whether the invalidation was already matched by any previous sibling or
/// ancestor. /// ancestor.
@ -149,16 +176,18 @@ pub struct Invalidation<'a> {
} }
impl<'a> Invalidation<'a> { impl<'a> Invalidation<'a> {
/// Create a new invalidation for a given selector and offset. /// Create a new invalidation for matching a dependency.
pub fn new( pub fn new(dependency: &'a Dependency, scope: Option<OpaqueElement>) -> Self {
selector: &'a Selector<SelectorImpl>, debug_assert!(
scope: Option<OpaqueElement>, dependency.selector_offset == dependency.selector.len() + 1 ||
offset: usize, dependency.invalidation_kind() != DependencyInvalidationKind::Element,
) -> Self { "No point to this, if the dependency matched the element we should just invalidate it"
);
Self { Self {
selector, dependency,
scope, scope,
offset, // + 1 to go past the combinator.
offset: dependency.selector.len() + 1 - dependency.selector_offset,
matched_by_any_previous: false, matched_by_any_previous: false,
} }
} }
@ -174,7 +203,11 @@ impl<'a> Invalidation<'a> {
// for the weird pseudos in <input type="number">. // for the weird pseudos in <input type="number">.
// //
// We should be able to do better here! // We should be able to do better here!
match self.selector.combinator_at_parse_order(self.offset - 1) { match self
.dependency
.selector
.combinator_at_parse_order(self.offset - 1)
{
Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true, Combinator::Descendant | Combinator::LaterSibling | Combinator::PseudoElement => true,
Combinator::Part | Combinator::Part |
Combinator::SlotAssignment | Combinator::SlotAssignment |
@ -188,7 +221,11 @@ impl<'a> Invalidation<'a> {
return InvalidationKind::Descendant(DescendantInvalidationKind::Dom); return InvalidationKind::Descendant(DescendantInvalidationKind::Dom);
} }
match self.selector.combinator_at_parse_order(self.offset - 1) { match self
.dependency
.selector
.combinator_at_parse_order(self.offset - 1)
{
Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => { Combinator::Child | Combinator::Descendant | Combinator::PseudoElement => {
InvalidationKind::Descendant(DescendantInvalidationKind::Dom) InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
}, },
@ -206,7 +243,11 @@ impl<'a> fmt::Debug for Invalidation<'a> {
use cssparser::ToCss; use cssparser::ToCss;
f.write_str("Invalidation(")?; f.write_str("Invalidation(")?;
for component in self.selector.iter_raw_parse_order_from(self.offset) { for component in self
.dependency
.selector
.iter_raw_parse_order_from(self.offset)
{
if matches!(*component, Component::Combinator(..)) { if matches!(*component, Component::Combinator(..)) {
break; break;
} }
@ -763,201 +804,251 @@ where
context.current_host = invalidation.scope; context.current_host = invalidation.scope;
matches_compound_selector_from( matches_compound_selector_from(
&invalidation.selector, &invalidation.dependency.selector,
invalidation.offset, invalidation.offset,
context, context,
&self.element, &self.element,
) )
}; };
let mut invalidated_self = false; let next_invalidation = match matching_result {
let mut matched = false; CompoundSelectorMatchingResult::NotMatched => {
match matching_result { return SingleInvalidationResult {
invalidated_self: false,
matched: false,
}
},
CompoundSelectorMatchingResult::FullyMatched => { CompoundSelectorMatchingResult::FullyMatched => {
debug!(" > Invalidation matched completely"); debug!(" > Invalidation matched completely");
matched = true; // We matched completely. If we're an inner selector now we need
invalidated_self = true; // to go outside our selector and carry on invalidating.
let mut cur_dependency = invalidation.dependency;
loop {
cur_dependency = match cur_dependency.parent {
None => {
return SingleInvalidationResult {
invalidated_self: true,
matched: true,
}
},
Some(ref p) => &**p,
};
debug!(" > Checking outer dependency {:?}", cur_dependency);
// The inner selector changed, now check if the full
// previous part of the selector did, before keeping
// checking for descendants.
if !self
.processor
.check_outer_dependency(cur_dependency, self.element)
{
return SingleInvalidationResult {
invalidated_self: false,
matched: false,
};
}
if cur_dependency.invalidation_kind() == DependencyInvalidationKind::Element {
continue;
}
debug!(" > Generating invalidation");
break Invalidation::new(cur_dependency, invalidation.scope);
}
}, },
CompoundSelectorMatchingResult::Matched { CompoundSelectorMatchingResult::Matched {
next_combinator_offset, next_combinator_offset,
} => { } => Invalidation {
let next_combinator = invalidation dependency: invalidation.dependency,
.selector scope: invalidation.scope,
.combinator_at_parse_order(next_combinator_offset); offset: next_combinator_offset + 1,
matched = true; matched_by_any_previous: false,
if matches!(next_combinator, Combinator::PseudoElement) {
// This will usually be the very next component, except for
// the fact that we store compound selectors the other way
// around, so there could also be state pseudo-classes.
let pseudo_selector = invalidation
.selector
.iter_raw_parse_order_from(next_combinator_offset + 1)
.skip_while(|c| matches!(**c, Component::NonTSPseudoClass(..)))
.next()
.unwrap();
let pseudo = match *pseudo_selector {
Component::PseudoElement(ref pseudo) => pseudo,
_ => unreachable!(
"Someone seriously messed up selector parsing: \
{:?} at offset {:?}: {:?}",
invalidation.selector, next_combinator_offset, pseudo_selector,
),
};
// FIXME(emilio): This is not ideal, and could not be
// accurate if we ever have stateful element-backed eager
// pseudos.
//
// Ideally, we'd just remove element-backed eager pseudos
// altogether, given they work fine without it. Only gotcha
// is that we wouldn't style them in parallel, which may or
// may not be an issue.
//
// Also, this could be more fine grained now (perhaps a
// RESTYLE_PSEUDOS hint?).
//
// Note that we'll also restyle the pseudo-element because
// it would match this invalidation.
if self.processor.invalidates_on_eager_pseudo_element() {
if pseudo.is_eager() {
invalidated_self = true;
}
// If we start or stop matching some marker rules, and
// don't have a marker, then we need to restyle the
// element to potentially create one.
//
// Same caveats as for other eager pseudos apply, this
// could be more fine-grained.
if pseudo.is_marker() && self.element.marker_pseudo_element().is_none() {
invalidated_self = true;
}
// FIXME: ::selection doesn't generate elements, so the
// regular invalidation doesn't work for it. We store
// the cached selection style holding off the originating
// element, so we need to restyle it in order to invalidate
// it. This is still not quite correct, since nothing
// triggers a repaint necessarily, but matches old Gecko
// behavior, and the ::selection implementation needs to
// change significantly anyway to implement
// https://github.com/w3c/csswg-drafts/issues/2474.
if pseudo.is_selection() {
invalidated_self = true;
}
}
}
let next_invalidation = Invalidation {
selector: invalidation.selector,
scope: invalidation.scope,
offset: next_combinator_offset + 1,
matched_by_any_previous: false,
};
debug!(
" > Invalidation matched, next: {:?}, ({:?})",
next_invalidation, next_combinator
);
let next_invalidation_kind = next_invalidation.kind();
// We can skip pushing under some circumstances, and we should
// because otherwise the invalidation list could grow
// exponentially.
//
// * First of all, both invalidations need to be of the same
// kind. This is because of how we propagate them going to
// the right of the tree for sibling invalidations and going
// down the tree for children invalidations. A sibling
// invalidation that ends up generating a children
// invalidation ends up (correctly) in five different lists,
// not in the same list five different times.
//
// * Then, the invalidation needs to be matched by a previous
// ancestor/sibling, in order to know that this invalidation
// has been generated already.
//
// * Finally, the new invalidation needs to be
// `effective_for_next()`, in order for us to know that it is
// still in the list, since we remove the dependencies that
// aren't from the lists for our children / siblings.
//
// To go through an example, let's imagine we are processing a
// dom subtree like:
//
// <div><address><div><div/></div></address></div>
//
// And an invalidation list with a single invalidation like:
//
// [div div div]
//
// When we process the invalidation list for the outer div, we
// match it, and generate a `div div` invalidation, so for the
// <address> child we have:
//
// [div div div, div div]
//
// With the first of them marked as `matched`.
//
// When we process the <address> child, we don't match any of
// them, so both invalidations go untouched to our children.
//
// When we process the second <div>, we match _both_
// invalidations.
//
// However, when matching the first, we can tell it's been
// matched, and not push the corresponding `div div`
// invalidation, since we know it's necessarily already on the
// list.
//
// Thus, without skipping the push, we'll arrive to the
// innermost <div> with:
//
// [div div div, div div, div div, div]
//
// While skipping it, we won't arrive here with duplicating
// dependencies:
//
// [div div div, div div, div]
//
let can_skip_pushing = next_invalidation_kind == invalidation_kind &&
invalidation.matched_by_any_previous &&
next_invalidation.effective_for_next();
if can_skip_pushing {
debug!(
" > Can avoid push, since the invalidation had \
already been matched before"
);
} else {
match next_invalidation_kind {
InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
descendant_invalidations
.dom_descendants
.push(next_invalidation);
},
InvalidationKind::Descendant(DescendantInvalidationKind::Part) => {
descendant_invalidations.parts.push(next_invalidation);
},
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
descendant_invalidations
.slotted_descendants
.push(next_invalidation);
},
InvalidationKind::Sibling => {
sibling_invalidations.push(next_invalidation);
},
}
}
}, },
CompoundSelectorMatchingResult::NotMatched => {}, };
debug_assert_ne!(
next_invalidation.offset, 0,
"Rightmost selectors shouldn't generate more invalidations",
);
let mut invalidated_self = false;
let next_combinator = next_invalidation
.dependency
.selector
.combinator_at_parse_order(next_invalidation.offset - 1);
if matches!(next_combinator, Combinator::PseudoElement) {
// This will usually be the very next component, except for
// the fact that we store compound selectors the other way
// around, so there could also be state pseudo-classes.
let pseudo = next_invalidation
.dependency
.selector
.iter_raw_parse_order_from(next_invalidation.offset)
.flat_map(|c| {
if let Component::PseudoElement(ref pseudo) = *c {
return Some(pseudo);
}
// TODO: Would be nice to make this a diagnostic_assert! of
// sorts.
debug_assert!(
c.maybe_allowed_after_pseudo_element(),
"Someone seriously messed up selector parsing: \
{:?} at offset {:?}: {:?}",
next_invalidation.dependency,
next_invalidation.offset,
c,
);
None
})
.next()
.unwrap();
// FIXME(emilio): This is not ideal, and could not be
// accurate if we ever have stateful element-backed eager
// pseudos.
//
// Ideally, we'd just remove element-backed eager pseudos
// altogether, given they work fine without it. Only gotcha
// is that we wouldn't style them in parallel, which may or
// may not be an issue.
//
// Also, this could be more fine grained now (perhaps a
// RESTYLE_PSEUDOS hint?).
//
// Note that we'll also restyle the pseudo-element because
// it would match this invalidation.
if self.processor.invalidates_on_eager_pseudo_element() {
if pseudo.is_eager() {
invalidated_self = true;
}
// If we start or stop matching some marker rules, and
// don't have a marker, then we need to restyle the
// element to potentially create one.
//
// Same caveats as for other eager pseudos apply, this
// could be more fine-grained.
if pseudo.is_marker() && self.element.marker_pseudo_element().is_none() {
invalidated_self = true;
}
// FIXME: ::selection doesn't generate elements, so the
// regular invalidation doesn't work for it. We store
// the cached selection style holding off the originating
// element, so we need to restyle it in order to invalidate
// it. This is still not quite correct, since nothing
// triggers a repaint necessarily, but matches old Gecko
// behavior, and the ::selection implementation needs to
// change significantly anyway to implement
// https://github.com/w3c/csswg-drafts/issues/2474.
if pseudo.is_selection() {
invalidated_self = true;
}
}
}
debug!(
" > Invalidation matched, next: {:?}, ({:?})",
next_invalidation, next_combinator
);
let next_invalidation_kind = next_invalidation.kind();
// We can skip pushing under some circumstances, and we should
// because otherwise the invalidation list could grow
// exponentially.
//
// * First of all, both invalidations need to be of the same
// kind. This is because of how we propagate them going to
// the right of the tree for sibling invalidations and going
// down the tree for children invalidations. A sibling
// invalidation that ends up generating a children
// invalidation ends up (correctly) in five different lists,
// not in the same list five different times.
//
// * Then, the invalidation needs to be matched by a previous
// ancestor/sibling, in order to know that this invalidation
// has been generated already.
//
// * Finally, the new invalidation needs to be
// `effective_for_next()`, in order for us to know that it is
// still in the list, since we remove the dependencies that
// aren't from the lists for our children / siblings.
//
// To go through an example, let's imagine we are processing a
// dom subtree like:
//
// <div><address><div><div/></div></address></div>
//
// And an invalidation list with a single invalidation like:
//
// [div div div]
//
// When we process the invalidation list for the outer div, we
// match it, and generate a `div div` invalidation, so for the
// <address> child we have:
//
// [div div div, div div]
//
// With the first of them marked as `matched`.
//
// When we process the <address> child, we don't match any of
// them, so both invalidations go untouched to our children.
//
// When we process the second <div>, we match _both_
// invalidations.
//
// However, when matching the first, we can tell it's been
// matched, and not push the corresponding `div div`
// invalidation, since we know it's necessarily already on the
// list.
//
// Thus, without skipping the push, we'll arrive to the
// innermost <div> with:
//
// [div div div, div div, div div, div]
//
// While skipping it, we won't arrive here with duplicating
// dependencies:
//
// [div div div, div div, div]
//
let can_skip_pushing = next_invalidation_kind == invalidation_kind &&
invalidation.matched_by_any_previous &&
next_invalidation.effective_for_next();
if can_skip_pushing {
debug!(
" > Can avoid push, since the invalidation had \
already been matched before"
);
} else {
match next_invalidation_kind {
InvalidationKind::Descendant(DescendantInvalidationKind::Dom) => {
descendant_invalidations
.dom_descendants
.push(next_invalidation);
},
InvalidationKind::Descendant(DescendantInvalidationKind::Part) => {
descendant_invalidations.parts.push(next_invalidation);
},
InvalidationKind::Descendant(DescendantInvalidationKind::Slotted) => {
descendant_invalidations
.slotted_descendants
.push(next_invalidation);
},
InvalidationKind::Sibling => {
sibling_invalidations.push(next_invalidation);
},
}
} }
SingleInvalidationResult { SingleInvalidationResult {
invalidated_self, invalidated_self,
matched, matched: true,
} }
} }
} }

View file

@ -42,7 +42,6 @@ where
descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>, descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
sibling_invalidations: &'a mut InvalidationVector<'selectors>, sibling_invalidations: &'a mut InvalidationVector<'selectors>,
invalidates_self: bool, invalidates_self: bool,
attr_selector_flags: InvalidationMapFlags,
} }
/// An invalidation processor for style changes due to state and attribute /// An invalidation processor for style changes due to state and attribute
@ -79,6 +78,39 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
} }
} }
/// Checks a dependency against a given element and wrapper, to see if something
/// changed.
pub fn check_dependency<E, W>(
dependency: &Dependency,
element: &E,
wrapper: &W,
mut context: &mut MatchingContext<'_, E::Impl>,
) -> bool
where
E: TElement,
W: selectors::Element<Impl = E::Impl>,
{
let matches_now = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
element,
&mut context,
&mut |_, _| {},
);
let matched_then = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
wrapper,
&mut context,
&mut |_, _| {},
);
matched_then != matches_now
}
/// Whether we should process the descendants of a given element for style /// Whether we should process the descendants of a given element for style
/// invalidation. /// invalidation.
pub fn should_process_descendants(data: &ElementData) -> bool { pub fn should_process_descendants(data: &ElementData) -> bool {
@ -133,6 +165,14 @@ where
true true
} }
fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool {
// We cannot assert about `element` having a snapshot here (in fact it
// most likely won't), because it may be an arbitrary descendant or
// later-sibling of the element we started invalidating with.
let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map);
check_dependency(dependency, &element, &wrapper, &mut self.matching_context)
}
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> { fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
&mut self.matching_context &mut self.matching_context
} }
@ -156,8 +196,6 @@ where
return false; return false;
} }
let mut attr_selector_flags = InvalidationMapFlags::empty();
// If we the visited state changed, we force a restyle here. Matching // If we the visited state changed, we force a restyle here. Matching
// doesn't depend on the actual visited state at all, so we can't look // doesn't depend on the actual visited state at all, so we can't look
// at matching results to decide what to do for this case. // at matching results to decide what to do for this case.
@ -175,7 +213,6 @@ where
let mut classes_removed = SmallVec::<[Atom; 8]>::new(); let mut classes_removed = SmallVec::<[Atom; 8]>::new();
let mut classes_added = SmallVec::<[Atom; 8]>::new(); let mut classes_added = SmallVec::<[Atom; 8]>::new();
if snapshot.class_changed() { if snapshot.class_changed() {
attr_selector_flags.insert(InvalidationMapFlags::HAS_CLASS_ATTR_SELECTOR);
// TODO(emilio): Do this more efficiently! // TODO(emilio): Do this more efficiently!
snapshot.each_class(|c| { snapshot.each_class(|c| {
if !element.has_class(c, CaseSensitivity::CaseSensitive) { if !element.has_class(c, CaseSensitivity::CaseSensitive) {
@ -193,7 +230,6 @@ where
let mut id_removed = None; let mut id_removed = None;
let mut id_added = None; let mut id_added = None;
if snapshot.id_changed() { if snapshot.id_changed() {
attr_selector_flags.insert(InvalidationMapFlags::HAS_ID_ATTR_SELECTOR);
let old_id = snapshot.id_attr(); let old_id = snapshot.id_attr();
let current_id = element.id(); let current_id = element.id();
@ -204,10 +240,7 @@ where
} }
if log_enabled!(::log::Level::Debug) { if log_enabled!(::log::Level::Debug) {
debug!( debug!("Collecting changes for: {:?}", element);
"Collecting changes for: {:?}, flags {:?}",
element, attr_selector_flags
);
if !state_changes.is_empty() { if !state_changes.is_empty() {
debug!(" > state: {:?}", state_changes); debug!(" > state: {:?}", state_changes);
} }
@ -220,7 +253,11 @@ where
classes_added, classes_removed classes_added, classes_removed
); );
} }
if snapshot.other_attr_changed() { let mut attributes_changed = false;
snapshot.each_attr_changed(|_| {
attributes_changed = true;
});
if attributes_changed {
debug!( debug!(
" > attributes changed, old: {}", " > attributes changed, old: {}",
snapshot.debug_list_attributes() snapshot.debug_list_attributes()
@ -255,7 +292,6 @@ where
descendant_invalidations, descendant_invalidations,
sibling_invalidations, sibling_invalidations,
invalidates_self: false, invalidates_self: false,
attr_selector_flags,
}; };
let document_origins = if !matches_document_author_rules { let document_origins = if !matches_document_author_rules {
@ -365,12 +401,13 @@ where
} }
} }
let should_examine_attribute_selector_map = self.snapshot.each_attr_changed(|attribute| {
self.snapshot.other_attr_changed() || map.flags.intersects(self.attr_selector_flags); if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) {
for dep in deps {
if should_examine_attribute_selector_map { self.scan_dependency(dep);
self.collect_dependencies_in_map(&map.other_attribute_affecting_selectors) }
} }
});
let state_changes = self.state_changes; let state_changes = self.state_changes;
if !state_changes.is_empty() { if !state_changes.is_empty() {
@ -378,19 +415,6 @@ where
} }
} }
fn collect_dependencies_in_map(&mut self, map: &'selectors SelectorMap<Dependency>) {
map.lookup_with_additional(
self.lookup_element,
self.matching_context.quirks_mode(),
self.removed_id,
self.classes_removed,
|dependency| {
self.scan_dependency(dependency);
true
},
);
}
fn collect_state_dependencies( fn collect_state_dependencies(
&mut self, &mut self,
map: &'selectors SelectorMap<StateDependency>, map: &'selectors SelectorMap<StateDependency>,
@ -412,28 +436,14 @@ where
} }
/// Check whether a dependency should be taken into account. /// Check whether a dependency should be taken into account.
#[inline]
fn check_dependency(&mut self, dependency: &Dependency) -> bool { fn check_dependency(&mut self, dependency: &Dependency) -> bool {
let element = &self.element; check_dependency(
let wrapper = &self.wrapper; dependency,
let matches_now = matches_selector( &self.element,
&dependency.selector, &self.wrapper,
dependency.selector_offset,
None,
element,
&mut self.matching_context, &mut self.matching_context,
&mut |_, _| {}, )
);
let matched_then = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
wrapper,
&mut self.matching_context,
&mut |_, _| {},
);
matched_then != matches_now
} }
fn scan_dependency(&mut self, dependency: &'selectors Dependency) { fn scan_dependency(&mut self, dependency: &'selectors Dependency) {
@ -456,18 +466,21 @@ where
let invalidation_kind = dependency.invalidation_kind(); let invalidation_kind = dependency.invalidation_kind();
if matches!(invalidation_kind, DependencyInvalidationKind::Element) { if matches!(invalidation_kind, DependencyInvalidationKind::Element) {
self.invalidates_self = true; if let Some(ref parent) = dependency.parent {
// We know something changed in the inner selector, go outwards
// now.
self.scan_dependency(parent);
} else {
self.invalidates_self = true;
}
return; return;
} }
debug_assert_ne!(dependency.selector_offset, 0); debug_assert_ne!(dependency.selector_offset, 0);
debug_assert_ne!(dependency.selector_offset, dependency.selector.len()); debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
let invalidation = Invalidation::new( let invalidation =
&dependency.selector, Invalidation::new(&dependency, self.matching_context.current_host.clone());
self.matching_context.current_host.clone(),
dependency.selector.len() - dependency.selector_offset + 1,
);
match invalidation_kind { match invalidation_kind {
DependencyInvalidationKind::Element => unreachable!(), DependencyInvalidationKind::Element => unreachable!(),

View file

@ -272,3 +272,26 @@ where
<Self as num_traits::Zero>::is_zero(self) <Self as num_traits::Zero>::is_zero(self)
} }
} }
/// A trait pretty much similar to num_traits::One, but without the need of
/// implementing `Mul`.
pub trait One {
/// Reutrns the one value.
fn one() -> Self;
/// Returns whether this value is one.
fn is_one(&self) -> bool;
}
impl<T> One for T
where
T: num_traits::One + PartialEq,
{
fn one() -> Self {
<Self as num_traits::One>::one()
}
fn is_one(&self) -> bool {
*self == One::one()
}
}

View file

@ -7,7 +7,6 @@
#![allow(unsafe_code)] #![allow(unsafe_code)]
#![deny(missing_docs)] #![deny(missing_docs)]
use crate::animation::AnimationState;
use crate::computed_value_flags::ComputedValueFlags; use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; use crate::context::{ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
use crate::context::{SharedStyleContext, StyleContext}; use crate::context::{SharedStyleContext, StyleContext};
@ -438,6 +437,8 @@ trait PrivateMatchMethods: TElement {
_restyle_hint: RestyleHint, _restyle_hint: RestyleHint,
_important_rules_changed: bool, _important_rules_changed: bool,
) { ) {
use crate::animation::AnimationState;
let this_opaque = self.as_node().opaque(); let this_opaque = self.as_node().opaque();
let shared_context = context.shared; let shared_context = context.shared;
let mut animation_states = shared_context.animation_states.write(); let mut animation_states = shared_context.animation_states.write();

View file

@ -4,9 +4,10 @@
//! Media features. //! Media features.
use super::media_feature_expression::{AspectRatio, RangeOrOperator}; use super::media_feature_expression::RangeOrOperator;
use super::Device; use super::Device;
use crate::parser::ParserContext; use crate::parser::ParserContext;
use crate::values::computed::position::Ratio;
use crate::values::computed::{CSSPixelLength, Resolution}; use crate::values::computed::{CSSPixelLength, Resolution};
use crate::Atom; use crate::Atom;
use cssparser::Parser; use cssparser::Parser;
@ -45,7 +46,7 @@ pub enum Evaluator {
Float(MediaFeatureEvaluator<f32>), Float(MediaFeatureEvaluator<f32>),
BoolInteger(MediaFeatureEvaluator<bool>), BoolInteger(MediaFeatureEvaluator<bool>),
/// A non-negative number ratio, such as the one from device-pixel-ratio. /// A non-negative number ratio, such as the one from device-pixel-ratio.
NumberRatio(MediaFeatureEvaluator<AspectRatio>), NumberRatio(MediaFeatureEvaluator<Ratio>),
/// A resolution. /// A resolution.
Resolution(MediaFeatureEvaluator<Resolution>), Resolution(MediaFeatureEvaluator<Resolution>),
/// A keyword value. /// A keyword value.

View file

@ -15,9 +15,8 @@ use crate::parser::{Parse, ParserContext};
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use crate::servo::media_queries::MEDIA_FEATURES; use crate::servo::media_queries::MEDIA_FEATURES;
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase}; use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::values::computed::position::Ratio;
use crate::values::computed::{self, ToComputedValue}; use crate::values::computed::{self, ToComputedValue};
#[cfg(feature = "gecko")]
use crate::values::specified::NonNegativeNumber;
use crate::values::specified::{Integer, Length, Number, Resolution}; use crate::values::specified::{Integer, Length, Number, Resolution};
use crate::values::{serialize_atom_identifier, CSSFloat}; use crate::values::{serialize_atom_identifier, CSSFloat};
use crate::{Atom, Zero}; use crate::{Atom, Zero};
@ -26,30 +25,6 @@ use std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// An aspect ratio, with a numerator and denominator.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct AspectRatio(pub CSSFloat, pub CSSFloat);
impl ToCss for AspectRatio {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
self.0.to_css(dest)?;
dest.write_str(" / ")?;
self.1.to_css(dest)
}
}
impl PartialOrd for AspectRatio {
fn partial_cmp(&self, other: &AspectRatio) -> Option<Ordering> {
f64::partial_cmp(
&(self.0 as f64 * other.1 as f64),
&(self.1 as f64 * other.0 as f64),
)
}
}
/// The kind of matching that should be performed on a media feature value. /// The kind of matching that should be performed on a media feature value.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum Range { pub enum Range {
@ -460,7 +435,7 @@ pub enum MediaExpressionValue {
BoolInteger(bool), BoolInteger(bool),
/// A single non-negative number or two non-negative numbers separated by '/', /// A single non-negative number or two non-negative numbers separated by '/',
/// with optional whitespace on either side of the '/'. /// with optional whitespace on either side of the '/'.
NumberRatio(AspectRatio), NumberRatio(Ratio),
/// A resolution. /// A resolution.
Resolution(Resolution), Resolution(Resolution),
/// An enumerated value, defined by the variant keyword table in the /// An enumerated value, defined by the variant keyword table in the
@ -517,24 +492,14 @@ impl MediaExpressionValue {
MediaExpressionValue::Float(number.get()) MediaExpressionValue::Float(number.get())
}, },
Evaluator::NumberRatio(..) => { Evaluator::NumberRatio(..) => {
#[cfg(feature = "gecko")] use crate::values::generics::position::Ratio as GenericRatio;
{ use crate::values::generics::NonNegative;
if static_prefs::pref!("layout.css.aspect-ratio-number.enabled") { use crate::values::specified::position::Ratio;
let a = NonNegativeNumber::parse(context, input)?.0.get();
let b = match input.try_parse(|input| input.expect_delim('/')) {
Ok(()) => NonNegativeNumber::parse(context, input)?.0.get(),
_ => 1.0,
};
return Ok(MediaExpressionValue::NumberRatio(AspectRatio(a, b)));
}
}
let a = Integer::parse_positive(context, input)?; let ratio = Ratio::parse(context, input)?;
input.expect_delim('/')?; MediaExpressionValue::NumberRatio(GenericRatio(
let b = Integer::parse_positive(context, input)?; NonNegative(ratio.0.get()),
MediaExpressionValue::NumberRatio(AspectRatio( NonNegative(ratio.1.get()),
a.value() as CSSFloat,
b.value() as CSSFloat,
)) ))
}, },
Evaluator::Resolution(..) => { Evaluator::Resolution(..) => {

View file

@ -207,11 +207,20 @@ where
} }
} }
impl Parse for UnicodeRange { impl Parse for crate::OwnedStr {
fn parse<'i, 't>( fn parse<'i, 't>(
_context: &ParserContext, _: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
UnicodeRange::parse(input).map_err(|e| e.into()) Ok(input.expect_string()?.as_ref().to_owned().into())
}
}
impl Parse for UnicodeRange {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Ok(UnicodeRange::parse(input)?)
} }
} }

View file

@ -10,8 +10,8 @@ use crate::dom::TElement;
use crate::font_metrics::FontMetricsProvider; use crate::font_metrics::FontMetricsProvider;
use crate::logical_geometry::WritingMode; use crate::logical_geometry::WritingMode;
use crate::media_queries::Device; use crate::media_queries::Device;
use crate::properties::{ComputedValues, StyleBuilder}; use crate::properties::{ComputedValues, StyleBuilder, Importance};
use crate::properties::{LonghandId, LonghandIdSet, CSSWideKeyword}; use crate::properties::{LonghandId, LonghandIdSet, CSSWideKeyword, PropertyFlags};
use crate::properties::{PropertyDeclaration, PropertyDeclarationId, DeclarationImportanceIterator}; use crate::properties::{PropertyDeclaration, PropertyDeclarationId, DeclarationImportanceIterator};
use crate::properties::{CASCADE_PROPERTY, ComputedValueFlags}; use crate::properties::{CASCADE_PROPERTY, ComputedValueFlags};
use crate::rule_cache::{RuleCache, RuleCacheConditions}; use crate::rule_cache::{RuleCache, RuleCacheConditions};
@ -20,7 +20,6 @@ use crate::selector_parser::PseudoElement;
use crate::stylesheets::{Origin, PerOrigin}; use crate::stylesheets::{Origin, PerOrigin};
use servo_arc::Arc; use servo_arc::Arc;
use crate::shared_lock::StylesheetGuards; use crate::shared_lock::StylesheetGuards;
use smallbitvec::SmallBitVec;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Cow; use std::borrow::Cow;
use std::cell::RefCell; use std::cell::RefCell;
@ -108,6 +107,84 @@ where
) )
} }
struct DeclarationIterator<'a> {
// Global to the iteration.
guards: &'a StylesheetGuards<'a>,
restriction: Option<PropertyFlags>,
// The rule we're iterating over.
current_rule_node: Option<&'a StrongRuleNode>,
// Per rule state.
declarations: DeclarationImportanceIterator<'a>,
origin: Origin,
importance: Importance,
}
impl<'a> DeclarationIterator<'a> {
#[inline]
fn new(rule_node: &'a StrongRuleNode, guards: &'a StylesheetGuards, pseudo: Option<&PseudoElement>) -> Self {
let restriction = pseudo.and_then(|p| p.property_restriction());
let mut iter = Self {
guards,
current_rule_node: Some(rule_node),
origin: Origin::Author,
importance: Importance::Normal,
declarations: DeclarationImportanceIterator::default(),
restriction,
};
iter.update_for_node(rule_node);
iter
}
fn update_for_node(&mut self, node: &'a StrongRuleNode) {
let origin = node.cascade_level().origin();
self.origin = origin;
self.importance = node.importance();
let guard = match origin {
Origin::Author => self.guards.author,
Origin::User | Origin::UserAgent => self.guards.ua_or_user,
};
self.declarations = match node.style_source() {
Some(source) => source.read(guard).declaration_importance_iter(),
None => DeclarationImportanceIterator::default(),
};
}
}
impl<'a> Iterator for DeclarationIterator<'a> {
type Item = (&'a PropertyDeclaration, Origin);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some((decl, importance)) = self.declarations.next_back() {
if self.importance != importance {
continue;
}
let origin = self.origin;
if let Some(restriction) = self.restriction {
// decl.id() is either a longhand or a custom
// property. Custom properties are always allowed, but
// longhands are only allowed if they have our
// restriction flag set.
if let PropertyDeclarationId::Longhand(id) = decl.id() {
if !id.flags().contains(restriction) && origin != Origin::UserAgent {
continue;
}
}
}
return Some((decl, origin));
}
let next_node = self.current_rule_node.take()?.parent()?;
self.current_rule_node = Some(next_node);
self.update_for_node(next_node);
}
}
}
fn cascade_rules<E>( fn cascade_rules<E>(
device: &Device, device: &Device,
pseudo: Option<&PseudoElement>, pseudo: Option<&PseudoElement>,
@ -130,54 +207,12 @@ where
parent_style.is_some(), parent_style.is_some(),
parent_style_ignoring_first_line.is_some() parent_style_ignoring_first_line.is_some()
); );
let empty = SmallBitVec::new();
let restriction = pseudo.and_then(|p| p.property_restriction());
let iter_declarations = || {
rule_node.self_and_ancestors().flat_map(|node| {
let origin = node.cascade_level().origin();
let node_importance = node.importance();
let guard = match origin {
Origin::Author => guards.author,
Origin::User | Origin::UserAgent => guards.ua_or_user,
};
let declarations = match node.style_source() {
Some(source) => source
.read(guard)
.declaration_importance_iter(),
None => DeclarationImportanceIterator::new(&[], &empty),
};
declarations
// Yield declarations later in source order (with more precedence) first.
.rev()
.filter_map(move |(declaration, declaration_importance)| {
if let Some(restriction) = restriction {
// declaration.id() is either a longhand or a custom
// property. Custom properties are always allowed, but
// longhands are only allowed if they have our
// restriction flag set.
if let PropertyDeclarationId::Longhand(id) = declaration.id() {
if !id.flags().contains(restriction) && origin != Origin::UserAgent {
return None;
}
}
}
if declaration_importance == node_importance {
Some((declaration, origin))
} else {
None
}
})
})
};
apply_declarations( apply_declarations(
device, device,
pseudo, pseudo,
rule_node, rule_node,
guards, guards,
iter_declarations, DeclarationIterator::new(rule_node, guards, pseudo),
parent_style, parent_style,
parent_style_ignoring_first_line, parent_style_ignoring_first_line,
layout_parent_style, layout_parent_style,
@ -208,12 +243,12 @@ pub enum CascadeMode<'a> {
/// NOTE: This function expects the declaration with more priority to appear /// NOTE: This function expects the declaration with more priority to appear
/// first. /// first.
pub fn apply_declarations<'a, E, F, I>( pub fn apply_declarations<'a, E, I>(
device: &Device, device: &Device,
pseudo: Option<&PseudoElement>, pseudo: Option<&PseudoElement>,
rules: &StrongRuleNode, rules: &StrongRuleNode,
guards: &StylesheetGuards, guards: &StylesheetGuards,
iter_declarations: F, iter: I,
parent_style: Option<&ComputedValues>, parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>, parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>, layout_parent_style: Option<&ComputedValues>,
@ -226,7 +261,6 @@ pub fn apply_declarations<'a, E, F, I>(
) -> Arc<ComputedValues> ) -> Arc<ComputedValues>
where where
E: TElement, E: TElement,
F: Fn() -> I,
I: Iterator<Item = (&'a PropertyDeclaration, Origin)>, I: Iterator<Item = (&'a PropertyDeclaration, Origin)>,
{ {
debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
@ -253,7 +287,7 @@ where
device, device,
); );
for (declaration, origin) in iter_declarations() { for (declaration, origin) in iter {
declarations.push((declaration, origin)); declarations.push((declaration, origin));
if let PropertyDeclaration::Custom(ref declaration) = *declaration { if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(declaration, origin); builder.cascade(declaration, origin);
@ -384,10 +418,15 @@ fn tweak_when_ignoring_colors(
if color.0.is_transparent() { if color.0.is_transparent() {
return; return;
} }
let color = builder.device.default_color(); // If the inherited color would be transparent, but we would
declarations_to_apply_unless_overriden.push( // override this with a non-transparent color, then override it with
PropertyDeclaration::Color(specified::ColorPropertyValue(color.into())) // the default color. Otherwise just let it inherit through.
) if builder.get_parent_inherited_text().clone_color().alpha == 0 {
let color = builder.device.default_color();
declarations_to_apply_unless_overriden.push(
PropertyDeclaration::Color(specified::ColorPropertyValue(color.into()))
)
}
}, },
// We honor url background-images if backplating. // We honor url background-images if backplating.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
@ -881,8 +920,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
let builder = &mut self.context.builder; let builder = &mut self.context.builder;
let parent_zoom = builder.get_parent_font().gecko().mAllowZoom; let parent_zoom = builder.get_parent_font().gecko().mAllowZoomAndMinSize;
let zoom = builder.get_font().gecko().mAllowZoom; let zoom = builder.get_font().gecko().mAllowZoomAndMinSize;
if zoom == parent_zoom { if zoom == parent_zoom {
return; return;
} }
@ -929,7 +968,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
} }
let mut min = Au(parent_font.mScriptMinSize); let mut min = Au(parent_font.mScriptMinSize);
if font.mAllowZoom { if font.mAllowZoomAndMinSize {
min = builder.device.zoom_text(min); min = builder.device.zoom_text(min);
} }

View file

@ -328,6 +328,7 @@ class Longhand(object):
"AlignItems", "AlignItems",
"AlignSelf", "AlignSelf",
"Appearance", "Appearance",
"AspectRatio",
"BreakBetween", "BreakBetween",
"BreakWithin", "BreakWithin",
"BackgroundRepeat", "BackgroundRepeat",
@ -356,6 +357,7 @@ class Longhand(object):
"JustifyItems", "JustifyItems",
"JustifySelf", "JustifySelf",
"LineBreak", "LineBreak",
"MasonryAutoFlow",
"MozForceBrokenImageIcon", "MozForceBrokenImageIcon",
"MozListReversed", "MozListReversed",
"MozScriptLevel", "MozScriptLevel",
@ -363,7 +365,6 @@ class Longhand(object):
"MozScriptSizeMultiplier", "MozScriptSizeMultiplier",
"TextDecorationSkipInk", "TextDecorationSkipInk",
"NonNegativeNumber", "NonNegativeNumber",
"Number",
"OffsetRotate", "OffsetRotate",
"Opacity", "Opacity",
"OutlineStyle", "OutlineStyle",
@ -687,6 +688,7 @@ class PropertyRestrictions:
def first_letter(data): def first_letter(data):
props = set([ props = set([
"color", "color",
"opacity",
"float", "float",
"initial-letter", "initial-letter",
@ -721,6 +723,7 @@ class PropertyRestrictions:
props = set([ props = set([
# Per spec. # Per spec.
"color", "color",
"opacity",
# Kinda like css-fonts? # Kinda like css-fonts?
"-moz-osx-font-smoothing", "-moz-osx-font-smoothing",

View file

@ -110,9 +110,17 @@ pub struct DeclarationImportanceIterator<'a> {
iter: Zip<Iter<'a, PropertyDeclaration>, smallbitvec::Iter<'a>>, iter: Zip<Iter<'a, PropertyDeclaration>, smallbitvec::Iter<'a>>,
} }
impl<'a> Default for DeclarationImportanceIterator<'a> {
fn default() -> Self {
Self {
iter: [].iter().zip(smallbitvec::Iter::default()),
}
}
}
impl<'a> DeclarationImportanceIterator<'a> { impl<'a> DeclarationImportanceIterator<'a> {
/// Constructor. /// Constructor.
pub fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self { fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self {
DeclarationImportanceIterator { DeclarationImportanceIterator {
iter: declarations.iter().zip(important.iter()), iter: declarations.iter().zip(important.iter()),
} }

View file

@ -802,7 +802,8 @@ fn static_assert() {
<% skip_position_longhands = " ".join(x.ident for x in SIDES) %> <% skip_position_longhands = " ".join(x.ident for x in SIDES) %>
<%self:impl_trait style_struct_name="Position" <%self:impl_trait style_struct_name="Position"
skip_longhands="${skip_position_longhands}"> skip_longhands="${skip_position_longhands}
masonry-auto-flow">
% for side in SIDES: % for side in SIDES:
<% impl_split_style_coord(side.ident, "mOffset", side.index) %> <% impl_split_style_coord(side.ident, "mOffset", side.index) %>
% endfor % endfor
@ -811,6 +812,7 @@ fn static_assert() {
self.gecko.mJustifyItems.computed = v; self.gecko.mJustifyItems.computed = v;
} }
${impl_simple_type_with_conversion("masonry_auto_flow", "mMasonryAutoFlow")}
</%self:impl_trait> </%self:impl_trait>
<% skip_outline_longhands = " ".join("outline-style outline-width".split() + <% skip_outline_longhands = " ".join("outline-style outline-width".split() +
@ -951,19 +953,19 @@ fn static_assert() {
if let Some(info) = v.keyword_info { if let Some(info) = v.keyword_info {
self.gecko.mFontSizeKeyword = match info.kw { self.gecko.mFontSizeKeyword = match info.kw {
KeywordSize::XXSmall => structs::NS_STYLE_FONT_SIZE_XXSMALL, KeywordSize::XXSmall => structs::StyleFontSize::Xxsmall,
KeywordSize::XSmall => structs::NS_STYLE_FONT_SIZE_XSMALL, KeywordSize::XSmall => structs::StyleFontSize::Xsmall,
KeywordSize::Small => structs::NS_STYLE_FONT_SIZE_SMALL, KeywordSize::Small => structs::StyleFontSize::Small,
KeywordSize::Medium => structs::NS_STYLE_FONT_SIZE_MEDIUM, KeywordSize::Medium => structs::StyleFontSize::Medium,
KeywordSize::Large => structs::NS_STYLE_FONT_SIZE_LARGE, KeywordSize::Large => structs::StyleFontSize::Large,
KeywordSize::XLarge => structs::NS_STYLE_FONT_SIZE_XLARGE, KeywordSize::XLarge => structs::StyleFontSize::Xxlarge,
KeywordSize::XXLarge => structs::NS_STYLE_FONT_SIZE_XXLARGE, KeywordSize::XXLarge => structs::StyleFontSize::Xxlarge,
KeywordSize::XXXLarge => structs::NS_STYLE_FONT_SIZE_XXXLARGE, KeywordSize::XXXLarge => structs::StyleFontSize::Xxxlarge,
} as u8; };
self.gecko.mFontSizeFactor = info.factor; self.gecko.mFontSizeFactor = info.factor;
self.gecko.mFontSizeOffset = info.offset.to_i32_au(); self.gecko.mFontSizeOffset = info.offset.to_i32_au();
} else { } else {
self.gecko.mFontSizeKeyword = structs::NS_STYLE_FONT_SIZE_NO_KEYWORD as u8; self.gecko.mFontSizeKeyword = structs::StyleFontSize::NoKeyword;
self.gecko.mFontSizeFactor = 1.; self.gecko.mFontSizeFactor = 1.;
self.gecko.mFontSizeOffset = 0; self.gecko.mFontSizeOffset = 0;
} }
@ -972,16 +974,16 @@ fn static_assert() {
pub fn clone_font_size(&self) -> FontSize { pub fn clone_font_size(&self) -> FontSize {
use crate::values::specified::font::{KeywordInfo, KeywordSize}; use crate::values::specified::font::{KeywordInfo, KeywordSize};
let size = Au(self.gecko.mSize).into(); let size = Au(self.gecko.mSize).into();
let kw = match self.gecko.mFontSizeKeyword as u32 { let kw = match self.gecko.mFontSizeKeyword {
structs::NS_STYLE_FONT_SIZE_XXSMALL => KeywordSize::XXSmall, structs::StyleFontSize::Xxsmall => KeywordSize::XXSmall,
structs::NS_STYLE_FONT_SIZE_XSMALL => KeywordSize::XSmall, structs::StyleFontSize::Xsmall => KeywordSize::XSmall,
structs::NS_STYLE_FONT_SIZE_SMALL => KeywordSize::Small, structs::StyleFontSize::Small => KeywordSize::Small,
structs::NS_STYLE_FONT_SIZE_MEDIUM => KeywordSize::Medium, structs::StyleFontSize::Medium => KeywordSize::Medium,
structs::NS_STYLE_FONT_SIZE_LARGE => KeywordSize::Large, structs::StyleFontSize::Large => KeywordSize::Large,
structs::NS_STYLE_FONT_SIZE_XLARGE => KeywordSize::XLarge, structs::StyleFontSize::Xlarge => KeywordSize::XLarge,
structs::NS_STYLE_FONT_SIZE_XXLARGE => KeywordSize::XXLarge, structs::StyleFontSize::Xxlarge => KeywordSize::XXLarge,
structs::NS_STYLE_FONT_SIZE_XXXLARGE => KeywordSize::XXXLarge, structs::StyleFontSize::Xxxlarge => KeywordSize::XXXLarge,
structs::NS_STYLE_FONT_SIZE_NO_KEYWORD => { structs::StyleFontSize::NoKeyword => {
return FontSize { return FontSize {
size, size,
keyword_info: None, keyword_info: None,
@ -1106,12 +1108,12 @@ fn static_assert() {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn set__x_text_zoom(&mut self, v: longhands::_x_text_zoom::computed_value::T) { pub fn set__x_text_zoom(&mut self, v: longhands::_x_text_zoom::computed_value::T) {
self.gecko.mAllowZoom = v.0; self.gecko.mAllowZoomAndMinSize = v.0;
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn copy__x_text_zoom_from(&mut self, other: &Self) { pub fn copy__x_text_zoom_from(&mut self, other: &Self) {
self.gecko.mAllowZoom = other.gecko.mAllowZoom; self.gecko.mAllowZoomAndMinSize = other.gecko.mAllowZoomAndMinSize;
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -1121,7 +1123,7 @@ fn static_assert() {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn clone__x_text_zoom(&self) -> longhands::_x_text_zoom::computed_value::T { pub fn clone__x_text_zoom(&self) -> longhands::_x_text_zoom::computed_value::T {
longhands::_x_text_zoom::computed_value::T(self.gecko.mAllowZoom) longhands::_x_text_zoom::computed_value::T(self.gecko.mAllowZoomAndMinSize)
} }
<% impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride") %> <% impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride") %>

View file

@ -68,8 +68,7 @@ pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>;
/// ///
/// FIXME: We need to add a path for custom properties, but that's trivial after /// FIXME: We need to add a path for custom properties, but that's trivial after
/// this (is a similar path to that of PropertyDeclaration). /// this (is a similar path to that of PropertyDeclaration).
#[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[derive(Debug, MallocSizeOf)]
#[derive(Debug)]
#[repr(u16)] #[repr(u16)]
pub enum AnimationValue { pub enum AnimationValue {
% for prop in data.longhands: % for prop in data.longhands:
@ -420,6 +419,7 @@ impl AnimationValue {
/// ///
/// SERVO ONLY: This doesn't properly handle things like updating 'em' units /// SERVO ONLY: This doesn't properly handle things like updating 'em' units
/// when animated font-size. /// when animated font-size.
#[cfg(feature = "servo")]
pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) { pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) {
match self { match self {
% for prop in data.longhands: % for prop in data.longhands:
@ -439,6 +439,11 @@ impl AnimationValue {
% endfor % endfor
} }
} }
/// As above, but a stub for Gecko.
#[cfg(feature = "gecko")]
pub fn set_in_style_for_servo(&self, _: &mut ComputedValues) {
}
} }
fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> { fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {

View file

@ -612,9 +612,7 @@ ${helpers.predefined_type(
engines="gecko", engines="gecko",
animation_value_type="none", animation_value_type="none",
flags="CREATES_STACKING_CONTEXT FIXPOS_CB", flags="CREATES_STACKING_CONTEXT FIXPOS_CB",
gecko_pref="layout.css.contain.enabled",
spec="https://drafts.csswg.org/css-contain/#contain-property", spec="https://drafts.csswg.org/css-contain/#contain-property",
enabled_in="chrome",
)} )}
// Non-standard // Non-standard

View file

@ -114,6 +114,17 @@ ${helpers.single_keyword(
animation_value_type="discrete", animation_value_type="discrete",
servo_restyle_damage="reflow", servo_restyle_damage="reflow",
)} )}
${helpers.predefined_type(
"justify-tracks",
"JustifyTracks",
"specified::JustifyTracks::default()",
engines="gecko",
gecko_pref="layout.css.grid-template-masonry-value.enabled",
animation_value_type="discrete",
servo_restyle_damage="reflow",
spec="https://github.com/w3c/csswg-drafts/issues/4650",
)}
% endif % endif
% if engine in ["servo-2013", "servo-2020"]: % if engine in ["servo-2013", "servo-2020"]:
@ -151,6 +162,17 @@ ${helpers.single_keyword(
servo_restyle_damage="reflow", servo_restyle_damage="reflow",
)} )}
${helpers.predefined_type(
"align-tracks",
"AlignTracks",
"specified::AlignTracks::default()",
engines="gecko",
gecko_pref="layout.css.grid-template-masonry-value.enabled",
animation_value_type="discrete",
servo_restyle_damage="reflow",
spec="https://github.com/w3c/csswg-drafts/issues/4650",
)}
${helpers.predefined_type( ${helpers.predefined_type(
"align-items", "align-items",
"AlignItems", "AlignItems",
@ -372,6 +394,16 @@ ${helpers.predefined_type(
% endfor % endfor
${helpers.predefined_type(
"masonry-auto-flow",
"MasonryAutoFlow",
"computed::MasonryAutoFlow::initial()",
engines="gecko",
gecko_pref="layout.css.grid-template-masonry-value.enabled",
animation_value_type="discrete",
spec="https://github.com/w3c/csswg-drafts/issues/4650",
)}
${helpers.predefined_type( ${helpers.predefined_type(
"grid-auto-flow", "grid-auto-flow",
"GridAutoFlow", "GridAutoFlow",
@ -415,17 +447,13 @@ ${helpers.predefined_type(
servo_restyle_damage="reflow", servo_restyle_damage="reflow",
)} )}
// NOTE(emilio): Before exposing this property to content, we probably need to
// change syntax and such, and make it apply to more elements.
//
// For now, it's used only for mapped attributes.
${helpers.predefined_type( ${helpers.predefined_type(
"aspect-ratio", "aspect-ratio",
"Number", "AspectRatio",
"computed::Number::zero()", "computed::AspectRatio::auto()",
engines="gecko servo-2013", engines="gecko servo-2013",
animation_value_type="ComputedValue", animation_value_type="discrete",
spec="Internal, for now", spec="https://drafts.csswg.org/css-sizing-4/#aspect-ratio",
enabled_in="", gecko_pref="layout.css.aspect-ratio.enabled",
servo_restyle_damage="reflow", servo_restyle_damage="reflow",
)} )}

View file

@ -278,8 +278,7 @@ impl SelectorMap<Rule> {
} }
impl<T: SelectorMapEntry> SelectorMap<T> { impl<T: SelectorMapEntry> SelectorMap<T> {
/// Inserts into the correct hash, trying id, class, localname and /// Inserts an entry into the correct bucket(s).
/// namespace.
pub fn insert( pub fn insert(
&mut self, &mut self,
entry: T, entry: T,
@ -287,46 +286,91 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
) -> Result<(), FailedAllocationError> { ) -> Result<(), FailedAllocationError> {
self.count += 1; self.count += 1;
let vector = match find_bucket(entry.selector()) { // NOTE(emilio): It'd be nice for this to be a separate function, but
Bucket::Root => &mut self.root, // then the compiler can't reason about the lifetime dependency between
Bucket::ID(id) => self // `entry` and `bucket`, and would force us to clone the rule in the
.id_hash // common path.
.try_entry(id.clone(), quirks_mode)? macro_rules! insert_into_bucket {
.or_insert_with(SmallVec::new), ($entry:ident, $bucket:expr) => {{
Bucket::Class(class) => self match $bucket {
.class_hash Bucket::Root => &mut self.root,
.try_entry(class.clone(), quirks_mode)? Bucket::ID(id) => self
.or_insert_with(SmallVec::new), .id_hash
Bucket::LocalName { name, lower_name } => { .try_entry(id.clone(), quirks_mode)?
// If the local name in the selector isn't lowercase, insert it .or_insert_with(SmallVec::new),
// into the rule hash twice. This means that, during lookup, we Bucket::Class(class) => self
// can always find the rules based on the local name of the .class_hash
// element, regardless of whether it's an html element in an .try_entry(class.clone(), quirks_mode)?
// html document (in which case we match against lower_name) or .or_insert_with(SmallVec::new),
// not (in which case we match against name). Bucket::LocalName { name, lower_name } => {
// // If the local name in the selector isn't lowercase,
// In the case of a non-html-element-in-html-document with a // insert it into the rule hash twice. This means that,
// lowercase localname and a non-lowercase selector, the // during lookup, we can always find the rules based on
// rulehash lookup may produce superfluous selectors, but the // the local name of the element, regardless of whether
// subsequent selector matching work will filter them out. // it's an html element in an html document (in which
if name != lower_name { // case we match against lower_name) or not (in which
self.local_name_hash // case we match against name).
.try_entry(lower_name.clone())? //
.or_insert_with(SmallVec::new) // In the case of a non-html-element-in-html-document
.try_push(entry.clone())?; // with a lowercase localname and a non-lowercase
// selector, the rulehash lookup may produce superfluous
// selectors, but the subsequent selector matching work
// will filter them out.
if name != lower_name {
self.local_name_hash
.try_entry(lower_name.clone())?
.or_insert_with(SmallVec::new)
.try_push($entry.clone())?;
}
self.local_name_hash
.try_entry(name.clone())?
.or_insert_with(SmallVec::new)
},
Bucket::Namespace(url) => self
.namespace_hash
.try_entry(url.clone())?
.or_insert_with(SmallVec::new),
Bucket::Universal => &mut self.other,
} }
self.local_name_hash .try_push($entry)?;
.try_entry(name.clone())? }};
.or_insert_with(SmallVec::new) }
},
Bucket::Namespace(url) => self let bucket = {
.namespace_hash let mut disjoint_buckets = SmallVec::new();
.try_entry(url.clone())? let bucket = find_bucket(entry.selector(), &mut disjoint_buckets);
.or_insert_with(SmallVec::new),
Bucket::Universal => &mut self.other, // See if inserting this selector in multiple entries in the
// selector map would be worth it. Consider a case like:
//
// .foo:where(div, #bar)
//
// There, `bucket` would be `Class(foo)`, and disjoint_buckets would
// be `[LocalName { div }, ID(bar)]`.
//
// Here we choose to insert the selector in the `.foo` bucket in
// such a case, as it's likely more worth it than inserting it in
// both `div` and `#bar`.
//
// This is specially true if there's any universal selector in the
// `disjoint_selectors` set, at which point we'd just be doing
// wasted work.
if !disjoint_buckets.is_empty() &&
disjoint_buckets
.iter()
.all(|b| b.more_specific_than(&bucket))
{
for bucket in &disjoint_buckets {
let entry = entry.clone();
insert_into_bucket!(entry, *bucket);
}
return Ok(());
}
bucket
}; };
vector.try_push(entry) insert_into_bucket!(entry, bucket);
Ok(())
} }
/// Looks up entries by id, class, local name, namespace, and other (in /// Looks up entries by id, class, local name, namespace, and other (in
@ -457,18 +501,43 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
} }
enum Bucket<'a> { enum Bucket<'a> {
Root, Universal,
ID(&'a Atom), Namespace(&'a Namespace),
Class(&'a Atom),
LocalName { LocalName {
name: &'a LocalName, name: &'a LocalName,
lower_name: &'a LocalName, lower_name: &'a LocalName,
}, },
Namespace(&'a Namespace), Class(&'a Atom),
Universal, ID(&'a Atom),
Root,
} }
fn specific_bucket_for<'a>(component: &'a Component<SelectorImpl>) -> Bucket<'a> { impl<'a> Bucket<'a> {
/// root > id > class > local name > namespace > universal.
#[inline]
fn specificity(&self) -> usize {
match *self {
Bucket::Universal => 0,
Bucket::Namespace(..) => 1,
Bucket::LocalName { .. } => 2,
Bucket::Class(..) => 3,
Bucket::ID(..) => 4,
Bucket::Root => 5,
}
}
#[inline]
fn more_specific_than(&self, other: &Self) -> bool {
self.specificity() > other.specificity()
}
}
type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>;
fn specific_bucket_for<'a>(
component: &'a Component<SelectorImpl>,
disjoint_buckets: &mut DisjointBuckets<'a>,
) -> Bucket<'a> {
match *component { match *component {
Component::Root => Bucket::Root, Component::Root => Bucket::Root,
Component::ID(ref id) => Bucket::ID(id), Component::ID(ref id) => Bucket::ID(id),
@ -498,14 +567,16 @@ fn specific_bucket_for<'a>(component: &'a Component<SelectorImpl>) -> Bucket<'a>
// //
// So inserting `span` in the rule hash makes sense since we want to // So inserting `span` in the rule hash makes sense since we want to
// match the slotted <span>. // match the slotted <span>.
Component::Slotted(ref selector) => find_bucket(selector.iter()), Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets),
Component::Host(Some(ref selector)) => find_bucket(selector.iter()), Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets),
Component::Is(ref list) | Component::Where(ref list) => { Component::Is(ref list) | Component::Where(ref list) => {
if list.len() == 1 { if list.len() == 1 {
find_bucket(list[0].iter()) find_bucket(list[0].iter(), disjoint_buckets)
} else { } else {
// TODO(emilio): We should handle the multiple element case by for selector in &**list {
// inserting multiple entries in the map. let bucket = find_bucket(selector.iter(), disjoint_buckets);
disjoint_buckets.push(bucket);
}
Bucket::Universal Bucket::Universal
} }
}, },
@ -515,39 +586,21 @@ fn specific_bucket_for<'a>(component: &'a Component<SelectorImpl>) -> Bucket<'a>
/// Searches a compound selector from left to right, and returns the appropriate /// Searches a compound selector from left to right, and returns the appropriate
/// bucket for it. /// bucket for it.
///
/// It also populates disjoint_buckets with dependencies from nested selectors
/// with any semantics like :is() and :where().
#[inline(always)] #[inline(always)]
fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> { fn find_bucket<'a>(
mut iter: SelectorIter<'a, SelectorImpl>,
disjoint_buckets: &mut DisjointBuckets<'a>,
) -> Bucket<'a> {
let mut current_bucket = Bucket::Universal; let mut current_bucket = Bucket::Universal;
loop { loop {
// We basically want to find the most specific bucket,
// where:
//
// root > id > class > local name > namespace > universal.
//
for ss in &mut iter { for ss in &mut iter {
let new_bucket = specific_bucket_for(ss); let new_bucket = specific_bucket_for(ss, disjoint_buckets);
match new_bucket { if new_bucket.more_specific_than(&current_bucket) {
Bucket::Root => return new_bucket, current_bucket = new_bucket;
Bucket::ID(..) => {
current_bucket = new_bucket;
},
Bucket::Class(..) => {
if !matches!(current_bucket, Bucket::ID(..)) {
current_bucket = new_bucket;
}
},
Bucket::LocalName { .. } => {
if matches!(current_bucket, Bucket::Universal | Bucket::Namespace(..)) {
current_bucket = new_bucket;
}
},
Bucket::Namespace(..) => {
if matches!(current_bucket, Bucket::Universal) {
current_bucket = new_bucket;
}
},
Bucket::Universal => {},
} }
} }
@ -558,7 +611,7 @@ fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
} }
} }
return current_bucket; current_bucket
} }
/// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode. /// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode.

View file

@ -391,12 +391,6 @@ impl NonTSPseudoClass {
pub fn needs_cache_revalidation(&self) -> bool { pub fn needs_cache_revalidation(&self) -> bool {
self.state_flag().is_empty() self.state_flag().is_empty()
} }
/// Returns true if the evaluation of the pseudo-class depends on the
/// element's attributes.
pub fn is_attr_based(&self) -> bool {
matches!(*self, NonTSPseudoClass::Lang(..))
}
} }
/// The abstract struct we implement the selector parser implementation on top /// The abstract struct we implement the selector parser implementation on top
@ -621,15 +615,14 @@ impl DerefMut for SnapshotMap {
} }
/// Servo's version of an element snapshot. /// Servo's version of an element snapshot.
#[derive(Debug)] #[derive(Debug, Default, MallocSizeOf)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub struct ServoElementSnapshot { pub struct ServoElementSnapshot {
/// The stored state of the element. /// The stored state of the element.
pub state: Option<ElementState>, pub state: Option<ElementState>,
/// The set of stored attributes and its values. /// The set of stored attributes and its values.
pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>, pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
/// Whether this element is an HTML element in an HTML document. /// The set of changed attributes and its values.
pub is_html_element_in_html_document: bool, pub changed_attrs: Vec<LocalName>,
/// Whether the class attribute changed or not. /// Whether the class attribute changed or not.
pub class_changed: bool, pub class_changed: bool,
/// Whether the id attribute changed or not. /// Whether the id attribute changed or not.
@ -640,15 +633,8 @@ pub struct ServoElementSnapshot {
impl ServoElementSnapshot { impl ServoElementSnapshot {
/// Create an empty element snapshot. /// Create an empty element snapshot.
pub fn new(is_html_element_in_html_document: bool) -> Self { pub fn new() -> Self {
ServoElementSnapshot { Self::default()
state: None,
attrs: None,
is_html_element_in_html_document: is_html_element_in_html_document,
class_changed: false,
id_changed: false,
other_attributes_changed: false,
}
} }
/// Returns whether the id attribute changed or not. /// Returns whether the id attribute changed or not.
@ -675,6 +661,17 @@ impl ServoElementSnapshot {
.map(|&(_, ref v)| v) .map(|&(_, ref v)| v)
} }
/// Executes the callback once for each attribute that changed.
#[inline]
pub fn each_attr_changed<F>(&self, mut callback: F)
where
F: FnMut(&LocalName),
{
for name in &self.changed_attrs {
callback(name)
}
}
fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
where where
F: FnMut(&AttrValue) -> bool, F: FnMut(&AttrValue) -> bool,

View file

@ -15,7 +15,6 @@ use std::cell::UnsafeCell;
use std::fmt; use std::fmt;
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
use std::mem; use std::mem;
use std::mem::ManuallyDrop;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use std::ptr; use std::ptr;
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{SharedMemoryBuilder, ToShmem};
@ -258,20 +257,20 @@ impl<T> Locked<T> {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
impl<T: ToShmem> ToShmem for Locked<T> { impl<T: ToShmem> ToShmem for Locked<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
let guard = self.shared_lock.read(); let guard = self.shared_lock.read();
ManuallyDrop::new(Locked { Ok(ManuallyDrop::new(Locked {
shared_lock: SharedRwLock::read_only(), shared_lock: SharedRwLock::read_only(),
data: UnsafeCell::new(ManuallyDrop::into_inner( data: UnsafeCell::new(ManuallyDrop::into_inner(
self.read_with(&guard).to_shmem(builder), self.read_with(&guard).to_shmem(builder)?,
)), )),
}) }))
} }
} }
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
impl<T: ToShmem> ToShmem for Locked<T> { impl<T: ToShmem> ToShmem for Locked<T> {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
panic!("ToShmem not supported in Servo currently") panic!("ToShmem not supported in Servo currently")
} }
} }

View file

@ -264,10 +264,6 @@ impl DocumentCondition {
return true; return true;
} }
if !pref!("layout.css.moz-document.url-prefix-hack.enabled") {
return false;
}
// Allow a single url-prefix() for compatibility. // Allow a single url-prefix() for compatibility.
// //
// See bug 1446470 and dependencies. // See bug 1446470 and dependencies.

View file

@ -15,9 +15,8 @@ use crate::stylesheets::{CssRule, Origin, StylesheetInDocument};
use crate::values::CssUrl; use crate::values::CssUrl;
use cssparser::SourceLocation; use cssparser::SourceLocation;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::mem::ManuallyDrop;
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
/// With asynchronous stylesheet parsing, we can't synchronously create a /// With asynchronous stylesheet parsing, we can't synchronously create a
/// GeckoStyleSheet. So we use this placeholder instead. /// GeckoStyleSheet. So we use this placeholder instead.
@ -184,8 +183,10 @@ pub struct ImportRule {
} }
impl ToShmem for ImportRule { impl ToShmem for ImportRule {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
panic!("ToShmem failed for ImportRule: cannot handle imported style sheets") Err(String::from(
"ToShmem failed for ImportRule: cannot handle imported style sheets",
))
} }
} }

View file

@ -40,7 +40,7 @@ use std::fmt;
use std::mem::{self, ManuallyDrop}; use std::mem::{self, ManuallyDrop};
use style_traits::ParsingMode; use style_traits::ParsingMode;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
pub use self::counter_style_rule::CounterStyleRule; pub use self::counter_style_rule::CounterStyleRule;
pub use self::document_rule::DocumentRule; pub use self::document_rule::DocumentRule;
@ -117,20 +117,25 @@ impl Drop for UrlExtraData {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
impl ToShmem for UrlExtraData { impl ToShmem for UrlExtraData {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
if self.0 & 1 == 0 { if self.0 & 1 == 0 {
let shared_extra_datas = unsafe { &structs::URLExtraData_sShared }; let shared_extra_datas = unsafe { &structs::URLExtraData_sShared };
let self_ptr = self.as_ref() as *const _ as *mut _; let self_ptr = self.as_ref() as *const _ as *mut _;
let sheet_id = shared_extra_datas let sheet_id = shared_extra_datas
.iter() .iter()
.position(|r| r.mRawPtr == self_ptr) .position(|r| r.mRawPtr == self_ptr);
.expect( let sheet_id = match sheet_id {
"ToShmem failed for UrlExtraData: expected sheet's URLExtraData to be in \ Some(id) => id,
URLExtraData::sShared", None => {
); return Err(String::from(
ManuallyDrop::new(UrlExtraData((sheet_id << 1) | 1)) "ToShmem failed for UrlExtraData: expected sheet's URLExtraData to be in \
URLExtraData::sShared",
));
},
};
Ok(ManuallyDrop::new(UrlExtraData((sheet_id << 1) | 1)))
} else { } else {
ManuallyDrop::new(UrlExtraData(self.0)) Ok(ManuallyDrop::new(UrlExtraData(self.0)))
} }
} }
} }

View file

@ -1318,12 +1318,6 @@ impl Stylist {
use crate::font_metrics::get_metrics_provider_for_product; use crate::font_metrics::get_metrics_provider_for_product;
let block = declarations.read_with(guards.author); let block = declarations.read_with(guards.author);
let iter_declarations = || {
block
.declaration_importance_iter()
.map(|(declaration, _)| (declaration, Origin::Author))
};
let metrics = get_metrics_provider_for_product(); let metrics = get_metrics_provider_for_product();
// We don't bother inserting these declarations in the rule tree, since // We don't bother inserting these declarations in the rule tree, since
@ -1332,12 +1326,14 @@ impl Stylist {
// TODO(emilio): Now that we fixed bug 1493420, we should consider // TODO(emilio): Now that we fixed bug 1493420, we should consider
// reversing this as it shouldn't be slow anymore, and should avoid // reversing this as it shouldn't be slow anymore, and should avoid
// generating two instantiations of apply_declarations. // generating two instantiations of apply_declarations.
properties::apply_declarations::<E, _, _>( properties::apply_declarations::<E, _>(
&self.device, &self.device,
/* pseudo = */ None, /* pseudo = */ None,
self.rule_tree.root(), self.rule_tree.root(),
guards, guards,
iter_declarations, block
.declaration_importance_iter()
.map(|(declaration, _)| (declaration, Origin::Author)),
Some(parent_style), Some(parent_style),
Some(parent_style), Some(parent_style),
Some(parent_style), Some(parent_style),

View file

@ -12,8 +12,7 @@ use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios};
/// ///
/// Unlike in computed values, each component value may exceed the /// Unlike in computed values, each component value may exceed the
/// range `[0.0, 1.0]`. /// range `[0.0, 1.0]`.
#[cfg_attr(feature = "servo", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)]
#[derive(Clone, Copy, Debug, PartialEq, ToAnimatedZero)]
pub struct RGBA { pub struct RGBA {
/// The red component. /// The red component.
pub red: f32, pub red: f32,

View file

@ -10,7 +10,8 @@ use crate::values::computed::{Context, ToComputedValue};
use crate::values::specified; use crate::values::specified;
pub use super::specified::{ pub use super::specified::{
AlignContent, AlignItems, ContentDistribution, JustifyContent, SelfAlignment, AlignContent, AlignItems, AlignTracks, ContentDistribution, JustifyContent, JustifyTracks,
SelfAlignment,
}; };
pub use super::specified::{AlignSelf, JustifySelf}; pub use super::specified::{AlignSelf, JustifySelf};
@ -35,7 +36,7 @@ pub use super::specified::{AlignSelf, JustifySelf};
/// sucks :(. /// sucks :(.
/// ///
/// See the discussion in https://bugzil.la/1384542. /// See the discussion in https://bugzil.la/1384542.
#[derive(Clone, Copy, Debug, Eq, PartialEq, ToCss, ToResolvedValue)] #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)]
#[repr(C)] #[repr(C)]
pub struct ComputedJustifyItems { pub struct ComputedJustifyItems {
/// The specified value for the property. Can contain the bare `legacy` /// The specified value for the property. Can contain the bare `legacy`

View file

@ -32,7 +32,7 @@ use std::mem::{self, ManuallyDrop};
use std::slice; use std::slice;
use style_traits::{CssWriter, ParseError, ToCss}; use style_traits::{CssWriter, ParseError, ToCss};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
pub use crate::values::computed::Length as MozScriptMinSize; pub use crate::values::computed::Length as MozScriptMinSize;
pub use crate::values::specified::font::{FontSynthesis, MozScriptSizeMultiplier}; pub use crate::values::specified::font::{FontSynthesis, MozScriptSizeMultiplier};
@ -466,19 +466,20 @@ pub enum FontFamilyList {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
impl ToShmem for FontFamilyList { impl ToShmem for FontFamilyList {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
// In practice, the only SharedFontList objects we create from shared // In practice, the only SharedFontList objects we create from shared
// style sheets are ones with a single generic entry. // style sheets are ones with a single generic entry.
ManuallyDrop::new(match *self { Ok(ManuallyDrop::new(match *self {
FontFamilyList::SharedFontList(ref r) => { FontFamilyList::SharedFontList(ref r) => {
assert!( if !(r.mNames.len() == 1 && r.mNames[0].mName.mRawPtr.is_null()) {
r.mNames.len() == 1 && r.mNames[0].mName.mRawPtr.is_null(), return Err(String::from(
"ToShmem failed for FontFamilyList: cannot handle non-generic families", "ToShmem failed for FontFamilyList: cannot handle non-generic families",
); ));
}
FontFamilyList::Generic(r.mNames[0].mGeneric) FontFamilyList::Generic(r.mNames[0].mGeneric)
}, },
FontFamilyList::Generic(t) => FontFamilyList::Generic(t), FontFamilyList::Generic(t) => FontFamilyList::Generic(t),
}) }))
} }
} }

View file

@ -21,7 +21,7 @@ use crate::media_queries::Device;
use crate::properties; use crate::properties;
use crate::properties::{ComputedValues, LonghandId, StyleBuilder}; use crate::properties::{ComputedValues, LonghandId, StyleBuilder};
use crate::rule_cache::RuleCacheConditions; use crate::rule_cache::RuleCacheConditions;
use crate::{ArcSlice, Atom}; use crate::{ArcSlice, Atom, One};
use euclid::default::Size2D; use euclid::default::Size2D;
use servo_arc::Arc; use servo_arc::Arc;
use std::cell::RefCell; use std::cell::RefCell;
@ -29,7 +29,10 @@ use std::cmp;
use std::f32; use std::f32;
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub use self::align::{AlignContent, AlignItems, JustifyContent, JustifyItems, SelfAlignment}; pub use self::align::{
AlignContent, AlignItems, AlignTracks, JustifyContent, JustifyItems, JustifyTracks,
SelfAlignment,
};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub use self::align::{AlignSelf, JustifySelf}; pub use self::align::{AlignSelf, JustifySelf};
pub use self::angle::Angle; pub use self::angle::Angle;
@ -68,7 +71,10 @@ pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle; pub use self::outline::OutlineStyle;
pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::percentage::{NonNegativePercentage, Percentage};
pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto, ZIndex}; pub use self::position::AspectRatio;
pub use self::position::{
GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, ZIndex,
};
pub use self::rect::NonNegativeLengthOrNumberRect; pub use self::rect::NonNegativeLengthOrNumberRect;
pub use self::resolution::Resolution; pub use self::resolution::Resolution;
pub use self::svg::MozContextProperties; pub use self::svg::MozContextProperties;
@ -223,8 +229,8 @@ impl<'a> Context<'a> {
pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength { pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength {
// We disable zoom for <svg:text> by unsetting the // We disable zoom for <svg:text> by unsetting the
// -x-text-zoom property, which leads to a false value // -x-text-zoom property, which leads to a false value
// in mAllowZoom // in mAllowZoomAndMinSize
if self.style().get_font().gecko.mAllowZoom { if self.style().get_font().gecko.mAllowZoomAndMinSize {
self.device().zoom_text(Au::from(size)).into() self.device().zoom_text(Au::from(size)).into()
} else { } else {
size size
@ -600,6 +606,18 @@ impl From<NonNegativeNumber> for CSSFloat {
} }
} }
impl One for NonNegativeNumber {
#[inline]
fn one() -> Self {
NonNegative(1.0)
}
#[inline]
fn is_one(&self) -> bool {
self.0 == 1.0
}
}
/// A wrapper of Number, but the value between 0 and 1 /// A wrapper of Number, but the value between 0 and 1
pub type ZeroToOneNumber = ZeroToOne<CSSFloat>; pub type ZeroToOneNumber = ZeroToOne<CSSFloat>;

View file

@ -7,13 +7,16 @@
//! //!
//! [position]: https://drafts.csswg.org/css-backgrounds-3/#position //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
use crate::values::computed::{Integer, LengthPercentage, Percentage}; use crate::values::computed::{Integer, LengthPercentage, NonNegativeNumber, Percentage};
use crate::values::generics::position::AspectRatio as GenericAspectRatio;
use crate::values::generics::position::Position as GenericPosition; use crate::values::generics::position::Position as GenericPosition;
use crate::values::generics::position::PositionComponent as GenericPositionComponent; use crate::values::generics::position::PositionComponent as GenericPositionComponent;
use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
use crate::values::generics::position::Ratio as GenericRatio;
use crate::values::generics::position::ZIndex as GenericZIndex; use crate::values::generics::position::ZIndex as GenericZIndex;
pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas}; pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow};
use crate::Zero; use crate::Zero;
use std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
@ -68,3 +71,25 @@ impl GenericPositionComponent for LengthPercentage {
/// A computed value for the `z-index` property. /// A computed value for the `z-index` property.
pub type ZIndex = GenericZIndex<Integer>; pub type ZIndex = GenericZIndex<Integer>;
/// A computed <ratio> value.
pub type Ratio = GenericRatio<NonNegativeNumber>;
impl PartialOrd for Ratio {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
f64::partial_cmp(
&((self.0).0 as f64 * (other.1).0 as f64),
&((self.1).0 as f64 * (other.0).0 as f64),
)
}
}
impl Ratio {
/// Returns a new Ratio.
pub fn new(a: f32, b: f32) -> Self {
GenericRatio(a.into(), b.into())
}
}
/// A computed value for the `aspect-ratio` property.
pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;

View file

@ -270,6 +270,7 @@ pub struct Ellipse<H, V, NonNegativeLengthPercentage> {
Copy, Copy,
Debug, Debug,
MallocSizeOf, MallocSizeOf,
Parse,
PartialEq, PartialEq,
SpecifiedValueInfo, SpecifiedValueInfo,
ToAnimatedValue, ToAnimatedValue,

View file

@ -337,7 +337,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
($slot:expr) => {{ ($slot:expr) => {{
let dummy = Self::MinMax(Default::default(), MinMaxOp::Max); let dummy = Self::MinMax(Default::default(), MinMaxOp::Max);
let result = mem::replace($slot, dummy); let result = mem::replace($slot, dummy);
let _ = mem::replace(self, result); *self = result;
}}; }};
} }
match *self { match *self {
@ -464,7 +464,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
replace_self_with!(&mut children[0]); replace_self_with!(&mut children[0]);
} else { } else {
// Else put our simplified children back. // Else put our simplified children back.
let _ = mem::replace(children_slot, children.into_boxed_slice().into()); *children_slot = children.into_boxed_slice().into();
} }
}, },
Self::Leaf(ref mut l) => { Self::Leaf(ref mut l) => {

View file

@ -5,13 +5,13 @@
//! Generic types for CSS values related to flexbox. //! Generic types for CSS values related to flexbox.
/// A generic value for the `flex-basis` property. /// A generic value for the `flex-basis` property.
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
#[derive( #[derive(
Animate, Animate,
Clone, Clone,
ComputeSquaredDistance, ComputeSquaredDistance,
Copy, Copy,
Debug, Debug,
MallocSizeOf,
Parse, Parse,
PartialEq, PartialEq,
SpecifiedValueInfo, SpecifiedValueInfo,

View file

@ -5,9 +5,9 @@
//! Generic types for font stuff. //! Generic types for font stuff.
use crate::parser::{Parse, ParserContext}; use crate::parser::{Parse, ParserContext};
use crate::One;
use byteorder::{BigEndian, ReadBytesExt}; use byteorder::{BigEndian, ReadBytesExt};
use cssparser::Parser; use cssparser::Parser;
use num_traits::One;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::io::Cursor; use std::io::Cursor;
use style_traits::{CssWriter, ParseError}; use style_traits::{CssWriter, ParseError};
@ -42,7 +42,7 @@ where
{ {
self.tag.to_css(dest)?; self.tag.to_css(dest)?;
// Don't serialize the default value. // Don't serialize the default value.
if self.value != Integer::one() { if !self.value.is_one() {
dest.write_char(' ')?; dest.write_char(' ')?;
self.value.to_css(dest)?; self.value.to_css(dest)?;
} }

View file

@ -714,7 +714,7 @@ impl Parse for LineNameList {
line_names.truncate(MAX_GRID_LINE as usize); line_names.truncate(MAX_GRID_LINE as usize);
} }
let (fill_start, fill_len) = fill_data.unwrap_or((usize::MAX, 0)); let (fill_start, fill_len) = fill_data.unwrap_or((0, 0));
Ok(LineNameList { Ok(LineNameList {
names: line_names.into(), names: line_names.into(),
@ -733,7 +733,7 @@ impl ToCss for LineNameList {
let fill_start = self.fill_start; let fill_start = self.fill_start;
let fill_len = self.fill_len; let fill_len = self.fill_len;
for (i, names) in self.names.iter().enumerate() { for (i, names) in self.names.iter().enumerate() {
if i == fill_start { if fill_len > 0 && i == fill_start {
dest.write_str(" repeat(auto-fill,")?; dest.write_str(" repeat(auto-fill,")?;
} }
@ -748,7 +748,7 @@ impl ToCss for LineNameList {
} }
dest.write_str("]")?; dest.write_str("]")?;
if i == fill_start + fill_len - 1 { if fill_len > 0 && i == fill_start + fill_len - 1 {
dest.write_str(")")?; dest.write_str(")")?;
} }
} }
@ -786,6 +786,9 @@ pub enum GenericGridTemplateComponent<L, I> {
/// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec.
#[animation(error)] #[animation(error)]
Subgrid(Box<LineNameList>), Subgrid(Box<LineNameList>),
/// `masonry` value.
/// https://github.com/w3c/csswg-drafts/issues/4650
Masonry,
} }
pub use self::GenericGridTemplateComponent as GridTemplateComponent; pub use self::GenericGridTemplateComponent as GridTemplateComponent;

View file

@ -174,13 +174,13 @@ impl<LengthPercentage> Size<LengthPercentage> {
/// A generic value for the `max-width` or `max-height` property. /// A generic value for the `max-width` or `max-height` property.
#[allow(missing_docs)] #[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
#[derive( #[derive(
Animate, Animate,
Clone, Clone,
ComputeSquaredDistance, ComputeSquaredDistance,
Copy, Copy,
Debug, Debug,
MallocSizeOf,
PartialEq, PartialEq,
SpecifiedValueInfo, SpecifiedValueInfo,
ToAnimatedValue, ToAnimatedValue,

View file

@ -5,6 +5,10 @@
//! Generic types for CSS handling of specified and computed values of //! Generic types for CSS handling of specified and computed values of
//! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position) //! [`position`](https://drafts.csswg.org/css-backgrounds-3/#position)
use crate::One;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
/// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position). /// A generic type for representing a CSS [position](https://drafts.csswg.org/css-values/#position).
#[derive( #[derive(
Animate, Animate,
@ -150,3 +154,104 @@ impl<Integer> ZIndex<Integer> {
} }
} }
} }
/// A generic value for the `<ratio>` value.
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToAnimatedZero,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub struct Ratio<N>(pub N, pub N);
impl<N> ToCss for Ratio<N>
where
N: ToCss + One + std::cmp::PartialEq,
{
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.0.to_css(dest)?;
// The second defaults to 1. So if it is one, we omit it in serialization.
if !self.1.is_one() {
dest.write_str(" / ")?;
self.1.to_css(dest)?;
}
Ok(())
}
}
/// Ratio or None.
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum PreferredRatio<N> {
/// Without specified ratio
#[css(skip)]
None,
/// With specified ratio
Ratio(#[css(field_bound)] Ratio<N>),
}
/// A generic value for the `aspect-ratio` property, the value is `auto || <ratio>`.
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C)]
pub struct GenericAspectRatio<N> {
/// Specifiy auto or not.
#[animation(constant)]
#[css(represents_keyword)]
pub auto: bool,
/// The preferred aspect-ratio value.
#[css(field_bound)]
pub ratio: PreferredRatio<N>,
}
pub use self::GenericAspectRatio as AspectRatio;
impl<N> AspectRatio<N> {
/// Returns `auto`
#[inline]
pub fn auto() -> Self {
AspectRatio {
auto: true,
ratio: PreferredRatio::None,
}
}
}

View file

@ -171,16 +171,6 @@ impl ContentDistribution {
Self { primary } Self { primary }
} }
fn from_bits(bits: u16) -> Self {
Self {
primary: AlignFlags::from_bits_truncate(bits as u8),
}
}
fn as_bits(&self) -> u16 {
self.primary.bits() as u16
}
/// Returns whether this value is a <baseline-position>. /// Returns whether this value is a <baseline-position>.
pub fn is_baseline_position(&self) -> bool { pub fn is_baseline_position(&self) -> bool {
matches!( matches!(
@ -292,6 +282,36 @@ impl SpecifiedValueInfo for AlignContent {
} }
} }
/// Value for the `align-tracks` property.
///
/// <https://github.com/w3c/csswg-drafts/issues/4650>
#[derive(
Clone,
Debug,
Default,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(transparent)]
#[css(comma)]
pub struct AlignTracks(#[css(iterable, if_empty = "normal")] pub crate::OwnedSlice<AlignContent>);
impl Parse for AlignTracks {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let values = input.parse_comma_separated(|input| AlignContent::parse(context, input))?;
Ok(AlignTracks(values.into()))
}
}
/// Value for the `justify-content` property. /// Value for the `justify-content` property.
/// ///
/// <https://drafts.csswg.org/css-align/#propdef-justify-content> /// <https://drafts.csswg.org/css-align/#propdef-justify-content>
@ -329,18 +349,35 @@ impl SpecifiedValueInfo for JustifyContent {
ContentDistribution::list_keywords(f, AxisDirection::Inline); ContentDistribution::list_keywords(f, AxisDirection::Inline);
} }
} }
/// Value for the `justify-tracks` property.
///
/// <https://github.com/w3c/csswg-drafts/issues/4650>
#[derive(
Clone,
Debug,
Default,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(transparent)]
#[css(comma)]
pub struct JustifyTracks(
#[css(iterable, if_empty = "normal")] pub crate::OwnedSlice<JustifyContent>,
);
#[cfg(feature = "gecko")] impl Parse for JustifyTracks {
impl From<u16> for JustifyContent { fn parse<'i, 't>(
fn from(bits: u16) -> Self { context: &ParserContext,
JustifyContent(ContentDistribution::from_bits(bits)) input: &mut Parser<'i, 't>,
} ) -> Result<Self, ParseError<'i>> {
} let values = input.parse_comma_separated(|input| JustifyContent::parse(context, input))?;
Ok(JustifyTracks(values.into()))
#[cfg(feature = "gecko")]
impl From<JustifyContent> for u16 {
fn from(v: JustifyContent) -> u16 {
v.0.as_bits()
} }
} }

View file

@ -263,22 +263,6 @@ impl Ellipse {
} }
} }
impl Parse for ShapeRadius {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if let Ok(lp) = input.try(|i| NonNegativeLengthPercentage::parse(context, i)) {
return Ok(generic::ShapeRadius::Length(lp));
}
try_match_ident_ignore_ascii_case! { input,
"closest-side" => Ok(generic::ShapeRadius::ClosestSide),
"farthest-side" => Ok(generic::ShapeRadius::FarthestSide),
}
}
}
impl Parse for Polygon { impl Parse for Polygon {
fn parse<'i, 't>( fn parse<'i, 't>(
context: &ParserContext, context: &ParserContext,

View file

@ -295,6 +295,18 @@ fn allow_grid_template_subgrids() -> bool {
false false
} }
#[cfg(feature = "gecko")]
#[inline]
fn allow_grid_template_masonry() -> bool {
static_prefs::pref!("layout.css.grid-template-masonry-value.enabled")
}
#[cfg(feature = "servo")]
#[inline]
fn allow_grid_template_masonry() -> bool {
false
}
impl Parse for GridTemplateComponent<LengthPercentage, Integer> { impl Parse for GridTemplateComponent<LengthPercentage, Integer> {
fn parse<'i, 't>( fn parse<'i, 't>(
context: &ParserContext, context: &ParserContext,
@ -319,7 +331,11 @@ impl GridTemplateComponent<LengthPercentage, Integer> {
return Ok(GridTemplateComponent::Subgrid(Box::new(t))); return Ok(GridTemplateComponent::Subgrid(Box::new(t)));
} }
} }
if allow_grid_template_masonry() {
if input.try(|i| i.expect_ident_matching("masonry")).is_ok() {
return Ok(GridTemplateComponent::Masonry);
}
}
let track_list = TrackList::parse(context, input)?; let track_list = TrackList::parse(context, input)?;
Ok(GridTemplateComponent::TrackList(Box::new(track_list))) Ok(GridTemplateComponent::TrackList(Box::new(track_list)))
} }

View file

@ -18,9 +18,8 @@ use crate::context::QuirksMode;
use crate::parser::{Parse, ParserContext}; use crate::parser::{Parse, ParserContext};
use crate::values::serialize_atom_identifier; use crate::values::serialize_atom_identifier;
use crate::values::specified::calc::CalcNode; use crate::values::specified::calc::CalcNode;
use crate::{Atom, Namespace, Prefix, Zero}; use crate::{Atom, Namespace, One, Prefix, Zero};
use cssparser::{Parser, Token}; use cssparser::{Parser, Token};
use num_traits::One;
use std::f32; use std::f32;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use std::ops::Add; use std::ops::Add;
@ -28,9 +27,9 @@ use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub use self::align::{AlignContent, AlignItems, AlignSelf, ContentDistribution}; pub use self::align::{AlignContent, AlignItems, AlignSelf, AlignTracks, ContentDistribution};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub use self::align::{JustifyContent, JustifyItems, JustifySelf, SelfAlignment}; pub use self::align::{JustifyContent, JustifyItems, JustifySelf, JustifyTracks, SelfAlignment};
pub use self::angle::{AllowUnitlessZeroAngle, Angle}; pub use self::angle::{AllowUnitlessZeroAngle, Angle};
pub use self::background::{BackgroundRepeat, BackgroundSize}; pub use self::background::{BackgroundRepeat, BackgroundSize};
pub use self::basic_shape::FillRule; pub use self::basic_shape::FillRule;
@ -72,7 +71,10 @@ pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle; pub use self::outline::OutlineStyle;
pub use self::percentage::Percentage; pub use self::percentage::Percentage;
pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto}; pub use self::position::AspectRatio;
pub use self::position::{
GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto,
};
pub use self::position::{PositionComponent, ZIndex}; pub use self::position::{PositionComponent, ZIndex};
pub use self::rect::NonNegativeLengthOrNumberRect; pub use self::rect::NonNegativeLengthOrNumberRect;
pub use self::resolution::Resolution; pub use self::resolution::Resolution;
@ -375,11 +377,29 @@ impl Parse for NonNegativeNumber {
} }
} }
impl One for NonNegativeNumber {
#[inline]
fn one() -> Self {
NonNegativeNumber::new(1.0)
}
#[inline]
fn is_one(&self) -> bool {
self.get() == 1.0
}
}
impl NonNegativeNumber { impl NonNegativeNumber {
/// Returns a new non-negative number with the value `val`. /// Returns a new non-negative number with the value `val`.
pub fn new(val: CSSFloat) -> Self { pub fn new(val: CSSFloat) -> Self {
NonNegative::<Number>(Number::new(val.max(0.))) NonNegative::<Number>(Number::new(val.max(0.)))
} }
/// Returns the numeric value.
#[inline]
pub fn get(&self) -> f32 {
self.0.get()
}
} }
/// A Number which is >= 1.0. /// A Number which is >= 1.0.
@ -542,14 +562,10 @@ impl One for Integer {
fn one() -> Self { fn one() -> Self {
Self::new(1) Self::new(1)
} }
}
// This is not great, because it loses calc-ness, but it's necessary for One. #[inline]
impl ::std::ops::Mul<Integer> for Integer { fn is_one(&self) -> bool {
type Output = Self; self.value() == 1
fn mul(self, other: Self) -> Self {
Self::new(self.value * other.value)
} }
} }

View file

@ -12,13 +12,14 @@ use crate::selector_map::PrecomputedHashMap;
use crate::str::HTML_SPACE_CHARACTERS; use crate::str::HTML_SPACE_CHARACTERS;
use crate::values::computed::LengthPercentage as ComputedLengthPercentage; use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
use crate::values::computed::{Context, Percentage, ToComputedValue}; use crate::values::computed::{Context, Percentage, ToComputedValue};
use crate::values::generics::position::AspectRatio as GenericAspectRatio;
use crate::values::generics::position::Position as GenericPosition; use crate::values::generics::position::Position as GenericPosition;
use crate::values::generics::position::PositionComponent as GenericPositionComponent; use crate::values::generics::position::PositionComponent as GenericPositionComponent;
use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
use crate::values::generics::position::Ratio as GenericRatio;
use crate::values::generics::position::ZIndex as GenericZIndex; use crate::values::generics::position::ZIndex as GenericZIndex;
use crate::values::specified::{AllowQuirks, Integer, LengthPercentage}; use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
use crate::Atom; use crate::{Atom, One, Zero};
use crate::Zero;
use cssparser::Parser; use cssparser::Parser;
use selectors::parser::SelectorParseErrorKind; use selectors::parser::SelectorParseErrorKind;
use servo_arc::Arc; use servo_arc::Arc;
@ -383,6 +384,171 @@ bitflags! {
} }
} }
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
/// Masonry auto-placement algorithm packing.
pub enum MasonryPlacement {
/// Place the item in the track(s) with the smallest extent so far.
Pack,
/// Place the item after the last item, from start to end.
Next,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
/// Masonry auto-placement algorithm item sorting option.
pub enum MasonryItemOrder {
/// Place all items with a definite placement before auto-placed items.
DefiniteFirst,
/// Place items in `order-modified document order`.
Ordered,
}
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
/// Controls how the Masonry layout algorithm works
/// specifying exactly how auto-placed items get flowed in the masonry axis.
pub struct MasonryAutoFlow {
/// Specify how to pick a auto-placement track.
#[css(contextual_skip_if = "is_pack_with_non_default_order")]
pub placement: MasonryPlacement,
/// Specify how to pick an item to place.
#[css(skip_if = "is_item_order_definite_first")]
pub order: MasonryItemOrder,
}
#[inline]
fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
*placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
}
#[inline]
fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
*order == MasonryItemOrder::DefiniteFirst
}
impl MasonryAutoFlow {
#[inline]
/// Get initial `masonry-auto-flow` value.
pub fn initial() -> MasonryAutoFlow {
MasonryAutoFlow {
placement: MasonryPlacement::Pack,
order: MasonryItemOrder::DefiniteFirst,
}
}
}
impl Parse for MasonryAutoFlow {
/// [ definite-first | ordered ] || [ pack | next ]
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<MasonryAutoFlow, ParseError<'i>> {
let mut value = MasonryAutoFlow::initial();
let mut got_placement = false;
let mut got_order = false;
while !input.is_exhausted() {
let location = input.current_source_location();
let ident = input.expect_ident()?;
let success = match_ignore_ascii_case! { &ident,
"pack" if !got_placement => {
got_placement = true;
true
},
"next" if !got_placement => {
value.placement = MasonryPlacement::Next;
got_placement = true;
true
},
"definite-first" if !got_order => {
got_order = true;
true
},
"ordered" if !got_order => {
value.order = MasonryItemOrder::Ordered;
got_order = true;
true
},
_ => false
};
if !success {
return Err(location
.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
}
}
if got_placement || got_order {
Ok(value)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
#[cfg(feature = "gecko")]
impl From<u8> for MasonryAutoFlow {
fn from(bits: u8) -> MasonryAutoFlow {
use crate::gecko_bindings::structs;
let mut value = MasonryAutoFlow::initial();
if bits & structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8 == 0 {
value.placement = MasonryPlacement::Next;
}
if bits & structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8 == 0 {
value.order = MasonryItemOrder::Ordered;
}
value
}
}
#[cfg(feature = "gecko")]
impl From<MasonryAutoFlow> for u8 {
fn from(v: MasonryAutoFlow) -> u8 {
use crate::gecko_bindings::structs;
let mut result: u8 = 0;
if v.placement == MasonryPlacement::Pack {
result |= structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8;
}
if v.order == MasonryItemOrder::DefiniteFirst {
result |= structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8;
}
result
}
}
impl Parse for GridAutoFlow { impl Parse for GridAutoFlow {
/// [ row | column ] || dense /// [ row | column ] || dense
fn parse<'i, 't>( fn parse<'i, 't>(
@ -711,3 +877,74 @@ impl GridTemplateAreas {
/// A specified value for the `z-index` property. /// A specified value for the `z-index` property.
pub type ZIndex = GenericZIndex<Integer>; pub type ZIndex = GenericZIndex<Integer>;
/// A specified value for the `aspect-ratio` property.
pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
impl Parse for AspectRatio {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
use crate::values::generics::position::PreferredRatio;
let location = input.current_source_location();
let mut auto = input.try(|i| i.expect_ident_matching("auto"));
let ratio = input.try(|i| Ratio::parse(context, i));
if auto.is_err() {
auto = input.try(|i| i.expect_ident_matching("auto"));
}
if auto.is_err() && ratio.is_err() {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
Ok(AspectRatio {
auto: auto.is_ok(),
ratio: match ratio {
Ok(ratio) => PreferredRatio::Ratio(ratio),
Err(..) => PreferredRatio::None,
},
})
}
}
impl AspectRatio {
/// Returns Self by a valid ratio.
pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
use crate::values::generics::position::PreferredRatio;
AspectRatio {
auto: true,
ratio: PreferredRatio::Ratio(GenericRatio(
NonNegativeNumber::new(w),
NonNegativeNumber::new(h),
)),
}
}
}
/// A specified <ratio> value.
pub type Ratio = GenericRatio<NonNegativeNumber>;
// https://drafts.csswg.org/css-values-4/#ratios
impl Parse for Ratio {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let a = NonNegativeNumber::parse(context, input)?;
let b = match input.try_parse(|input| input.expect_delim('/')) {
Ok(()) => NonNegativeNumber::parse(context, input)?,
_ => One::one(),
};
// The computed value of a <ratio> is the pair of numbers provided, unless
// both numbers are zero, in which case the computed value is the pair (1, 0)
// (same as 1 / 0).
// https://drafts.csswg.org/css-values-4/#ratios
if a.is_zero() && b.is_zero() {
return Ok(GenericRatio(One::one(), Zero::zero()));
}
return Ok(GenericRatio(a, b));
}
}

View file

@ -128,6 +128,7 @@ impl ToComputedValue for LineHeight {
Eq, Eq,
MallocSizeOf, MallocSizeOf,
PartialEq, PartialEq,
Parse,
SpecifiedValueInfo, SpecifiedValueInfo,
ToComputedValue, ToComputedValue,
ToCss, ToCss,
@ -144,30 +145,6 @@ pub enum TextOverflowSide {
String(crate::OwnedStr), String(crate::OwnedStr),
} }
impl Parse for TextOverflowSide {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<TextOverflowSide, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Ident(ref ident) => {
match_ignore_ascii_case! { ident,
"clip" => Ok(TextOverflowSide::Clip),
"ellipsis" => Ok(TextOverflowSide::Ellipsis),
_ => Err(location.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent(ident.clone())
))
}
},
Token::QuotedString(ref v) => {
Ok(TextOverflowSide::String(v.as_ref().to_owned().into()))
},
ref t => Err(location.new_unexpected_token_error(t.clone())),
}
}
}
#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
/// text-overflow. Specifies rendering when inline content overflows its line box edge. /// text-overflow. Specifies rendering when inline content overflows its line box edge.
pub struct TextOverflow { pub struct TextOverflow {

View file

@ -15,7 +15,14 @@ pub struct ParseVariantAttrs {
pub condition: Option<Path>, pub condition: Option<Path>,
} }
#[darling(attributes(parse), default)]
#[derive(Default, FromField)]
pub struct ParseFieldAttrs {
field_bound: bool,
}
fn parse_non_keyword_variant( fn parse_non_keyword_variant(
where_clause: &mut Option<syn::WhereClause>,
name: &syn::Ident, name: &syn::Ident,
variant: &VariantInfo, variant: &VariantInfo,
variant_attrs: &CssVariantAttrs, variant_attrs: &CssVariantAttrs,
@ -32,7 +39,14 @@ fn parse_non_keyword_variant(
"We only support deriving parse for simple variants" "We only support deriving parse for simple variants"
); );
let variant_name = &variant.ast().ident; let variant_name = &variant.ast().ident;
let ty = &bindings[0].ast().ty; let binding_ast = &bindings[0].ast();
let ty = &binding_ast.ty;
let field_attrs = cg::parse_field_attrs::<ParseFieldAttrs>(binding_ast);
if field_attrs.field_bound {
cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse));
}
let mut parse = if skip_try { let mut parse = if skip_try {
quote! { quote! {
let v = <#ty as crate::parser::Parse>::parse(context, input)?; let v = <#ty as crate::parser::Parse>::parse(context, input)?;
@ -67,15 +81,12 @@ fn parse_non_keyword_variant(
} }
pub fn derive(mut input: DeriveInput) -> TokenStream { pub fn derive(mut input: DeriveInput) -> TokenStream {
{ let mut where_clause = input.generics.where_clause.take();
let mut where_clause = input.generics.where_clause.take(); for param in input.generics.type_params() {
for param in input.generics.type_params() { cg::add_predicate(
cg::add_predicate( &mut where_clause,
&mut where_clause, parse_quote!(#param: crate::parser::Parse),
parse_quote!(#param: crate::parser::Parse), );
);
}
input.generics.where_clause = where_clause;
} }
let name = &input.ident; let name = &input.ident;
@ -88,12 +99,13 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
let mut effective_variants = 0; let mut effective_variants = 0;
for variant in s.variants().iter() { for variant in s.variants().iter() {
let css_variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&variant.ast()); let css_variant_attrs = cg::parse_variant_attrs_from_ast::<CssVariantAttrs>(&variant.ast());
let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast());
if css_variant_attrs.skip { if css_variant_attrs.skip {
continue; continue;
} }
effective_variants += 1; effective_variants += 1;
let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast());
saw_condition |= parse_attrs.condition.is_some(); saw_condition |= parse_attrs.condition.is_some();
if !variant.bindings().is_empty() { if !variant.bindings().is_empty() {
@ -142,8 +154,14 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
let mut parse_non_keywords = quote! {}; let mut parse_non_keywords = quote! {};
for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() { for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() {
let skip_try = !has_keywords && i == non_keywords.len() - 1; let skip_try = !has_keywords && i == non_keywords.len() - 1;
let parse_variant = let parse_variant = parse_non_keyword_variant(
parse_non_keyword_variant(name, variant, css_attrs, parse_attrs, skip_try); &mut where_clause,
name,
variant,
css_attrs,
parse_attrs,
skip_try,
);
parse_non_keywords.extend(parse_variant); parse_non_keywords.extend(parse_variant);
} }
@ -171,6 +189,9 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
quote! { Self::parse(input) } quote! { Self::parse(input) }
}; };
let has_non_keywords = !non_keywords.is_empty();
input.generics.where_clause = where_clause;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let parse_trait_impl = quote! { let parse_trait_impl = quote! {
@ -189,7 +210,7 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
return parse_trait_impl; return parse_trait_impl;
} }
assert!(non_keywords.is_empty()); assert!(!has_non_keywords);
// TODO(emilio): It'd be nice to get rid of these, but that makes the // TODO(emilio): It'd be nice to get rid of these, but that makes the
// conversion harder... // conversion harder...

View file

@ -13,8 +13,8 @@
/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout /// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for /// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
/// locality reasons. Using `OpaqueNode` enforces this invariant. /// locality reasons. Using `OpaqueNode` enforces this invariant.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf, Deserialize, Serialize))] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
pub struct OpaqueNode(pub usize); pub struct OpaqueNode(pub usize);
impl OpaqueNode { impl OpaqueNode {

View file

@ -13,7 +13,7 @@ use std::marker::PhantomData;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::ptr::NonNull; use std::ptr::NonNull;
use std::{fmt, iter, mem, slice}; use std::{fmt, iter, mem, slice};
use to_shmem::{SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
/// A struct that basically replaces a `Box<[T]>`, but which cbindgen can /// A struct that basically replaces a `Box<[T]>`, but which cbindgen can
/// understand. /// understand.
@ -159,10 +159,10 @@ impl<T: MallocSizeOf + Sized> MallocSizeOf for OwnedSlice<T> {
} }
impl<T: ToShmem + Sized> ToShmem for OwnedSlice<T> { impl<T: ToShmem + Sized> ToShmem for OwnedSlice<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> mem::ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
unsafe { unsafe {
let dest = to_shmem::to_shmem_slice(self.iter(), builder); let dest = to_shmem::to_shmem_slice(self.iter(), builder)?;
mem::ManuallyDrop::new(Self::from(Box::from_raw(dest))) Ok(mem::ManuallyDrop::new(Self::from(Box::from_raw(dest))))
} }
} }
} }

View file

@ -42,6 +42,11 @@ use std::slice;
use std::str; use std::str;
use thin_slice::ThinBoxedSlice; use thin_slice::ThinBoxedSlice;
/// Result type for ToShmem::to_shmem.
///
/// The String is an error message describing why the call failed.
pub type Result<T> = std::result::Result<ManuallyDrop<T>, String>;
// Various pointer arithmetic functions in this file can be replaced with // Various pointer arithmetic functions in this file can be replaced with
// functions on `Layout` once they have stabilized: // functions on `Layout` once they have stabilized:
// //
@ -114,13 +119,13 @@ impl SharedMemoryBuilder {
/// a shared memory buffer by calling ToShmem::to_shmem on it. /// a shared memory buffer by calling ToShmem::to_shmem on it.
/// ///
/// Panics if there is insufficient space in the buffer. /// Panics if there is insufficient space in the buffer.
pub fn write<T: ToShmem>(&mut self, value: &T) -> *mut T { pub fn write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String> {
// Reserve space for the value. // Reserve space for the value.
let dest: *mut T = self.alloc_value(); let dest: *mut T = self.alloc_value();
// Make a clone of the value with all of its heap allocations // Make a clone of the value with all of its heap allocations
// placed in the shared memory buffer. // placed in the shared memory buffer.
let value = value.to_shmem(self); let value = value.to_shmem(self)?;
unsafe { unsafe {
// Copy the value into the buffer. // Copy the value into the buffer.
@ -128,7 +133,7 @@ impl SharedMemoryBuilder {
} }
// Return a pointer to the shared value. // Return a pointer to the shared value.
dest Ok(dest)
} }
/// Reserves space in the shared memory buffer to fit a value of type T, /// Reserves space in the shared memory buffer to fit a value of type T,
@ -190,7 +195,10 @@ pub trait ToShmem: Sized {
/// ///
/// The return type is wrapped in ManuallyDrop to make it harder to /// The return type is wrapped in ManuallyDrop to make it harder to
/// accidentally invoke the destructor of the value that is produced. /// accidentally invoke the destructor of the value that is produced.
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self>; ///
/// Returns a Result so that we can gracefully recover from unexpected
/// content.
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self>;
} }
#[macro_export] #[macro_export]
@ -201,8 +209,8 @@ macro_rules! impl_trivial_to_shmem {
fn to_shmem( fn to_shmem(
&self, &self,
_builder: &mut $crate::SharedMemoryBuilder, _builder: &mut $crate::SharedMemoryBuilder,
) -> ::std::mem::ManuallyDrop<Self> { ) -> $crate::Result<Self> {
::std::mem::ManuallyDrop::new(*self) $crate::Result::Ok(::std::mem::ManuallyDrop::new(*self))
} }
} }
)* )*
@ -231,58 +239,60 @@ impl_trivial_to_shmem!(cssparser::SourceLocation);
impl_trivial_to_shmem!(cssparser::TokenSerializationType); impl_trivial_to_shmem!(cssparser::TokenSerializationType);
impl<T> ToShmem for PhantomData<T> { impl<T> ToShmem for PhantomData<T> {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
ManuallyDrop::new(*self) Ok(ManuallyDrop::new(*self))
} }
} }
impl<T: ToShmem> ToShmem for Range<T> { impl<T: ToShmem> ToShmem for Range<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
ManuallyDrop::new(Range { Ok(ManuallyDrop::new(Range {
start: ManuallyDrop::into_inner(self.start.to_shmem(builder)), start: ManuallyDrop::into_inner(self.start.to_shmem(builder)?),
end: ManuallyDrop::into_inner(self.end.to_shmem(builder)), end: ManuallyDrop::into_inner(self.end.to_shmem(builder)?),
}) }))
} }
} }
impl ToShmem for cssparser::UnicodeRange { impl ToShmem for cssparser::UnicodeRange {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
ManuallyDrop::new(cssparser::UnicodeRange { Ok(ManuallyDrop::new(cssparser::UnicodeRange {
start: self.start, start: self.start,
end: self.end, end: self.end,
}) }))
} }
} }
impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) { impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
ManuallyDrop::new(( Ok(ManuallyDrop::new((
ManuallyDrop::into_inner(self.0.to_shmem(builder)), ManuallyDrop::into_inner(self.0.to_shmem(builder)?),
ManuallyDrop::into_inner(self.1.to_shmem(builder)), ManuallyDrop::into_inner(self.1.to_shmem(builder)?),
)) )))
} }
} }
impl<T: ToShmem> ToShmem for Wrapping<T> { impl<T: ToShmem> ToShmem for Wrapping<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(self.0.to_shmem(builder)))) Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(
self.0.to_shmem(builder)?,
))))
} }
} }
impl<T: ToShmem> ToShmem for Box<T> { impl<T: ToShmem> ToShmem for Box<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// Reserve space for the boxed value. // Reserve space for the boxed value.
let dest: *mut T = builder.alloc_value(); let dest: *mut T = builder.alloc_value();
// Make a clone of the boxed value with all of its heap allocations // Make a clone of the boxed value with all of its heap allocations
// placed in the shared memory buffer. // placed in the shared memory buffer.
let value = (**self).to_shmem(builder); let value = (**self).to_shmem(builder)?;
unsafe { unsafe {
// Copy the value into the buffer. // Copy the value into the buffer.
ptr::write(dest, ManuallyDrop::into_inner(value)); ptr::write(dest, ManuallyDrop::into_inner(value));
ManuallyDrop::new(Box::from_raw(dest)) Ok(ManuallyDrop::new(Box::from_raw(dest)))
} }
} }
} }
@ -293,7 +303,7 @@ unsafe fn to_shmem_slice_ptr<'a, T, I>(
src: I, src: I,
dest: *mut T, dest: *mut T,
builder: &mut SharedMemoryBuilder, builder: &mut SharedMemoryBuilder,
) -> *mut [T] ) -> std::result::Result<*mut [T], String>
where where
T: 'a + ToShmem, T: 'a + ToShmem,
I: ExactSizeIterator<Item = &'a T>, I: ExactSizeIterator<Item = &'a T>,
@ -303,15 +313,18 @@ where
// Make a clone of each element from the iterator with its own heap // Make a clone of each element from the iterator with its own heap
// allocations placed in the buffer, and copy that clone into the buffer. // allocations placed in the buffer, and copy that clone into the buffer.
for (src, dest) in src.zip(dest.iter_mut()) { for (src, dest) in src.zip(dest.iter_mut()) {
ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder))); ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder)?));
} }
dest Ok(dest)
} }
/// Writes all the items in `src` into a slice in the shared memory buffer and /// Writes all the items in `src` into a slice in the shared memory buffer and
/// returns a pointer to the slice. /// returns a pointer to the slice.
pub unsafe fn to_shmem_slice<'a, T, I>(src: I, builder: &mut SharedMemoryBuilder) -> *mut [T] pub unsafe fn to_shmem_slice<'a, T, I>(
src: I,
builder: &mut SharedMemoryBuilder,
) -> std::result::Result<*mut [T], String>
where where
T: 'a + ToShmem, T: 'a + ToShmem,
I: ExactSizeIterator<Item = &'a T>, I: ExactSizeIterator<Item = &'a T>,
@ -321,16 +334,16 @@ where
} }
impl<T: ToShmem> ToShmem for Box<[T]> { impl<T: ToShmem> ToShmem for Box<[T]> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
unsafe { unsafe {
let dest = to_shmem_slice(self.iter(), builder); let dest = to_shmem_slice(self.iter(), builder)?;
ManuallyDrop::new(Box::from_raw(dest)) Ok(ManuallyDrop::new(Box::from_raw(dest)))
} }
} }
} }
impl<T: ToShmem> ToShmem for ThinBoxedSlice<T> { impl<T: ToShmem> ToShmem for ThinBoxedSlice<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// We could support this if we needed but in practice we will never // We could support this if we needed but in practice we will never
// need to handle such big ThinBoxedSlices. // need to handle such big ThinBoxedSlices.
assert!( assert!(
@ -340,14 +353,14 @@ impl<T: ToShmem> ToShmem for ThinBoxedSlice<T> {
); );
unsafe { unsafe {
let dest = to_shmem_slice(self.iter(), builder); let dest = to_shmem_slice(self.iter(), builder)?;
ManuallyDrop::new(ThinBoxedSlice::from_raw(dest)) Ok(ManuallyDrop::new(ThinBoxedSlice::from_raw(dest)))
} }
} }
} }
impl ToShmem for Box<str> { impl ToShmem for Box<str> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// Reserve space for the string bytes. // Reserve space for the string bytes.
let dest: *mut u8 = builder.alloc_array(self.len()); let dest: *mut u8 = builder.alloc_array(self.len());
@ -355,15 +368,15 @@ impl ToShmem for Box<str> {
// Copy the value into the buffer. // Copy the value into the buffer.
ptr::copy(self.as_ptr(), dest, self.len()); ptr::copy(self.as_ptr(), dest, self.len());
ManuallyDrop::new(Box::from_raw(str::from_utf8_unchecked_mut( Ok(ManuallyDrop::new(Box::from_raw(
slice::from_raw_parts_mut(dest, self.len()), str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(dest, self.len())),
))) )))
} }
} }
} }
impl ToShmem for String { impl ToShmem for String {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// Reserve space for the string bytes. // Reserve space for the string bytes.
let dest: *mut u8 = builder.alloc_array(self.len()); let dest: *mut u8 = builder.alloc_array(self.len());
@ -371,13 +384,17 @@ impl ToShmem for String {
// Copy the value into the buffer. // Copy the value into the buffer.
ptr::copy(self.as_ptr(), dest, self.len()); ptr::copy(self.as_ptr(), dest, self.len());
ManuallyDrop::new(String::from_raw_parts(dest, self.len(), self.len())) Ok(ManuallyDrop::new(String::from_raw_parts(
dest,
self.len(),
self.len(),
)))
} }
} }
} }
impl ToShmem for CString { impl ToShmem for CString {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
let len = self.as_bytes_with_nul().len(); let len = self.as_bytes_with_nul().len();
// Reserve space for the string bytes. // Reserve space for the string bytes.
@ -387,53 +404,55 @@ impl ToShmem for CString {
// Copy the value into the buffer. // Copy the value into the buffer.
ptr::copy(self.as_ptr(), dest, len); ptr::copy(self.as_ptr(), dest, len);
ManuallyDrop::new(CString::from_raw(dest)) Ok(ManuallyDrop::new(CString::from_raw(dest)))
} }
} }
} }
impl<T: ToShmem> ToShmem for Vec<T> { impl<T: ToShmem> ToShmem for Vec<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
unsafe { unsafe {
let dest = to_shmem_slice(self.iter(), builder) as *mut T; let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
let dest_vec = Vec::from_raw_parts(dest, self.len(), self.len()); let dest_vec = Vec::from_raw_parts(dest, self.len(), self.len());
ManuallyDrop::new(dest_vec) Ok(ManuallyDrop::new(dest_vec))
} }
} }
} }
impl<T: ToShmem, A: Array<Item = T>> ToShmem for SmallVec<A> { impl<T: ToShmem, A: Array<Item = T>> ToShmem for SmallVec<A> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
let dest_vec = unsafe { let dest_vec = unsafe {
if self.spilled() { if self.spilled() {
// Place the items in a separate allocation in the shared memory // Place the items in a separate allocation in the shared memory
// buffer. // buffer.
let dest = to_shmem_slice(self.iter(), builder) as *mut T; let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
SmallVec::from_raw_parts(dest, self.len(), self.len()) SmallVec::from_raw_parts(dest, self.len(), self.len())
} else { } else {
// Place the items inline. // Place the items inline.
let mut s = SmallVec::new(); let mut s = SmallVec::new();
to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder); to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder)?;
s.set_len(self.len()); s.set_len(self.len());
s s
} }
}; };
ManuallyDrop::new(dest_vec) Ok(ManuallyDrop::new(dest_vec))
} }
} }
impl<T: ToShmem> ToShmem for Option<T> { impl<T: ToShmem> ToShmem for Option<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
ManuallyDrop::new( let v = match self {
self.as_ref() Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)),
.map(|v| ManuallyDrop::into_inner(v.to_shmem(builder))), None => None,
) };
Ok(ManuallyDrop::new(v))
} }
} }
impl<T: 'static + ToShmem> ToShmem for Arc<T> { impl<T: 'static + ToShmem> ToShmem for Arc<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// Assert that we don't encounter any shared references to values we // Assert that we don't encounter any shared references to values we
// don't expect. Those we expect are those noted by calling // don't expect. Those we expect are those noted by calling
// add_allowed_duplication_type, and should be types where we're fine // add_allowed_duplication_type, and should be types where we're fine
@ -453,7 +472,7 @@ impl<T: 'static + ToShmem> ToShmem for Arc<T> {
// Make a clone of the Arc-owned value with all of its heap allocations // Make a clone of the Arc-owned value with all of its heap allocations
// placed in the shared memory buffer. // placed in the shared memory buffer.
let value = (**self).to_shmem(builder); let value = (**self).to_shmem(builder)?;
// Create a new Arc with the shared value and have it place its // Create a new Arc with the shared value and have it place its
// ArcInner in the shared memory buffer. // ArcInner in the shared memory buffer.
@ -466,13 +485,13 @@ impl<T: 'static + ToShmem> ToShmem for Arc<T> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
builder.shared_values.insert(self.heap_ptr()); builder.shared_values.insert(self.heap_ptr());
ManuallyDrop::new(static_arc) Ok(ManuallyDrop::new(static_arc))
} }
} }
} }
impl<H: 'static + ToShmem, T: 'static + ToShmem> ToShmem for ThinArc<H, T> { impl<H: 'static + ToShmem, T: 'static + ToShmem> ToShmem for ThinArc<H, T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// We don't currently have any shared ThinArc values in stylesheets, // We don't currently have any shared ThinArc values in stylesheets,
// so don't support them for now. // so don't support them for now.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -484,8 +503,11 @@ impl<H: 'static + ToShmem, T: 'static + ToShmem> ToShmem for ThinArc<H, T> {
// Make a clone of the Arc-owned header and slice values with all of // Make a clone of the Arc-owned header and slice values with all of
// their heap allocations placed in the shared memory buffer. // their heap allocations placed in the shared memory buffer.
let header = self.header.header.to_shmem(builder); let header = self.header.header.to_shmem(builder)?;
let values: Vec<ManuallyDrop<T>> = self.slice.iter().map(|v| v.to_shmem(builder)).collect(); let mut values = Vec::with_capacity(self.slice.len());
for v in self.slice.iter() {
values.push(v.to_shmem(builder)?);
}
// Create a new ThinArc with the shared value and have it place // Create a new ThinArc with the shared value and have it place
// its ArcInner in the shared memory buffer. // its ArcInner in the shared memory buffer.
@ -499,13 +521,13 @@ impl<H: 'static + ToShmem, T: 'static + ToShmem> ToShmem for ThinArc<H, T> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
builder.shared_values.insert(self.heap_ptr()); builder.shared_values.insert(self.heap_ptr());
ManuallyDrop::new(static_arc) Ok(ManuallyDrop::new(static_arc))
} }
} }
} }
impl ToShmem for SmallBitVec { impl ToShmem for SmallBitVec {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
let storage = match self.clone().into_storage() { let storage = match self.clone().into_storage() {
InternalStorage::Spilled(vs) => { InternalStorage::Spilled(vs) => {
// Reserve space for the boxed slice values. // Reserve space for the boxed slice values.
@ -524,13 +546,15 @@ impl ToShmem for SmallBitVec {
}, },
InternalStorage::Inline(x) => InternalStorage::Inline(x), InternalStorage::Inline(x) => InternalStorage::Inline(x),
}; };
ManuallyDrop::new(unsafe { SmallBitVec::from_storage(storage) }) Ok(ManuallyDrop::new(unsafe {
SmallBitVec::from_storage(storage)
}))
} }
} }
#[cfg(feature = "string_cache")] #[cfg(feature = "string_cache")]
impl<Static: string_cache::StaticAtomSet> ToShmem for string_cache::Atom<Static> { impl<Static: string_cache::StaticAtomSet> ToShmem for string_cache::Atom<Static> {
fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _: &mut SharedMemoryBuilder) -> Result<Self> {
// NOTE(emilio): In practice, this can be implemented trivially if // NOTE(emilio): In practice, this can be implemented trivially if
// string_cache could expose the implementation detail of static atoms // string_cache could expose the implementation detail of static atoms
// being an index into the static table (and panicking in the // being an index into the static table (and panicking in the

View file

@ -27,13 +27,23 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
input.generics.where_clause = where_clause; input.generics.where_clause = where_clause;
let match_body = cg::fmap_match(&input, BindStyle::Ref, |binding| { // Do all of the `to_shmem()?` calls before the `ManuallyDrop::into_inner()`
quote! { // calls, so that we don't drop a value in the shared memory buffer if one
::std::mem::ManuallyDrop::into_inner( // of the `to_shmem`s fails.
::to_shmem::ToShmem::to_shmem(#binding, builder) let match_body = cg::fmap2_match(
) &input,
} BindStyle::Ref,
}); |binding| {
quote! {
::to_shmem::ToShmem::to_shmem(#binding, builder)?
}
},
|binding| {
Some(quote! {
::std::mem::ManuallyDrop::into_inner(#binding)
})
},
);
let name = &input.ident; let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
@ -44,12 +54,12 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
fn to_shmem( fn to_shmem(
&self, &self,
builder: &mut ::to_shmem::SharedMemoryBuilder, builder: &mut ::to_shmem::SharedMemoryBuilder,
) -> ::std::mem::ManuallyDrop<Self> { ) -> ::to_shmem::Result<Self> {
::std::mem::ManuallyDrop::new( Ok(::std::mem::ManuallyDrop::new(
match *self { match *self {
#match_body #match_body
} }
) ))
} }
} }
} }

View file

@ -20,7 +20,6 @@ pub use crate::origin::{ImmutableOrigin, MutableOrigin, OpaqueOrigin};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::fmt; use std::fmt;
use std::hash::Hasher; use std::hash::Hasher;
use std::mem::ManuallyDrop;
use std::net::IpAddr; use std::net::IpAddr;
use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
use std::path::Path; use std::path::Path;
@ -34,7 +33,7 @@ pub use url::Host;
pub struct ServoUrl(#[ignore_malloc_size_of = "Arc"] Arc<Url>); pub struct ServoUrl(#[ignore_malloc_size_of = "Arc"] Arc<Url>);
impl ToShmem for ServoUrl { impl ToShmem for ServoUrl {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> { fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
unimplemented!("If servo wants to share stylesheets across processes, ToShmem for Url must be implemented") unimplemented!("If servo wants to share stylesheets across processes, ToShmem for Url must be implemented")
} }
} }

View file

@ -6,7 +6,7 @@ use selectors::parser::{SelectorParseError, SelectorParseErrorKind};
use style::invalidation::element::invalidation_map::Dependency; use style::invalidation::element::invalidation_map::Dependency;
use style::properties; use style::properties;
size_of_test!(test_size_of_dependency, Dependency, 16); size_of_test!(test_size_of_dependency, Dependency, 24);
size_of_test!( size_of_test!(
test_size_of_property_declaration, test_size_of_property_declaration,

View file

@ -201,13 +201,13 @@ fn test_insert() {
0, 0,
selector_map selector_map
.class_hash .class_hash
.get(&Atom::from("foo"), QuirksMode::NoQuirks) .get(&Atom::from("intro"), QuirksMode::NoQuirks)
.unwrap()[0] .unwrap()[0]
.source_order .source_order
); );
assert!(selector_map assert!(selector_map
.class_hash .class_hash
.get(&Atom::from("intro"), QuirksMode::NoQuirks) .get(&Atom::from("foo"), QuirksMode::NoQuirks)
.is_none()); .is_none());
} }