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]]
name = "smallbitvec"
version = "2.3.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1764fe2b30ee783bfe3b9b37b2649d8d590b3148bb12e0079715d4d5c673562e"
checksum = "797a4eaffb90d896f29698d45676f9f940a71936d7574996a7df54593ba209fa"
[[package]]
name = "smallvec"

View file

@ -85,9 +85,22 @@ pub fn add_predicate(where_clause: &mut Option<syn::WhereClause>, pred: WherePre
.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
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);
s.variants_mut().iter_mut().for_each(|v| {
@ -95,12 +108,20 @@ where
});
s.each_variant(|variant| {
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!();
computations.append_all(fields_pairs.map(|(field, mapped_field)| {
let expr = f(field.clone());
let expr = f(field);
quote! { let #mapped_field = #expr; }
}));
computations.append_all(
mapped_fields
.iter()
.map(|mapped_field| match g(mapped_field) {
Some(expr) => quote! { let #mapped_field = #expr; },
None => quote!(),
}),
);
computations.append_all(mapped);
Some(computations)
})

View file

@ -3331,7 +3331,7 @@ impl Document {
pub fn element_state_will_change(&self, el: &Element) {
let mut entry = self.ensure_pending_restyle(el);
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();
if snapshot.state.is_none() {
@ -3347,7 +3347,7 @@ impl Document {
// could in theory do it in the DOM I think.
let mut entry = self.ensure_pending_restyle(el);
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") {
entry.hint.insert(RestyleHint::RESTYLE_STYLE_ATTRIBUTE);
@ -3359,12 +3359,21 @@ impl Document {
let snapshot = entry.snapshot.as_mut().unwrap();
if attr.local_name() == &local_name!("id") {
if snapshot.id_changed {
return;
}
snapshot.id_changed = true;
} else if attr.local_name() == &local_name!("class") {
if snapshot.class_changed {
return;
}
snapshot.class_changed = true;
} else {
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() {
let attrs = el
.attrs()

View file

@ -322,13 +322,7 @@ where
},
}
// The only other parser-allowed Component in this sequence is a state
// 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"
);
if !iter.matches_for_stateless_pseudo_element() {
return false;
}

View file

@ -104,41 +104,57 @@ bitflags! {
/// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set
/// as well.
const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4;
/// Whether we are after any of the pseudo-like things.
const AFTER_PSEUDO = Self::AFTER_PART.bits | Self::AFTER_SLOTTED.bits | Self::AFTER_PSEUDO_ELEMENT.bits;
/// Whether we explicitly disallow combinators.
const DISALLOW_COMBINATORS = 1 << 5;
/// Whether we explicitly disallow pseudo-element-like things.
const DISALLOW_PSEUDOS = 1 << 6;
}
}
impl SelectorParsingState {
#[inline]
fn allows_functional_pseudo_classes(self) -> bool {
!self.intersects(SelectorParsingState::AFTER_PSEUDO)
fn allows_pseudos(self) -> bool {
// NOTE(emilio): We allow pseudos after ::part and such.
!self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS)
}
#[inline]
fn allows_slotted(self) -> bool {
!self.intersects(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]
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]
fn allows_non_functional_pseudo_classes(self) -> bool {
!self.intersects(
SelectorParsingState::AFTER_SLOTTED |
SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT,
)
!self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT)
}
#[inline]
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)]
pub enum SelectorParseErrorKind<'i> {
PseudoElementInComplexSelector,
NoQualifiedNameInAttributeSelector(Token<'i>),
EmptySelector,
DanglingCombinator,
@ -321,15 +336,25 @@ impl<Impl: SelectorImpl> SelectorList<Impl> {
parser: &P,
input: &mut CssParser<'i, 't>,
) -> Result<Self, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
{
Self::parse_with_state(parser, input, SelectorParsingState::empty())
}
fn parse_with_state<'i, 't, P>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Self, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
{
let mut values = SmallVec::new();
loop {
values.push(
input
.parse_until_before(Delimiter::Comma, |input| parse_selector(parser, input))?,
);
values.push(input.parse_until_before(Delimiter::Comma, |input| {
parse_selector(parser, input, state)
})?);
match input.next() {
Err(_) => return Ok(SelectorList(values)),
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>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
let location = input.current_source_location();
let selector = parse_selector(parser, input)?;
// Ensure they're actually all compound selectors without pseudo-elements.
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_selector(
parser,
input,
state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS,
)
}
/// Parse a comma separated list of compound selectors.
@ -380,7 +396,9 @@ where
Impl: SelectorImpl,
{
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())
}
@ -404,18 +422,66 @@ pub struct AncestorHashes {
pub packed_hashes: [u32; 3],
}
fn collect_ancestor_hashes<Impl: SelectorImpl>(
iter: SelectorIter<Impl>,
quirks_mode: QuirksMode,
hashes: &mut [u32; 4],
len: &mut usize,
) -> bool
where
Impl::Identifier: PrecomputedHash,
Impl::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 {
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
Impl::Identifier: PrecomputedHash,
Impl::ClassName: PrecomputedHash,
@ -424,18 +490,14 @@ impl AncestorHashes {
{
// Compute ancestor hashes for the bloom filter.
let mut hashes = [0u32; 4];
let mut hash_iter = AncestorIter::new(iter).filter_map(|x| x.ancestor_hash(quirks_mode));
for i in 0..4 {
hashes[i] = match hash_iter.next() {
Some(x) => x & BLOOM_HASH_MASK,
None => break,
}
}
let mut len = 0;
collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len);
debug_assert!(len <= 4);
// Now, pack the fourth hash (if it exists) into the upper byte of each of
// the other three hashes.
let fourth = hashes[3];
if fourth != 0 {
if len == 4 {
let fourth = hashes[3];
hashes[0] |= (fourth & 0x000000ff) << 24;
hashes[1] |= (fourth & 0x0000ff00) << 16;
hashes[2] |= (fourth & 0x00ff0000) << 8;
@ -741,6 +803,33 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> {
self.next_sequence().is_none()
}
#[inline]
pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool {
let first = match self.next() {
Some(c) => c,
// Note that this is the common path that we keep inline: the
// pseudo-element not having anything to its right.
None => return true,
};
self.matches_for_stateless_pseudo_element_internal(first)
}
#[inline(never)]
fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<Impl>) -> bool {
if !first.matches_for_stateless_pseudo_element() {
return false;
}
for component in self {
// The only other parser-allowed Components in this sequence are
// state pseudo-classes, or one of the other things that can contain
// them.
if !component.matches_for_stateless_pseudo_element() {
return false;
}
}
true
}
/// Returns remaining count of the simple selectors and combinators in the Selector.
#[inline]
pub fn selector_length(&self) -> usize {
@ -980,43 +1069,6 @@ pub enum Component<Impl: SelectorImpl> {
}
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.
pub fn is_combinator(&self) -> bool {
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
where
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>(
parser: &P,
input: &mut CssParser<'i, 't>,
mut state: SelectorParsingState,
) -> Result<Selector<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
@ -1545,16 +1644,14 @@ where
let mut part = false;
'outer_loop: loop {
// Parse a sequence of simple selectors.
let state = match parse_compound_selector(parser, input, &mut builder)? {
Some(state) => state,
None => {
return Err(input.new_custom_error(if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
} else {
SelectorParseErrorKind::EmptySelector
}));
},
};
let empty = parse_compound_selector(parser, &mut state, input, &mut builder)?;
if empty {
return Err(input.new_custom_error(if builder.has_combinators() {
SelectorParseErrorKind::DanglingCombinator
} else {
SelectorParseErrorKind::EmptySelector
}));
}
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
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);
}
@ -1611,7 +1713,7 @@ impl<Impl: SelectorImpl> Selector<Impl> {
where
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>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
sink: &mut S,
) -> Result<bool, ParseError<'i, P::Error>>
where
@ -1635,6 +1738,9 @@ where
}) |
Ok(OptionalQName::None(_)) => Ok(false),
Ok(OptionalQName::Some(namespace, local_name)) => {
if state.intersects(SelectorParsingState::AFTER_PSEUDO) {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
match namespace {
QNamePrefix::ImplicitAnyNamespace => {},
QNamePrefix::ImplicitDefaultNamespace(url) => {
@ -2006,11 +2112,14 @@ fn parse_attribute_flags<'i, 't>(
fn parse_negation<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
{
let state = state | SelectorParsingState::INSIDE_NEGATION;
// We use a sequence because a type selector may be represented as two Components.
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
// 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,
Err(ParseError {
kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
@ -2027,7 +2136,7 @@ where
Err(e) => return Err(e.into()),
};
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)) => {
sequence.push(s);
},
@ -2054,12 +2163,13 @@ where
/// | [ HASH | class | attrib | pseudo | negation ]+
///
/// `Err(())` means invalid selector.
/// `Ok(None)` is an empty selector
/// `Ok(true)` is an empty selector
fn parse_compound_selector<'i, 't, P, Impl>(
parser: &P,
state: &mut SelectorParsingState,
input: &mut CssParser<'i, 't>,
builder: &mut SelectorBuilder<Impl>,
) -> Result<Option<SelectorParsingState>, ParseError<'i, P::Error>>
) -> Result<bool, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
@ -2067,13 +2177,12 @@ where
input.skip_whitespace();
let mut empty = true;
if parse_type_selector(parser, input, builder)? {
if parse_type_selector(parser, input, *state, builder)? {
empty = false;
}
let mut state = SelectorParsingState::empty();
loop {
let result = match parse_one_simple_selector(parser, input, state)? {
let result = match parse_one_simple_selector(parser, input, *state)? {
None => break,
Some(result) => result,
};
@ -2132,17 +2241,13 @@ where
},
}
}
if empty {
// An empty selector is invalid.
Ok(None)
} else {
Ok(Some(state))
}
Ok(empty)
}
fn parse_is_or_where<'i, 't, P, Impl>(
parser: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
component: impl FnOnce(Box<[Selector<Impl>]>) -> Component<Impl>,
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
@ -2150,15 +2255,16 @@ where
Impl: SelectorImpl,
{
debug_assert!(parser.parse_is_and_where());
let inner = SelectorList::parse(parser, input)?;
// https://drafts.csswg.org/selectors/#matches-pseudo:
//
// Pseudo-elements cannot be represented by the matches-any
// pseudo-class; they are not valid within :is().
//
if inner.0.iter().any(|i| i.has_pseudo_element()) {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidPseudoElementInsideWhere));
}
let inner = SelectorList::parse_with_state(
parser,
input,
state | SelectorParsingState::DISALLOW_PSEUDOS,
)?;
Ok(component(inner.0.into_vec().into_boxed_slice()))
}
@ -2172,40 +2278,51 @@ where
P: Parser<'i, Impl = Impl>,
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,
"nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
"nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
"nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?),
"nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
"is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, Component::Where),
"host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
"nth-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthChild),
"nth-of-type" => return parse_nth_pseudo_class(parser, input, state, Component::NthOfType),
"nth-last-child" => return parse_nth_pseudo_class(parser, input, state, Component::NthLastChild),
"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, state, Component::Is),
"where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where),
"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" => {
if state.intersects(SelectorParsingState::INSIDE_NEGATION) {
return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent("not".into())
));
}
debug_assert!(state.is_empty());
return parse_negation(parser, input)
return parse_negation(parser, input, state)
},
_ => {}
}
if !state.allows_custom_functional_pseudo_classes() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass)
}
fn parse_nth_pseudo_class<'i, 't, Impl, F>(
fn parse_nth_pseudo_class<'i, 't, P, Impl, F>(
_: &P,
input: &mut CssParser<'i, 't>,
state: SelectorParsingState,
selector: F,
) -> Result<Component<Impl>, BasicParseError<'i>>
) -> Result<Component<Impl>, ParseError<'i, P::Error>>
where
P: Parser<'i, Impl = Impl>,
Impl: SelectorImpl,
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)?;
Ok(selector(a, b))
}
@ -2290,7 +2407,7 @@ where
};
let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name);
if is_pseudo_element {
if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) {
if !state.allows_pseudos() {
return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState));
}
let pseudo_element = if is_functional {
@ -2317,7 +2434,7 @@ where
);
}
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)));
}

View file

@ -978,7 +978,13 @@ where
} => {
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
// so we have to filter them out.
// See the spec issue https://github.com/w3c/csswg-drafts/issues/1824
@ -986,17 +992,7 @@ where
guard
.normal_declaration_iter()
.filter(|declaration| declaration.is_animatable())
.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,
.map(|decl| (decl, Origin::Author)),
Some(previous_style),
Some(previous_style),
Some(previous_style),

View file

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

View file

@ -7,6 +7,7 @@
use crate::context::QuirksMode;
use crate::dom::{TDocument, TElement, TNode, TShadowRoot};
use crate::invalidation::element::invalidation_map::Dependency;
use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Invalidation};
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use crate::Atom;
@ -130,7 +131,7 @@ where
{
results: &'a mut Q::Output,
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>
@ -143,6 +144,14 @@ where
true
}
fn check_outer_dependency(&mut self, _: &Dependency, _: E) -> bool {
debug_assert!(
false,
"How? We should only have parent-less dependencies here!"
);
true
}
fn collect_invalidations(
&mut self,
element: E,
@ -171,11 +180,10 @@ where
self_invalidations
};
for selector in self.selector_list.0.iter() {
for dependency in self.dependencies.iter() {
target_vector.push(Invalidation::new(
selector,
dependency,
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 {
query_selector_slow::<E, Q>(root, selector_list, results, &mut matching_context);
} else {
let dependencies = selector_list
.0
.iter()
.map(|selector| Dependency::for_full_selector_invalidation(selector.clone()))
.collect::<SmallVec<[_; 5]>>();
let mut processor = QuerySelectorProcessor::<E, Q> {
results,
matching_context,
selector_list,
dependencies: &dependencies,
};
for node in root.dom_children() {

View file

@ -87,9 +87,9 @@ bitflags! {
/// <https://html.spec.whatwg.org/multipage/#selector-out-of-range>
const IN_OUTOFRANGE_STATE = 1 << 28;
/// <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>
const IN_MOZ_READWRITE_STATE = 1 << 30;
const IN_READWRITE_STATE = 1 << 30;
/// <https://html.spec.whatwg.org/multipage/#selector-default>
const IN_DEFAULT_STATE = 1 << 31;
/// 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
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::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
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::values::computed::position::Ratio;
use crate::values::computed::CSSPixelLength;
use crate::values::computed::Resolution;
use crate::Atom;
@ -91,7 +92,7 @@ fn eval_device_height(
fn eval_aspect_ratio_for<F>(
device: &Device,
query_value: Option<AspectRatio>,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
get_size: F,
) -> bool
@ -104,14 +105,14 @@ where
};
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)
}
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
fn eval_aspect_ratio(
device: &Device,
query_value: Option<AspectRatio>,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
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
fn eval_device_aspect_ratio(
device: &Device,
query_value: Option<AspectRatio>,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
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
/// array, with appropriate entries (and potentially any new code needed
/// to support new types in these entries and (2) ensuring that either
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
/// would be returned by the evaluator function could change.
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 53] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
ParsingRequirements::empty(),
),
feature!(
atom!("height"),
AllowsRanges::Yes,
Evaluator::Length(eval_height),
ParsingRequirements::empty(),
),
feature!(
atom!("aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_aspect_ratio),
ParsingRequirements::empty(),
),
feature!(
atom!("orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_orientation, Orientation),
ParsingRequirements::empty(),
),
feature!(
atom!("device-width"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_width),
ParsingRequirements::empty(),
),
feature!(
atom!("device-height"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_height),
ParsingRequirements::empty(),
),
feature!(
atom!("device-aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_device_aspect_ratio),
ParsingRequirements::empty(),
),
feature!(
atom!("-moz-device-orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_device_orientation, Orientation),
ParsingRequirements::empty(),
),
// Webkit extensions that we support for de-facto web compatibility.
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
feature!(
atom!("device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
ParsingRequirements::WEBKIT_PREFIX,
),
// -webkit-transform-3d.
feature!(
atom!("transform-3d"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_transform_3d),
ParsingRequirements::WEBKIT_PREFIX,
),
feature!(
atom!("-moz-device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
ParsingRequirements::empty(),
),
feature!(
atom!("resolution"),
AllowsRanges::Yes,
Evaluator::Resolution(eval_resolution),
ParsingRequirements::empty(),
),
feature!(
atom!("display-mode"),
AllowsRanges::No,
keyword_evaluator!(eval_display_mode, DisplayMode),
ParsingRequirements::empty(),
),
feature!(
atom!("grid"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_grid),
ParsingRequirements::empty(),
),
feature!(
atom!("scan"),
AllowsRanges::No,
keyword_evaluator!(eval_scan, Scan),
ParsingRequirements::empty(),
),
feature!(
atom!("color"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color),
ParsingRequirements::empty(),
),
feature!(
atom!("color-index"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color_index),
ParsingRequirements::empty(),
),
feature!(
atom!("monochrome"),
AllowsRanges::Yes,
Evaluator::Integer(eval_monochrome),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-reduced-motion"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
ParsingRequirements::empty(),
),
feature!(
atom!("overflow-block"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_block, OverflowBlock),
ParsingRequirements::empty(),
),
feature!(
atom!("overflow-inline"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_inline, OverflowInline),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-color-scheme"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
ParsingRequirements::empty(),
),
feature!(
atom!("pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_pointer, Pointer),
ParsingRequirements::empty(),
),
feature!(
atom!("any-pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_any_pointer, Pointer),
ParsingRequirements::empty(),
),
feature!(
atom!("hover"),
AllowsRanges::No,
keyword_evaluator!(eval_hover, Hover),
ParsingRequirements::empty(),
),
feature!(
atom!("any-hover"),
AllowsRanges::No,
keyword_evaluator!(eval_any_hover, Hover),
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
// and therefore not worth standardizing.
feature!(
atom!("-moz-is-glyph"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_glyph),
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-is-resource-document"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_resource_document),
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-os-version"),
AllowsRanges::No,
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-end-backward")),
system_metric_feature!(atom!("-moz-scrollbar-end-forward")),
system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")),
system_metric_feature!(atom!("-moz-overlay-scrollbars")),
system_metric_feature!(atom!("-moz-windows-default-theme")),
system_metric_feature!(atom!("-moz-mac-graphite-theme")),
system_metric_feature!(atom!("-moz-mac-yosemite-theme")),
system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")),
system_metric_feature!(atom!("-moz-windows-compositor")),
system_metric_feature!(atom!("-moz-windows-classic")),
system_metric_feature!(atom!("-moz-windows-glass")),
system_metric_feature!(atom!("-moz-menubar-drag")),
system_metric_feature!(atom!("-moz-swipe-animation-enabled")),
system_metric_feature!(atom!("-moz-gtk-csd-available")),
system_metric_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default")),
system_metric_feature!(atom!("-moz-gtk-csd-transparent-background")),
system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")),
system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")),
system_metric_feature!(atom!("-moz-gtk-csd-close-button")),
system_metric_feature!(atom!("-moz-gtk-csd-reversed-placement")),
system_metric_feature!(atom!("-moz-system-dark-theme")),
// This is the only system-metric media feature that's accessible to
// content as of today.
// FIXME(emilio): Restrict (or remove?) when bug 1035774 lands.
feature!(
atom!("-moz-touch-enabled"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_touch_enabled),
ParsingRequirements::empty(),
),
];
}
/// Adding new media features requires (1) adding the new feature to this
/// array, with appropriate entries (and potentially any new code needed
/// to support new types in these entries and (2) ensuring that either
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
/// would be returned by the evaluator function could change.
pub static MEDIA_FEATURES: [MediaFeatureDescription; 53] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
ParsingRequirements::empty(),
),
feature!(
atom!("height"),
AllowsRanges::Yes,
Evaluator::Length(eval_height),
ParsingRequirements::empty(),
),
feature!(
atom!("aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_aspect_ratio),
ParsingRequirements::empty(),
),
feature!(
atom!("orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_orientation, Orientation),
ParsingRequirements::empty(),
),
feature!(
atom!("device-width"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_width),
ParsingRequirements::empty(),
),
feature!(
atom!("device-height"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_height),
ParsingRequirements::empty(),
),
feature!(
atom!("device-aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_device_aspect_ratio),
ParsingRequirements::empty(),
),
feature!(
atom!("-moz-device-orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_device_orientation, Orientation),
ParsingRequirements::empty(),
),
// Webkit extensions that we support for de-facto web compatibility.
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
feature!(
atom!("device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
ParsingRequirements::WEBKIT_PREFIX,
),
// -webkit-transform-3d.
feature!(
atom!("transform-3d"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_transform_3d),
ParsingRequirements::WEBKIT_PREFIX,
),
feature!(
atom!("-moz-device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
ParsingRequirements::empty(),
),
feature!(
atom!("resolution"),
AllowsRanges::Yes,
Evaluator::Resolution(eval_resolution),
ParsingRequirements::empty(),
),
feature!(
atom!("display-mode"),
AllowsRanges::No,
keyword_evaluator!(eval_display_mode, DisplayMode),
ParsingRequirements::empty(),
),
feature!(
atom!("grid"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_grid),
ParsingRequirements::empty(),
),
feature!(
atom!("scan"),
AllowsRanges::No,
keyword_evaluator!(eval_scan, Scan),
ParsingRequirements::empty(),
),
feature!(
atom!("color"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color),
ParsingRequirements::empty(),
),
feature!(
atom!("color-index"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color_index),
ParsingRequirements::empty(),
),
feature!(
atom!("monochrome"),
AllowsRanges::Yes,
Evaluator::Integer(eval_monochrome),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-reduced-motion"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
ParsingRequirements::empty(),
),
feature!(
atom!("overflow-block"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_block, OverflowBlock),
ParsingRequirements::empty(),
),
feature!(
atom!("overflow-inline"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_inline, OverflowInline),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-color-scheme"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
ParsingRequirements::empty(),
),
feature!(
atom!("pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_pointer, Pointer),
ParsingRequirements::empty(),
),
feature!(
atom!("any-pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_any_pointer, Pointer),
ParsingRequirements::empty(),
),
feature!(
atom!("hover"),
AllowsRanges::No,
keyword_evaluator!(eval_hover, Hover),
ParsingRequirements::empty(),
),
feature!(
atom!("any-hover"),
AllowsRanges::No,
keyword_evaluator!(eval_any_hover, Hover),
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
// and therefore not worth standardizing.
feature!(
atom!("-moz-is-glyph"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_glyph),
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-is-resource-document"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_resource_document),
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-os-version"),
AllowsRanges::No,
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-end-backward")),
system_metric_feature!(atom!("-moz-scrollbar-end-forward")),
system_metric_feature!(atom!("-moz-scrollbar-thumb-proportional")),
system_metric_feature!(atom!("-moz-overlay-scrollbars")),
system_metric_feature!(atom!("-moz-windows-default-theme")),
system_metric_feature!(atom!("-moz-mac-graphite-theme")),
system_metric_feature!(atom!("-moz-mac-yosemite-theme")),
system_metric_feature!(atom!("-moz-windows-accent-color-in-titlebar")),
system_metric_feature!(atom!("-moz-windows-compositor")),
system_metric_feature!(atom!("-moz-windows-classic")),
system_metric_feature!(atom!("-moz-windows-glass")),
system_metric_feature!(atom!("-moz-menubar-drag")),
system_metric_feature!(atom!("-moz-swipe-animation-enabled")),
system_metric_feature!(atom!("-moz-gtk-csd-available")),
system_metric_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default")),
system_metric_feature!(atom!("-moz-gtk-csd-transparent-background")),
system_metric_feature!(atom!("-moz-gtk-csd-minimize-button")),
system_metric_feature!(atom!("-moz-gtk-csd-maximize-button")),
system_metric_feature!(atom!("-moz-gtk-csd-close-button")),
system_metric_feature!(atom!("-moz-gtk-csd-reversed-placement")),
system_metric_feature!(atom!("-moz-system-dark-theme")),
// This is the only system-metric media feature that's accessible to
// content as of today.
// FIXME(emilio): Restrict (or remove?) when bug 1035774 lands.
feature!(
atom!("-moz-touch-enabled"),
AllowsRanges::No,
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-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("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).
("-moz-focusring", MozFocusRing, IN_FOCUSRING_STATE, _),
("-moz-broken", MozBroken, IN_BROKEN_STATE, _),
@ -79,8 +80,8 @@ macro_rules! apply_non_ts_list {
("out-of-range", OutOfRange, IN_OUTOFRANGE_STATE, _),
("default", Default, IN_DEFAULT_STATE, _),
("placeholder-shown", PlaceholderShown, IN_PLACEHOLDER_SHOWN_STATE, _),
("-moz-read-only", MozReadOnly, IN_MOZ_READONLY_STATE, _),
("-moz-read-write", MozReadWrite, IN_MOZ_READWRITE_STATE, _),
("read-only", ReadOnly, IN_READONLY_STATE, _),
("read-write", ReadWrite, IN_READWRITE_STATE, _),
("-moz-submit-invalid", MozSubmitInvalid, IN_MOZ_SUBMITINVALID_STATE, _),
("-moz-ui-valid", MozUIValid, IN_MOZ_UI_VALID_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::After |
PseudoElement::Marker |
PseudoElement::Placeholder
PseudoElement::Placeholder |
PseudoElement::FileChooserButton
)
}
@ -92,6 +93,12 @@ impl PseudoElement {
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.
#[inline]
pub fn is_before_or_after(&self) -> bool {
@ -153,7 +160,15 @@ impl PseudoElement {
/// Whether this pseudo-element is enabled for all content.
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.

View file

@ -119,6 +119,8 @@ impl NonTSPseudoClass {
match_ignore_ascii_case! { &name,
$($css => Some(NonTSPseudoClass::$name),)*
"-moz-full-screen" => Some(NonTSPseudoClass::Fullscreen),
"-moz-read-only" => Some(NonTSPseudoClass::ReadOnly),
"-moz-read-write" => Some(NonTSPseudoClass::ReadWrite),
_ => None,
}
}
@ -227,17 +229,6 @@ impl NonTSPseudoClass {
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 {
@ -352,7 +343,8 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
#[inline]
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]

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@
//! Rust helpers for Gecko's nsTArray.
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::ops::{Deref, DerefMut};
use std::slice;
@ -128,3 +128,16 @@ impl<T> nsTArray<T> {
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.
use gecko_bindings::structs::root::mozilla::detail::CopyablePtr;
use servo_arc::{Arc, RawOffsetArc};
use std::marker::PhantomData;
use std::mem::{forget, transmute};
@ -332,3 +333,16 @@ impl<GeckoType> OwnedOrNull<GeckoType> {
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/. */
#![allow(unsafe_code)]
// This is needed for the constants in atom_macro.rs, because we have some
// atoms whose names differ only by case, e.g. datetime and dateTime.
#![allow(non_upper_case_globals)]
@ -30,7 +29,7 @@ use std::num::NonZeroUsize;
use std::ops::Deref;
use std::{slice, str};
use style_traits::SpecifiedValueInfo;
use to_shmem::{SharedMemoryBuilder, ToShmem};
use to_shmem::{self, SharedMemoryBuilder, ToShmem};
#[macro_use]
#[allow(improper_ctypes, non_camel_case_types, missing_docs)]
@ -132,14 +131,15 @@ impl Borrow<WeakAtom> for Atom {
}
impl ToShmem for Atom {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
assert!(
self.is_static(),
"ToShmem failed for Atom: must be a static atom: {}",
self
);
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
if !self.is_static() {
return Err(format!(
"ToShmem failed for Atom: must be a static atom: {}",
self
));
}
ManuallyDrop::new(Atom(self.0))
Ok(ManuallyDrop::new(Atom(self.0)))
}
}

View file

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

View file

@ -6,7 +6,9 @@
use crate::context::QuirksMode;
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::{Atom, LocalName, Namespace};
use fallible::FallibleVec;
@ -47,6 +49,23 @@ pub struct Dependency {
/// The offset into the selector that we should match on.
pub selector_offset: usize,
/// The parent dependency for an ancestor selector. For example, consider
/// the following:
///
/// .foo .bar:where(.baz span) .qux
/// ^ ^ ^
/// A B C
///
/// We'd generate:
///
/// * One dependency for .qux (offset: 0, parent: None)
/// * One dependency for .baz pointing to B with parent being a
/// dependency pointing to C.
/// * One dependency from .bar pointing to C (parent: None)
/// * One dependency from .foo pointing to A (parent: None)
///
pub parent: Option<Box<Dependency>>,
}
/// The kind of elements down the tree this dependency may affect.
@ -71,6 +90,22 @@ pub enum DependencyInvalidationKind {
}
impl Dependency {
/// Creates a dummy dependency to invalidate the whole selector.
///
/// This is necessary because document state invalidation wants to
/// invalidate all elements in the document.
///
/// The offset is such as that Invalidation::new(self) returns a zero
/// offset. That is, it points to a virtual "combinator" outside of the
/// selector, so calling combinator() on such a dependency will panic.
pub fn for_full_selector_invalidation(selector: Selector<SelectorImpl>) -> Self {
Self {
selector_offset: selector.len() + 1,
selector,
parent: None,
}
}
/// Returns the combinator to the right of the partial selector this
/// dependency represents.
///
@ -130,31 +165,18 @@ impl SelectorMapEntry for StateDependency {
/// The same, but for document state selectors.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct DocumentStateDependency {
/// The selector that is affected. We don't need to track an offset, since
/// when it changes it changes for the whole document anyway.
/// We track `Dependency` even though we don't need to track an offset,
/// since when it changes it changes for the whole document anyway.
#[cfg_attr(
feature = "gecko",
ignore_malloc_size_of = "CssRules have primary refs, we measure there"
)]
#[cfg_attr(feature = "servo", ignore_malloc_size_of = "Arc")]
pub selector: Selector<SelectorImpl>,
pub dependency: Dependency,
/// The state this dependency is affected by.
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.
///
/// 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.
pub document_state_selectors: Vec<DocumentStateDependency>,
/// A map of other attribute affecting selectors.
pub other_attribute_affecting_selectors: SelectorMap<Dependency>,
/// A set of flags that contain whether various special attributes are used
/// in this invalidation map.
pub flags: InvalidationMapFlags,
pub other_attribute_affecting_selectors:
PrecomputedHashMap<LocalName, SmallVec<[Dependency; 1]>>,
}
impl InvalidationMap {
@ -190,8 +210,7 @@ impl InvalidationMap {
id_to_selector: MaybeCaseInsensitiveHashMap::new(),
state_affecting_selectors: SelectorMap::new(),
document_state_selectors: Vec::new(),
other_attribute_affecting_selectors: SelectorMap::new(),
flags: InvalidationMapFlags::empty(),
other_attribute_affecting_selectors: PrecomputedHashMap::default(),
}
}
@ -199,7 +218,9 @@ impl InvalidationMap {
pub fn len(&self) -> usize {
self.state_affecting_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
.iter()
.fold(0, |accum, (_, ref v)| accum + v.len()) +
@ -215,7 +236,6 @@ impl InvalidationMap {
self.state_affecting_selectors.clear();
self.document_state_selectors.clear();
self.other_attribute_affecting_selectors.clear();
self.flags = InvalidationMapFlags::empty();
}
/// Adds a selector to this `InvalidationMap`. Returns Err(..) to
@ -227,115 +247,60 @@ impl InvalidationMap {
) -> Result<(), FailedAllocationError> {
debug!("InvalidationMap::note_selector({:?})", selector);
let mut iter = selector.iter();
let mut combinator;
let mut index = 0;
let mut document_state = DocumentState::empty();
loop {
let sequence_start = index;
let mut compound_visitor = CompoundSelectorDependencyCollector {
classes: SmallVec::new(),
ids: SmallVec::new(),
state: ElementState::empty(),
{
let mut parent_stack = SmallVec::new();
let mut alloc_error = None;
let mut collector = SelectorDependencyCollector {
map: self,
document_state: &mut document_state,
other_attributes: false,
flags: &mut self.flags,
selector,
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.
//
// Note that this works because we can't have combinators nested
// inside simple selectors (i.e. in :not() or :-moz-any()).
//
// 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.
let visit_result = collector.visit_whole_selector();
debug_assert_eq!(!visit_result, alloc_error.is_some());
if let Some(alloc_error) = alloc_error {
return Err(alloc_error);
}
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() {
self.document_state_selectors
.try_push(DocumentStateDependency {
state: document_state,
selector: selector.clone(),
})?;
let dep = DocumentStateDependency {
state: document_state,
dependency: Dependency::for_full_selector_invalidation(selector.clone()),
};
self.document_state_selectors.try_push(dep)?;
}
Ok(())
}
}
/// A struct that collects invalidations for a given compound selector.
///
/// FIXME(emilio, bug 1509418): :where is mishandled, figure out the right way
/// 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> {
struct PerCompoundState {
/// The offset at which our compound starts.
offset: usize,
/// 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.
///
@ -343,76 +308,226 @@ struct CompoundSelectorDependencyCollector<'a> {
/// state and it changes for everything.
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
/// presence of :not, :-moz-any, .foo.bar.baz, etc.
classes: SmallVec<[Atom; 5]>,
/// This starts empty. It grows when we find nested :is and :where selector
/// lists.
parent_selectors: &'a mut SmallVec<[(Selector<SelectorImpl>, usize); 5]>,
/// The IDs this compound selector is affected by.
///
/// 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]>,
/// The quirks mode of the document where we're inserting dependencies.
quirks_mode: QuirksMode,
/// Whether it affects other attribute-dependent selectors that aren't ID or
/// class selectors (NB: We still set this to true in presence of [class] or
/// [id] attribute selectors).
other_attributes: bool,
/// State relevant to a given compound selector.
compound_state: PerCompoundState,
/// The invalidation map flags, that we set when some attribute selectors are present.
flags: &'a mut InvalidationMapFlags,
/// The allocation error, if we OOM.
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;
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 {
#[cfg(feature = "gecko")]
use crate::selector_parser::NonTSPseudoClass;
match *s {
Component::ID(ref id) => {
self.ids.push(id.clone());
},
Component::Class(ref class) => {
self.classes.push(class.clone());
Component::ID(ref atom) | Component::Class(ref atom) => {
let dependency = self.dependency();
let map = match *s {
Component::ID(..) => &mut self.map.id_to_selector,
Component::Class(..) => &mut self.map.class_to_selector,
_ => unreachable!(),
};
let entry = match map.try_entry(atom.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) => {
self.other_attributes |= pc.is_attr_based();
self.state |= match *pc {
self.compound_state.element_state |= match *pc {
#[cfg(feature = "gecko")]
NonTSPseudoClass::Dir(ref dir) => dir.element_state(),
_ => pc.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(
&mut self,
constraint: &NamespaceConstraint<&Namespace>,
_local_name: &LocalName,
_: &NamespaceConstraint<&Namespace>,
local_name: &LocalName,
local_name_lower: &LocalName,
) -> bool {
self.other_attributes = true;
let may_match_in_no_namespace = match *constraint {
NamespaceConstraint::Any => true,
NamespaceConstraint::Specific(ref ns) => ns.is_empty(),
};
if !self.add_attr_dependency(local_name.clone()) {
return false;
}
if may_match_in_no_namespace {
if *local_name_lower == local_name!("id") {
self.flags
.insert(InvalidationMapFlags::HAS_ID_ATTR_SELECTOR)
} else if *local_name_lower == local_name!("class") {
self.flags
.insert(InvalidationMapFlags::HAS_CLASS_ATTR_SELECTOR)
}
if local_name != local_name_lower && !self.add_attr_dependency(local_name_lower.clone()) {
return false;
}
true

View file

@ -7,10 +7,10 @@
use crate::context::StackLimitChecker;
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::{CompoundSelectorMatchingResult, MatchingContext};
use selectors::parser::{Combinator, Component, Selector};
use selectors::parser::{Combinator, Component};
use selectors::OpaqueElement;
use smallvec::SmallVec;
use std::fmt;
@ -34,6 +34,27 @@ where
false
}
/// When a dependency from a :where or :is selector matches, it may still be
/// the case that we don't need to invalidate the full style. Consider the
/// case of:
///
/// div .foo:where(.bar *, .baz) .qux
///
/// We can get to the `*` part after a .bar class change, but you only need
/// to restyle the element if it also matches .foo.
///
/// Similarly, you only need to restyle .baz if the whole result of matching
/// the selector changes.
///
/// This function is called to check the result of matching the "outer"
/// dependency that we generate for the parent of the `:where` selector,
/// that is, in the case above it should match
/// `div .foo:where(.bar *, .baz)`.
///
/// Returning true unconditionally here is over-optimistic and may
/// over-invalidate.
fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool;
/// The matching context that should be used to process invalidations.
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl>;
@ -127,7 +148,11 @@ enum InvalidationKind {
/// relative to a current element we are processing, must be restyled.
#[derive(Clone)]
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.
///
/// 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
/// of the selector written as parsed / serialized.
///
/// It is initialized from the offset from `dependency`.
offset: usize,
/// Whether the invalidation was already matched by any previous sibling or
/// ancestor.
@ -149,16 +176,18 @@ pub struct Invalidation<'a> {
}
impl<'a> Invalidation<'a> {
/// Create a new invalidation for a given selector and offset.
pub fn new(
selector: &'a Selector<SelectorImpl>,
scope: Option<OpaqueElement>,
offset: usize,
) -> Self {
/// Create a new invalidation for matching a dependency.
pub fn new(dependency: &'a Dependency, scope: Option<OpaqueElement>) -> Self {
debug_assert!(
dependency.selector_offset == dependency.selector.len() + 1 ||
dependency.invalidation_kind() != DependencyInvalidationKind::Element,
"No point to this, if the dependency matched the element we should just invalidate it"
);
Self {
selector,
dependency,
scope,
offset,
// + 1 to go past the combinator.
offset: dependency.selector.len() + 1 - dependency.selector_offset,
matched_by_any_previous: false,
}
}
@ -174,7 +203,11 @@ impl<'a> Invalidation<'a> {
// for the weird pseudos in <input type="number">.
//
// 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::Part |
Combinator::SlotAssignment |
@ -188,7 +221,11 @@ impl<'a> Invalidation<'a> {
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 => {
InvalidationKind::Descendant(DescendantInvalidationKind::Dom)
},
@ -206,7 +243,11 @@ impl<'a> fmt::Debug for Invalidation<'a> {
use cssparser::ToCss;
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(..)) {
break;
}
@ -763,201 +804,251 @@ where
context.current_host = invalidation.scope;
matches_compound_selector_from(
&invalidation.selector,
&invalidation.dependency.selector,
invalidation.offset,
context,
&self.element,
)
};
let mut invalidated_self = false;
let mut matched = false;
match matching_result {
let next_invalidation = match matching_result {
CompoundSelectorMatchingResult::NotMatched => {
return SingleInvalidationResult {
invalidated_self: false,
matched: false,
}
},
CompoundSelectorMatchingResult::FullyMatched => {
debug!(" > Invalidation matched completely");
matched = true;
invalidated_self = true;
// We matched completely. If we're an inner selector now we need
// to go outside our selector and carry on invalidating.
let mut cur_dependency = invalidation.dependency;
loop {
cur_dependency = match cur_dependency.parent {
None => {
return SingleInvalidationResult {
invalidated_self: true,
matched: true,
}
},
Some(ref p) => &**p,
};
debug!(" > Checking outer dependency {:?}", cur_dependency);
// The inner selector changed, now check if the full
// previous part of the selector did, before keeping
// checking for descendants.
if !self
.processor
.check_outer_dependency(cur_dependency, self.element)
{
return SingleInvalidationResult {
invalidated_self: false,
matched: false,
};
}
if cur_dependency.invalidation_kind() == DependencyInvalidationKind::Element {
continue;
}
debug!(" > Generating invalidation");
break Invalidation::new(cur_dependency, invalidation.scope);
}
},
CompoundSelectorMatchingResult::Matched {
next_combinator_offset,
} => {
let next_combinator = invalidation
.selector
.combinator_at_parse_order(next_combinator_offset);
matched = true;
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);
},
}
}
} => Invalidation {
dependency: invalidation.dependency,
scope: invalidation.scope,
offset: next_combinator_offset + 1,
matched_by_any_previous: false,
},
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 {
invalidated_self,
matched,
matched: true,
}
}
}

View file

@ -42,7 +42,6 @@ where
descendant_invalidations: &'a mut DescendantInvalidationLists<'selectors>,
sibling_invalidations: &'a mut InvalidationVector<'selectors>,
invalidates_self: bool,
attr_selector_flags: InvalidationMapFlags,
}
/// 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
/// invalidation.
pub fn should_process_descendants(data: &ElementData) -> bool {
@ -133,6 +165,14 @@ where
true
}
fn check_outer_dependency(&mut self, dependency: &Dependency, element: E) -> bool {
// We cannot assert about `element` having a snapshot here (in fact it
// most likely won't), because it may be an arbitrary descendant or
// later-sibling of the element we started invalidating with.
let wrapper = ElementWrapper::new(element, &*self.shared_context.snapshot_map);
check_dependency(dependency, &element, &wrapper, &mut self.matching_context)
}
fn matching_context(&mut self) -> &mut MatchingContext<'a, E::Impl> {
&mut self.matching_context
}
@ -156,8 +196,6 @@ where
return false;
}
let mut attr_selector_flags = InvalidationMapFlags::empty();
// 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
// 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_added = SmallVec::<[Atom; 8]>::new();
if snapshot.class_changed() {
attr_selector_flags.insert(InvalidationMapFlags::HAS_CLASS_ATTR_SELECTOR);
// TODO(emilio): Do this more efficiently!
snapshot.each_class(|c| {
if !element.has_class(c, CaseSensitivity::CaseSensitive) {
@ -193,7 +230,6 @@ where
let mut id_removed = None;
let mut id_added = None;
if snapshot.id_changed() {
attr_selector_flags.insert(InvalidationMapFlags::HAS_ID_ATTR_SELECTOR);
let old_id = snapshot.id_attr();
let current_id = element.id();
@ -204,10 +240,7 @@ where
}
if log_enabled!(::log::Level::Debug) {
debug!(
"Collecting changes for: {:?}, flags {:?}",
element, attr_selector_flags
);
debug!("Collecting changes for: {:?}", element);
if !state_changes.is_empty() {
debug!(" > state: {:?}", state_changes);
}
@ -220,7 +253,11 @@ where
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!(
" > attributes changed, old: {}",
snapshot.debug_list_attributes()
@ -255,7 +292,6 @@ where
descendant_invalidations,
sibling_invalidations,
invalidates_self: false,
attr_selector_flags,
};
let document_origins = if !matches_document_author_rules {
@ -365,12 +401,13 @@ where
}
}
let should_examine_attribute_selector_map =
self.snapshot.other_attr_changed() || map.flags.intersects(self.attr_selector_flags);
if should_examine_attribute_selector_map {
self.collect_dependencies_in_map(&map.other_attribute_affecting_selectors)
}
self.snapshot.each_attr_changed(|attribute| {
if let Some(deps) = map.other_attribute_affecting_selectors.get(attribute) {
for dep in deps {
self.scan_dependency(dep);
}
}
});
let state_changes = self.state_changes;
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(
&mut self,
map: &'selectors SelectorMap<StateDependency>,
@ -412,28 +436,14 @@ where
}
/// Check whether a dependency should be taken into account.
#[inline]
fn check_dependency(&mut self, dependency: &Dependency) -> bool {
let element = &self.element;
let wrapper = &self.wrapper;
let matches_now = matches_selector(
&dependency.selector,
dependency.selector_offset,
None,
element,
check_dependency(
dependency,
&self.element,
&self.wrapper,
&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) {
@ -456,18 +466,21 @@ where
let invalidation_kind = dependency.invalidation_kind();
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;
}
debug_assert_ne!(dependency.selector_offset, 0);
debug_assert_ne!(dependency.selector_offset, dependency.selector.len());
let invalidation = Invalidation::new(
&dependency.selector,
self.matching_context.current_host.clone(),
dependency.selector.len() - dependency.selector_offset + 1,
);
let invalidation =
Invalidation::new(&dependency, self.matching_context.current_host.clone());
match invalidation_kind {
DependencyInvalidationKind::Element => unreachable!(),

View file

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

View file

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

View file

@ -15,9 +15,8 @@ use crate::parser::{Parse, ParserContext};
#[cfg(feature = "servo")]
use crate::servo::media_queries::MEDIA_FEATURES;
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::values::computed::position::Ratio;
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::{serialize_atom_identifier, CSSFloat};
use crate::{Atom, Zero};
@ -26,30 +25,6 @@ use std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write};
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.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum Range {
@ -460,7 +435,7 @@ pub enum MediaExpressionValue {
BoolInteger(bool),
/// A single non-negative number or two non-negative numbers separated by '/',
/// with optional whitespace on either side of the '/'.
NumberRatio(AspectRatio),
NumberRatio(Ratio),
/// A resolution.
Resolution(Resolution),
/// An enumerated value, defined by the variant keyword table in the
@ -517,24 +492,14 @@ impl MediaExpressionValue {
MediaExpressionValue::Float(number.get())
},
Evaluator::NumberRatio(..) => {
#[cfg(feature = "gecko")]
{
if static_prefs::pref!("layout.css.aspect-ratio-number.enabled") {
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)));
}
}
use crate::values::generics::position::Ratio as GenericRatio;
use crate::values::generics::NonNegative;
use crate::values::specified::position::Ratio;
let a = Integer::parse_positive(context, input)?;
input.expect_delim('/')?;
let b = Integer::parse_positive(context, input)?;
MediaExpressionValue::NumberRatio(AspectRatio(
a.value() as CSSFloat,
b.value() as CSSFloat,
let ratio = Ratio::parse(context, input)?;
MediaExpressionValue::NumberRatio(GenericRatio(
NonNegative(ratio.0.get()),
NonNegative(ratio.1.get()),
))
},
Evaluator::Resolution(..) => {

View file

@ -207,11 +207,20 @@ where
}
}
impl Parse for UnicodeRange {
impl Parse for crate::OwnedStr {
fn parse<'i, 't>(
_context: &ParserContext,
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> 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::logical_geometry::WritingMode;
use crate::media_queries::Device;
use crate::properties::{ComputedValues, StyleBuilder};
use crate::properties::{LonghandId, LonghandIdSet, CSSWideKeyword};
use crate::properties::{ComputedValues, StyleBuilder, Importance};
use crate::properties::{LonghandId, LonghandIdSet, CSSWideKeyword, PropertyFlags};
use crate::properties::{PropertyDeclaration, PropertyDeclarationId, DeclarationImportanceIterator};
use crate::properties::{CASCADE_PROPERTY, ComputedValueFlags};
use crate::rule_cache::{RuleCache, RuleCacheConditions};
@ -20,7 +20,6 @@ use crate::selector_parser::PseudoElement;
use crate::stylesheets::{Origin, PerOrigin};
use servo_arc::Arc;
use crate::shared_lock::StylesheetGuards;
use smallbitvec::SmallBitVec;
use smallvec::SmallVec;
use std::borrow::Cow;
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>(
device: &Device,
pseudo: Option<&PseudoElement>,
@ -130,54 +207,12 @@ where
parent_style.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(
device,
pseudo,
rule_node,
guards,
iter_declarations,
DeclarationIterator::new(rule_node, guards, pseudo),
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
@ -208,12 +243,12 @@ pub enum CascadeMode<'a> {
/// NOTE: This function expects the declaration with more priority to appear
/// first.
pub fn apply_declarations<'a, E, F, I>(
pub fn apply_declarations<'a, E, I>(
device: &Device,
pseudo: Option<&PseudoElement>,
rules: &StrongRuleNode,
guards: &StylesheetGuards,
iter_declarations: F,
iter: I,
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
@ -226,7 +261,6 @@ pub fn apply_declarations<'a, E, F, I>(
) -> Arc<ComputedValues>
where
E: TElement,
F: Fn() -> I,
I: Iterator<Item = (&'a PropertyDeclaration, Origin)>,
{
debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
@ -253,7 +287,7 @@ where
device,
);
for (declaration, origin) in iter_declarations() {
for (declaration, origin) in iter {
declarations.push((declaration, origin));
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(declaration, origin);
@ -384,10 +418,15 @@ fn tweak_when_ignoring_colors(
if color.0.is_transparent() {
return;
}
let color = builder.device.default_color();
declarations_to_apply_unless_overriden.push(
PropertyDeclaration::Color(specified::ColorPropertyValue(color.into()))
)
// If the inherited color would be transparent, but we would
// override this with a non-transparent color, then override it with
// the default color. Otherwise just let it inherit through.
if 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.
#[cfg(feature = "gecko")]
@ -881,8 +920,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
let builder = &mut self.context.builder;
let parent_zoom = builder.get_parent_font().gecko().mAllowZoom;
let zoom = builder.get_font().gecko().mAllowZoom;
let parent_zoom = builder.get_parent_font().gecko().mAllowZoomAndMinSize;
let zoom = builder.get_font().gecko().mAllowZoomAndMinSize;
if zoom == parent_zoom {
return;
}
@ -929,7 +968,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
}
let mut min = Au(parent_font.mScriptMinSize);
if font.mAllowZoom {
if font.mAllowZoomAndMinSize {
min = builder.device.zoom_text(min);
}

View file

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

View file

@ -110,9 +110,17 @@ pub struct DeclarationImportanceIterator<'a> {
iter: Zip<Iter<'a, PropertyDeclaration>, smallbitvec::Iter<'a>>,
}
impl<'a> Default for DeclarationImportanceIterator<'a> {
fn default() -> Self {
Self {
iter: [].iter().zip(smallbitvec::Iter::default()),
}
}
}
impl<'a> DeclarationImportanceIterator<'a> {
/// Constructor.
pub fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self {
fn new(declarations: &'a [PropertyDeclaration], important: &'a SmallBitVec) -> Self {
DeclarationImportanceIterator {
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) %>
<%self:impl_trait style_struct_name="Position"
skip_longhands="${skip_position_longhands}">
skip_longhands="${skip_position_longhands}
masonry-auto-flow">
% for side in SIDES:
<% impl_split_style_coord(side.ident, "mOffset", side.index) %>
% endfor
@ -811,6 +812,7 @@ fn static_assert() {
self.gecko.mJustifyItems.computed = v;
}
${impl_simple_type_with_conversion("masonry_auto_flow", "mMasonryAutoFlow")}
</%self:impl_trait>
<% skip_outline_longhands = " ".join("outline-style outline-width".split() +
@ -951,19 +953,19 @@ fn static_assert() {
if let Some(info) = v.keyword_info {
self.gecko.mFontSizeKeyword = match info.kw {
KeywordSize::XXSmall => structs::NS_STYLE_FONT_SIZE_XXSMALL,
KeywordSize::XSmall => structs::NS_STYLE_FONT_SIZE_XSMALL,
KeywordSize::Small => structs::NS_STYLE_FONT_SIZE_SMALL,
KeywordSize::Medium => structs::NS_STYLE_FONT_SIZE_MEDIUM,
KeywordSize::Large => structs::NS_STYLE_FONT_SIZE_LARGE,
KeywordSize::XLarge => structs::NS_STYLE_FONT_SIZE_XLARGE,
KeywordSize::XXLarge => structs::NS_STYLE_FONT_SIZE_XXLARGE,
KeywordSize::XXXLarge => structs::NS_STYLE_FONT_SIZE_XXXLARGE,
} as u8;
KeywordSize::XXSmall => structs::StyleFontSize::Xxsmall,
KeywordSize::XSmall => structs::StyleFontSize::Xsmall,
KeywordSize::Small => structs::StyleFontSize::Small,
KeywordSize::Medium => structs::StyleFontSize::Medium,
KeywordSize::Large => structs::StyleFontSize::Large,
KeywordSize::XLarge => structs::StyleFontSize::Xxlarge,
KeywordSize::XXLarge => structs::StyleFontSize::Xxlarge,
KeywordSize::XXXLarge => structs::StyleFontSize::Xxxlarge,
};
self.gecko.mFontSizeFactor = info.factor;
self.gecko.mFontSizeOffset = info.offset.to_i32_au();
} 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.mFontSizeOffset = 0;
}
@ -972,16 +974,16 @@ fn static_assert() {
pub fn clone_font_size(&self) -> FontSize {
use crate::values::specified::font::{KeywordInfo, KeywordSize};
let size = Au(self.gecko.mSize).into();
let kw = match self.gecko.mFontSizeKeyword as u32 {
structs::NS_STYLE_FONT_SIZE_XXSMALL => KeywordSize::XXSmall,
structs::NS_STYLE_FONT_SIZE_XSMALL => KeywordSize::XSmall,
structs::NS_STYLE_FONT_SIZE_SMALL => KeywordSize::Small,
structs::NS_STYLE_FONT_SIZE_MEDIUM => KeywordSize::Medium,
structs::NS_STYLE_FONT_SIZE_LARGE => KeywordSize::Large,
structs::NS_STYLE_FONT_SIZE_XLARGE => KeywordSize::XLarge,
structs::NS_STYLE_FONT_SIZE_XXLARGE => KeywordSize::XXLarge,
structs::NS_STYLE_FONT_SIZE_XXXLARGE => KeywordSize::XXXLarge,
structs::NS_STYLE_FONT_SIZE_NO_KEYWORD => {
let kw = match self.gecko.mFontSizeKeyword {
structs::StyleFontSize::Xxsmall => KeywordSize::XXSmall,
structs::StyleFontSize::Xsmall => KeywordSize::XSmall,
structs::StyleFontSize::Small => KeywordSize::Small,
structs::StyleFontSize::Medium => KeywordSize::Medium,
structs::StyleFontSize::Large => KeywordSize::Large,
structs::StyleFontSize::Xlarge => KeywordSize::XLarge,
structs::StyleFontSize::Xxlarge => KeywordSize::XXLarge,
structs::StyleFontSize::Xxxlarge => KeywordSize::XXXLarge,
structs::StyleFontSize::NoKeyword => {
return FontSize {
size,
keyword_info: None,
@ -1106,12 +1108,12 @@ fn static_assert() {
#[allow(non_snake_case)]
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)]
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)]
@ -1121,7 +1123,7 @@ fn static_assert() {
#[allow(non_snake_case)]
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") %>

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
/// this (is a similar path to that of PropertyDeclaration).
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
#[derive(Debug)]
#[derive(Debug, MallocSizeOf)]
#[repr(u16)]
pub enum AnimationValue {
% for prop in data.longhands:
@ -420,6 +419,7 @@ impl AnimationValue {
///
/// SERVO ONLY: This doesn't properly handle things like updating 'em' units
/// when animated font-size.
#[cfg(feature = "servo")]
pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) {
match self {
% for prop in data.longhands:
@ -439,6 +439,11 @@ impl AnimationValue {
% 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, ()> {

View file

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

View file

@ -114,6 +114,17 @@ ${helpers.single_keyword(
animation_value_type="discrete",
servo_restyle_damage="reflow",
)}
${helpers.predefined_type(
"justify-tracks",
"JustifyTracks",
"specified::JustifyTracks::default()",
engines="gecko",
gecko_pref="layout.css.grid-template-masonry-value.enabled",
animation_value_type="discrete",
servo_restyle_damage="reflow",
spec="https://github.com/w3c/csswg-drafts/issues/4650",
)}
% endif
% if engine in ["servo-2013", "servo-2020"]:
@ -151,6 +162,17 @@ ${helpers.single_keyword(
servo_restyle_damage="reflow",
)}
${helpers.predefined_type(
"align-tracks",
"AlignTracks",
"specified::AlignTracks::default()",
engines="gecko",
gecko_pref="layout.css.grid-template-masonry-value.enabled",
animation_value_type="discrete",
servo_restyle_damage="reflow",
spec="https://github.com/w3c/csswg-drafts/issues/4650",
)}
${helpers.predefined_type(
"align-items",
"AlignItems",
@ -372,6 +394,16 @@ ${helpers.predefined_type(
% endfor
${helpers.predefined_type(
"masonry-auto-flow",
"MasonryAutoFlow",
"computed::MasonryAutoFlow::initial()",
engines="gecko",
gecko_pref="layout.css.grid-template-masonry-value.enabled",
animation_value_type="discrete",
spec="https://github.com/w3c/csswg-drafts/issues/4650",
)}
${helpers.predefined_type(
"grid-auto-flow",
"GridAutoFlow",
@ -415,17 +447,13 @@ ${helpers.predefined_type(
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(
"aspect-ratio",
"Number",
"computed::Number::zero()",
"AspectRatio",
"computed::AspectRatio::auto()",
engines="gecko servo-2013",
animation_value_type="ComputedValue",
spec="Internal, for now",
enabled_in="",
animation_value_type="discrete",
spec="https://drafts.csswg.org/css-sizing-4/#aspect-ratio",
gecko_pref="layout.css.aspect-ratio.enabled",
servo_restyle_damage="reflow",
)}

View file

@ -278,8 +278,7 @@ impl SelectorMap<Rule> {
}
impl<T: SelectorMapEntry> SelectorMap<T> {
/// Inserts into the correct hash, trying id, class, localname and
/// namespace.
/// Inserts an entry into the correct bucket(s).
pub fn insert(
&mut self,
entry: T,
@ -287,46 +286,91 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
) -> Result<(), FailedAllocationError> {
self.count += 1;
let vector = match find_bucket(entry.selector()) {
Bucket::Root => &mut self.root,
Bucket::ID(id) => self
.id_hash
.try_entry(id.clone(), quirks_mode)?
.or_insert_with(SmallVec::new),
Bucket::Class(class) => self
.class_hash
.try_entry(class.clone(), quirks_mode)?
.or_insert_with(SmallVec::new),
Bucket::LocalName { name, lower_name } => {
// If the local name in the selector isn't lowercase, insert it
// into the rule hash twice. This means that, during lookup, we
// can always find the rules based on the local name of the
// element, regardless of whether it's an html element in an
// html document (in which case we match against lower_name) or
// not (in which case we match against name).
//
// In the case of a non-html-element-in-html-document with a
// lowercase localname and a non-lowercase selector, the
// rulehash lookup may produce superfluous selectors, but the
// subsequent selector matching work will filter them out.
if name != lower_name {
self.local_name_hash
.try_entry(lower_name.clone())?
.or_insert_with(SmallVec::new)
.try_push(entry.clone())?;
// NOTE(emilio): It'd be nice for this to be a separate function, but
// then the compiler can't reason about the lifetime dependency between
// `entry` and `bucket`, and would force us to clone the rule in the
// common path.
macro_rules! insert_into_bucket {
($entry:ident, $bucket:expr) => {{
match $bucket {
Bucket::Root => &mut self.root,
Bucket::ID(id) => self
.id_hash
.try_entry(id.clone(), quirks_mode)?
.or_insert_with(SmallVec::new),
Bucket::Class(class) => self
.class_hash
.try_entry(class.clone(), quirks_mode)?
.or_insert_with(SmallVec::new),
Bucket::LocalName { name, lower_name } => {
// If the local name in the selector isn't lowercase,
// insert it into the rule hash twice. This means that,
// during lookup, we can always find the rules based on
// the local name of the element, regardless of whether
// it's an html element in an html document (in which
// case we match against lower_name) or not (in which
// case we match against name).
//
// In the case of a non-html-element-in-html-document
// with a lowercase localname and a non-lowercase
// selector, the rulehash lookup may produce superfluous
// selectors, but the subsequent selector matching work
// will filter them out.
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_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,
.try_push($entry)?;
}};
}
let bucket = {
let mut disjoint_buckets = SmallVec::new();
let bucket = find_bucket(entry.selector(), &mut disjoint_buckets);
// 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
@ -457,18 +501,43 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
}
enum Bucket<'a> {
Root,
ID(&'a Atom),
Class(&'a Atom),
Universal,
Namespace(&'a Namespace),
LocalName {
name: &'a LocalName,
lower_name: &'a LocalName,
},
Namespace(&'a Namespace),
Universal,
Class(&'a Atom),
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 {
Component::Root => Bucket::Root,
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
// match the slotted <span>.
Component::Slotted(ref selector) => find_bucket(selector.iter()),
Component::Host(Some(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(), disjoint_buckets),
Component::Is(ref list) | Component::Where(ref list) => {
if list.len() == 1 {
find_bucket(list[0].iter())
find_bucket(list[0].iter(), disjoint_buckets)
} else {
// TODO(emilio): We should handle the multiple element case by
// inserting multiple entries in the map.
for selector in &**list {
let bucket = find_bucket(selector.iter(), disjoint_buckets);
disjoint_buckets.push(bucket);
}
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
/// bucket for it.
///
/// It also populates disjoint_buckets with dependencies from nested selectors
/// with any semantics like :is() and :where().
#[inline(always)]
fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
fn find_bucket<'a>(
mut iter: SelectorIter<'a, SelectorImpl>,
disjoint_buckets: &mut DisjointBuckets<'a>,
) -> Bucket<'a> {
let mut current_bucket = Bucket::Universal;
loop {
// We basically want to find the most specific bucket,
// where:
//
// root > id > class > local name > namespace > universal.
//
for ss in &mut iter {
let new_bucket = specific_bucket_for(ss);
match new_bucket {
Bucket::Root => return 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 => {},
let new_bucket = specific_bucket_for(ss, disjoint_buckets);
if new_bucket.more_specific_than(&current_bucket) {
current_bucket = new_bucket;
}
}
@ -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.

View file

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

View file

@ -15,7 +15,6 @@ use std::cell::UnsafeCell;
use std::fmt;
#[cfg(feature = "servo")]
use std::mem;
use std::mem::ManuallyDrop;
#[cfg(feature = "gecko")]
use std::ptr;
use to_shmem::{SharedMemoryBuilder, ToShmem};
@ -258,20 +257,20 @@ impl<T> Locked<T> {
#[cfg(feature = "gecko")]
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();
ManuallyDrop::new(Locked {
Ok(ManuallyDrop::new(Locked {
shared_lock: SharedRwLock::read_only(),
data: UnsafeCell::new(ManuallyDrop::into_inner(
self.read_with(&guard).to_shmem(builder),
self.read_with(&guard).to_shmem(builder)?,
)),
})
}))
}
}
#[cfg(feature = "servo")]
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")
}
}

View file

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

View file

@ -15,9 +15,8 @@ use crate::stylesheets::{CssRule, Origin, StylesheetInDocument};
use crate::values::CssUrl;
use cssparser::SourceLocation;
use std::fmt::{self, Write};
use std::mem::ManuallyDrop;
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
/// GeckoStyleSheet. So we use this placeholder instead.
@ -184,8 +183,10 @@ pub struct ImportRule {
}
impl ToShmem for ImportRule {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
panic!("ToShmem failed for ImportRule: cannot handle imported style sheets")
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
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 style_traits::ParsingMode;
#[cfg(feature = "gecko")]
use to_shmem::{SharedMemoryBuilder, ToShmem};
use to_shmem::{self, SharedMemoryBuilder, ToShmem};
pub use self::counter_style_rule::CounterStyleRule;
pub use self::document_rule::DocumentRule;
@ -117,20 +117,25 @@ impl Drop for UrlExtraData {
#[cfg(feature = "gecko")]
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 {
let shared_extra_datas = unsafe { &structs::URLExtraData_sShared };
let self_ptr = self.as_ref() as *const _ as *mut _;
let sheet_id = shared_extra_datas
.iter()
.position(|r| r.mRawPtr == self_ptr)
.expect(
"ToShmem failed for UrlExtraData: expected sheet's URLExtraData to be in \
URLExtraData::sShared",
);
ManuallyDrop::new(UrlExtraData((sheet_id << 1) | 1))
.position(|r| r.mRawPtr == self_ptr);
let sheet_id = match sheet_id {
Some(id) => id,
None => {
return Err(String::from(
"ToShmem failed for UrlExtraData: expected sheet's URLExtraData to be in \
URLExtraData::sShared",
));
},
};
Ok(ManuallyDrop::new(UrlExtraData((sheet_id << 1) | 1)))
} else {
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;
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();
// 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
// reversing this as it shouldn't be slow anymore, and should avoid
// generating two instantiations of apply_declarations.
properties::apply_declarations::<E, _, _>(
properties::apply_declarations::<E, _>(
&self.device,
/* pseudo = */ None,
self.rule_tree.root(),
guards,
iter_declarations,
block
.declaration_importance_iter()
.map(|(declaration, _)| (declaration, Origin::Author)),
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
/// range `[0.0, 1.0]`.
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
#[derive(Clone, Copy, Debug, PartialEq, ToAnimatedZero)]
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)]
pub struct RGBA {
/// The red component.
pub red: f32,

View file

@ -10,7 +10,8 @@ use crate::values::computed::{Context, ToComputedValue};
use crate::values::specified;
pub use super::specified::{
AlignContent, AlignItems, ContentDistribution, JustifyContent, SelfAlignment,
AlignContent, AlignItems, AlignTracks, ContentDistribution, JustifyContent, JustifyTracks,
SelfAlignment,
};
pub use super::specified::{AlignSelf, JustifySelf};
@ -35,7 +36,7 @@ pub use super::specified::{AlignSelf, JustifySelf};
/// sucks :(.
///
/// 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)]
pub struct ComputedJustifyItems {
/// 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 style_traits::{CssWriter, ParseError, ToCss};
#[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::specified::font::{FontSynthesis, MozScriptSizeMultiplier};
@ -466,19 +466,20 @@ pub enum FontFamilyList {
#[cfg(feature = "gecko")]
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
// style sheets are ones with a single generic entry.
ManuallyDrop::new(match *self {
Ok(ManuallyDrop::new(match *self {
FontFamilyList::SharedFontList(ref r) => {
assert!(
r.mNames.len() == 1 && r.mNames[0].mName.mRawPtr.is_null(),
"ToShmem failed for FontFamilyList: cannot handle non-generic families",
);
if !(r.mNames.len() == 1 && r.mNames[0].mName.mRawPtr.is_null()) {
return Err(String::from(
"ToShmem failed for FontFamilyList: cannot handle non-generic families",
));
}
FontFamilyList::Generic(r.mNames[0].mGeneric)
},
FontFamilyList::Generic(t) => FontFamilyList::Generic(t),
})
}))
}
}

View file

@ -21,7 +21,7 @@ use crate::media_queries::Device;
use crate::properties;
use crate::properties::{ComputedValues, LonghandId, StyleBuilder};
use crate::rule_cache::RuleCacheConditions;
use crate::{ArcSlice, Atom};
use crate::{ArcSlice, Atom, One};
use euclid::default::Size2D;
use servo_arc::Arc;
use std::cell::RefCell;
@ -29,7 +29,10 @@ use std::cmp;
use std::f32;
#[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")]
pub use self::align::{AlignSelf, JustifySelf};
pub use self::angle::Angle;
@ -68,7 +71,10 @@ pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle;
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::resolution::Resolution;
pub use self::svg::MozContextProperties;
@ -223,8 +229,8 @@ impl<'a> Context<'a> {
pub fn maybe_zoom_text(&self, size: CSSPixelLength) -> CSSPixelLength {
// We disable zoom for <svg:text> by unsetting the
// -x-text-zoom property, which leads to a false value
// in mAllowZoom
if self.style().get_font().gecko.mAllowZoom {
// in mAllowZoomAndMinSize
if self.style().get_font().gecko.mAllowZoomAndMinSize {
self.device().zoom_text(Au::from(size)).into()
} else {
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
pub type ZeroToOneNumber = ZeroToOne<CSSFloat>;

View file

@ -7,13 +7,16 @@
//!
//! [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::PositionComponent as GenericPositionComponent;
use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
use crate::values::generics::position::Ratio as GenericRatio;
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 std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
@ -68,3 +71,25 @@ impl GenericPositionComponent for LengthPercentage {
/// A computed value for the `z-index` property.
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,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,

View file

@ -337,7 +337,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
($slot:expr) => {{
let dummy = Self::MinMax(Default::default(), MinMaxOp::Max);
let result = mem::replace($slot, dummy);
let _ = mem::replace(self, result);
*self = result;
}};
}
match *self {
@ -464,7 +464,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
replace_self_with!(&mut children[0]);
} else {
// 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) => {

View file

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

View file

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

View file

@ -714,7 +714,7 @@ impl Parse for LineNameList {
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 {
names: line_names.into(),
@ -733,7 +733,7 @@ impl ToCss for LineNameList {
let fill_start = self.fill_start;
let fill_len = self.fill_len;
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,")?;
}
@ -748,7 +748,7 @@ impl ToCss for LineNameList {
}
dest.write_str("]")?;
if i == fill_start + fill_len - 1 {
if fill_len > 0 && i == fill_start + fill_len - 1 {
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.
#[animation(error)]
Subgrid(Box<LineNameList>),
/// `masonry` value.
/// https://github.com/w3c/csswg-drafts/issues/4650
Masonry,
}
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.
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,

View file

@ -5,6 +5,10 @@
//! Generic types for CSS handling of specified and computed values of
//! [`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).
#[derive(
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 }
}
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>.
pub fn is_baseline_position(&self) -> bool {
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.
///
/// <https://drafts.csswg.org/css-align/#propdef-justify-content>
@ -329,18 +349,35 @@ impl SpecifiedValueInfo for JustifyContent {
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 From<u16> for JustifyContent {
fn from(bits: u16) -> Self {
JustifyContent(ContentDistribution::from_bits(bits))
}
}
#[cfg(feature = "gecko")]
impl From<JustifyContent> for u16 {
fn from(v: JustifyContent) -> u16 {
v.0.as_bits()
impl Parse for JustifyTracks {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let values = input.parse_comma_separated(|input| JustifyContent::parse(context, input))?;
Ok(JustifyTracks(values.into()))
}
}

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 {
fn parse<'i, 't>(
context: &ParserContext,

View file

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

View file

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

View file

@ -12,13 +12,14 @@ use crate::selector_map::PrecomputedHashMap;
use crate::str::HTML_SPACE_CHARACTERS;
use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
use crate::values::computed::{Context, Percentage, ToComputedValue};
use crate::values::generics::position::AspectRatio as GenericAspectRatio;
use crate::values::generics::position::Position as GenericPosition;
use crate::values::generics::position::PositionComponent as GenericPositionComponent;
use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
use crate::values::generics::position::Ratio as GenericRatio;
use crate::values::generics::position::ZIndex as GenericZIndex;
use crate::values::specified::{AllowQuirks, Integer, LengthPercentage};
use crate::Atom;
use crate::Zero;
use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
use crate::{Atom, One, Zero};
use cssparser::Parser;
use selectors::parser::SelectorParseErrorKind;
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 {
/// [ row | column ] || dense
fn parse<'i, 't>(
@ -711,3 +877,74 @@ impl GridTemplateAreas {
/// A specified value for the `z-index` property.
pub type ZIndex = GenericZIndex<Integer>;
/// A specified value for the `aspect-ratio` property.
pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
impl Parse for AspectRatio {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
use crate::values::generics::position::PreferredRatio;
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,
MallocSizeOf,
PartialEq,
Parse,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
@ -144,30 +145,6 @@ pub enum TextOverflowSide {
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)]
/// text-overflow. Specifies rendering when inline content overflows its line box edge.
pub struct TextOverflow {

View file

@ -15,7 +15,14 @@ pub struct ParseVariantAttrs {
pub condition: Option<Path>,
}
#[darling(attributes(parse), default)]
#[derive(Default, FromField)]
pub struct ParseFieldAttrs {
field_bound: bool,
}
fn parse_non_keyword_variant(
where_clause: &mut Option<syn::WhereClause>,
name: &syn::Ident,
variant: &VariantInfo,
variant_attrs: &CssVariantAttrs,
@ -32,7 +39,14 @@ fn parse_non_keyword_variant(
"We only support deriving parse for simple variants"
);
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 {
quote! {
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 {
{
let mut where_clause = input.generics.where_clause.take();
for param in input.generics.type_params() {
cg::add_predicate(
&mut where_clause,
parse_quote!(#param: crate::parser::Parse),
);
}
input.generics.where_clause = where_clause;
let mut where_clause = input.generics.where_clause.take();
for param in input.generics.type_params() {
cg::add_predicate(
&mut where_clause,
parse_quote!(#param: crate::parser::Parse),
);
}
let name = &input.ident;
@ -88,12 +99,13 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
let mut effective_variants = 0;
for variant in s.variants().iter() {
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 {
continue;
}
effective_variants += 1;
let parse_attrs = cg::parse_variant_attrs_from_ast::<ParseVariantAttrs>(&variant.ast());
saw_condition |= parse_attrs.condition.is_some();
if !variant.bindings().is_empty() {
@ -142,8 +154,14 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
let mut parse_non_keywords = quote! {};
for (i, (variant, css_attrs, parse_attrs)) in non_keywords.iter().enumerate() {
let skip_try = !has_keywords && i == non_keywords.len() - 1;
let parse_variant =
parse_non_keyword_variant(name, variant, css_attrs, parse_attrs, skip_try);
let parse_variant = parse_non_keyword_variant(
&mut where_clause,
name,
variant,
css_attrs,
parse_attrs,
skip_try,
);
parse_non_keywords.extend(parse_variant);
}
@ -171,6 +189,9 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
quote! { Self::parse(input) }
};
let has_non_keywords = !non_keywords.is_empty();
input.generics.where_clause = where_clause;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let parse_trait_impl = quote! {
@ -189,7 +210,7 @@ pub fn derive(mut input: DeriveInput) -> TokenStream {
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
// 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
/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
/// locality reasons. Using `OpaqueNode` enforces this invariant.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf, Deserialize, Serialize))]
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
pub struct OpaqueNode(pub usize);
impl OpaqueNode {

View file

@ -13,7 +13,7 @@ use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::ptr::NonNull;
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
/// understand.
@ -159,10 +159,10 @@ impl<T: MallocSizeOf + Sized> MallocSizeOf 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 {
let dest = to_shmem::to_shmem_slice(self.iter(), builder);
mem::ManuallyDrop::new(Self::from(Box::from_raw(dest)))
let dest = to_shmem::to_shmem_slice(self.iter(), builder)?;
Ok(mem::ManuallyDrop::new(Self::from(Box::from_raw(dest))))
}
}
}

View file

@ -42,6 +42,11 @@ use std::slice;
use std::str;
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
// functions on `Layout` once they have stabilized:
//
@ -114,13 +119,13 @@ impl SharedMemoryBuilder {
/// a shared memory buffer by calling ToShmem::to_shmem on it.
///
/// Panics if there is insufficient space in the buffer.
pub fn write<T: ToShmem>(&mut self, value: &T) -> *mut T {
pub fn write<T: ToShmem>(&mut self, value: &T) -> std::result::Result<*mut T, String> {
// Reserve space for the value.
let dest: *mut T = self.alloc_value();
// Make a clone of the value with all of its heap allocations
// placed in the shared memory buffer.
let value = value.to_shmem(self);
let value = value.to_shmem(self)?;
unsafe {
// Copy the value into the buffer.
@ -128,7 +133,7 @@ impl SharedMemoryBuilder {
}
// Return a pointer to the shared value.
dest
Ok(dest)
}
/// 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
/// 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]
@ -201,8 +209,8 @@ macro_rules! impl_trivial_to_shmem {
fn to_shmem(
&self,
_builder: &mut $crate::SharedMemoryBuilder,
) -> ::std::mem::ManuallyDrop<Self> {
::std::mem::ManuallyDrop::new(*self)
) -> $crate::Result<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<T> ToShmem for PhantomData<T> {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
ManuallyDrop::new(*self)
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
Ok(ManuallyDrop::new(*self))
}
}
impl<T: ToShmem> ToShmem for Range<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
ManuallyDrop::new(Range {
start: ManuallyDrop::into_inner(self.start.to_shmem(builder)),
end: ManuallyDrop::into_inner(self.end.to_shmem(builder)),
})
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
Ok(ManuallyDrop::new(Range {
start: ManuallyDrop::into_inner(self.start.to_shmem(builder)?),
end: ManuallyDrop::into_inner(self.end.to_shmem(builder)?),
}))
}
}
impl ToShmem for cssparser::UnicodeRange {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
ManuallyDrop::new(cssparser::UnicodeRange {
fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> Result<Self> {
Ok(ManuallyDrop::new(cssparser::UnicodeRange {
start: self.start,
end: self.end,
})
}))
}
}
impl<T: ToShmem, U: ToShmem> ToShmem for (T, U) {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
ManuallyDrop::new((
ManuallyDrop::into_inner(self.0.to_shmem(builder)),
ManuallyDrop::into_inner(self.1.to_shmem(builder)),
))
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
Ok(ManuallyDrop::new((
ManuallyDrop::into_inner(self.0.to_shmem(builder)?),
ManuallyDrop::into_inner(self.1.to_shmem(builder)?),
)))
}
}
impl<T: ToShmem> ToShmem for Wrapping<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(self.0.to_shmem(builder))))
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
Ok(ManuallyDrop::new(Wrapping(ManuallyDrop::into_inner(
self.0.to_shmem(builder)?,
))))
}
}
impl<T: ToShmem> ToShmem for Box<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// Reserve space for the boxed value.
let dest: *mut T = builder.alloc_value();
// Make a clone of the boxed value with all of its heap allocations
// placed in the shared memory buffer.
let value = (**self).to_shmem(builder);
let value = (**self).to_shmem(builder)?;
unsafe {
// Copy the value into the buffer.
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,
dest: *mut T,
builder: &mut SharedMemoryBuilder,
) -> *mut [T]
) -> std::result::Result<*mut [T], String>
where
T: 'a + ToShmem,
I: ExactSizeIterator<Item = &'a T>,
@ -303,15 +313,18 @@ where
// Make a clone of each element from the iterator with its own heap
// allocations placed in the buffer, and copy that clone into the buffer.
for (src, dest) in src.zip(dest.iter_mut()) {
ptr::write(dest, ManuallyDrop::into_inner(src.to_shmem(builder)));
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
/// 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
T: 'a + ToShmem,
I: ExactSizeIterator<Item = &'a T>,
@ -321,16 +334,16 @@ where
}
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 {
let dest = to_shmem_slice(self.iter(), builder);
ManuallyDrop::new(Box::from_raw(dest))
let dest = to_shmem_slice(self.iter(), builder)?;
Ok(ManuallyDrop::new(Box::from_raw(dest)))
}
}
}
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
// need to handle such big ThinBoxedSlices.
assert!(
@ -340,14 +353,14 @@ impl<T: ToShmem> ToShmem for ThinBoxedSlice<T> {
);
unsafe {
let dest = to_shmem_slice(self.iter(), builder);
ManuallyDrop::new(ThinBoxedSlice::from_raw(dest))
let dest = to_shmem_slice(self.iter(), builder)?;
Ok(ManuallyDrop::new(ThinBoxedSlice::from_raw(dest)))
}
}
}
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.
let dest: *mut u8 = builder.alloc_array(self.len());
@ -355,15 +368,15 @@ impl ToShmem for Box<str> {
// Copy the value into the buffer.
ptr::copy(self.as_ptr(), dest, self.len());
ManuallyDrop::new(Box::from_raw(str::from_utf8_unchecked_mut(
slice::from_raw_parts_mut(dest, self.len()),
Ok(ManuallyDrop::new(Box::from_raw(
str::from_utf8_unchecked_mut(slice::from_raw_parts_mut(dest, self.len())),
)))
}
}
}
impl ToShmem for String {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
// Reserve space for the string bytes.
let dest: *mut u8 = builder.alloc_array(self.len());
@ -371,13 +384,17 @@ impl ToShmem for String {
// Copy the value into the buffer.
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 {
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();
// Reserve space for the string bytes.
@ -387,53 +404,55 @@ impl ToShmem for CString {
// Copy the value into the buffer.
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> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
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());
ManuallyDrop::new(dest_vec)
Ok(ManuallyDrop::new(dest_vec))
}
}
}
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 {
if self.spilled() {
// Place the items in a separate allocation in the shared memory
// buffer.
let dest = to_shmem_slice(self.iter(), builder) as *mut T;
let dest = to_shmem_slice(self.iter(), builder)? as *mut T;
SmallVec::from_raw_parts(dest, self.len(), self.len())
} else {
// Place the items inline.
let mut s = SmallVec::new();
to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder);
to_shmem_slice_ptr(self.iter(), s.as_mut_ptr(), builder)?;
s.set_len(self.len());
s
}
};
ManuallyDrop::new(dest_vec)
Ok(ManuallyDrop::new(dest_vec))
}
}
impl<T: ToShmem> ToShmem for Option<T> {
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> ManuallyDrop<Self> {
ManuallyDrop::new(
self.as_ref()
.map(|v| ManuallyDrop::into_inner(v.to_shmem(builder))),
)
fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result<Self> {
let v = match self {
Some(v) => Some(ManuallyDrop::into_inner(v.to_shmem(builder)?)),
None => None,
};
Ok(ManuallyDrop::new(v))
}
}
impl<T: '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
// don't expect. Those we expect are those noted by calling
// 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
// 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
// ArcInner in the shared memory buffer.
@ -466,13 +485,13 @@ impl<T: 'static + ToShmem> ToShmem for Arc<T> {
#[cfg(debug_assertions)]
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> {
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,
// so don't support them for now.
#[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
// their heap allocations placed in the shared memory buffer.
let header = self.header.header.to_shmem(builder);
let values: Vec<ManuallyDrop<T>> = self.slice.iter().map(|v| v.to_shmem(builder)).collect();
let header = self.header.header.to_shmem(builder)?;
let mut values = Vec::with_capacity(self.slice.len());
for v in self.slice.iter() {
values.push(v.to_shmem(builder)?);
}
// Create a new ThinArc with the shared value and have it place
// its ArcInner in the shared memory buffer.
@ -499,13 +521,13 @@ impl<H: 'static + ToShmem, T: 'static + ToShmem> ToShmem for ThinArc<H, T> {
#[cfg(debug_assertions)]
builder.shared_values.insert(self.heap_ptr());
ManuallyDrop::new(static_arc)
Ok(ManuallyDrop::new(static_arc))
}
}
}
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() {
InternalStorage::Spilled(vs) => {
// Reserve space for the boxed slice values.
@ -524,13 +546,15 @@ impl ToShmem for SmallBitVec {
},
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")]
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
// string_cache could expose the implementation detail of static atoms
// 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;
let match_body = cg::fmap_match(&input, BindStyle::Ref, |binding| {
quote! {
::std::mem::ManuallyDrop::into_inner(
::to_shmem::ToShmem::to_shmem(#binding, builder)
)
}
});
// Do all of the `to_shmem()?` calls before the `ManuallyDrop::into_inner()`
// calls, so that we don't drop a value in the shared memory buffer if one
// of the `to_shmem`s fails.
let match_body = cg::fmap2_match(
&input,
BindStyle::Ref,
|binding| {
quote! {
::to_shmem::ToShmem::to_shmem(#binding, builder)?
}
},
|binding| {
Some(quote! {
::std::mem::ManuallyDrop::into_inner(#binding)
})
},
);
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
@ -44,12 +54,12 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream {
fn to_shmem(
&self,
builder: &mut ::to_shmem::SharedMemoryBuilder,
) -> ::std::mem::ManuallyDrop<Self> {
::std::mem::ManuallyDrop::new(
) -> ::to_shmem::Result<Self> {
Ok(::std::mem::ManuallyDrop::new(
match *self {
#match_body
}
)
))
}
}
}

View file

@ -20,7 +20,6 @@ pub use crate::origin::{ImmutableOrigin, MutableOrigin, OpaqueOrigin};
use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::Hasher;
use std::mem::ManuallyDrop;
use std::net::IpAddr;
use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
use std::path::Path;
@ -34,7 +33,7 @@ pub use url::Host;
pub struct ServoUrl(#[ignore_malloc_size_of = "Arc"] Arc<Url>);
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")
}
}

View file

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

View file

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