diff options
author | bors-servo <infra@servo.org> | 2023-05-19 22:49:36 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-19 22:49:36 +0200 |
commit | 2426a38a4d11615d71c34649b83436aa1f109260 (patch) | |
tree | a5c682e2f3d93813aa5e42c8f50f3f6512b73574 | |
parent | 71c5bb02b41b8d98b1612563558c2c14b3e20b1a (diff) | |
parent | dbb92b99ad359634e8b84417b8264f84bfc5d056 (diff) | |
download | servo-2426a38a4d11615d71c34649b83436aa1f109260.tar.gz servo-2426a38a4d11615d71c34649b83436aa1f109260.zip |
Auto merge of #29748 - Loirooriol:sync, r=mrobinson
Backport several style changes from Gecko
<!-- Please describe your changes on the following line: -->
---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [ ] These changes fix #___ (GitHub issue number if applicable)
<!-- Either: -->
- [X] There are tests for these changes OR
- [ ] These changes do not require tests because ___
<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->
<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
101 files changed, 2068 insertions, 1558 deletions
diff --git a/Cargo.lock b/Cargo.lock index df7b8eb2465..18e9f495468 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6177,6 +6177,7 @@ dependencies = [ "log", "malloc_size_of", "malloc_size_of_derive", + "mime", "new_debug_unreachable", "num-derive", "num-integer", diff --git a/components/layout/generated_content.rs b/components/layout/generated_content.rs index d69ddd65991..89f65732761 100644 --- a/components/layout/generated_content.rs +++ b/components/layout/generated_content.rs @@ -272,8 +272,8 @@ impl<'a, 'b> ResolveGeneratedContentFragmentMutator<'a, 'b> { self.traversal.quote -= 1 } }, - GeneratedContentInfo::ContentItem(ContentItem::Url(..)) => { - unreachable!("Servo doesn't parse content: url(..) yet") + GeneratedContentInfo::ContentItem(ContentItem::Image(..)) => { + unreachable!("Servo doesn't parse content: url(..) nor image-set(..) yet") }, } }; diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index ab20c88c721..7b619dacf45 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -354,9 +354,9 @@ where attr_val.map_or("".to_string(), |s| s.to_string()), )); }, - ContentItem::Url(image_url) => { + ContentItem::Image(image) => { if let Some(replaced_content) = - ReplacedContent::from_image_url(element, context, image_url) + ReplacedContent::from_image(element, context, image) { vec.push(PseudoElementContentItem::Replaced(replaced_content)); } diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs index 50722dab917..642e83f7905 100644 --- a/components/layout_2020/replaced.rs +++ b/components/layout_2020/replaced.rs @@ -21,6 +21,7 @@ use std::fmt; use std::sync::{Arc, Mutex}; use style::properties::ComputedValues; use style::servo::url::ComputedUrl; +use style::values::computed::image::Image as ComputedImage; use style::values::computed::{Length, LengthOrAuto}; use style::values::CSSFloat; use style::Zero; @@ -184,6 +185,17 @@ impl ReplacedContent { None } + pub fn from_image<'dom>( + element: impl NodeExt<'dom>, + context: &LayoutContext, + image: &ComputedImage, + ) -> Option<Self> { + match image { + ComputedImage::Url(image_url) => Self::from_image_url(element, context, image_url), + _ => None, // TODO + } + } + fn flow_relative_intrinsic_size(&self, style: &ComputedValues) -> Vec2<Option<Length>> { let intrinsic_size = PhysicalSize::new(self.intrinsic.width, self.intrinsic.height); Vec2::from_physical_size(&intrinsic_size, style.writing_mode) diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index bf4ff1b85bc..1e8bd2848be 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -96,7 +96,7 @@ use std::time::Duration; use style::animation::{AnimationSetKey, DocumentAnimationSet, ElementAnimationSet}; use style::context::SharedStyleContext; use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; -use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode}; +use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; use style::driver; use style::error_reporting::RustLogReporter; use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL}; @@ -1285,11 +1285,7 @@ impl LayoutThread { ); // Flush shadow roots stylesheets if dirty. - document.flush_shadow_roots_stylesheets( - &self.stylist.device(), - document.quirks_mode(), - guards.author.clone(), - ); + document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author.clone()); let restyles = std::mem::take(&mut data.pending_restyles); debug!("Draining restyles: {}", restyles.len()); diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 60b00075ddb..b2392e441a1 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -82,7 +82,7 @@ use style::animation::DocumentAnimationSet; use style::context::{ QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext, }; -use style::dom::{TDocument, TElement, TNode}; +use style::dom::{TElement, TNode}; use style::driver; use style::error_reporting::RustLogReporter; use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL}; @@ -943,11 +943,7 @@ impl LayoutThread { } // Flush shadow roots stylesheets if dirty. - document.flush_shadow_roots_stylesheets( - &self.stylist.device(), - document.quirks_mode(), - guards.author.clone(), - ); + document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author.clone()); let restyles = std::mem::take(&mut data.pending_restyles); debug!("Draining restyles: {}", restyles.len()); diff --git a/components/net/mime_classifier.rs b/components/net/mime_classifier.rs index 2a32fcf9948..f3ced86dbc5 100644 --- a/components/net/mime_classifier.rs +++ b/components/net/mime_classifier.rs @@ -461,6 +461,8 @@ impl GroupedClassifier { fn image_classifer() -> GroupedClassifier { GroupedClassifier { byte_matchers: vec![ + // Keep this in sync with 'is_supported_mime_type' from + // components/style/servo/media_queries.rs Box::new(ByteMatcher::image_x_icon()), Box::new(ByteMatcher::image_x_icon_cursor()), Box::new(ByteMatcher::image_bmp()), diff --git a/components/script/dom/documentorshadowroot.rs b/components/script/dom/documentorshadowroot.rs index 43d345e5e01..51bce1c7b2d 100644 --- a/components/script/dom/documentorshadowroot.rs +++ b/components/script/dom/documentorshadowroot.rs @@ -20,11 +20,10 @@ use servo_arc::Arc; use servo_atoms::Atom; use std::collections::HashMap; use std::fmt; -use style::context::QuirksMode; use style::invalidation::media_queries::{MediaListKey, ToMediaListKey}; use style::media_queries::MediaList; use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; -use style::stylesheets::{CssRule, Origin, Stylesheet}; +use style::stylesheets::{Stylesheet, StylesheetContents}; #[derive(Clone, JSTraceable, MallocSizeOf)] #[unrooted_must_root_lint::must_root] @@ -48,19 +47,11 @@ impl PartialEq for StyleSheetInDocument { impl ToMediaListKey for StyleSheetInDocument { fn to_media_list_key(&self) -> MediaListKey { - self.sheet.to_media_list_key() + self.sheet.contents.to_media_list_key() } } impl ::style::stylesheets::StylesheetInDocument for StyleSheetInDocument { - fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin { - self.sheet.origin(guard) - } - - fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode { - self.sheet.quirks_mode(guard) - } - fn enabled(&self) -> bool { self.sheet.enabled() } @@ -69,8 +60,8 @@ impl ::style::stylesheets::StylesheetInDocument for StyleSheetInDocument { self.sheet.media(guard) } - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.sheet.rules(guard) + fn contents(&self) -> &StylesheetContents { + self.sheet.contents() } } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index de34eb401e0..d8b80df754a 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -3525,11 +3525,11 @@ impl Element { } pub fn read_write_state(&self) -> bool { - self.state.get().contains(ElementState::IN_READ_WRITE_STATE) + self.state.get().contains(ElementState::IN_READWRITE_STATE) } pub fn set_read_write_state(&self, value: bool) { - self.set_state(ElementState::IN_READ_WRITE_STATE, value) + self.set_state(ElementState::IN_READWRITE_STATE, value) } pub fn placeholder_shown_state(&self) -> bool { diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 0463fb14f0b..696e4566b63 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -297,7 +297,7 @@ impl HTMLInputElement { .clone(); HTMLInputElement { htmlelement: HTMLElement::new_inherited_with_state( - ElementState::IN_ENABLED_STATE | ElementState::IN_READ_WRITE_STATE, + ElementState::IN_ENABLED_STATE | ElementState::IN_READWRITE_STATE, local_name, prefix, document, diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index c6d9331532f..aee9c1eadff 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -148,7 +148,7 @@ impl HTMLTextAreaElement { .clone(); HTMLTextAreaElement { htmlelement: HTMLElement::new_inherited_with_state( - ElementState::IN_ENABLED_STATE | ElementState::IN_READ_WRITE_STATE, + ElementState::IN_ENABLED_STATE | ElementState::IN_READWRITE_STATE, local_name, prefix, document, diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs index c70c13d8cef..1fa9faae287 100644 --- a/components/script/dom/shadowroot.rs +++ b/components/script/dom/shadowroot.rs @@ -19,15 +19,13 @@ use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; use crate::dom::window::Window; use crate::stylesheet_set::StylesheetSetRef; use dom_struct::dom_struct; -use selectors::context::QuirksMode; use servo_arc::Arc; use servo_atoms::Atom; use style::author_styles::AuthorStyles; use style::dom::TElement; -use style::media_queries::Device; use style::shared_lock::SharedRwLockReadGuard; use style::stylesheets::Stylesheet; -use style::stylist::CascadeData; +use style::stylist::{CascadeData, Stylist}; /// Whether a shadow root hosts an User Agent widget. #[derive(JSTraceable, MallocSizeOf, PartialEq)] @@ -245,8 +243,7 @@ pub trait LayoutShadowRootHelpers<'dom> { fn get_style_data_for_layout(self) -> &'dom CascadeData; unsafe fn flush_stylesheets<E: TElement>( self, - device: &Device, - quirks_mode: QuirksMode, + stylist: &mut Stylist, guard: &SharedRwLockReadGuard, ); } @@ -277,13 +274,12 @@ impl<'dom> LayoutShadowRootHelpers<'dom> for LayoutDom<'dom, ShadowRoot> { #[allow(unsafe_code)] unsafe fn flush_stylesheets<E: TElement>( self, - device: &Device, - quirks_mode: QuirksMode, + stylist: &mut Stylist, guard: &SharedRwLockReadGuard, ) { let author_styles = self.unsafe_get().author_styles.borrow_mut_for_layout(); if author_styles.stylesheets.dirty() { - author_styles.flush::<E>(device, quirks_mode, guard); + author_styles.flush::<E>(stylist, guard); } } } diff --git a/components/script/layout_dom/document.rs b/components/script/layout_dom/document.rs index e723f731cab..392c1686f97 100644 --- a/components/script/layout_dom/document.rs +++ b/components/script/layout_dom/document.rs @@ -12,10 +12,10 @@ use script_layout_interface::wrapper_traits::LayoutDataTrait; use selectors::matching::QuirksMode; use std::marker::PhantomData; use style::dom::{TDocument, TNode}; -use style::media_queries::Device; use style::shared_lock::{ SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard as StyleSharedRwLockReadGuard, }; +use style::stylist::Stylist; // A wrapper around documents that ensures ayout can only ever access safe properties. pub struct ServoLayoutDocument<'dom, LayoutDataType: LayoutDataTrait> { @@ -90,8 +90,7 @@ impl<'ld, LayoutDataType: LayoutDataTrait> ServoLayoutDocument<'ld, LayoutDataTy pub fn flush_shadow_roots_stylesheets( &self, - device: &Device, - quirks_mode: QuirksMode, + stylist: &mut Stylist, guard: &StyleSharedRwLockReadGuard, ) { unsafe { @@ -100,7 +99,7 @@ impl<'ld, LayoutDataType: LayoutDataTrait> ServoLayoutDocument<'ld, LayoutDataTy } self.document.flush_shadow_roots_stylesheets(); for shadow_root in self.shadow_roots() { - shadow_root.flush_stylesheets(device, quirks_mode, guard); + shadow_root.flush_stylesheets(stylist, guard); } } } diff --git a/components/script/layout_dom/shadow_root.rs b/components/script/layout_dom/shadow_root.rs index 2fd1511ccfa..19336befe74 100644 --- a/components/script/layout_dom/shadow_root.rs +++ b/components/script/layout_dom/shadow_root.rs @@ -7,13 +7,11 @@ use crate::dom::shadowroot::{LayoutShadowRootHelpers, ShadowRoot}; use crate::layout_dom::ServoLayoutElement; use crate::layout_dom::ServoLayoutNode; use script_layout_interface::wrapper_traits::LayoutDataTrait; -use selectors::matching::QuirksMode; use std::fmt; use std::marker::PhantomData; use style::dom::TShadowRoot; -use style::media_queries::Device; use style::shared_lock::SharedRwLockReadGuard as StyleSharedRwLockReadGuard; -use style::stylist::CascadeData; +use style::stylist::{CascadeData, Stylist}; pub struct ServoShadowRoot<'dom, LayoutDataType: LayoutDataTrait> { /// The wrapped private DOM ShadowRoot. @@ -74,11 +72,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ServoShadowRoot<'dom, LayoutDataType pub unsafe fn flush_stylesheets( &self, - device: &Device, - quirks_mode: QuirksMode, + stylist: &mut Stylist, guard: &StyleSharedRwLockReadGuard, ) { self.shadow_root - .flush_stylesheets::<ServoLayoutElement<LayoutDataType>>(device, quirks_mode, guard) + .flush_stylesheets::<ServoLayoutElement<LayoutDataType>>(stylist, guard) } } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index e99b1b18412..0b8d7d2331e 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -212,6 +212,12 @@ macro_rules! with_all_bounds { /// pseudo-elements type PseudoElement: $($CommonBounds)* + PseudoElement<Impl = Self>; + + /// Whether attribute hashes should be collected for filtering + /// purposes. + fn should_collect_attr_hash(_name: &Self::LocalName) -> bool { + false + } } } } @@ -482,6 +488,34 @@ where Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => { class.precomputed_hash() }, + Component::AttributeInNoNamespace { ref local_name, .. } + if Impl::should_collect_attr_hash(local_name) => + { + // AttributeInNoNamespace is only used when local_name == + // local_name_lower. + local_name.precomputed_hash() + }, + Component::AttributeInNoNamespaceExists { + ref local_name, + ref local_name_lower, + .. + } => { + // Only insert the local-name into the filter if it's all + // lowercase. Otherwise we would need to test both hashes, and + // our data structures aren't really set up for that. + if local_name != local_name_lower || !Impl::should_collect_attr_hash(local_name) { + continue; + } + local_name.precomputed_hash() + }, + Component::AttributeOther(ref selector) => { + if selector.local_name != selector.local_name_lower || + !Impl::should_collect_attr_hash(&selector.local_name) + { + continue; + } + selector.local_name.precomputed_hash() + }, Component::Is(ref list) | Component::Where(ref list) => { // :where and :is OR their selectors, so we can't put any hash // in the filter if there's more than one selector, as that'd @@ -825,7 +859,7 @@ impl<'a, Impl: 'a + SelectorImpl> SelectorIter<'a, Impl> { #[inline] pub(crate) fn is_featureless_host_selector(&mut self) -> bool { self.selector_length() > 0 && - self.all(|component| matches!(*component, Component::Host(..))) && + self.all(|component| component.is_host()) && self.next_sequence().is_none() } @@ -1089,10 +1123,17 @@ pub enum Component<Impl: SelectorImpl> { impl<Impl: SelectorImpl> Component<Impl> { /// Returns true if this is a combinator. + #[inline] pub fn is_combinator(&self) -> bool { matches!(*self, Component::Combinator(_)) } + /// Returns true if this is a :host() selector. + #[inline] + pub fn is_host(&self) -> bool { + matches!(*self, Component::Host(..)) + } + /// Returns the value as a combinator if applicable, None otherwise. pub fn as_combinator(&self) -> Option<Combinator> { match *self { diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index a88ccf0fd88..70219416452 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -50,6 +50,7 @@ lazy_static = "1" log = { version = "0.4", features = ["std"] } malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = "0.1" +mime = "0.3.13" new_debug_unreachable = "1.0" num-derive = "0.3" num-integer = "0.1" diff --git a/components/style/author_styles.rs b/components/style/author_styles.rs index 58d9bda423a..dfd33711ed2 100644 --- a/components/style/author_styles.rs +++ b/components/style/author_styles.rs @@ -5,16 +5,16 @@ //! A set of author stylesheets and their computed representation, such as the //! ones used for ShadowRoot. -use crate::context::QuirksMode; use crate::dom::TElement; #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::invalidation::media_queries::ToMediaListKey; -use crate::media_queries::Device; use crate::shared_lock::SharedRwLockReadGuard; +use crate::stylist::Stylist; use crate::stylesheet_set::AuthorStylesheetSet; use crate::stylesheets::StylesheetInDocument; use crate::stylist::CascadeData; +use servo_arc::Arc; /// A set of author stylesheets and their computed representation, such as the /// ones used for ShadowRoot. @@ -27,7 +27,14 @@ where /// and all that stuff. pub stylesheets: AuthorStylesheetSet<S>, /// The actual cascade data computed from the stylesheets. - pub data: CascadeData, + #[ignore_malloc_size_of = "Measured as part of the stylist"] + pub data: Arc<CascadeData>, +} + +lazy_static! { + static ref EMPTY_CASCADE_DATA: Arc<CascadeData> = { + Arc::new_leaked(CascadeData::new()) + }; } impl<S> AuthorStyles<S> @@ -39,7 +46,7 @@ where pub fn new() -> Self { Self { stylesheets: AuthorStylesheetSet::new(), - data: CascadeData::new(), + data: EMPTY_CASCADE_DATA.clone(), } } @@ -50,8 +57,7 @@ where #[inline] pub fn flush<E>( &mut self, - device: &Device, - quirks_mode: QuirksMode, + stylist: &mut Stylist, guard: &SharedRwLockReadGuard, ) where E: TElement, @@ -61,10 +67,10 @@ where .stylesheets .flush::<E>(/* host = */ None, /* snapshot_map = */ None); - // Ignore OOM. - let _ = self - .data - .rebuild(device, quirks_mode, flusher.sheets, guard); + let result = stylist.rebuild_author_data(&self.data, flusher.sheets, guard); + if let Ok(Some(new_data)) = result { + self.data = new_data; + } } } diff --git a/components/style/bloom.rs b/components/style/bloom.rs index c17b31d1bee..1840c780506 100644 --- a/components/style/bloom.rs +++ b/components/style/bloom.rs @@ -102,6 +102,16 @@ impl<E: TElement> PushedElement<E> { } } +/// Returns whether the attribute name is excluded from the bloom filter. +/// +/// We do this for attributes that are very common but not commonly used in +/// selectors. +#[inline] +#[cfg(feature = "gecko")] +pub fn is_attr_name_excluded_from_filter(atom: &crate::Atom) -> bool { + *atom == atom!("class") || *atom == atom!("id") || *atom == atom!("style") +} + fn each_relevant_element_hash<E, F>(element: E, mut f: F) where E: TElement, @@ -115,6 +125,15 @@ where } element.each_class(|class| f(class.get_hash())); + + #[cfg(feature = "gecko")] + if static_prefs::pref!("layout.css.bloom-filter-attribute-names.enabled") { + element.each_attr_name(|name| { + if !is_attr_name_excluded_from_filter(name) { + f(name.get_hash()) + } + }); + } } impl<E: TElement> Drop for StyleBloom<E> { diff --git a/components/style/context.rs b/components/style/context.rs index 08fdb4d1eef..a7d5aa4a67b 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -116,21 +116,6 @@ impl Default for StyleSystemOptions { } } -impl StyleSystemOptions { - #[cfg(feature = "servo")] - /// On Gecko's nightly build? - pub fn is_nightly(&self) -> bool { - false - } - - #[cfg(feature = "gecko")] - /// On Gecko's nightly build? - #[inline] - pub fn is_nightly(&self) -> bool { - structs::GECKO_IS_NIGHTLY - } -} - /// A shared style context. /// /// There's exactly one of these during a given restyle traversal, and it's diff --git a/components/style/dom.rs b/components/style/dom.rs index c23b985a628..3d088956289 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -519,6 +519,12 @@ pub trait TElement: { } + /// Internal iterator for the attribute names of this element. + #[cfg(feature = "gecko")] + fn each_attr_name<F>(&self, callback: F) + where + F: FnMut(&AtomIdent); + /// Internal iterator for the part names that this element exports for a /// given part name. fn each_exported_part<F>(&self, _name: &AtomIdent, _callback: F) diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 0852b4574fa..8a9f4065b8b 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -54,86 +54,79 @@ bitflags! { /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-broken const IN_BROKEN_STATE = 1 << 14; /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-loading - const IN_LOADING_STATE = 1 << 17; + const IN_LOADING_STATE = 1 << 15; /// <https://html.spec.whatwg.org/multipage/#selector-required> - const IN_REQUIRED_STATE = 1 << 21; + const IN_REQUIRED_STATE = 1 << 16; /// <https://html.spec.whatwg.org/multipage/#selector-optional> - const IN_OPTIONAL_STATE = 1 << 22; - /// <https://html.spec.whatwg.org/multipage/#selector-read-write> - const IN_READ_WRITE_STATE = 1 << 22; + const IN_OPTIONAL_STATE = 1 << 17; /// <https://html.spec.whatwg.org/multipage/#selector-defined> - const IN_DEFINED_STATE = 1 << 23; + const IN_DEFINED_STATE = 1 << 18; /// <https://html.spec.whatwg.org/multipage/#selector-visited> - const IN_VISITED_STATE = 1 << 24; + const IN_VISITED_STATE = 1 << 19; /// <https://html.spec.whatwg.org/multipage/#selector-link> - const IN_UNVISITED_STATE = 1 << 25; + const IN_UNVISITED_STATE = 1 << 20; /// <https://drafts.csswg.org/selectors-4/#the-any-link-pseudo> const IN_VISITED_OR_UNVISITED_STATE = ElementState::IN_VISITED_STATE.bits | ElementState::IN_UNVISITED_STATE.bits; /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-drag-over - const IN_DRAGOVER_STATE = 1 << 26; + const IN_DRAGOVER_STATE = 1 << 21; /// <https://html.spec.whatwg.org/multipage/#selector-in-range> - const IN_INRANGE_STATE = 1 << 27; + const IN_INRANGE_STATE = 1 << 22; /// <https://html.spec.whatwg.org/multipage/#selector-out-of-range> - const IN_OUTOFRANGE_STATE = 1 << 28; + const IN_OUTOFRANGE_STATE = 1 << 23; /// <https://html.spec.whatwg.org/multipage/#selector-read-only> - const IN_READONLY_STATE = 1 << 29; + const IN_READONLY_STATE = 1 << 24; /// <https://html.spec.whatwg.org/multipage/#selector-read-write> - const IN_READWRITE_STATE = 1 << 30; + const IN_READWRITE_STATE = 1 << 25; /// <https://html.spec.whatwg.org/multipage/#selector-default> - const IN_DEFAULT_STATE = 1 << 31; + const IN_DEFAULT_STATE = 1 << 26; /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-submit-invalid - const IN_MOZ_SUBMITINVALID_STATE = 1 << 32; - /// Non-standard & undocumented. - const IN_OPTIMUM_STATE = 1 << 33; - /// Non-standard & undocumented. - const IN_SUB_OPTIMUM_STATE = 1 << 34; + const IN_MOZ_SUBMITINVALID_STATE = 1 << 27; /// Non-standard & undocumented. - const IN_SUB_SUB_OPTIMUM_STATE = 1 << 35; + const IN_OPTIMUM_STATE = 1 << 28; /// Non-standard & undocumented. - const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 36; + const IN_SUB_OPTIMUM_STATE = 1 << 29; /// Non-standard & undocumented. - const IN_STYLEEDITOR_TRANSITIONING_STATE = 1 << 37; + const IN_SUB_SUB_OPTIMUM_STATE = 1 << 30; /// Non-standard & undocumented. - const IN_INCREMENT_SCRIPT_LEVEL_STATE = 1 << 38; - /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-focusring - /// - /// But also https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo - const IN_FOCUSRING_STATE = 1 << 39; + const IN_INCREMENT_SCRIPT_LEVEL_STATE = 1 << 31; + /// <https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo> + const IN_FOCUSRING_STATE = 1 << 32; /// <https://drafts.csswg.org/selectors-4/#the-focus-within-pseudo> - const IN_FOCUS_WITHIN_STATE = 1 << 43; + const IN_FOCUS_WITHIN_STATE = 1 << 33; /// :dir matching; the states are used for dynamic change detection. /// State that elements that match :dir(ltr) are in. - const IN_LTR_STATE = 1 << 44; + const IN_LTR_STATE = 1 << 34; /// State that elements that match :dir(rtl) are in. - const IN_RTL_STATE = 1 << 45; + const IN_RTL_STATE = 1 << 35; /// State that HTML elements that have a "dir" attr are in. - const IN_HAS_DIR_ATTR_STATE = 1 << 46; + const IN_HAS_DIR_ATTR_STATE = 1 << 36; /// State that HTML elements with dir="ltr" (or something /// case-insensitively equal to "ltr") are in. - const IN_HAS_DIR_ATTR_LTR_STATE = 1 << 47; + const IN_HAS_DIR_ATTR_LTR_STATE = 1 << 37; /// State that HTML elements with dir="rtl" (or something /// case-insensitively equal to "rtl") are in. - const IN_HAS_DIR_ATTR_RTL_STATE = 1 << 48; + const IN_HAS_DIR_ATTR_RTL_STATE = 1 << 38; /// State that HTML <bdi> elements without a valid-valued "dir" attr or /// any HTML elements (including <bdi>) with dir="auto" (or something /// case-insensitively equal to "auto") are in. - const IN_HAS_DIR_ATTR_LIKE_AUTO_STATE = 1 << 49; + const IN_HAS_DIR_ATTR_LIKE_AUTO_STATE = 1 << 39; /// Non-standard & undocumented. - const IN_AUTOFILL_STATE = 1 << 50; + const IN_AUTOFILL_STATE = 1 << 40; /// Non-standard & undocumented. - const IN_AUTOFILL_PREVIEW_STATE = 1 << 51; + const IN_AUTOFILL_PREVIEW_STATE = 1 << 41; /// State that dialog element is modal, for centered alignment - /// - /// https://html.spec.whatwg.org/multipage/#centered-alignment - const IN_MODAL_DIALOG_STATE = 1 << 53; - - /// https://html.spec.whatwg.org/multipage/#inert-subtrees - const IN_MOZINERT_STATE = 1 << 54; + /// <https://html.spec.whatwg.org/multipage/#centered-alignment> + const IN_MODAL_DIALOG_STATE = 1 << 42; + /// <https://html.spec.whatwg.org/multipage/#inert-subtrees> + const IN_MOZINERT_STATE = 1 << 43; /// State for the topmost dialog element in top layer - const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 55; - /// Non-standard & undocumented. - const IN_HANDLER_NOPLUGINS = 1 << 56; + const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44; + /// Initially used for the devtools highlighter, but now somehow only + /// used for the devtools accessibility inspector. + const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45; + /// Used for the devtools style editor. Probably should go away. + const IN_STYLEEDITOR_TRANSITIONING_STATE = 1 << 46; } } diff --git a/components/style/error_reporting.rs b/components/style/error_reporting.rs index 6e63b0ad68a..eebb0901309 100644 --- a/components/style/error_reporting.rs +++ b/components/style/error_reporting.rs @@ -54,6 +54,8 @@ pub enum ContextualParseError<'a> { InvalidMediaRule(&'a str, ParseError<'a>), /// A value was not recognized. UnsupportedValue(&'a str, ParseError<'a>), + /// A never-matching `:host` selector was found. + NeverMatchingHostSelector(String), } impl<'a> fmt::Display for ContextualParseError<'a> { @@ -210,6 +212,9 @@ impl<'a> fmt::Display for ContextualParseError<'a> { parse_error_to_str(err, f) }, ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f), + ContextualParseError::NeverMatchingHostSelector(ref selector) => { + write!(f, ":host selector is not featureless: {}", selector) + } } } } diff --git a/components/style/font_face.rs b/components/style/font_face.rs index 884978c5c3b..5016f91182c 100644 --- a/components/style/font_face.rs +++ b/components/style/font_face.rs @@ -20,9 +20,13 @@ use crate::values::specified::font::SpecifiedFontStyle; #[cfg(feature = "gecko")] use crate::values::specified::font::SpecifiedFontVariationSettings; use crate::values::specified::font::{AbsoluteFontWeight, FontStretch}; +#[cfg(feature = "gecko")] +use crate::values::specified::font::MetricsOverride; use crate::values::specified::url::SpecifiedUrl; use crate::values::specified::Angle; #[cfg(feature = "gecko")] +use crate::values::specified::NonNegativePercentage; +#[cfg(feature = "gecko")] use cssparser::UnicodeRange; use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser}; use cssparser::{CowRcStr, SourceLocation}; @@ -194,7 +198,7 @@ impl FontStretchRange { fn compute_stretch(s: &FontStretch) -> f32 { match *s { FontStretch::Keyword(ref kw) => kw.compute().0, - FontStretch::Stretch(ref p) => p.get(), + FontStretch::Stretch(ref p) => p.0.get(), FontStretch::System(..) => unreachable!(), } } @@ -418,6 +422,18 @@ macro_rules! is_descriptor_enabled { ("font-variation-settings") => { static_prefs::pref!("layout.css.font-variations.enabled") }; + ("ascent-override") => { + static_prefs::pref!("layout.css.font-metrics-overrides.enabled") + }; + ("descent-override") => { + static_prefs::pref!("layout.css.font-metrics-overrides.enabled") + }; + ("line-gap-override") => { + static_prefs::pref!("layout.css.font-metrics-overrides.enabled") + }; + ("size-adjust") => { + static_prefs::pref!("layout.css.size-adjust.enabled") + }; ($name:tt) => { true }; @@ -576,6 +592,18 @@ font_face_descriptors! { /// The language override of this font face. "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue, + + /// The ascent override for this font face. + "ascent-override" ascent_override / mAscentOverride: MetricsOverride, + + /// The descent override for this font face. + "descent-override" descent_override / mDescentOverride: MetricsOverride, + + /// The line-gap override for this font face. + "line-gap-override" line_gap_override / mLineGapOverride: MetricsOverride, + + /// The size adjustment for this font face. + "size-adjust" size_adjust / mSizeAdjust: NonNegativePercentage, ] } diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index 0ce43bca46d..0689aa6c0c4 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -4,7 +4,6 @@ //! Data needed to style a Gecko document. -use crate::context::QuirksMode; use crate::dom::TElement; use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs::{self, RawServoStyleSet, ServoStyleSetSizes}; @@ -15,7 +14,7 @@ use crate::media_queries::{Device, MediaList}; use crate::properties::ComputedValues; use crate::selector_parser::SnapshotMap; use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; -use crate::stylesheets::{CssRule, Origin, StylesheetContents, StylesheetInDocument}; +use crate::stylesheets::{StylesheetContents, StylesheetInDocument}; use crate::stylist::Stylist; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use malloc_size_of::MallocSizeOfOps; @@ -69,16 +68,6 @@ impl GeckoStyleSheet { fn inner(&self) -> &StyleSheetInfo { unsafe { &*(self.raw().mInner as *const StyleSheetInfo) } } - - /// Gets the StylesheetContents for this stylesheet. - pub fn contents(&self) -> &StylesheetContents { - debug_assert!(!self.inner().mContents.mRawPtr.is_null()); - unsafe { - let contents = - (&**StylesheetContents::as_arc(&&*self.inner().mContents.mRawPtr)) as *const _; - &*contents - } - } } impl Drop for GeckoStyleSheet { @@ -95,14 +84,6 @@ impl Clone for GeckoStyleSheet { } impl StylesheetInDocument for GeckoStyleSheet { - fn origin(&self, _guard: &SharedRwLockReadGuard) -> Origin { - self.contents().origin - } - - fn quirks_mode(&self, _guard: &SharedRwLockReadGuard) -> QuirksMode { - self.contents().quirks_mode - } - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { use crate::gecko_bindings::structs::mozilla::dom::MediaList as DomMediaList; use std::mem; @@ -120,13 +101,19 @@ impl StylesheetInDocument for GeckoStyleSheet { // All the stylesheets Servo knows about are enabled, because that state is // handled externally by Gecko. + #[inline] fn enabled(&self) -> bool { true } #[inline] - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.contents().rules(guard) + fn contents(&self) -> &StylesheetContents { + debug_assert!(!self.inner().mContents.mRawPtr.is_null()); + unsafe { + let contents = + (&**StylesheetContents::as_arc(&&*self.inner().mContents.mRawPtr)) as *const _; + &*contents + } } } diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index bfc78b84b15..4c23620dd15 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -275,10 +275,6 @@ enum PrefersReducedMotion { Reduce, } -fn color_scheme_no_preference_enabled(_: &crate::parser::ParserContext) -> bool { - static_prefs::pref!("layout.css.prefers-color-scheme-no-preference.enabled") -} - /// Values for the prefers-color-scheme media feature. #[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] #[repr(u8)] @@ -286,8 +282,6 @@ fn color_scheme_no_preference_enabled(_: &crate::parser::ParserContext) -> bool pub enum PrefersColorScheme { Light, Dark, - #[parse(condition = "color_scheme_no_preference_enabled")] - NoPreference, } /// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion @@ -408,7 +402,46 @@ fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorSc unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document()) }; match query_value { Some(v) => prefers_color_scheme == v, - None => prefers_color_scheme != PrefersColorScheme::NoPreference, + None => true, + } +} + +/// Values for the -moz-toolbar-prefers-color-scheme media feature. +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)] +#[repr(u8)] +enum ToolbarPrefersColorScheme { + Dark, + Light, + System, +} + +/// The color-scheme of the toolbar in the current Firefox theme. This is based +/// on a pref managed by the front-end. +fn eval_toolbar_prefers_color_scheme(d: &Device, query_value: Option<ToolbarPrefersColorScheme>) -> bool { + let toolbar_value = match static_prefs::pref!("browser.theme.toolbar-theme") { + 0 => ToolbarPrefersColorScheme::Dark, + 1 => ToolbarPrefersColorScheme::Light, + _ => ToolbarPrefersColorScheme::System, + }; + + let query_value = match query_value { + Some(v) => v, + None => return true, + }; + + if query_value == toolbar_value { + return true; + } + + if toolbar_value != ToolbarPrefersColorScheme::System { + return false; + } + + // System might match light and dark as well. + match query_value { + ToolbarPrefersColorScheme::Dark => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Dark)), + ToolbarPrefersColorScheme::Light => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Light)), + ToolbarPrefersColorScheme::System => true, } } @@ -541,22 +574,6 @@ fn eval_moz_is_resource_document( query_value.map_or(is_resource_doc, |v| v == is_resource_doc) } -fn eval_system_metric( - device: &Device, - query_value: Option<bool>, - metric: Atom, - accessible_from_content: bool, -) -> bool { - let supports_metric = unsafe { - bindings::Gecko_MediaFeatures_HasSystemMetric( - device.document(), - metric.as_ptr(), - accessible_from_content, - ) - }; - query_value.map_or(supports_metric, |v| v == supports_metric) -} - fn eval_moz_os_version( device: &Device, query_value: Option<Atom>, @@ -573,15 +590,63 @@ fn eval_moz_os_version( query_value.as_ptr() == os_version } -macro_rules! system_metric_feature { - ($feature_name:expr) => {{ - fn __eval(device: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool { - eval_system_metric( - device, - query_value, - $feature_name, - /* accessible_from_content = */ false, - ) +fn get_lnf_int(int_id: i32) -> i32 { + unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } +} + +fn get_lnf_int_as_bool(int_id: i32) -> bool { + get_lnf_int(int_id) != 0 +} + +fn get_scrollbar_start_backward(int_id: i32) -> bool { + (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartBackward as i32) != 0 +} + +fn get_scrollbar_start_forward(int_id: i32) -> bool { + (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_StartForward as i32) != 0 +} + +fn get_scrollbar_end_backward(int_id: i32) -> bool { + (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndBackward as i32) != 0 +} + +fn get_scrollbar_end_forward(int_id: i32) -> bool { + (get_lnf_int(int_id) & bindings::LookAndFeel_eScrollArrow_EndForward as i32) != 0 +} + +macro_rules! lnf_int_feature { + ($feature_name:expr, $int_id:ident, $get_value:ident) => {{ + fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool { + let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32); + query_value.map_or(value, |v| v == value) + } + + feature!( + $feature_name, + AllowsRanges::No, + Evaluator::BoolInteger(__eval), + ParsingRequirements::CHROME_AND_UA_ONLY, + ) + }}; + ($feature_name:expr, $int_id:ident) => {{ + lnf_int_feature!($feature_name, $int_id, get_lnf_int_as_bool) + }}; +} + +/// bool pref-based features are an slightly less convenient to start using +/// version of @supports -moz-bool-pref, but with some benefits, mainly that +/// they can support dynamic changes, and don't require a pref lookup every time +/// they're used. +/// +/// In order to use them you need to make sure that the pref defined as a static +/// pref, with `rust: true`. The feature name needs to be defined in +/// `StaticAtoms.py` just like the others. In order to support dynamic changes, +/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp +macro_rules! bool_pref_feature { + ($feature_name:expr, $pref:tt) => {{ + fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool { + let value = static_prefs::pref!($pref); + query_value.map_or(value, |v| v == value) } feature!( @@ -598,7 +663,7 @@ macro_rules! system_metric_feature { /// 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; 56] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 62] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -808,27 +873,40 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 56] = [ Evaluator::BoolInteger(eval_moz_non_native_content_theme), 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-big-sur-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")), + feature!( + atom!("-moz-toolbar-prefers-color-scheme"), + AllowsRanges::No, + keyword_evaluator!(eval_toolbar_prefers_color_scheme, ToolbarPrefersColorScheme), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), + + lnf_int_feature!(atom!("-moz-scrollbar-start-backward"), ScrollArrowStyle, get_scrollbar_start_backward), + lnf_int_feature!(atom!("-moz-scrollbar-start-forward"), ScrollArrowStyle, get_scrollbar_start_forward), + lnf_int_feature!(atom!("-moz-scrollbar-end-backward"), ScrollArrowStyle, get_scrollbar_end_backward), + lnf_int_feature!(atom!("-moz-scrollbar-end-forward"), ScrollArrowStyle, get_scrollbar_end_forward), + lnf_int_feature!(atom!("-moz-scrollbar-thumb-proportional"), ScrollSliderStyle), + lnf_int_feature!(atom!("-moz-overlay-scrollbars"), UseOverlayScrollbars), + lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), + lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), + lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), + lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme), + lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar), + lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), + lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), + lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), + lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled), + lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable), + lnf_int_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default"), GTKCSDHideTitlebarByDefault), + lnf_int_feature!(atom!("-moz-gtk-csd-transparent-background"), GTKCSDTransparentBackground), + lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), + lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), + lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), + lnf_int_feature!(atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement), + lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), + + bool_pref_feature!(atom!("-moz-proton"), "browser.proton.enabled"), + bool_pref_feature!(atom!("-moz-proton-modals"), "browser.proton.modals.enabled"), + bool_pref_feature!(atom!("-moz-proton-contextmenus"), "browser.proton.contextmenus.enabled"), + bool_pref_feature!(atom!("-moz-proton-doorhangers"), "browser.proton.doorhangers.enabled"), + bool_pref_feature!(atom!("-moz-proton-places-tooltip"), "browser.proton.places-tooltip.enabled"), ]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 08ef628f1ff..5d960a8e9e1 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -432,4 +432,11 @@ impl Device { }; SideOffsets2D::new(top, right, bottom, left) } + + /// Returns true if the given MIME type is supported + pub fn is_supported_mime_type(&self, mime_type: &str) -> bool { + unsafe { + bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32) + } + } } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index a9083581234..b2fe46a9a2c 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -247,6 +247,11 @@ impl ::selectors::SelectorImpl for SelectorImpl { type PseudoElement = PseudoElement; type NonTSPseudoClass = NonTSPseudoClass; + + fn should_collect_attr_hash(name: &AtomIdent) -> bool { + static_prefs::pref!("layout.css.bloom-filter-attribute-names.enabled") && + !crate::bloom::is_attr_name_excluded_from_filter(name) + } } impl<'a> SelectorParser<'a> { diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 7d4b4e03f73..3f23981a823 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -570,7 +570,7 @@ impl<'le> GeckoElement<'le> { } #[inline(always)] - fn attrs(&self) -> &[structs::AttrArray_InternalAttr] { + fn non_mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] { unsafe { let attrs = match self.0.mAttrs.mImpl.mPtr.as_ref() { Some(attrs) => attrs, @@ -582,11 +582,28 @@ impl<'le> GeckoElement<'le> { } #[inline(always)] + fn mapped_attrs(&self) -> &[structs::AttrArray_InternalAttr] { + unsafe { + let attrs = match self.0.mAttrs.mImpl.mPtr.as_ref() { + Some(attrs) => attrs, + None => return &[], + }; + + let attrs = match attrs.mMappedAttrs.as_ref() { + Some(attrs) => attrs, + None => return &[], + }; + + attrs.mBuffer.as_slice(attrs.mAttrCount as usize) + } + } + + #[inline(always)] fn get_part_attr(&self) -> Option<&structs::nsAttrValue> { if !self.has_part_attr() { return None; } - snapshot_helpers::find_attr(self.attrs(), &atom!("part")) + snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("part")) } #[inline(always)] @@ -602,7 +619,7 @@ impl<'le> GeckoElement<'le> { } } - snapshot_helpers::find_attr(self.attrs(), &atom!("class")) + snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("class")) } #[inline] @@ -1167,7 +1184,7 @@ impl<'le> TElement for GeckoElement<'le> { #[inline] fn exports_any_part(&self) -> bool { - snapshot_helpers::find_attr(self.attrs(), &atom!("exportparts")).is_some() + snapshot_helpers::find_attr(self.non_mapped_attrs(), &atom!("exportparts")).is_some() } // FIXME(emilio): we should probably just return a reference to the Atom. @@ -1177,7 +1194,25 @@ impl<'le> TElement for GeckoElement<'le> { return None; } - snapshot_helpers::get_id(self.attrs()) + snapshot_helpers::get_id(self.non_mapped_attrs()) + } + + fn each_attr_name<F>(&self, mut callback: F) + where + F: FnMut(&AtomIdent), + { + for attr in self.non_mapped_attrs().iter().chain(self.mapped_attrs().iter()) { + let is_nodeinfo = attr.mName.mBits & 1 != 0; + unsafe { + let atom = if is_nodeinfo { + let node_info = &*((attr.mName.mBits & !1) as *const structs::NodeInfo); + node_info.mInner.mName + } else { + attr.mName.mBits as *const nsAtom + }; + AtomIdent::with(atom, |a| callback(a)) + } + } } fn each_class<F>(&self, callback: F) @@ -1197,7 +1232,7 @@ impl<'le> TElement for GeckoElement<'le> { where F: FnMut(&AtomIdent), { - snapshot_helpers::each_exported_part(self.attrs(), name, callback) + snapshot_helpers::each_exported_part(self.non_mapped_attrs(), name, callback) } fn each_part<F>(&self, callback: F) @@ -2058,7 +2093,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { return false; } - let element_id = match snapshot_helpers::get_id(self.attrs()) { + let element_id = match snapshot_helpers::get_id(self.non_mapped_attrs()) { Some(id) => id, None => return false, }; @@ -2078,7 +2113,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { #[inline] fn imported_part(&self, name: &AtomIdent) -> Option<AtomIdent> { - snapshot_helpers::imported_part(self.attrs(), name) + snapshot_helpers::imported_part(self.non_mapped_attrs(), name) } #[inline(always)] diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index 29e0962a897..a7c2b04df13 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -185,7 +185,7 @@ pub struct DocumentStateDependency { /// In particular, we want to lookup as few things as possible to get the fewer /// selectors the better, so this looks up by id, class, or looks at the list of /// state/other attribute affecting selectors. -#[derive(Debug, MallocSizeOf)] +#[derive(Clone, Debug, MallocSizeOf)] pub struct InvalidationMap { /// A map from a given class name to all the selectors with that class /// selector. diff --git a/components/style/invalidation/element/invalidator.rs b/components/style/invalidation/element/invalidator.rs index c148b63e3e3..79131a8008f 100644 --- a/components/style/invalidation/element/invalidator.rs +++ b/components/style/invalidation/element/invalidator.rs @@ -20,10 +20,10 @@ pub trait InvalidationProcessor<'a, E> where E: TElement, { - /// Whether an invalidation that contains only an eager pseudo-element - /// selector like ::before or ::after triggers invalidation of the element - /// that would originate it. - fn invalidates_on_eager_pseudo_element(&self) -> bool { + /// Whether an invalidation that contains only a pseudo-element selector + /// like ::before or ::after triggers invalidation of the element that would + /// originate it. + fn invalidates_on_pseudo_element(&self) -> bool { false } @@ -878,76 +878,32 @@ where .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. + if matches!(next_combinator, Combinator::PseudoElement) && + self.processor.invalidates_on_pseudo_element() + { + // We need to invalidate the element whenever pseudos change, for + // two reasons: // - // 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. + // * Eager pseudo styles are stored as part of the originating + // element's computed style. // - // Also, this could be more fine grained now (perhaps a - // RESTYLE_PSEUDOS hint?). + // * Lazy pseudo-styles might be cached on the originating + // element's pseudo-style cache. // - // 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; - } - } + // This could be more fine-grained (perhaps with a RESTYLE_PSEUDOS + // hint?). + // + // Note that we'll also restyle the pseudo-element because it would + // match this invalidation. + // + // FIXME: For non-element-backed pseudos this is still not quite + // correct. For example for ::selection even though we invalidate + // the style properly there's nothing that triggers a repaint + // necessarily. Though this matches old Gecko behavior, and the + // ::selection implementation needs to change significantly anyway + // to implement https://github.com/w3c/csswg-drafts/issues/2474 for + // example. + invalidated_self = true; } debug!( diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index 128feae76be..bbb1fb46a80 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -158,10 +158,10 @@ impl<'a, 'b: 'a, E: 'a> InvalidationProcessor<'a, E> where E: TElement, { - /// We need to invalidate style on an eager pseudo-element, in order to - /// process changes that could otherwise end up in ::before or ::after - /// content being generated. - fn invalidates_on_eager_pseudo_element(&self) -> bool { + /// We need to invalidate style on pseudo-elements, in order to process + /// changes that could otherwise end up in ::before or ::after content being + /// generated, and invalidate lazy pseudo caches. + fn invalidates_on_pseudo_element(&self) -> bool { true } diff --git a/components/style/invalidation/media_queries.rs b/components/style/invalidation/media_queries.rs index 75149a02891..6928b29d3d9 100644 --- a/components/style/invalidation/media_queries.rs +++ b/components/style/invalidation/media_queries.rs @@ -8,7 +8,7 @@ use crate::context::QuirksMode; use crate::media_queries::Device; use crate::shared_lock::SharedRwLockReadGuard; use crate::stylesheets::{DocumentRule, ImportRule, MediaRule}; -use crate::stylesheets::{NestedRuleIterationCondition, Stylesheet, SupportsRule}; +use crate::stylesheets::{NestedRuleIterationCondition, StylesheetContents, SupportsRule}; use fxhash::FxHashSet; /// A key for a given media query result. @@ -43,13 +43,13 @@ pub trait ToMediaListKey: Sized { } } -impl ToMediaListKey for Stylesheet {} +impl ToMediaListKey for StylesheetContents {} impl ToMediaListKey for ImportRule {} impl ToMediaListKey for MediaRule {} /// A struct that holds the result of a media query evaluation pass for the /// media queries that evaluated successfully. -#[derive(Debug, MallocSizeOf, PartialEq)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq)] pub struct EffectiveMediaQueryResults { /// The set of media lists that matched last time. set: FxHashSet<MediaListKey>, diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs index 41be485d5c0..1658439da21 100644 --- a/components/style/media_queries/media_feature_expression.rs +++ b/components/style/media_queries/media_feature_expression.rs @@ -196,23 +196,37 @@ fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, () _ => return Err(()), } }; - Ok(Some(match first_delim { - '=' => Operator::Equal, - '>' => { - if input.try_parse(|i| i.expect_delim('=')).is_ok() { - Operator::GreaterThanEqual - } else { - Operator::GreaterThan - } - }, - '<' => { - if input.try_parse(|i| i.expect_delim('=')).is_ok() { - Operator::LessThanEqual - } else { - Operator::LessThan - } - }, + let operator = match first_delim { + '=' => return Ok(Some(Operator::Equal)), + '>' => Operator::GreaterThan, + '<' => Operator::LessThan, _ => return Err(()), + }; + + // https://drafts.csswg.org/mediaqueries-4/#mq-syntax: + // + // No whitespace is allowed between the “<” or “>” + // <delim-token>s and the following “=” <delim-token>, if it’s + // present. + // + // TODO(emilio): Maybe we should ignore comments as well? + // https://github.com/w3c/csswg-drafts/issues/6248 + let parsed_equal = input.try_parse(|i| { + let t = i.next_including_whitespace().map_err(|_| ())?; + if !matches!(t, Token::Delim('=')) { + return Err(()) + } + Ok(()) + }).is_ok(); + + if !parsed_equal { + return Ok(Some(operator)); + } + + Ok(Some(match operator { + Operator::GreaterThan => Operator::GreaterThanEqual, + Operator::LessThan => Operator::LessThanEqual, + _ => unreachable!(), })) } @@ -221,7 +235,11 @@ fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool { #[cfg(feature = "gecko")] { if *feature == atom!("forced-colors") { - return !static_prefs::pref!("layout.css.forced-colors.enabled"); + // forced-colors is always enabled in the ua and chrome. On + // the web it is hidden behind a preference, which is defaulted + // to 'true' as of bug 1659511. + return !context.in_ua_or_chrome_sheet() && + !static_prefs::pref!("layout.css.forced-colors.enabled"); } // prefers-contrast is always enabled in the ua and chrome. On // the web it is hidden behind a preference. diff --git a/components/style/properties/build.py b/components/style/properties/build.py index 97ad10408ea..6df38fcefe3 100644 --- a/components/style/properties/build.py +++ b/components/style/properties/build.py @@ -37,6 +37,7 @@ STYLE_STRUCT_LIST = [ "list", "margin", "outline", + "page", "padding", "position", "svg", diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index f7ab9f95fde..9c3e6ebd85e 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -379,13 +379,15 @@ where type DeclarationsToApplyUnlessOverriden = SmallVec<[PropertyDeclaration; 2]>; fn tweak_when_ignoring_colors( - builder: &StyleBuilder, + context: &computed::Context, longhand_id: LonghandId, origin: Origin, declaration: &mut Cow<PropertyDeclaration>, declarations_to_apply_unless_overriden: &mut DeclarationsToApplyUnlessOverriden, ) { use crate::values::specified::Color; + use crate::values::computed::ToComputedValue; + use cssparser::RGBA; if !longhand_id.ignored_when_document_colors_disabled() { return; @@ -399,39 +401,25 @@ fn tweak_when_ignoring_colors( // Don't override background-color on ::-moz-color-swatch. It is set as an // author style (via the style attribute), but it's pretty important for it // to show up for obvious reasons :) - if builder.pseudo.map_or(false, |p| p.is_color_swatch()) && + if context.builder.pseudo.map_or(false, |p| p.is_color_swatch()) && longhand_id == LonghandId::BackgroundColor { return; } - fn alpha_channel(color: &Color) -> u8 { - match *color { - // Seems safe enough to assume that the default color and system - // colors are opaque in HCM, though maybe we shouldn't asume the - // later? - #[cfg(feature = "gecko")] - Color::InheritFromBodyQuirk | Color::System(..) => 255, - // We don't have the actual color here, but since except for color: - // transparent we force opaque text colors, it seems sane to do - // this. You can technically fool this bit of code with: - // - // color: transparent; background-color: currentcolor; - // - // but this is best-effort, and that seems unlikely to happen in - // practice. - Color::CurrentColor => 255, - // Complex colors are results of interpolation only and probably - // shouldn't show up around here in HCM, but we've always treated - // them as opaque effectively so keep doing it. - Color::Complex { .. } => 255, - Color::Numeric { ref parsed, .. } => parsed.alpha, - } + fn alpha_channel(color: &Color, context: &computed::Context) -> u8 { + // We assume here currentColor is opaque. + let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255)); + color.alpha } // A few special-cases ahead. match **declaration { PropertyDeclaration::BackgroundColor(ref color) => { + // We honor system colors. + if color.is_system() { + return; + } // For background-color, we revert or initial-with-preserved-alpha // otherwise, this is needed to preserve semi-transparent // backgrounds. @@ -440,24 +428,27 @@ fn tweak_when_ignoring_colors( // should consider not doing that even if it causes some issues like // bug 1625036, or finding a performant way to preserve the original // widget background color's rgb channels but not alpha... - let alpha = alpha_channel(color); + let alpha = alpha_channel(color, context); if alpha != 0 { - let mut color = builder.device.default_background_color(); + let mut color = context.builder.device.default_background_color(); color.alpha = alpha; declarations_to_apply_unless_overriden .push(PropertyDeclaration::BackgroundColor(color.into())) } }, PropertyDeclaration::Color(ref color) => { - // We honor color: transparent, and "revert-or-initial" otherwise. - if alpha_channel(&color.0) == 0 { + // We honor color: transparent and system colors. + if color.0.is_system() { + return; + } + if alpha_channel(&color.0, context) == 0 { return; } // If the inherited color would be transparent, but we would // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. - if builder.get_parent_inherited_text().clone_color().alpha == 0 { - let color = builder.device.default_color(); + if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { + let color = context.builder.device.default_color(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), )) @@ -631,7 +622,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // properties that are marked as ignored in that mode. if ignore_colors { tweak_when_ignoring_colors( - &self.context.builder, + &self.context, longhand_id, origin, &mut declaration, diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index 047d129a2ff..577547d099d 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -19,7 +19,6 @@ COUNTED_UNKNOWN_PROPERTIES = [ "text-size-adjust", "-webkit-font-feature-settings", "-webkit-user-drag", - "size", "-webkit-clip-path", "orphans", "widows", diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 4fe90fb560b..8a523ccb66f 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -261,6 +261,11 @@ class Property(object): self.extra_prefixes = parse_property_aliases(extra_prefixes) self.flags = flags.split() if flags else [] + def rule_types_allowed_names(self): + for name in RULE_VALUES: + if self.rule_types_allowed & RULE_VALUES[name] != 0: + yield name + def experimental(self, engine): if engine == "gecko": return bool(self.gecko_pref) @@ -478,6 +483,7 @@ class Longhand(Property): "LineBreak", "MasonryAutoFlow", "MozForceBrokenImageIcon", + "text::MozControlCharacterVisibility", "MozListReversed", "MathDepth", "MozScriptMinSize", @@ -495,6 +501,7 @@ class Longhand(Property): "Percentage", "PositiveIntegerOrNone", "Resize", + "RubyPosition", "SVGOpacity", "SVGPaintOrder", "ScrollSnapAlign", @@ -505,6 +512,7 @@ class Longhand(Property): "TextAlignLast", "TextDecorationLine", "TextEmphasisPosition", + "TextJustify", "TextTransform", "TextUnderlinePosition", "TouchAction", @@ -597,6 +605,11 @@ class Alias(object): def type(): return "alias" + def rule_types_allowed_names(self): + for name in RULE_VALUES: + if self.rule_types_allowed & RULE_VALUES[name] != 0: + yield name + def experimental(self, engine): if engine == "gecko": return bool(self.gecko_pref) @@ -866,6 +879,7 @@ class PropertyRestrictions: def marker(data): return set( [ + "white-space", "color", "text-combine-upright", "text-transform", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 9bb13eb62b0..7a864c06d27 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -781,6 +781,9 @@ fn static_assert() { % endfor </%self:impl_trait> +<%self:impl_trait style_struct_name="Page"> +</%self:impl_trait> + <% skip_position_longhands = " ".join(x.ident for x in SIDES) %> <%self:impl_trait style_struct_name="Position" skip_longhands="${skip_position_longhands} diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 30eaac343ac..ecd714cebfc 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -554,7 +554,7 @@ keyword = keyword=Keyword(name, values, **keyword_kwargs) %> <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> - use crate::properties::longhands::system_font::SystemFont; + use crate::values::specified::font::SystemFont; pub mod computed_value { #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] @@ -707,8 +707,7 @@ </%def> <%def name="single_keyword(name, values, vector=False, - extra_specified=None, needs_conversion=False, - gecko_pref_controlled_initial_value=None, **kwargs)"> + needs_conversion=False, **kwargs)"> <% keyword_kwargs = {a: kwargs.pop(a, None) for a in [ 'gecko_constant_prefix', @@ -725,11 +724,13 @@ ]} %> - <%def name="inner_body(keyword, extra_specified=None, needs_conversion=False, - gecko_pref_controlled_initial_value=None)"> - <%def name="variants(variants, include_aliases)"> - % for variant in variants: - % if include_aliases: + <%def name="inner_body(keyword, needs_conversion=False)"> + pub use self::computed_value::T as SpecifiedValue; + pub mod computed_value { + #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] + #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + pub enum T { + % for variant in keyword.values_for(engine): <% aliases = [] for alias, v in keyword.aliases_for(engine).items(): @@ -739,56 +740,16 @@ % if aliases: #[parse(aliases = "${','.join(sorted(aliases))}")] % endif - % endif ${to_camel_case(variant)}, % endfor - </%def> - % if extra_specified: - #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] - #[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToCss, - ToShmem, - )] - pub enum SpecifiedValue { - ${variants(keyword.values_for(engine) + extra_specified.split(), bool(extra_specified))} - } - % else: - pub use self::computed_value::T as SpecifiedValue; - % endif - pub mod computed_value { - #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] - #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, PartialEq, ToCss, ToResolvedValue)] - % if not extra_specified: - #[derive(Parse, SpecifiedValueInfo, ToComputedValue, ToShmem)] - % endif - pub enum T { - ${variants(data.longhands_by_name[name].keyword.values_for(engine), not extra_specified)} } } #[inline] pub fn get_initial_value() -> computed_value::T { - % if engine == "gecko" and gecko_pref_controlled_initial_value: - if static_prefs::pref!("${gecko_pref_controlled_initial_value.split('=')[0]}") { - return computed_value::T::${to_camel_case(gecko_pref_controlled_initial_value.split('=')[1])}; - } - % endif computed_value::T::${to_camel_case(values.split()[0])} } #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { - % if engine == "gecko" and gecko_pref_controlled_initial_value: - if static_prefs::pref!("${gecko_pref_controlled_initial_value.split('=')[0]}") { - return SpecifiedValue::${to_camel_case(gecko_pref_controlled_initial_value.split('=')[1])}; - } - % endif SpecifiedValue::${to_camel_case(values.split()[0])} } #[inline] @@ -799,10 +760,7 @@ % if needs_conversion: <% - conversion_values = keyword.values_for(engine) - if extra_specified: - conversion_values += extra_specified.split() - conversion_values += keyword.aliases_for(engine).keys() + conversion_values = keyword.values_for(engine) + list(keyword.aliases_for(engine).keys()) %> ${gecko_keyword_conversion(keyword, values=conversion_values)} % endif @@ -817,8 +775,7 @@ % else: <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> ${inner_body(Keyword(name, values, **keyword_kwargs), - extra_specified=extra_specified, needs_conversion=needs_conversion, - gecko_pref_controlled_initial_value=gecko_pref_controlled_initial_value)} + needs_conversion=needs_conversion)} % if caller: ${caller.body()} % endif diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 73a2ec87065..f2d0234a918 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -48,7 +48,6 @@ ${helpers.single_keyword( engines="gecko servo-2013 servo-2020" animation_value_type="discrete" gecko_enum_prefix="StylePositionProperty" - flags="CREATES_STACKING_CONTEXT ABSPOS_CB" spec="https://drafts.csswg.org/css-position/#position-property" servo_restyle_damage="rebuild_and_reflow" > @@ -330,7 +329,7 @@ ${helpers.predefined_type( engines="gecko servo-2013 servo-2020", extra_prefixes=transform_extra_prefixes, animation_value_type="ComputedValue", - flags="CREATES_STACKING_CONTEXT FIXPOS_CB CAN_ANIMATE_ON_COMPOSITOR", + flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-transforms/#propdef-transform", servo_restyle_damage="reflow_out_of_flow", )} @@ -342,7 +341,7 @@ ${helpers.predefined_type( engines="gecko servo-2013", animation_value_type="ComputedValue", boxed=True, - flags="CREATES_STACKING_CONTEXT FIXPOS_CB CAN_ANIMATE_ON_COMPOSITOR", + flags="CAN_ANIMATE_ON_COMPOSITOR", gecko_pref="layout.css.individual-transform.enabled", spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", servo_restyle_damage = "reflow_out_of_flow", @@ -355,7 +354,7 @@ ${helpers.predefined_type( engines="gecko servo-2013", animation_value_type="ComputedValue", boxed=True, - flags="CREATES_STACKING_CONTEXT FIXPOS_CB CAN_ANIMATE_ON_COMPOSITOR", + flags="CAN_ANIMATE_ON_COMPOSITOR", gecko_pref="layout.css.individual-transform.enabled", spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", servo_restyle_damage = "reflow_out_of_flow", @@ -368,7 +367,7 @@ ${helpers.predefined_type( engines="gecko servo-2013", animation_value_type="ComputedValue", boxed=True, - flags="CREATES_STACKING_CONTEXT FIXPOS_CB CAN_ANIMATE_ON_COMPOSITOR", + flags="CAN_ANIMATE_ON_COMPOSITOR", gecko_pref="layout.css.individual-transform.enabled", spec="https://drafts.csswg.org/css-transforms-2/#individual-transforms", servo_restyle_damage="reflow_out_of_flow", @@ -382,7 +381,7 @@ ${helpers.predefined_type( engines="gecko", animation_value_type="ComputedValue", gecko_pref="layout.css.motion-path.enabled", - flags="CREATES_STACKING_CONTEXT FIXPOS_CB CAN_ANIMATE_ON_COMPOSITOR", + flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.fxtf.org/motion-1/#offset-path-property", servo_restyle_damage="reflow_out_of_flow" )} @@ -477,7 +476,6 @@ ${helpers.single_keyword( "auto isolate", engines="gecko", spec="https://drafts.fxtf.org/compositing/#isolation", - flags="CREATES_STACKING_CONTEXT", gecko_enum_prefix="StyleIsolation", animation_value_type="discrete", )} @@ -530,7 +528,6 @@ ${helpers.predefined_type( gecko_ffi_name="mChildPerspective", spec="https://drafts.csswg.org/css-transforms/#perspective", extra_prefixes=transform_extra_prefixes, - flags="CREATES_STACKING_CONTEXT FIXPOS_CB", animation_value_type="AnimatedPerspective", servo_restyle_damage = "reflow_out_of_flow", )} @@ -574,7 +571,6 @@ ${helpers.predefined_type( engines="gecko servo-2013 servo-2020", spec="https://drafts.csswg.org/css-transforms-2/#transform-style-property", extra_prefixes=transform_extra_prefixes, - flags="CREATES_STACKING_CONTEXT FIXPOS_CB", animation_value_type="discrete", servo_restyle_damage = "reflow_out_of_flow", )} @@ -598,7 +594,6 @@ ${helpers.predefined_type( "specified::Contain::empty()", engines="gecko", animation_value_type="none", - flags="CREATES_STACKING_CONTEXT FIXPOS_CB", spec="https://drafts.csswg.org/css-contain/#contain-property", )} diff --git a/components/style/properties/longhands/effects.mako.rs b/components/style/properties/longhands/effects.mako.rs index 00ede146cd4..5470c74d47c 100644 --- a/components/style/properties/longhands/effects.mako.rs +++ b/components/style/properties/longhands/effects.mako.rs @@ -13,7 +13,7 @@ ${helpers.predefined_type( "1.0", engines="gecko servo-2013 servo-2020", animation_value_type="ComputedValue", - flags="CREATES_STACKING_CONTEXT CAN_ANIMATE_ON_COMPOSITOR", + flags="CAN_ANIMATE_ON_COMPOSITOR", spec="https://drafts.csswg.org/css-color/#transparency", servo_restyle_damage = "reflow_out_of_flow", )} @@ -56,7 +56,6 @@ ${helpers.predefined_type( animation_value_type="AnimatedFilterList", vector_animation_type="with_zero", extra_prefixes="webkit", - flags="CREATES_STACKING_CONTEXT FIXPOS_CB", spec="https://drafts.fxtf.org/filters/#propdef-filter", )} @@ -71,7 +70,6 @@ ${helpers.predefined_type( separator="Space", animation_value_type="AnimatedFilterList", vector_animation_type="with_zero", - flags="CREATES_STACKING_CONTEXT FIXPOS_CB", gecko_pref="layout.css.backdrop-filter.enabled", spec="https://drafts.fxtf.org/filter-effects-2/#propdef-backdrop-filter", )} @@ -84,6 +82,5 @@ ${helpers.single_keyword( engines="gecko servo-2013 servo-2020", gecko_enum_prefix="StyleBlend", animation_value_type="discrete", - flags="CREATES_STACKING_CONTEXT", spec="https://drafts.fxtf.org/compositing/#propdef-mix-blend-mode", )} diff --git a/components/style/properties/longhands/font.mako.rs b/components/style/properties/longhands/font.mako.rs index 1fb52a01c77..e84a5574d1a 100644 --- a/components/style/properties/longhands/font.mako.rs +++ b/components/style/properties/longhands/font.mako.rs @@ -288,208 +288,153 @@ ${helpers.predefined_type( )} % if engine == "gecko": - pub mod system_font { - //! We deal with system fonts here - //! - //! System fonts can only be set as a group via the font shorthand. - //! They resolve at compute time (not parse time -- this lets the - //! browser respond to changes to the OS font settings). - //! - //! While Gecko handles these as a separate property and keyword - //! values on each property indicating that the font should be picked - //! from the -x-system-font property, we avoid this. Instead, - //! each font longhand has a special SystemFont variant which contains - //! the specified system font. When the cascade function (in helpers) - //! detects that a value has a system font, it will resolve it, and - //! cache it on the ComputedValues. After this, it can be just fetched - //! whenever a font longhand on the same element needs the system font. - //! - //! When a longhand property is holding a SystemFont, it's serialized - //! to an empty string as if its value comes from a shorthand with - //! variable reference. We may want to improve this behavior at some - //! point. See also https://github.com/w3c/csswg-drafts/issues/1586. - - use cssparser::{Parser, ToCss}; - use crate::values::computed::font::GenericFontFamily; - use crate::properties::longhands; - use std::fmt; - use std::hash::{Hash, Hasher}; - use style_traits::ParseError; - use crate::values::computed::{ToComputedValue, Context}; - - <% - system_fonts = """caption icon menu message-box small-caption status-bar - -moz-window -moz-document -moz-workspace -moz-desktop - -moz-info -moz-dialog -moz-button -moz-pull-down-menu - -moz-list -moz-field""".split() - kw_font_props = """font_variant_caps - font_kerning font_variant_position font_variant_ligatures - font_variant_east_asian font_variant_numeric - font_optical_sizing""".split() - kw_cast = """font_variant_caps font_kerning font_variant_position - font_optical_sizing""".split() - %> - #[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, - SpecifiedValueInfo, ToCss, ToShmem)] - pub enum SystemFont { - % for font in system_fonts: - ${to_camel_case(font)}, - % endfor - } - - // ComputedValues are compared at times - // so we need these impls. We don't want to - // add Eq to Number (which contains a float) - // so instead we have an eq impl which skips the - // cached values - impl PartialEq for ComputedSystemFont { - fn eq(&self, other: &Self) -> bool { - self.system_font == other.system_font - } - } - impl Eq for ComputedSystemFont {} - - impl Hash for ComputedSystemFont { - fn hash<H: Hasher>(&self, hasher: &mut H) { - self.system_font.hash(hasher) - } - } - - impl ToComputedValue for SystemFont { - type ComputedValue = ComputedSystemFont; - - fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { - use crate::gecko_bindings::bindings; - use crate::gecko_bindings::structs::{LookAndFeel_FontID, nsFont}; - use std::mem; - use crate::values::computed::Percentage; - use crate::values::specified::font::KeywordInfo; - use crate::values::computed::font::{FontFamily, FontSize, FontStretch, FontStyle, FontFamilyList}; - use crate::values::generics::NonNegative; - - let id = match *self { - % for font in system_fonts: - SystemFont::${to_camel_case(font)} => { - LookAndFeel_FontID::${to_camel_case(font.replace("-moz-", ""))} - } - % endfor - }; - - let mut system = mem::MaybeUninit::<nsFont>::uninit(); - let system = unsafe { - bindings::Gecko_nsFont_InitSystem( - system.as_mut_ptr(), - id as i32, - cx.style().get_font().gecko(), - cx.device().document() - ); - &mut *system.as_mut_ptr() - }; - let font_weight = longhands::font_weight::computed_value::T::from_gecko_weight(system.weight); - let font_stretch = FontStretch(NonNegative(Percentage(unsafe { - bindings::Gecko_FontStretch_ToFloat(system.stretch) - }))); - let font_style = FontStyle::from_gecko(system.style); - let ret = ComputedSystemFont { - font_family: FontFamily { - families: FontFamilyList::SharedFontList( - unsafe { system.fontlist.mFontlist.mBasePtr.to_safe() } - ), - is_system_font: true, - }, - font_size: FontSize { - size: NonNegative(cx.maybe_zoom_text(system.size.0)), - keyword_info: KeywordInfo::none() - }, - font_weight, - font_stretch, - font_style, - font_size_adjust: longhands::font_size_adjust::computed_value - ::T::from_gecko_adjust(system.sizeAdjust), - % for kwprop in kw_font_props: - ${kwprop}: longhands::${kwprop}::computed_value::T::from_gecko_keyword( - system.${to_camel_case_lower(kwprop.replace('font_', ''))} - % if kwprop in kw_cast: - as u32 - % endif - ), - % endfor - font_language_override: longhands::font_language_override::computed_value - ::T(system.languageOverride), - font_feature_settings: longhands::font_feature_settings::get_initial_value(), - font_variation_settings: longhands::font_variation_settings::get_initial_value(), - font_variant_alternates: longhands::font_variant_alternates::get_initial_value(), - system_font: *self, - default_font_type: system.fontlist.mDefaultFontType, - }; - unsafe { bindings::Gecko_nsFont_Destroy(system); } - ret - } - - fn from_computed_value(_: &ComputedSystemFont) -> Self { - unreachable!() - } +pub mod system_font { + //! We deal with system fonts here + //! + //! System fonts can only be set as a group via the font shorthand. + //! They resolve at compute time (not parse time -- this lets the + //! browser respond to changes to the OS font settings). + //! + //! While Gecko handles these as a separate property and keyword + //! values on each property indicating that the font should be picked + //! from the -x-system-font property, we avoid this. Instead, + //! each font longhand has a special SystemFont variant which contains + //! the specified system font. When the cascade function (in helpers) + //! detects that a value has a system font, it will resolve it, and + //! cache it on the ComputedValues. After this, it can be just fetched + //! whenever a font longhand on the same element needs the system font. + //! + //! When a longhand property is holding a SystemFont, it's serialized + //! to an empty string as if its value comes from a shorthand with + //! variable reference. We may want to improve this behavior at some + //! point. See also https://github.com/w3c/csswg-drafts/issues/1586. + + use crate::values::computed::font::GenericFontFamily; + use crate::properties::longhands; + use std::hash::{Hash, Hasher}; + use crate::values::computed::{ToComputedValue, Context}; + use crate::values::specified::font::SystemFont; + + <% + kw_font_props = """font_variant_caps + font_kerning font_variant_position font_variant_ligatures + font_variant_east_asian font_variant_numeric + font_optical_sizing""".split() + kw_cast = """font_variant_caps font_kerning font_variant_position + font_optical_sizing""".split() + %> + + // ComputedValues are compared at times + // so we need these impls. We don't want to + // add Eq to Number (which contains a float) + // so instead we have an eq impl which skips the + // cached values + impl PartialEq for ComputedSystemFont { + fn eq(&self, other: &Self) -> bool { + self.system_font == other.system_font } + } + impl Eq for ComputedSystemFont {} - #[inline] - /// Compute and cache a system font - /// - /// Must be called before attempting to compute a system font - /// specified value - pub fn resolve_system_font(system: SystemFont, context: &mut Context) { - // Checking if context.cached_system_font.is_none() isn't enough, - // if animating from one system font to another the cached system font - // may change - if Some(system) != context.cached_system_font.as_ref().map(|x| x.system_font) { - let computed = system.to_computed_value(context); - context.cached_system_font = Some(computed); - } + impl Hash for ComputedSystemFont { + fn hash<H: Hasher>(&self, hasher: &mut H) { + self.system_font.hash(hasher) } + } - #[derive(Clone, Debug)] - pub struct ComputedSystemFont { - % for name in SYSTEM_FONT_LONGHANDS: - pub ${name}: longhands::${name}::computed_value::T, - % endfor - pub system_font: SystemFont, - pub default_font_type: GenericFontFamily, + impl ToComputedValue for SystemFont { + type ComputedValue = ComputedSystemFont; + + fn to_computed_value(&self, cx: &Context) -> Self::ComputedValue { + use crate::gecko_bindings::bindings; + use crate::gecko_bindings::structs::nsFont; + use std::mem; + use crate::values::computed::Percentage; + use crate::values::specified::font::KeywordInfo; + use crate::values::computed::font::{FontFamily, FontSize, FontStretch, FontStyle, FontFamilyList}; + use crate::values::generics::NonNegative; + + let mut system = mem::MaybeUninit::<nsFont>::uninit(); + let system = unsafe { + bindings::Gecko_nsFont_InitSystem( + system.as_mut_ptr(), + *self, + cx.style().get_font().gecko(), + cx.device().document() + ); + &mut *system.as_mut_ptr() + }; + let font_weight = longhands::font_weight::computed_value::T::from_gecko_weight(system.weight); + let font_stretch = FontStretch(NonNegative(Percentage(unsafe { + bindings::Gecko_FontStretch_ToFloat(system.stretch) + }))); + let font_style = FontStyle::from_gecko(system.style); + let ret = ComputedSystemFont { + font_family: FontFamily { + families: FontFamilyList::SharedFontList( + unsafe { system.fontlist.mFontlist.mBasePtr.to_safe() } + ), + is_system_font: true, + }, + font_size: FontSize { + size: NonNegative(cx.maybe_zoom_text(system.size.0)), + keyword_info: KeywordInfo::none() + }, + font_weight, + font_stretch, + font_style, + font_size_adjust: longhands::font_size_adjust::computed_value + ::T::from_gecko_adjust(system.sizeAdjust), + % for kwprop in kw_font_props: + ${kwprop}: longhands::${kwprop}::computed_value::T::from_gecko_keyword( + system.${to_camel_case_lower(kwprop.replace('font_', ''))} + % if kwprop in kw_cast: + as u32 + % endif + ), + % endfor + font_language_override: longhands::font_language_override::computed_value + ::T(system.languageOverride), + font_feature_settings: longhands::font_feature_settings::get_initial_value(), + font_variation_settings: longhands::font_variation_settings::get_initial_value(), + font_variant_alternates: longhands::font_variant_alternates::get_initial_value(), + system_font: *self, + default_font_type: system.fontlist.mDefaultFontType, + }; + unsafe { bindings::Gecko_nsFont_Destroy(system); } + ret } - impl SystemFont { - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { - try_match_ident_ignore_ascii_case! { input, - % for font in system_fonts: - "${font}" => Ok(SystemFont::${to_camel_case(font)}), - % endfor - } - } + fn from_computed_value(_: &ComputedSystemFont) -> Self { + unreachable!() } + } - impl ToCss for SystemFont { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - // We may want to do something better in the future, see - // w3c/csswg-drafts#1586. - dest.write_str("-moz-use-system-font") - } + #[inline] + /// Compute and cache a system font + /// + /// Must be called before attempting to compute a system font + /// specified value + pub fn resolve_system_font(system: SystemFont, context: &mut Context) { + // Checking if context.cached_system_font.is_none() isn't enough, + // if animating from one system font to another the cached system font + // may change + if Some(system) != context.cached_system_font.as_ref().map(|x| x.system_font) { + let computed = system.to_computed_value(context); + context.cached_system_font = Some(computed); } } -% else: - pub mod system_font { - use cssparser::Parser; - - // We don't parse system fonts, but in the interest of not littering - // a lot of code with `if engine == "gecko"` conditionals, we have a - // dummy system font module that does nothing - - #[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] - /// void enum for system font, can never exist - pub enum SystemFont {} - impl SystemFont { - pub fn parse(_: &mut Parser) -> Result<Self, ()> { - Err(()) - } - } + + #[derive(Clone, Debug)] + pub struct ComputedSystemFont { + % for name in SYSTEM_FONT_LONGHANDS: + pub ${name}: longhands::${name}::computed_value::T, + % endfor + pub system_font: SystemFont, + pub default_font_type: GenericFontFamily, } + +} % endif ${helpers.single_keyword( diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs index 32f9819a42d..cea3fde021a 100644 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ b/components/style/properties/longhands/inherited_box.mako.rs @@ -84,8 +84,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "image-orientation", - "none from-image", - gecko_pref_controlled_initial_value="layout.css.image-orientation.initial-from-image=from-image", + "from-image none", engines="gecko", gecko_enum_prefix="StyleImageOrientation", animation_value_type="discrete", diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index 0ce27e0e18f..246e7ff36a6 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -96,45 +96,16 @@ ${helpers.predefined_type( servo_restyle_damage="rebuild_and_reflow", )} -// TODO(pcwalton): Support `text-justify: distribute`. -<%helpers:single_keyword - name="text-justify" - values="auto none inter-word" +${helpers.predefined_type( + "text-justify", + "TextJustify", + "computed::TextJustify::Auto", engines="gecko servo-2013 servo-2020", servo_2020_pref="layout.2020.unimplemented", - extra_gecko_values="inter-character" - extra_specified="${'distribute' if engine == 'gecko' else ''}" - gecko_enum_prefix="StyleTextJustify" - animation_value_type="discrete" - spec="https://drafts.csswg.org/css-text/#propdef-text-justify" - servo_restyle_damage="rebuild_and_reflow" -> - % if engine == 'gecko': - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value(&self, _: &Context) -> computed_value::T { - match *self { - % for value in "Auto None InterCharacter InterWord".split(): - SpecifiedValue::${value} => computed_value::T::${value}, - % endfor - // https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute - SpecifiedValue::Distribute => computed_value::T::InterCharacter, - } - } - - #[inline] - fn from_computed_value(computed: &computed_value::T) -> SpecifiedValue { - match *computed { - % for value in "Auto None InterCharacter InterWord".split(): - computed_value::T::${value} => SpecifiedValue::${value}, - % endfor - } - } - } - % endif -</%helpers:single_keyword> + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-text/#propdef-text-justify", + servo_restyle_damage="rebuild_and_reflow", +)} ${helpers.predefined_type( "text-align-last", @@ -329,13 +300,13 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-ruby/#ruby-align-property", )} -${helpers.single_keyword( +${helpers.predefined_type( "ruby-position", - "over under", + "RubyPosition", + "computed::RubyPosition::AlternateOver", engines="gecko", - animation_value_type="discrete", spec="https://drafts.csswg.org/css-ruby/#ruby-position-property", - gecko_enum_prefix="StyleRubyPosition", + animation_value_type="discrete", )} // CSS Writing Modes Module Level 3 @@ -360,15 +331,13 @@ ${helpers.single_keyword( servo_restyle_damage="rebuild_and_reflow", )} -${helpers.single_keyword( +${helpers.predefined_type( "-moz-control-character-visibility", - "hidden visible", + "text::MozControlCharacterVisibility", + "Default::default()", engines="gecko", - gecko_enum_prefix="StyleControlCharacterVisibility", - gecko_pref_controlled_initial_value="layout.css.control-characters.visible=visible", animation_value_type="none", - gecko_ffi_name="mControlCharacterVisibility", - spec="Nonstandard", + spec="Nonstandard" )} // text underline offset diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index 233ef365329..f08abbc6235 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -73,13 +73,26 @@ ${helpers.single_keyword( ${helpers.predefined_type( "caret-color", + "color::CaretColor", + "generics::color::CaretColor::auto()", + engines="gecko", + spec="https://drafts.csswg.org/css-ui/#caret-color", + animation_value_type="CaretColor", + boxed=True, + ignored_when_colors_disabled=True, +)} + +${helpers.predefined_type( + "accent-color", "ColorOrAuto", "generics::color::ColorOrAuto::Auto", engines="gecko", - spec="https://drafts.csswg.org/css-ui/#caret-color", - animation_value_type="AnimatedCaretColor", + spec="https://drafts.csswg.org/css-ui-4/#widget-accent", + gecko_pref="layout.css.accent-color.enabled", + animation_value_type="ColorOrAuto", boxed=True, ignored_when_colors_disabled=True, + has_effect_on_gecko_scrollbars=False, )} ${helpers.predefined_type( diff --git a/components/style/properties/longhands/outline.mako.rs b/components/style/properties/longhands/outline.mako.rs index 5c3dd15cbb6..e5cd09fbe82 100644 --- a/components/style/properties/longhands/outline.mako.rs +++ b/components/style/properties/longhands/outline.mako.rs @@ -51,6 +51,7 @@ ${helpers.predefined_type( engines="gecko", boxed=True, animation_value_type="BorderCornerRadius", + gecko_pref="layout.css.moz-outline-radius.enabled", spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-outline-radius)", )} % endfor diff --git a/components/style/properties/longhands/page.mako.rs b/components/style/properties/longhands/page.mako.rs new file mode 100644 index 00000000000..298456cb753 --- /dev/null +++ b/components/style/properties/longhands/page.mako.rs @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +<%namespace name="helpers" file="/helpers.mako.rs" /> +<% from data import PAGE_RULE %> + +<% data.new_style_struct("Page", inherited=False) %> + +${helpers.predefined_type( + "size", + "PageSize", + "computed::PageSize::auto()", + engines="gecko", + gecko_pref="layout.css.page-size.enabled", + initial_specified_value="specified::PageSize::auto()", + spec="https://drafts.csswg.org/css-page-3/#page-size-prop", + boxed=True, + animation_value_type="none", + rule_types_allowed=PAGE_RULE, +)} diff --git a/components/style/properties/longhands/position.mako.rs b/components/style/properties/longhands/position.mako.rs index df118faed1a..1795bde7e5f 100644 --- a/components/style/properties/longhands/position.mako.rs +++ b/components/style/properties/longhands/position.mako.rs @@ -60,7 +60,6 @@ ${helpers.predefined_type( "computed::ZIndex::auto()", engines="gecko servo-2013 servo-2020", spec="https://www.w3.org/TR/CSS2/visuren.html#z-index", - flags="CREATES_STACKING_CONTEXT", animation_value_type="ComputedValue", )} diff --git a/components/style/properties/longhands/svg.mako.rs b/components/style/properties/longhands/svg.mako.rs index a09f3e7b656..f6128693582 100644 --- a/components/style/properties/longhands/svg.mako.rs +++ b/components/style/properties/longhands/svg.mako.rs @@ -81,7 +81,6 @@ ${helpers.predefined_type( "generics::basic_shape::ClipPath::None", engines="gecko", animation_value_type="basic_shape::ClipPath", - flags="CREATES_STACKING_CONTEXT", spec="https://drafts.fxtf.org/css-masking/#propdef-clip-path", )} @@ -183,7 +182,6 @@ ${helpers.predefined_type( vector=True, extra_prefixes="webkit", animation_value_type="discrete", - flags="CREATES_STACKING_CONTEXT", )} ${helpers.predefined_type( diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 1dfdfcc1a73..400eaedf577 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -53,7 +53,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "-moz-window-shadow", - "default none menu tooltip sheet", + "default none menu tooltip sheet cliprounded", engines="gecko", gecko_ffi_name="mWindowShadow", gecko_enum_prefix="StyleWindowShadow", diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 95d59df064d..99d283da46c 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -32,7 +32,6 @@ use crate::computed_value_flags::*; use crate::hash::FxHashMap; use crate::media_queries::Device; use crate::parser::ParserContext; -use crate::properties::longhands::system_font::SystemFont; use crate::selector_parser::PseudoElement; #[cfg(feature = "servo")] use servo_config::prefs; use style_traits::{CssWriter, KeywordsCollectFn, ParseError, ParsingMode}; @@ -44,6 +43,7 @@ use crate::values::generics::text::LineHeight; use crate::values::{computed, resolved}; use crate::values::computed::NonNegativeLength; use crate::values::serialize_atom_name; +use crate::values::specified::font::SystemFont; use crate::rule_tree::StrongRuleNode; use crate::Zero; use crate::str::{CssString, CssStringWriter}; @@ -1068,28 +1068,20 @@ impl CSSWideKeyword { bitflags! { /// A set of flags for properties. pub struct PropertyFlags: u16 { - /// This property requires a stacking context. - const CREATES_STACKING_CONTEXT = 1 << 0; - /// This property has values that can establish a containing block for - /// fixed positioned and absolutely positioned elements. - const FIXPOS_CB = 1 << 1; - /// This property has values that can establish a containing block for - /// absolutely positioned elements. - const ABSPOS_CB = 1 << 2; /// This longhand property applies to ::first-letter. - const APPLIES_TO_FIRST_LETTER = 1 << 3; + const APPLIES_TO_FIRST_LETTER = 1 << 1; /// This longhand property applies to ::first-line. - const APPLIES_TO_FIRST_LINE = 1 << 4; + const APPLIES_TO_FIRST_LINE = 1 << 2; /// This longhand property applies to ::placeholder. - const APPLIES_TO_PLACEHOLDER = 1 << 5; + const APPLIES_TO_PLACEHOLDER = 1 << 3; /// This longhand property applies to ::cue. - const APPLIES_TO_CUE = 1 << 6; + const APPLIES_TO_CUE = 1 << 4; /// This longhand property applies to ::marker. - const APPLIES_TO_MARKER = 1 << 7; + const APPLIES_TO_MARKER = 1 << 5; /// This property is a legacy shorthand. /// /// https://drafts.csswg.org/css-cascade/#legacy-shorthand - const IS_LEGACY_SHORTHAND = 1 << 8; + const IS_LEGACY_SHORTHAND = 1 << 6; /* The following flags are currently not used in Rust code, they * only need to be listed in corresponding properties so that @@ -1757,7 +1749,21 @@ impl UnparsedValue { shorthand_cache.insert((shorthand, longhand), declaration); } - Cow::Borrowed(&shorthand_cache[&(shorthand, longhand_id)]) + let key = (shorthand, longhand_id); + match shorthand_cache.get(&key) { + Some(decl) => Cow::Borrowed(decl), + None => { + // FIXME: We should always have the key here but it seems + // sometimes we don't, see bug 1696409. + #[cfg(feature = "gecko")] + { + if structs::GECKO_IS_NIGHTLY { + panic!("Expected {:?} to be in the cache but it was not!", key); + } + } + invalid_at_computed_value_time() + } + } } } diff --git a/components/style/properties/shorthands/font.mako.rs b/components/style/properties/shorthands/font.mako.rs index 176164c4b1e..26205c2ea9c 100644 --- a/components/style/properties/shorthands/font.mako.rs +++ b/components/style/properties/shorthands/font.mako.rs @@ -34,11 +34,11 @@ use crate::parser::Parse; use crate::properties::longhands::{font_family, font_style, font_weight, font_stretch}; use crate::properties::longhands::font_variant_caps; - #[cfg(feature = "gecko")] - use crate::properties::longhands::system_font::SystemFont; use crate::values::specified::text::LineHeight; use crate::values::specified::FontSize; use crate::values::specified::font::{FontStretch, FontStretchKeyword}; + #[cfg(feature = "gecko")] + use crate::values::specified::font::SystemFont; <% gecko_sub_properties = "kerning language_override size_adjust \ @@ -197,7 +197,7 @@ let font_stretch = match *self.font_stretch { FontStretch::Keyword(kw) => kw, FontStretch::Stretch(percentage) => { - match FontStretchKeyword::from_percentage(percentage.get()) { + match FontStretchKeyword::from_percentage(percentage.0.get()) { Some(kw) => kw, None => return Ok(()), } @@ -289,7 +289,9 @@ % for p in subprops_for_value_info: ${p}::collect_completion_keywords(f); % endfor - <longhands::system_font::SystemFont as SpecifiedValueInfo>::collect_completion_keywords(f); + % if engine == "gecko": + <SystemFont as SpecifiedValueInfo>::collect_completion_keywords(f); + % endif } } </%helpers:shorthand> diff --git a/components/style/properties/shorthands/outline.mako.rs b/components/style/properties/shorthands/outline.mako.rs index 75b5fcb717b..db397498919 100644 --- a/components/style/properties/shorthands/outline.mako.rs +++ b/components/style/properties/shorthands/outline.mako.rs @@ -83,6 +83,7 @@ <%helpers:shorthand name="-moz-outline-radius" engines="gecko" + gecko_pref="layout.css.moz-outline-radius.enabled" sub_properties="${' '.join( '-moz-outline-radius-%s' % corner for corner in ['topleft', 'topright', 'bottomright', 'bottomleft'] diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 4ea8cc1a018..40806ed47af 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -94,7 +94,7 @@ pub trait SelectorMapEntry: Sized + Clone { /// * https://bugzilla.mozilla.org/show_bug.cgi?id=681755 /// /// TODO: Tune the initial capacity of the HashMap -#[derive(Debug, MallocSizeOf)] +#[derive(Clone, Debug, MallocSizeOf)] pub struct SelectorMap<T: 'static> { /// Rules that have `:root` selectors. pub root: SmallVec<[T; 1]>, @@ -615,7 +615,7 @@ fn find_bucket<'a>( } /// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode. -#[derive(Debug, MallocSizeOf)] +#[derive(Clone, Debug, MallocSizeOf)] pub struct MaybeCaseInsensitiveHashMap<K: PrecomputedHash + Hash + Eq, V: 'static>( PrecomputedHashMap<K, V>, ); diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index e3841c15482..67d4d4f6981 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -108,7 +108,7 @@ pub enum PseudoElementCascadeType { } /// A per-pseudo map, from a given pseudo to a `T`. -#[derive(MallocSizeOf)] +#[derive(Clone, MallocSizeOf)] pub struct PerPseudoElementMap<T> { entries: [Option<T>; PSEUDO_COUNT], } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index 4b535a9b875..cee76053ec0 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -19,6 +19,7 @@ use app_units::Au; use cssparser::RGBA; use euclid::default::Size2D as UntypedSize2D; use euclid::{Scale, SideOffsets2D, Size2D}; +use mime::Mime; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use style_traits::viewport::ViewportConstraints; use style_traits::{CSSPixel, DevicePixel}; @@ -202,6 +203,23 @@ impl Device { pub fn safe_area_insets(&self) -> SideOffsets2D<f32, CSSPixel> { SideOffsets2D::zero() } + + /// Returns true if the given MIME type is supported + pub fn is_supported_mime_type(&self, mime_type: &str) -> bool { + match mime_type.parse::<Mime>() { + Ok(m) => { + // Keep this in sync with 'image_classifer' from + // components/net/mime_classifier.rs + m == mime::IMAGE_BMP + || m == mime::IMAGE_GIF + || m == mime::IMAGE_PNG + || m == mime::IMAGE_JPEG + || m == "image/x-icon" + || m == "image/webp" + } + _ => false, + } + } } /// https://drafts.csswg.org/mediaqueries-4/#width diff --git a/components/style/servo/selector_parser.rs b/components/style/servo/selector_parser.rs index ce61c172553..7d5b8c5772a 100644 --- a/components/style/servo/selector_parser.rs +++ b/components/style/servo/selector_parser.rs @@ -372,7 +372,7 @@ impl NonTSPseudoClass { Disabled => ElementState::IN_DISABLED_STATE, Checked => ElementState::IN_CHECKED_STATE, Indeterminate => ElementState::IN_INDETERMINATE_STATE, - ReadOnly | ReadWrite => ElementState::IN_READ_WRITE_STATE, + ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE, PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE, Target => ElementState::IN_TARGET_STATE, diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index 9e858a4566c..e2937e06e75 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -184,7 +184,9 @@ pub struct SheetCollectionFlusher<'a, S> where S: StylesheetInDocument + PartialEq + 'static, { - iter: slice::IterMut<'a, StylesheetSetEntry<S>>, + // TODO: This can be made an iterator again once + // https://github.com/rust-lang/rust/pull/82771 lands on stable. + entries: &'a mut [StylesheetSetEntry<S>], validity: DataValidity, dirty: bool, } @@ -204,32 +206,42 @@ where pub fn data_validity(&self) -> DataValidity { self.validity } + + /// Returns an iterator over the remaining list of sheets to consume. + pub fn sheets<'b>(&'b self) -> impl Iterator<Item = &'b S> { + self.entries.iter().map(|entry| &entry.sheet) + } } -impl<'a, S> Iterator for SheetCollectionFlusher<'a, S> +impl<'a, S> SheetCollectionFlusher<'a, S> where S: StylesheetInDocument + PartialEq + 'static, { - type Item = (&'a S, SheetRebuildKind); - - fn next(&mut self) -> Option<Self::Item> { - loop { - let potential_sheet = self.iter.next()?; - + /// Iterates over all sheets and values that we have to invalidate. + /// + /// TODO(emilio): This would be nicer as an iterator but we can't do that + /// until https://github.com/rust-lang/rust/pull/82771 stabilizes. + /// + /// Since we don't have a good use-case for partial iteration, this does the + /// trick for now. + pub fn each(self, mut callback: impl FnMut(&S, SheetRebuildKind) -> bool) { + for potential_sheet in self.entries.iter_mut() { let committed = mem::replace(&mut potential_sheet.committed, true); - if !committed { + let rebuild_kind = if !committed { // If the sheet was uncommitted, we need to do a full rebuild // anyway. - return Some((&potential_sheet.sheet, SheetRebuildKind::Full)); - } - - let rebuild_kind = match self.validity { - DataValidity::Valid => continue, - DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly, - DataValidity::FullyInvalid => SheetRebuildKind::Full, + SheetRebuildKind::Full + } else { + match self.validity { + DataValidity::Valid => continue, + DataValidity::CascadeInvalid => SheetRebuildKind::CascadeOnly, + DataValidity::FullyInvalid => SheetRebuildKind::Full, + } }; - return Some((&potential_sheet.sheet, rebuild_kind)); + if !callback(&potential_sheet.sheet, rebuild_kind) { + return; + } } } } @@ -357,7 +369,7 @@ where let validity = mem::replace(&mut self.data_validity, DataValidity::Valid); SheetCollectionFlusher { - iter: self.entries.iter_mut(), + entries: &mut self.entries, dirty, validity, } @@ -408,7 +420,7 @@ macro_rules! sheet_set_methods { ) { debug!(concat!($set_name, "::append_stylesheet")); self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet, guard); + let collection = self.collection_for(&sheet); collection.append(sheet); } @@ -423,7 +435,7 @@ macro_rules! sheet_set_methods { debug!(concat!($set_name, "::insert_stylesheet_before")); self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet, guard); + let collection = self.collection_for(&sheet); collection.insert_before(sheet, &before_sheet); } @@ -437,7 +449,7 @@ macro_rules! sheet_set_methods { debug!(concat!($set_name, "::remove_stylesheet")); self.collect_invalidations_for(device, &sheet, guard); - let collection = self.collection_for(&sheet, guard); + let collection = self.collection_for(&sheet); collection.remove(&sheet) } @@ -487,7 +499,7 @@ macro_rules! sheet_set_methods { RuleChangeKind::StyleRuleDeclarations => DataValidity::FullyInvalid, }; - let collection = self.collection_for(&sheet, guard); + let collection = self.collection_for(&sheet); collection.set_data_validity_at_least(validity); } }; @@ -505,12 +517,8 @@ where } } - fn collection_for( - &mut self, - sheet: &S, - guard: &SharedRwLockReadGuard, - ) -> &mut SheetCollection<S> { - let origin = sheet.origin(guard); + fn collection_for(&mut self, sheet: &S) -> &mut SheetCollection<S> { + let origin = sheet.contents().origin; self.collections.borrow_mut_for_origin(&origin) } @@ -658,11 +666,7 @@ where self.collection.len() } - fn collection_for( - &mut self, - _sheet: &S, - _guard: &SharedRwLockReadGuard, - ) -> &mut SheetCollection<S> { + fn collection_for(&mut self, _sheet: &S) -> &mut SheetCollection<S> { &mut self.collection } diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index 5efae0a3ee5..396be242024 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -6,12 +6,11 @@ //! //! [import]: https://drafts.csswg.org/css-cascade-3/#at-import -use crate::context::QuirksMode; use crate::media_queries::MediaList; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; -use crate::stylesheets::{CssRule, Origin, StylesheetInDocument}; +use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::values::CssUrl; use cssparser::SourceLocation; use std::fmt::{self, Write}; @@ -61,6 +60,19 @@ impl ImportSheet { ImportSheet::Pending(_) => None, } } + + /// Returns the media list for this import rule. + pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { + self.as_sheet().and_then(|s| s.media(guard)) + } + + /// Returns the rule list for this import rule. + pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { + match self.as_sheet() { + Some(s) => s.rules(guard), + None => &[], + } + } } #[cfg(feature = "gecko")] @@ -85,68 +97,20 @@ impl DeepCloneWithLock for ImportSheet { } } -#[cfg(feature = "gecko")] -impl StylesheetInDocument for ImportSheet { - fn origin(&self, _guard: &SharedRwLockReadGuard) -> Origin { - match *self { - ImportSheet::Sheet(ref s) => s.contents().origin, - ImportSheet::Pending(ref p) => p.origin, - } - } - - fn quirks_mode(&self, _guard: &SharedRwLockReadGuard) -> QuirksMode { - match *self { - ImportSheet::Sheet(ref s) => s.contents().quirks_mode, - ImportSheet::Pending(ref p) => p.quirks_mode, - } - } - - fn enabled(&self) -> bool { - match *self { - ImportSheet::Sheet(ref s) => s.enabled(), - ImportSheet::Pending(_) => true, - } - } - - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - match *self { - ImportSheet::Sheet(ref s) => s.media(guard), - ImportSheet::Pending(_) => None, - } - } - - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - match *self { - ImportSheet::Sheet(ref s) => s.contents().rules(guard), - ImportSheet::Pending(_) => &[], - } - } -} - /// A sheet that is held from an import rule. #[cfg(feature = "servo")] #[derive(Debug)] pub struct ImportSheet(pub ::servo_arc::Arc<crate::stylesheets::Stylesheet>); #[cfg(feature = "servo")] -impl StylesheetInDocument for ImportSheet { - fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin { - self.0.origin(guard) - } - - fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode { - self.0.quirks_mode(guard) - } - - fn enabled(&self) -> bool { - self.0.enabled() - } - - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { +impl ImportSheet { + /// Returns the media list for this import rule. + pub fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { self.0.media(guard) } - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { + /// Returns the rules for this import rule. + pub fn rules<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a [CssRule] { self.0.rules(guard) } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 524d54d9c33..914af2cfb66 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -53,7 +53,7 @@ pub struct TopLevelRuleParser<'a> { /// This won't contain any namespaces, and only nested parsers created with /// `ParserContext::new_with_rule_type` will. pub context: ParserContext<'a>, - /// The current stajkj/te of the parser. + /// The current state of the parser. pub state: State, /// Whether we have tried to parse was invalid due to being in the wrong /// place (e.g. an @import rule was found while in the `Body` state). Reset @@ -587,6 +587,38 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { } } +#[inline(never)] +fn check_for_useless_selector( + input: &mut Parser, + context: &ParserContext, + selectors: &SelectorList<SelectorImpl>, +) { + use cssparser::ToCss; + + 'selector_loop: for selector in selectors.0.iter() { + let mut current = selector.iter(); + loop { + let mut found_host = false; + let mut found_non_host = false; + for component in &mut current { + if component.is_host() { + found_host = true; + } else { + found_non_host = true; + } + if found_host && found_non_host { + let location = input.current_source_location(); + context.log_css_error(location, ContextualParseError::NeverMatchingHostSelector(selector.to_css_string())); + continue 'selector_loop; + } + } + if current.next_sequence().is_none() { + break; + } + } + } +} + impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { type Prelude = SelectorList<SelectorImpl>; type QualifiedRule = CssRule; @@ -601,7 +633,11 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { namespaces: self.namespaces, url_data: Some(self.context.url_data), }; - SelectorList::parse(&selector_parser, input) + let selectors = SelectorList::parse(&selector_parser, input)?; + if self.context.error_reporting_enabled() { + check_for_useless_selector(input, &self.context, &selectors); + } + Ok(selectors) } fn parse_block<'t>( diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index cc52a9550be..a7010ff066e 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -7,7 +7,6 @@ use crate::context::QuirksMode; use crate::media_queries::Device; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::StylesheetInDocument; use crate::stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, SupportsRule}; use smallvec::SmallVec; use std::slice; @@ -227,10 +226,13 @@ impl NestedRuleIterationCondition for EffectiveRules { fn process_import( guard: &SharedRwLockReadGuard, device: &Device, - _quirks_mode: QuirksMode, + quirks_mode: QuirksMode, rule: &ImportRule, ) -> bool { - rule.stylesheet.is_effective_for_device(device, guard) + match rule.stylesheet.media(guard) { + Some(m) => m.evaluate(device, quirks_mode), + None => true, + } } fn process_media( diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 41c80c3cc66..2f7706a6979 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -4,7 +4,6 @@ use crate::context::QuirksMode; use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; -use crate::invalidation::media_queries::{MediaListKey, ToMediaListKey}; use crate::media_queries::{Device, MediaList}; use crate::parser::ParserContext; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; @@ -102,10 +101,10 @@ impl StylesheetContents { Self { rules: CssRules::new(rules, &shared_lock), - origin: origin, + origin, url_data: RwLock::new(url_data), - namespaces: namespaces, - quirks_mode: quirks_mode, + namespaces, + quirks_mode, source_map_url: RwLock::new(source_map_url), source_url: RwLock::new(source_url), } @@ -218,12 +217,6 @@ macro_rules! rule_filter { /// A trait to represent a given stylesheet in a document. pub trait StylesheetInDocument: ::std::fmt::Debug { - /// Get the stylesheet origin. - fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin; - - /// Get the stylesheet quirks mode. - fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode; - /// Get whether this stylesheet is enabled. fn enabled(&self) -> bool; @@ -231,7 +224,12 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList>; /// Returns a reference to the list of rules in this stylesheet. - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule]; + fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { + self.contents().rules(guard) + } + + /// Returns a reference to the contents of the stylesheet. + fn contents(&self) -> &StylesheetContents; /// Return an iterator using the condition `C`. #[inline] @@ -243,18 +241,19 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { where C: NestedRuleIterationCondition, { + let contents = self.contents(); RulesIterator::new( device, - self.quirks_mode(guard), + contents.quirks_mode, guard, - self.rules(guard).iter(), + contents.rules(guard).iter(), ) } /// Returns whether the style-sheet applies for the current device. fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool { match self.media(guard) { - Some(medialist) => medialist.evaluate(device, self.quirks_mode(guard)), + Some(medialist) => medialist.evaluate(device, self.contents().quirks_mode), None => true, } } @@ -285,14 +284,6 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { } impl StylesheetInDocument for Stylesheet { - fn origin(&self, _guard: &SharedRwLockReadGuard) -> Origin { - self.contents.origin - } - - fn quirks_mode(&self, _guard: &SharedRwLockReadGuard) -> QuirksMode { - self.contents.quirks_mode - } - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { Some(self.media.read_with(guard)) } @@ -302,8 +293,8 @@ impl StylesheetInDocument for Stylesheet { } #[inline] - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.contents.rules(guard) + fn contents(&self) -> &StylesheetContents { + &self.contents } } @@ -321,21 +312,7 @@ impl PartialEq for DocumentStyleSheet { } } -impl ToMediaListKey for DocumentStyleSheet { - fn to_media_list_key(&self) -> MediaListKey { - self.0.to_media_list_key() - } -} - impl StylesheetInDocument for DocumentStyleSheet { - fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin { - self.0.origin(guard) - } - - fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode { - self.0.quirks_mode(guard) - } - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { self.0.media(guard) } @@ -345,8 +322,8 @@ impl StylesheetInDocument for DocumentStyleSheet { } #[inline] - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.0.rules(guard) + fn contents(&self) -> &StylesheetContents { + self.0.contents() } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 5cb40b968eb..74704e707df 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -11,7 +11,7 @@ use crate::element_state::{DocumentState, ElementState}; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion}; use crate::invalidation::element::invalidation_map::InvalidationMap; -use crate::invalidation::media_queries::{EffectiveMediaQueryResults, ToMediaListKey}; +use crate::invalidation::media_queries::EffectiveMediaQueryResults; use crate::invalidation::stylesheets::RuleChangeKind; use crate::media_queries::Device; use crate::properties::{self, CascadeMode, ComputedValues}; @@ -60,17 +60,33 @@ pub type StylistSheet = crate::stylesheets::DocumentStyleSheet; #[cfg(feature = "gecko")] pub type StylistSheet = crate::gecko::data::GeckoStyleSheet; -lazy_static! { - /// A cache of computed user-agent data, to be shared across documents. - static ref UA_CASCADE_DATA_CACHE: Mutex<UserAgentCascadeDataCache> = - Mutex::new(UserAgentCascadeDataCache::new()); +trait CascadeDataCacheEntry : Sized { + /// Returns a reference to the cascade data. + fn cascade_data(&self) -> &CascadeData; + /// Rebuilds the cascade data for the new stylesheet collection. The + /// collection is guaranteed to be dirty. + fn rebuild<S>( + device: &Device, + quirks_mode: QuirksMode, + collection: SheetCollectionFlusher<S>, + guard: &SharedRwLockReadGuard, + old_entry: &Self, + ) -> Result<Arc<Self>, FailedAllocationError> + where + S: StylesheetInDocument + PartialEq + 'static; + /// Measures heap memory usage. + #[cfg(feature = "gecko")] + fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes); } -struct UserAgentCascadeDataCache { - entries: Vec<Arc<UserAgentCascadeData>>, +struct CascadeDataCache<Entry> { + entries: Vec<Arc<Entry>>, } -impl UserAgentCascadeDataCache { +impl<Entry> CascadeDataCache<Entry> +where + Entry: CascadeDataCacheEntry, +{ fn new() -> Self { Self { entries: vec![] } } @@ -79,53 +95,67 @@ impl UserAgentCascadeDataCache { self.entries.len() } - // FIXME(emilio): This may need to be keyed on quirks-mode too, though there - // aren't class / id selectors on those sheets, usually, so it's probably - // ok... - fn lookup<'a, I, S>( + // FIXME(emilio): This may need to be keyed on quirks-mode too, though for + // UA sheets there aren't class / id selectors on those sheets, usually, so + // it's probably ok... For the other cache the quirks mode shouldn't differ + // so also should be fine. + fn lookup<'a, S>( &'a mut self, - sheets: I, device: &Device, quirks_mode: QuirksMode, + collection: SheetCollectionFlusher<S>, guard: &SharedRwLockReadGuard, - ) -> Result<Arc<UserAgentCascadeData>, FailedAllocationError> + old_entry: &Entry, + ) -> Result<Option<Arc<Entry>>, FailedAllocationError> where - I: Iterator<Item = &'a S> + Clone, - S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static, + S: StylesheetInDocument + PartialEq + 'static, { + debug!("StyleSheetCache::lookup({})", self.len()); + + if !collection.dirty() { + return Ok(None); + } + let mut key = EffectiveMediaQueryResults::new(); - debug!("UserAgentCascadeDataCache::lookup({:?})", device); - for sheet in sheets.clone() { + for sheet in collection.sheets() { CascadeData::collect_applicable_media_query_results_into(device, sheet, guard, &mut key) } for entry in &self.entries { - if entry.cascade_data.effective_media_query_results == key { - return Ok(entry.clone()); + if std::ptr::eq(&**entry, old_entry) { + // Avoid reusing our old entry (this can happen if we get + // invalidated due to CSSOM mutations and our old stylesheet + // contents were already unique, for example). This old entry + // will be pruned from the cache with take_unused() afterwards. + continue; + } + if entry.cascade_data().effective_media_query_results != key { + continue; + } + if log_enabled!(log::Level::Debug) { + debug!("cache hit for:"); + for sheet in collection.sheets() { + debug!(" > {:?}", sheet); + } } + // The line below ensures the "committed" bit is updated properly + // below. + collection.each(|_, _| true); + return Ok(Some(entry.clone())); } - let mut new_data = UserAgentCascadeData { - cascade_data: CascadeData::new(), - precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(), - }; - debug!("> Picking the slow path"); - for sheet in sheets { - new_data.cascade_data.add_stylesheet( - device, - quirks_mode, - sheet, - guard, - SheetRebuildKind::Full, - Some(&mut new_data.precomputed_pseudo_element_decls), - )?; - } + let new_entry = Entry::rebuild( + device, + quirks_mode, + collection, + guard, + old_entry, + )?; - let new_data = Arc::new(new_data); - self.entries.push(new_data.clone()); - Ok(new_data) + self.entries.push(new_entry.clone()); + Ok(Some(new_entry)) } /// Returns all the cascade datas that are not being used (that is, that are @@ -135,7 +165,7 @@ impl UserAgentCascadeDataCache { /// keep alive some other documents (like the SVG documents kept alive by /// URL references), and thus we don't want to drop them while locking the /// cache to not deadlock. - fn take_unused(&mut self) -> SmallVec<[Arc<UserAgentCascadeData>; 3]> { + fn take_unused(&mut self) -> SmallVec<[Arc<Entry>; 3]> { let mut unused = SmallVec::new(); for i in (0..self.entries.len()).rev() { // is_unique() returns false for static references, but we never @@ -148,7 +178,7 @@ impl UserAgentCascadeDataCache { unused } - fn take_all(&mut self) -> Vec<Arc<UserAgentCascadeData>> { + fn take_all(&mut self) -> Vec<Arc<Entry>> { mem::replace(&mut self.entries, Vec::new()) } @@ -173,6 +203,58 @@ pub fn add_size_of_ua_cache(ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSet .add_size_of(ops, sizes); } +lazy_static! { + /// A cache of computed user-agent data, to be shared across documents. + static ref UA_CASCADE_DATA_CACHE: Mutex<UserAgentCascadeDataCache> = + Mutex::new(UserAgentCascadeDataCache::new()); +} + +impl CascadeDataCacheEntry for UserAgentCascadeData { + fn cascade_data(&self) -> &CascadeData { + &self.cascade_data + } + + fn rebuild<S>( + device: &Device, + quirks_mode: QuirksMode, + collection: SheetCollectionFlusher<S>, + guard: &SharedRwLockReadGuard, + _old: &Self, + ) -> Result<Arc<Self>, FailedAllocationError> + where + S: StylesheetInDocument + PartialEq + 'static + { + // TODO: Maybe we should support incremental rebuilds, though they seem + // uncommon and rebuild() doesn't deal with + // precomputed_pseudo_element_decls for now so... + let mut new_data = Self { + cascade_data: CascadeData::new(), + precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations::default(), + }; + + for sheet in collection.sheets() { + new_data.cascade_data.add_stylesheet( + device, + quirks_mode, + sheet, + guard, + SheetRebuildKind::Full, + Some(&mut new_data.precomputed_pseudo_element_decls), + )?; + } + + Ok(Arc::new(new_data)) + } + + #[cfg(feature = "gecko")] + fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { + self.cascade_data.add_size_of(ops, sizes); + sizes.mPrecomputedPseudos += self.precomputed_pseudo_element_decls.size_of(ops); + } +} + +type UserAgentCascadeDataCache = CascadeDataCache<UserAgentCascadeData>; + type PrecomputedPseudoElementDeclarations = PerPseudoElementMap<Vec<ApplicableDeclarationBlock>>; #[derive(Default)] @@ -188,14 +270,6 @@ struct UserAgentCascadeData { precomputed_pseudo_element_decls: PrecomputedPseudoElementDeclarations, } -impl UserAgentCascadeData { - #[cfg(feature = "gecko")] - fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { - self.cascade_data.add_size_of(ops, sizes); - sizes.mPrecomputedPseudos += self.precomputed_pseudo_element_decls.size_of(ops); - } -} - /// All the computed information for all the stylesheets that apply to the /// document. #[derive(Default)] @@ -262,19 +336,29 @@ impl DocumentCascadeData { guards: &StylesheetGuards, ) -> Result<(), FailedAllocationError> where - S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static, + S: StylesheetInDocument + PartialEq + 'static, { // First do UA sheets. { - if flusher.flush_origin(Origin::UserAgent).dirty() { - let origin_sheets = flusher.origin_sheets(Origin::UserAgent); - let _unused_cascade_datas = { - let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap(); - self.user_agent = - ua_cache.lookup(origin_sheets, device, quirks_mode, guards.ua_or_user)?; - debug!("User agent data cache size {:?}", ua_cache.len()); - ua_cache.take_unused() - }; + let origin_flusher = flusher.flush_origin(Origin::UserAgent); + // Dirty check is just a minor optimization (no need to grab the + // lock if nothing has changed). + if origin_flusher.dirty() { + let mut ua_cache = UA_CASCADE_DATA_CACHE.lock().unwrap(); + let new_data = ua_cache.lookup( + device, + quirks_mode, + origin_flusher, + guards.ua_or_user, + &self.user_agent, + )?; + if let Some(new_data) = new_data { + self.user_agent = new_data; + } + let _unused_entries = ua_cache.take_unused(); + // See the comments in take_unused() as for why the following + // line. + std::mem::drop(ua_cache); } } @@ -371,6 +455,10 @@ pub struct Stylist { /// The list of stylesheets. stylesheets: StylistStylesheetSet, + /// A cache of CascadeDatas for AuthorStylesheetSets (i.e., shadow DOM). + #[cfg_attr(feature = "servo", ignore_malloc_size_of = "XXX: how to handle this?")] + author_data_cache: CascadeDataCache<CascadeData>, + /// If true, the quirks-mode stylesheet is applied. #[cfg_attr(feature = "servo", ignore_malloc_size_of = "defined in selectors")] quirks_mode: QuirksMode, @@ -422,6 +510,7 @@ impl Stylist { device, quirks_mode, stylesheets: StylistStylesheetSet::new(), + author_data_cache: CascadeDataCache::new(), cascade_data: Default::default(), author_styles_enabled: AuthorStylesEnabled::Yes, rule_tree: RuleTree::new(), @@ -447,6 +536,31 @@ impl Stylist { self.cascade_data.iter_origins() } + /// Does what the name says, to prevent author_data_cache to grow without + /// bound. + pub fn remove_unique_author_data_cache_entries(&mut self) { + self.author_data_cache.take_unused(); + } + + /// Rebuilds (if needed) the CascadeData given a sheet collection. + pub fn rebuild_author_data<S>( + &mut self, + old_data: &CascadeData, + collection: SheetCollectionFlusher<S>, + guard: &SharedRwLockReadGuard, + ) -> Result<Option<Arc<CascadeData>>, FailedAllocationError> + where + S: StylesheetInDocument + PartialEq + 'static, + { + self.author_data_cache.lookup( + &self.device, + self.quirks_mode, + collection, + guard, + old_data, + ) + } + /// Iterate over the extra data in origin order. #[inline] pub fn iter_extra_data_origins(&self) -> ExtraStyleDataIterator { @@ -1355,6 +1469,7 @@ impl Stylist { #[cfg(feature = "gecko")] pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { self.cascade_data.add_size_of(ops, sizes); + self.author_data_cache.add_size_of(ops, sizes); sizes.mRuleTree += self.rule_tree.size_of(ops); // We may measure other fields in the future if DMD says it's worth it. @@ -1368,7 +1483,7 @@ impl Stylist { /// This struct holds data which users of Stylist may want to extract /// from stylesheets which can be done at the same time as updating. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] #[cfg_attr(feature = "servo", derive(MallocSizeOf))] pub struct ExtraStyleData { /// A list of effective font-face rules and their origin. @@ -1389,11 +1504,6 @@ pub struct ExtraStyleData { } #[cfg(feature = "gecko")] -unsafe impl Sync for ExtraStyleData {} -#[cfg(feature = "gecko")] -unsafe impl Send for ExtraStyleData {} - -#[cfg(feature = "gecko")] impl ExtraStyleData { /// Add the given @font-face rule. fn add_font_face(&mut self, rule: &Arc<Locked<FontFaceRule>>) { @@ -1633,7 +1743,7 @@ impl<'a> SelectorVisitor for StylistSelectorVisitor<'a> { } /// A set of rules for element and pseudo-elements. -#[derive(Debug, Default, MallocSizeOf)] +#[derive(Clone, Debug, Default, MallocSizeOf)] struct GenericElementAndPseudoRules<Map> { /// Rules from stylesheets at this `CascadeData`'s origin. element_map: Map, @@ -1712,7 +1822,7 @@ impl PartElementAndPseudoRules { /// /// FIXME(emilio): Consider renaming and splitting in `CascadeData` and /// `InvalidationData`? That'd make `clear_cascade_data()` clearer. -#[derive(Debug, MallocSizeOf)] +#[derive(Debug, Clone, MallocSizeOf)] pub struct CascadeData { /// The data coming from normal style rules that apply to elements at this /// cascade level. @@ -1822,7 +1932,7 @@ impl CascadeData { guard: &SharedRwLockReadGuard, ) -> Result<(), FailedAllocationError> where - S: StylesheetInDocument + ToMediaListKey + PartialEq + 'static, + S: StylesheetInDocument + PartialEq + 'static, { if !collection.dirty() { return Ok(()); @@ -1836,18 +1946,21 @@ impl CascadeData { DataValidity::FullyInvalid => self.clear(), } - for (stylesheet, rebuild_kind) in collection { - self.add_stylesheet( + let mut result = Ok(()); + + collection.each(|stylesheet, rebuild_kind| { + result = self.add_stylesheet( device, quirks_mode, stylesheet, guard, rebuild_kind, /* precomputed_pseudo_element_decls = */ None, - )?; - } + ); + result.is_ok() + }); - Ok(()) + result } /// Returns the invalidation map. @@ -1922,14 +2035,14 @@ impl CascadeData { guard: &SharedRwLockReadGuard, results: &mut EffectiveMediaQueryResults, ) where - S: StylesheetInDocument + ToMediaListKey + 'static, + S: StylesheetInDocument + 'static, { if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { return; } debug!(" + {:?}", stylesheet); - results.saw_effective(stylesheet); + results.saw_effective(stylesheet.contents()); for rule in stylesheet.effective_rules(device, guard) { match *rule { @@ -1959,16 +2072,17 @@ impl CascadeData { mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where - S: StylesheetInDocument + ToMediaListKey + 'static, + S: StylesheetInDocument + 'static, { if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { return Ok(()); } - let origin = stylesheet.origin(guard); + let contents = stylesheet.contents(); + let origin = contents.origin; if rebuild_kind.should_rebuild_invalidation() { - self.effective_media_query_results.saw_effective(stylesheet); + self.effective_media_query_results.saw_effective(contents); } for rule in stylesheet.effective_rules(device, guard) { @@ -2143,13 +2257,13 @@ impl CascadeData { quirks_mode: QuirksMode, ) -> bool where - S: StylesheetInDocument + ToMediaListKey + 'static, + S: StylesheetInDocument + 'static, { use crate::invalidation::media_queries::PotentiallyEffectiveMediaRules; let effective_now = stylesheet.is_effective_for_device(device, guard); - let effective_then = self.effective_media_query_results.was_effective(stylesheet); + let effective_then = self.effective_media_query_results.was_effective(stylesheet.contents()); if effective_now != effective_then { debug!( @@ -2184,9 +2298,10 @@ impl CascadeData { }, CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); - let effective_now = import_rule - .stylesheet - .is_effective_for_device(&device, guard); + let effective_now = match import_rule.stylesheet.media(guard) { + Some(m) => m.evaluate(device, quirks_mode), + None => true, + }; let effective_then = self .effective_media_query_results .was_effective(import_rule); @@ -2258,8 +2373,33 @@ impl CascadeData { self.selectors_for_cache_revalidation.clear(); self.effective_media_query_results.clear(); } +} + +impl CascadeDataCacheEntry for CascadeData { + fn cascade_data(&self) -> &CascadeData { + self + } + + fn rebuild<S>( + device: &Device, + quirks_mode: QuirksMode, + collection: SheetCollectionFlusher<S>, + guard: &SharedRwLockReadGuard, + old: &Self, + ) -> Result<Arc<Self>, FailedAllocationError> + where + S: StylesheetInDocument + PartialEq + 'static + { + debug_assert!(collection.dirty(), "We surely need to do something?"); + // If we're doing a full rebuild anyways, don't bother cloning the data. + let mut updatable_entry = match collection.data_validity() { + DataValidity::Valid | DataValidity::CascadeInvalid => old.clone(), + DataValidity::FullyInvalid => Self::new(), + }; + updatable_entry.rebuild(device, quirks_mode, collection, guard)?; + Ok(Arc::new(updatable_entry)) + } - /// Measures heap usage. #[cfg(feature = "gecko")] fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) { self.normal_rules.add_size_of(ops, sizes); diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index 9f4fa5c52b5..773310dd9b6 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -35,17 +35,14 @@ impl RGBA { #[inline] pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self { RGBA { - red: red, - green: green, - blue: blue, - alpha: alpha, + red, + green, + blue, + alpha, } } } -/// Unlike Animate for computed colors, we don't clamp any component values. -/// -/// FIXME(nox): Why do computed colors even implement Animate? impl Animate for RGBA { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { @@ -57,15 +54,11 @@ impl Animate for RGBA { } alpha = alpha.min(1.); - let red = - (self.red * self.alpha).animate(&(other.red * other.alpha), procedure)? * 1. / alpha; - let green = (self.green * self.alpha).animate(&(other.green * other.alpha), procedure)? * - 1. / - alpha; - let blue = - (self.blue * self.alpha).animate(&(other.blue * other.alpha), procedure)? * 1. / alpha; - - Ok(RGBA::new(red, green, blue, alpha)) + let red = (self.red * self.alpha).animate(&(other.red * other.alpha), procedure)?; + let green = (self.green * self.alpha).animate(&(other.green * other.alpha), procedure)?; + let blue = (self.blue * self.alpha).animate(&(other.blue * other.alpha), procedure)?; + let inv = 1. / alpha; + Ok(RGBA::new(red * inv, green * inv, blue * inv, alpha)) } } @@ -97,21 +90,75 @@ pub type Color = GenericColor<RGBA>; impl Color { fn effective_intermediate_rgba(&self) -> RGBA { - match *self { - GenericColor::Numeric(color) => color, - GenericColor::CurrentColor => RGBA::transparent(), - GenericColor::Complex { color, ratios } => RGBA { - alpha: color.alpha * ratios.bg, - ..color.clone() - }, + if self.ratios.bg == 0. { + return RGBA::transparent(); + } + + if self.ratios.bg == 1. { + return self.color; + } + + RGBA { + alpha: self.color.alpha * self.ratios.bg, + ..self.color + } + } + + /// Mix two colors into one. + pub fn mix( + left_color: &Color, + left_weight: f32, + right_color: &Color, + right_weight: f32, + ) -> Self { + let left_bg = left_color.scaled_rgba(); + let right_bg = right_color.scaled_rgba(); + let alpha = (left_bg.alpha * left_weight + + right_bg.alpha * right_weight) + .min(1.); + + let mut fg = 0.; + let mut red = 0.; + let mut green = 0.; + let mut blue = 0.; + + let colors = [ + (left_color, &left_bg, left_weight), + (right_color, &right_bg, right_weight), + ]; + + for &(color, bg, weight) in &colors { + fg += color.ratios.fg * weight; + + red += bg.red * bg.alpha * weight; + green += bg.green * bg.alpha * weight; + blue += bg.blue * bg.alpha * weight; } + + let color = if alpha <= 0. { + RGBA::transparent() + } else { + let inv = 1. / alpha; + RGBA::new(red * inv, green * inv, blue * inv, alpha) + }; + + Self::new(color, ComplexColorRatios { bg: 1., fg }) } - fn effective_ratios(&self) -> ComplexColorRatios { - match *self { - GenericColor::Numeric(..) => ComplexColorRatios::NUMERIC, - GenericColor::CurrentColor => ComplexColorRatios::CURRENT_COLOR, - GenericColor::Complex { ratios, .. } => ratios, + fn scaled_rgba(&self) -> RGBA { + if self.ratios.bg == 0. { + return RGBA::transparent(); + } + + if self.ratios.bg == 1. { + return self.color; + } + + RGBA { + red: self.color.red * self.ratios.bg, + green: self.color.green * self.ratios.bg, + blue: self.color.blue * self.ratios.bg, + alpha: self.color.alpha * self.ratios.bg, } } } @@ -119,140 +166,140 @@ impl Color { impl Animate for Color { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - use self::GenericColor::*; - - // Common cases are interpolating between two numeric colors, - // two currentcolors, and a numeric color and a currentcolor. - let (this_weight, other_weight) = procedure.weights(); - - Ok(match (*self, *other, procedure) { - // Any interpolation of currentcolor with currentcolor returns currentcolor. - (CurrentColor, CurrentColor, Procedure::Interpolate { .. }) => CurrentColor, - // Animating two numeric colors. - (Numeric(c1), Numeric(c2), _) => Numeric(c1.animate(&c2, procedure)?), - // Combinations of numeric color and currentcolor - (CurrentColor, Numeric(color), _) => Self::with_ratios( - color, - ComplexColorRatios { - bg: other_weight as f32, - fg: this_weight as f32, - }, - ), - (Numeric(color), CurrentColor, _) => Self::with_ratios( - color, - ComplexColorRatios { - bg: this_weight as f32, - fg: other_weight as f32, - }, - ), + let self_numeric = self.is_numeric(); + let other_numeric = other.is_numeric(); + + if self_numeric && other_numeric { + return Ok(Self::rgba(self.color.animate(&other.color, procedure)?)); + } + + let self_currentcolor = self.is_currentcolor(); + let other_currentcolor = other.is_currentcolor(); - // Any other animation of currentcolor with currentcolor. - (CurrentColor, CurrentColor, _) => Self::with_ratios( + if self_currentcolor && other_currentcolor { + let (self_weight, other_weight) = procedure.weights(); + return Ok(Self::new( RGBA::transparent(), ComplexColorRatios { bg: 0., - fg: (this_weight + other_weight) as f32, + fg: (self_weight + other_weight) as f32, }, - ), - - // Defer to complex calculations - _ => { - // Compute the "scaled" contribution for `color`. - fn scaled_rgba(color: &Color) -> RGBA { - match *color { - GenericColor::Numeric(color) => color, - GenericColor::CurrentColor => RGBA::transparent(), - GenericColor::Complex { color, ratios } => RGBA { - red: color.red * ratios.bg, - green: color.green * ratios.bg, - blue: color.blue * ratios.bg, - alpha: color.alpha * ratios.bg, - }, - } - } - - // Each `Color`, represents a complex combination of foreground color and - // background color where fg and bg represent the overall - // contributions. ie: - // - // color = { bg * mColor, fg * foreground } - // = { bg_color , fg_color } - // = bg_color + fg_color - // - // where `foreground` is `currentcolor`, and `bg_color`, - // `fg_color` are the scaled background and foreground - // contributions. - // - // Each operation, lerp, addition, or accumulate, can be - // represented as a scaled-addition each complex color. ie: - // - // p * col1 + q * col2 - // - // where p = (1 - a), q = a for lerp(a), p = 1, q = 1 for - // addition, etc. - // - // Therefore: - // - // col1 op col2 - // = p * col1 + q * col2 - // = p * { bg_color1, fg_color1 } + q * { bg_color2, fg_color2 } - // = p * (bg_color1 + fg_color1) + q * (bg_color2 + fg_color2) - // = p * bg_color1 + p * fg_color1 + q * bg_color2 + p * fg_color2 - // = (p * bg_color1 + q * bg_color2) + (p * fg_color1 + q * fg_color2) - // = (bg_color1 op bg_color2) + (fg_color1 op fg_color2) - // - // fg_color1 op fg_color2 is equivalent to (fg1 op fg2) * foreground, - // so the final color is: - // - // = { bg_color, fg_color } - // = { 1 * (bg_color1 op bg_color2), (fg1 op fg2) * foreground } - - // To perform the operation on two complex colors, we need to - // generate the scaled contributions of each background color - // component. - let bg_color1 = scaled_rgba(self); - let bg_color2 = scaled_rgba(other); - // Perform bg_color1 op bg_color2 - let bg_color = bg_color1.animate(&bg_color2, procedure)?; - - // Calculate the final foreground color ratios; perform - // animation on effective fg ratios. - let ComplexColorRatios { fg: fg1, .. } = self.effective_ratios(); - let ComplexColorRatios { fg: fg2, .. } = other.effective_ratios(); - // Perform fg1 op fg2 - let fg = fg1.animate(&fg2, procedure)?; - - Self::with_ratios(bg_color, ComplexColorRatios { bg: 1., fg }) - }, - }) + )); + } + + // FIXME(emilio): Without these special cases tests fail, looks fairly + // sketchy! + if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) { + let (self_weight, other_weight) = procedure.weights(); + return Ok(if self_numeric { + Self::new( + self.color, + ComplexColorRatios { + bg: self_weight as f32, + fg: other_weight as f32, + }, + ) + } else { + Self::new( + other.color, + ComplexColorRatios { + bg: other_weight as f32, + fg: self_weight as f32, + }, + ) + }); + } + + // Compute the "scaled" contribution for `color`. + // Each `Color`, represents a complex combination of foreground color and + // background color where fg and bg represent the overall + // contributions. ie: + // + // color = { bg * mColor, fg * foreground } + // = { bg_color , fg_color } + // = bg_color + fg_color + // + // where `foreground` is `currentcolor`, and `bg_color`, + // `fg_color` are the scaled background and foreground + // contributions. + // + // Each operation, lerp, addition, or accumulate, can be + // represented as a scaled-addition each complex color. ie: + // + // p * col1 + q * col2 + // + // where p = (1 - a), q = a for lerp(a), p = 1, q = 1 for + // addition, etc. + // + // Therefore: + // + // col1 op col2 + // = p * col1 + q * col2 + // = p * { bg_color1, fg_color1 } + q * { bg_color2, fg_color2 } + // = p * (bg_color1 + fg_color1) + q * (bg_color2 + fg_color2) + // = p * bg_color1 + p * fg_color1 + q * bg_color2 + p * fg_color2 + // = (p * bg_color1 + q * bg_color2) + (p * fg_color1 + q * fg_color2) + // = (bg_color1 op bg_color2) + (fg_color1 op fg_color2) + // + // fg_color1 op fg_color2 is equivalent to (fg1 op fg2) * foreground, + // so the final color is: + // + // = { bg_color, fg_color } + // = { 1 * (bg_color1 op bg_color2), (fg1 op fg2) * foreground } + // + // To perform the operation on two complex colors, we need to + // generate the scaled contributions of each background color + // component. + let bg_color1 = self.scaled_rgba(); + let bg_color2 = other.scaled_rgba(); + + // Perform bg_color1 op bg_color2 + let bg_color = bg_color1.animate(&bg_color2, procedure)?; + + // Calculate the final foreground color ratios; perform + // animation on effective fg ratios. + let fg = self.ratios.fg.animate(&other.ratios.fg, procedure)?; + + Ok(Self::new(bg_color, ComplexColorRatios { bg: 1., fg })) } } impl ComputeSquaredDistance for Color { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - use self::GenericColor::*; - - // All comments from the Animate impl also applies here. - Ok(match (*self, *other) { - (CurrentColor, CurrentColor) => SquaredDistance::from_sqrt(0.), - (Numeric(c1), Numeric(c2)) => c1.compute_squared_distance(&c2)?, - (CurrentColor, Numeric(color)) | (Numeric(color), CurrentColor) => { - // `computed_squared_distance` is symmetric. - color.compute_squared_distance(&RGBA::transparent())? + - SquaredDistance::from_sqrt(1.) - }, - (_, _) => { - let self_color = self.effective_intermediate_rgba(); - let other_color = other.effective_intermediate_rgba(); - let self_ratios = self.effective_ratios(); - let other_ratios = other.effective_ratios(); - - self_color.compute_squared_distance(&other_color)? + - self_ratios.bg.compute_squared_distance(&other_ratios.bg)? + - self_ratios.fg.compute_squared_distance(&other_ratios.fg)? - }, - }) + // All comments from the Animate impl also apply here. + let self_numeric = self.is_numeric(); + let other_numeric = other.is_numeric(); + + if self_numeric && other_numeric { + return self.color.compute_squared_distance(&other.color); + } + + let self_currentcolor = self.is_currentcolor(); + let other_currentcolor = other.is_currentcolor(); + if self_currentcolor && other_currentcolor { + return Ok(SquaredDistance::from_sqrt(0.)); + } + + if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) { + let color = if self_numeric { + &self.color + } else { + &other.color + }; + // `computed_squared_distance` is symmetric. + return Ok(color.compute_squared_distance(&RGBA::transparent())? + + SquaredDistance::from_sqrt(1.)); + } + + let self_color = self.effective_intermediate_rgba(); + let other_color = other.effective_intermediate_rgba(); + let self_ratios = self.ratios; + let other_ratios = other.ratios; + + Ok(self_color.compute_squared_distance(&other_color)? + + self_ratios.bg.compute_squared_distance(&other_ratios.bg)? + + self_ratios.fg.compute_squared_distance(&other_ratios.fg)?) } } diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 6098ea4590e..664fed95886 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -6,7 +6,7 @@ use crate::values::animated::color::RGBA as AnimatedRGBA; use crate::values::animated::ToAnimatedValue; -use crate::values::generics::color::{Color as GenericColor, ColorOrAuto as GenericColorOrAuto}; +use crate::values::generics::color::{GenericColor, GenericColorOrAuto, GenericCaretColor}; use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; @@ -29,13 +29,18 @@ impl Color { /// Combine this complex color with the given foreground color into /// a numeric RGBA color. It currently uses linear blending. pub fn to_rgba(&self, fg_color: RGBA) -> RGBA { - let (color, ratios) = match *self { - // Common cases that the complex color is either pure numeric - // color or pure currentcolor. - GenericColor::Numeric(color) => return color, - GenericColor::CurrentColor => return fg_color, - GenericColor::Complex { color, ratios } => (color, ratios), - }; + // Common cases that the complex color is either pure numeric color or + // pure currentcolor. + if self.is_numeric() { + return self.color; + } + + if self.is_currentcolor() { + return fg_color; + } + + let ratios = &self.ratios; + let color = &self.color; // For the more complicated case that the alpha value differs, // we use the following formula to compute the components: @@ -59,13 +64,14 @@ impl Color { if a <= 0. { return RGBA::transparent(); } - let a = f32::min(a, 1.); + let a = a.min(1.); - let inverse_a = 1. / a; - let r = (p1 * r1 + p2 * r2) * inverse_a; - let g = (p1 * g1 + p2 * g2) * inverse_a; - let b = (p1 * b1 + p2 * b2) * inverse_a; - return RGBA::from_floats(r, g, b, a); + let inv = 1. / a; + + let r = (p1 * r1 + p2 * r2) * inv; + let g = (p1 * g1 + p2 * g2) * inv; + let b = (p1 * b1 + p2 * b2) * inv; + RGBA::from_floats(r, g, b, a) } } @@ -74,11 +80,13 @@ impl ToCss for Color { where W: fmt::Write, { - match *self { - GenericColor::Numeric(color) => color.to_css(dest), - GenericColor::CurrentColor => CSSParserColor::CurrentColor.to_css(dest), - _ => Ok(()), + if self.is_currentcolor() { + return CSSParserColor::CurrentColor.to_css(dest); + } + if self.is_numeric() { + return self.color.to_css(dest); } + Ok(()) } } @@ -104,3 +112,6 @@ impl ToAnimatedValue for RGBA { /// auto | <color> pub type ColorOrAuto = GenericColorOrAuto<Color>; + +/// caret-color +pub type CaretColor = GenericCaretColor<Color>; diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs index 40cfe46efa6..1ae46c772ab 100644 --- a/components/style/values/computed/counters.rs +++ b/components/style/values/computed/counters.rs @@ -4,7 +4,7 @@ //! Computed values for counter properties -use crate::values::computed::url::ComputedImageUrl; +use crate::values::computed::image::Image; use crate::values::generics::counters as generics; use crate::values::generics::counters::CounterIncrement as GenericCounterIncrement; use crate::values::generics::counters::CounterSetOrReset as GenericCounterSetOrReset; @@ -16,7 +16,7 @@ pub type CounterIncrement = GenericCounterIncrement<i32>; pub type CounterSetOrReset = GenericCounterSetOrReset<i32>; /// A computed value for the `content` property. -pub type Content = generics::GenericContent<ComputedImageUrl>; +pub type Content = generics::GenericContent<Image>; /// A computed content item. -pub type ContentItem = generics::GenericContentItem<ComputedImageUrl>; +pub type ContentItem = generics::GenericContentItem<Image>; diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index df6a8ed667e..0bd177bcc85 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -828,14 +828,14 @@ impl ToComputedValue for specified::MathDepth { let parent = cx.builder.get_parent_font().clone_math_depth() as i32; let style = cx.builder.get_parent_font().clone_math_style(); if style == MathStyleValue::Compact { - parent + 1 + parent.saturating_add(1) } else { parent } }, specified::MathDepth::Add(rel) => { let parent = cx.builder.get_parent_font().clone_math_depth(); - parent as i32 + rel.to_computed_value(cx) + (parent as i32).saturating_add(rel.to_computed_value(cx)) }, specified::MathDepth::Absolute(abs) => abs.to_computed_value(cx), }; diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 5e123a07abb..6290ac68a5d 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -74,9 +74,20 @@ impl ToComputedValue for specified::ImageSet { fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { let items = self.items.to_computed_value(context); let dpr = context.device().device_pixel_ratio().get(); + + // If no item have a supported MIME type, the behavior is undefined by the standard + // By default, we select the first item + let mut supported_image = false; let mut selected_index = 0; let mut selected_resolution = items[0].resolution.dppx(); - for (i, item) in items.iter().enumerate().skip(1) { + + for (i, item) in items.iter().enumerate() { + + // If the MIME type is not supported, we discard the ImageSetItem + if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) { + continue; + } + let candidate_resolution = item.resolution.dppx(); // https://drafts.csswg.org/css-images-4/#image-set-notation: @@ -97,7 +108,9 @@ impl ToComputedValue for specified::ImageSet { false }; - if better_candidate() { + // The first item with a supported MIME type is obviously the current best candidate + if !supported_image || better_candidate() { + supported_image = true; selected_index = i; selected_resolution = candidate_resolution; } diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 0908273b88c..bcba3eb9bce 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -185,7 +185,10 @@ impl Size { GenericSize::Auto => false, GenericSize::LengthPercentage(ref lp) => lp.is_definitely_zero(), #[cfg(feature = "gecko")] - GenericSize::ExtremumLength(..) => false, + GenericSize::MinContent | + GenericSize::MaxContent | + GenericSize::MozFitContent | + GenericSize::MozAvailable => false } } } @@ -495,37 +498,6 @@ pub type NonNegativeLengthPercentageOrNormal = /// Either a non-negative `<length>` or a `<number>`. pub type NonNegativeLengthOrNumber = GenericLengthOrNumber<NonNegativeLength, NonNegativeNumber>; -/// A type for possible values for min- and max- flavors of width, height, -/// block-size, and inline-size. -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - FromPrimitive, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToAnimatedValue, - ToAnimatedZero, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum ExtremumLength { - #[parse(aliases = "-moz-max-content")] - MaxContent, - #[parse(aliases = "-moz-min-content")] - MinContent, - MozFitContent, - MozAvailable, -} - /// A computed value for `min-width`, `min-height`, `width` or `height` property. pub type Size = GenericSize<NonNegativeLengthPercentage>; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 9dbbf40b5f4..826bb2a2448 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -63,7 +63,7 @@ pub use self::font::{FontVariantAlternates, FontWeight}; pub use self::font::{FontVariantEastAsian, FontVariationSettings}; pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::image::{Gradient, Image, LineDirection, MozImageRect}; -pub use self::length::{CSSPixelLength, ExtremumLength, NonNegativeLength}; +pub use self::length::{CSSPixelLength, NonNegativeLength}; pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto}; @@ -87,9 +87,9 @@ pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; pub use self::text::TextUnderlinePosition; pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight}; -pub use self::text::{OverflowWrap, TextOverflow, WordBreak, WordSpacing}; +pub use self::text::{OverflowWrap, RubyPosition, TextOverflow, WordBreak, WordSpacing}; pub use self::text::{TextAlign, TextAlignLast, TextEmphasisPosition, TextEmphasisStyle}; -pub use self::text::{TextDecorationLength, TextDecorationSkipInk}; +pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify}; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform, TransformOperation}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs index 4dd8804596e..d8ef03c1f6d 100644 --- a/components/style/values/computed/page.rs +++ b/components/style/values/computed/page.rs @@ -11,4 +11,4 @@ use crate::values::generics::size::Size2D; pub use generics::page::Orientation; pub use generics::page::PaperSize; /// Computed value of the @page size descriptor -pub type PageSize = generics::page::PageSize<Size2D<NonNegativeLength>>; +pub type PageSize = generics::page::GenericPageSize<Size2D<NonNegativeLength>>; diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index b77695e06c0..b42dfb488a3 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -18,10 +18,10 @@ use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; -pub use crate::values::specified::text::{TextAlignLast, TextUnderlinePosition}; -pub use crate::values::specified::{LineBreak, OverflowWrap, WordBreak}; +pub use crate::values::specified::text::{TextAlignLast, TextUnderlinePosition, MozControlCharacterVisibility}; +pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; -pub use crate::values::specified::{TextDecorationSkipInk, TextTransform}; +pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; /// A computed value for the `initial-letter` property. pub type InitialLetter = GenericInitialLetter<CSSFloat, CSSInteger>; diff --git a/components/style/values/computed/ui.rs b/components/style/values/computed/ui.rs index ae12dfcdae0..562b7d15897 100644 --- a/components/style/values/computed/ui.rs +++ b/components/style/values/computed/ui.rs @@ -5,7 +5,7 @@ //! Computed values for UI properties use crate::values::computed::color::Color; -use crate::values::computed::url::ComputedImageUrl; +use crate::values::computed::image::Image; use crate::values::computed::Number; use crate::values::generics::ui as generics; @@ -16,7 +16,7 @@ pub use crate::values::specified::ui::{MozForceBrokenImageIcon, UserSelect}; pub type Cursor = generics::GenericCursor<CursorImage>; /// A computed value for item of `image cursors`. -pub type CursorImage = generics::GenericCursorImage<ComputedImageUrl, Number>; +pub type CursorImage = generics::GenericCursorImage<Image, Number>; /// A computed value for `scrollbar-color` property. pub type ScrollbarColor = generics::GenericScrollbarColor<Color>; diff --git a/components/style/values/generics/color.rs b/components/style/values/generics/color.rs index b4f2e7445ea..5b477dee60d 100644 --- a/components/style/values/generics/color.rs +++ b/components/style/values/generics/color.rs @@ -6,6 +6,11 @@ /// Ratios representing the contribution of color and currentcolor to /// the final color value. +/// +/// NOTE(emilio): For animated colors, the sum of these two might be more than +/// one (because the background color would've been scaled down already). So +/// beware that it is not generally safe to assume that if bg is 1 then fg is 0, +/// for example. #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] #[repr(C)] pub struct ComplexColorRatios { @@ -22,59 +27,52 @@ impl ComplexColorRatios { pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. }; } -/// This enum represents a combined color from a numeric color and +/// This struct represents a combined color from a numeric color and /// the current foreground color (currentcolor keyword). #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] -#[repr(C, u8)] -pub enum GenericColor<RGBA> { - /// Numeric RGBA color. - Numeric(RGBA), - - /// The current foreground color. - CurrentColor, - - /// A linear combination of numeric color and currentcolor. +#[repr(C)] +pub struct GenericColor<RGBA> { + /// The actual numeric color. + pub color: RGBA, + /// The ratios of mixing between numeric and currentcolor. /// The formula is: `color * ratios.bg + currentcolor * ratios.fg`. - Complex { - /// The actual numeric color. - color: RGBA, - /// The ratios of mixing between numeric and currentcolor. - ratios: ComplexColorRatios, - }, + pub ratios: ComplexColorRatios, } pub use self::GenericColor as Color; +impl Color<cssparser::RGBA> { + /// Returns a color value representing currentcolor. + pub fn currentcolor() -> Self { + Color { + color: cssparser::RGBA::transparent(), + ratios: ComplexColorRatios::CURRENT_COLOR, + } + } +} + impl<RGBA> Color<RGBA> { /// Create a color based upon the specified ratios. - pub fn with_ratios(color: RGBA, ratios: ComplexColorRatios) -> Self { - if ratios == ComplexColorRatios::NUMERIC { - Color::Numeric(color) - } else if ratios == ComplexColorRatios::CURRENT_COLOR { - Color::CurrentColor - } else { - Color::Complex { color, ratios } - } + pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self { + Self { color, ratios } } /// Returns a numeric color representing the given RGBA value. pub fn rgba(color: RGBA) -> Self { - Color::Numeric(color) - } - - /// Returns a complex color value representing currentcolor. - pub fn currentcolor() -> Self { - Color::CurrentColor + Self { + color, + ratios: ComplexColorRatios::NUMERIC, + } } /// Whether it is a numeric color (no currentcolor component). pub fn is_numeric(&self) -> bool { - matches!(*self, Color::Numeric(..)) + self.ratios == ComplexColorRatios::NUMERIC } /// Whether it is a currentcolor value (no numeric color component). pub fn is_currentcolor(&self) -> bool { - matches!(*self, Color::CurrentColor) + self.ratios == ComplexColorRatios::CURRENT_COLOR } } @@ -98,6 +96,7 @@ impl<RGBA> From<RGBA> for Color<RGBA> { ToAnimatedValue, ToAnimatedZero, ToComputedValue, + ToResolvedValue, ToCss, ToShmem, )] @@ -110,3 +109,32 @@ pub enum GenericColorOrAuto<C> { } pub use self::GenericColorOrAuto as ColorOrAuto; + +/// Caret color is effectively a ColorOrAuto, but resolves `auto` to +/// currentColor. +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToAnimatedZero, + ToComputedValue, + ToCss, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericCaretColor<C>(pub GenericColorOrAuto<C>); + +impl<C> GenericCaretColor<C> { + /// Returns the `auto` value. + pub fn auto() -> Self { + GenericCaretColor(GenericColorOrAuto::Auto) + } +} + +pub use self::GenericCaretColor as CaretColor; diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs index 76ee97ce175..2a8f70c8f44 100644 --- a/components/style/values/generics/counters.rs +++ b/components/style/values/generics/counters.rs @@ -148,18 +148,18 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool { Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToShmem, )] #[repr(u8)] -pub enum GenericContent<ImageUrl> { +pub enum GenericContent<Image> { /// `normal` reserved keyword. Normal, /// `none` reserved keyword. None, /// Content items. - Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<ImageUrl>>), + Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>), } pub use self::GenericContent as Content; -impl<ImageUrl> Content<ImageUrl> { +impl<Image> Content<Image> { /// Whether `self` represents list of items. #[inline] pub fn is_items(&self) -> bool { @@ -180,14 +180,13 @@ impl<ImageUrl> Content<ImageUrl> { Eq, MallocSizeOf, PartialEq, - SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] -pub enum GenericContentItem<ImageUrl> { +pub enum GenericContentItem<I> { /// Literal string content. String(crate::OwnedStr), /// `counter(name, style)`. @@ -220,8 +219,8 @@ pub enum GenericContentItem<ImageUrl> { /// `attr([namespace? `|`]? ident)` #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] Attr(Attr), - /// `url(url)` - Url(ImageUrl), + /// image-set(url) | url(url) + Image(I), } pub use self::GenericContentItem as ContentItem; diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index d49a8a2b4de..014ae46db02 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -132,7 +132,7 @@ pub struct GenericImageSet<Image, Resolution> { /// An optional percent and a cross fade image. #[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss, + Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] pub struct GenericImageSetItem<Image, Resolution> { @@ -142,7 +142,33 @@ pub struct GenericImageSetItem<Image, Resolution> { /// /// TODO: Skip serialization if it is 1x. pub resolution: Resolution, - // TODO: type() function. + + /// The `type(<string>)` + /// (Optional) Specify the image's MIME type + pub mime_type: crate::OwnedStr, + + /// True if mime_type has been specified + pub has_mime_type: bool, +} + +impl<I: style_traits::ToCss, R: style_traits::ToCss> ToCss for GenericImageSetItem<I, R> +{ + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: fmt::Write, + { + self.image.to_css(dest)?; + dest.write_str(" ")?; + self.resolution.to_css(dest)?; + + if self.has_mime_type { + dest.write_str(" ")?; + dest.write_str("type(")?; + self.mime_type.to_css(dest)?; + dest.write_str(")")?; + } + Ok(()) + } } pub use self::GenericImageSet as ImageSet; diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs index d9a074623c3..f3e025f80b7 100644 --- a/components/style/values/generics/length.rs +++ b/components/style/values/generics/length.rs @@ -5,8 +5,6 @@ //! Generic types for CSS values related to length. use crate::parser::{Parse, ParserContext}; -#[cfg(feature = "gecko")] -use crate::values::computed::ExtremumLength; use crate::Zero; use cssparser::Parser; use style_traits::ParseError; @@ -153,7 +151,16 @@ pub enum GenericSize<LengthPercent> { Auto, #[cfg(feature = "gecko")] #[animation(error)] - ExtremumLength(ExtremumLength), + MaxContent, + #[cfg(feature = "gecko")] + #[animation(error)] + MinContent, + #[cfg(feature = "gecko")] + #[animation(error)] + MozFitContent, + #[cfg(feature = "gecko")] + #[animation(error)] + MozAvailable, } pub use self::GenericSize as Size; @@ -196,7 +203,18 @@ pub enum GenericMaxSize<LengthPercent> { None, #[cfg(feature = "gecko")] #[animation(error)] - ExtremumLength(ExtremumLength), + #[parse(aliases = "-moz-max-content")] + MaxContent, + #[cfg(feature = "gecko")] + #[animation(error)] + #[parse(aliases = "-moz-min-content")] + MinContent, + #[cfg(feature = "gecko")] + #[animation(error)] + MozFitContent, + #[cfg(feature = "gecko")] + #[animation(error)] + MozAvailable, } pub use self::GenericMaxSize as MaxSize; diff --git a/components/style/values/generics/page.rs b/components/style/values/generics/page.rs index fc771062333..35ffee7b97e 100644 --- a/components/style/values/generics/page.rs +++ b/components/style/values/generics/page.rs @@ -86,7 +86,7 @@ pub enum Orientation { ToShmem, )] #[repr(C, u8)] -pub enum PageSize<S> { +pub enum GenericPageSize<S> { /// Page dimensions. Size(S), /// Paper size with no orientation. @@ -99,6 +99,8 @@ pub enum PageSize<S> { Auto, } +pub use self::GenericPageSize as PageSize; + impl<S> PageSize<S> { /// `auto` value. #[inline] diff --git a/components/style/values/generics/ui.rs b/components/style/values/generics/ui.rs index 04c2951c70b..ff6aefdc63e 100644 --- a/components/style/values/generics/ui.rs +++ b/components/style/values/generics/ui.rs @@ -61,15 +61,14 @@ impl<Image: ToCss> ToCss for Cursor<Image> { Debug, MallocSizeOf, PartialEq, - SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] -pub struct GenericCursorImage<ImageUrl, Number> { +pub struct GenericCursorImage<Image, Number> { /// The url to parse images from. - pub url: ImageUrl, + pub image: Image, /// Whether the image has a hotspot or not. pub has_hotspot: bool, /// The x coordinate. @@ -80,12 +79,12 @@ pub struct GenericCursorImage<ImageUrl, Number> { pub use self::GenericCursorImage as CursorImage; -impl<ImageUrl: ToCss, Number: ToCss> ToCss for CursorImage<ImageUrl, Number> { +impl<Image: ToCss, Number: ToCss> ToCss for CursorImage<Image, Number> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write, { - self.url.to_css(dest)?; + self.image.to_css(dest)?; if self.has_hotspot { dest.write_str(" ")?; self.hotspot_x.to_css(dest)?; diff --git a/components/style/values/resolved/color.rs b/components/style/values/resolved/color.rs index 1b845c58e9f..dbf6375e5bf 100644 --- a/components/style/values/resolved/color.rs +++ b/components/style/values/resolved/color.rs @@ -6,7 +6,7 @@ use super::{Context, ToResolvedValue}; -use crate::values::computed; +use crate::values::computed::color as computed; use crate::values::generics::color as generics; impl ToResolvedValue for computed::Color { @@ -20,26 +20,26 @@ impl ToResolvedValue for computed::Color { #[inline] fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - generics::Color::Numeric(resolved) + generics::Color::rgba(resolved) } } -impl ToResolvedValue for computed::ColorOrAuto { +impl ToResolvedValue for computed::CaretColor { // A resolved caret-color value is an rgba color, with auto resolving to // currentcolor. type ResolvedValue = cssparser::RGBA; #[inline] fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue { - let color = match self { + let color = match self.0 { generics::ColorOrAuto::Color(color) => color, - generics::ColorOrAuto::Auto => generics::Color::CurrentColor, + generics::ColorOrAuto::Auto => generics::Color::currentcolor(), }; color.to_resolved_value(context) } #[inline] fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - generics::ColorOrAuto::Color(computed::Color::from_resolved_value(resolved)) + generics::CaretColor(generics::ColorOrAuto::Color(computed::Color::from_resolved_value(resolved))) } } diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index e30ea5215d0..32f4f9bacae 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -6,7 +6,7 @@ use crate::custom_properties::Name as CustomPropertyName; use crate::parser::{Parse, ParserContext}; -use crate::properties::{LonghandId, PropertyDeclarationId, PropertyFlags}; +use crate::properties::{LonghandId, PropertyDeclarationId}; use crate::properties::{PropertyId, ShorthandId}; use crate::values::generics::box_::AnimationIterationCount as GenericAnimationIterationCount; use crate::values::generics::box_::Perspective as GenericPerspective; @@ -1086,46 +1086,58 @@ bitflags! { /// The change bits that we care about. #[derive(Default, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] - pub struct WillChangeBits: u8 { - /// Whether the stacking context will change. - const STACKING_CONTEXT = 1 << 0; - /// Whether `transform` will change. + pub struct WillChangeBits: u16 { + /// Whether a property which can create a stacking context **on any + /// box** will change. + const STACKING_CONTEXT_UNCONDITIONAL = 1 << 0; + /// Whether `transform` or related properties will change. const TRANSFORM = 1 << 1; /// Whether `scroll-position` will change. const SCROLL = 1 << 2; + /// Whether `contain` will change. + const CONTAIN = 1 << 3; /// Whether `opacity` will change. - const OPACITY = 1 << 3; - /// Fixed pos containing block. - const FIXPOS_CB = 1 << 4; - /// Abs pos containing block. - const ABSPOS_CB = 1 << 5; + const OPACITY = 1 << 4; + /// Whether `perspective` will change. + const PERSPECTIVE = 1 << 5; + /// Whether `z-index` will change. + const Z_INDEX = 1 << 6; + /// Whether any property which creates a containing block for non-svg + /// text frames will change. + const FIXPOS_CB_NON_SVG = 1 << 7; + /// Whether the position property will change. + const POSITION = 1 << 8; } } +#[cfg(feature = "gecko")] fn change_bits_for_longhand(longhand: LonghandId) -> WillChangeBits { - let mut flags = match longhand { + match longhand { LonghandId::Opacity => WillChangeBits::OPACITY, - LonghandId::Transform => WillChangeBits::TRANSFORM, - #[cfg(feature = "gecko")] - LonghandId::Translate | LonghandId::Rotate | LonghandId::Scale | LonghandId::OffsetPath => { - WillChangeBits::TRANSFORM + LonghandId::Contain => WillChangeBits::CONTAIN, + LonghandId::Perspective => WillChangeBits::PERSPECTIVE, + LonghandId::Position => { + WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::POSITION + }, + LonghandId::ZIndex => WillChangeBits::Z_INDEX, + LonghandId::Transform | + LonghandId::TransformStyle | + LonghandId::Translate | + LonghandId::Rotate | + LonghandId::Scale | + LonghandId::OffsetPath => WillChangeBits::TRANSFORM, + LonghandId::BackdropFilter | LonghandId::Filter => { + WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::FIXPOS_CB_NON_SVG }, + LonghandId::MixBlendMode | + LonghandId::Isolation | + LonghandId::MaskImage | + LonghandId::ClipPath => WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL, _ => WillChangeBits::empty(), - }; - - let property_flags = longhand.flags(); - if property_flags.contains(PropertyFlags::CREATES_STACKING_CONTEXT) { - flags |= WillChangeBits::STACKING_CONTEXT; - } - if property_flags.contains(PropertyFlags::FIXPOS_CB) { - flags |= WillChangeBits::FIXPOS_CB; } - if property_flags.contains(PropertyFlags::ABSPOS_CB) { - flags |= WillChangeBits::ABSPOS_CB; - } - flags } +#[cfg(feature = "gecko")] fn change_bits_for_maybe_property(ident: &str, context: &ParserContext) -> WillChangeBits { let id = match PropertyId::parse_ignoring_rule_type(ident, context) { Ok(id) => id, @@ -1143,6 +1155,7 @@ fn change_bits_for_maybe_property(ident: &str, context: &ParserContext) -> WillC } } +#[cfg(feature = "gecko")] impl Parse for WillChange { /// auto | <animateable-feature># fn parse<'i, 't>( @@ -1838,10 +1851,6 @@ pub enum Appearance { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozMacSourceListSelection, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacVibrancyDark, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozMacVibrancyLight, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozMacVibrantTitlebarDark, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozMacVibrantTitlebarLight, diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index c42683bce1f..d5733a6ce4a 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -374,9 +374,9 @@ impl CalcNode { rhs.negate(); sum.push(rhs); }, - ref t => { - let t = t.clone(); - return Err(input.new_unexpected_token_error(t)); + _ => { + input.reset(&start); + break; }, } }, diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index e6491049619..773a93366b8 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -9,8 +9,9 @@ use super::AllowQuirks; use crate::gecko_bindings::structs::nscolor; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; -use crate::values::generics::color::{Color as GenericColor, ColorOrAuto as GenericColorOrAuto}; +use crate::values::generics::color::{GenericColorOrAuto, GenericCaretColor}; use crate::values::specified::calc::CalcNode; +use crate::values::specified::Percentage; use cssparser::{AngleOrNumber, Color as CSSParserColor, Parser, Token, RGBA}; use cssparser::{BasicParseErrorKind, NumberOrPercentage, ParseErrorKind}; use itoa; @@ -19,6 +20,117 @@ use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; +/// A restricted version of the css `color-mix()` function, which only supports +/// percentages and sRGB color-space interpolation. +/// +/// https://drafts.csswg.org/css-color-5/#color-mix +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] +#[allow(missing_docs)] +pub struct ColorMix { + pub left: Color, + pub left_percentage: Percentage, + pub right: Color, + pub right_percentage: Percentage, +} + +#[cfg(feature = "gecko")] +#[inline] +fn allow_color_mix() -> bool { + static_prefs::pref!("layout.css.color-mix.enabled") +} + +#[cfg(feature = "servo")] +#[inline] +fn allow_color_mix() -> bool { + false +} + +// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem +// particularly complete, and disagrees with the examples. +// +// [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed +impl Parse for ColorMix { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let enabled = + context.chrome_rules_enabled() || allow_color_mix(); + + if !enabled { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + input.expect_function_matching("color-mix")?; + + // NOTE(emilio): This implements the syntax described here for now, + // might need to get updated in the future. + // + // https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765 + input.parse_nested_block(|input| { + input.expect_ident_matching("in")?; + // TODO: support multiple interpolation spaces. + input.expect_ident_matching("srgb")?; + input.expect_comma()?; + + let left = Color::parse(context, input)?; + let left_percentage = input.try_parse(|input| Percentage::parse(context, input)).ok(); + + input.expect_comma()?; + + let right = Color::parse(context, input)?; + let right_percentage = input + .try_parse(|input| Percentage::parse(context, input)) + .unwrap_or_else(|_| { + Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())) + }); + + let left_percentage = + left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get())); + Ok(ColorMix { + left, + left_percentage, + right, + right_percentage, + }) + }) + } +} + +impl ToCss for ColorMix { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool { + if percent.is_calc() { + return false; + } + if percent.get() == 0.5 { + return other.get() == 0.5; + } + if is_left { + return false; + } + (1.0 - percent.get() - other.get()).abs() <= f32::EPSILON + } + + dest.write_str("color-mix(in srgb, ")?; + self.left.to_css(dest)?; + if !can_omit(&self.left_percentage, &self.right_percentage, true) { + dest.write_str(" ")?; + self.left_percentage.to_css(dest)?; + } + dest.write_str(", ")?; + self.right.to_css(dest)?; + if !can_omit(&self.right_percentage, &self.left_percentage, false) { + dest.write_str(" ")?; + self.right_percentage.to_css(dest)?; + } + dest.write_str(")") + } +} + /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum Color { @@ -36,6 +148,8 @@ pub enum Color { /// A system color #[cfg(feature = "gecko")] System(SystemColor), + /// A color mix. + ColorMix(Box<ColorMix>), /// Quirksmode-only rule for inheriting color from the body #[cfg(feature = "gecko")] InheritFromBodyQuirk, @@ -72,8 +186,6 @@ pub enum SystemColor { #[css(skip)] TextSelectForeground, #[css(skip)] - TextSelectForegroundCustom, - #[css(skip)] TextSelectBackgroundDisabled, #[css(skip)] TextSelectBackgroundAttention, @@ -215,8 +327,6 @@ pub enum SystemColor { /// Font smoothing background colors needed by the Mac OS X theme, based on /// -moz-appearance names. - MozMacVibrancyLight, - MozMacVibrancyDark, MozMacVibrantTitlebarLight, MozMacVibrantTitlebarDark, MozMacMenupopup, @@ -235,10 +345,6 @@ pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozAccentColorForeground, - /// Accent color for title bar. - MozWinAccentcolor, - /// Color from drawing text over the accent color. - MozWinAccentcolortext, /// Media rebar text. MozWinMediatext, /// Communications rebar text. @@ -338,8 +444,6 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen } fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> { - use crate::values::specified::Percentage; - Ok(Percentage::parse(self.0, input)?.get()) } @@ -398,6 +502,10 @@ impl Parse for Color { } } + if let Ok(mix) = input.try_parse(|i| ColorMix::parse(context, i)) { + return Ok(Color::ColorMix(Box::new(mix))); + } + match e.kind { ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(t)) => { Err(e.location.new_custom_error(StyleParseErrorKind::ValueError( @@ -425,7 +533,9 @@ impl ToCss for Color { Color::Numeric { parsed: ref rgba, .. } => rgba.to_css(dest), + // TODO: Could represent this as a color-mix() instead. Color::Complex(_) => Ok(()), + Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] Color::System(system) => system.to_css(dest), #[cfg(feature = "gecko")] @@ -447,6 +557,18 @@ fn parse_hash_color(value: &[u8]) -> Result<RGBA, ()> { } impl Color { + /// Returns whether this color is a system color. + #[cfg(feature = "gecko")] + pub fn is_system(&self) -> bool { + matches!(self, Color::System(..)) + } + + /// Returns whether this color is a system color. + #[cfg(feature = "servo")] + pub fn is_system(&self) -> bool { + false + } + /// Returns currentcolor value. #[inline] pub fn currentcolor() -> Color { @@ -562,17 +684,28 @@ impl Color { /// /// If `context` is `None`, and the specified color requires data from /// the context to resolve, then `None` is returned. - pub fn to_computed_color(&self, _context: Option<&Context>) -> Option<ComputedColor> { + pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> { Some(match *self { Color::CurrentColor => ComputedColor::currentcolor(), Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed), Color::Complex(ref complex) => *complex, + Color::ColorMix(ref mix) => { + use crate::values::animated::color::Color as AnimatedColor; + use crate::values::animated::ToAnimatedValue; + + let left = mix.left.to_computed_color(context)?.to_animated_value(); + let right = mix.right.to_computed_color(context)?.to_animated_value(); + ToAnimatedValue::from_animated_value(AnimatedColor::mix( + &left, + mix.left_percentage.get(), + &right, + mix.right_percentage.get(), + )) + }, #[cfg(feature = "gecko")] - Color::System(system) => system.compute(_context?), + Color::System(system) => system.compute(context?), #[cfg(feature = "gecko")] - Color::InheritFromBodyQuirk => { - ComputedColor::rgba(_context?.device().body_text_color()) - }, + Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()), }) } } @@ -585,11 +718,13 @@ impl ToComputedValue for Color { } fn from_computed_value(computed: &ComputedColor) -> Self { - match *computed { - GenericColor::Numeric(color) => Color::rgba(color), - GenericColor::CurrentColor => Color::currentcolor(), - GenericColor::Complex { .. } => Color::Complex(*computed), + if computed.is_numeric() { + return Color::rgba(computed.color); } + if computed.is_currentcolor() { + return Color::currentcolor(); + } + Color::Complex(*computed) } } @@ -671,3 +806,15 @@ impl Parse for ColorPropertyValue { /// auto | <color> pub type ColorOrAuto = GenericColorOrAuto<Color>; + +/// caret-color +pub type CaretColor = GenericCaretColor<Color>; + +impl Parse for CaretColor { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + ColorOrAuto::parse(context, input).map(GenericCaretColor) + } +} diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index ece5739ba0e..aa442549fa5 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -11,15 +11,15 @@ use crate::values::generics::counters as generics; use crate::values::generics::counters::CounterPair; #[cfg(feature = "gecko")] use crate::values::generics::CounterStyle; -use crate::values::specified::url::SpecifiedImageUrl; +use crate::values::specified::image::Image; #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] use crate::values::specified::Attr; use crate::values::specified::Integer; use crate::values::CustomIdent; use cssparser::{Parser, Token}; -#[cfg(feature = "servo-layout-2013")] +#[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] use selectors::parser::SelectorParseErrorKind; -use style_traits::{ParseError, StyleParseErrorKind}; +use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind}; /// A specified value for the `counter-increment` property. pub type CounterIncrement = generics::GenericCounterIncrement<Integer>; @@ -83,10 +83,10 @@ fn parse_counters<'i, 't>( } /// The specified value for the `content` property. -pub type Content = generics::GenericContent<SpecifiedImageUrl>; +pub type Content = generics::GenericContent<Image>; /// The specified value for a content item in the `content` property. -pub type ContentItem = generics::GenericContentItem<SpecifiedImageUrl>; +pub type ContentItem = generics::GenericContentItem<Image>; impl Content { #[cfg(feature = "servo-layout-2013")] @@ -137,8 +137,8 @@ impl Parse for Content { loop { #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] { - if let Ok(url) = input.try_parse(|i| SpecifiedImageUrl::parse(context, i)) { - content.push(generics::ContentItem::Url(url)); + if let Ok(image) = input.try_parse(|i| Image::parse_only_url(context, i)) { + content.push(generics::ContentItem::Image(image)); continue; } } @@ -171,6 +171,7 @@ impl Parse for Content { Ok(generics::ContentItem::Attr(Attr::parse_function(context, input)?)) }), _ => { + use style_traits::StyleParseErrorKind; let name = name.clone(); return Err(input.new_custom_error( StyleParseErrorKind::UnexpectedFunction(name), @@ -213,3 +214,20 @@ impl Parse for Content { Ok(generics::Content::Items(content.into())) } } + +impl<Image> SpecifiedValueInfo for generics::GenericContentItem<Image> { + fn collect_completion_keywords(f: KeywordsCollectFn) { + f(&[ + "url", + "image-set", + "counter", + "counters", + "attr", + "open-quote", + "close-quote", + "no-open-quote", + "no-close-quote", + "-moz-alt-content", + ]); + } +} diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index b83b6411fee..329dedd31ca 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -9,7 +9,6 @@ use crate::context::QuirksMode; #[cfg(feature = "gecko")] use crate::gecko_bindings::bindings; use crate::parser::{Parse, ParserContext}; -use crate::properties::longhands::system_font::SystemFont; use crate::values::computed::font::{FamilyName, FontFamilyList, FontStyleAngle, SingleFontFamily}; use crate::values::computed::{font as computed, Length, NonNegativeLength}; use crate::values::computed::{Angle as ComputedAngle, Percentage as ComputedPercentage}; @@ -19,7 +18,7 @@ use crate::values::generics::font::{self as generics, FeatureTagValue, FontSetti use crate::values::generics::NonNegative; use crate::values::specified::length::{FontBaseSize, PX_PER_PT}; use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage}; -use crate::values::specified::{NoCalcLength, NonNegativeNumber, Number, Percentage}; +use crate::values::specified::{NoCalcLength, NonNegativeNumber, Number, NonNegativePercentage}; use crate::values::CustomIdent; use crate::Atom; use cssparser::{Parser, Token}; @@ -65,6 +64,54 @@ macro_rules! system_font_methods { }; } +/// System fonts. +#[repr(u8)] +#[derive( + Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem +)] +#[allow(missing_docs)] +#[cfg(feature = "gecko")] +pub enum SystemFont { + Caption, + Icon, + Menu, + MessageBox, + SmallCaption, + StatusBar, + MozWindow, + MozDocument, + MozWorkspace, + MozDesktop, + MozInfo, + MozDialog, + MozButton, + MozPullDownMenu, + MozList, + MozField, + #[css(skip)] + End, // Just for indexing purposes. +} + +// We don't parse system fonts in servo, but in the interest of not +// littering a lot of code with `if engine == "gecko"` conditionals, +// we have a dummy system font module that does nothing + +#[derive( + Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem +)] +#[allow(missing_docs)] +#[cfg(feature = "servo")] +/// void enum for system font, can never exist +pub enum SystemFont {} + +#[allow(missing_docs)] +#[cfg(feature = "servo")] +impl SystemFont { + pub fn parse(_: &mut Parser) -> Result<Self, ()> { + Err(()) + } +} + const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8; const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71; @@ -359,13 +406,11 @@ impl ToComputedValue for FontStyle { /// A value for the `font-stretch` property. /// /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop -/// -/// TODO(emilio): We could derive Parse if we had NonNegativePercentage. #[allow(missing_docs)] -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] #[repr(u8)] pub enum FontStretch { - Stretch(Percentage), + Stretch(NonNegativePercentage), Keyword(FontStretchKeyword), #[css(skip)] System(SystemFont), @@ -452,32 +497,13 @@ impl FontStretch { system_font_methods!(FontStretch, font_stretch); } -impl Parse for FontStretch { - fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result<Self, ParseError<'i>> { - // From https://drafts.csswg.org/css-fonts-4/#font-stretch-prop: - // - // Values less than 0% are not allowed and are treated as parse - // errors. - if let Ok(percentage) = - input.try_parse(|input| Percentage::parse_non_negative(context, input)) - { - return Ok(FontStretch::Stretch(percentage)); - } - - Ok(FontStretch::Keyword(FontStretchKeyword::parse(input)?)) - } -} - impl ToComputedValue for FontStretch { type ComputedValue = computed::FontStretch; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { FontStretch::Stretch(ref percentage) => { - computed::FontStretch(NonNegative(percentage.to_computed_value(context))) + computed::FontStretch(percentage.to_computed_value(context)) }, FontStretch::Keyword(ref kw) => computed::FontStretch(NonNegative(kw.compute())), FontStretch::System(_) => self.compute_system(context), @@ -485,7 +511,7 @@ impl ToComputedValue for FontStretch { } fn from_computed_value(computed: &Self::ComputedValue) -> Self { - FontStretch::Stretch(Percentage::from_computed_value(&(computed.0).0)) + FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative((computed.0).0))) } } @@ -2226,6 +2252,37 @@ impl Parse for VariationValue<Number> { } } +/// A metrics override value for a @font-face descriptor +/// +/// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +pub enum MetricsOverride { + /// A non-negative `<percentage>` of the computed font size + Override(NonNegativePercentage), + /// Normal metrics from the font. + Normal, +} + +impl MetricsOverride { + #[inline] + /// Get default value with `normal` + pub fn normal() -> MetricsOverride { + MetricsOverride::Normal + } + + /// The ToComputedValue implementation, used for @font-face descriptors. + /// + /// Valid override percentages must be non-negative; we return -1.0 to indicate + /// the absence of an override (i.e. 'normal'). + #[inline] + pub fn compute(&self) -> ComputedPercentage { + match *self { + MetricsOverride::Normal => ComputedPercentage(-1.0), + MetricsOverride::Override(percent) => ComputedPercentage(percent.0.get()), + } + } +} + #[derive( Clone, Copy, diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 3ec678cd237..85c178e064d 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -181,7 +181,7 @@ impl Parse for Image { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Image, ParseError<'i>> { - Image::parse_with_cors_mode(context, input, CorsMode::None, /* allow_none = */ true) + Image::parse_with_cors_mode(context, input, CorsMode::None, /* allow_none = */ true, /* only_url = */ false) } } @@ -191,23 +191,32 @@ impl Image { input: &mut Parser<'i, 't>, cors_mode: CorsMode, allow_none: bool, + only_url: bool, ) -> Result<Image, ParseError<'i>> { if allow_none && input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { return Ok(generic::Image::None); } + if let Ok(url) = input .try_parse(|input| SpecifiedImageUrl::parse_with_cors_mode(context, input, cors_mode)) { return Ok(generic::Image::Url(url)); } - if let Ok(gradient) = input.try_parse(|i| Gradient::parse(context, i)) { - return Ok(generic::Image::Gradient(Box::new(gradient))); - } + if image_set_enabled() { - if let Ok(is) = input.try_parse(|input| ImageSet::parse(context, input, cors_mode)) { + if let Ok(is) = input.try_parse(|input| ImageSet::parse(context, input, cors_mode, only_url)) { return Ok(generic::Image::ImageSet(Box::new(is))); } } + + if only_url { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + if let Ok(gradient) = input.try_parse(|i| Gradient::parse(context, i)) { + return Ok(generic::Image::Gradient(Box::new(gradient))); + } + if cross_fade_enabled() { if let Ok(cf) = input.try_parse(|input| CrossFade::parse(context, input, cors_mode)) { return Ok(generic::Image::CrossFade(Box::new(cf))); @@ -264,6 +273,21 @@ impl Image { input, CorsMode::Anonymous, /* allow_none = */ true, + /* only_url = */ false, + ) + } + + /// Provides an alternate method for parsing, but only for urls. + pub fn parse_only_url<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Image, ParseError<'i>> { + Self::parse_with_cors_mode( + context, + input, + CorsMode::None, + /* allow_none = */ false, + /* only_url = */ true, ) } } @@ -310,7 +334,7 @@ impl CrossFadeImage { cors_mode: CorsMode, ) -> Result<Self, ParseError<'i>> { if let Ok(image) = input.try_parse(|input| { - Image::parse_with_cors_mode(context, input, cors_mode, /* allow_none = */ false) + Image::parse_with_cors_mode(context, input, cors_mode, /* allow_none = */ false, /* only_url = */ false) }) { return Ok(Self::Image(image)); } @@ -339,10 +363,11 @@ impl ImageSet { context: &ParserContext, input: &mut Parser<'i, 't>, cors_mode: CorsMode, + only_url: bool, ) -> Result<Self, ParseError<'i>> { input.expect_function_matching("image-set")?; let items = input.parse_nested_block(|input| { - input.parse_comma_separated(|input| ImageSetItem::parse(context, input, cors_mode)) + input.parse_comma_separated(|input| ImageSetItem::parse(context, input, cors_mode, only_url)) })?; Ok(Self { selected_index: 0, @@ -352,10 +377,18 @@ impl ImageSet { } impl ImageSetItem { + fn parse_type<'i>(p: &mut Parser<'i, '_>) -> Result<crate::OwnedStr, ParseError<'i>> { + p.expect_function_matching("type")?; + p.parse_nested_block(|input| { + Ok(input.expect_string()?.as_ref().to_owned().into()) + }) + } + fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, cors_mode: CorsMode, + only_url: bool, ) -> Result<Self, ParseError<'i>> { let image = match input.try_parse(|i| i.expect_url_or_string()) { Ok(url) => Image::Url(SpecifiedImageUrl::parse_from_string( @@ -364,13 +397,23 @@ impl ImageSetItem { cors_mode, )), Err(..) => Image::parse_with_cors_mode( - context, input, cors_mode, /* allow_none = */ false, + context, input, cors_mode, /* allow_none = */ false, /* only_url = */ only_url )?, }; - let resolution = input - .try_parse(|input| Resolution::parse(context, input)) - .unwrap_or(Resolution::X(1.0)); - Ok(Self { image, resolution }) + + let mut resolution = input.try_parse(|input| Resolution::parse(context, input)).ok(); + let mime_type = input.try_parse(Self::parse_type).ok(); + + // Try to parse resolution after type(). + if mime_type.is_some() && resolution.is_none() { + resolution = input.try_parse(|input| Resolution::parse(context, input)).ok(); + } + + let resolution = resolution.unwrap_or(Resolution::X(1.0)); + let has_mime_type = mime_type.is_some(); + let mime_type = mime_type.unwrap_or_default(); + + Ok(Self { image, resolution, has_mime_type, mime_type }) } } diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index cf529408c98..aaad895dbce 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -1226,6 +1226,27 @@ impl Parse for Size { } } +macro_rules! parse_size_non_length { + ($size:ident, $input:expr, $auto_or_none:expr => $auto_or_none_ident:ident) => {{ + let size = $input.try_parse(|input| { + Ok(try_match_ident_ignore_ascii_case! { input, + #[cfg(feature = "gecko")] + "min-content" | "-moz-min-content" => $size::MinContent, + #[cfg(feature = "gecko")] + "max-content" | "-moz-max-content" => $size::MaxContent, + #[cfg(feature = "gecko")] + "-moz-fit-content" => $size::MozFitContent, + #[cfg(feature = "gecko")] + "-moz-available" => $size::MozAvailable, + $auto_or_none => $size::$auto_or_none_ident, + }) + }); + if size.is_ok() { + return size; + } + }}; +} + impl Size { /// Parses, with quirks. pub fn parse_quirky<'i, 't>( @@ -1233,16 +1254,7 @@ impl Size { input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>> { - #[cfg(feature = "gecko")] - { - if let Ok(l) = input.try_parse(computed::ExtremumLength::parse) { - return Ok(GenericSize::ExtremumLength(l)); - } - } - - if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { - return Ok(GenericSize::Auto); - } + parse_size_non_length!(Size, input, "auto" => Auto); let length = NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)?; Ok(GenericSize::LengthPercentage(length)) @@ -1274,16 +1286,7 @@ impl MaxSize { input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>> { - #[cfg(feature = "gecko")] - { - if let Ok(l) = input.try_parse(computed::ExtremumLength::parse) { - return Ok(GenericMaxSize::ExtremumLength(l)); - } - } - - if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(GenericMaxSize::None); - } + parse_size_non_length!(MaxSize, input, "none" => None); let length = NonNegativeLengthPercentage::parse_quirky(context, input, allow_quirks)?; Ok(GenericMaxSize::LengthPercentage(length)) diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index d91f9b8195f..8a9cb73f4da 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -71,7 +71,7 @@ pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; pub use self::page::{Orientation, PageSize, PaperSize}; -pub use self::percentage::Percentage; +pub use self::percentage::{Percentage, NonNegativePercentage}; pub use self::position::AspectRatio; pub use self::position::{ GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, @@ -86,10 +86,11 @@ pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; pub use self::svg_path::SVGPathData; pub use self::text::TextAlignLast; pub use self::text::TextUnderlinePosition; +pub use self::text::RubyPosition; pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAlign}; pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; -pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextTransform}; +pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform}; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs index eac4d91cf8f..a152f7d8dc0 100644 --- a/components/style/values/specified/percentage.rs +++ b/components/style/values/specified/percentage.rs @@ -7,6 +7,7 @@ use crate::parser::{Parse, ParserContext}; use crate::values::computed::percentage::Percentage as ComputedPercentage; use crate::values::computed::{Context, ToComputedValue}; +use crate::values::generics::NonNegative; use crate::values::specified::calc::CalcNode; use crate::values::specified::Number; use crate::values::{serialize_percentage, CSSFloat}; @@ -172,3 +173,24 @@ impl ToComputedValue for Percentage { } impl SpecifiedValueInfo for Percentage {} + +/// A wrapper of Percentage, whose value must be >= 0. +pub type NonNegativePercentage = NonNegative<Percentage>; + +impl Parse for NonNegativePercentage { + #[inline] + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + Ok(NonNegative(Percentage::parse_non_negative(context, input)?)) + } +} + +impl NonNegativePercentage { + /// Convert to ComputedPercentage, for FontFaceRule size-adjust getter. + #[inline] + pub fn compute(&self) -> ComputedPercentage { + ComputedPercentage(self.0.get()) + } +} diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 7c5c712eb3e..ce652734060 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -22,6 +22,7 @@ use selectors::parser::SelectorParseErrorKind; use std::fmt::{self, Write}; use style_traits::values::SequenceWriter; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use style_traits::{KeywordsCollectFn, SpecifiedValueInfo}; use unicode_segmentation::UnicodeSegmentation; /// A specified type for the `initial-letter` property. @@ -1000,6 +1001,67 @@ pub enum WordBreak { BreakWord, } +/// Values for the `text-justify` CSS property. +#[repr(u8)] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +pub enum TextJustify { + Auto, + None, + InterWord, + // See https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute + // and https://github.com/w3c/csswg-drafts/issues/6156 for the alias. + #[parse(aliases = "distribute")] + InterCharacter, +} + +/// Values for the `-moz-control-character-visibility` CSS property. +#[repr(u8)] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +pub enum MozControlCharacterVisibility { + Hidden, + Visible, +} + +#[cfg(feature = "gecko")] +impl Default for MozControlCharacterVisibility { + fn default() -> Self { + if static_prefs::pref!("layout.css.control-characters.visible") { + Self::Visible + } else { + Self::Hidden + } + } +} + + /// Values for the `line-break` property. #[repr(u8)] #[derive( @@ -1193,3 +1255,72 @@ impl ToCss for TextUnderlinePosition { Ok(()) } } + +/// Values for `ruby-position` property +#[repr(u8)] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[allow(missing_docs)] +pub enum RubyPosition { + AlternateOver, + AlternateUnder, + Over, + Under, +} + +impl Parse for RubyPosition { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<RubyPosition, ParseError<'i>> { + // Parse alternate before + let alternate = input.try_parse(|i| i.expect_ident_matching("alternate")).is_ok(); + if alternate && input.is_exhausted() { + return Ok(RubyPosition::AlternateOver); + } + // Parse over / under + let over = try_match_ident_ignore_ascii_case! { input, + "over" => true, + "under" => false, + }; + // Parse alternate after + let alternate = alternate || + input.try_parse(|i| i.expect_ident_matching("alternate")).is_ok(); + + Ok(match (over, alternate) { + (true, true) => RubyPosition::AlternateOver, + (false, true) => RubyPosition::AlternateUnder, + (true, false) => RubyPosition::Over, + (false, false) => RubyPosition::Under, + }) + } +} + +impl ToCss for RubyPosition { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + dest.write_str(match self { + RubyPosition::AlternateOver => "alternate", + RubyPosition::AlternateUnder => "alternate under", + RubyPosition::Over => "over", + RubyPosition::Under => "under", + }) + } +} + +impl SpecifiedValueInfo for RubyPosition { + fn collect_completion_keywords(f: KeywordsCollectFn) { + f(&["alternate", "over", "under"]) + } +} diff --git a/components/style/values/specified/ui.rs b/components/style/values/specified/ui.rs index 74440a67205..4594cbfcc77 100644 --- a/components/style/values/specified/ui.rs +++ b/components/style/values/specified/ui.rs @@ -7,17 +7,17 @@ use crate::parser::{Parse, ParserContext}; use crate::values::generics::ui as generics; use crate::values::specified::color::Color; -use crate::values::specified::url::SpecifiedImageUrl; +use crate::values::specified::image::Image; use crate::values::specified::Number; use cssparser::Parser; use std::fmt::{self, Write}; -use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; +use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; /// A specified value for the `cursor` property. pub type Cursor = generics::GenericCursor<CursorImage>; /// A specified value for item of `image cursors`. -pub type CursorImage = generics::GenericCursorImage<SpecifiedImageUrl, Number>; +pub type CursorImage = generics::GenericCursorImage<Image, Number>; impl Parse for Cursor { /// cursor: [<url> [<number> <number>]?]# [auto | default | ...] @@ -47,7 +47,7 @@ impl Parse for CursorImage { ) -> Result<Self, ParseError<'i>> { use crate::Zero; - let url = SpecifiedImageUrl::parse(context, input)?; + let image = Image::parse_only_url(context, input)?; let mut has_hotspot = false; let mut hotspot_x = Number::zero(); let mut hotspot_y = Number::zero(); @@ -59,7 +59,7 @@ impl Parse for CursorImage { } Ok(Self { - url, + image, has_hotspot, hotspot_x, hotspot_y, @@ -67,6 +67,13 @@ impl Parse for CursorImage { } } +// This trait is manually implemented because we don't support the whole <image> +// syntax for cursors +impl SpecifiedValueInfo for CursorImage { + fn collect_completion_keywords(f: KeywordsCollectFn) { + f(&["url", "image-set"]); + } +} /// Specified value of `-moz-force-broken-image-icon` #[derive( Clone, diff --git a/servo-tidy.toml b/servo-tidy.toml index 7f1ce0b7ab2..74186f1b8ea 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -91,6 +91,11 @@ packages = [ # Files that are ignored for all tidy and lint checks. files = [ "./components/net/tests/parsable_mime/text", + # These are ignored to avoid diverging from Gecko + "./components/style/properties/helpers.mako.rs", + "./components/style/stylesheets/rule_parser.rs", + "./components/style/stylist.rs", + "./components/style/values/computed/image.rs", # Mako does not lend itself easily to splitting long lines "./components/style/properties/helpers/animated_properties.mako.rs", "./components/style/properties/shorthands/text.mako.rs", diff --git a/tests/wpt/metadata-layout-2020/css/css-values/clamp-length-computed.html.ini b/tests/wpt/metadata-layout-2020/css/css-values/clamp-length-computed.html.ini deleted file mode 100644 index 215c616a975..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-values/clamp-length-computed.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[clamp-length-computed.html] - [Property letter-spacing value 'clamp(10px, 35px , 30px)'] - expected: FAIL - - [Property letter-spacing value 'clamp(10px, 35px /*foo*/, 30px)'] - expected: FAIL - - [Property letter-spacing value 'clamp(10px /* foo */ , 35px, 30px)'] - expected: FAIL - - [Property letter-spacing value 'clamp(10px , 35px, 30px)'] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/inheritance.html.ini b/tests/wpt/metadata/css/css-text/inheritance.html.ini index f8cb0572c62..151ed41303a 100644 --- a/tests/wpt/metadata/css/css-text/inheritance.html.ini +++ b/tests/wpt/metadata/css/css-text/inheritance.html.ini @@ -1,7 +1,4 @@ [inheritance.html] - [Property text-justify inherits] - expected: FAIL - [Property text-align-all has initial value start] expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/parsing/text-justify-computed-legacy.html.ini b/tests/wpt/metadata/css/css-text/parsing/text-justify-computed-legacy.html.ini deleted file mode 100644 index 210a79f60ac..00000000000 --- a/tests/wpt/metadata/css/css-text/parsing/text-justify-computed-legacy.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[text-justify-computed-legacy.html] - [Property text-justify value 'distribute'] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/parsing/text-justify-computed.html.ini b/tests/wpt/metadata/css/css-text/parsing/text-justify-computed.html.ini index ae897a3a05a..267731c47cc 100644 --- a/tests/wpt/metadata/css/css-text/parsing/text-justify-computed.html.ini +++ b/tests/wpt/metadata/css/css-text/parsing/text-justify-computed.html.ini @@ -1,7 +1,3 @@ [text-justify-computed.html] [Property text-justify value 'inter-character' computes to 'inter-character'] expected: FAIL - - [Property text-justify value 'inter-character'] - expected: FAIL - diff --git a/tests/wpt/metadata/css/css-text/parsing/text-justify-valid.html.ini b/tests/wpt/metadata/css/css-text/parsing/text-justify-valid.html.ini deleted file mode 100644 index 96b9e419553..00000000000 --- a/tests/wpt/metadata/css/css-text/parsing/text-justify-valid.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[text-justify-valid.html] - [e.style['text-justify'\] = "inter-character" should set the property value] - expected: FAIL - diff --git a/tests/wpt/metadata/css/css-text/text-justify/distribute-alias.tentative.html.ini b/tests/wpt/metadata/css/css-text/text-justify/distribute-alias.tentative.html.ini deleted file mode 100644 index 053373e177d..00000000000 --- a/tests/wpt/metadata/css/css-text/text-justify/distribute-alias.tentative.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[distribute-alias.tentative.html] - [text-justify: distribute is a parse-time alias of inter-character] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-text/text-justify/text-justify-interpolation.html.ini b/tests/wpt/metadata/css/css-text/text-justify/text-justify-interpolation.html.ini index 9ddf45dbf47..b03f0bf0df1 100644 --- a/tests/wpt/metadata/css/css-text/text-justify/text-justify-interpolation.html.ini +++ b/tests/wpt/metadata/css/css-text/text-justify/text-justify-interpolation.html.ini @@ -47,18 +47,6 @@ [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (0.3) should be [inter-character\]] expected: FAIL - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (0.5) should be [inter-character\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (0.6) should be [inter-character\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (1) should be [inter-character\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (1.5) should be [inter-character\]] - expected: FAIL - [CSS Transitions with transition: all: property <text-justify> from [auto\] to [inter-character\] at (-0.3) should be [inter-character\]] expected: FAIL @@ -68,39 +56,6 @@ [CSS Transitions with transition: all: property <text-justify> from [auto\] to [inter-character\] at (0.3) should be [inter-character\]] expected: FAIL - [CSS Transitions with transition: all: property <text-justify> from [auto\] to [inter-character\] at (0.5) should be [inter-character\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [auto\] to [inter-character\] at (0.6) should be [inter-character\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [auto\] to [inter-character\] at (1) should be [inter-character\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [auto\] to [inter-character\] at (1.5) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (-0.3) should be [auto\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (0) should be [auto\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (0.3) should be [auto\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (0.5) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (0.6) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (1) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [auto\] to [inter-character\] at (1.5) should be [inter-character\]] - expected: FAIL - [Web Animations: property <text-justify> from [auto\] to [inter-character\] at (-0.3) should be [auto\]] expected: FAIL @@ -131,60 +86,6 @@ [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (0.3) should be [distribute\]] expected: FAIL - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (0.5) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (0.6) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (1) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (1.5) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (-0.3) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (0) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (0.3) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (0.5) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (0.6) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (1) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-character\] to [distribute\] at (1.5) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (-0.3) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (0) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (0.3) should be [inter-character\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (0.5) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (0.6) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (1) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-character\] to [distribute\] at (1.5) should be [distribute\]] - expected: FAIL - [Web Animations: property <text-justify> from [inter-character\] to [distribute\] at (-0.3) should be [inter-character\]] expected: FAIL @@ -215,18 +116,6 @@ [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (0.3) should be [distribute\]] expected: FAIL - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (0.5) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (0.6) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (1) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (1.5) should be [distribute\]] - expected: FAIL - [CSS Transitions with transition: all: property <text-justify> from [inter-word\] to [distribute\] at (-0.3) should be [distribute\]] expected: FAIL @@ -236,39 +125,6 @@ [CSS Transitions with transition: all: property <text-justify> from [inter-word\] to [distribute\] at (0.3) should be [distribute\]] expected: FAIL - [CSS Transitions with transition: all: property <text-justify> from [inter-word\] to [distribute\] at (0.5) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-word\] to [distribute\] at (0.6) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-word\] to [distribute\] at (1) should be [distribute\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [inter-word\] to [distribute\] at (1.5) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (-0.3) should be [inter-word\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (0) should be [inter-word\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (0.3) should be [inter-word\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (0.5) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (0.6) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (1) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [inter-word\] to [distribute\] at (1.5) should be [distribute\]] - expected: FAIL - [Web Animations: property <text-justify> from [inter-word\] to [distribute\] at (-0.3) should be [inter-word\]] expected: FAIL @@ -299,18 +155,6 @@ [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (0.3) should be [none\]] expected: FAIL - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (0.5) should be [none\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (0.6) should be [none\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (1) should be [none\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (1.5) should be [none\]] - expected: FAIL - [CSS Transitions with transition: all: property <text-justify> from [distribute\] to [none\] at (-0.3) should be [none\]] expected: FAIL @@ -320,39 +164,6 @@ [CSS Transitions with transition: all: property <text-justify> from [distribute\] to [none\] at (0.3) should be [none\]] expected: FAIL - [CSS Transitions with transition: all: property <text-justify> from [distribute\] to [none\] at (0.5) should be [none\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [distribute\] to [none\] at (0.6) should be [none\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [distribute\] to [none\] at (1) should be [none\]] - expected: FAIL - - [CSS Transitions with transition: all: property <text-justify> from [distribute\] to [none\] at (1.5) should be [none\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (-0.3) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (0) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (0.3) should be [distribute\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (0.5) should be [none\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (0.6) should be [none\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (1) should be [none\]] - expected: FAIL - - [CSS Animations: property <text-justify> from [distribute\] to [none\] at (1.5) should be [none\]] - expected: FAIL - [Web Animations: property <text-justify> from [distribute\] to [none\] at (-0.3) should be [distribute\]] expected: FAIL @@ -373,39 +184,3 @@ [Web Animations: property <text-justify> from [distribute\] to [none\] at (1.5) should be [none\]] expected: FAIL - - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (-0.3) should be [auto\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (0) should be [auto\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [auto\] to [inter-character\] at (0.3) should be [auto\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (-0.3) should be [inter-character\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (0) should be [inter-character\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-character\] to [distribute\] at (0.3) should be [inter-character\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (-0.3) should be [inter-word\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (0) should be [inter-word\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [inter-word\] to [distribute\] at (0.3) should be [inter-word\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (-0.3) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (0) should be [distribute\]] - expected: FAIL - - [CSS Transitions: property <text-justify> from [distribute\] to [none\] at (0.3) should be [distribute\]] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-values/clamp-length-computed.html.ini b/tests/wpt/metadata/css/css-values/clamp-length-computed.html.ini index 52278514aa7..add1bb314b6 100644 --- a/tests/wpt/metadata/css/css-values/clamp-length-computed.html.ini +++ b/tests/wpt/metadata/css/css-values/clamp-length-computed.html.ini @@ -10,15 +10,3 @@ [Property letter-spacing value 'clamp(10px, 20px, 30px)' computes to '20px'] expected: FAIL - - [Property letter-spacing value 'clamp(10px, 35px , 30px)'] - expected: FAIL - - [Property letter-spacing value 'clamp(10px, 35px /*foo*/, 30px)'] - expected: FAIL - - [Property letter-spacing value 'clamp(10px /* foo */ , 35px, 30px)'] - expected: FAIL - - [Property letter-spacing value 'clamp(10px , 35px, 30px)'] - expected: FAIL diff --git a/tests/wpt/metadata/css/mediaqueries/test_media_queries.html.ini b/tests/wpt/metadata/css/mediaqueries/test_media_queries.html.ini index fd0f36ba676..4d3fea1a136 100644 --- a/tests/wpt/metadata/css/mediaqueries/test_media_queries.html.ini +++ b/tests/wpt/metadata/css/mediaqueries/test_media_queries.html.ini @@ -4402,3 +4402,9 @@ [expression_should_be_unknown: overflow-block: optional-paged] expected: FAIL + + [expression_should_be_parseable: width > = 0px] + expected: FAIL + + [expression_should_be_parseable: width < = 0px] + expected: FAIL |