diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 8409639055f..2d0ef951067 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,3 +1,5 @@ +-moz-gtk-csd-titlebar-radius +-moz-gtk-menu-radius DOMContentLoaded abort activate diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index a541ee411c7..853a9c9b643 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -105,6 +105,8 @@ impl CSSRule { }, StyleCssRule::Page(_) => unreachable!(), StyleCssRule::Document(_) => unimplemented!(), // TODO + StyleCssRule::Layer(_) => unimplemented!(), // TODO + StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO } } diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index adbeba7e39d..ebd54df0c31 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::attr::AttrHelpersForLayout; use crate::dom::bindings::inheritance::{ CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, }; @@ -264,6 +265,16 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement } } + #[inline(always)] + fn each_attr_name(&self, mut callback: F) + where + F: FnMut(&style::LocalName), + { + for attr in self.element.attrs() { + callback(style::values::GenericAtomIdent::cast(attr.local_name())) + } + } + fn has_dirty_descendants(&self) -> bool { unsafe { self.as_node() diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 48e3016458e..9657227f91a 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -38,7 +38,7 @@ use std::sync::Mutex; use style::media_queries::MediaList; use style::parser::ParserContext; use style::shared_lock::{Locked, SharedRwLock}; -use style::stylesheets::import_rule::ImportSheet; +use style::stylesheets::import_rule::{ImportLayer, ImportSheet}; use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents}; use style::values::CssUrl; @@ -358,6 +358,7 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> { context: &ParserContext, lock: &SharedRwLock, media: Arc>, + layer: Option, ) -> Arc> { let sheet = Arc::new(Stylesheet { contents: StylesheetContents::from_shared_data( @@ -374,8 +375,9 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> { let stylesheet = ImportSheet(sheet.clone()); let import = ImportRule { url, - source_location, stylesheet, + layer, + source_location, }; let url = match import.url.url().cloned() { diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 6353fb4c5e5..db0f7fce99f 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -6,6 +6,7 @@ use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StyleSource}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::shared_lock::Locked; use servo_arc::Arc; use smallvec::SmallVec; @@ -69,8 +70,10 @@ pub struct ApplicableDeclarationBlock { /// The bits containing the source order, cascade level, and shadow cascade /// order. bits: ApplicableDeclarationBits, - /// The specificity of the selector this block is represented by. + /// The specificity of the selector. pub specificity: u32, + /// The layer order of the selector. + pub layer_order: LayerOrder, } impl ApplicableDeclarationBlock { @@ -85,16 +88,24 @@ impl ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), bits: ApplicableDeclarationBits::new(0, level), specificity: 0, + layer_order: LayerOrder::root(), } } - /// Constructs an applicable declaration block from the given components + /// Constructs an applicable declaration block from the given components. #[inline] - pub fn new(source: StyleSource, order: u32, level: CascadeLevel, specificity: u32) -> Self { + pub fn new( + source: StyleSource, + source_order: u32, + level: CascadeLevel, + specificity: u32, + layer_order: LayerOrder, + ) -> Self { ApplicableDeclarationBlock { source, - bits: ApplicableDeclarationBits::new(order, level), + bits: ApplicableDeclarationBits::new(source_order, level), specificity, + layer_order, } } diff --git a/components/style/bloom.rs b/components/style/bloom.rs index 1840c780506..dc722b7bdba 100644 --- a/components/style/bloom.rs +++ b/components/style/bloom.rs @@ -8,6 +8,7 @@ #![deny(missing_docs)] use crate::dom::{SendElement, TElement}; +use crate::LocalName; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use owning_ref::OwningHandle; use selectors::bloom::BloomFilter; @@ -107,9 +108,8 @@ impl PushedElement { /// 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") +pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool { + return *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style") } fn each_relevant_element_hash(element: E, mut f: F) @@ -126,14 +126,11 @@ 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()) - } - }); - } + element.each_attr_name(|name| { + if !is_attr_name_excluded_from_filter(name) { + f(name.get_hash()) + } + }); } impl Drop for StyleBloom { diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index df3f9660dd4..96328d9a51a 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -71,10 +71,29 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), ]; +fn get_titlebar_radius(device: &Device) -> VariableValue { + VariableValue::pixel(device.titlebar_radius()) +} + +fn get_menu_radius(device: &Device) -> VariableValue { + VariableValue::pixel(device.menu_radius()) +} + +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 2] = [ + make_variable!(atom!("-moz-gtk-csd-titlebar-radius"), get_titlebar_radius), + make_variable!(atom!("-moz-gtk-menu-radius"), get_menu_radius), +]; + impl CssEnvironment { #[inline] fn get(&self, name: &Atom, device: &Device) -> Option { - let var = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; + if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) { + return Some((var.evaluator)(device)); + } + if !device.is_chrome_document() { + return None; + } + let var = CHROME_ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; Some((var.evaluator)(device)) } } diff --git a/components/style/dom.rs b/components/style/dom.rs index c85ff1edaff..308006efd11 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -520,10 +520,9 @@ pub trait TElement: } /// Internal iterator for the attribute names of this element. - #[cfg(feature = "gecko")] fn each_attr_name(&self, callback: F) where - F: FnMut(&AtomIdent); + F: FnMut(&LocalName); /// Internal iterator for the part names that this element exports for a /// given part name. diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 7aed10b6568..05fcdb5b12d 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -9,34 +9,24 @@ #![allow(non_snake_case, missing_docs)] use crate::gecko::url::CssUrlData; -use crate::gecko_bindings::structs::RawServoAnimationValue; -use crate::gecko_bindings::structs::RawServoCounterStyleRule; -use crate::gecko_bindings::structs::RawServoCssUrlData; -use crate::gecko_bindings::structs::RawServoDeclarationBlock; -use crate::gecko_bindings::structs::RawServoFontFaceRule; -use crate::gecko_bindings::structs::RawServoFontFeatureValuesRule; -use crate::gecko_bindings::structs::RawServoImportRule; -use crate::gecko_bindings::structs::RawServoKeyframe; -use crate::gecko_bindings::structs::RawServoKeyframesRule; -use crate::gecko_bindings::structs::RawServoMediaList; -use crate::gecko_bindings::structs::RawServoMediaRule; -use crate::gecko_bindings::structs::RawServoMozDocumentRule; -use crate::gecko_bindings::structs::RawServoNamespaceRule; -use crate::gecko_bindings::structs::RawServoPageRule; -use crate::gecko_bindings::structs::RawServoStyleRule; -use crate::gecko_bindings::structs::RawServoStyleSheetContents; -use crate::gecko_bindings::structs::RawServoSupportsRule; -use crate::gecko_bindings::structs::ServoCssRules; +use crate::gecko_bindings::structs::{ + RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, RawServoDeclarationBlock, + RawServoFontFaceRule, RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe, + RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, RawServoMediaRule, + RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule, + RawServoStyleRule, RawServoStyleSheetContents, RawServoSupportsRule, ServoCssRules, +}; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; use crate::properties::animated_properties::AnimationValue; use crate::properties::{ComputedValues, PropertyDeclarationBlock}; use crate::shared_lock::Locked; use crate::stylesheets::keyframes_rule::Keyframe; -use crate::stylesheets::{CounterStyleRule, CssRules, FontFaceRule, FontFeatureValuesRule}; -use crate::stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule}; -use crate::stylesheets::{NamespaceRule, PageRule}; -use crate::stylesheets::{StyleRule, StylesheetContents, SupportsRule}; +use crate::stylesheets::{ + CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule, + KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, ScrollTimelineRule, StyleRule, + StylesheetContents, SupportsRule, +}; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -83,6 +73,9 @@ impl_arc_ffi!(Locked => RawServoKeyframe impl_arc_ffi!(Locked => RawServoKeyframesRule [Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]); +impl_arc_ffi!(Locked => RawServoLayerRule + [Servo_LayerRule_AddRef, Servo_LayerRule_Release]); + impl_arc_ffi!(Locked => RawServoMediaList [Servo_MediaList_AddRef, Servo_MediaList_Release]); @@ -95,6 +88,9 @@ impl_arc_ffi!(Locked => RawServoNamespaceRule impl_arc_ffi!(Locked => RawServoPageRule [Servo_PageRule_AddRef, Servo_PageRule_Release]); +impl_arc_ffi!(Locked => RawServoScrollTimelineRule + [Servo_ScrollTimelineRule_AddRef, Servo_ScrollTimelineRule_Release]); + impl_arc_ffi!(Locked => RawServoSupportsRule [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]); diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 664a12f4108..40a34095d6f 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -406,45 +406,6 @@ fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> 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, - } -} - bitflags! { /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction struct PointerCapabilities: u8 { @@ -607,6 +568,16 @@ fn eval_moz_windows_non_native_menus( query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus) } +fn eval_moz_overlay_scrollbars( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let use_overlay = + unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) }; + query_value.map_or(use_overlay, |v| v == use_overlay) +} + fn get_lnf_int(int_id: i32) -> i32 { unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } } @@ -680,7 +651,7 @@ macro_rules! bool_pref_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; 60] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -890,37 +861,35 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ Evaluator::BoolInteger(eval_moz_non_native_content_theme), ParsingRequirements::CHROME_AND_UA_ONLY, ), - feature!( - atom!("-moz-toolbar-prefers-color-scheme"), - AllowsRanges::No, - keyword_evaluator!(eval_toolbar_prefers_color_scheme, ToolbarPrefersColorScheme), - ParsingRequirements::CHROME_AND_UA_ONLY, - ), feature!( atom!("-moz-windows-non-native-menus"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_windows_non_native_menus), ParsingRequirements::CHROME_AND_UA_ONLY, ), + feature!( + atom!("-moz-overlay-scrollbars"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_overlay_scrollbars), + 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-mac-rtl"), MacRTL), 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), diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 2f5a9673d4c..7bab0b002d1 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -93,7 +93,9 @@ impl Device { document, default_values: ComputedValues::default_values(doc), root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), - body_text_color: AtomicUsize::new(prefs.mColors.mDefault as usize), + // This gets updated when we see the , so it doesn't really + // matter which color-scheme we look at here. + body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize), used_root_font_size: AtomicBool::new(false), used_font_metrics: AtomicBool::new(false), used_viewport_size: AtomicBool::new(false), @@ -386,13 +388,18 @@ impl Device { } /// Returns the default background color. - pub fn default_background_color(&self) -> RGBA { - convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefaultBackground) + /// + /// This is only for forced-colors/high-contrast, so looking at light colors + /// is ok. + pub fn default_background_color_for_forced_colors(&self) -> RGBA { + convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefaultBackground) } /// Returns the default foreground color. - pub fn default_color(&self) -> RGBA { - convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefault) + /// + /// See above for looking at light colors only. + pub fn default_color_for_forced_colors(&self) -> RGBA { + convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefault) } /// Returns the current effective text zoom. @@ -439,4 +446,24 @@ impl Device { bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32) } } + + /// Returns the gtk titlebar radius in CSS pixels. + pub fn titlebar_radius(&self) -> f32 { + unsafe { + bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) as f32 + } + } + + /// Returns the gtk menu radius in CSS pixels. + pub fn menu_radius(&self) -> f32 { + unsafe { + bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) as f32 + } + } + + /// Return whether the document is a chrome document. + #[inline] + pub fn is_chrome_document(&self) -> bool { + self.pref_sheet_prefs().mIsChrome + } } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 5379454daa0..7028d554aa2 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -249,8 +249,7 @@ impl ::selectors::SelectorImpl for SelectorImpl { 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) + !crate::bloom::is_attr_name_excluded_from_filter(name) } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 476e710795d..95da8b986f6 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1016,7 +1016,7 @@ impl<'le> TElement for GeckoElement<'le> { #[inline] fn namespace(&self) -> &WeakNamespace { unsafe { - let namespace_manager = structs::nsContentUtils_sNameSpaceManager; + let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr; WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr) } } diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index edecc3fc243..fc39b8cca76 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -543,6 +543,7 @@ impl StylesheetInvalidationSet { FontFeatureValues(..) | FontFace(..) | Keyframes(..) | + ScrollTimeline(..) | Style(..) => { if is_generic_change { // TODO(emilio): We need to do this for selector / keyframe @@ -555,7 +556,7 @@ impl StylesheetInvalidationSet { self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) }, - Document(..) | Import(..) | Media(..) | Supports(..) => { + Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { if !is_generic_change && !EffectiveRules::is_effective(guard, device, quirks_mode, rule) { @@ -596,7 +597,7 @@ impl StylesheetInvalidationSet { } } }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) => { + Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, @@ -618,6 +619,10 @@ impl StylesheetInvalidationSet { // existing elements. } }, + ScrollTimeline(..) => { + // TODO: Bug 1676784: check if animation-timeline name is referenced. + // Now we do nothing. + }, CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => { debug!( " > Found unsupported rule, marking the whole subtree \ diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index e3f919cc56e..fe5bae33fe8 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -430,7 +430,7 @@ fn tweak_when_ignoring_colors( // widget background color's rgb channels but not alpha... let alpha = alpha_channel(color, context); if alpha != 0 { - let mut color = context.builder.device.default_background_color(); + let mut color = context.builder.device.default_background_color_for_forced_colors(); color.alpha = alpha; declarations_to_apply_unless_overriden .push(PropertyDeclaration::BackgroundColor(color.into())) @@ -448,7 +448,7 @@ fn tweak_when_ignoring_colors( // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { - let color = context.builder.device.default_color(); + let color = context.builder.device.default_color_for_forced_colors(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), )) @@ -794,12 +794,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { { builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); } - if self - .author_specified - .contains_any(LonghandIdSet::padding_properties()) - { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING); - } if self .author_specified @@ -859,13 +853,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // We're using the same reset style as another element, and we'll skip // applying the relevant properties. So we need to do the relevant - // bookkeeping here to keep these two bits correct. + // bookkeeping here to keep these bits correct. // - // Note that all the properties involved are non-inherited, so we don't - // need to do anything else other than just copying the bits over. - let reset_props_bits = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | - ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING; - builder.add_flags(cached_style.flags & reset_props_bits); + // Note that the border/background properties are non-inherited, so we + // don't need to do anything else other than just copying the bits over. + // + // When using this optimization, we also need to copy whether the old + // style specified viewport units / used font-relative lengths, this one + // would as well. It matches the same rules, so it is the right thing + // to do anyways, even if it's only used on inherited properties. + let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | + ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS | + ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS | + ComputedValueFlags::USES_VIEWPORT_UNITS; + builder.add_flags(cached_style.flags & bits_to_copy); true } @@ -916,12 +917,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // we have a generic family to actually replace it with. let prioritize_user_fonts = !use_document_fonts && default_font_type != GenericFontFamily::None && - matches!( - generic, - GenericFontFamily::None | - GenericFontFamily::Fantasy | - GenericFontFamily::Cursive - ); + !generic.valid_for_user_font_prioritization(); if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback { // Nothing to do. diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs index 0942bbd0d13..25adc3327ca 100644 --- a/components/style/properties/computed_value_flags.rs +++ b/components/style/properties/computed_value_flags.rs @@ -84,12 +84,6 @@ bitflags! { /// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845 const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14; - /// Whether there are author-specified rules for padding-* properties. - /// - /// FIXME(emilio): Try to merge this with BORDER_BACKGROUND, see - /// https://github.com/w3c/csswg-drafts/issues/4777 - const HAS_AUTHOR_SPECIFIED_PADDING = 1 << 15; - /// Whether there are author-specified rules for `font-family`. const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16; diff --git a/components/style/properties/data.py b/components/style/properties/data.py index b2ab520ad3d..49157860fbb 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -476,6 +476,7 @@ class Longhand(Property): "FontWeight", "GreaterThanOrEqualToOneNumber", "GridAutoFlow", + "ImageRendering", "InitialLetter", "Integer", "JustifyContent", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 86cdeefa982..bc0a78183a8 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -1049,7 +1049,7 @@ fn static_assert() { % if member: ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member}; % else: - ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}; + ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone(); % endif } } @@ -1183,7 +1183,7 @@ fn static_assert() { <% skip_box_longhands= """display animation-name animation-delay animation-duration animation-direction animation-fill-mode animation-play-state - animation-iteration-count animation-timing-function + animation-iteration-count animation-timeline animation-timing-function clear transition-duration transition-delay transition-timing-function transition-property -webkit-line-clamp""" %> @@ -1445,6 +1445,27 @@ fn static_assert() { ${impl_copy_animation_value('iteration_count', 'IterationCount')} ${impl_animation_or_transition_timing_function('animation')} + pub fn set_animation_timeline(&mut self, v: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + { + let v = v.into_iter(); + debug_assert_ne!(v.len(), 0); + let input_len = v.len(); + self.gecko.mAnimations.ensure_len(input_len); + + self.gecko.mAnimationTimelineCount = input_len as u32; + for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) { + gecko.mTimeline = servo; + } + } + pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline { + self.gecko.mAnimations[index].mTimeline.clone() + } + ${impl_animation_count('timeline', 'Timeline')} + ${impl_copy_animation_value('timeline', 'Timeline')} + #[allow(non_snake_case)] pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) { self.gecko.mLineClamp = match v { diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index c8942fdc6ff..62c3da725f5 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -320,6 +320,22 @@ ${helpers.predefined_type( rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, )} +${helpers.predefined_type( + "animation-timeline", + "AnimationTimeline", + "computed::AnimationTimeline::auto()", + engines="gecko servo-2013 servo-2020", + servo_2013_pref="layout.2013.unimplemented", + servo_2020_pref="layout.2020.unimplemented", + initial_specified_value="specified::AnimationTimeline::auto()", + vector=True, + need_index=True, + animation_value_type="none", + gecko_pref="layout.css.scroll-linked-animations.enabled", + spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + <% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %> ${helpers.predefined_type( diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs index cea3fde021a..294f36bd96f 100644 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ b/components/style/properties/longhands/inherited_box.mako.rs @@ -69,17 +69,13 @@ ${helpers.single_keyword( // According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` // And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337) -${helpers.single_keyword( +${helpers.predefined_type( "image-rendering", - "auto crisp-edges", + "ImageRendering", + "computed::ImageRendering::Auto", engines="gecko servo-2013 servo-2020", - extra_gecko_values="optimizespeed optimizequality", - extra_servo_2013_values="pixelated", - extra_servo_2020_values="pixelated", - gecko_aliases="-moz-crisp-edges=crisp-edges", - gecko_enum_prefix="StyleImageRendering", - animation_value_type="discrete", spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", + animation_value_type="discrete", )} ${helpers.single_keyword( diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index badcc3f2012..44dfdd74a2a 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -104,6 +104,8 @@ ${helpers.predefined_type( gecko_pref="layout.css.color-scheme.enabled", animation_value_type="discrete", has_effect_on_gecko_scrollbars=False, + ignored_when_colors_disabled=True, + enabled_in="chrome", )} ${helpers.predefined_type( diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 67183062e2f..4ee388dfe6c 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -945,18 +945,6 @@ impl LonghandIdSet { &HAS_NO_EFFECT_ON_SCROLLBARS } - /// Returns the set of padding properties for the purpose of disabling - /// native appearance. - #[inline] - pub fn padding_properties() -> &'static Self { - <% assert "padding" in logical_groups %> - ${static_longhand_id_set( - "PADDING_PROPERTIES", - lambda p: p.logical_group == "padding" - )} - &PADDING_PROPERTIES - } - /// Returns the set of border properties for the purpose of disabling native /// appearance. #[inline] @@ -2921,7 +2909,7 @@ pub mod style_structs { } /// Returns true if animation properties are equal between styles, but without - /// considering keyframe data. + /// considering keyframe data and animation-timeline. #[cfg(feature = "servo")] pub fn animations_equals(&self, other: &Self) -> bool { self.animation_name_iter().eq(other.animation_name_iter()) && @@ -3098,27 +3086,76 @@ impl ComputedValues { /// /// Note that the value will usually be the computed value, except for /// colors, where it's resolved. - pub fn get_longhand_property_value( + /// + /// TODO(emilio): We should move all the special resolution from + /// nsComputedDOMStyle to ToResolvedValue instead. + pub fn get_resolved_value( &self, property_id: LonghandId, - dest: &mut CssWriter - ) -> fmt::Result - where - W: Write, - { + dest: &mut CssStringWriter, + ) -> fmt::Result { use crate::values::resolved::ToResolvedValue; + let mut dest = CssWriter::new(dest); + let context = resolved::Context { + style: self, + }; + match property_id { + % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): + <% props = list(props) %> + ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { + let value = match property_id { + % for prop in props: + LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), + % endfor + _ => unsafe { debug_unreachable!() }, + }; + value.to_resolved_value(&context).to_css(&mut dest) + } + % endfor + } + } + + /// Returns the given longhand's resolved value as a property declaration. + pub fn resolved_declaration(&self, property_id: LonghandId) -> PropertyDeclaration { + use crate::values::resolved::ToResolvedValue; + use crate::values::computed::ToComputedValue; + let context = resolved::Context { style: self, }; - // TODO(emilio): Is it worth to merge branches here just like - // PropertyDeclaration::to_css does? match property_id { - % for prop in data.longhands: - LonghandId::${prop.camel_case} => { - let value = self.clone_${prop.ident}(); - value.to_resolved_value(&context).to_css(dest) + % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()): + <% props = list(props) %> + ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => { + let value = match property_id { + % for prop in props: + LonghandId::${prop.camel_case} => self.clone_${prop.ident}(), + % endfor + _ => unsafe { debug_unreachable!() }, + }; + let resolved = value.to_resolved_value(&context); + let computed = ToResolvedValue::from_resolved_value(resolved); + let specified = ToComputedValue::from_computed_value(&computed); + % if props[0].boxed: + let specified = Box::new(specified); + % endif + % if len(props) == 1: + PropertyDeclaration::${props[0].camel_case}(specified) + % else: + unsafe { + let mut out = mem::MaybeUninit::uninit(); + ptr::write( + out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified_type}>, + PropertyDeclarationVariantRepr { + tag: property_id as u16, + value: specified, + }, + ); + out.assume_init() + } + % endif } % endfor } @@ -3195,9 +3232,9 @@ impl ComputedValues { match property { PropertyDeclarationId::Longhand(id) => { let mut s = String::new(); - self.get_longhand_property_value( + self.get_resolved_value( id, - &mut CssWriter::new(&mut s) + &mut s ).unwrap(); s } diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index 4beef9a0d95..5cd52902ec9 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -189,11 +189,11 @@ macro_rules! try_parse_one { sub_properties="animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction - animation-fill-mode animation-play-state" + animation-fill-mode animation-play-state animation-timeline" rule_types_allowed="Style" spec="https://drafts.csswg.org/css-animations/#propdef-animation"> <% - props = "name duration timing_function delay iteration_count \ + props = "name timeline duration timing_function delay iteration_count \ direction fill_mode play_state".split() %> % for prop in props: @@ -210,6 +210,13 @@ macro_rules! try_parse_one { % endfor } + fn scroll_linked_animations_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.scroll-linked-animations.enabled"); + #[cfg(feature = "servo")] + return false; + } + fn parse_one_animation<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, @@ -234,6 +241,9 @@ macro_rules! try_parse_one { try_parse_one!(context, input, fill_mode, animation_fill_mode); try_parse_one!(context, input, play_state, animation_play_state); try_parse_one!(context, input, name, animation_name); + if scroll_linked_animations_enabled() { + try_parse_one!(context, input, timeline, animation_timeline); + } parsed -= 1; break @@ -280,22 +290,46 @@ macro_rules! try_parse_one { // If any value list length is differs then we don't do a shorthand serialization // either. - % for name in props[1:]: + % for name in props[2:]: if len != self.animation_${name}.0.len() { return Ok(()) } % endfor + // If the preference of animation-timeline is disabled, `self.animation_timeline` is + // None. + if self.animation_timeline.map_or(false, |v| len != v.0.len()) { + return Ok(()); + } + for i in 0..len { if i != 0 { dest.write_str(", ")?; } - % for name in props[1:]: + % for name in props[2:]: self.animation_${name}.0[i].to_css(dest)?; dest.write_str(" ")?; % endfor + self.animation_name.0[i].to_css(dest)?; + + // Based on the spec, the default values of other properties must be output in at + // least the cases necessary to distinguish an animation-name. The serialization + // order of animation-timeline is always later than animation-name, so it's fine + // to not serialize it if it is the default value. It's still possible to + // distinguish them (because we always serialize animation-name). + // https://drafts.csswg.org/css-animations-1/#animation + // https://drafts.csswg.org/css-animations-2/#typedef-single-animation + // + // Note: it's also fine to always serialize this. However, it seems Blink + // doesn't serialize default animation-timeline now, so we follow the same rule. + if let Some(ref timeline) = self.animation_timeline { + if !timeline.0[i].is_auto() { + dest.write_char(' ')?; + timeline.0[i].to_css(dest)?; + } + } } Ok(()) } diff --git a/components/style/properties/shorthands/list.mako.rs b/components/style/properties/shorthands/list.mako.rs index f757c368cfa..0ba42d3fca4 100644 --- a/components/style/properties/shorthands/list.mako.rs +++ b/components/style/properties/shorthands/list.mako.rs @@ -7,7 +7,6 @@ <%helpers:shorthand name="list-style" engines="gecko servo-2013 servo-2020" sub_properties="list-style-position list-style-image list-style-type" - derive_serialize="True" spec="https://drafts.csswg.org/css-lists/#propdef-list-style"> use crate::properties::longhands::{list_style_image, list_style_position, list_style_type}; use crate::values::specified::Image; @@ -104,4 +103,43 @@ _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { + use longhands::list_style_position::SpecifiedValue as ListStylePosition; + use longhands::list_style_type::SpecifiedValue as ListStyleType; + use longhands::list_style_image::SpecifiedValue as ListStyleImage; + let mut have_one_non_initial_value = false; + #[cfg(any(feature = "gecko", feature = "servo-layout-2013"))] + let position_is_initial = self.list_style_position == &ListStylePosition::Outside; + #[cfg(feature = "servo-layout-2020")] + let position_is_initial = self.list_style_position == Some(&ListStylePosition::Outside); + if !position_is_initial { + self.list_style_position.to_css(dest)?; + have_one_non_initial_value = true; + } + if self.list_style_image != &ListStyleImage::None { + if have_one_non_initial_value { + dest.write_str(" ")?; + } + self.list_style_image.to_css(dest)?; + have_one_non_initial_value = true; + } + #[cfg(feature = "gecko")] + let type_is_initial = self.list_style_type == &ListStyleType::disc(); + #[cfg(feature = "servo")] + let type_is_initial = self.list_style_type == &ListStyleType::Disc; + if !type_is_initial { + if have_one_non_initial_value { + dest.write_str(" ")?; + } + self.list_style_type.to_css(dest)?; + have_one_non_initial_value = true; + } + if !have_one_non_initial_value { + self.list_style_position.to_css(dest)?; + } + Ok(()) + } + } diff --git a/components/style/properties/shorthands/text.mako.rs b/components/style/properties/shorthands/text.mako.rs index 03637d557e5..eb0c5dc4003 100644 --- a/components/style/properties/shorthands/text.mako.rs +++ b/components/style/properties/shorthands/text.mako.rs @@ -89,6 +89,14 @@ } % if engine == "gecko": + if !is_auto_thickness { + if has_value { + dest.write_str(" ")?; + } + self.text_decoration_thickness.to_css(dest)?; + has_value = true; + } + if !is_solid_style { if has_value { dest.write_str(" ")?; @@ -104,13 +112,6 @@ self.text_decoration_color.to_css(dest)?; has_value = true; } - - if !is_auto_thickness { - if has_value { - dest.write_str(" ")?; - } - self.text_decoration_thickness.to_css(dest)?; - } % endif Ok(()) diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 86293e5c785..65f55ea887a 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -12,7 +12,7 @@ use crate::selector_map::SelectorMap; use crate::selector_parser::PseudoElement; use crate::shared_lock::Locked; use crate::stylesheets::Origin; -use crate::stylist::{AuthorStylesEnabled, Rule, RuleInclusion, Stylist}; +use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; use smallvec::SmallVec; @@ -148,7 +148,7 @@ where f(self); if start != self.rules.len() { self.rules[start..] - .sort_unstable_by_key(|block| (block.specificity, block.source_order())); + .sort_unstable_by_key(|block| (block.layer_order, block.specificity, block.source_order())); } self.context.current_host = old_host; self.in_sort_scope = false; @@ -173,7 +173,7 @@ where }; self.in_tree(None, |collector| { - collector.collect_rules_in_map(map, cascade_level); + collector.collect_rules_in_map(map, cascade_level, cascade_data); }); } @@ -214,7 +214,7 @@ where } #[inline] - fn collect_rules_in_list(&mut self, part_rules: &[Rule], cascade_level: CascadeLevel) { + fn collect_rules_in_list(&mut self, part_rules: &[Rule], cascade_level: CascadeLevel, cascade_data: &CascadeData) { debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); SelectorMap::get_matching_rules( self.element, @@ -223,11 +223,12 @@ where &mut self.context, &mut self.flags_setter, cascade_level, + cascade_data, ); } #[inline] - fn collect_rules_in_map(&mut self, map: &SelectorMap, cascade_level: CascadeLevel) { + fn collect_rules_in_map(&mut self, map: &SelectorMap, cascade_level: CascadeLevel, cascade_data: &CascadeData) { debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); map.get_all_matching_rules( self.element, @@ -236,6 +237,7 @@ where &mut self.context, &mut self.flags_setter, cascade_level, + cascade_data, ); } @@ -277,7 +279,7 @@ where let cascade_level = CascadeLevel::AuthorNormal { shadow_cascade_order, }; - collector.collect_rules_in_map(slotted_rules, cascade_level); + collector.collect_rules_in_map(slotted_rules, cascade_level, data); }); } } @@ -303,7 +305,7 @@ where let cascade_level = CascadeLevel::same_tree_author_normal(); self.in_shadow_tree(containing_shadow.host(), |collector| { if let Some(map) = cascade_data.normal_rules(collector.pseudo_element) { - collector.collect_rules_in_map(map, cascade_level); + collector.collect_rules_in_map(map, cascade_level, cascade_data); } // Collect rules from :host::part() and such @@ -319,7 +321,7 @@ where hash_target.each_part(|part| { if let Some(part_rules) = part_rules.get(&part.0) { - collector.collect_rules_in_list(part_rules, cascade_level); + collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); } }); }); @@ -352,7 +354,7 @@ where let cascade_level = CascadeLevel::AuthorNormal { shadow_cascade_order, }; - collector.collect_rules_in_map(host_rules, cascade_level); + collector.collect_rules_in_map(host_rules, cascade_level, style_data); }); } @@ -386,30 +388,30 @@ where let inner_shadow_host = inner_shadow.host(); let outer_shadow = inner_shadow_host.containing_shadow(); - let part_rules = match outer_shadow { - Some(shadow) => shadow - .style_data() - .and_then(|data| data.part_rules(self.pseudo_element)), - None => self + let cascade_data = match outer_shadow { + Some(shadow) => shadow.style_data(), + None => Some(self .stylist .cascade_data() .borrow_for_origin(Origin::Author) - .part_rules(self.pseudo_element), + ), }; - if let Some(part_rules) = part_rules { - let containing_host = outer_shadow.map(|s| s.host()); - let cascade_level = CascadeLevel::AuthorNormal { - shadow_cascade_order, - }; - self.in_tree(containing_host, |collector| { - for p in &parts { - if let Some(part_rules) = part_rules.get(&p.0) { - collector.collect_rules_in_list(part_rules, cascade_level); + if let Some(cascade_data) = cascade_data { + if let Some(part_rules) = cascade_data.part_rules(self.pseudo_element) { + let containing_host = outer_shadow.map(|s| s.host()); + let cascade_level = CascadeLevel::AuthorNormal { + shadow_cascade_order, + }; + self.in_tree(containing_host, |collector| { + for p in &parts { + if let Some(part_rules) = part_rules.get(&p.0) { + collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); + } } - } - }); - shadow_cascade_order.inc(); + }); + shadow_cascade_order.inc(); + } } inner_shadow = match outer_shadow { diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 40806ed47af..a2b5d6bb6b6 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -12,7 +12,7 @@ use crate::hash::map as hash_map; use crate::hash::{HashMap, HashSet}; use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; -use crate::stylist::Rule; +use crate::stylist::{Rule, CascadeData}; use crate::{Atom, LocalName, Namespace, WeakAtom}; use fallible::FallibleVec; use hashglobe::FailedAllocationError; @@ -104,10 +104,14 @@ pub struct SelectorMap { pub class_hash: MaybeCaseInsensitiveHashMap>, /// A hash from local name to rules which contain that local name selector. pub local_name_hash: PrecomputedHashMap>, + /// A hash from attributes to rules which contain that attribute selector. + pub attribute_hash: PrecomputedHashMap>, /// A hash from namespace to rules which contain that namespace selector. pub namespace_hash: PrecomputedHashMap>, /// All other rules. pub other: SmallVec<[T; 1]>, + /// Whether we should bucket by attribute names. + bucket_attributes: bool, /// The number of entries in this map. pub count: usize, } @@ -129,18 +133,32 @@ impl SelectorMap { root: SmallVec::new(), id_hash: MaybeCaseInsensitiveHashMap::new(), class_hash: MaybeCaseInsensitiveHashMap::new(), + attribute_hash: HashMap::default(), local_name_hash: HashMap::default(), namespace_hash: HashMap::default(), other: SmallVec::new(), + #[cfg(feature = "gecko")] + bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"), + #[cfg(feature = "servo")] + bucket_attributes: false, count: 0, } } + /// Trivially constructs an empty `SelectorMap`, with attribute bucketing + /// explicitly disabled. + pub fn new_without_attribute_bucketing() -> Self { + let mut ret = Self::new(); + ret.bucket_attributes = false; + ret + } + /// Clears the hashmap retaining storage. pub fn clear(&mut self) { self.root.clear(); self.id_hash.clear(); self.class_hash.clear(); + self.attribute_hash.clear(); self.local_name_hash.clear(); self.namespace_hash.clear(); self.other.clear(); @@ -171,6 +189,7 @@ impl SelectorMap { context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel, + cascade_data: &CascadeData, ) where E: TElement, F: FnMut(&E, ElementSelectorFlags), @@ -189,6 +208,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ); } @@ -201,6 +221,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } } @@ -214,10 +235,27 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } }); + if self.bucket_attributes { + rule_hash_target.each_attr_name(|name| { + if let Some(rules) = self.attribute_hash.get(name) { + SelectorMap::get_matching_rules( + element, + rules, + matching_rules_list, + context, + flags_setter, + cascade_level, + cascade_data, + ) + } + }); + } + if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) { SelectorMap::get_matching_rules( element, @@ -226,6 +264,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } @@ -237,6 +276,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } @@ -247,6 +287,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ); } @@ -258,6 +299,7 @@ impl SelectorMap { context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel, + cascade_data: &CascadeData, ) where E: TElement, F: FnMut(&E, ElementSelectorFlags), @@ -271,7 +313,7 @@ impl SelectorMap { context, flags_setter, ) { - matching_rules.push(rule.to_applicable_declaration_block(cascade_level)); + matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } } @@ -302,6 +344,7 @@ impl SelectorMap { .class_hash .try_entry(class.clone(), quirks_mode)? .or_insert_with(SmallVec::new), + Bucket::Attribute { name, lower_name } | Bucket::LocalName { name, lower_name } => { // If the local name in the selector isn't lowercase, // insert it into the rule hash twice. This means that, @@ -316,13 +359,19 @@ impl SelectorMap { // selector, the rulehash lookup may produce superfluous // selectors, but the subsequent selector matching work // will filter them out. + let is_attribute = matches!($bucket, Bucket::Attribute { .. }); + let hash = if is_attribute { + &mut self.attribute_hash + } else { + &mut self.local_name_hash + }; if name != lower_name { - self.local_name_hash + hash .try_entry(lower_name.clone())? .or_insert_with(SmallVec::new) .try_push($entry.clone())?; } - self.local_name_hash + hash .try_entry(name.clone())? .or_insert_with(SmallVec::new) }, @@ -338,7 +387,7 @@ impl SelectorMap { let bucket = { let mut disjoint_buckets = SmallVec::new(); - let bucket = find_bucket(entry.selector(), &mut disjoint_buckets); + let bucket = find_bucket(entry.selector(), &mut disjoint_buckets, self.bucket_attributes); // See if inserting this selector in multiple entries in the // selector map would be worth it. Consider a case like: @@ -409,8 +458,29 @@ impl SelectorMap { let mut done = false; element.each_class(|class| { - if !done { - if let Some(v) = self.class_hash.get(class, quirks_mode) { + if done { + return; + } + if let Some(v) = self.class_hash.get(class, quirks_mode) { + for entry in v.iter() { + if !f(&entry) { + done = true; + return; + } + } + } + }); + + if done { + return false; + } + + if self.bucket_attributes { + element.each_attr_name(|name| { + if done { + return; + } + if let Some(v) = self.attribute_hash.get(name) { for entry in v.iter() { if !f(&entry) { done = true; @@ -418,10 +488,11 @@ impl SelectorMap { } } } + }); + + if done { + return false; } - }); - if done { - return false; } if let Some(v) = self.local_name_hash.get(element.local_name()) { @@ -507,6 +578,10 @@ enum Bucket<'a> { name: &'a LocalName, lower_name: &'a LocalName, }, + Attribute { + name: &'a LocalName, + lower_name: &'a LocalName, + }, Class(&'a Atom), ID(&'a Atom), Root, @@ -520,9 +595,10 @@ impl<'a> Bucket<'a> { Bucket::Universal => 0, Bucket::Namespace(..) => 1, Bucket::LocalName { .. } => 2, - Bucket::Class(..) => 3, - Bucket::ID(..) => 4, - Bucket::Root => 5, + Bucket::Attribute { .. } => 3, + Bucket::Class(..) => 4, + Bucket::ID(..) => 5, + Bucket::Root => 6, } } @@ -537,11 +613,24 @@ type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>; fn specific_bucket_for<'a>( component: &'a Component, disjoint_buckets: &mut DisjointBuckets<'a>, + bucket_attributes: bool, ) -> Bucket<'a> { match *component { Component::Root => Bucket::Root, Component::ID(ref id) => Bucket::ID(id), Component::Class(ref class) => Bucket::Class(class), + Component::AttributeInNoNamespace { ref local_name, .. } if bucket_attributes => Bucket::Attribute { + name: local_name, + lower_name: local_name, + }, + Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } if bucket_attributes => Bucket::Attribute { + name: local_name, + lower_name: local_name_lower, + }, + Component::AttributeOther(ref selector) if bucket_attributes => Bucket::Attribute { + name: &selector.local_name, + lower_name: &selector.local_name_lower, + }, Component::LocalName(ref selector) => Bucket::LocalName { name: &selector.name, lower_name: &selector.lower_name, @@ -567,14 +656,14 @@ fn specific_bucket_for<'a>( // // So inserting `span` in the rule hash makes sense since we want to // match the slotted . - Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets), - Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets), + Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), + Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), Component::Is(ref list) | Component::Where(ref list) => { if list.len() == 1 { - find_bucket(list[0].iter(), disjoint_buckets) + find_bucket(list[0].iter(), disjoint_buckets, bucket_attributes) } else { for selector in &**list { - let bucket = find_bucket(selector.iter(), disjoint_buckets); + let bucket = find_bucket(selector.iter(), disjoint_buckets, bucket_attributes); disjoint_buckets.push(bucket); } Bucket::Universal @@ -593,12 +682,13 @@ fn specific_bucket_for<'a>( fn find_bucket<'a>( mut iter: SelectorIter<'a, SelectorImpl>, disjoint_buckets: &mut DisjointBuckets<'a>, + bucket_attributes: bool, ) -> Bucket<'a> { let mut current_bucket = Bucket::Universal; loop { for ss in &mut iter { - let new_bucket = specific_bucket_for(ss, disjoint_buckets); + let new_bucket = specific_bucket_for(ss, disjoint_buckets, bucket_attributes); if new_bucket.more_specific_than(¤t_bucket) { current_bucket = new_bucket; } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index cee76053ec0..d575b7c2df7 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -190,12 +190,12 @@ impl Device { } /// Returns the default background color. - pub fn default_background_color(&self) -> RGBA { + pub fn default_background_color_for_forced_colors(&self) -> RGBA { RGBA::new(255, 255, 255, 255) } - /// Returns the default color color. - pub fn default_color(&self) -> RGBA { + /// Returns the default foreground color. + pub fn default_color_for_forced_colors(&self) -> RGBA { RGBA::new(0, 0, 0, 255) } @@ -220,6 +220,24 @@ impl Device { _ => false, } } + + /// Returns the gtk titlebar radius in CSS pixels. + /// TODO: implement this method. + pub fn titlebar_radius(&self) -> f32 { + 0.0 + } + + /// Returns the gtk menu radius in CSS pixels. + /// TODO: implement this method. + pub fn menu_radius(&self) -> f32 { + 0.0 + } + + /// Return whether the document is a chrome document. + #[inline] + pub fn is_chrome_document(&self) -> bool { + false + } } /// https://drafts.csswg.org/mediaqueries-4/#width diff --git a/components/style/shared_lock.rs b/components/style/shared_lock.rs index 5bfa7d19737..d524f0c6cdf 100644 --- a/components/style/shared_lock.rs +++ b/components/style/shared_lock.rs @@ -94,6 +94,12 @@ impl SharedRwLock { SharedRwLock { cell: None } } + #[cfg(feature = "gecko")] + #[inline] + fn ptr(&self) -> *const SomethingZeroSizedButTyped { + self.cell.as_ref().map(|cell| cell.as_ptr() as *const _).unwrap_or(ptr::null()) + } + /// Wrap the given data to make its access protected by this lock. pub fn wrap(&self, data: T) -> Locked { Locked { @@ -144,6 +150,14 @@ impl<'a> Drop for SharedRwLockReadGuard<'a> { } } +impl<'a> SharedRwLockReadGuard<'a> { + #[inline] + #[cfg(feature = "gecko")] + fn ptr(&self) -> *const SomethingZeroSizedButTyped { + self.0.as_ref().map(|r| &**r as *const _).unwrap_or(ptr::null()) + } +} + /// Proof that a shared lock was obtained for writing (servo). #[cfg(feature = "servo")] pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock); @@ -190,25 +204,18 @@ impl Locked { } #[cfg(feature = "gecko")] - fn same_lock_as(&self, derefed_guard: Option<&SomethingZeroSizedButTyped>) -> bool { - ptr::eq( - self.shared_lock - .cell - .as_ref() - .map(|cell| cell.as_ptr()) - .unwrap_or(ptr::null_mut()), - derefed_guard - .map(|guard| guard as *const _ as *mut _) - .unwrap_or(ptr::null_mut()), - ) + fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool { + ptr::eq(self.shared_lock.ptr(), ptr) } /// Access the data for reading. pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T { #[cfg(feature = "gecko")] assert!( - self.is_read_only_lock() || self.same_lock_as(guard.0.as_ref().map(|r| &**r)), - "Locked::read_with called with a guard from an unrelated SharedRwLock" + self.is_read_only_lock() || self.same_lock_as(guard.ptr()), + "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}", + self.shared_lock.ptr(), + guard.ptr(), ); #[cfg(not(feature = "gecko"))] assert!(self.same_lock_as(&guard.0)); @@ -235,7 +242,7 @@ impl Locked { pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T { #[cfg(feature = "gecko")] assert!( - !self.is_read_only_lock() && self.same_lock_as(Some(&guard.0)), + !self.is_read_only_lock() && self.same_lock_as(&*guard.0), "Locked::write_with called with a guard from a read only or unrelated SharedRwLock" ); #[cfg(not(feature = "gecko"))] diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 49b34fbbbff..50eb51fba35 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -786,10 +786,7 @@ impl StyleSharingCache { } // It's possible that there are no styles for either id. - let may_match_different_id_rules = - checks::may_match_different_id_rules(shared, target.element, candidate.element); - - if may_match_different_id_rules { + if checks::may_match_different_id_rules(shared, target.element, candidate.element) { trace!("Miss: ID Attr"); return None; } diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 5e66e91e894..31028f98571 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -152,63 +152,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } } - /// https://html.spec.whatwg.org/multipage/#inert-subtrees - /// - /// If -moz-inert is applied then add: - /// -moz-user-focus: none; - /// -moz-user-input: none; - /// -moz-user-modify: read-only; - /// user-select: none; - /// pointer-events: none; - /// cursor: default; - /// - /// NOTE: dialog:-moz-topmost-modal-dialog is used to override above - /// rules to remove the inertness for the topmost modal dialog. - /// - /// NOTE: If this or the pointer-events tweak is removed, then - /// minimal-xul.css and the scrollbar style caching need to be tweaked. - #[cfg(feature = "gecko")] - fn adjust_for_inert(&mut self) { - use crate::values::specified::ui::CursorKind; - use crate::values::specified::ui::UserSelect; - use properties::longhands::_moz_inert::computed_value::T as Inert; - use properties::longhands::_moz_user_focus::computed_value::T as UserFocus; - use properties::longhands::_moz_user_input::computed_value::T as UserInput; - use properties::longhands::_moz_user_modify::computed_value::T as UserModify; - use properties::longhands::cursor::computed_value::T as Cursor; - use properties::longhands::pointer_events::computed_value::T as PointerEvents; - - let needs_update = { - let ui = self.style.get_inherited_ui(); - if ui.clone__moz_inert() == Inert::None { - return; - } - - ui.clone__moz_user_focus() != UserFocus::None || - ui.clone__moz_user_input() != UserInput::None || - ui.clone__moz_user_modify() != UserModify::ReadOnly || - ui.clone_pointer_events() != PointerEvents::None || - ui.clone_cursor().keyword != CursorKind::Default || - ui.clone_cursor().images != Default::default() - }; - - if needs_update { - let ui = self.style.mutate_inherited_ui(); - ui.set__moz_user_focus(UserFocus::None); - ui.set__moz_user_input(UserInput::None); - ui.set__moz_user_modify(UserModify::ReadOnly); - ui.set_pointer_events(PointerEvents::None); - ui.set_cursor(Cursor { - images: Default::default(), - keyword: CursorKind::Default, - }); - } - - if self.style.get_ui().clone_user_select() != UserSelect::None { - self.style.mutate_ui().set_user_select(UserSelect::None); - } - } - /// Whether we should skip any item-based display property blockification on /// this element. fn skip_item_display_fixup(&self, element: Option) -> bool @@ -909,7 +852,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { #[cfg(feature = "gecko")] { self.adjust_for_appearance(element); - self.adjust_for_inert(); self.adjust_for_marker_pseudo(); } self.set_bits(); diff --git a/components/style/stylesheets/font_feature_values_rule.rs b/components/style/stylesheets/font_feature_values_rule.rs index dc9e9f333db..dc128e77fb0 100644 --- a/components/style/stylesheets/font_feature_values_rule.rs +++ b/components/style/stylesheets/font_feature_values_rule.rs @@ -396,13 +396,14 @@ macro_rules! font_feature_values_blocks { type AtRule = (); type Error = StyleParseErrorKind<'i>; - fn parse_prelude<'t>(&mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>) - -> Result> { + fn parse_prelude<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result> { match_ignore_ascii_case! { &*name, $( - $name => Ok(Self::Prelude::$ident_camel), + $name => Ok(BlockType::$ident_camel), )* _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), } diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index 8f518f29bac..7352dea0b9e 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -11,21 +11,13 @@ use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; use crate::stylesheets::{CssRule, StylesheetInDocument}; +use crate::stylesheets::layer_rule::LayerName; use crate::values::CssUrl; use cssparser::SourceLocation; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; use to_shmem::{self, SharedMemoryBuilder, ToShmem}; -/// With asynchronous stylesheet parsing, we can't synchronously create a -/// GeckoStyleSheet. So we use this placeholder instead. -#[cfg(feature = "gecko")] -#[derive(Clone, Debug)] -pub struct PendingSheet { - origin: Origin, - quirks_mode: QuirksMode, -} - /// A sheet that is held from an import rule. #[cfg(feature = "gecko")] #[derive(Debug)] @@ -34,7 +26,7 @@ pub enum ImportSheet { Sheet(crate::gecko::data::GeckoStyleSheet), /// An @import created while parsing off-main-thread, whose Gecko sheet has /// yet to be created and attached. - Pending(PendingSheet), + Pending, } #[cfg(feature = "gecko")] @@ -45,11 +37,8 @@ impl ImportSheet { } /// Creates a pending ImportSheet for a load that has not started yet. - pub fn new_pending(origin: Origin, quirks_mode: QuirksMode) -> Self { - ImportSheet::Pending(PendingSheet { - origin, - quirks_mode, - }) + pub fn new_pending() -> Self { + ImportSheet::Pending } /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it @@ -63,7 +52,7 @@ impl ImportSheet { } Some(s) }, - ImportSheet::Pending(_) => None, + ImportSheet::Pending => None, } } @@ -98,7 +87,7 @@ impl DeepCloneWithLock for ImportSheet { }; ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) }) }, - ImportSheet::Pending(ref p) => ImportSheet::Pending(p.clone()), + ImportSheet::Pending => ImportSheet::Pending, } } } @@ -135,6 +124,30 @@ impl DeepCloneWithLock for ImportSheet { } } +/// The layer keyword or function in an import rule. +#[derive(Debug, Clone)] +pub struct ImportLayer { + /// The layer name, or None for an anonymous layer. + pub name: Option, +} + + +impl ToCss for ImportLayer { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + match self.name { + None => dest.write_str("layer"), + Some(ref name) => { + dest.write_str("layer(")?; + name.to_css(dest)?; + dest.write_char(')') + }, + } + } +} + /// The [`@import`][import] at-rule. /// /// [import]: https://drafts.csswg.org/css-cascade-3/#at-import @@ -148,6 +161,9 @@ pub struct ImportRule { /// ImportSheet just has stub behavior until it appears. pub stylesheet: ImportSheet, + /// A `layer()` function name. + pub layer: Option, + /// The line and column of the rule's source code. pub source_location: SourceLocation, } @@ -170,6 +186,7 @@ impl DeepCloneWithLock for ImportRule { ImportRule { url: self.url.clone(), stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params), + layer: self.layer.clone(), source_location: self.source_location.clone(), } } @@ -180,14 +197,18 @@ impl ToCssWithGuard for ImportRule { dest.write_str("@import ")?; self.url.to_css(&mut CssWriter::new(dest))?; - match self.stylesheet.media(guard) { - Some(media) if !media.is_empty() => { - dest.write_str(" ")?; + if let Some(media) = self.stylesheet.media(guard) { + if !media.is_empty() { + dest.write_char(' ')?; media.to_css(&mut CssWriter::new(dest))?; - }, - _ => {}, - }; + } + } - dest.write_str(";") + if let Some(ref layer) = self.layer { + dest.write_char(' ')?; + layer.to_css(&mut CssWriter::new(dest))?; + } + + dest.write_char(';') } } diff --git a/components/style/stylesheets/keyframes_rule.rs b/components/style/stylesheets/keyframes_rule.rs index 374f6f1d8ec..8b721b7a52c 100644 --- a/components/style/stylesheets/keyframes_rule.rs +++ b/components/style/stylesheets/keyframes_rule.rs @@ -14,6 +14,7 @@ use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::str::CssStringWriter; +use crate::stylesheets::layer_rule::LayerId; use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::values::{serialize_percentage, KeyframesName}; @@ -357,6 +358,8 @@ pub struct KeyframesAnimation { pub properties_changed: LonghandIdSet, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, + /// The id of the cascade layer the keyframe rule was in. + pub layer_id: LayerId, } /// Get all the animated properties in a keyframes animation. @@ -409,12 +412,14 @@ impl KeyframesAnimation { pub fn from_keyframes( keyframes: &[Arc>], vendor_prefix: Option, + layer_id: LayerId, guard: &SharedRwLockReadGuard, ) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: LonghandIdSet::new(), vendor_prefix, + layer_id, }; if keyframes.is_empty() { diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs new file mode 100644 index 00000000000..ee066d813e2 --- /dev/null +++ b/components/style/stylesheets/layer_rule.rs @@ -0,0 +1,235 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A [`@layer`][layer] urle. +//! +//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering + +use crate::parser::{Parse, ParserContext}; +use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; +use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; +use crate::values::AtomIdent; + +use super::CssRules; + +use cssparser::{Parser, SourceLocation, ToCss as CssParserToCss, Token}; +use servo_arc::Arc; +use smallvec::SmallVec; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ParseError, ToCss}; + +/// The order of a given layer. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerOrder(u32); + +impl LayerOrder { + /// The order of the root layer. + pub const fn root() -> Self { + Self(std::u32::MAX) + } + + /// The first cascade layer order. + pub const fn first() -> Self { + Self(0) + } + + /// Increment the cascade layer order. + #[inline] + pub fn inc(&mut self) { + self.0 += 1; + } +} + +/// The id of a given layer, a sequentially-increasing identifier. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerId(pub u32); + +impl LayerId { + /// The id of the root layer. + pub const fn root() -> Self { + Self(0) + } +} + +/// A ``: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name +#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] +pub struct LayerName(pub SmallVec<[AtomIdent; 1]>); + +impl LayerName { + /// Returns an empty layer name (which isn't a valid final state, so caller + /// is responsible to fill up the name before use). + pub fn new_empty() -> Self { + Self(Default::default()) + } + + /// Returns a synthesized name for an anonymous layer. + pub fn new_anonymous() -> Self { + use std::sync::atomic::{AtomicUsize, Ordering}; + static NEXT_ANONYMOUS_LAYER_NAME: AtomicUsize = AtomicUsize::new(0); + + let mut name = SmallVec::new(); + let next_id = NEXT_ANONYMOUS_LAYER_NAME.fetch_add(1, Ordering::Relaxed); + // The parens don't _technically_ prevent conflicts with authors, as + // authors could write escaped parens as part of the identifier, I + // think, but highly reduces the possibility. + name.push(AtomIdent::from(&*format!("-moz-anon-layer({})", next_id))); + + LayerName(name) + } + + /// Returns the names of the layers. That is, for a layer like `foo.bar`, + /// it'd return [foo, bar]. + pub fn layer_names(&self) -> &[AtomIdent] { + &self.0 + } +} + +impl Parse for LayerName { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut result = SmallVec::new(); + result.push(AtomIdent::from(&**input.expect_ident()?)); + loop { + let next_name = input.try_parse(|input| -> Result> { + match input.next_including_whitespace()? { + Token::Delim('.') => {}, + other => { + let t = other.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + } + + let name = match input.next_including_whitespace()? { + Token::Ident(ref ident) => ident, + other => { + let t = other.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + }; + + Ok(AtomIdent::from(&**name)) + }); + + match next_name { + Ok(name) => result.push(name), + Err(..) => break, + } + } + Ok(LayerName(result)) + } +} + +impl ToCss for LayerName { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + let mut first = true; + for name in self.0.iter() { + if !first { + dest.write_char('.')?; + } + first = false; + name.to_css(dest)?; + } + Ok(()) + } +} + +/// The kind of layer rule this is. +#[derive(Debug, ToShmem)] +pub enum LayerRuleKind { + /// A block `@layer ? { ... }` + Block { + /// The layer name, or `None` if anonymous. + name: Option, + /// The nested rules. + rules: Arc>, + }, + /// A statement `@layer , , ;` + Statement { + /// The list of layers to sort. + names: Vec, + }, +} + +/// A [`@layer`][layer] rule. +/// +/// [layer]: https://drafts.csswg.org/css-cascade-5/#layering +#[derive(Debug, ToShmem)] +pub struct LayerRule { + /// The kind of layer rule we are. + pub kind: LayerRuleKind, + /// The source position where this media rule was found. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for LayerRule { + fn to_css( + &self, + guard: &SharedRwLockReadGuard, + dest: &mut crate::str::CssStringWriter, + ) -> fmt::Result { + dest.write_str("@layer")?; + match self.kind { + LayerRuleKind::Block { + ref name, + ref rules, + } => { + if let Some(ref name) = *name { + dest.write_char(' ')?; + name.to_css(&mut CssWriter::new(dest))?; + } + rules.read_with(guard).to_css_block(guard, dest) + }, + LayerRuleKind::Statement { ref names } => { + let mut writer = CssWriter::new(dest); + let mut first = true; + for name in &**names { + if first { + writer.write_char(' ')?; + } else { + writer.write_str(", ")?; + } + first = false; + name.to_css(&mut writer)?; + } + dest.write_char(';') + }, + } + } +} + +impl DeepCloneWithLock for LayerRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + Self { + kind: match self.kind { + LayerRuleKind::Block { + ref name, + ref rules, + } => LayerRuleKind::Block { + name: name.clone(), + rules: Arc::new( + lock.wrap( + rules + .read_with(guard) + .deep_clone_with_lock(lock, guard, params), + ), + ), + }, + LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement { + names: names.clone(), + }, + }, + source_location: self.source_location.clone(), + } + } +} diff --git a/components/style/stylesheets/loader.rs b/components/style/stylesheets/loader.rs index ce7c563db05..c145bafdc04 100644 --- a/components/style/stylesheets/loader.rs +++ b/components/style/stylesheets/loader.rs @@ -8,7 +8,7 @@ use crate::media_queries::MediaList; use crate::parser::ParserContext; use crate::shared_lock::{Locked, SharedRwLock}; -use crate::stylesheets::import_rule::ImportRule; +use crate::stylesheets::import_rule::{ImportRule, ImportLayer}; use crate::values::CssUrl; use cssparser::SourceLocation; use servo_arc::Arc; @@ -25,5 +25,6 @@ pub trait StylesheetLoader { context: &ParserContext, lock: &SharedRwLock, media: Arc>, + layer: Option, ) -> Arc>; } diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index fd9be56b5b5..762bca6033f 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -11,6 +11,7 @@ mod font_face_rule; pub mod font_feature_values_rule; pub mod import_rule; pub mod keyframes_rule; +pub mod layer_rule; mod loader; mod media_rule; mod namespace_rule; @@ -19,6 +20,7 @@ mod page_rule; mod rule_list; mod rule_parser; mod rules_iterator; +pub mod scroll_timeline_rule; mod style_rule; mod stylesheet; pub mod supports_rule; @@ -49,6 +51,7 @@ pub use self::font_face_rule::FontFaceRule; pub use self::font_feature_values_rule::FontFeatureValuesRule; pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; +pub use self::layer_rule::LayerRule; pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; @@ -60,6 +63,7 @@ pub use self::rules_iterator::{AllRules, EffectiveRules}; pub use self::rules_iterator::{ EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator, }; +pub use self::scroll_timeline_rule::ScrollTimelineRule; pub use self::style_rule::StyleRule; pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; @@ -257,6 +261,8 @@ pub enum CssRule { Supports(Arc>), Page(Arc>), Document(Arc>), + Layer(Arc>), + ScrollTimeline(Arc>), } impl CssRule { @@ -297,16 +303,22 @@ impl CssRule { CssRule::Document(ref lock) => { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, + + // TODO(emilio): Add memory reporting for @layer rules. + CssRule::Layer(_) => 0, + CssRule::ScrollTimeline(_) => 0, } } } +/// https://drafts.csswg.org/cssom-1/#dom-cssrule-type #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] +#[repr(u8)] pub enum CssRuleType { // https://drafts.csswg.org/cssom/#the-cssrule-interface Style = 1, - Charset = 2, + // Charset = 2, // Historical Import = 3, Media = 4, FontFace = 5, @@ -315,7 +327,7 @@ pub enum CssRuleType { Keyframes = 7, Keyframe = 8, // https://drafts.csswg.org/cssom/#the-cssrule-interface - Margin = 9, + // Margin = 9, // Not implemented yet. Namespace = 10, // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface CounterStyle = 11, @@ -323,10 +335,14 @@ pub enum CssRuleType { Supports = 12, // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface Document = 13, - // https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues + // https://drafts.csswg.org/css-fonts/#om-fontfeaturevalues FontFeatureValues = 14, // https://drafts.csswg.org/css-device-adapt/#css-rule-interface Viewport = 15, + // After viewport, all rules should return 0 from the API, but we still need + // a constant somewhere. + Layer = 16, + ScrollTimeline = 17, } #[allow(missing_docs)] @@ -353,6 +369,8 @@ impl CssRule { CssRule::Supports(_) => CssRuleType::Supports, CssRule::Page(_) => CssRuleType::Page, CssRule::Document(_) => CssRuleType::Document, + CssRule::Layer(_) => CssRuleType::Layer, + CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, } } @@ -361,6 +379,7 @@ impl CssRule { // CssRule::Charset(..) => State::Start, CssRule::Import(..) => State::Imports, CssRule::Namespace(..) => State::Namespaces, + // TODO(emilio): Do we need something for EarlyLayers? _ => State::Body, } } @@ -485,6 +504,16 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, + CssRule::Layer(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Layer(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), + )) + } + CssRule::ScrollTimeline(ref arc) => { + let rule = arc.read_with(guard); + CssRule::ScrollTimeline(Arc::new(lock.wrap(rule.clone()))) + } } } } @@ -505,6 +534,8 @@ impl ToCssWithGuard for CssRule { CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Layer(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest), } } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 914af2cfb66..7e3e4f9f86c 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -15,20 +15,23 @@ use crate::shared_lock::{Locked, SharedRwLock}; use crate::str::starts_with_ignore_ascii_case; use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::font_feature_values_rule::parse_family_name_list; +use crate::stylesheets::import_rule::ImportLayer; use crate::stylesheets::keyframes_rule::parse_keyframe_list; +use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind}; +use crate::stylesheets::scroll_timeline_rule::ScrollTimelineDescriptors; use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; -use crate::stylesheets::viewport_rule; -use crate::stylesheets::AllowImportRules; -use crate::stylesheets::{CorsMode, DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule}; -use crate::stylesheets::{CssRule, CssRuleType, CssRules, RulesMutateError, StylesheetLoader}; -use crate::stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule}; +use crate::stylesheets::{ + viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, + FontFeatureValuesRule, KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, + RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, +}; use crate::values::computed::font::FamilyName; -use crate::values::{CssUrl, CustomIdent, KeyframesName}; +use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName}; use crate::{Namespace, Prefix}; -use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser}; use cssparser::{ - BasicParseError, BasicParseErrorKind, CowRcStr, ParseErrorKind, ParserState, SourcePosition, + AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, Parser, ParserState, + QualifiedRuleParser, RuleListParser, SourcePosition, }; use selectors::SelectorList; use servo_arc::Arc; @@ -130,12 +133,14 @@ impl<'b> TopLevelRuleParser<'b> { pub enum State { /// We haven't started parsing rules. Start = 1, - /// We're parsing `@import` rules. - Imports = 2, + /// We're parsing early `@layer` statement rules. + EarlyLayers = 2, + /// We're parsing `@import` and early `@layer` statement rules. + Imports = 3, /// We're parsing `@namespace` rules. - Namespaces = 3, + Namespaces = 4, /// We're parsing the main body of the stylesheet. - Body = 4, + Body = 5, } #[derive(Clone, Debug, MallocSizeOf, ToShmem)] @@ -168,9 +173,13 @@ pub enum AtRulePrelude { /// A @document rule, with its conditional. Document(DocumentCondition), /// A @import rule prelude. - Import(CssUrl, Arc>), + Import(CssUrl, Arc>, Option), /// A @namespace rule prelude. Namespace(Option, Namespace), + /// A @layer rule prelude. + Layer(Vec), + /// A @scroll-timeline rule prelude. + ScrollTimeline(TimelineName), } impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { @@ -182,7 +191,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, - ) -> Result> { + ) -> Result> { match_ignore_ascii_case! { &*name, "import" => { if !self.check_state(State::Imports) { @@ -203,12 +212,32 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { let url_string = input.expect_url_or_string()?.as_ref().to_owned(); let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None); + #[cfg(feature = "gecko")] + let layers_enabled = static_prefs::pref!("layout.css.cascade-layers.enabled"); + #[cfg(feature = "servo")] + let layers_enabled = false; + + let layer = if !layers_enabled { + None + } else if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() { + Some(ImportLayer { + name: None, + }) + } else { + input.try_parse(|input| { + input.expect_function_matching("layer")?; + input.parse_nested_block(|input| { + LayerName::parse(&self.context, input) + }).map(|name| ImportLayer { + name: Some(name), + }) + }).ok() + }; + let media = MediaList::parse(&self.context, input); let media = Arc::new(self.shared_lock.wrap(media)); - let prelude = AtRulePrelude::Import(url, media); - - return Ok(prelude); + return Ok(AtRulePrelude::Import(url, media, layer)); }, "namespace" => { if !self.check_state(State::Namespaces) { @@ -225,8 +254,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { Err(e) => return Err(e.into()), }; let url = Namespace::from(maybe_namespace.as_ref()); - let prelude = AtRulePrelude::Namespace(prefix, url); - return Ok(prelude); + return Ok(AtRulePrelude::Namespace(prefix, url)); }, // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet // anything left is invalid. @@ -261,9 +289,9 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &mut self, prelude: AtRulePrelude, start: &ParserState, - ) -> Result { + ) -> Result { let rule = match prelude { - AtRulePrelude::Import(url, media) => { + AtRulePrelude::Import(url, media, layer) => { let loader = self .loader .expect("Expected a stylesheet loader for @import"); @@ -274,6 +302,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &self.context, &self.shared_lock, media, + layer, ); self.state = State::Imports; @@ -295,7 +324,19 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { source_location: start.source_location(), }))) }, - _ => return Err(()), + AtRulePrelude::Layer(ref names) => { + if names.is_empty() { + return Err(()); + } + if self.state <= State::EarlyLayers { + self.state = State::EarlyLayers; + } else { + self.state = State::Body; + } + AtRuleParser::rule_without_block(&mut self.nested(), prelude, start) + .expect("All validity checks on the nested parser should be done before changing self.state") + }, + _ => AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)?, }; Ok((start.position(), rule)) @@ -379,41 +420,37 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { name: CowRcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result> { - match_ignore_ascii_case! { &*name, + Ok(match_ignore_ascii_case! { &*name, "media" => { let media_queries = MediaList::parse(self.context, input); let arc = Arc::new(self.shared_lock.wrap(media_queries)); - Ok(Self::Prelude::Media(arc)) + AtRulePrelude::Media(arc) }, "supports" => { let cond = SupportsCondition::parse(input)?; - Ok(Self::Prelude::Supports(cond)) + AtRulePrelude::Supports(cond) }, "font-face" => { - Ok(Self::Prelude::FontFace) + AtRulePrelude::FontFace }, - "font-feature-values" => { - if !cfg!(feature = "gecko") { - // Support for this rule is not fully implemented in Servo yet. - return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "layer" => { + let names = input.try_parse(|input| { + input.parse_comma_separated(|input| { + LayerName::parse(self.context, input) + }) + }).unwrap_or_default(); + AtRulePrelude::Layer(names) + }, + "font-feature-values" if cfg!(feature = "gecko") => { let family_names = parse_family_name_list(self.context, input)?; - Ok(Self::Prelude::FontFeatureValues(family_names)) + AtRulePrelude::FontFeatureValues(family_names) }, - "counter-style" => { - if !cfg!(feature = "gecko") { - // Support for this rule is not fully implemented in Servo yet. - return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "counter-style" if cfg!(feature = "gecko") => { let name = parse_counter_style_name_definition(input)?; - Ok(Self::Prelude::CounterStyle(name)) + AtRulePrelude::CounterStyle(name) }, - "viewport" => { - if viewport_rule::enabled() { - Ok(Self::Prelude::Viewport) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "viewport" if viewport_rule::enabled() => { + AtRulePrelude::Viewport }, "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") { @@ -429,28 +466,22 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) } let name = KeyframesName::parse(self.context, input)?; - - Ok(Self::Prelude::Keyframes(name, prefix)) + AtRulePrelude::Keyframes(name, prefix) }, - "page" => { - if cfg!(feature = "gecko") { - Ok(Self::Prelude::Page) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "page" if cfg!(feature = "gecko") => { + AtRulePrelude::Page }, - "-moz-document" => { - if !cfg!(feature = "gecko") { - return Err(input.new_custom_error( - StyleParseErrorKind::UnsupportedAtRule(name.clone()) - )) - } - + "-moz-document" if cfg!(feature = "gecko") => { let cond = DocumentCondition::parse(self.context, input)?; - Ok(Self::Prelude::Document(cond)) + AtRulePrelude::Document(cond) }, - _ => Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + #[cfg(feature = "gecko")] + "scroll-timeline" if static_prefs::pref!("layout.css.scroll-linked-animations.enabled") => { + let name = TimelineName::parse(self.context, input)?; + AtRulePrelude::ScrollTimeline(name) + }, + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) + }) } fn parse_block<'t>( @@ -577,14 +608,62 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, - _ => Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(CowRcStr::from( - "Unsupported AtRule Prelude.", - ))), - location: start.source_location(), - }), + AtRulePrelude::Layer(names) => { + let name = match names.len() { + 0 | 1 => names.into_iter().next(), + _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), + }; + Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap( + LayerRule { + kind: LayerRuleKind::Block { + name, + rules: self.parse_nested_rules(input, CssRuleType::Layer), + }, + source_location: start.source_location(), + }, + )))) + }, + AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { + // These rules don't have blocks. + Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)) + }, + AtRulePrelude::ScrollTimeline(name) => { + let context = ParserContext::new_with_rule_type( + self.context, + CssRuleType::ScrollTimeline, + self.namespaces, + ); + + Ok(CssRule::ScrollTimeline(Arc::new(self.shared_lock.wrap( + ScrollTimelineRule { + name, + descriptors: ScrollTimelineDescriptors::parse(&context, input)?, + source_location: start.source_location(), + }, + )))) + }, } } + + #[inline] + fn rule_without_block( + &mut self, + prelude: AtRulePrelude, + start: &ParserState, + ) -> Result { + Ok(match prelude { + AtRulePrelude::Layer(names) => { + if names.is_empty() { + return Err(()); + } + CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { + kind: LayerRuleKind::Statement { names }, + source_location: start.source_location(), + }))) + }, + _ => return Err(()), + }) + } } #[inline(never)] diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index a7010ff066e..32851dd2cca 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -51,60 +51,68 @@ where pub fn skip_children(&mut self) { self.stack.pop(); } -} -fn children_of_rule<'a, C>( - rule: &'a CssRule, - device: &'a Device, - quirks_mode: QuirksMode, - guard: &'a SharedRwLockReadGuard<'_>, - effective: &mut bool, -) -> Option> -where - C: NestedRuleIterationCondition + 'static, -{ - *effective = true; - match *rule { - CssRule::Namespace(_) | - CssRule::Style(_) | - CssRule::FontFace(_) | - CssRule::CounterStyle(_) | - CssRule::Viewport(_) | - CssRule::Keyframes(_) | - CssRule::Page(_) | - CssRule::FontFeatureValues(_) => None, - CssRule::Import(ref import_rule) => { - let import_rule = import_rule.read_with(guard); - if !C::process_import(guard, device, quirks_mode, import_rule) { - *effective = false; - return None; + /// Returns the children of `rule`, and whether `rule` is effective. + pub fn children( + rule: &'a CssRule, + device: &'a Device, + quirks_mode: QuirksMode, + guard: &'a SharedRwLockReadGuard<'_>, + effective: &mut bool, + ) -> Option> { + *effective = true; + match *rule { + CssRule::Namespace(_) | + CssRule::Style(_) | + CssRule::FontFace(_) | + CssRule::CounterStyle(_) | + CssRule::Viewport(_) | + CssRule::Keyframes(_) | + CssRule::ScrollTimeline(_) | + CssRule::Page(_) | + CssRule::FontFeatureValues(_) => None, + CssRule::Import(ref import_rule) => { + let import_rule = import_rule.read_with(guard); + if !C::process_import(guard, device, quirks_mode, import_rule) { + *effective = false; + return None; + } + Some(import_rule.stylesheet.rules(guard).iter()) + }, + CssRule::Document(ref doc_rule) => { + let doc_rule = doc_rule.read_with(guard); + if !C::process_document(guard, device, quirks_mode, doc_rule) { + *effective = false; + return None; + } + Some(doc_rule.rules.read_with(guard).0.iter()) + }, + CssRule::Media(ref lock) => { + let media_rule = lock.read_with(guard); + if !C::process_media(guard, device, quirks_mode, media_rule) { + *effective = false; + return None; + } + Some(media_rule.rules.read_with(guard).0.iter()) + }, + CssRule::Supports(ref lock) => { + let supports_rule = lock.read_with(guard); + if !C::process_supports(guard, device, quirks_mode, supports_rule) { + *effective = false; + return None; + } + Some(supports_rule.rules.read_with(guard).0.iter()) + }, + CssRule::Layer(ref lock) => { + use crate::stylesheets::layer_rule::LayerRuleKind; + + let layer_rule = lock.read_with(guard); + match layer_rule.kind { + LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()), + LayerRuleKind::Statement { .. } => None, + } } - Some(import_rule.stylesheet.rules(guard).iter()) - }, - CssRule::Document(ref doc_rule) => { - let doc_rule = doc_rule.read_with(guard); - if !C::process_document(guard, device, quirks_mode, doc_rule) { - *effective = false; - return None; - } - Some(doc_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Media(ref lock) => { - let media_rule = lock.read_with(guard); - if !C::process_media(guard, device, quirks_mode, media_rule) { - *effective = false; - return None; - } - Some(media_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Supports(ref lock) => { - let supports_rule = lock.read_with(guard); - if !C::process_supports(guard, device, quirks_mode, supports_rule) { - *effective = false; - return None; - } - Some(supports_rule.rules.read_with(guard).0.iter()) - }, + } } } @@ -129,7 +137,7 @@ where }; let mut effective = true; - let children = children_of_rule::( + let children = Self::children( rule, self.device, self.quirks_mode, @@ -315,7 +323,7 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { guard: &'a SharedRwLockReadGuard<'b>, rule: &'a CssRule, ) -> Self { - let children = children_of_rule::(rule, device, quirks_mode, guard, &mut false); + let children = RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) } } diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs new file mode 100644 index 00000000000..bbc5d9caf8f --- /dev/null +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -0,0 +1,327 @@ +/* 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/. */ + +//! scroll-timeline-at-rule: https://drafts.csswg.org/scroll-animations/#scroll-timeline-at-rule + +use crate::parser::{Parse, ParserContext}; +use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use crate::str::CssStringWriter; +use crate::values::specified::{LengthPercentage, Number}; +use crate::values::{AtomIdent, TimelineName}; +use cssparser::{AtRuleParser, CowRcStr, DeclarationParser, Parser, SourceLocation, Token}; +use selectors::parser::SelectorParseErrorKind; +use std::fmt::{self, Debug, Write}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// A [`@scroll-timeline`][descriptors] rule. +/// +/// [descriptors] https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors +#[derive(Clone, Debug, ToShmem)] +pub struct ScrollTimelineRule { + /// The name of the current scroll timeline. + pub name: TimelineName, + /// The descriptors. + pub descriptors: ScrollTimelineDescriptors, + /// The line and column of the rule's source code. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for ScrollTimelineRule { + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + let mut dest = CssWriter::new(dest); + dest.write_str("@scroll-timeline ")?; + self.name.to_css(&mut dest)?; + dest.write_str(" { ")?; + self.descriptors.to_css(&mut dest)?; + dest.write_str("}") + } +} + +/// The descriptors of @scroll-timeline. +/// +/// https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors +#[derive(Clone, Debug, Default, ToShmem)] +pub struct ScrollTimelineDescriptors { + /// The source of the current scroll timeline. + pub source: Option, + /// The orientation of the current scroll timeline. + pub orientation: Option, + /// The scroll timeline's scrollOffsets. + pub offsets: Option, +} + +impl Parse for ScrollTimelineDescriptors { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + use crate::cssparser::DeclarationListParser; + use crate::error_reporting::ContextualParseError; + + let mut descriptors = ScrollTimelineDescriptors::default(); + let parser = ScrollTimelineDescriptorsParser { + context, + descriptors: &mut descriptors, + }; + let mut iter = DeclarationListParser::new(input, parser); + while let Some(declaration) = iter.next() { + if let Err((error, slice)) = declaration { + let location = error.location; + let error = ContextualParseError::UnsupportedRule(slice, error); + context.log_css_error(location, error) + } + } + + Ok(descriptors) + } +} + +// Basically, this is used for the serialization of CSSScrollTimelineRule, so we follow the +// instructions in https://drafts.csswg.org/scroll-animations-1/#serialize-a-cssscrolltimelinerule. +impl ToCss for ScrollTimelineDescriptors { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if let Some(ref value) = self.source { + dest.write_str("source: ")?; + value.to_css(dest)?; + dest.write_str("; ")?; + } + + if let Some(ref value) = self.orientation { + dest.write_str("orientation: ")?; + value.to_css(dest)?; + dest.write_str("; ")?; + } + + // https://github.com/w3c/csswg-drafts/issues/6617 + if let Some(ref value) = self.offsets { + dest.write_str("scroll-offsets: ")?; + value.to_css(dest)?; + dest.write_str("; ")?; + } + Ok(()) + } +} + +struct ScrollTimelineDescriptorsParser<'a, 'b: 'a> { + context: &'a ParserContext<'b>, + descriptors: &'a mut ScrollTimelineDescriptors, +} + +impl<'a, 'b, 'i> AtRuleParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> { + type Prelude = (); + type AtRule = (); + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> DeclarationParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> { + type Declaration = (); + type Error = StyleParseErrorKind<'i>; + + fn parse_value<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + macro_rules! parse_descriptor { + ( + $( $name: tt / $ident: ident, )* + ) => { + match_ignore_ascii_case! { &*name, + $( + $name => { + let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; + self.descriptors.$ident = Some(value) + }, + )* + _ => { + return Err(input.new_custom_error( + SelectorParseErrorKind::UnexpectedIdent(name.clone()), + )) + } + } + } + } + parse_descriptor! { + "source" / source, + "orientation" / orientation, + "scroll-offsets" / offsets, + }; + Ok(()) + } +} + +/// The scroll-timeline source. +/// +/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-source +#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] +pub enum Source { + /// The scroll container. + Selector(ScrollTimelineSelector), + /// The initial value. The scrollingElement of the Document associated with the Window that is + /// the current global object. + Auto, + /// Null. However, it's not clear what is the expected behavior of this. See the spec issue: + /// https://drafts.csswg.org/scroll-animations/#issue-0d1e73bd + None, +} + +impl Default for Source { + fn default() -> Self { + Source::Auto + } +} + +/// The scroll-timeline orientation. +/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-orientation +/// +/// Note: the initial orientation is auto, and we will treat it as block, the same as the +/// definition of ScrollTimelineOptions (WebIDL API). +/// https://drafts.csswg.org/scroll-animations/#dom-scrolltimelineoptions-orientation +#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] +pub enum Orientation { + /// The initial value. + Auto, + /// The direction along the block axis. This is the default value. + Block, + /// The direction along the inline axis + Inline, + /// The physical horizontal direction. + Horizontal, + /// The physical vertical direction. + Vertical, +} + +impl Default for Orientation { + fn default() -> Self { + Orientation::Auto + } +} + +/// Scroll-timeline offsets. We treat None as an empty vector. +/// value: none | # +/// +/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-scroll-offsets +#[derive(Clone, Default, Debug, ToCss, ToShmem)] +#[css(comma)] +pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimelineOffset]>); + +impl Parse for ScrollOffsets { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(ScrollOffsets(Box::new([]))); + } + + Ok(ScrollOffsets( + input + .parse_comma_separated(|i| ScrollTimelineOffset::parse(context, i))? + .into_boxed_slice(), + )) + } +} + +/// A . +/// value: auto | | +/// +/// https://drafts.csswg.org/scroll-animations/#typedef-scroll-timeline-offset +#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] +pub enum ScrollTimelineOffset { + /// The initial value. A container-based offset. + Auto, + /// A container-based offset with the distance indicated by the value along source's scroll + /// range in orientation. + LengthPercentage(LengthPercentage), + /// An element-based offset. + ElementOffset(ElementOffset), +} + +/// An . +/// +/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset-edge +#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] +pub enum ElementOffsetEdge { + /// Start edge + Start, + /// End edge. + End, +} + +/// An . +/// value: selector( ) [ || ]? +/// +/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset +#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] +pub struct ElementOffset { + /// The target whose intersection with source's scrolling box determines the concrete scroll + /// offset. + target: ScrollTimelineSelector, + /// An optional value of . If not provided, the default value is start. + edge: Option, + /// An optional value of threshold. If not provided, the default value is 0. + threshold: Option, +} + +impl Parse for ElementOffset { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let target = ScrollTimelineSelector::parse(context, input)?; + + // Parse `[ || ]?` + let mut edge = input.try_parse(ElementOffsetEdge::parse).ok(); + let threshold = input.try_parse(|i| Number::parse(context, i)).ok(); + if edge.is_none() { + edge = input.try_parse(ElementOffsetEdge::parse).ok(); + } + + Ok(ElementOffset { + target, + edge, + threshold, + }) + } +} + +/// The type of the selector ID. +#[derive(Clone, Eq, PartialEq, ToShmem)] +pub struct ScrollTimelineSelector(AtomIdent); + +impl Parse for ScrollTimelineSelector { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + // Parse `selector()`. + input.expect_function_matching("selector")?; + input.parse_nested_block(|i| match i.next()? { + Token::IDHash(id) => Ok(ScrollTimelineSelector(id.as_ref().into())), + _ => Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + }) + } +} + +impl ToCss for ScrollTimelineSelector { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + use crate::cssparser::ToCss as CssparserToCss; + dest.write_str("selector(")?; + dest.write_char('#')?; + self.0.to_css(dest)?; + dest.write_char(')') + } +} + +impl Debug for ScrollTimelineSelector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(&mut CssWriter::new(f)) + } +} diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index b8e7f246c19..1b6b59bac78 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -278,15 +278,8 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { rule_filter! { effective_style_rules(Style => StyleRule), - effective_media_rules(Media => MediaRule), effective_font_face_rules(FontFace => FontFaceRule), - effective_font_face_feature_values_rules(FontFeatureValues => FontFeatureValuesRule), - effective_counter_style_rules(CounterStyle => CounterStyleRule), effective_viewport_rules(Viewport => ViewportRule), - effective_keyframes_rules(Keyframes => KeyframesRule), - effective_supports_rules(Supports => SupportsRule), - effective_page_rules(Page => PageRule), - effective_document_rules(Document => DocumentRule), } } @@ -367,7 +360,10 @@ impl SanitizationKind { CssRule::Document(..) | CssRule::Media(..) | CssRule::Supports(..) | - CssRule::Import(..) => false, + CssRule::Import(..) | + // TODO(emilio): Perhaps Layer should not be always sanitized? But + // we sanitize @media and co, so this seems safer for now. + CssRule::Layer(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, @@ -375,7 +371,8 @@ impl SanitizationKind { CssRule::Page(..) | CssRule::FontFeatureValues(..) | CssRule::Viewport(..) | - CssRule::CounterStyle(..) => !is_standard, + CssRule::CounterStyle(..) | + CssRule::ScrollTimeline(..) => !is_standard, } } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index fb7334d3600..0663ea440d9 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -25,11 +25,12 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheets::keyframes_rule::KeyframesAnimation; +use crate::stylesheets::layer_rule::{LayerName, LayerId, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; #[cfg(feature = "gecko")] use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule}; -use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter}; +use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter, EffectiveRulesIterator}; use crate::thread_state::{self, ThreadState}; use crate::{Atom, LocalName, Namespace, WeakAtom}; use fallible::FallibleVec; @@ -292,6 +293,8 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { )?; } + new_data.cascade_data.compute_layer_order(); + Ok(Arc::new(new_data)) } @@ -1866,6 +1869,23 @@ impl PartElementAndPseudoRules { } } +#[derive(Clone, Debug, MallocSizeOf)] +struct CascadeLayer { + id: LayerId, + order: LayerOrder, + children: Vec, +} + +impl CascadeLayer { + const fn root() -> Self { + Self { + id: LayerId::root(), + order: LayerOrder::root(), + children: vec![], + } + } +} + /// Data resulting from performing the CSS cascade that is specific to a given /// origin. /// @@ -1931,6 +1951,12 @@ pub struct CascadeData { /// by name. animations: PrecomputedHashMap, + /// A map from cascade layer name to layer order. + layer_id: FxHashMap, + + /// The list of cascade layers, indexed by their layer id. + layers: SmallVec<[CascadeLayer; 1]>, + /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -1961,8 +1987,18 @@ impl CascadeData { state_dependencies: ElementState::empty(), document_state_dependencies: DocumentState::empty(), mapped_ids: PrecomputedHashSet::default(), - selectors_for_cache_revalidation: SelectorMap::new(), + // NOTE: We disable attribute bucketing for revalidation because we + // rely on the buckets to match, but we don't want to just not share + // style across elements with different attributes. + // + // An alternative to this would be to perform a style sharing check + // like may_match_different_id_rules which would check that the + // attribute buckets match on all scopes. But that seems + // somewhat gnarly. + selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), animations: Default::default(), + layer_id: Default::default(), + layers: smallvec::smallvec![CascadeLayer::root()], extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2009,6 +2045,8 @@ impl CascadeData { result.is_ok() }); + self.compute_layer_order(); + result } @@ -2070,6 +2108,43 @@ impl CascadeData { self.part_rules.is_some() } + #[inline] + fn layer_order_for(&self, id: LayerId) -> LayerOrder { + self.layers[id.0 as usize].order + } + + fn compute_layer_order(&mut self) { + debug_assert_ne!(self.layers.len(), 0, "There should be at least the root layer!"); + if self.layers.len() == 1 { + return; // Nothing to do + } + let (first, remaining) = self.layers.split_at_mut(1); + let root = &mut first[0]; + let mut order = LayerOrder::first(); + compute_layer_order_for_subtree(root, remaining, &mut order); + + // NOTE(emilio): This is a bit trickier than it should to avoid having + // to clone() around layer indices. + fn compute_layer_order_for_subtree( + parent: &mut CascadeLayer, + remaining_layers: &mut [CascadeLayer], + order: &mut LayerOrder, + ) { + for child in parent.children.iter() { + debug_assert!(parent.id < *child, "Children are always registered after parents"); + let child_index = (child.0 - parent.id.0 - 1) as usize; + let (first, remaining) = remaining_layers.split_at_mut(child_index + 1); + let child = &mut first[child_index]; + compute_layer_order_for_subtree(child, remaining, order); + } + + if parent.id != LayerId::root() { + parent.order = *order; + order.inc(); + } + } + } + /// Collects all the applicable media query results into `results`. /// /// This duplicates part of the logic in `add_stylesheet`, which is @@ -2117,31 +2192,25 @@ impl CascadeData { } } - // Returns Err(..) to signify OOM - fn add_stylesheet( + fn add_rule_list( &mut self, + rules: std::slice::Iter<'_, CssRule>, device: &Device, quirks_mode: QuirksMode, stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, + mut current_layer: &mut LayerName, + current_layer_id: LayerId, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where S: StylesheetInDocument + 'static, { - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { - return Ok(()); - } - - let contents = stylesheet.contents(); - let origin = contents.origin; - - if rebuild_kind.should_rebuild_invalidation() { - self.effective_media_query_results.saw_effective(contents); - } - - for rule in stylesheet.effective_rules(device, guard) { + for rule in rules { + // Handle leaf rules first, as those are by far the most common + // ones, and are always effective, so we can skip some checks. + let mut handled = true; match *rule { CssRule::Style(ref locked) => { let style_rule = locked.read_with(&guard); @@ -2154,7 +2223,8 @@ impl CascadeData { if let Some(pseudo) = pseudo_element { if pseudo.is_precomputed() { debug_assert!(selector.is_universal()); - debug_assert!(matches!(origin, Origin::UserAgent)); + debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); + debug_assert_eq!(current_layer_id, LayerId::root()); precomputed_pseudo_element_decls .as_mut() @@ -2165,6 +2235,7 @@ impl CascadeData { self.rules_source_order, CascadeLevel::UANormal, selector.specificity(), + LayerOrder::root(), )); continue; } @@ -2180,6 +2251,7 @@ impl CascadeData { hashes, locked.clone(), self.rules_source_order, + current_layer_id, ); if rebuild_kind.should_rebuild_invalidation() { @@ -2243,43 +2315,52 @@ impl CascadeData { } self.rules_source_order += 1; }, - CssRule::Import(ref lock) => { - if rebuild_kind.should_rebuild_invalidation() { - let import_rule = lock.read_with(guard); - self.effective_media_query_results - .saw_effective(import_rule); - } - - // NOTE: effective_rules visits the inner stylesheet if - // appropriate. - }, - CssRule::Media(ref lock) => { - if rebuild_kind.should_rebuild_invalidation() { - let media_rule = lock.read_with(guard); - self.effective_media_query_results.saw_effective(media_rule); - } - }, CssRule::Keyframes(ref keyframes_rule) => { + #[cfg(feature = "gecko")] + use hashglobe::hash_map::Entry; + #[cfg(feature = "servo")] + use hashglobe::fake::Entry; + let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - - // Don't let a prefixed keyframes animation override a non-prefixed one. - let needs_insertion = keyframes_rule.vendor_prefix.is_none() || - self.animations - .get(keyframes_rule.name.as_atom()) - .map_or(true, |rule| rule.vendor_prefix.is_some()); - if needs_insertion { - let animation = KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - guard, - ); - debug!("Found valid keyframe animation: {:?}", animation); - self.animations - .try_insert(keyframes_rule.name.as_atom().clone(), animation)?; + match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? { + Entry::Vacant(e) => { + e.insert(KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + current_layer_id, + guard, + )); + }, + Entry::Occupied(mut e) => { + // Don't let a prefixed keyframes animation override + // a non-prefixed one. + // + // TODO(emilio): This will need to be harder for + // layers. + let needs_insert = + keyframes_rule.vendor_prefix.is_none() || + e.get().vendor_prefix.is_some(); + if needs_insert { + e.insert(KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + current_layer_id, + guard, + )); + } + }, } }, #[cfg(feature = "gecko")] + CssRule::ScrollTimeline(..) => { + // TODO: Bug 1676791: set the timeline into animation. + // https://phabricator.services.mozilla.com/D126452 + // + // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue + // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. + }, + #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { self.extra_data.add_font_face(rule); }, @@ -2295,14 +2376,223 @@ impl CascadeData { CssRule::Page(ref rule) => { self.extra_data.add_page(rule); }, + CssRule::Viewport(..) => {}, + _ => { + handled = false; + }, + } + + if handled { + // Assert that there are no children, and that the rule is + // effective. + if cfg!(debug_assertions) { + let mut effective = false; + let children = EffectiveRulesIterator::children( + rule, + device, + quirks_mode, + guard, + &mut effective, + ); + debug_assert!(children.is_none()); + debug_assert!(effective); + } + continue; + } + + let mut effective = false; + let children = EffectiveRulesIterator::children( + rule, + device, + quirks_mode, + guard, + &mut effective, + ); + + if !effective { + continue; + } + + fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerId { + // TODO: Measure what's more common / expensive, if + // layer.clone() or the double hash lookup in the insert + // case. + if let Some(id) = data.layer_id.get(layer) { + return *id; + } + let id = LayerId(data.layers.len() as u32); + + let parent_layer_id = if layer.layer_names().len() > 1 { + let mut parent = layer.clone(); + parent.0.pop(); + + *data.layer_id + .get_mut(&parent) + .expect("Parent layers should be registered before child layers") + } else { + LayerId::root() + }; + + data.layers[parent_layer_id.0 as usize].children.push(id); + data.layers.push(CascadeLayer { + id, + // NOTE(emilio): Order is evaluated after rebuild in + // compute_layer_order. + order: LayerOrder::first(), + children: vec![], + }); + + data.layer_id.insert(layer.clone(), id); + + id + } + + fn maybe_register_layers( + data: &mut CascadeData, + name: Option<&LayerName>, + current_layer: &mut LayerName, + pushed_layers: &mut usize, + ) -> LayerId { + let anon_name; + let name = match name { + Some(name) => name, + None => { + anon_name = LayerName::new_anonymous(); + &anon_name + }, + }; + + let mut id = LayerId::root(); + for name in name.layer_names() { + current_layer.0.push(name.clone()); + id = maybe_register_layer(data, ¤t_layer); + *pushed_layers += 1; + } + debug_assert_ne!(id, LayerId::root()); + id + } + + let mut layer_names_to_pop = 0; + let mut children_layer_id = current_layer_id; + match *rule { + CssRule::Import(ref lock) => { + let import_rule = lock.read_with(guard); + if rebuild_kind.should_rebuild_invalidation() { + self.effective_media_query_results + .saw_effective(import_rule); + } + if let Some(ref layer) = import_rule.layer { + children_layer_id = maybe_register_layers( + self, + layer.name.as_ref(), + &mut current_layer, + &mut layer_names_to_pop, + ); + } + + }, + CssRule::Media(ref lock) => { + if rebuild_kind.should_rebuild_invalidation() { + let media_rule = lock.read_with(guard); + self.effective_media_query_results.saw_effective(media_rule); + } + }, + CssRule::Layer(ref lock) => { + use crate::stylesheets::layer_rule::LayerRuleKind; + + let layer_rule = lock.read_with(guard); + match layer_rule.kind { + LayerRuleKind::Block { ref name, .. } => { + children_layer_id = maybe_register_layers( + self, + name.as_ref(), + &mut current_layer, + &mut layer_names_to_pop, + ); + } + LayerRuleKind::Statement { ref names } => { + for name in &**names { + let mut pushed = 0; + // There are no children, so we can ignore the + // return value. + maybe_register_layers( + self, + Some(name), + &mut current_layer, + &mut pushed, + ); + for _ in 0..pushed { + current_layer.0.pop(); + } + } + } + } + }, // We don't care about any other rule. _ => {}, } + + if let Some(children) = children { + self.add_rule_list( + children, + device, + quirks_mode, + stylesheet, + guard, + rebuild_kind, + current_layer, + children_layer_id, + precomputed_pseudo_element_decls.as_deref_mut(), + )?; + } + + for _ in 0..layer_names_to_pop { + current_layer.0.pop(); + } } Ok(()) } + // Returns Err(..) to signify OOM + fn add_stylesheet( + &mut self, + device: &Device, + quirks_mode: QuirksMode, + stylesheet: &S, + guard: &SharedRwLockReadGuard, + rebuild_kind: SheetRebuildKind, + mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, + ) -> Result<(), FailedAllocationError> + where + S: StylesheetInDocument + 'static, + { + if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { + return Ok(()); + } + + let contents = stylesheet.contents(); + + if rebuild_kind.should_rebuild_invalidation() { + self.effective_media_query_results.saw_effective(contents); + } + + let mut current_layer = LayerName::new_empty(); + self.add_rule_list( + contents.rules(guard).iter(), + device, + quirks_mode, + stylesheet, + guard, + rebuild_kind, + &mut current_layer, + LayerId::root(), + precomputed_pseudo_element_decls.as_deref_mut(), + )?; + + Ok(()) + } + /// Returns whether all the media-feature affected values matched before and /// match now in the given stylesheet. pub fn media_feature_affected_matches( @@ -2345,9 +2635,11 @@ impl CascadeData { CssRule::CounterStyle(..) | CssRule::Supports(..) | CssRule::Keyframes(..) | + CssRule::ScrollTimeline(..) | CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | + CssRule::Layer(..) | CssRule::FontFeatureValues(..) => { // Not affected by device changes. continue; @@ -2413,6 +2705,9 @@ impl CascadeData { host_rules.clear(); } self.animations.clear(); + self.layer_id.clear(); + self.layers.clear(); + self.layers.push(CascadeLayer::root()); self.extra_data.clear(); self.rules_source_order = 0; self.num_selectors = 0; @@ -2501,6 +2796,9 @@ pub struct Rule { /// we could repurpose that storage here if we needed to. pub source_order: u32, + /// The current layer id of this style rule. + pub layer_id: LayerId, + /// The actual style rule. #[cfg_attr( feature = "gecko", @@ -2527,9 +2825,16 @@ impl Rule { pub fn to_applicable_declaration_block( &self, level: CascadeLevel, + cascade_data: &CascadeData, ) -> ApplicableDeclarationBlock { let source = StyleSource::from_rule(self.style_rule.clone()); - ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity()) + ApplicableDeclarationBlock::new( + source, + self.source_order, + level, + self.specificity(), + cascade_data.layer_order_for(self.layer_id), + ) } /// Creates a new Rule. @@ -2538,12 +2843,14 @@ impl Rule { hashes: AncestorHashes, style_rule: Arc>, source_order: u32, + layer_id: LayerId, ) -> Self { Rule { - selector: selector, - hashes: hashes, - style_rule: style_rule, - source_order: source_order, + selector, + hashes, + style_rule, + source_order, + layer_id, } } } diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index 22f5e306976..ce467343005 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -11,14 +11,12 @@ use crate::values::generics::box_::Perspective as GenericPerspective; use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign; use crate::values::specified::box_ as specified; -pub use crate::values::specified::box_::Clear as SpecifiedClear; -pub use crate::values::specified::box_::{AnimationName, Appearance, BreakBetween, BreakWithin}; -pub use crate::values::specified::box_::{Contain, Display, Float as SpecifiedFloat, Overflow}; -pub use crate::values::specified::box_::{OverflowAnchor, OverflowClipBox, OverscrollBehavior}; pub use crate::values::specified::box_::{ - ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType, + AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, + Clear as SpecifiedClear, Contain, Display, Float as SpecifiedFloat, Overflow, OverflowAnchor, + OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, + ScrollSnapType, TouchAction, TransitionProperty, WillChange, }; -pub use crate::values::specified::box_::{TouchAction, TransitionProperty, WillChange}; /// A computed value for the `vertical-align` property. pub type VerticalAlign = GenericVerticalAlign; diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index e2b29f61d5e..18fbe8c6fd8 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -439,6 +439,26 @@ pub enum GenericFontFamily { MozEmoji, } +impl GenericFontFamily { + /// When we disallow websites to override fonts, we ignore some generic + /// families that the website might specify, since they're not configured by + /// the user. See bug 789788 and bug 1730098. + #[cfg(feature = "gecko")] + pub (crate) fn valid_for_user_font_prioritization(self) -> bool { + match self { + Self::None | + Self::Fantasy | + Self::Cursive | + Self::SystemUi | + Self::MozEmoji => false, + + Self::Serif | + Self::SansSerif | + Self::Monospace => true, + } + } +} + impl Parse for SingleFontFamily { /// Parse a font-family value. fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { @@ -579,14 +599,14 @@ impl FontFamilyList { self.list = crate::ArcSlice::from_iter(new_list.into_iter()); } - /// If there's a generic font family on the list (which isn't cursive or - /// fantasy), then move it to the front of the list. Otherwise, prepend the - /// default generic. + /// If there's a generic font family on the list which is suitable for user + /// font prioritization, then move it to the front of the list. Otherwise, + /// prepend the default generic. #[cfg(feature = "gecko")] pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { let index_of_first_generic = self.iter().position(|f| { match *f { - SingleFontFamily::Generic(f) => f != GenericFontFamily::Cursive && f != GenericFontFamily::Fantasy, + SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(), _ => false, } }); diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 6290ac68a5d..a0cb8f3fe45 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -24,6 +24,8 @@ use std::f32::consts::PI; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; +pub use specified::ImageRendering; + /// Computed values for an image according to CSS-IMAGES. /// pub type Image = diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 3c4f7a2dbdc..02b42b48241 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -189,7 +189,7 @@ impl Size { #[cfg(feature = "gecko")] GenericSize::MinContent | GenericSize::MaxContent | - GenericSize::MozFitContent | + GenericSize::FitContent | GenericSize::MozAvailable | GenericSize::FitContentFunction(_) => false } diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index c87142ac1b5..955a1b93877 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -44,7 +44,7 @@ pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageSlice, BorderImageWidth}; -pub use self::box_::{AnimationIterationCount, AnimationName, Contain}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain}; pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; @@ -62,7 +62,7 @@ pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis}; 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::image::{Gradient, Image, LineDirection, MozImageRect, ImageRendering}; pub use self::length::{CSSPixelLength, NonNegativeLength}; pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs index dfb3504e3cf..8d2596fffae 100644 --- a/components/style/values/generics/length.rs +++ b/components/style/values/generics/length.rs @@ -157,7 +157,7 @@ pub enum GenericSize { MinContent, #[cfg(feature = "gecko")] #[animation(error)] - MozFitContent, + FitContent, #[cfg(feature = "gecko")] #[animation(error)] MozAvailable, @@ -207,15 +207,13 @@ pub enum GenericMaxSize { None, #[cfg(feature = "gecko")] #[animation(error)] - #[parse(aliases = "-moz-max-content")] MaxContent, #[cfg(feature = "gecko")] #[animation(error)] - #[parse(aliases = "-moz-min-content")] MinContent, #[cfg(feature = "gecko")] #[animation(error)] - MozFitContent, + FitContent, #[cfg(feature = "gecko")] #[animation(error)] MozAvailable, diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 92987f89fe4..604872ba5dc 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -687,7 +687,7 @@ pub trait IsParallelTo { impl ToCss for Rotate where - Number: Copy + ToCss, + Number: Copy + ToCss + Zero, Angle: ToCss, (Number, Number, Number): IsParallelTo, { @@ -700,25 +700,41 @@ where Rotate::None => dest.write_str("none"), Rotate::Rotate(ref angle) => angle.to_css(dest), Rotate::Rotate3D(x, y, z, ref angle) => { - // If a 3d rotation is specified, the property must serialize with an axis - // specified. If the axis is parallel with the x, y, or z axises, it must - // serialize as the appropriate keyword. + // If the axis is parallel with the x or y axes, it must serialize as the + // appropriate keyword. If a rotation about the z axis (that is, in 2D) is + // specified, the property must serialize as just an + // // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization let v = (x, y, z); - if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { - dest.write_char('x')?; + let axis = if x.is_zero() && y.is_zero() && z.is_zero() { + // The zero length vector is parallel to every other vector, so + // is_parallel_to() returns true for it. However, it is definitely different + // from x axis, y axis, or z axis, and it's meaningless to perform a rotation + // using that direction vector. So we *have* to serialize it using that same + // vector - we can't simplify to some theoretically parallel axis-aligned + // vector. + None + } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { + Some("x ") } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { - dest.write_char('y')?; + Some("y ") } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { - dest.write_char('z')?; + // When we're parallel to the z-axis, we can just serialize the angle. + return angle.to_css(dest); } else { - x.to_css(dest)?; - dest.write_char(' ')?; - y.to_css(dest)?; - dest.write_char(' ')?; - z.to_css(dest)?; + None + }; + match axis { + Some(a) => dest.write_str(a)?, + None => { + x.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest)?; + dest.write_char(' ')?; + z.to_css(dest)?; + dest.write_char(' ')?; + } } - dest.write_char(' ')?; angle.to_css(dest) }, } diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 79ec424ab3a..b8e7f1f3b8b 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -464,29 +464,34 @@ impl ToCss for CustomIdent { } } +/// The or . +/// The definition of these two names are the same, so we use the same type for them. +/// +/// /// #[derive( Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] -pub enum KeyframesName { +#[repr(C, u8)] +pub enum TimelineOrKeyframesName { /// Ident(CustomIdent), /// QuotedString(Atom), } -impl KeyframesName { +impl TimelineOrKeyframesName { /// pub fn from_ident(value: &str) -> Self { let location = SourceLocation { line: 0, column: 0 }; let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok(); match custom_ident { - Some(ident) => KeyframesName::Ident(ident), - None => KeyframesName::QuotedString(value.into()), + Some(ident) => Self::Ident(ident), + None => Self::QuotedString(value.into()), } } - /// Create a new KeyframesName from Atom. + /// Create a new TimelineOrKeyframesName from Atom. #[cfg(feature = "gecko")] pub fn from_atom(atom: Atom) -> Self { debug_assert_ne!(atom, atom!("")); @@ -494,19 +499,19 @@ impl KeyframesName { // FIXME: We might want to preserve , but currently Gecko // stores both of and into nsAtom, so // we can't tell it. - KeyframesName::Ident(CustomIdent(atom)) + Self::Ident(CustomIdent(atom)) } /// The name as an Atom pub fn as_atom(&self) -> &Atom { match *self { - KeyframesName::Ident(ref ident) => &ident.0, - KeyframesName::QuotedString(ref atom) => atom, + Self::Ident(ref ident) => &ident.0, + Self::QuotedString(ref atom) => atom, } } } -impl Eq for KeyframesName {} +impl Eq for TimelineOrKeyframesName {} /// A trait that returns whether a given type is the `auto` value or not. So far /// only needed for background-size serialization, which special-cases `auto`. @@ -515,13 +520,13 @@ pub trait IsAuto { fn is_auto(&self) -> bool; } -impl PartialEq for KeyframesName { +impl PartialEq for TimelineOrKeyframesName { fn eq(&self, other: &Self) -> bool { self.as_atom() == other.as_atom() } } -impl hash::Hash for KeyframesName { +impl hash::Hash for TimelineOrKeyframesName { fn hash(&self, state: &mut H) where H: hash::Hasher, @@ -530,32 +535,40 @@ impl hash::Hash for KeyframesName { } } -impl Parse for KeyframesName { +impl Parse for TimelineOrKeyframesName { fn parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let location = input.current_source_location(); match *input.next()? { - Token::Ident(ref s) => Ok(KeyframesName::Ident(CustomIdent::from_ident( + Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident( location, s, &["none"], )?)), - Token::QuotedString(ref s) => Ok(KeyframesName::QuotedString(Atom::from(s.as_ref()))), + Token::QuotedString(ref s) => { + Ok(Self::QuotedString(Atom::from(s.as_ref()))) + }, ref t => Err(location.new_unexpected_token_error(t.clone())), } } } -impl ToCss for KeyframesName { +impl ToCss for TimelineOrKeyframesName { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { - KeyframesName::Ident(ref ident) => ident.to_css(dest), - KeyframesName::QuotedString(ref atom) => atom.to_string().to_css(dest), + Self::Ident(ref ident) => ident.to_css(dest), + Self::QuotedString(ref atom) => atom.to_string().to_css(dest), } } } + +/// The typedef of . +pub type TimelineName = TimelineOrKeyframesName; + +/// The typedef of . +pub type KeyframesName = TimelineOrKeyframesName; diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs index fa60f66507d..08c6b443678 100644 --- a/components/style/values/specified/angle.rs +++ b/components/style/values/specified/angle.rs @@ -130,6 +130,15 @@ impl Angle { } } + /// Creates an angle with the given value in radians. + #[inline] + pub fn from_radians(value: CSSFloat) -> Self { + Angle { + value: AngleDimension::Rad(value), + was_calc: false, + } + } + /// Return `0deg`. pub fn zero() -> Self { Self::from_degrees(0.0, false) @@ -141,6 +150,13 @@ impl Angle { self.value.degrees() } + /// Returns the value of the angle in radians. + #[inline] + pub fn radians(&self) -> CSSFloat { + const RAD_PER_DEG: f32 = PI / 180.0; + self.value.degrees() * RAD_PER_DEG + } + /// Whether this specified angle came from a `calc()` expression. #[inline] pub fn was_calc(&self) -> bool { diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 76dd263e831..7d5543cf7fc 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -13,7 +13,7 @@ use crate::values::generics::box_::Perspective as GenericPerspective; use crate::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword}; use crate::values::specified::length::{LengthPercentage, NonNegativeLength}; use crate::values::specified::{AllowQuirks, Number}; -use crate::values::{CustomIdent, KeyframesName}; +use crate::values::{CustomIdent, KeyframesName, TimelineName}; use crate::Atom; use cssparser::Parser; use num_traits::FromPrimitive; @@ -110,8 +110,6 @@ pub enum DisplayInside { #[cfg(feature = "gecko")] MozBox, #[cfg(feature = "gecko")] - MozStack, - #[cfg(feature = "gecko")] MozDeck, #[cfg(feature = "gecko")] MozPopup, @@ -226,8 +224,6 @@ impl Display { #[cfg(feature = "gecko")] pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox); #[cfg(feature = "gecko")] - pub const MozStack: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozStack); - #[cfg(feature = "gecko")] pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck); #[cfg(feature = "gecko")] pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup); @@ -608,8 +604,6 @@ impl Parse for Display { #[cfg(feature = "gecko")] "-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox, #[cfg(feature = "gecko")] - "-moz-stack" if moz_display_values_enabled(context) => Display::MozStack, - #[cfg(feature = "gecko")] "-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck, #[cfg(feature = "gecko")] "-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup, @@ -768,6 +762,67 @@ impl Parse for AnimationName { } } +/// A value for the . +/// +/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline +/// cbindgen:private-default-tagged-enum-constructor=false +#[derive( + Clone, + Debug, + Eq, + Hash, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum AnimationTimeline { + /// Use default timeline. The animation’s timeline is a DocumentTimeline. + Auto, + /// The animation is not associated with a timeline. + None, + /// The scroll-timeline name + Timeline(TimelineName), +} + +impl AnimationTimeline { + /// Returns the `auto` value. + pub fn auto() -> Self { + Self::Auto + } + + /// Returns true if it is auto (i.e. the default value). + pub fn is_auto(&self) -> bool { + matches!(self, Self::Auto) + } +} + +impl Parse for AnimationTimeline { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + // We are using the same parser for TimelineName and KeyframesName, but animation-timeline + // accepts "auto", so need to manually parse this. (We can not derive Parse because + // TimelineName excludes only "none" keyword.) + // FIXME: Bug 1733260: we may drop None based on the spec issue: + // Note: https://github.com/w3c/csswg-drafts/issues/6674. + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(Self::Auto); + } + + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(Self::None); + } + + TimelineName::parse(context, input).map(AnimationTimeline::Timeline) + } +} + /// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index a22d6fd5f18..a043e4ea96e 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -21,7 +21,7 @@ use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; /// The name of the mathematical function that we're parsing. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Parse)] pub enum MathFunction { /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc Calc, @@ -31,6 +31,18 @@ pub enum MathFunction { Max, /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp Clamp, + /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin + Sin, + /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos + Cos, + /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan + Tan, + /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin + Asin, + /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos + Acos, + /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan + Atan, } /// A leaf node inside a `Calc` expression's AST. @@ -231,6 +243,13 @@ impl generic::CalcNodeLeaf for Leaf { } } +fn trig_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.trig.enabled"); + #[cfg(feature = "servo")] + return false; +} + /// A calc node representation for specified values. pub type CalcNode = generic::GenericCalcNode; @@ -301,6 +320,17 @@ impl CalcNode { let function = CalcNode::math_function(name, location)?; CalcNode::parse(context, input, function, expected_unit) }, + (&Token::Ident(ref ident), _) => { + if !trig_enabled() { + return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))); + } + let number = match_ignore_ascii_case! { &**ident, + "e" => std::f32::consts::E, + "pi" => std::f32::consts::PI, + _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))), + }; + Ok(CalcNode::Leaf(Leaf::Number(number))) + }, (t, _) => Err(location.new_unexpected_token_error(t.clone())), } } @@ -350,6 +380,47 @@ impl CalcNode { Ok(Self::MinMax(arguments.into(), op)) }, + MathFunction::Sin | + MathFunction::Cos | + MathFunction::Tan => { + let argument = Self::parse_argument(context, input, CalcUnit::Angle)?; + let radians = match argument.to_number() { + Ok(v) => v, + Err(()) => match argument.to_angle() { + Ok(angle) => angle.radians(), + Err(()) => return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ), + }, + }; + let number = match function { + MathFunction::Sin => radians.sin(), + MathFunction::Cos => radians.cos(), + MathFunction::Tan => radians.tan(), + _ => unsafe { debug_unreachable!("We just checked!"); }, + }; + Ok(Self::Leaf(Leaf::Number(number))) + }, + MathFunction::Asin | + MathFunction::Acos | + MathFunction::Atan => { + let argument = Self::parse_argument(context, input, CalcUnit::Number)?; + let number = match argument.to_number() { + Ok(v) => v, + Err(()) => return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ), + }; + + let radians = match function { + MathFunction::Asin => number.asin(), + MathFunction::Acos => number.acos(), + MathFunction::Atan => number.atan(), + _ => unsafe { debug_unreachable!("We just checked!"); }, + }; + + Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) + }, } }) } @@ -522,13 +593,18 @@ impl CalcNode { name: &CowRcStr<'i>, location: cssparser::SourceLocation, ) -> Result> { - Ok(match_ignore_ascii_case! { &*name, - "calc" => MathFunction::Calc, - "min" => MathFunction::Min, - "max" => MathFunction::Max, - "clamp" => MathFunction::Clamp, - _ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), - }) + use self::MathFunction::*; + + let function = match MathFunction::from_ident(&*name) { + Ok(f) => f, + Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), + }; + + if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !trig_enabled() { + return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); + } + + Ok(function) } /// Convenience parsing function for integers. diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index f84883702c7..fe22266ab23 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -194,20 +194,6 @@ impl ToCss for ColorMix { } } -/// The color scheme for a specific system color. -#[cfg(feature = "gecko")] -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -#[repr(u8)] -pub enum SystemColorScheme { - /// The default color-scheme for the document. - #[css(skip)] - Default, - /// A light color scheme. - Light, - /// A dark color scheme. - Dark, -} - /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum Color { @@ -222,10 +208,9 @@ pub enum Color { }, /// A complex color value from computed value Complex(ComputedColor), - /// Either a system color, or a `-moz-system-color(, light|dark)` - /// function which allows chrome code to choose between color schemes. + /// A system color. #[cfg(feature = "gecko")] - System(SystemColor, SystemColorScheme), + System(SystemColor), /// A color mix. ColorMix(Box), /// Quirksmode-only rule for inheriting color from the body @@ -233,36 +218,19 @@ pub enum Color { InheritFromBodyQuirk, } -/// System colors. +/// System colors. A bunch of these are ad-hoc, others come from Windows: +/// +/// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor +/// +/// Others are HTML/CSS specific. Spec is: +/// +/// https://drafts.csswg.org/css-color/#css-system-colors +/// https://drafts.csswg.org/css-color/#deprecated-system-colors #[allow(missing_docs)] #[cfg(feature = "gecko")] #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] #[repr(u8)] pub enum SystemColor { - #[css(skip)] - WindowBackground, - #[css(skip)] - WindowForeground, - #[css(skip)] - WidgetBackground, - #[css(skip)] - WidgetForeground, - #[css(skip)] - WidgetSelectBackground, - #[css(skip)] - WidgetSelectForeground, - #[css(skip)] - Widget3DHighlight, - #[css(skip)] - Widget3DShadow, - #[css(skip)] - TextBackground, - #[css(skip)] - TextForeground, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectBackground, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectForeground, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] TextSelectBackgroundDisabled, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] @@ -310,6 +278,7 @@ pub enum SystemColor { #[css(skip)] ThemedScrollbarThumbInactive, Activeborder, + /// Background in the (active) titlebar. Activecaption, Appworkspace, Background, @@ -317,16 +286,23 @@ pub enum SystemColor { Buttonhighlight, Buttonshadow, Buttontext, + /// Text color in the (active) titlebar. Captiontext, #[parse(aliases = "-moz-field")] Field, + /// Used for disabled field backgrounds. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozDisabledfield, #[parse(aliases = "-moz-fieldtext")] Fieldtext, + Graytext, Highlight, Highlighttext, Inactiveborder, + /// Background in the (inactive) titlebar. Inactivecaption, + /// Text color in the (inactive) titlebar. Inactivecaptiontext, Infobackground, Infotext, @@ -351,13 +327,15 @@ pub enum SystemColor { /// Used to highlight valid regions to drop something onto. MozDragtargetzone, /// Used for selected but not focused cell backgrounds. + #[parse(aliases = "-moz-html-cellhighlight")] MozCellhighlight, /// Used for selected but not focused cell text. + #[parse(aliases = "-moz-html-cellhighlighttext")] MozCellhighlighttext, - /// Used for selected but not focused html cell backgrounds. - MozHtmlCellhighlight, - /// Used for selected but not focused html cell text. - MozHtmlCellhighlighttext, + /// Used for selected and focused html cell backgrounds. + Selecteditem, + /// Used for selected and focused html cell text. + Selecteditemtext, /// Used to button text background when hovered. MozButtonhoverface, /// Used to button text color when hovered. @@ -378,10 +356,16 @@ pub enum SystemColor { /// Used for button text when pressed. #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozGtkButtonactivetext, + MozButtonactivetext, + + /// Used for button background when pressed. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozButtonactiveface, + + /// Used for button background when disabled. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozButtondisabledface, - /// Used for button text when pressed. - MozMacButtonactivetext, /// Background color of chrome toolbars in active windows. MozMacChromeActive, /// Background color of chrome toolbars in inactive windows. @@ -423,6 +407,10 @@ pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozAccentColorForeground, + /// The background-color for :autofill-ed inputs. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozAutofillBackground, + /// Media rebar text. MozWinMediatext, /// Communications rebar text. @@ -457,14 +445,6 @@ pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozColheaderhovertext, - /// Color of text in the (active) titlebar. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozGtkTitlebarText, - - /// Color of text in the (inactive) titlebar. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozGtkTitlebarInactiveText, - #[css(skip)] End, // Just for array-indexing purposes. } @@ -472,31 +452,20 @@ pub enum SystemColor { #[cfg(feature = "gecko")] impl SystemColor { #[inline] - fn compute(&self, cx: &Context, scheme: SystemColorScheme) -> ComputedColor { + fn compute(&self, cx: &Context) -> ComputedColor { use crate::gecko_bindings::bindings; - let colors = &cx.device().pref_sheet_prefs().mColors; - let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); - - // TODO: At least Canvas / CanvasText should be color-scheme aware - // (probably the link colors too). - convert_nscolor_to_computedcolor(match *self { - SystemColor::Canvastext => colors.mDefault, - SystemColor::Canvas => colors.mDefaultBackground, - SystemColor::Linktext => colors.mLink, - SystemColor::Activetext => colors.mActiveLink, - SystemColor::Visitedtext => colors.mVisitedLink, - - _ => { - let color = unsafe { - bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme) - }; - if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { - return ComputedColor::currentcolor(); - } - color - }, - }) + // TODO: We should avoid cloning here most likely, though it's + // cheap-ish. + let style_color_scheme = + cx.style().get_inherited_ui().clone_color_scheme(); + let color = unsafe { + bindings::Gecko_ComputeSystemColor(*self, cx.device().document(), &style_color_scheme) + }; + if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { + return ComputedColor::currentcolor(); + } + convert_nscolor_to_computedcolor(color) } } @@ -570,21 +539,6 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen } } -#[cfg(feature = "gecko")] -fn parse_moz_system_color<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<(SystemColor, SystemColorScheme), ParseError<'i>> { - debug_assert!(context.chrome_rules_enabled()); - input.expect_function_matching("-moz-system-color")?; - input.parse_nested_block(|input| { - let color = SystemColor::parse(context, input)?; - input.expect_comma()?; - let scheme = SystemColorScheme::parse(input)?; - Ok((color, scheme)) - }) -} - impl Parse for Color { fn parse<'i, 't>( context: &ParserContext, @@ -610,15 +564,7 @@ impl Parse for Color { #[cfg(feature = "gecko")] { if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) { - return Ok(Color::System(system, SystemColorScheme::Default)); - } - - if context.chrome_rules_enabled() { - if let Ok((color, scheme)) = - input.try_parse(|i| parse_moz_system_color(context, i)) - { - return Ok(Color::System(color, scheme)); - } + return Ok(Color::System(system)); } } @@ -657,17 +603,7 @@ impl ToCss for Color { Color::Complex(_) => Ok(()), Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] - Color::System(system, scheme) => { - if scheme == SystemColorScheme::Default { - system.to_css(dest) - } else { - dest.write_str("-moz-system-color(")?; - system.to_css(dest)?; - dest.write_str(", ")?; - scheme.to_css(dest)?; - dest.write_char(')') - } - }, + Color::System(system) => system.to_css(dest), #[cfg(feature = "gecko")] Color::InheritFromBodyQuirk => Ok(()), } @@ -835,7 +771,7 @@ impl Color { )) }, #[cfg(feature = "gecko")] - Color::System(system, scheme) => system.compute(context?, scheme), + Color::System(system) => system.compute(context?), #[cfg(feature = "gecko")] Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()), }) @@ -995,6 +931,11 @@ impl ColorScheme { bits: ColorSchemeFlags::empty(), } } + + /// Returns the raw bitfield. + pub fn raw_bits(&self) -> u8 { + self.bits.bits + } } impl Parse for ColorScheme { diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs index 3e3085c8849..9b01cc2a6c4 100644 --- a/components/style/values/specified/gecko.rs +++ b/components/style/values/specified/gecko.rs @@ -49,6 +49,11 @@ impl Parse for IntersectionObserverRootMargin { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { + use crate::Zero; + if input.is_exhausted() { + // If there are zero elements in tokens, set tokens to ["0px"]. + return Ok(IntersectionObserverRootMargin(Rect::all(LengthPercentage::zero()))); + } let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?; Ok(IntersectionObserverRootMargin(rect)) } diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index f2167e52800..095ce20f4c7 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -1226,3 +1226,42 @@ impl MozImageRect { }) } } + +/// https://drafts.csswg.org/css-images/#propdef-image-rendering +#[allow(missing_docs)] +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ImageRendering { + Auto, + #[cfg(feature = "gecko")] + Smooth, + #[parse(aliases = "-moz-crisp-edges")] + CrispEdges, + Pixelated, + // From the spec: + // + // This property previously accepted the values optimizeSpeed and + // optimizeQuality. These are now deprecated; a user agent must accept + // them as valid values but must treat them as having the same behavior + // as crisp-edges and smooth respectively, and authors must not use + // them. + // + #[cfg(feature = "gecko")] + Optimizespeed, + #[cfg(feature = "gecko")] + Optimizequality, +} diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index e73a9ddfde0..6105155d81d 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -1235,7 +1235,7 @@ macro_rules! parse_size_non_length { #[cfg(feature = "gecko")] "max-content" | "-moz-max-content" => $size::MaxContent, #[cfg(feature = "gecko")] - "-moz-fit-content" => $size::MozFitContent, + "fit-content" | "-moz-fit-content" => $size::FitContent, #[cfg(feature = "gecko")] "-moz-available" => $size::MozAvailable, $auto_or_none => $size::$auto_or_none_ident, diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 6cd952cd82f..64bc063960e 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -36,7 +36,7 @@ pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; -pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display}; pub use self::box_::{Appearance, BreakBetween, BreakWithin}; pub use self::box_::{Clear, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; @@ -55,7 +55,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::{EndingShape as GradientEndingShape, Gradient}; -pub use self::image::{Image, MozImageRect}; +pub use self::image::{Image, MozImageRect, ImageRendering}; pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; diff --git a/servo-tidy.toml b/servo-tidy.toml index da2ca334ac9..b22afbf5ecf 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -82,7 +82,12 @@ files = [ # These are ignored to avoid diverging from Gecko "./components/style/counter_style/mod.rs", "./components/style/properties/helpers.mako.rs", + "./components/style/rule_collector.rs", + "./components/style/selector_map.rs", + "./components/style/stylesheets/import_rule.rs", + "./components/style/stylesheets/layer_rule.rs", "./components/style/stylesheets/rule_parser.rs", + "./components/style/stylesheets/scroll_timeline_rule.rs", "./components/style/stylist.rs", "./components/style/values/computed/font.rs", "./components/style/values/computed/image.rs", diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 31b7869a258..c89a401bbc0 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -15,6 +15,7 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::selector_map::SelectorMap; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::shared_lock::SharedRwLock; +use style::stylesheets::layer_rule::LayerId; use style::stylesheets::StyleRule; use style::stylist::needs_revalidation_for_testing; use style::stylist::{Rule, Stylist}; @@ -52,6 +53,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { AncestorHashes::new(s, QuirksMode::NoQuirks), locked.clone(), i as u32, + LayerId::root(), ) }) .collect() diff --git a/tests/wpt/metadata/css/css-transforms/animation/rotate-interpolation.html.ini b/tests/wpt/metadata/css/css-transforms/animation/rotate-interpolation.html.ini index 8590593db04..75fd404ab49 100644 --- a/tests/wpt/metadata/css/css-transforms/animation/rotate-interpolation.html.ini +++ b/tests/wpt/metadata/css/css-transforms/animation/rotate-interpolation.html.ini @@ -344,14 +344,5 @@ [Web Animations: property from [1 -2.5 3.64 100deg\] to [1 -2.5 3.64 -100deg\] at (1) should be [0.22 -0.55 0.8 -100deg\]] expected: FAIL - [CSS Transitions: property from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]] - expected: FAIL - - [CSS Animations: property from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]] - expected: FAIL - [Web Animations: property from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]] expected: FAIL diff --git a/tests/wpt/metadata/css/css-transforms/parsing/rotate-parsing-valid.html.ini b/tests/wpt/metadata/css/css-transforms/parsing/rotate-parsing-valid.html.ini deleted file mode 100644 index b7662bdbf26..00000000000 --- a/tests/wpt/metadata/css/css-transforms/parsing/rotate-parsing-valid.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[rotate-parsing-valid.html] - [e.style['rotate'\] = "0 0 0 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "400grad z" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "0 0 0.5 400grad" should set the property value] - expected: FAIL - - [e.style['rotate'\] = "0 0 1 400grad" should set the property value] - expected: FAIL diff --git a/tests/wpt/metadata/css/cssom/shorthand-values.html.ini b/tests/wpt/metadata/css/cssom/shorthand-values.html.ini index badceaee7fe..a41c085aee4 100644 --- a/tests/wpt/metadata/css/cssom/shorthand-values.html.ini +++ b/tests/wpt/metadata/css/cssom/shorthand-values.html.ini @@ -29,8 +29,5 @@ [The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.] expected: FAIL - [The serialization of list-style-type: circle; list-style-position: inside; list-style-image: none; should be canonical.] - expected: FAIL - [The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical.] expected: FAIL