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

style: Sync changes from mozilla-central.

See each individual commit for details.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/21735)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2018-09-18 06:02:00 -04:00 committed by GitHub
commit bdf450336e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1212 additions and 923 deletions

View file

@ -685,7 +685,9 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> {
type Impl = SelectorImpl;
fn opaque(&self) -> ::selectors::OpaqueElement {
::selectors::OpaqueElement::new(self.as_node().opaque().0 as *const ())
::selectors::OpaqueElement::new(unsafe {
&*(self.as_node().opaque().0 as *const ())
})
}
fn parent_element(&self) -> Option<ServoLayoutElement<'le>> {
@ -1258,7 +1260,9 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> {
type Impl = SelectorImpl;
fn opaque(&self) -> ::selectors::OpaqueElement {
::selectors::OpaqueElement::new(self.as_node().opaque().0 as *const ())
::selectors::OpaqueElement::new(unsafe {
&*(self.as_node().opaque().0 as *const ())
})
}
fn parent_element(&self) -> Option<Self> {

View file

@ -2616,8 +2616,11 @@ impl VirtualMethods for Element {
impl<'a> SelectorsElement for DomRoot<Element> {
type Impl = SelectorImpl;
#[allow(unsafe_code)]
fn opaque(&self) -> ::selectors::OpaqueElement {
::selectors::OpaqueElement::new(self.reflector().get_jsobject().get())
::selectors::OpaqueElement::new(unsafe {
&*self.reflector().get_jsobject().get()
})
}
fn parent_element(&self) -> Option<DomRoot<Element>> {

View file

@ -8,18 +8,22 @@
use attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
use matching::{ElementSelectorFlags, MatchingContext};
use parser::SelectorImpl;
use servo_arc::NonZeroPtrMut;
use std::fmt::Debug;
use std::ptr::NonNull;
/// Opaque representation of an Element, for identity comparisons. We use
/// NonZeroPtrMut to get the NonZero optimization.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct OpaqueElement(NonZeroPtrMut<()>);
pub struct OpaqueElement(NonNull<()>);
unsafe impl Send for OpaqueElement {}
impl OpaqueElement {
/// Creates a new OpaqueElement from an arbitrarily-typed pointer.
pub fn new<T>(ptr: *const T) -> Self {
OpaqueElement(NonZeroPtrMut::new(ptr as *const () as *mut ()))
pub fn new<T>(ptr: &T) -> Self {
unsafe {
OpaqueElement(NonNull::new_unchecked(ptr as *const T as *const () as *mut ()))
}
}
}

View file

@ -19,7 +19,7 @@
//!
//! [1]: https://bugzilla.mozilla.org/show_bug.cgi?id=1360883
// The semantics of Arc are alread documented in the Rust docs, so we don't
// The semantics of `Arc` are alread documented in the Rust docs, so we don't
// duplicate those here.
#![allow(missing_docs)]
@ -74,72 +74,41 @@ macro_rules! offset_of {
/// necessarily) at _exactly_ `MAX_REFCOUNT + 1` references.
const MAX_REFCOUNT: usize = (isize::MAX) as usize;
/// Wrapper type for pointers to get the non-zero optimization. When
/// NonZero/Shared/Unique are stabilized, we should just use Shared
/// here to get the same effect. Gankro is working on this in [1].
/// An atomically reference counted shared pointer
///
/// It's unfortunate that this needs to infect all the caller types
/// with 'static. It would be nice to just use a &() and a PhantomData<T>
/// instead, but then the compiler can't determine whether the &() should
/// be thin or fat (which depends on whether or not T is sized). Given
/// that this is all a temporary hack, this restriction is fine for now.
/// See the documentation for [`Arc`] in the standard library. Unlike the
/// standard library `Arc`, this `Arc` does not support weak reference counting.
///
/// [1]: https://github.com/rust-lang/rust/issues/27730
// FIXME: remove this and use std::ptr::NonNull when Firefox requires Rust 1.25+
pub struct NonZeroPtrMut<T: ?Sized + 'static>(&'static mut T);
impl<T: ?Sized> NonZeroPtrMut<T> {
pub fn new(ptr: *mut T) -> Self {
assert!(!(ptr as *mut u8).is_null());
NonZeroPtrMut(unsafe { mem::transmute(ptr) })
}
pub fn ptr(&self) -> *mut T {
self.0 as *const T as *mut T
}
}
impl<T: ?Sized + 'static> Clone for NonZeroPtrMut<T> {
fn clone(&self) -> Self {
NonZeroPtrMut::new(self.ptr())
}
}
impl<T: ?Sized + 'static> fmt::Pointer for NonZeroPtrMut<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr(), f)
}
}
impl<T: ?Sized + 'static> fmt::Debug for NonZeroPtrMut<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Pointer>::fmt(self, f)
}
}
impl<T: ?Sized + 'static> PartialEq for NonZeroPtrMut<T> {
fn eq(&self, other: &Self) -> bool {
self.ptr() == other.ptr()
}
}
impl<T: ?Sized + 'static> Eq for NonZeroPtrMut<T> {}
impl<T: Sized + 'static> Hash for NonZeroPtrMut<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ptr().hash(state)
}
}
/// [`Arc`]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html
#[repr(C)]
pub struct Arc<T: ?Sized + 'static> {
p: NonZeroPtrMut<ArcInner<T>>,
pub struct Arc<T: ?Sized> {
p: ptr::NonNull<ArcInner<T>>,
}
/// An Arc that is known to be uniquely owned
/// An `Arc` that is known to be uniquely owned
///
/// This lets us build arcs that we can mutate before
/// freezing, without needing to change the allocation
pub struct UniqueArc<T: ?Sized + 'static>(Arc<T>);
/// When `Arc`s are constructed, they are known to be
/// uniquely owned. In such a case it is safe to mutate
/// the contents of the `Arc`. Normally, one would just handle
/// this by mutating the data on the stack before allocating the
/// `Arc`, however it's possible the data is large or unsized
/// and you need to heap-allocate it earlier in such a way
/// that it can be freely converted into a regular `Arc` once you're
/// done.
///
/// `UniqueArc` exists for this purpose, when constructed it performs
/// the same allocations necessary for an `Arc`, however it allows mutable access.
/// Once the mutation is finished, you can call `.shareable()` and get a regular `Arc`
/// out of it.
///
/// ```rust
/// # use servo_arc::UniqueArc;
/// let data = [1, 2, 3, 4, 5];
/// let mut x = UniqueArc::new(data);
/// x[4] = 7; // mutate!
/// let y = x.shareable(); // y is an Arc<T>
/// ```
pub struct UniqueArc<T: ?Sized>(Arc<T>);
impl<T> UniqueArc<T> {
#[inline]
@ -149,7 +118,7 @@ impl<T> UniqueArc<T> {
}
#[inline]
/// Convert to a shareable Arc<T> once we're done using it
/// Convert to a shareable Arc<T> once we're done mutating it
pub fn shareable(self) -> Arc<T> {
self.0
}
@ -172,6 +141,7 @@ impl<T> DerefMut for UniqueArc<T> {
unsafe impl<T: ?Sized + Sync + Send> Send for Arc<T> {}
unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}
/// The object allocated by an Arc<T>
#[repr(C)]
struct ArcInner<T: ?Sized> {
count: atomic::AtomicUsize,
@ -182,36 +152,52 @@ unsafe impl<T: ?Sized + Sync + Send> Send for ArcInner<T> {}
unsafe impl<T: ?Sized + Sync + Send> Sync for ArcInner<T> {}
impl<T> Arc<T> {
/// Construct an `Arc<T>`
#[inline]
pub fn new(data: T) -> Self {
let x = Box::new(ArcInner {
count: atomic::AtomicUsize::new(1),
data: data,
});
Arc {
p: NonZeroPtrMut::new(Box::into_raw(x)),
unsafe {
Arc {
p: ptr::NonNull::new_unchecked(Box::into_raw(x)),
}
}
}
/// Convert the Arc<T> to a raw pointer, suitable for use across FFI
///
/// Note: This returns a pointer to the data T, which is offset in the allocation.
///
/// It is recommended to use RawOffsetArc for this.
#[inline]
pub fn into_raw(this: Self) -> *const T {
fn into_raw(this: Self) -> *const T {
let ptr = unsafe { &((*this.ptr()).data) as *const _ };
mem::forget(this);
ptr
}
/// Reconstruct the Arc<T> from a raw pointer obtained from into_raw()
///
/// Note: This raw pointer will be offset in the allocation and must be preceded
/// by the atomic count.
///
/// It is recommended to use RawOffsetArc for this
#[inline]
unsafe fn from_raw(ptr: *const T) -> Self {
// To find the corresponding pointer to the `ArcInner` we need
// to subtract the offset of the `data` field from the pointer.
let ptr = (ptr as *const u8).offset(-offset_of!(ArcInner<T>, data));
Arc {
p: NonZeroPtrMut::new(ptr as *mut ArcInner<T>),
p: ptr::NonNull::new_unchecked(ptr as *mut ArcInner<T>),
}
}
/// Produce a pointer to the data that can be converted back
/// to an arc
/// to an Arc. This is basically an `&Arc<T>`, without the extra indirection.
/// It has the benefits of an `&T` but also knows about the underlying refcount
/// and can be converted into more `Arc<T>`s if necessary.
#[inline]
pub fn borrow_arc<'a>(&'a self) -> ArcBorrow<'a, T> {
ArcBorrow(&**self)
@ -240,7 +226,7 @@ impl<T> Arc<T> {
/// Returns the address on the heap of the Arc itself -- not the T within it -- for memory
/// reporting.
pub fn heap_ptr(&self) -> *const c_void {
self.p.ptr() as *const ArcInner<T> as *const c_void
self.p.as_ptr() as *const ArcInner<T> as *const c_void
}
}
@ -261,13 +247,15 @@ impl<T: ?Sized> Arc<T> {
let _ = Box::from_raw(self.ptr());
}
/// Test pointer equality between the two Arcs, i.e. they must be the _same_
/// allocation
#[inline]
pub fn ptr_eq(this: &Self, other: &Self) -> bool {
this.ptr() == other.ptr()
}
fn ptr(&self) -> *mut ArcInner<T> {
self.p.ptr()
self.p.as_ptr()
}
}
@ -300,8 +288,10 @@ impl<T: ?Sized> Clone for Arc<T> {
process::abort();
}
Arc {
p: NonZeroPtrMut::new(self.ptr()),
unsafe {
Arc {
p: ptr::NonNull::new_unchecked(self.ptr()),
}
}
}
}
@ -316,6 +306,19 @@ impl<T: ?Sized> Deref for Arc<T> {
}
impl<T: Clone> Arc<T> {
/// Makes a mutable reference to the `Arc`, cloning if necessary
///
/// This is functionally equivalent to [`Arc::make_mut`][mm] from the standard library.
///
/// If this `Arc` is uniquely owned, `make_mut()` will provide a mutable
/// reference to the contents. If not, `make_mut()` will create a _new_ `Arc`
/// with a copy of the contents, update `this` to point to it, and provide
/// a mutable reference to its contents.
///
/// This is useful for implementing copy-on-write schemes where you wish to
/// avoid copying things if your `Arc` is not shared.
///
/// [mm]: https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#method.make_mut
#[inline]
pub fn make_mut(this: &mut Self) -> &mut T {
if !this.is_unique() {
@ -335,6 +338,7 @@ impl<T: Clone> Arc<T> {
}
impl<T: ?Sized> Arc<T> {
/// Provides mutable access to the contents _if_ the `Arc` is uniquely owned.
#[inline]
pub fn get_mut(this: &mut Self) -> Option<&mut T> {
if this.is_unique() {
@ -347,6 +351,7 @@ impl<T: ?Sized> Arc<T> {
}
}
/// Whether or not the `Arc` is uniquely owned (is the refcount 1?)
#[inline]
pub fn is_unique(&self) -> bool {
// See the extensive discussion in [1] for why this needs to be Acquire.
@ -402,6 +407,7 @@ impl<T: ?Sized + PartialEq> PartialEq for Arc<T> {
!Self::ptr_eq(self, other) && *(*self) != *(*other)
}
}
impl<T: ?Sized + PartialOrd> PartialOrd for Arc<T> {
fn partial_cmp(&self, other: &Arc<T>) -> Option<Ordering> {
(**self).partial_cmp(&**other)
@ -615,8 +621,10 @@ impl<H, T> Arc<HeaderSlice<H, [T]>> {
size_of::<usize>() * 2,
"The Arc will be fat"
);
Arc {
p: NonZeroPtrMut::new(ptr),
unsafe {
Arc {
p: ptr::NonNull::new_unchecked(ptr),
}
}
}
@ -651,7 +659,22 @@ impl<H> HeaderWithLength<H> {
}
type HeaderSliceWithLength<H, T> = HeaderSlice<HeaderWithLength<H>, T>;
pub struct ThinArc<H: 'static, T: 'static> {
/// A "thin" `Arc` containing dynamically sized data
///
/// This is functionally equivalent to Arc<(H, [T])>
///
/// When you create an `Arc` containing a dynamically sized type
/// like `HeaderSlice<H, [T]>`, the `Arc` is represented on the stack
/// as a "fat pointer", where the length of the slice is stored
/// alongside the `Arc`'s pointer. In some situations you may wish to
/// have a thin pointer instead, perhaps for FFI compatibility
/// or space efficiency.
///
/// `ThinArc` solves this by storing the length in the allocation itself,
/// via `HeaderSliceWithLength`.
#[repr(C)]
pub struct ThinArc<H, T> {
ptr: *mut ArcInner<HeaderSliceWithLength<H, [T; 1]>>,
}
@ -670,7 +693,7 @@ fn thin_to_thick<H, T>(
fake_slice as *mut ArcInner<HeaderSliceWithLength<H, [T]>>
}
impl<H: 'static, T: 'static> ThinArc<H, T> {
impl<H, T> ThinArc<H, T> {
/// Temporarily converts |self| into a bonafide Arc and exposes it to the
/// provided callback. The refcount is not modified.
#[inline]
@ -679,9 +702,11 @@ impl<H: 'static, T: 'static> ThinArc<H, T> {
F: FnOnce(&Arc<HeaderSliceWithLength<H, [T]>>) -> U,
{
// Synthesize transient Arc, which never touches the refcount of the ArcInner.
let transient = NoDrop::new(Arc {
p: NonZeroPtrMut::new(thin_to_thick(self.ptr)),
});
let transient = unsafe {
NoDrop::new(Arc {
p: ptr::NonNull::new_unchecked(thin_to_thick(self.ptr)),
})
};
// Expose the transient Arc to the callback, which may clone it if it wants.
let result = f(&transient);
@ -695,6 +720,16 @@ impl<H: 'static, T: 'static> ThinArc<H, T> {
result
}
/// Creates a `ThinArc` for a HeaderSlice using the given header struct and
/// iterator to generate the slice.
pub fn from_header_and_iter<I>(header: H, items: I) -> Self
where
I: Iterator<Item = T> + ExactSizeIterator,
{
let header = HeaderWithLength::new(header, items.len());
Arc::into_thin(Arc::from_header_and_iter(header, items))
}
/// Returns the address on the heap of the ThinArc itself -- not the T
/// within it -- for memory reporting.
#[inline]
@ -712,22 +747,22 @@ impl<H, T> Deref for ThinArc<H, T> {
}
}
impl<H: 'static, T: 'static> Clone for ThinArc<H, T> {
impl<H, T> Clone for ThinArc<H, T> {
#[inline]
fn clone(&self) -> Self {
ThinArc::with_arc(self, |a| Arc::into_thin(a.clone()))
}
}
impl<H: 'static, T: 'static> Drop for ThinArc<H, T> {
impl<H, T> Drop for ThinArc<H, T> {
#[inline]
fn drop(&mut self) {
let _ = Arc::from_thin(ThinArc { ptr: self.ptr });
}
}
impl<H: 'static, T: 'static> Arc<HeaderSliceWithLength<H, [T]>> {
/// Converts an Arc into a ThinArc. This consumes the Arc, so the refcount
impl<H, T> Arc<HeaderSliceWithLength<H, [T]>> {
/// Converts an `Arc` into a `ThinArc`. This consumes the `Arc`, so the refcount
/// is not modified.
#[inline]
pub fn into_thin(a: Self) -> ThinArc<H, T> {
@ -744,29 +779,31 @@ impl<H: 'static, T: 'static> Arc<HeaderSliceWithLength<H, [T]>> {
}
}
/// Converts a ThinArc into an Arc. This consumes the ThinArc, so the refcount
/// Converts a `ThinArc` into an `Arc`. This consumes the `ThinArc`, so the refcount
/// is not modified.
#[inline]
pub fn from_thin(a: ThinArc<H, T>) -> Self {
let ptr = thin_to_thick(a.ptr);
mem::forget(a);
Arc {
p: NonZeroPtrMut::new(ptr),
unsafe {
Arc {
p: ptr::NonNull::new_unchecked(ptr),
}
}
}
}
impl<H: PartialEq + 'static, T: PartialEq + 'static> PartialEq for ThinArc<H, T> {
impl<H: PartialEq, T: PartialEq> PartialEq for ThinArc<H, T> {
#[inline]
fn eq(&self, other: &ThinArc<H, T>) -> bool {
ThinArc::with_arc(self, |a| ThinArc::with_arc(other, |b| *a == *b))
}
}
impl<H: Eq + 'static, T: Eq + 'static> Eq for ThinArc<H, T> {}
impl<H: Eq, T: Eq> Eq for ThinArc<H, T> {}
/// An Arc, except it holds a pointer to the T instead of to the
/// entire ArcInner.
/// An `Arc`, except it holds a pointer to the T instead of to the
/// entire ArcInner. This struct is FFI-compatible.
///
/// ```text
/// Arc<T> RawOffsetArc<T>
@ -779,31 +816,35 @@ impl<H: Eq + 'static, T: Eq + 'static> Eq for ThinArc<H, T> {}
///
/// This means that this is a direct pointer to
/// its contained data (and can be read from by both C++ and Rust),
/// but we can also convert it to a "regular" Arc<T> by removing the offset
/// but we can also convert it to a "regular" Arc<T> by removing the offset.
///
/// This is very useful if you have an Arc-containing struct shared between Rust and C++,
/// and wish for C++ to be able to read the data behind the `Arc` without incurring
/// an FFI call overhead.
#[derive(Eq)]
#[repr(C)]
pub struct RawOffsetArc<T: 'static> {
ptr: NonZeroPtrMut<T>,
pub struct RawOffsetArc<T> {
ptr: ptr::NonNull<T>,
}
unsafe impl<T: 'static + Sync + Send> Send for RawOffsetArc<T> {}
unsafe impl<T: 'static + Sync + Send> Sync for RawOffsetArc<T> {}
unsafe impl<T: Sync + Send> Send for RawOffsetArc<T> {}
unsafe impl<T: Sync + Send> Sync for RawOffsetArc<T> {}
impl<T: 'static> Deref for RawOffsetArc<T> {
impl<T> Deref for RawOffsetArc<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.ptr.ptr() }
unsafe { &*self.ptr.as_ptr() }
}
}
impl<T: 'static> Clone for RawOffsetArc<T> {
impl<T> Clone for RawOffsetArc<T> {
#[inline]
fn clone(&self) -> Self {
Arc::into_raw_offset(self.clone_arc())
}
}
impl<T: 'static> Drop for RawOffsetArc<T> {
impl<T> Drop for RawOffsetArc<T> {
fn drop(&mut self) {
let _ = Arc::from_raw_offset(RawOffsetArc {
ptr: self.ptr.clone(),
@ -811,7 +852,7 @@ impl<T: 'static> Drop for RawOffsetArc<T> {
}
}
impl<T: fmt::Debug + 'static> fmt::Debug for RawOffsetArc<T> {
impl<T: fmt::Debug> fmt::Debug for RawOffsetArc<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
@ -827,7 +868,7 @@ impl<T: PartialEq> PartialEq for RawOffsetArc<T> {
}
}
impl<T: 'static> RawOffsetArc<T> {
impl<T> RawOffsetArc<T> {
/// Temporarily converts |self| into a bonafide Arc and exposes it to the
/// provided callback. The refcount is not modified.
#[inline]
@ -836,7 +877,7 @@ impl<T: 'static> RawOffsetArc<T> {
F: FnOnce(&Arc<T>) -> U,
{
// Synthesize transient Arc, which never touches the refcount of the ArcInner.
let transient = unsafe { NoDrop::new(Arc::from_raw(self.ptr.ptr())) };
let transient = unsafe { NoDrop::new(Arc::from_raw(self.ptr.as_ptr())) };
// Expose the transient Arc to the callback, which may clone it if it wants.
let result = f(&transient);
@ -852,6 +893,8 @@ impl<T: 'static> RawOffsetArc<T> {
/// If uniquely owned, provide a mutable reference
/// Else create a copy, and mutate that
///
/// This is functionally the same thing as `Arc::make_mut`
#[inline]
pub fn make_mut(&mut self) -> &mut T
where
@ -872,54 +915,57 @@ impl<T: 'static> RawOffsetArc<T> {
}
}
/// Clone it as an Arc
/// Clone it as an `Arc`
#[inline]
pub fn clone_arc(&self) -> Arc<T> {
RawOffsetArc::with_arc(self, |a| a.clone())
}
/// Produce a pointer to the data that can be converted back
/// to an arc
/// to an `Arc`
#[inline]
pub fn borrow_arc<'a>(&'a self) -> ArcBorrow<'a, T> {
ArcBorrow(&**self)
}
}
impl<T: 'static> Arc<T> {
/// Converts an Arc into a RawOffsetArc. This consumes the Arc, so the refcount
impl<T> Arc<T> {
/// Converts an `Arc` into a `RawOffsetArc`. This consumes the `Arc`, so the refcount
/// is not modified.
#[inline]
pub fn into_raw_offset(a: Self) -> RawOffsetArc<T> {
RawOffsetArc {
ptr: NonZeroPtrMut::new(Arc::into_raw(a) as *mut T),
unsafe {
RawOffsetArc {
ptr: ptr::NonNull::new_unchecked(Arc::into_raw(a) as *mut T),
}
}
}
/// Converts a RawOffsetArc into an Arc. This consumes the RawOffsetArc, so the refcount
/// Converts a `RawOffsetArc` into an `Arc`. This consumes the `RawOffsetArc`, so the refcount
/// is not modified.
#[inline]
pub fn from_raw_offset(a: RawOffsetArc<T>) -> Self {
let ptr = a.ptr.ptr();
let ptr = a.ptr.as_ptr();
mem::forget(a);
unsafe { Arc::from_raw(ptr) }
}
}
/// A "borrowed Arc". This is a pointer to
/// A "borrowed `Arc`". This is a pointer to
/// a T that is known to have been allocated within an
/// Arc.
/// `Arc`.
///
/// This is equivalent in guarantees to `&Arc<T>`, however it is
/// a bit more flexible. To obtain an `&Arc<T>` you must have
/// an Arc<T> instance somewhere pinned down until we're done with it.
/// an `Arc<T>` instance somewhere pinned down until we're done with it.
/// It's also a direct pointer to `T`, so using this involves less pointer-chasing
///
/// However, Gecko hands us refcounted things as pointers to T directly,
/// so we have to conjure up a temporary Arc on the stack each time. The
/// same happens for when the object is managed by a RawOffsetArc.
/// However, C++ code may hand us refcounted things as pointers to T directly,
/// so we have to conjure up a temporary `Arc` on the stack each time. The
/// same happens for when the object is managed by a `RawOffsetArc`.
///
/// ArcBorrow lets us deal with borrows of known-refcounted objects
/// without needing to worry about how they're actually stored.
/// `ArcBorrow` lets us deal with borrows of known-refcounted objects
/// without needing to worry about where the `Arc<T>` is.
#[derive(Debug, Eq, PartialEq)]
pub struct ArcBorrow<'a, T: 'a>(&'a T);
@ -932,6 +978,7 @@ impl<'a, T> Clone for ArcBorrow<'a, T> {
}
impl<'a, T> ArcBorrow<'a, T> {
/// Clone this as an `Arc<T>`. This bumps the refcount.
#[inline]
pub fn clone_arc(&self) -> Arc<T> {
let arc = unsafe { Arc::from_raw(self.0) };
@ -947,10 +994,14 @@ impl<'a, T> ArcBorrow<'a, T> {
ArcBorrow(r)
}
/// Compare two `ArcBorrow`s via pointer equality. Will only return
/// true if they come from the same allocation
pub fn ptr_eq(this: &Self, other: &Self) -> bool {
this.0 as *const T == other.0 as *const T
}
/// Temporarily converts |self| into a bonafide Arc and exposes it to the
/// provided callback. The refcount is not modified.
#[inline]
pub fn with_arc<F, U>(&self, f: F) -> U
where
@ -989,18 +1040,25 @@ impl<'a, T> Deref for ArcBorrow<'a, T> {
}
}
/// A tagged union that can represent Arc<A> or Arc<B> while only consuming a
/// single word. The type is also NonZero, and thus can be stored in an Option
/// A tagged union that can represent `Arc<A>` or `Arc<B>` while only consuming a
/// single word. The type is also `NonNull`, and thus can be stored in an Option
/// without increasing size.
///
/// This is functionally equivalent to
/// `enum ArcUnion<A, B> { First(Arc<A>), Second(Arc<B>)` but only takes up
/// up a single word of stack space.
///
/// This could probably be extended to support four types if necessary.
pub struct ArcUnion<A: 'static, B: 'static> {
p: NonZeroPtrMut<()>,
phantom_a: PhantomData<&'static A>,
phantom_b: PhantomData<&'static B>,
pub struct ArcUnion<A, B> {
p: ptr::NonNull<()>,
phantom_a: PhantomData<A>,
phantom_b: PhantomData<B>,
}
impl<A: PartialEq + 'static, B: PartialEq + 'static> PartialEq for ArcUnion<A, B> {
unsafe impl<A: Sync + Send, B: Send + Sync> Send for ArcUnion<A, B> {}
unsafe impl<A: Sync + Send, B: Send + Sync> Sync for ArcUnion<A, B> {}
impl<A: PartialEq, B: PartialEq> PartialEq for ArcUnion<A, B> {
fn eq(&self, other: &Self) -> bool {
use ArcUnionBorrow::*;
match (self.borrow(), other.borrow()) {
@ -1011,16 +1069,17 @@ impl<A: PartialEq + 'static, B: PartialEq + 'static> PartialEq for ArcUnion<A, B
}
}
/// This represents a borrow of an `ArcUnion`.
#[derive(Debug)]
pub enum ArcUnionBorrow<'a, A: 'static, B: 'static> {
pub enum ArcUnionBorrow<'a, A: 'a, B: 'a> {
First(ArcBorrow<'a, A>),
Second(ArcBorrow<'a, B>),
}
impl<A: 'static, B: 'static> ArcUnion<A, B> {
fn new(ptr: *mut ()) -> Self {
impl<A, B> ArcUnion<A, B> {
unsafe fn new(ptr: *mut ()) -> Self {
ArcUnion {
p: NonZeroPtrMut::new(ptr),
p: ptr::NonNull::new_unchecked(ptr),
phantom_a: PhantomData,
phantom_b: PhantomData,
}
@ -1034,37 +1093,37 @@ impl<A: 'static, B: 'static> ArcUnion<A, B> {
/// Returns an enum representing a borrow of either A or B.
pub fn borrow(&self) -> ArcUnionBorrow<A, B> {
if self.is_first() {
let ptr = self.p.ptr() as *const A;
let ptr = self.p.as_ptr() as *const A;
let borrow = unsafe { ArcBorrow::from_ref(&*ptr) };
ArcUnionBorrow::First(borrow)
} else {
let ptr = ((self.p.ptr() as usize) & !0x1) as *const B;
let ptr = ((self.p.as_ptr() as usize) & !0x1) as *const B;
let borrow = unsafe { ArcBorrow::from_ref(&*ptr) };
ArcUnionBorrow::Second(borrow)
}
}
/// Creates an ArcUnion from an instance of the first type.
/// Creates an `ArcUnion` from an instance of the first type.
pub fn from_first(other: Arc<A>) -> Self {
Self::new(Arc::into_raw(other) as *mut _)
unsafe { Self::new(Arc::into_raw(other) as *mut _) }
}
/// Creates an ArcUnion from an instance of the second type.
/// Creates an `ArcUnion` from an instance of the second type.
pub fn from_second(other: Arc<B>) -> Self {
Self::new(((Arc::into_raw(other) as usize) | 0x1) as *mut _)
unsafe { Self::new(((Arc::into_raw(other) as usize) | 0x1) as *mut _) }
}
/// Returns true if this ArcUnion contains the first type.
/// Returns true if this `ArcUnion` contains the first type.
pub fn is_first(&self) -> bool {
self.p.ptr() as usize & 0x1 == 0
self.p.as_ptr() as usize & 0x1 == 0
}
/// Returns true if this ArcUnion contains the second type.
/// Returns true if this `ArcUnion` contains the second type.
pub fn is_second(&self) -> bool {
!self.is_first()
}
/// Returns a borrow of the first type if applicable, otherwise None.
/// Returns a borrow of the first type if applicable, otherwise `None`.
pub fn as_first(&self) -> Option<ArcBorrow<A>> {
match self.borrow() {
ArcUnionBorrow::First(x) => Some(x),
@ -1081,7 +1140,7 @@ impl<A: 'static, B: 'static> ArcUnion<A, B> {
}
}
impl<A: 'static, B: 'static> Clone for ArcUnion<A, B> {
impl<A, B> Clone for ArcUnion<A, B> {
fn clone(&self) -> Self {
match self.borrow() {
ArcUnionBorrow::First(x) => ArcUnion::from_first(x.clone_arc()),
@ -1090,7 +1149,7 @@ impl<A: 'static, B: 'static> Clone for ArcUnion<A, B> {
}
}
impl<A: 'static, B: 'static> Drop for ArcUnion<A, B> {
impl<A, B> Drop for ArcUnion<A, B> {
fn drop(&mut self) {
match self.borrow() {
ArcUnionBorrow::First(x) => unsafe {

View file

@ -10,7 +10,7 @@ use Atom;
use cssparser::{Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType};
use hash::map::Entry;
use precomputed_hash::PrecomputedHash;
use properties::{CSSWideKeyword, DeclaredValue};
use properties::{CSSWideKeyword, CustomDeclarationValue};
use selector_map::{PrecomputedHashMap, PrecomputedHashSet};
use selectors::parser::SelectorParseErrorKind;
use servo_arc::Arc;
@ -519,14 +519,14 @@ impl<'a> CustomPropertiesBuilder<'a> {
pub fn cascade(
&mut self,
name: &'a Name,
specified_value: DeclaredValue<'a, Arc<SpecifiedValue>>,
specified_value: &CustomDeclarationValue,
) {
let was_already_present = !self.seen.insert(name);
if was_already_present {
return;
}
if !self.value_may_affect_style(name, &specified_value) {
if !self.value_may_affect_style(name, specified_value) {
return;
}
@ -538,13 +538,12 @@ impl<'a> CustomPropertiesBuilder<'a> {
}
let map = self.custom_properties.as_mut().unwrap();
match specified_value {
DeclaredValue::Value(ref specified_value) => {
self.may_have_cycles |= !specified_value.references.is_empty();
map.insert(name.clone(), (*specified_value).clone());
match *specified_value {
CustomDeclarationValue::Value(ref unparsed_value) => {
self.may_have_cycles |= !unparsed_value.references.is_empty();
map.insert(name.clone(), (*unparsed_value).clone());
},
DeclaredValue::WithVariables(_) => unreachable!(),
DeclaredValue::CSSWideKeyword(keyword) => match keyword {
CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
CSSWideKeyword::Initial => {
map.remove(name);
},
@ -557,11 +556,11 @@ impl<'a> CustomPropertiesBuilder<'a> {
fn value_may_affect_style(
&self,
name: &Name,
value: &DeclaredValue<Arc<SpecifiedValue>>,
value: &CustomDeclarationValue,
) -> bool {
match *value {
DeclaredValue::CSSWideKeyword(CSSWideKeyword::Unset) |
DeclaredValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Unset) |
CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit) => {
// Custom properties are inherited by default. So
// explicit 'inherit' or 'unset' means we can just use
// any existing value in the inherited CustomPropertiesMap.
@ -577,12 +576,12 @@ impl<'a> CustomPropertiesBuilder<'a> {
.or_else(|| self.inherited.and_then(|m| m.get(name)));
match (existing_value, value) {
(None, &DeclaredValue::CSSWideKeyword(CSSWideKeyword::Initial)) => {
(None, &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Initial)) => {
// The initial value of a custom property is the same as it
// not existing in the map.
return false;
},
(Some(existing_value), &DeclaredValue::Value(specified_value)) => {
(Some(existing_value), &CustomDeclarationValue::Value(ref specified_value)) => {
// Don't bother overwriting an existing inherited value with
// the same specified value.
if existing_value == specified_value {

View file

@ -0,0 +1,808 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The main cascading algorithm of the style system.
use context::QuirksMode;
use custom_properties::CustomPropertiesBuilder;
use dom::TElement;
use font_metrics::FontMetricsProvider;
use logical_geometry::WritingMode;
use media_queries::Device;
use properties::{ComputedValues, StyleBuilder};
use properties::{LonghandId, LonghandIdSet};
use properties::{PropertyDeclaration, PropertyDeclarationId, DeclarationImportanceIterator};
use properties::CASCADE_PROPERTY;
use rule_cache::{RuleCache, RuleCacheConditions};
use rule_tree::{CascadeLevel, StrongRuleNode};
use selector_parser::PseudoElement;
use servo_arc::Arc;
use shared_lock::StylesheetGuards;
use smallbitvec::SmallBitVec;
use std::borrow::Cow;
use std::cell::RefCell;
use style_adjuster::StyleAdjuster;
use values::computed;
/// We split the cascade in two phases: 'early' properties, and 'late'
/// properties.
///
/// Early properties are the ones that don't have dependencies _and_ other
/// properties depend on, for example, writing-mode related properties, color
/// (for currentColor), or font-size (for em, etc).
///
/// Late properties are all the others.
trait CascadePhase {
fn is_early() -> bool;
}
struct EarlyProperties;
impl CascadePhase for EarlyProperties {
fn is_early() -> bool {
true
}
}
struct LateProperties;
impl CascadePhase for LateProperties {
fn is_early() -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ApplyResetProperties {
No,
Yes,
}
/// Performs the CSS cascade, computing new styles for an element from its parent style.
///
/// The arguments are:
///
/// * `device`: Used to get the initial viewport and other external state.
///
/// * `rule_node`: The rule node in the tree that represent the CSS rules that
/// matched.
///
/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
///
/// Returns the computed values.
/// * `flags`: Various flags.
///
pub fn cascade<E>(
device: &Device,
pseudo: Option<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
visited_rules: Option<&StrongRuleNode>,
font_metrics_provider: &FontMetricsProvider,
quirks_mode: QuirksMode,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
cascade_rules(
device,
pseudo,
rule_node,
guards,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
font_metrics_provider,
CascadeMode::Unvisited { visited_rules },
quirks_mode,
rule_cache,
rule_cache_conditions,
element,
)
}
fn cascade_rules<E>(
device: &Device,
pseudo: Option<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
font_metrics_provider: &FontMetricsProvider,
cascade_mode: CascadeMode,
quirks_mode: QuirksMode,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
debug_assert_eq!(
parent_style.is_some(),
parent_style_ignoring_first_line.is_some()
);
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 cascade_level = node.cascade_level();
let node_importance = node.importance();
let declarations = match node.style_source() {
Some(source) => source
.read(cascade_level.guard(guards))
.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) {
return None;
}
}
}
if declaration_importance == node_importance {
Some((declaration, cascade_level))
} else {
None
}
})
})
};
apply_declarations(
device,
pseudo,
rule_node,
guards,
iter_declarations,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
font_metrics_provider,
cascade_mode,
quirks_mode,
rule_cache,
rule_cache_conditions,
element,
)
}
/// Whether we're cascading for visited or unvisited styles.
#[derive(Clone, Copy)]
pub enum CascadeMode<'a> {
/// We're cascading for unvisited styles.
Unvisited {
/// The visited rules that should match the visited style.
visited_rules: Option<&'a StrongRuleNode>,
},
/// We're cascading for visited styles.
Visited {
/// The writing mode of our unvisited style, needed to correctly resolve
/// logical properties..
writing_mode: WritingMode,
},
}
/// NOTE: This function expects the declaration with more priority to appear
/// first.
pub fn apply_declarations<'a, E, F, I>(
device: &Device,
pseudo: Option<&PseudoElement>,
rules: &StrongRuleNode,
guards: &StylesheetGuards,
iter_declarations: F,
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
font_metrics_provider: &FontMetricsProvider,
cascade_mode: CascadeMode,
quirks_mode: QuirksMode,
rule_cache: Option<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
F: Fn() -> I,
I: Iterator<Item = (&'a PropertyDeclaration, CascadeLevel)>,
{
debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
debug_assert_eq!(
parent_style.is_some(),
parent_style_ignoring_first_line.is_some()
);
#[cfg(feature = "gecko")]
debug_assert!(
parent_style.is_none() ||
::std::ptr::eq(
parent_style.unwrap(),
parent_style_ignoring_first_line.unwrap()
) ||
parent_style.unwrap().pseudo() == Some(PseudoElement::FirstLine)
);
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
let custom_properties = {
let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties());
for (declaration, _cascade_level) in iter_declarations() {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(&declaration.name, &declaration.value);
}
}
builder.build()
};
let mut context = computed::Context {
is_root_element: pseudo.is_none() && element.map_or(false, |e| e.is_root()),
// We'd really like to own the rules here to avoid refcount traffic, but
// animation's usage of `apply_declarations` make this tricky. See bug
// 1375525.
builder: StyleBuilder::new(
device,
parent_style,
parent_style_ignoring_first_line,
pseudo,
Some(rules.clone()),
custom_properties,
),
cached_system_font: None,
in_media_query: false,
for_smil_animation: false,
for_non_inherited_property: None,
font_metrics_provider,
quirks_mode,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
};
let using_cached_reset_properties = {
let mut cascade = Cascade::new(&mut context, cascade_mode);
cascade
.apply_properties::<EarlyProperties, I>(ApplyResetProperties::Yes, iter_declarations());
cascade.compute_visited_style_if_needed(
element,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
guards,
);
let using_cached_reset_properties =
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
let apply_reset = if using_cached_reset_properties {
ApplyResetProperties::No
} else {
ApplyResetProperties::Yes
};
cascade.apply_properties::<LateProperties, I>(apply_reset, iter_declarations());
using_cached_reset_properties
};
context.builder.clear_modified_reset();
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
StyleAdjuster::new(&mut context.builder)
.adjust(layout_parent_style.unwrap_or(inherited_style), element);
}
if context.builder.modified_reset() || using_cached_reset_properties {
// If we adjusted any reset structs, we can't cache this ComputedValues.
//
// Also, if we re-used existing reset structs, don't bother caching it
// back again. (Aside from being wasted effort, it will be wrong, since
// context.rule_cache_conditions won't be set appropriately if we didn't
// compute those reset properties.)
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
context.builder.build()
}
fn should_ignore_declaration_when_ignoring_document_colors(
device: &Device,
longhand_id: LonghandId,
cascade_level: CascadeLevel,
pseudo: Option<&PseudoElement>,
declaration: &mut Cow<PropertyDeclaration>,
) -> bool {
if !longhand_id.ignored_when_document_colors_disabled() {
return false;
}
let is_ua_or_user_rule = matches!(
cascade_level,
CascadeLevel::UANormal |
CascadeLevel::UserNormal |
CascadeLevel::UserImportant |
CascadeLevel::UAImportant
);
if is_ua_or_user_rule {
return false;
}
let is_style_attribute = matches!(
cascade_level,
CascadeLevel::StyleAttributeNormal | CascadeLevel::StyleAttributeImportant
);
// Don't override colors on pseudo-element's style attributes. The
// background-color on ::-moz-color-swatch is an example. Those are set
// as an author style (via the style attribute), but it's pretty
// important for it to show up for obvious reasons :)
if pseudo.is_some() && is_style_attribute {
return false;
}
// Treat background-color a bit differently. If the specified color is
// anything other than a fully transparent color, convert it into the
// Device's default background color.
{
let background_color = match **declaration {
PropertyDeclaration::BackgroundColor(ref color) => color,
_ => return true,
};
if background_color.is_transparent() {
return false;
}
}
let color = device.default_background_color();
*declaration.to_mut() = PropertyDeclaration::BackgroundColor(color.into());
false
}
struct Cascade<'a, 'b: 'a> {
context: &'a mut computed::Context<'b>,
cascade_mode: CascadeMode<'a>,
seen: LonghandIdSet,
saved_font_size: Option<PropertyDeclaration>,
saved_font_family: Option<PropertyDeclaration>,
}
impl<'a, 'b: 'a> Cascade<'a, 'b> {
fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self {
Self {
context,
cascade_mode,
seen: LonghandIdSet::default(),
saved_font_size: None,
saved_font_family: None,
}
}
fn substitute_variables_if_needed<'decl>(
&mut self,
declaration: &'decl PropertyDeclaration,
) -> Cow<'decl, PropertyDeclaration> {
let declaration = match *declaration {
PropertyDeclaration::WithVariables(ref declaration) => declaration,
ref d => return Cow::Borrowed(d),
};
if !declaration.id.inherited() {
self.context
.rule_cache_conditions
.borrow_mut()
.set_uncacheable();
}
Cow::Owned(declaration.value.substitute_variables(
declaration.id,
self.context.builder.custom_properties.as_ref(),
self.context.quirks_mode,
))
}
fn apply_declaration<Phase: CascadePhase>(
&mut self,
longhand_id: LonghandId,
declaration: &PropertyDeclaration,
) {
// FIXME(emilio): Find a cleaner abstraction for this.
//
// font-size and font-family are special because in Gecko they're
// they're dependent on other early props, like lang and
// -moz-min-font-size-ratio. This sucks a bit, we should ideally
// move the font-size computation code somewhere else...
if Phase::is_early() {
if longhand_id == LonghandId::FontSize {
self.saved_font_size = Some(declaration.clone());
return;
}
if longhand_id == LonghandId::FontFamily {
self.saved_font_family = Some(declaration.clone());
return;
}
}
self.apply_declaration_ignoring_phase(longhand_id, declaration);
}
#[inline(always)]
fn apply_declaration_ignoring_phase(
&mut self,
longhand_id: LonghandId,
declaration: &PropertyDeclaration,
) {
// We could (and used to) use a pattern match here, but that bloats this
// function to over 100K of compiled code!
//
// To improve i-cache behavior, we outline the individual functions and
// use virtual dispatch instead.
let discriminant = longhand_id as usize;
(CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
}
fn apply_properties<'decls, Phase, I>(
&mut self,
apply_reset: ApplyResetProperties,
declarations: I,
) where
Phase: CascadePhase,
I: Iterator<Item = (&'decls PropertyDeclaration, CascadeLevel)>,
{
let apply_reset = apply_reset == ApplyResetProperties::Yes;
debug_assert!(
!Phase::is_early() || apply_reset,
"Should always apply reset properties in the early phase, since we \
need to know font-size / writing-mode to decide whether to use the \
cached reset properties"
);
let ignore_colors = !self.context.builder.device.use_document_colors();
for (declaration, cascade_level) in declarations {
let declaration_id = declaration.id();
let longhand_id = match declaration_id {
PropertyDeclarationId::Longhand(id) => id,
PropertyDeclarationId::Custom(..) => continue,
};
let inherited = longhand_id.inherited();
if !apply_reset && !inherited {
continue;
}
if Phase::is_early() != longhand_id.is_early_property() {
continue;
}
debug_assert!(!Phase::is_early() || !longhand_id.is_logical());
let physical_longhand_id = if Phase::is_early() {
longhand_id
} else {
longhand_id.to_physical(self.context.builder.writing_mode)
};
if self.seen.contains(physical_longhand_id) {
continue;
}
// Only a few properties are allowed to depend on the visited state
// of links. When cascading visited styles, we can save time by
// only processing these properties.
if matches!(self.cascade_mode, CascadeMode::Visited { .. }) &&
!physical_longhand_id.is_visited_dependent()
{
continue;
}
let mut declaration = self.substitute_variables_if_needed(declaration);
// When document colors are disabled, skip properties that are
// marked as ignored in that mode, unless they come from a UA or
// user style sheet.
if ignore_colors {
let should_ignore = should_ignore_declaration_when_ignoring_document_colors(
self.context.builder.device,
longhand_id,
cascade_level,
self.context.builder.pseudo,
&mut declaration,
);
if should_ignore {
continue;
}
}
self.seen.insert(physical_longhand_id);
// FIXME(emilio): We should avoid generating code for logical
// longhands and just use the physical ones, then rename
// physical_longhand_id to just longhand_id.
self.apply_declaration::<Phase>(longhand_id, &*declaration);
}
if Phase::is_early() {
self.fixup_font_and_apply_saved_font_properties();
self.compute_writing_mode();
} else {
self.finished_applying_properties();
}
}
fn compute_writing_mode(&mut self) {
let writing_mode = match self.cascade_mode {
CascadeMode::Unvisited { .. } => {
WritingMode::new(self.context.builder.get_inherited_box())
},
CascadeMode::Visited { writing_mode } => writing_mode,
};
self.context.builder.writing_mode = writing_mode;
}
fn compute_visited_style_if_needed<E>(
&mut self,
element: Option<E>,
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
guards: &StylesheetGuards,
) where
E: TElement,
{
let visited_rules = match self.cascade_mode {
CascadeMode::Unvisited { visited_rules } => visited_rules,
CascadeMode::Visited { .. } => return,
};
let visited_rules = match visited_rules {
Some(rules) => rules,
None => return,
};
let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
macro_rules! visited_parent {
($parent:expr) => {
if is_link {
$parent
} else {
$parent.map(|p| p.visited_style().unwrap_or(p))
}
};
}
let writing_mode = self.context.builder.writing_mode;
// We could call apply_declarations directly, but that'd cause
// another instantiation of this function which is not great.
let style = cascade_rules(
self.context.builder.device,
self.context.builder.pseudo,
visited_rules,
guards,
visited_parent!(parent_style),
visited_parent!(parent_style_ignoring_first_line),
visited_parent!(layout_parent_style),
self.context.font_metrics_provider,
CascadeMode::Visited { writing_mode },
self.context.quirks_mode,
// The rule cache doesn't care about caching :visited
// styles, we cache the unvisited style instead. We still do
// need to set the caching dependencies properly if present
// though, so the cache conditions need to match.
/* rule_cache = */ None,
&mut *self.context.rule_cache_conditions.borrow_mut(),
element,
);
self.context.builder.visited_style = Some(style);
}
fn finished_applying_properties(&mut self) {
let builder = &mut self.context.builder;
#[cfg(feature = "gecko")]
{
if let Some(bg) = builder.get_background_if_mutated() {
bg.fill_arrays();
}
if let Some(svg) = builder.get_svg_if_mutated() {
svg.fill_arrays();
}
}
#[cfg(feature = "servo")]
{
// TODO(emilio): Use get_font_if_mutated instead.
if self.seen.contains(LonghandId::FontStyle) ||
self.seen.contains(LonghandId::FontWeight) ||
self.seen.contains(LonghandId::FontStretch) ||
self.seen.contains(LonghandId::FontFamily)
{
builder.mutate_font().compute_font_hash();
}
}
}
fn try_to_use_cached_reset_properties(
&mut self,
cache: Option<&'b RuleCache>,
guards: &StylesheetGuards,
) -> bool {
let cache = match cache {
Some(cache) => cache,
None => return false,
};
let cached_style = match cache.find(guards, &self.context.builder) {
Some(style) => style,
None => return false,
};
self.context.builder.copy_reset_from(cached_style);
true
}
// FIXME(emilio): It'd be really nice to simplify all this, somehow. This is
// very annoying code in lots of ways, and there are various bits about it
// which I think are broken or could be improved, see the various FIXMEs
// below.
fn fixup_font_and_apply_saved_font_properties(&mut self) {
let font_family = self.saved_font_family.take();
let font_size = self.saved_font_size.take();
let mut _skip_font_family = false;
#[cfg(feature = "gecko")]
{
// <svg:text> is not affected by text zoom, and it uses a preshint
// to disable it. We fix up the struct when this happens by
// unzooming its contained font values, which will have been zoomed
// in the parent.
//
// FIXME(emilio): Could be cleaner if we just removed this property
// and made a style adjustment o something like that.
if self.seen.contains(LonghandId::XTextZoom) {
let builder = &mut self.context.builder;
let parent_zoom = builder.get_parent_font().gecko().mAllowZoom;
let zoom = builder.get_font().gecko().mAllowZoom;
if zoom != parent_zoom {
debug_assert!(
!zoom,
"We only ever disable text zoom (in svg:text), never enable it"
);
let device = builder.device;
builder.mutate_font().unzoom_fonts(device);
}
}
// Whenever a single generic value is specified, Gecko used to do a
// bunch of recalculation walking up the rule tree, including
// handling the font-size stuff.
//
// It basically repopulated the font struct with the default font
// for a given generic and language. We handle the font-size stuff
// separately, so this boils down to just copying over the
// font-family lists (no other aspect of the default font can be
// configured).
if self.seen.contains(LonghandId::XLang) || self.seen.contains(LonghandId::FontFamily) {
// If just the language changed, the inherited generic is all we
// need.
let mut generic = self.context.builder.get_parent_font().gecko().mGenericID;
// FIXME(emilio): Isn't this bogus for CSS wide keywords like
// reset or such?
if let Some(ref declaration) = font_family {
if let PropertyDeclaration::FontFamily(ref fam) = *declaration {
if let Some(id) = fam.single_generic() {
generic = id;
// In case of a specified font family with a single
// generic, we will end up setting font family
// below, but its value would get overwritten later
// in the pipeline when cascading.
//
// We instead skip cascading font-family in that
// case.
//
// In case of the language changing, we wish for a
// specified font-family to override this, so we do
// not skip cascading then.
_skip_font_family = true;
}
}
}
// FIXME(emilio): Why both setting the generic and passing it
// down?
let pres_context = self.context.builder.device.pres_context();
let gecko_font = self.context.builder.mutate_font().gecko_mut();
gecko_font.mGenericID = generic;
unsafe {
::gecko_bindings::bindings::Gecko_nsStyleFont_PrefillDefaultForGeneric(
gecko_font,
pres_context,
generic,
);
}
}
}
// It is important that font-size is computed before the late
// properties (for em units), but after font-family (for the
// base-font-size dependence for default and keyword font-sizes).
//
// It's important that font-family comes after the other font properties
// to support system fonts.
//
// NOTE(emilio): I haven't verified that comment, but it was there.
// Verify, and if it's false make font-size the only weird property?
if !_skip_font_family {
if let Some(ref declaration) = font_family {
self.apply_declaration_ignoring_phase(LonghandId::FontFamily, declaration);
#[cfg(feature = "gecko")]
{
let context = &mut self.context;
let device = context.builder.device;
if let PropertyDeclaration::FontFamily(ref val) = *declaration {
if val.get_system().is_some() {
let default = context
.cached_system_font
.as_ref()
.unwrap()
.default_font_type;
context.builder.mutate_font().fixup_system(default);
} else {
context.builder.mutate_font().fixup_none_generic(device);
}
}
}
}
}
if let Some(declaration) = font_size {
self.apply_declaration_ignoring_phase(LonghandId::FontSize, &declaration);
} else {
#[cfg(feature = "gecko")]
{
if self.seen.contains(LonghandId::XLang) ||
self.seen.contains(LonghandId::MozScriptLevel) ||
self.seen.contains(LonghandId::MozMinFontSizeRatio) ||
self.seen.contains(LonghandId::FontFamily)
{
use properties::{CSSWideKeyword, WideKeywordDeclaration};
// font-size must be explicitly inherited to handle lang
// changes and scriptlevel changes.
//
// FIXME(emilio): That looks a bit bogus...
let inherit = PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
id: LonghandId::FontSize,
keyword: CSSWideKeyword::Inherit,
});
self.apply_declaration_ignoring_phase(LonghandId::FontSize, &inherit);
}
}
}
}
}

View file

@ -849,7 +849,7 @@ impl PropertyDeclarationBlock {
for declaration in self.normal_declaration_iter() {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(&declaration.name, declaration.value.borrow());
builder.cascade(&declaration.name, &declaration.value);
}
}

View file

@ -289,7 +289,7 @@
#[allow(unused_imports)]
use properties::longhands;
#[allow(unused_imports)]
use properties::{DeclaredValue, LonghandId, LonghandIdSet};
use properties::{LonghandId, LonghandIdSet};
#[allow(unused_imports)]
use properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration};
#[allow(unused_imports)]
@ -312,20 +312,6 @@
declaration: &PropertyDeclaration,
context: &mut computed::Context,
) {
let value = match *declaration {
PropertyDeclaration::${property.camel_case}(ref value) => {
DeclaredValue::Value(value)
},
PropertyDeclaration::CSSWideKeyword(ref declaration) => {
debug_assert_eq!(declaration.id, LonghandId::${property.camel_case});
DeclaredValue::CSSWideKeyword(declaration.keyword)
},
PropertyDeclaration::WithVariables(..) => {
panic!("variables should already have been substituted")
}
_ => panic!("entered the wrong cascade_property() implementation"),
};
context.for_non_inherited_property =
% if property.style_struct.inherited:
None;
@ -333,79 +319,86 @@
Some(LonghandId::${property.camel_case});
% endif
match value {
DeclaredValue::Value(specified_value) => {
% if property.ident in SYSTEM_FONT_LONGHANDS and product == "gecko":
if let Some(sf) = specified_value.get_system() {
longhands::system_font::resolve_system_font(sf, context);
let specified_value = match *declaration {
PropertyDeclaration::${property.camel_case}(ref value) => value,
PropertyDeclaration::CSSWideKeyword(ref declaration) => {
debug_assert_eq!(declaration.id, LonghandId::${property.camel_case});
match declaration.keyword {
% if not data.current_style_struct.inherited:
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Initial => {
% if property.ident == "font_size":
computed::FontSize::cascade_initial_font_size(context);
% else:
context.builder.reset_${property.ident}();
% endif
},
% if data.current_style_struct.inherited:
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Inherit => {
% if not property.style_struct.inherited:
context.rule_cache_conditions.borrow_mut().set_uncacheable();
% endif
% if property.ident == "font_size":
computed::FontSize::cascade_inherit_font_size(context);
% else:
context.builder.inherit_${property.ident}();
% endif
}
% endif
% if not property.style_struct.inherited and property.logical:
context.rule_cache_conditions.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
% endif
% if property.is_vector:
// In the case of a vector property we want to pass
// down an iterator so that this can be computed
// without allocation
//
// However, computing requires a context, but the
// style struct being mutated is on the context. We
// temporarily remove it, mutate it, and then put it
// back. Vector longhands cannot touch their own
// style struct whilst computing, else this will
// panic.
let mut s =
context.builder.take_${data.current_style_struct.name_lower}();
{
let iter = specified_value.compute_iter(context);
s.set_${property.ident}(iter);
}
context.builder.put_${data.current_style_struct.name_lower}(s);
% else:
% if property.boxed:
let computed = (**specified_value).to_computed_value(context);
% else:
let computed = specified_value.to_computed_value(context);
% endif
% if property.ident == "font_size":
specified::FontSize::cascade_specified_font_size(
context,
&specified_value,
computed,
);
% else:
context.builder.set_${property.ident}(computed)
% endif
% endif
}
DeclaredValue::WithVariables(_) => unreachable!(),
DeclaredValue::CSSWideKeyword(keyword) => match keyword {
% if not data.current_style_struct.inherited:
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Initial => {
% if property.ident == "font_size":
computed::FontSize::cascade_initial_font_size(context);
% else:
context.builder.reset_${property.ident}();
% endif
},
% if data.current_style_struct.inherited:
CSSWideKeyword::Unset |
% endif
CSSWideKeyword::Inherit => {
% if not property.style_struct.inherited:
context.rule_cache_conditions.borrow_mut().set_uncacheable();
% endif
% if property.ident == "font_size":
computed::FontSize::cascade_inherit_font_size(context);
% else:
context.builder.inherit_${property.ident}();
% endif
}
return;
}
}
PropertyDeclaration::WithVariables(..) => {
panic!("variables should already have been substituted")
}
_ => panic!("entered the wrong cascade_property() implementation"),
};
% if property.ident in SYSTEM_FONT_LONGHANDS and product == "gecko":
if let Some(sf) = specified_value.get_system() {
longhands::system_font::resolve_system_font(sf, context);
}
% endif
% if not property.style_struct.inherited and property.logical:
context.rule_cache_conditions.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
% endif
% if property.is_vector:
// In the case of a vector property we want to pass down an
// iterator so that this can be computed without allocation.
//
// However, computing requires a context, but the style struct
// being mutated is on the context. We temporarily remove it,
// mutate it, and then put it back. Vector longhands cannot
// touch their own style struct whilst computing, else this will
// panic.
let mut s =
context.builder.take_${data.current_style_struct.name_lower}();
{
let iter = specified_value.compute_iter(context);
s.set_${property.ident}(iter);
}
context.builder.put_${data.current_style_struct.name_lower}(s);
% else:
% if property.boxed:
let computed = (**specified_value).to_computed_value(context);
% else:
let computed = specified_value.to_computed_value(context);
% endif
% if property.ident == "font_size":
specified::FontSize::cascade_specified_font_size(
context,
&specified_value,
computed,
);
% else:
context.builder.set_${property.ident}(computed)
% endif
% endif
}
pub fn parse_declared<'i, 't>(

View file

@ -13,13 +13,9 @@
#[cfg(feature = "servo")]
use app_units::Au;
use arrayvec::{ArrayVec, Drain as ArrayVecDrain};
use dom::TElement;
use custom_properties::CustomPropertiesBuilder;
use servo_arc::{Arc, UniqueArc};
use smallbitvec::SmallBitVec;
use std::borrow::Cow;
use std::{ops, ptr};
use std::cell::RefCell;
use std::fmt::{self, Write};
use std::mem::{self, ManuallyDrop};
@ -27,8 +23,6 @@ use cssparser::{Parser, RGBA, TokenSerializationType};
use cssparser::ParserInput;
#[cfg(feature = "servo")] use euclid::SideOffsets2D;
use context::QuirksMode;
use font_metrics::FontMetricsProvider;
#[cfg(feature = "gecko")] use gecko_bindings::bindings;
#[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
#[cfg(feature = "servo")] use logical_geometry::LogicalMargin;
#[cfg(feature = "servo")] use computed_values;
@ -37,11 +31,9 @@ use logical_geometry::WritingMode;
use media_queries::Device;
use parser::ParserContext;
use properties::longhands::system_font::SystemFont;
use rule_cache::{RuleCache, RuleCacheConditions};
use selector_parser::PseudoElement;
use selectors::parser::SelectorParseErrorKind;
#[cfg(feature = "servo")] use servo_config::prefs::PREFS;
use shared_lock::StylesheetGuards;
use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode};
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
use stylesheets::{CssRuleType, Origin, UrlExtraData};
@ -49,12 +41,12 @@ use values::generics::text::LineHeight;
use values::computed;
use values::computed::NonNegativeLength;
use values::serialize_atom_name;
use rule_tree::{CascadeLevel, StrongRuleNode};
use rule_tree::StrongRuleNode;
use self::computed_value_flags::*;
use str::{CssString, CssStringBorrow, CssStringWriter};
use style_adjuster::StyleAdjuster;
pub use self::declaration_block::*;
pub use self::cascade::*;
<%!
from collections import defaultdict
@ -66,6 +58,8 @@ pub use self::declaration_block::*;
pub mod computed_value_flags;
#[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
pub mod declaration_block;
#[path="${repr(os.path.join(os.path.dirname(__file__), 'cascade.rs'))[1:-1]}"]
pub mod cascade;
/// Conversion with fewer impls than From/Into
pub trait MaybeBoxed<Out> {
@ -731,7 +725,7 @@ static ${name}: LonghandIdSet = LonghandIdSet {
</%def>
/// A set of longhand properties
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
pub struct LonghandIdSet {
storage: [u32; (${len(data.longhands)} - 1 + 32) / 32]
}
@ -859,25 +853,22 @@ impl CSSWideKeyword {
CSSWideKeyword::Unset => "unset",
}
}
/// Takes the result of cssparser::Parser::expect_ident() and converts it
/// to a CSSWideKeyword.
pub fn from_ident<'i>(ident: &str) -> Option<Self> {
match_ignore_ascii_case! { ident,
// If modifying this set of keyword, also update values::CustomIdent::from_ident
"initial" => Some(CSSWideKeyword::Initial),
"inherit" => Some(CSSWideKeyword::Inherit),
"unset" => Some(CSSWideKeyword::Unset),
_ => None
}
}
}
impl CSSWideKeyword {
fn parse(input: &mut Parser) -> Result<Self, ()> {
let ident = input.expect_ident().map_err(|_| ())?.clone();
let keyword = {
let ident = input.expect_ident().map_err(|_| ())?;
match_ignore_ascii_case! { ident,
// If modifying this set of keyword, also update values::CustomIdent::from_ident
"initial" => CSSWideKeyword::Initial,
"inherit" => CSSWideKeyword::Inherit,
"unset" => CSSWideKeyword::Unset,
_ => return Err(()),
}
};
input.expect_exhausted().map_err(|_| ())?;
CSSWideKeyword::from_ident(&ident).ok_or(())
Ok(keyword)
}
}
@ -969,6 +960,7 @@ impl LonghandId {
}
/// Returns whether the longhand property is inherited by default.
#[inline]
pub fn inherited(&self) -> bool {
${static_longhand_id_set("INHERITED", lambda p: p.style_struct.inherited)}
INHERITED.contains(*self)
@ -1121,16 +1113,19 @@ impl LonghandId {
}
/// Returns PropertyFlags for given longhand property.
pub fn flags(&self) -> PropertyFlags {
match *self {
#[inline(always)]
pub fn flags(self) -> PropertyFlags {
// TODO(emilio): This can be simplified further as Rust gains more
// constant expression support.
const FLAGS: [u8; ${len(data.longhands)}] = [
% for property in data.longhands:
LonghandId::${property.camel_case} =>
% for flag in property.flags:
PropertyFlags::${flag} |
% endfor
PropertyFlags::empty(),
% for flag in property.flags:
PropertyFlags::${flag}.bits |
% endfor
0,
% endfor
}
];
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
}
/// Only a few properties are allowed to depend on the visited state of
@ -1160,40 +1155,14 @@ impl LonghandId {
/// Returns true if the property is one that is ignored when document
/// colors are disabled.
fn is_ignored_when_document_colors_disabled(
&self,
cascade_level: CascadeLevel,
pseudo: Option<<&PseudoElement>,
) -> bool {
let is_ua_or_user_rule = matches!(
cascade_level,
CascadeLevel::UANormal |
CascadeLevel::UserNormal |
CascadeLevel::UserImportant |
CascadeLevel::UAImportant
);
#[inline]
fn ignored_when_document_colors_disabled(self) -> bool {
${static_longhand_id_set(
"IGNORED_WHEN_COLORS_DISABLED",
lambda p: p.ignored_when_colors_disabled
)}
if is_ua_or_user_rule {
return false;
}
let is_style_attribute = matches!(
cascade_level,
CascadeLevel::StyleAttributeNormal |
CascadeLevel::StyleAttributeImportant
);
// Don't override colors on pseudo-element's style attributes. The
// background-color on ::-moz-color-swatch is an example. Those are set
// as an author style (via the style attribute), but it's pretty
// important for it to show up for obvious reasons :)
if pseudo.is_some() && is_style_attribute {
return false;
}
matches!(*self,
${" | ".join([("LonghandId::" + p.camel_case)
for p in data.longhands if p.ignored_when_colors_disabled])}
)
IGNORED_WHEN_COLORS_DISABLED.contains(self)
}
/// The computed value of some properties depends on the (sometimes
@ -1469,47 +1438,6 @@ impl ShorthandId {
}
}
/// Servo's representation of a declared value for a given `T`, which is the
/// declared value for that property.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DeclaredValue<'a, T: 'a> {
/// A known specified value from the stylesheet.
Value(&'a T),
/// An unparsed value that contains `var()` functions.
WithVariables(&'a Arc<UnparsedValue>),
/// An CSS-wide keyword.
CSSWideKeyword(CSSWideKeyword),
}
/// A variant of DeclaredValue that owns its data. This separation exists so
/// that PropertyDeclaration can avoid embedding a DeclaredValue (and its
/// extra discriminant word) and synthesize dependent DeclaredValues for
/// PropertyDeclaration instances as needed.
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, Debug, Eq, PartialEq, ToCss)]
pub enum DeclaredValueOwned<T> {
/// A known specified value from the stylesheet.
Value(T),
/// An unparsed value that contains `var()` functions.
WithVariables(
#[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
Arc<UnparsedValue>
),
/// An CSS-wide keyword.
CSSWideKeyword(CSSWideKeyword),
}
impl<T> DeclaredValueOwned<T> {
/// Creates a dependent DeclaredValue from this DeclaredValueOwned.
fn borrow(&self) -> DeclaredValue<T> {
match *self {
DeclaredValueOwned::Value(ref v) => DeclaredValue::Value(v),
DeclaredValueOwned::WithVariables(ref v) => DeclaredValue::WithVariables(v),
DeclaredValueOwned::CSSWideKeyword(v) => DeclaredValue::CSSWideKeyword(v),
}
}
}
/// An unparsed property value that contains `var()` functions.
#[derive(Debug, Eq, PartialEq)]
pub struct UnparsedValue {
@ -1968,6 +1896,16 @@ pub struct VariableDeclaration {
value: Arc<UnparsedValue>,
}
/// A custom property declaration value is either an unparsed value or a CSS
/// wide-keyword.
#[derive(Clone, PartialEq, ToCss)]
pub enum CustomDeclarationValue {
/// A value.
Value(Arc<::custom_properties::SpecifiedValue>),
/// A wide keyword.
CSSWideKeyword(CSSWideKeyword),
}
/// A custom property declaration with the property name and the declared value.
#[cfg_attr(feature = "gecko", derive(MallocSizeOf))]
#[derive(Clone, PartialEq, ToCss)]
@ -1977,7 +1915,7 @@ pub struct CustomDeclaration {
pub name: ::custom_properties::Name,
/// The value of the custom property.
#[cfg_attr(feature = "gecko", ignore_malloc_size_of = "XXX: how to handle this?")]
pub value: DeclaredValueOwned<Arc<::custom_properties::SpecifiedValue>>,
pub value: CustomDeclarationValue,
}
impl fmt::Debug for PropertyDeclaration {
@ -2159,13 +2097,13 @@ impl PropertyDeclaration {
/// This is the case of custom properties and values that contain
/// unsubstituted variables.
pub fn value_is_unparsed(&self) -> bool {
match *self {
PropertyDeclaration::WithVariables(..) => true,
PropertyDeclaration::Custom(ref declaration) => {
!matches!(declaration.value.borrow(), DeclaredValue::CSSWideKeyword(..))
}
_ => false,
}
match *self {
PropertyDeclaration::WithVariables(..) => true,
PropertyDeclaration::Custom(ref declaration) => {
matches!(declaration.value, CustomDeclarationValue::Value(..))
}
_ => false,
}
}
/// Returns true if this property declaration is for one of the animatable
@ -2210,9 +2148,9 @@ impl PropertyDeclaration {
// before adding skip_whitespace here.
// This probably affects some test results.
let value = match input.try(CSSWideKeyword::parse) {
Ok(keyword) => DeclaredValueOwned::CSSWideKeyword(keyword),
Ok(keyword) => CustomDeclarationValue::CSSWideKeyword(keyword),
Err(()) => match ::custom_properties::SpecifiedValue::parse(input) {
Ok(value) => DeclaredValueOwned::Value(value),
Ok(value) => CustomDeclarationValue::Value(value),
Err(e) => return Err(StyleParseErrorKind::new_invalid(
format!("--{}", property_name),
e,
@ -3469,6 +3407,10 @@ impl<'a> StyleBuilder<'a> {
self.modified_reset = true;
% endif
// TODO(emilio): There's a maybe-worth it optimization here: We should
// avoid allocating a new reset struct if `reset_struct` and our struct
// is the same pointer. Would remove a bunch of stupid allocations if
// you did something like `* { all: initial }` or what not.
self.${property.style_struct.ident}.mutate()
.reset_${property.ident}(
reset_struct,
@ -3740,538 +3682,12 @@ pub type CascadePropertyFn =
/// A per-longhand array of functions to perform the CSS cascade on each of
/// them, effectively doing virtual dispatch.
static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [
pub static CASCADE_PROPERTY: [CascadePropertyFn; ${len(data.longhands)}] = [
% for property in data.longhands:
longhands::${property.ident}::cascade_property,
% endfor
];
/// Performs the CSS cascade, computing new styles for an element from its parent style.
///
/// The arguments are:
///
/// * `device`: Used to get the initial viewport and other external state.
///
/// * `rule_node`: The rule node in the tree that represent the CSS rules that
/// matched.
///
/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
///
/// Returns the computed values.
/// * `flags`: Various flags.
///
pub fn cascade<E>(
device: &Device,
pseudo: Option<<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<<&ComputedValues>,
parent_style_ignoring_first_line: Option<<&ComputedValues>,
layout_parent_style: Option<<&ComputedValues>,
visited_rules: Option<<&StrongRuleNode>,
font_metrics_provider: &FontMetricsProvider,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
cascade_rules(
device,
pseudo,
rule_node,
guards,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
font_metrics_provider,
CascadeMode::Unvisited { visited_rules },
quirks_mode,
rule_cache,
rule_cache_conditions,
element,
)
}
fn cascade_rules<E>(
device: &Device,
pseudo: Option<<&PseudoElement>,
rule_node: &StrongRuleNode,
guards: &StylesheetGuards,
parent_style: Option<<&ComputedValues>,
parent_style_ignoring_first_line: Option<<&ComputedValues>,
layout_parent_style: Option<<&ComputedValues>,
font_metrics_provider: &FontMetricsProvider,
cascade_mode: CascadeMode,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
{
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
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 cascade_level = node.cascade_level();
let node_importance = node.importance();
let declarations = match node.style_source() {
Some(source) => {
source.read(cascade_level.guard(guards)).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) {
return None
}
}
}
if declaration_importance == node_importance {
Some((declaration, cascade_level))
} else {
None
}
})
})
};
apply_declarations(
device,
pseudo,
rule_node,
guards,
iter_declarations,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
font_metrics_provider,
cascade_mode,
quirks_mode,
rule_cache,
rule_cache_conditions,
element,
)
}
/// Whether we're cascading for visited or unvisited styles.
#[derive(Clone, Copy)]
pub enum CascadeMode<'a> {
/// We're cascading for unvisited styles.
Unvisited {
/// The visited rules that should match the visited style.
visited_rules: Option<<&'a StrongRuleNode>,
},
/// We're cascading for visited styles.
Visited {
/// The writing mode of our unvisited style, needed to correctly resolve
/// logical properties..
writing_mode: WritingMode,
},
}
/// NOTE: This function expects the declaration with more priority to appear
/// first.
pub fn apply_declarations<'a, E, F, I>(
device: &Device,
pseudo: Option<<&PseudoElement>,
rules: &StrongRuleNode,
guards: &StylesheetGuards,
iter_declarations: F,
parent_style: Option<<&ComputedValues>,
parent_style_ignoring_first_line: Option<<&ComputedValues>,
layout_parent_style: Option<<&ComputedValues>,
font_metrics_provider: &FontMetricsProvider,
cascade_mode: CascadeMode,
quirks_mode: QuirksMode,
rule_cache: Option<<&RuleCache>,
rule_cache_conditions: &mut RuleCacheConditions,
element: Option<E>,
) -> Arc<ComputedValues>
where
E: TElement,
F: Fn() -> I,
I: Iterator<Item = (&'a PropertyDeclaration, CascadeLevel)>,
{
debug_assert!(layout_parent_style.is_none() || parent_style.is_some());
debug_assert_eq!(parent_style.is_some(), parent_style_ignoring_first_line.is_some());
#[cfg(feature = "gecko")]
debug_assert!(parent_style.is_none() ||
::std::ptr::eq(parent_style.unwrap(),
parent_style_ignoring_first_line.unwrap()) ||
parent_style.unwrap().pseudo() == Some(PseudoElement::FirstLine));
let inherited_style =
parent_style.unwrap_or(device.default_computed_values());
let custom_properties = {
let mut builder =
CustomPropertiesBuilder::new(inherited_style.custom_properties());
for (declaration, _cascade_level) in iter_declarations() {
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(&declaration.name, declaration.value.borrow());
}
}
builder.build()
};
let mut context = computed::Context {
is_root_element: pseudo.is_none() && element.map_or(false, |e| e.is_root()),
// We'd really like to own the rules here to avoid refcount traffic, but
// animation's usage of `apply_declarations` make this tricky. See bug
// 1375525.
builder: StyleBuilder::new(
device,
parent_style,
parent_style_ignoring_first_line,
pseudo,
Some(rules.clone()),
custom_properties,
),
cached_system_font: None,
in_media_query: false,
for_smil_animation: false,
for_non_inherited_property: None,
font_metrics_provider,
quirks_mode,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
};
let ignore_colors = !device.use_document_colors();
// Set computed values, overwriting earlier declarations for the same
// property.
let mut seen = LonghandIdSet::new();
// Declaration blocks are stored in increasing precedence order, we want
// them in decreasing order here.
//
// We could (and used to) use a pattern match here, but that bloats this
// function to over 100K of compiled code!
//
// To improve i-cache behavior, we outline the individual functions and use
// virtual dispatch instead.
let mut apply_reset = true;
% for category_to_cascade_now in ["early", "other"]:
% if category_to_cascade_now == "early":
// Pull these out so that we can compute them in a specific order
// without introducing more iterations.
let mut font_size = None;
let mut font_family = None;
% endif
for (declaration, cascade_level) in iter_declarations() {
let declaration_id = declaration.id();
let longhand_id = match declaration_id {
PropertyDeclarationId::Longhand(id) => id,
PropertyDeclarationId::Custom(..) => continue,
};
if !apply_reset && !longhand_id.inherited() {
continue;
}
if
% if category_to_cascade_now == "early":
!
% endif
longhand_id.is_early_property()
{
continue
}
<% maybe_to_physical = ".to_physical(writing_mode)" if category_to_cascade_now != "early" else "" %>
let physical_longhand_id = longhand_id ${maybe_to_physical};
if seen.contains(physical_longhand_id) {
continue
}
// Only a few properties are allowed to depend on the visited state
// of links. When cascading visited styles, we can save time by
// only processing these properties.
if matches!(cascade_mode, CascadeMode::Visited { .. }) &&
!physical_longhand_id.is_visited_dependent() {
continue
}
let mut declaration = match *declaration {
PropertyDeclaration::WithVariables(ref declaration) => {
if !declaration.id.inherited() {
context.rule_cache_conditions.borrow_mut()
.set_uncacheable();
}
Cow::Owned(declaration.value.substitute_variables(
declaration.id,
context.builder.custom_properties.as_ref(),
context.quirks_mode
))
}
ref d => Cow::Borrowed(d)
};
// When document colors are disabled, skip properties that are
// marked as ignored in that mode, unless they come from a UA or
// user style sheet.
if ignore_colors &&
longhand_id.is_ignored_when_document_colors_disabled(
cascade_level,
context.builder.pseudo
)
{
let non_transparent_background = match *declaration {
PropertyDeclaration::BackgroundColor(ref color) => {
// Treat background-color a bit differently. If the specified
// color is anything other than a fully transparent color, convert
// it into the Device's default background color.
color.is_non_transparent()
}
_ => continue
};
// FIXME: moving this out of `match` is a work around for
// borrows being lexical.
if non_transparent_background {
let color = device.default_background_color();
declaration =
Cow::Owned(PropertyDeclaration::BackgroundColor(color.into()));
}
}
seen.insert(physical_longhand_id);
% if category_to_cascade_now == "early":
if LonghandId::FontSize == longhand_id {
font_size = Some(declaration.clone());
continue;
}
if LonghandId::FontFamily == longhand_id {
font_family = Some(declaration.clone());
continue;
}
% endif
let discriminant = longhand_id as usize;
(CASCADE_PROPERTY[discriminant])(&*declaration, &mut context);
}
% if category_to_cascade_now == "early":
let writing_mode = match cascade_mode {
CascadeMode::Unvisited { .. } => {
WritingMode::new(context.builder.get_inherited_box())
}
CascadeMode::Visited { writing_mode } => writing_mode,
};
context.builder.writing_mode = writing_mode;
if let CascadeMode::Unvisited { visited_rules: Some(visited_rules) } = cascade_mode {
let is_link = pseudo.is_none() && element.unwrap().is_link();
macro_rules! visited_parent {
($parent:expr) => {
if is_link {
$parent
} else {
$parent.map(|p| p.visited_style().unwrap_or(p))
}
}
}
// We could call apply_declarations directly, but that'd cause
// another instantiation of this function which is not great.
context.builder.visited_style = Some(cascade_rules(
device,
pseudo,
visited_rules,
guards,
visited_parent!(parent_style),
visited_parent!(parent_style_ignoring_first_line),
visited_parent!(layout_parent_style),
font_metrics_provider,
CascadeMode::Visited { writing_mode },
quirks_mode,
// The rule cache doesn't care about caching :visited
// styles, we cache the unvisited style instead. We still do
// need to set the caching dependencies properly if present
// though, so the cache conditions need to match.
/* rule_cache = */ None,
&mut *context.rule_cache_conditions.borrow_mut(),
element,
));
}
let mut _skip_font_family = false;
% if product == "gecko":
// <svg:text> is not affected by text zoom, and it uses a preshint to
// disable it. We fix up the struct when this happens by unzooming
// its contained font values, which will have been zoomed in the parent
if seen.contains(LonghandId::XTextZoom) {
let zoom = context.builder.get_font().gecko().mAllowZoom;
let parent_zoom = context.style().get_parent_font().gecko().mAllowZoom;
if zoom != parent_zoom {
debug_assert!(!zoom,
"We only ever disable text zoom (in svg:text), never enable it");
// can't borrow both device and font, use the take/put machinery
let mut font = context.builder.take_font();
font.unzoom_fonts(context.device());
context.builder.put_font(font);
}
}
// Whenever a single generic value is specified, gecko will do a bunch of
// recalculation walking up the rule tree, including handling the font-size stuff.
// It basically repopulates the font struct with the default font for a given
// generic and language. We handle the font-size stuff separately, so this boils
// down to just copying over the font-family lists (no other aspect of the default
// font can be configured).
if seen.contains(LonghandId::XLang) || font_family.is_some() {
// if just the language changed, the inherited generic is all we need
let mut generic = inherited_style.get_font().gecko().mGenericID;
if let Some(ref declaration) = font_family {
if let PropertyDeclaration::FontFamily(ref fam) = **declaration {
if let Some(id) = fam.single_generic() {
generic = id;
// In case of a specified font family with a single generic, we will
// end up setting font family below, but its value would get
// overwritten later in the pipeline when cascading.
//
// We instead skip cascading font-family in that case.
//
// In case of the language changing, we wish for a specified font-
// family to override this, so we do not skip cascading then.
_skip_font_family = true;
}
}
}
let pres_context = context.builder.device.pres_context();
let gecko_font = context.builder.mutate_font().gecko_mut();
gecko_font.mGenericID = generic;
unsafe {
bindings::Gecko_nsStyleFont_PrefillDefaultForGeneric(
gecko_font,
pres_context,
generic,
);
}
}
% endif
// It is important that font_size is computed before
// the late properties (for em units), but after font-family
// (for the base-font-size dependence for default and keyword font-sizes)
// Additionally, when we support system fonts they will have to be
// computed early, and *before* font_family, so I'm including
// font_family here preemptively instead of keeping it within
// the early properties.
//
// To avoid an extra iteration, we just pull out the property
// during the early iteration and cascade them in order
// after it.
if !_skip_font_family {
if let Some(ref declaration) = font_family {
let discriminant = LonghandId::FontFamily as usize;
(CASCADE_PROPERTY[discriminant])(declaration, &mut context);
% if product == "gecko":
let device = context.builder.device;
if let PropertyDeclaration::FontFamily(ref val) = **declaration {
if val.get_system().is_some() {
let default = context.cached_system_font
.as_ref().unwrap().default_font_type;
context.builder.mutate_font().fixup_system(default);
} else {
context.builder.mutate_font().fixup_none_generic(device);
}
}
% endif
}
}
if let Some(ref declaration) = font_size {
let discriminant = LonghandId::FontSize as usize;
(CASCADE_PROPERTY[discriminant])(declaration, &mut context);
% if product == "gecko":
// Font size must be explicitly inherited to handle lang changes and
// scriptlevel changes.
} else if seen.contains(LonghandId::XLang) ||
seen.contains(LonghandId::MozScriptLevel) ||
seen.contains(LonghandId::MozMinFontSizeRatio) ||
font_family.is_some() {
let discriminant = LonghandId::FontSize as usize;
let size = PropertyDeclaration::CSSWideKeyword(WideKeywordDeclaration {
id: LonghandId::FontSize,
keyword: CSSWideKeyword::Inherit,
});
(CASCADE_PROPERTY[discriminant])(&size, &mut context);
% endif
}
if let Some(style) = rule_cache.and_then(|c| c.find(guards, &context.builder)) {
context.builder.copy_reset_from(style);
apply_reset = false;
}
% endif // category == "early"
% endfor
let mut builder = context.builder;
% if product == "gecko":
if let Some(ref mut bg) = builder.get_background_if_mutated() {
bg.fill_arrays();
}
if let Some(ref mut svg) = builder.get_svg_if_mutated() {
svg.fill_arrays();
}
% endif
% if product == "servo":
if seen.contains(LonghandId::FontStyle) ||
seen.contains(LonghandId::FontWeight) ||
seen.contains(LonghandId::FontStretch) ||
seen.contains(LonghandId::FontFamily) {
builder.mutate_font().compute_font_hash();
}
% endif
builder.clear_modified_reset();
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
StyleAdjuster::new(&mut builder).adjust(
layout_parent_style.unwrap_or(inherited_style),
element,
);
}
if builder.modified_reset() || !apply_reset {
// If we adjusted any reset structs, we can't cache this ComputedValues.
//
// Also, if we re-used existing reset structs, don't bother caching it
// back again. (Aside from being wasted effort, it will be wrong, since
// context.rule_cache_conditions won't be set appropriately if we
// didn't compute those reset properties.)
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
builder.build()
}
/// See StyleAdjuster::adjust_for_border_width.
pub fn adjust_border_width(style: &mut StyleBuilder) {

View file

@ -12,7 +12,7 @@ use gecko::selector_parser::PseudoElement;
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use properties::{Importance, LonghandIdSet, PropertyDeclarationBlock};
use servo_arc::{Arc, ArcBorrow, ArcUnion, ArcUnionBorrow, NonZeroPtrMut};
use servo_arc::{Arc, ArcBorrow, ArcUnion, ArcUnionBorrow};
use shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
use smallvec::SmallVec;
use std::io::{self, Write};
@ -938,19 +938,20 @@ impl MallocSizeOf for RuleNode {
}
}
// FIXME: use std::ptr::NonNull when Firefox requires Rust 1.25+
#[derive(Clone)]
struct WeakRuleNode {
p: NonZeroPtrMut<RuleNode>,
p: ptr::NonNull<RuleNode>,
}
/// A strong reference to a rule node.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct StrongRuleNode {
p: NonZeroPtrMut<RuleNode>,
p: ptr::NonNull<RuleNode>,
}
unsafe impl Send for StrongRuleNode {}
unsafe impl Sync for StrongRuleNode {}
#[cfg(feature = "servo")]
malloc_size_of_is_0!(StrongRuleNode);
@ -968,7 +969,7 @@ impl StrongRuleNode {
fn from_ptr(ptr: *mut RuleNode) -> Self {
StrongRuleNode {
p: NonZeroPtrMut::new(ptr),
p: ptr::NonNull::new(ptr).expect("Pointer must not be null"),
}
}
@ -1052,7 +1053,7 @@ impl StrongRuleNode {
/// Raw pointer to the RuleNode
pub fn ptr(&self) -> *mut RuleNode {
self.p.ptr()
self.p.as_ptr()
}
fn get(&self) -> &RuleNode {
@ -1665,12 +1666,12 @@ impl WeakRuleNode {
fn from_ptr(ptr: *mut RuleNode) -> Self {
WeakRuleNode {
p: NonZeroPtrMut::new(ptr),
p: ptr::NonNull::new(ptr).expect("Pointer must not be null"),
}
}
fn ptr(&self) -> *mut RuleNode {
self.p.ptr()
self.p.as_ptr()
}
}

View file

@ -76,12 +76,13 @@ use properties::ComputedValues;
use rule_tree::StrongRuleNode;
use selectors::NthIndexCache;
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
use servo_arc::{Arc, NonZeroPtrMut};
use servo_arc::Arc;
use smallbitvec::SmallBitVec;
use smallvec::SmallVec;
use std::marker::PhantomData;
use std::mem;
use std::ops::Deref;
use std::ptr::NonNull;
use style_resolver::{PrimaryStyle, ResolvedElementStyles};
use stylist::Stylist;
use uluru::{Entry, LRUCache};
@ -112,10 +113,16 @@ pub enum StyleSharingBehavior {
/// Opaque pointer type to compare ComputedValues identities.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct OpaqueComputedValues(NonZeroPtrMut<()>);
pub struct OpaqueComputedValues(NonNull<()>);
unsafe impl Send for OpaqueComputedValues {}
unsafe impl Sync for OpaqueComputedValues {}
impl OpaqueComputedValues {
fn from(cv: &ComputedValues) -> Self {
let p = NonZeroPtrMut::new(cv as *const ComputedValues as *const () as *mut ());
let p = unsafe {
NonNull::new_unchecked(cv as *const ComputedValues as *const () as *mut ())
};
OpaqueComputedValues(p)
}

View file

@ -953,7 +953,7 @@ impl ExtremumLength {
/// TODO: After these values are supported for both axes (and maybe
/// unprefixed, see bug 1322780) all this complexity can go away, and
/// everything can be derived (no need for uncacheable stuff).
fn valid_for(&self, wm: WritingMode, longhand: LonghandId) -> bool {
fn valid_for(wm: WritingMode, longhand: LonghandId) -> bool {
// We only make sense on the inline axis.
match longhand {
// FIXME(emilio): The flex-basis thing is not quite clear...
@ -1018,7 +1018,7 @@ impl ToComputedValue for specified::MozLength {
.rule_cache_conditions
.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
if !ext.valid_for(
if !ExtremumLength::valid_for(
context.builder.writing_mode,
context.for_non_inherited_property.unwrap(),
) {
@ -1080,7 +1080,7 @@ impl ToComputedValue for specified::MaxLength {
.rule_cache_conditions
.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
if !ext.valid_for(
if !ExtremumLength::valid_for(
context.builder.writing_mode,
context.for_non_inherited_property.unwrap(),
) {

View file

@ -47,8 +47,6 @@ pub enum CalcNode {
pub enum CalcUnit {
/// `<number>`
Number,
/// `<integer>`
Integer,
/// `<length>`
Length,
/// `<percentage>`
@ -281,8 +279,7 @@ impl CalcNode {
let new_root = CalcNode::Mul(Box::new(root), Box::new(rhs));
root = new_root;
},
// TODO(emilio): Figure out why the `Integer` check.
Ok(&Token::Delim('/')) if expected_unit != CalcUnit::Integer => {
Ok(&Token::Delim('/')) => {
let rhs = Self::parse_one(context, input, expected_unit)?;
let new_root = CalcNode::Div(Box::new(root), Box::new(rhs));
root = new_root;
@ -532,10 +529,8 @@ impl CalcNode {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<CSSInteger, ParseError<'i>> {
Self::parse(context, input, CalcUnit::Integer)?
.to_number()
.map(|n| n as CSSInteger)
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
Self::parse_number(context, input)
.map(|n| n.round() as CSSInteger)
}
/// Convenience parsing function for `<length> | <percentage>`.

View file

@ -317,12 +317,12 @@ impl Color {
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
/// Returns false if the color is completely transparent, and
/// true otherwise.
pub fn is_non_transparent(&self) -> bool {
/// Returns true if the color is completely transparent, and false
/// otherwise.
pub fn is_transparent(&self) -> bool {
match *self {
Color::Numeric { ref parsed, .. } => parsed.alpha != 0,
_ => true,
Color::Numeric { ref parsed, .. } => parsed.alpha == 0,
_ => false,
}
}
}

View file

@ -5,7 +5,7 @@
use cssparser::{Parser, ParserInput};
use servo_arc::Arc;
use style::custom_properties::{Name, SpecifiedValue, CustomPropertiesMap, CustomPropertiesBuilder};
use style::properties::DeclaredValue;
use style::properties::CustomDeclarationValue;
use test::{self, Bencher};
fn cascade(
@ -21,7 +21,7 @@ fn cascade(
let mut builder = CustomPropertiesBuilder::new(inherited);
for &(ref name, ref val) in &values {
builder.cascade(name, DeclaredValue::Value(val));
builder.cascade(name, &CustomDeclarationValue::Value(val.clone()));
}
builder.build()

View file

@ -18,7 +18,7 @@ use style::context::QuirksMode;
use style::error_reporting::{ParseErrorReporter, ContextualParseError};
use style::media_queries::MediaList;
use style::properties::{CSSWideKeyword, CustomDeclaration};
use style::properties::{DeclaredValueOwned, Importance};
use style::properties::{CustomDeclarationValue, Importance};
use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
use style::properties::longhands::{self, animation_timing_function};
use style::shared_lock::SharedRwLock;
@ -113,7 +113,7 @@ fn test_parse_stylesheet() {
(
PropertyDeclaration::Custom(CustomDeclaration {
name: Atom::from("a"),
value: DeclaredValueOwned::CSSWideKeyword(CSSWideKeyword::Inherit),
value: CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit),
}),
Importance::Important,
),