Auto merge of #29816 - Loirooriol:sync, r=mrobinson

Backport several style changes from Gecko (3)

<!-- Please describe your changes on the following line: -->
This continues https://github.com/servo/servo/pull/29772.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [ ] These changes fix #___ (GitHub issue number if applicable)

<!-- Either: -->
- [X] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
This commit is contained in:
bors-servo 2023-05-31 20:44:42 +02:00 committed by GitHub
commit 7399a3a686
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2128 additions and 723 deletions

View file

@ -1,3 +1,5 @@
-moz-gtk-csd-titlebar-radius
-moz-gtk-menu-radius
DOMContentLoaded DOMContentLoaded
abort abort
activate activate

View file

@ -105,6 +105,8 @@ impl CSSRule {
}, },
StyleCssRule::Page(_) => unreachable!(), StyleCssRule::Page(_) => unreachable!(),
StyleCssRule::Document(_) => unimplemented!(), // TODO StyleCssRule::Document(_) => unimplemented!(), // TODO
StyleCssRule::Layer(_) => unimplemented!(), // TODO
StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO
} }
} }

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::dom::attr::AttrHelpersForLayout;
use crate::dom::bindings::inheritance::{ use crate::dom::bindings::inheritance::{
CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId,
}; };
@ -264,6 +265,16 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement
} }
} }
#[inline(always)]
fn each_attr_name<F>(&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 { fn has_dirty_descendants(&self) -> bool {
unsafe { unsafe {
self.as_node() self.as_node()

View file

@ -38,7 +38,7 @@ use std::sync::Mutex;
use style::media_queries::MediaList; use style::media_queries::MediaList;
use style::parser::ParserContext; use style::parser::ParserContext;
use style::shared_lock::{Locked, SharedRwLock}; 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::StylesheetLoader as StyleStylesheetLoader;
use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents}; use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents};
use style::values::CssUrl; use style::values::CssUrl;
@ -358,6 +358,7 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
context: &ParserContext, context: &ParserContext,
lock: &SharedRwLock, lock: &SharedRwLock,
media: Arc<Locked<MediaList>>, media: Arc<Locked<MediaList>>,
layer: Option<ImportLayer>,
) -> Arc<Locked<ImportRule>> { ) -> Arc<Locked<ImportRule>> {
let sheet = Arc::new(Stylesheet { let sheet = Arc::new(Stylesheet {
contents: StylesheetContents::from_shared_data( contents: StylesheetContents::from_shared_data(
@ -374,8 +375,9 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
let stylesheet = ImportSheet(sheet.clone()); let stylesheet = ImportSheet(sheet.clone());
let import = ImportRule { let import = ImportRule {
url, url,
source_location,
stylesheet, stylesheet,
layer,
source_location,
}; };
let url = match import.url.url().cloned() { let url = match import.url.url().cloned() {

View file

@ -6,6 +6,7 @@
use crate::properties::PropertyDeclarationBlock; use crate::properties::PropertyDeclarationBlock;
use crate::rule_tree::{CascadeLevel, StyleSource}; use crate::rule_tree::{CascadeLevel, StyleSource};
use crate::stylesheets::layer_rule::LayerOrder;
use crate::shared_lock::Locked; use crate::shared_lock::Locked;
use servo_arc::Arc; use servo_arc::Arc;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -69,8 +70,10 @@ pub struct ApplicableDeclarationBlock {
/// The bits containing the source order, cascade level, and shadow cascade /// The bits containing the source order, cascade level, and shadow cascade
/// order. /// order.
bits: ApplicableDeclarationBits, bits: ApplicableDeclarationBits,
/// The specificity of the selector this block is represented by. /// The specificity of the selector.
pub specificity: u32, pub specificity: u32,
/// The layer order of the selector.
pub layer_order: LayerOrder,
} }
impl ApplicableDeclarationBlock { impl ApplicableDeclarationBlock {
@ -85,16 +88,24 @@ impl ApplicableDeclarationBlock {
source: StyleSource::from_declarations(declarations), source: StyleSource::from_declarations(declarations),
bits: ApplicableDeclarationBits::new(0, level), bits: ApplicableDeclarationBits::new(0, level),
specificity: 0, 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] #[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 { ApplicableDeclarationBlock {
source, source,
bits: ApplicableDeclarationBits::new(order, level), bits: ApplicableDeclarationBits::new(source_order, level),
specificity, specificity,
layer_order,
} }
} }

View file

@ -8,6 +8,7 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use crate::dom::{SendElement, TElement}; use crate::dom::{SendElement, TElement};
use crate::LocalName;
use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use owning_ref::OwningHandle; use owning_ref::OwningHandle;
use selectors::bloom::BloomFilter; use selectors::bloom::BloomFilter;
@ -107,9 +108,8 @@ impl<E: TElement> PushedElement<E> {
/// We do this for attributes that are very common but not commonly used in /// We do this for attributes that are very common but not commonly used in
/// selectors. /// selectors.
#[inline] #[inline]
#[cfg(feature = "gecko")] pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool {
pub fn is_attr_name_excluded_from_filter(atom: &crate::Atom) -> bool { return *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style")
*atom == atom!("class") || *atom == atom!("id") || *atom == atom!("style")
} }
fn each_relevant_element_hash<E, F>(element: E, mut f: F) fn each_relevant_element_hash<E, F>(element: E, mut f: F)
@ -126,15 +126,12 @@ where
element.each_class(|class| f(class.get_hash())); 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| { element.each_attr_name(|name| {
if !is_attr_name_excluded_from_filter(name) { if !is_attr_name_excluded_from_filter(name) {
f(name.get_hash()) f(name.get_hash())
} }
}); });
} }
}
impl<E: TElement> Drop for StyleBloom<E> { impl<E: TElement> Drop for StyleBloom<E> {
fn drop(&mut self) { fn drop(&mut self) {

View file

@ -71,10 +71,29 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), 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 { impl CssEnvironment {
#[inline] #[inline]
fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> { fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> {
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)) Some((var.evaluator)(device))
} }
} }

View file

@ -520,10 +520,9 @@ pub trait TElement:
} }
/// Internal iterator for the attribute names of this element. /// Internal iterator for the attribute names of this element.
#[cfg(feature = "gecko")]
fn each_attr_name<F>(&self, callback: F) fn each_attr_name<F>(&self, callback: F)
where where
F: FnMut(&AtomIdent); F: FnMut(&LocalName);
/// Internal iterator for the part names that this element exports for a /// Internal iterator for the part names that this element exports for a
/// given part name. /// given part name.

View file

@ -9,34 +9,24 @@
#![allow(non_snake_case, missing_docs)] #![allow(non_snake_case, missing_docs)]
use crate::gecko::url::CssUrlData; use crate::gecko::url::CssUrlData;
use crate::gecko_bindings::structs::RawServoAnimationValue; use crate::gecko_bindings::structs::{
use crate::gecko_bindings::structs::RawServoCounterStyleRule; RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, RawServoDeclarationBlock,
use crate::gecko_bindings::structs::RawServoCssUrlData; RawServoFontFaceRule, RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe,
use crate::gecko_bindings::structs::RawServoDeclarationBlock; RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, RawServoMediaRule,
use crate::gecko_bindings::structs::RawServoFontFaceRule; RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule,
use crate::gecko_bindings::structs::RawServoFontFeatureValuesRule; RawServoStyleRule, RawServoStyleSheetContents, RawServoSupportsRule, ServoCssRules,
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::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
use crate::media_queries::MediaList; use crate::media_queries::MediaList;
use crate::properties::animated_properties::AnimationValue; use crate::properties::animated_properties::AnimationValue;
use crate::properties::{ComputedValues, PropertyDeclarationBlock}; use crate::properties::{ComputedValues, PropertyDeclarationBlock};
use crate::shared_lock::Locked; use crate::shared_lock::Locked;
use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::keyframes_rule::Keyframe;
use crate::stylesheets::{CounterStyleRule, CssRules, FontFaceRule, FontFeatureValuesRule}; use crate::stylesheets::{
use crate::stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule}; CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
use crate::stylesheets::{NamespaceRule, PageRule}; KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, ScrollTimelineRule, StyleRule,
use crate::stylesheets::{StyleRule, StylesheetContents, SupportsRule}; StylesheetContents, SupportsRule,
};
use servo_arc::{Arc, ArcBorrow}; use servo_arc::{Arc, ArcBorrow};
use std::{mem, ptr}; use std::{mem, ptr};
@ -83,6 +73,9 @@ impl_arc_ffi!(Locked<Keyframe> => RawServoKeyframe
impl_arc_ffi!(Locked<KeyframesRule> => RawServoKeyframesRule impl_arc_ffi!(Locked<KeyframesRule> => RawServoKeyframesRule
[Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]); [Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]);
impl_arc_ffi!(Locked<LayerRule> => RawServoLayerRule
[Servo_LayerRule_AddRef, Servo_LayerRule_Release]);
impl_arc_ffi!(Locked<MediaList> => RawServoMediaList impl_arc_ffi!(Locked<MediaList> => RawServoMediaList
[Servo_MediaList_AddRef, Servo_MediaList_Release]); [Servo_MediaList_AddRef, Servo_MediaList_Release]);
@ -95,6 +88,9 @@ impl_arc_ffi!(Locked<NamespaceRule> => RawServoNamespaceRule
impl_arc_ffi!(Locked<PageRule> => RawServoPageRule impl_arc_ffi!(Locked<PageRule> => RawServoPageRule
[Servo_PageRule_AddRef, Servo_PageRule_Release]); [Servo_PageRule_AddRef, Servo_PageRule_Release]);
impl_arc_ffi!(Locked<ScrollTimelineRule> => RawServoScrollTimelineRule
[Servo_ScrollTimelineRule_AddRef, Servo_ScrollTimelineRule_Release]);
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]); [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);

View file

@ -406,45 +406,6 @@ fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorSc
} }
} }
/// Values for the -moz-toolbar-prefers-color-scheme media feature.
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
#[repr(u8)]
enum ToolbarPrefersColorScheme {
Dark,
Light,
System,
}
/// The color-scheme of the toolbar in the current Firefox theme. This is based
/// on a pref managed by the front-end.
fn eval_toolbar_prefers_color_scheme(d: &Device, query_value: Option<ToolbarPrefersColorScheme>) -> bool {
let toolbar_value = match static_prefs::pref!("browser.theme.toolbar-theme") {
0 => ToolbarPrefersColorScheme::Dark,
1 => ToolbarPrefersColorScheme::Light,
_ => ToolbarPrefersColorScheme::System,
};
let query_value = match query_value {
Some(v) => v,
None => return true,
};
if query_value == toolbar_value {
return true;
}
if toolbar_value != ToolbarPrefersColorScheme::System {
return false;
}
// System might match light and dark as well.
match query_value {
ToolbarPrefersColorScheme::Dark => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Dark)),
ToolbarPrefersColorScheme::Light => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Light)),
ToolbarPrefersColorScheme::System => true,
}
}
bitflags! { bitflags! {
/// https://drafts.csswg.org/mediaqueries-4/#mf-interaction /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
struct PointerCapabilities: u8 { 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) query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus)
} }
fn eval_moz_overlay_scrollbars(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> 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 { fn get_lnf_int(int_id: i32) -> i32 {
unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } 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 /// to support new types in these entries and (2) ensuring that either
/// nsPresContext::MediaFeatureValuesChanged is called when the value that /// nsPresContext::MediaFeatureValuesChanged is called when the value that
/// would be returned by the evaluator function could change. /// would be returned by the evaluator function could change.
pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
feature!( feature!(
atom!("width"), atom!("width"),
AllowsRanges::Yes, AllowsRanges::Yes,
@ -890,37 +861,35 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [
Evaluator::BoolInteger(eval_moz_non_native_content_theme), Evaluator::BoolInteger(eval_moz_non_native_content_theme),
ParsingRequirements::CHROME_AND_UA_ONLY, 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!( feature!(
atom!("-moz-windows-non-native-menus"), atom!("-moz-windows-non-native-menus"),
AllowsRanges::No, AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_windows_non_native_menus), Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
ParsingRequirements::CHROME_AND_UA_ONLY, 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-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-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-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-end-forward"), ScrollArrowStyle, get_scrollbar_end_forward),
lnf_int_feature!(atom!("-moz-scrollbar-thumb-proportional"), ScrollSliderStyle), 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-menubar-drag"), MenuBarDrag),
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), 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-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-accent-color-in-titlebar"), WindowsAccentColorInTitlebar),
lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor),
lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic),
lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass),
lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled), 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-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-minimize-button"), GTKCSDMinimizeButton),
lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton),
lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton),

View file

@ -93,7 +93,9 @@ impl Device {
document, document,
default_values: ComputedValues::default_values(doc), default_values: ComputedValues::default_values(doc),
root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), 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 <body>, 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_root_font_size: AtomicBool::new(false),
used_font_metrics: AtomicBool::new(false), used_font_metrics: AtomicBool::new(false),
used_viewport_size: AtomicBool::new(false), used_viewport_size: AtomicBool::new(false),
@ -386,13 +388,18 @@ impl Device {
} }
/// Returns the default background color. /// 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. /// 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. /// Returns the current effective text zoom.
@ -439,4 +446,24 @@ impl Device {
bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32) 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
}
} }

View file

@ -249,7 +249,6 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type NonTSPseudoClass = NonTSPseudoClass; type NonTSPseudoClass = NonTSPseudoClass;
fn should_collect_attr_hash(name: &AtomIdent) -> bool { 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)
} }
} }

View file

@ -1016,7 +1016,7 @@ impl<'le> TElement for GeckoElement<'le> {
#[inline] #[inline]
fn namespace(&self) -> &WeakNamespace { fn namespace(&self) -> &WeakNamespace {
unsafe { 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) WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr)
} }
} }

View file

@ -543,6 +543,7 @@ impl StylesheetInvalidationSet {
FontFeatureValues(..) | FontFeatureValues(..) |
FontFace(..) | FontFace(..) |
Keyframes(..) | Keyframes(..) |
ScrollTimeline(..) |
Style(..) => { Style(..) => {
if is_generic_change { if is_generic_change {
// TODO(emilio): We need to do this for selector / keyframe // 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) self.collect_invalidations_for_rule(rule, guard, device, quirks_mode)
}, },
Document(..) | Import(..) | Media(..) | Supports(..) => { Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => {
if !is_generic_change && if !is_generic_change &&
!EffectiveRules::is_effective(guard, device, quirks_mode, rule) !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 // Do nothing, relevant nested rules are visited as part of the
// iteration. // iteration.
}, },
@ -618,6 +619,10 @@ impl StylesheetInvalidationSet {
// existing elements. // existing elements.
} }
}, },
ScrollTimeline(..) => {
// TODO: Bug 1676784: check if animation-timeline name is referenced.
// Now we do nothing.
},
CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => { CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => {
debug!( debug!(
" > Found unsupported rule, marking the whole subtree \ " > Found unsupported rule, marking the whole subtree \

View file

@ -430,7 +430,7 @@ fn tweak_when_ignoring_colors(
// widget background color's rgb channels but not alpha... // widget background color's rgb channels but not alpha...
let alpha = alpha_channel(color, context); let alpha = alpha_channel(color, context);
if alpha != 0 { 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; color.alpha = alpha;
declarations_to_apply_unless_overriden declarations_to_apply_unless_overriden
.push(PropertyDeclaration::BackgroundColor(color.into())) .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 // override this with a non-transparent color, then override it with
// the default color. Otherwise just let it inherit through. // the default color. Otherwise just let it inherit through.
if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { 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( declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color(
specified::ColorPropertyValue(color.into()), specified::ColorPropertyValue(color.into()),
)) ))
@ -794,12 +794,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
{ {
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); 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 if self
.author_specified .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 // 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 // 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 // Note that the border/background properties are non-inherited, so we
// need to do anything else other than just copying the bits over. // 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; // When using this optimization, we also need to copy whether the old
builder.add_flags(cached_style.flags & reset_props_bits); // 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 true
} }
@ -916,12 +917,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
// we have a generic family to actually replace it with. // we have a generic family to actually replace it with.
let prioritize_user_fonts = !use_document_fonts && let prioritize_user_fonts = !use_document_fonts &&
default_font_type != GenericFontFamily::None && default_font_type != GenericFontFamily::None &&
matches!( !generic.valid_for_user_font_prioritization();
generic,
GenericFontFamily::None |
GenericFontFamily::Fantasy |
GenericFontFamily::Cursive
);
if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback { if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback {
// Nothing to do. // Nothing to do.

View file

@ -84,12 +84,6 @@ bitflags! {
/// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845 /// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845
const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14; 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`. /// Whether there are author-specified rules for `font-family`.
const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16; const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16;

View file

@ -476,6 +476,7 @@ class Longhand(Property):
"FontWeight", "FontWeight",
"GreaterThanOrEqualToOneNumber", "GreaterThanOrEqualToOneNumber",
"GridAutoFlow", "GridAutoFlow",
"ImageRendering",
"InitialLetter", "InitialLetter",
"Integer", "Integer",
"JustifyContent", "JustifyContent",

View file

@ -1049,7 +1049,7 @@ fn static_assert() {
% if member: % if member:
ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member}; ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member};
% else: % else:
ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}; ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone();
% endif % endif
} }
} }
@ -1183,7 +1183,7 @@ fn static_assert() {
<% skip_box_longhands= """display <% skip_box_longhands= """display
animation-name animation-delay animation-duration animation-name animation-delay animation-duration
animation-direction animation-fill-mode animation-play-state 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 clear transition-duration transition-delay
transition-timing-function transition-property transition-timing-function transition-property
-webkit-line-clamp""" %> -webkit-line-clamp""" %>
@ -1445,6 +1445,27 @@ fn static_assert() {
${impl_copy_animation_value('iteration_count', 'IterationCount')} ${impl_copy_animation_value('iteration_count', 'IterationCount')}
${impl_animation_or_transition_timing_function('animation')} ${impl_animation_or_transition_timing_function('animation')}
pub fn set_animation_timeline<I>(&mut self, v: I)
where
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
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)] #[allow(non_snake_case)]
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) { pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
self.gecko.mLineClamp = match v { self.gecko.mLineClamp = match v {

View file

@ -320,6 +320,22 @@ ${helpers.predefined_type(
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, 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" %> <% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
${helpers.predefined_type( ${helpers.predefined_type(

View file

@ -69,17 +69,13 @@ ${helpers.single_keyword(
// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` // 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) // And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337)
${helpers.single_keyword( ${helpers.predefined_type(
"image-rendering", "image-rendering",
"auto crisp-edges", "ImageRendering",
"computed::ImageRendering::Auto",
engines="gecko servo-2013 servo-2020", 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", spec="https://drafts.csswg.org/css-images/#propdef-image-rendering",
animation_value_type="discrete",
)} )}
${helpers.single_keyword( ${helpers.single_keyword(

View file

@ -104,6 +104,8 @@ ${helpers.predefined_type(
gecko_pref="layout.css.color-scheme.enabled", gecko_pref="layout.css.color-scheme.enabled",
animation_value_type="discrete", animation_value_type="discrete",
has_effect_on_gecko_scrollbars=False, has_effect_on_gecko_scrollbars=False,
ignored_when_colors_disabled=True,
enabled_in="chrome",
)} )}
${helpers.predefined_type( ${helpers.predefined_type(

View file

@ -945,18 +945,6 @@ impl LonghandIdSet {
&HAS_NO_EFFECT_ON_SCROLLBARS &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 /// Returns the set of border properties for the purpose of disabling native
/// appearance. /// appearance.
#[inline] #[inline]
@ -2921,7 +2909,7 @@ pub mod style_structs {
} }
/// Returns true if animation properties are equal between styles, but without /// Returns true if animation properties are equal between styles, but without
/// considering keyframe data. /// considering keyframe data and animation-timeline.
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
pub fn animations_equals(&self, other: &Self) -> bool { pub fn animations_equals(&self, other: &Self) -> bool {
self.animation_name_iter().eq(other.animation_name_iter()) && 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 /// Note that the value will usually be the computed value, except for
/// colors, where it's resolved. /// colors, where it's resolved.
pub fn get_longhand_property_value<W>( ///
/// TODO(emilio): We should move all the special resolution from
/// nsComputedDOMStyle to ToResolvedValue instead.
pub fn get_resolved_value(
&self, &self,
property_id: LonghandId, property_id: LonghandId,
dest: &mut CssWriter<W> dest: &mut CssStringWriter,
) -> fmt::Result ) -> fmt::Result {
where
W: Write,
{
use crate::values::resolved::ToResolvedValue; 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 { let context = resolved::Context {
style: self, style: self,
}; };
// TODO(emilio): Is it worth to merge branches here just like
// PropertyDeclaration::to_css does?
match property_id { match property_id {
% for prop in data.longhands: % for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()):
LonghandId::${prop.camel_case} => { <% props = list(props) %>
let value = self.clone_${prop.ident}(); ${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => {
value.to_resolved_value(&context).to_css(dest) 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 % endfor
} }
@ -3195,9 +3232,9 @@ impl ComputedValues {
match property { match property {
PropertyDeclarationId::Longhand(id) => { PropertyDeclarationId::Longhand(id) => {
let mut s = String::new(); let mut s = String::new();
self.get_longhand_property_value( self.get_resolved_value(
id, id,
&mut CssWriter::new(&mut s) &mut s
).unwrap(); ).unwrap();
s s
} }

View file

@ -189,11 +189,11 @@ macro_rules! try_parse_one {
sub_properties="animation-name animation-duration sub_properties="animation-name animation-duration
animation-timing-function animation-delay animation-timing-function animation-delay
animation-iteration-count animation-direction animation-iteration-count animation-direction
animation-fill-mode animation-play-state" animation-fill-mode animation-play-state animation-timeline"
rule_types_allowed="Style" rule_types_allowed="Style"
spec="https://drafts.csswg.org/css-animations/#propdef-animation"> 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() direction fill_mode play_state".split()
%> %>
% for prop in props: % for prop in props:
@ -210,6 +210,13 @@ macro_rules! try_parse_one {
% endfor % 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>( fn parse_one_animation<'i, 't>(
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, 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, fill_mode, animation_fill_mode);
try_parse_one!(context, input, play_state, animation_play_state); try_parse_one!(context, input, play_state, animation_play_state);
try_parse_one!(context, input, name, animation_name); try_parse_one!(context, input, name, animation_name);
if scroll_linked_animations_enabled() {
try_parse_one!(context, input, timeline, animation_timeline);
}
parsed -= 1; parsed -= 1;
break 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 // If any value list length is differs then we don't do a shorthand serialization
// either. // either.
% for name in props[1:]: % for name in props[2:]:
if len != self.animation_${name}.0.len() { if len != self.animation_${name}.0.len() {
return Ok(()) return Ok(())
} }
% endfor % 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 { for i in 0..len {
if i != 0 { if i != 0 {
dest.write_str(", ")?; dest.write_str(", ")?;
} }
% for name in props[1:]: % for name in props[2:]:
self.animation_${name}.0[i].to_css(dest)?; self.animation_${name}.0[i].to_css(dest)?;
dest.write_str(" ")?; dest.write_str(" ")?;
% endfor % endfor
self.animation_name.0[i].to_css(dest)?; 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(()) Ok(())
} }

View file

@ -7,7 +7,6 @@
<%helpers:shorthand name="list-style" <%helpers:shorthand name="list-style"
engines="gecko servo-2013 servo-2020" engines="gecko servo-2013 servo-2020"
sub_properties="list-style-position list-style-image list-style-type" sub_properties="list-style-position list-style-image list-style-type"
derive_serialize="True"
spec="https://drafts.csswg.org/css-lists/#propdef-list-style"> 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::properties::longhands::{list_style_image, list_style_position, list_style_type};
use crate::values::specified::Image; use crate::values::specified::Image;
@ -104,4 +103,43 @@
_ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
} }
} }
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> 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(())
}
}
</%helpers:shorthand> </%helpers:shorthand>

View file

@ -89,6 +89,14 @@
} }
% if engine == "gecko": % 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 !is_solid_style {
if has_value { if has_value {
dest.write_str(" ")?; dest.write_str(" ")?;
@ -104,13 +112,6 @@
self.text_decoration_color.to_css(dest)?; self.text_decoration_color.to_css(dest)?;
has_value = true; has_value = true;
} }
if !is_auto_thickness {
if has_value {
dest.write_str(" ")?;
}
self.text_decoration_thickness.to_css(dest)?;
}
% endif % endif
Ok(()) Ok(())

View file

@ -12,7 +12,7 @@ use crate::selector_map::SelectorMap;
use crate::selector_parser::PseudoElement; use crate::selector_parser::PseudoElement;
use crate::shared_lock::Locked; use crate::shared_lock::Locked;
use crate::stylesheets::Origin; 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 selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use servo_arc::ArcBorrow; use servo_arc::ArcBorrow;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -148,7 +148,7 @@ where
f(self); f(self);
if start != self.rules.len() { if start != self.rules.len() {
self.rules[start..] 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.context.current_host = old_host;
self.in_sort_scope = false; self.in_sort_scope = false;
@ -173,7 +173,7 @@ where
}; };
self.in_tree(None, |collector| { 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] #[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"); debug_assert!(self.in_sort_scope, "Rules gotta be sorted");
SelectorMap::get_matching_rules( SelectorMap::get_matching_rules(
self.element, self.element,
@ -223,11 +223,12 @@ where
&mut self.context, &mut self.context,
&mut self.flags_setter, &mut self.flags_setter,
cascade_level, cascade_level,
cascade_data,
); );
} }
#[inline] #[inline]
fn collect_rules_in_map(&mut self, map: &SelectorMap<Rule>, cascade_level: CascadeLevel) { fn collect_rules_in_map(&mut self, map: &SelectorMap<Rule>, cascade_level: CascadeLevel, cascade_data: &CascadeData) {
debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); debug_assert!(self.in_sort_scope, "Rules gotta be sorted");
map.get_all_matching_rules( map.get_all_matching_rules(
self.element, self.element,
@ -236,6 +237,7 @@ where
&mut self.context, &mut self.context,
&mut self.flags_setter, &mut self.flags_setter,
cascade_level, cascade_level,
cascade_data,
); );
} }
@ -277,7 +279,7 @@ where
let cascade_level = CascadeLevel::AuthorNormal { let cascade_level = CascadeLevel::AuthorNormal {
shadow_cascade_order, 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(); let cascade_level = CascadeLevel::same_tree_author_normal();
self.in_shadow_tree(containing_shadow.host(), |collector| { self.in_shadow_tree(containing_shadow.host(), |collector| {
if let Some(map) = cascade_data.normal_rules(collector.pseudo_element) { 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 // Collect rules from :host::part() and such
@ -319,7 +321,7 @@ where
hash_target.each_part(|part| { hash_target.each_part(|part| {
if let Some(part_rules) = part_rules.get(&part.0) { 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 { let cascade_level = CascadeLevel::AuthorNormal {
shadow_cascade_order, shadow_cascade_order,
}; };
collector.collect_rules_in_map(host_rules, cascade_level); collector.collect_rules_in_map(host_rules, cascade_level, style_data);
}); });
} }
@ -386,18 +388,17 @@ where
let inner_shadow_host = inner_shadow.host(); let inner_shadow_host = inner_shadow.host();
let outer_shadow = inner_shadow_host.containing_shadow(); let outer_shadow = inner_shadow_host.containing_shadow();
let part_rules = match outer_shadow { let cascade_data = match outer_shadow {
Some(shadow) => shadow Some(shadow) => shadow.style_data(),
.style_data() None => Some(self
.and_then(|data| data.part_rules(self.pseudo_element)),
None => self
.stylist .stylist
.cascade_data() .cascade_data()
.borrow_for_origin(Origin::Author) .borrow_for_origin(Origin::Author)
.part_rules(self.pseudo_element), ),
}; };
if let Some(part_rules) = part_rules { 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 containing_host = outer_shadow.map(|s| s.host());
let cascade_level = CascadeLevel::AuthorNormal { let cascade_level = CascadeLevel::AuthorNormal {
shadow_cascade_order, shadow_cascade_order,
@ -405,12 +406,13 @@ where
self.in_tree(containing_host, |collector| { self.in_tree(containing_host, |collector| {
for p in &parts { for p in &parts {
if let Some(part_rules) = part_rules.get(&p.0) { if let Some(part_rules) = part_rules.get(&p.0) {
collector.collect_rules_in_list(part_rules, cascade_level); collector.collect_rules_in_list(part_rules, cascade_level, cascade_data);
} }
} }
}); });
shadow_cascade_order.inc(); shadow_cascade_order.inc();
} }
}
inner_shadow = match outer_shadow { inner_shadow = match outer_shadow {
Some(s) => s, Some(s) => s,

View file

@ -12,7 +12,7 @@ use crate::hash::map as hash_map;
use crate::hash::{HashMap, HashSet}; use crate::hash::{HashMap, HashSet};
use crate::rule_tree::CascadeLevel; use crate::rule_tree::CascadeLevel;
use crate::selector_parser::SelectorImpl; use crate::selector_parser::SelectorImpl;
use crate::stylist::Rule; use crate::stylist::{Rule, CascadeData};
use crate::{Atom, LocalName, Namespace, WeakAtom}; use crate::{Atom, LocalName, Namespace, WeakAtom};
use fallible::FallibleVec; use fallible::FallibleVec;
use hashglobe::FailedAllocationError; use hashglobe::FailedAllocationError;
@ -104,10 +104,14 @@ pub struct SelectorMap<T: 'static> {
pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>, pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
/// A hash from local name to rules which contain that local name selector. /// A hash from local name to rules which contain that local name selector.
pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>, pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
/// A hash from attributes to rules which contain that attribute selector.
pub attribute_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
/// A hash from namespace to rules which contain that namespace selector. /// A hash from namespace to rules which contain that namespace selector.
pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>, pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>,
/// All other rules. /// All other rules.
pub other: SmallVec<[T; 1]>, pub other: SmallVec<[T; 1]>,
/// Whether we should bucket by attribute names.
bucket_attributes: bool,
/// The number of entries in this map. /// The number of entries in this map.
pub count: usize, pub count: usize,
} }
@ -129,18 +133,32 @@ impl<T: 'static> SelectorMap<T> {
root: SmallVec::new(), root: SmallVec::new(),
id_hash: MaybeCaseInsensitiveHashMap::new(), id_hash: MaybeCaseInsensitiveHashMap::new(),
class_hash: MaybeCaseInsensitiveHashMap::new(), class_hash: MaybeCaseInsensitiveHashMap::new(),
attribute_hash: HashMap::default(),
local_name_hash: HashMap::default(), local_name_hash: HashMap::default(),
namespace_hash: HashMap::default(), namespace_hash: HashMap::default(),
other: SmallVec::new(), 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, 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. /// Clears the hashmap retaining storage.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.root.clear(); self.root.clear();
self.id_hash.clear(); self.id_hash.clear();
self.class_hash.clear(); self.class_hash.clear();
self.attribute_hash.clear();
self.local_name_hash.clear(); self.local_name_hash.clear();
self.namespace_hash.clear(); self.namespace_hash.clear();
self.other.clear(); self.other.clear();
@ -171,6 +189,7 @@ impl SelectorMap<Rule> {
context: &mut MatchingContext<E::Impl>, context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F, flags_setter: &mut F,
cascade_level: CascadeLevel, cascade_level: CascadeLevel,
cascade_data: &CascadeData,
) where ) where
E: TElement, E: TElement,
F: FnMut(&E, ElementSelectorFlags), F: FnMut(&E, ElementSelectorFlags),
@ -189,6 +208,7 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, flags_setter,
cascade_level, cascade_level,
cascade_data,
); );
} }
@ -201,6 +221,7 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, flags_setter,
cascade_level, cascade_level,
cascade_data,
) )
} }
} }
@ -214,10 +235,27 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, flags_setter,
cascade_level, 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()) { if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) {
SelectorMap::get_matching_rules( SelectorMap::get_matching_rules(
element, element,
@ -226,6 +264,7 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, flags_setter,
cascade_level, cascade_level,
cascade_data,
) )
} }
@ -237,6 +276,7 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, flags_setter,
cascade_level, cascade_level,
cascade_data,
) )
} }
@ -247,6 +287,7 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, flags_setter,
cascade_level, cascade_level,
cascade_data,
); );
} }
@ -258,6 +299,7 @@ impl SelectorMap<Rule> {
context: &mut MatchingContext<E::Impl>, context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F, flags_setter: &mut F,
cascade_level: CascadeLevel, cascade_level: CascadeLevel,
cascade_data: &CascadeData,
) where ) where
E: TElement, E: TElement,
F: FnMut(&E, ElementSelectorFlags), F: FnMut(&E, ElementSelectorFlags),
@ -271,7 +313,7 @@ impl SelectorMap<Rule> {
context, context,
flags_setter, 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<T: SelectorMapEntry> SelectorMap<T> {
.class_hash .class_hash
.try_entry(class.clone(), quirks_mode)? .try_entry(class.clone(), quirks_mode)?
.or_insert_with(SmallVec::new), .or_insert_with(SmallVec::new),
Bucket::Attribute { name, lower_name } |
Bucket::LocalName { name, lower_name } => { Bucket::LocalName { name, lower_name } => {
// If the local name in the selector isn't lowercase, // If the local name in the selector isn't lowercase,
// insert it into the rule hash twice. This means that, // insert it into the rule hash twice. This means that,
@ -316,13 +359,19 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
// selector, the rulehash lookup may produce superfluous // selector, the rulehash lookup may produce superfluous
// selectors, but the subsequent selector matching work // selectors, but the subsequent selector matching work
// will filter them out. // 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 { if name != lower_name {
self.local_name_hash hash
.try_entry(lower_name.clone())? .try_entry(lower_name.clone())?
.or_insert_with(SmallVec::new) .or_insert_with(SmallVec::new)
.try_push($entry.clone())?; .try_push($entry.clone())?;
} }
self.local_name_hash hash
.try_entry(name.clone())? .try_entry(name.clone())?
.or_insert_with(SmallVec::new) .or_insert_with(SmallVec::new)
}, },
@ -338,7 +387,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
let bucket = { let bucket = {
let mut disjoint_buckets = SmallVec::new(); 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 // See if inserting this selector in multiple entries in the
// selector map would be worth it. Consider a case like: // selector map would be worth it. Consider a case like:
@ -409,7 +458,9 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
let mut done = false; let mut done = false;
element.each_class(|class| { element.each_class(|class| {
if !done { if done {
return;
}
if let Some(v) = self.class_hash.get(class, quirks_mode) { if let Some(v) = self.class_hash.get(class, quirks_mode) {
for entry in v.iter() { for entry in v.iter() {
if !f(&entry) { if !f(&entry) {
@ -418,12 +469,32 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
} }
} }
} }
}
}); });
if done { if done {
return false; 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;
return;
}
}
}
});
if done {
return false;
}
}
if let Some(v) = self.local_name_hash.get(element.local_name()) { if let Some(v) = self.local_name_hash.get(element.local_name()) {
for entry in v.iter() { for entry in v.iter() {
if !f(&entry) { if !f(&entry) {
@ -507,6 +578,10 @@ enum Bucket<'a> {
name: &'a LocalName, name: &'a LocalName,
lower_name: &'a LocalName, lower_name: &'a LocalName,
}, },
Attribute {
name: &'a LocalName,
lower_name: &'a LocalName,
},
Class(&'a Atom), Class(&'a Atom),
ID(&'a Atom), ID(&'a Atom),
Root, Root,
@ -520,9 +595,10 @@ impl<'a> Bucket<'a> {
Bucket::Universal => 0, Bucket::Universal => 0,
Bucket::Namespace(..) => 1, Bucket::Namespace(..) => 1,
Bucket::LocalName { .. } => 2, Bucket::LocalName { .. } => 2,
Bucket::Class(..) => 3, Bucket::Attribute { .. } => 3,
Bucket::ID(..) => 4, Bucket::Class(..) => 4,
Bucket::Root => 5, Bucket::ID(..) => 5,
Bucket::Root => 6,
} }
} }
@ -537,11 +613,24 @@ type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>;
fn specific_bucket_for<'a>( fn specific_bucket_for<'a>(
component: &'a Component<SelectorImpl>, component: &'a Component<SelectorImpl>,
disjoint_buckets: &mut DisjointBuckets<'a>, disjoint_buckets: &mut DisjointBuckets<'a>,
bucket_attributes: bool,
) -> Bucket<'a> { ) -> Bucket<'a> {
match *component { match *component {
Component::Root => Bucket::Root, Component::Root => Bucket::Root,
Component::ID(ref id) => Bucket::ID(id), Component::ID(ref id) => Bucket::ID(id),
Component::Class(ref class) => Bucket::Class(class), 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 { Component::LocalName(ref selector) => Bucket::LocalName {
name: &selector.name, name: &selector.name,
lower_name: &selector.lower_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 // So inserting `span` in the rule hash makes sense since we want to
// match the slotted <span>. // match the slotted <span>.
Component::Slotted(ref selector) => find_bucket(selector.iter(), 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), Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes),
Component::Is(ref list) | Component::Where(ref list) => { Component::Is(ref list) | Component::Where(ref list) => {
if list.len() == 1 { if list.len() == 1 {
find_bucket(list[0].iter(), disjoint_buckets) find_bucket(list[0].iter(), disjoint_buckets, bucket_attributes)
} else { } else {
for selector in &**list { 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); disjoint_buckets.push(bucket);
} }
Bucket::Universal Bucket::Universal
@ -593,12 +682,13 @@ fn specific_bucket_for<'a>(
fn find_bucket<'a>( fn find_bucket<'a>(
mut iter: SelectorIter<'a, SelectorImpl>, mut iter: SelectorIter<'a, SelectorImpl>,
disjoint_buckets: &mut DisjointBuckets<'a>, disjoint_buckets: &mut DisjointBuckets<'a>,
bucket_attributes: bool,
) -> Bucket<'a> { ) -> Bucket<'a> {
let mut current_bucket = Bucket::Universal; let mut current_bucket = Bucket::Universal;
loop { loop {
for ss in &mut iter { 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(&current_bucket) { if new_bucket.more_specific_than(&current_bucket) {
current_bucket = new_bucket; current_bucket = new_bucket;
} }

View file

@ -190,12 +190,12 @@ impl Device {
} }
/// Returns the default background color. /// 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) RGBA::new(255, 255, 255, 255)
} }
/// Returns the default color color. /// Returns the default foreground color.
pub fn default_color(&self) -> RGBA { pub fn default_color_for_forced_colors(&self) -> RGBA {
RGBA::new(0, 0, 0, 255) RGBA::new(0, 0, 0, 255)
} }
@ -220,6 +220,24 @@ impl Device {
_ => false, _ => 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 /// https://drafts.csswg.org/mediaqueries-4/#width

View file

@ -94,6 +94,12 @@ impl SharedRwLock {
SharedRwLock { cell: None } 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. /// Wrap the given data to make its access protected by this lock.
pub fn wrap<T>(&self, data: T) -> Locked<T> { pub fn wrap<T>(&self, data: T) -> Locked<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). /// Proof that a shared lock was obtained for writing (servo).
#[cfg(feature = "servo")] #[cfg(feature = "servo")]
pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock); pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock);
@ -190,25 +204,18 @@ impl<T> Locked<T> {
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
fn same_lock_as(&self, derefed_guard: Option<&SomethingZeroSizedButTyped>) -> bool { fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool {
ptr::eq( ptr::eq(self.shared_lock.ptr(), ptr)
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()),
)
} }
/// Access the data for reading. /// Access the data for reading.
pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T { pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
assert!( assert!(
self.is_read_only_lock() || self.same_lock_as(guard.0.as_ref().map(|r| &**r)), self.is_read_only_lock() || self.same_lock_as(guard.ptr()),
"Locked::read_with called with a guard from an unrelated SharedRwLock" "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}",
self.shared_lock.ptr(),
guard.ptr(),
); );
#[cfg(not(feature = "gecko"))] #[cfg(not(feature = "gecko"))]
assert!(self.same_lock_as(&guard.0)); assert!(self.same_lock_as(&guard.0));
@ -235,7 +242,7 @@ impl<T> Locked<T> {
pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T { pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
assert!( 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" "Locked::write_with called with a guard from a read only or unrelated SharedRwLock"
); );
#[cfg(not(feature = "gecko"))] #[cfg(not(feature = "gecko"))]

View file

@ -786,10 +786,7 @@ impl<E: TElement> StyleSharingCache<E> {
} }
// It's possible that there are no styles for either id. // It's possible that there are no styles for either id.
let may_match_different_id_rules = if checks::may_match_different_id_rules(shared, target.element, candidate.element) {
checks::may_match_different_id_rules(shared, target.element, candidate.element);
if may_match_different_id_rules {
trace!("Miss: ID Attr"); trace!("Miss: ID Attr");
return None; return None;
} }

View file

@ -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 /// Whether we should skip any item-based display property blockification on
/// this element. /// this element.
fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool
@ -909,7 +852,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
{ {
self.adjust_for_appearance(element); self.adjust_for_appearance(element);
self.adjust_for_inert();
self.adjust_for_marker_pseudo(); self.adjust_for_marker_pseudo();
} }
self.set_bits(); self.set_bits();

View file

@ -396,13 +396,14 @@ macro_rules! font_feature_values_blocks {
type AtRule = (); type AtRule = ();
type Error = StyleParseErrorKind<'i>; type Error = StyleParseErrorKind<'i>;
fn parse_prelude<'t>(&mut self, fn parse_prelude<'t>(
&mut self,
name: CowRcStr<'i>, name: CowRcStr<'i>,
input: &mut Parser<'i, 't>) input: &mut Parser<'i, 't>,
-> Result<Self::Prelude, ParseError<'i>> { ) -> Result<BlockType, ParseError<'i>> {
match_ignore_ascii_case! { &*name, match_ignore_ascii_case! { &*name,
$( $(
$name => Ok(Self::Prelude::$ident_camel), $name => Ok(BlockType::$ident_camel),
)* )*
_ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
} }

View file

@ -11,21 +11,13 @@ use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock};
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter; use crate::str::CssStringWriter;
use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::stylesheets::{CssRule, StylesheetInDocument};
use crate::stylesheets::layer_rule::LayerName;
use crate::values::CssUrl; use crate::values::CssUrl;
use cssparser::SourceLocation; use cssparser::SourceLocation;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
use to_shmem::{self, SharedMemoryBuilder, ToShmem}; use to_shmem::{self, SharedMemoryBuilder, ToShmem};
/// With asynchronous stylesheet parsing, we can't synchronously create a
/// GeckoStyleSheet. So we use this placeholder instead.
#[cfg(feature = "gecko")]
#[derive(Clone, Debug)]
pub struct PendingSheet {
origin: Origin,
quirks_mode: QuirksMode,
}
/// A sheet that is held from an import rule. /// A sheet that is held from an import rule.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[derive(Debug)] #[derive(Debug)]
@ -34,7 +26,7 @@ pub enum ImportSheet {
Sheet(crate::gecko::data::GeckoStyleSheet), Sheet(crate::gecko::data::GeckoStyleSheet),
/// An @import created while parsing off-main-thread, whose Gecko sheet has /// An @import created while parsing off-main-thread, whose Gecko sheet has
/// yet to be created and attached. /// yet to be created and attached.
Pending(PendingSheet), Pending,
} }
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
@ -45,11 +37,8 @@ impl ImportSheet {
} }
/// Creates a pending ImportSheet for a load that has not started yet. /// Creates a pending ImportSheet for a load that has not started yet.
pub fn new_pending(origin: Origin, quirks_mode: QuirksMode) -> Self { pub fn new_pending() -> Self {
ImportSheet::Pending(PendingSheet { ImportSheet::Pending
origin,
quirks_mode,
})
} }
/// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it
@ -63,7 +52,7 @@ impl ImportSheet {
} }
Some(s) Some(s)
}, },
ImportSheet::Pending(_) => None, ImportSheet::Pending => None,
} }
} }
@ -98,7 +87,7 @@ impl DeepCloneWithLock for ImportSheet {
}; };
ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) }) 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<LayerName>,
}
impl ToCss for ImportLayer {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> 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. /// The [`@import`][import] at-rule.
/// ///
/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import /// [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. /// ImportSheet just has stub behavior until it appears.
pub stylesheet: ImportSheet, pub stylesheet: ImportSheet,
/// A `layer()` function name.
pub layer: Option<ImportLayer>,
/// The line and column of the rule's source code. /// The line and column of the rule's source code.
pub source_location: SourceLocation, pub source_location: SourceLocation,
} }
@ -170,6 +186,7 @@ impl DeepCloneWithLock for ImportRule {
ImportRule { ImportRule {
url: self.url.clone(), url: self.url.clone(),
stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params), stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
layer: self.layer.clone(),
source_location: self.source_location.clone(), source_location: self.source_location.clone(),
} }
} }
@ -180,14 +197,18 @@ impl ToCssWithGuard for ImportRule {
dest.write_str("@import ")?; dest.write_str("@import ")?;
self.url.to_css(&mut CssWriter::new(dest))?; self.url.to_css(&mut CssWriter::new(dest))?;
match self.stylesheet.media(guard) { if let Some(media) = self.stylesheet.media(guard) {
Some(media) if !media.is_empty() => { if !media.is_empty() {
dest.write_str(" ")?; dest.write_char(' ')?;
media.to_css(&mut CssWriter::new(dest))?; 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(';')
} }
} }

View file

@ -14,6 +14,7 @@ use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration};
use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard};
use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::shared_lock::{Locked, ToCssWithGuard};
use crate::str::CssStringWriter; use crate::str::CssStringWriter;
use crate::stylesheets::layer_rule::LayerId;
use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::rule_parser::VendorPrefix;
use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::stylesheets::{CssRuleType, StylesheetContents};
use crate::values::{serialize_percentage, KeyframesName}; use crate::values::{serialize_percentage, KeyframesName};
@ -357,6 +358,8 @@ pub struct KeyframesAnimation {
pub properties_changed: LonghandIdSet, pub properties_changed: LonghandIdSet,
/// Vendor prefix type the @keyframes has. /// Vendor prefix type the @keyframes has.
pub vendor_prefix: Option<VendorPrefix>, pub vendor_prefix: Option<VendorPrefix>,
/// The id of the cascade layer the keyframe rule was in.
pub layer_id: LayerId,
} }
/// Get all the animated properties in a keyframes animation. /// Get all the animated properties in a keyframes animation.
@ -409,12 +412,14 @@ impl KeyframesAnimation {
pub fn from_keyframes( pub fn from_keyframes(
keyframes: &[Arc<Locked<Keyframe>>], keyframes: &[Arc<Locked<Keyframe>>],
vendor_prefix: Option<VendorPrefix>, vendor_prefix: Option<VendorPrefix>,
layer_id: LayerId,
guard: &SharedRwLockReadGuard, guard: &SharedRwLockReadGuard,
) -> Self { ) -> Self {
let mut result = KeyframesAnimation { let mut result = KeyframesAnimation {
steps: vec![], steps: vec![],
properties_changed: LonghandIdSet::new(), properties_changed: LonghandIdSet::new(),
vendor_prefix, vendor_prefix,
layer_id,
}; };
if keyframes.is_empty() { if keyframes.is_empty() {

View file

@ -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 `<layer-name>`: 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<Self, ParseError<'i>> {
let mut result = SmallVec::new();
result.push(AtomIdent::from(&**input.expect_ident()?));
loop {
let next_name = input.try_parse(|input| -> Result<AtomIdent, ParseError<'i>> {
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<W>(&self, dest: &mut CssWriter<W>) -> 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 <name>? { ... }`
Block {
/// The layer name, or `None` if anonymous.
name: Option<LayerName>,
/// The nested rules.
rules: Arc<Locked<CssRules>>,
},
/// A statement `@layer <name>, <name>, <name>;`
Statement {
/// The list of layers to sort.
names: Vec<LayerName>,
},
}
/// 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(),
}
}
}

View file

@ -8,7 +8,7 @@
use crate::media_queries::MediaList; use crate::media_queries::MediaList;
use crate::parser::ParserContext; use crate::parser::ParserContext;
use crate::shared_lock::{Locked, SharedRwLock}; use crate::shared_lock::{Locked, SharedRwLock};
use crate::stylesheets::import_rule::ImportRule; use crate::stylesheets::import_rule::{ImportRule, ImportLayer};
use crate::values::CssUrl; use crate::values::CssUrl;
use cssparser::SourceLocation; use cssparser::SourceLocation;
use servo_arc::Arc; use servo_arc::Arc;
@ -25,5 +25,6 @@ pub trait StylesheetLoader {
context: &ParserContext, context: &ParserContext,
lock: &SharedRwLock, lock: &SharedRwLock,
media: Arc<Locked<MediaList>>, media: Arc<Locked<MediaList>>,
layer: Option<ImportLayer>,
) -> Arc<Locked<ImportRule>>; ) -> Arc<Locked<ImportRule>>;
} }

View file

@ -11,6 +11,7 @@ mod font_face_rule;
pub mod font_feature_values_rule; pub mod font_feature_values_rule;
pub mod import_rule; pub mod import_rule;
pub mod keyframes_rule; pub mod keyframes_rule;
pub mod layer_rule;
mod loader; mod loader;
mod media_rule; mod media_rule;
mod namespace_rule; mod namespace_rule;
@ -19,6 +20,7 @@ mod page_rule;
mod rule_list; mod rule_list;
mod rule_parser; mod rule_parser;
mod rules_iterator; mod rules_iterator;
pub mod scroll_timeline_rule;
mod style_rule; mod style_rule;
mod stylesheet; mod stylesheet;
pub mod supports_rule; 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::font_feature_values_rule::FontFeatureValuesRule;
pub use self::import_rule::ImportRule; pub use self::import_rule::ImportRule;
pub use self::keyframes_rule::KeyframesRule; pub use self::keyframes_rule::KeyframesRule;
pub use self::layer_rule::LayerRule;
pub use self::loader::StylesheetLoader; pub use self::loader::StylesheetLoader;
pub use self::media_rule::MediaRule; pub use self::media_rule::MediaRule;
pub use self::namespace_rule::NamespaceRule; pub use self::namespace_rule::NamespaceRule;
@ -60,6 +63,7 @@ pub use self::rules_iterator::{AllRules, EffectiveRules};
pub use self::rules_iterator::{ pub use self::rules_iterator::{
EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator, EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator,
}; };
pub use self::scroll_timeline_rule::ScrollTimelineRule;
pub use self::style_rule::StyleRule; pub use self::style_rule::StyleRule;
pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind};
pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet};
@ -257,6 +261,8 @@ pub enum CssRule {
Supports(Arc<Locked<SupportsRule>>), Supports(Arc<Locked<SupportsRule>>),
Page(Arc<Locked<PageRule>>), Page(Arc<Locked<PageRule>>),
Document(Arc<Locked<DocumentRule>>), Document(Arc<Locked<DocumentRule>>),
Layer(Arc<Locked<LayerRule>>),
ScrollTimeline(Arc<Locked<ScrollTimelineRule>>),
} }
impl CssRule { impl CssRule {
@ -297,16 +303,22 @@ impl CssRule {
CssRule::Document(ref lock) => { CssRule::Document(ref lock) => {
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) 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)] #[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
#[repr(u8)]
pub enum CssRuleType { pub enum CssRuleType {
// https://drafts.csswg.org/cssom/#the-cssrule-interface // https://drafts.csswg.org/cssom/#the-cssrule-interface
Style = 1, Style = 1,
Charset = 2, // Charset = 2, // Historical
Import = 3, Import = 3,
Media = 4, Media = 4,
FontFace = 5, FontFace = 5,
@ -315,7 +327,7 @@ pub enum CssRuleType {
Keyframes = 7, Keyframes = 7,
Keyframe = 8, Keyframe = 8,
// https://drafts.csswg.org/cssom/#the-cssrule-interface // https://drafts.csswg.org/cssom/#the-cssrule-interface
Margin = 9, // Margin = 9, // Not implemented yet.
Namespace = 10, Namespace = 10,
// https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface
CounterStyle = 11, CounterStyle = 11,
@ -323,10 +335,14 @@ pub enum CssRuleType {
Supports = 12, Supports = 12,
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface
Document = 13, Document = 13,
// https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues // https://drafts.csswg.org/css-fonts/#om-fontfeaturevalues
FontFeatureValues = 14, FontFeatureValues = 14,
// https://drafts.csswg.org/css-device-adapt/#css-rule-interface // https://drafts.csswg.org/css-device-adapt/#css-rule-interface
Viewport = 15, 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)] #[allow(missing_docs)]
@ -353,6 +369,8 @@ impl CssRule {
CssRule::Supports(_) => CssRuleType::Supports, CssRule::Supports(_) => CssRuleType::Supports,
CssRule::Page(_) => CssRuleType::Page, CssRule::Page(_) => CssRuleType::Page,
CssRule::Document(_) => CssRuleType::Document, CssRule::Document(_) => CssRuleType::Document,
CssRule::Layer(_) => CssRuleType::Layer,
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
} }
} }
@ -361,6 +379,7 @@ impl CssRule {
// CssRule::Charset(..) => State::Start, // CssRule::Charset(..) => State::Start,
CssRule::Import(..) => State::Imports, CssRule::Import(..) => State::Imports,
CssRule::Namespace(..) => State::Namespaces, CssRule::Namespace(..) => State::Namespaces,
// TODO(emilio): Do we need something for EarlyLayers?
_ => State::Body, _ => State::Body,
} }
} }
@ -485,6 +504,16 @@ impl DeepCloneWithLock for CssRule {
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), 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::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Page(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::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),
} }
} }
} }

View file

@ -15,20 +15,23 @@ use crate::shared_lock::{Locked, SharedRwLock};
use crate::str::starts_with_ignore_ascii_case; use crate::str::starts_with_ignore_ascii_case;
use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::document_rule::DocumentCondition;
use crate::stylesheets::font_feature_values_rule::parse_family_name_list; 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::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::stylesheet::Namespaces;
use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::supports_rule::SupportsCondition;
use crate::stylesheets::viewport_rule; use crate::stylesheets::{
use crate::stylesheets::AllowImportRules; viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule,
use crate::stylesheets::{CorsMode, DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule}; FontFeatureValuesRule, KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule,
use crate::stylesheets::{CssRule, CssRuleType, CssRules, RulesMutateError, StylesheetLoader}; RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule,
use crate::stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule}; };
use crate::values::computed::font::FamilyName; use crate::values::computed::font::FamilyName;
use crate::values::{CssUrl, CustomIdent, KeyframesName}; use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName};
use crate::{Namespace, Prefix}; use crate::{Namespace, Prefix};
use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
use cssparser::{ use cssparser::{
BasicParseError, BasicParseErrorKind, CowRcStr, ParseErrorKind, ParserState, SourcePosition, AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, Parser, ParserState,
QualifiedRuleParser, RuleListParser, SourcePosition,
}; };
use selectors::SelectorList; use selectors::SelectorList;
use servo_arc::Arc; use servo_arc::Arc;
@ -130,12 +133,14 @@ impl<'b> TopLevelRuleParser<'b> {
pub enum State { pub enum State {
/// We haven't started parsing rules. /// We haven't started parsing rules.
Start = 1, Start = 1,
/// We're parsing `@import` rules. /// We're parsing early `@layer` statement rules.
Imports = 2, EarlyLayers = 2,
/// We're parsing `@import` and early `@layer` statement rules.
Imports = 3,
/// We're parsing `@namespace` rules. /// We're parsing `@namespace` rules.
Namespaces = 3, Namespaces = 4,
/// We're parsing the main body of the stylesheet. /// We're parsing the main body of the stylesheet.
Body = 4, Body = 5,
} }
#[derive(Clone, Debug, MallocSizeOf, ToShmem)] #[derive(Clone, Debug, MallocSizeOf, ToShmem)]
@ -168,9 +173,13 @@ pub enum AtRulePrelude {
/// A @document rule, with its conditional. /// A @document rule, with its conditional.
Document(DocumentCondition), Document(DocumentCondition),
/// A @import rule prelude. /// A @import rule prelude.
Import(CssUrl, Arc<Locked<MediaList>>), Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportLayer>),
/// A @namespace rule prelude. /// A @namespace rule prelude.
Namespace(Option<Prefix>, Namespace), Namespace(Option<Prefix>, Namespace),
/// A @layer rule prelude.
Layer(Vec<LayerName>),
/// A @scroll-timeline rule prelude.
ScrollTimeline(TimelineName),
} }
impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
@ -182,7 +191,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
&mut self, &mut self,
name: CowRcStr<'i>, name: CowRcStr<'i>,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i>> { ) -> Result<AtRulePrelude, ParseError<'i>> {
match_ignore_ascii_case! { &*name, match_ignore_ascii_case! { &*name,
"import" => { "import" => {
if !self.check_state(State::Imports) { 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_string = input.expect_url_or_string()?.as_ref().to_owned();
let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None); 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 = MediaList::parse(&self.context, input);
let media = Arc::new(self.shared_lock.wrap(media)); let media = Arc::new(self.shared_lock.wrap(media));
let prelude = AtRulePrelude::Import(url, media); return Ok(AtRulePrelude::Import(url, media, layer));
return Ok(prelude);
}, },
"namespace" => { "namespace" => {
if !self.check_state(State::Namespaces) { if !self.check_state(State::Namespaces) {
@ -225,8 +254,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
let url = Namespace::from(maybe_namespace.as_ref()); let url = Namespace::from(maybe_namespace.as_ref());
let prelude = AtRulePrelude::Namespace(prefix, url); return Ok(AtRulePrelude::Namespace(prefix, url));
return Ok(prelude);
}, },
// @charset is removed by rust-cssparser if its the first rule in the stylesheet // @charset is removed by rust-cssparser if its the first rule in the stylesheet
// anything left is invalid. // anything left is invalid.
@ -263,7 +291,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
start: &ParserState, start: &ParserState,
) -> Result<Self::AtRule, ()> { ) -> Result<Self::AtRule, ()> {
let rule = match prelude { let rule = match prelude {
AtRulePrelude::Import(url, media) => { AtRulePrelude::Import(url, media, layer) => {
let loader = self let loader = self
.loader .loader
.expect("Expected a stylesheet loader for @import"); .expect("Expected a stylesheet loader for @import");
@ -274,6 +302,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
&self.context, &self.context,
&self.shared_lock, &self.shared_lock,
media, media,
layer,
); );
self.state = State::Imports; self.state = State::Imports;
@ -295,7 +324,19 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
source_location: start.source_location(), 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)) Ok((start.position(), rule))
@ -379,41 +420,37 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
name: CowRcStr<'i>, name: CowRcStr<'i>,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self::Prelude, ParseError<'i>> { ) -> Result<Self::Prelude, ParseError<'i>> {
match_ignore_ascii_case! { &*name, Ok(match_ignore_ascii_case! { &*name,
"media" => { "media" => {
let media_queries = MediaList::parse(self.context, input); let media_queries = MediaList::parse(self.context, input);
let arc = Arc::new(self.shared_lock.wrap(media_queries)); let arc = Arc::new(self.shared_lock.wrap(media_queries));
Ok(Self::Prelude::Media(arc)) AtRulePrelude::Media(arc)
}, },
"supports" => { "supports" => {
let cond = SupportsCondition::parse(input)?; let cond = SupportsCondition::parse(input)?;
Ok(Self::Prelude::Supports(cond)) AtRulePrelude::Supports(cond)
}, },
"font-face" => { "font-face" => {
Ok(Self::Prelude::FontFace) AtRulePrelude::FontFace
}, },
"font-feature-values" => { "layer" => {
if !cfg!(feature = "gecko") { let names = input.try_parse(|input| {
// Support for this rule is not fully implemented in Servo yet. input.parse_comma_separated(|input| {
return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) 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)?; let family_names = parse_family_name_list(self.context, input)?;
Ok(Self::Prelude::FontFeatureValues(family_names)) AtRulePrelude::FontFeatureValues(family_names)
}, },
"counter-style" => { "counter-style" if cfg!(feature = "gecko") => {
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())))
}
let name = parse_counter_style_name_definition(input)?; let name = parse_counter_style_name_definition(input)?;
Ok(Self::Prelude::CounterStyle(name)) AtRulePrelude::CounterStyle(name)
}, },
"viewport" => { "viewport" if viewport_rule::enabled() => {
if viewport_rule::enabled() { AtRulePrelude::Viewport
Ok(Self::Prelude::Viewport)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
}
}, },
"keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") { 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()))) return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
} }
let name = KeyframesName::parse(self.context, input)?; let name = KeyframesName::parse(self.context, input)?;
AtRulePrelude::Keyframes(name, prefix)
Ok(Self::Prelude::Keyframes(name, prefix))
}, },
"page" => { "page" if cfg!(feature = "gecko") => {
if cfg!(feature = "gecko") { AtRulePrelude::Page
Ok(Self::Prelude::Page)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
}
}, },
"-moz-document" => { "-moz-document" if cfg!(feature = "gecko") => {
if !cfg!(feature = "gecko") {
return Err(input.new_custom_error(
StyleParseErrorKind::UnsupportedAtRule(name.clone())
))
}
let cond = DocumentCondition::parse(self.context, input)?; 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>( fn parse_block<'t>(
@ -577,14 +608,62 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
}, },
)))) ))))
}, },
_ => Err(ParseError { AtRulePrelude::Layer(names) => {
kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(CowRcStr::from( let name = match names.len() {
"Unsupported AtRule Prelude.", 0 | 1 => names.into_iter().next(),
))), _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
location: start.source_location(), };
}), 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<Self::AtRule, ()> {
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)] #[inline(never)]

View file

@ -51,18 +51,15 @@ where
pub fn skip_children(&mut self) { pub fn skip_children(&mut self) {
self.stack.pop(); self.stack.pop();
} }
}
fn children_of_rule<'a, C>( /// Returns the children of `rule`, and whether `rule` is effective.
pub fn children(
rule: &'a CssRule, rule: &'a CssRule,
device: &'a Device, device: &'a Device,
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
guard: &'a SharedRwLockReadGuard<'_>, guard: &'a SharedRwLockReadGuard<'_>,
effective: &mut bool, effective: &mut bool,
) -> Option<slice::Iter<'a, CssRule>> ) -> Option<slice::Iter<'a, CssRule>> {
where
C: NestedRuleIterationCondition + 'static,
{
*effective = true; *effective = true;
match *rule { match *rule {
CssRule::Namespace(_) | CssRule::Namespace(_) |
@ -71,6 +68,7 @@ where
CssRule::CounterStyle(_) | CssRule::CounterStyle(_) |
CssRule::Viewport(_) | CssRule::Viewport(_) |
CssRule::Keyframes(_) | CssRule::Keyframes(_) |
CssRule::ScrollTimeline(_) |
CssRule::Page(_) | CssRule::Page(_) |
CssRule::FontFeatureValues(_) => None, CssRule::FontFeatureValues(_) => None,
CssRule::Import(ref import_rule) => { CssRule::Import(ref import_rule) => {
@ -105,6 +103,16 @@ where
} }
Some(supports_rule.rules.read_with(guard).0.iter()) 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,
}
}
}
} }
} }
@ -129,7 +137,7 @@ where
}; };
let mut effective = true; let mut effective = true;
let children = children_of_rule::<C>( let children = Self::children(
rule, rule,
self.device, self.device,
self.quirks_mode, self.quirks_mode,
@ -315,7 +323,7 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> {
guard: &'a SharedRwLockReadGuard<'b>, guard: &'a SharedRwLockReadGuard<'b>,
rule: &'a CssRule, rule: &'a CssRule,
) -> Self { ) -> Self {
let children = children_of_rule::<AllRules>(rule, device, quirks_mode, guard, &mut false); let children = RulesIterator::<AllRules>::children(rule, device, quirks_mode, guard, &mut false);
EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter()))
} }
} }

View file

@ -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<Source>,
/// The orientation of the current scroll timeline.
pub orientation: Option<Orientation>,
/// The scroll timeline's scrollOffsets.
pub offsets: Option<ScrollOffsets>,
}
impl Parse for ScrollTimelineDescriptors {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
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<W>(&self, dest: &mut CssWriter<W>) -> 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 | <scroll-timeline-offset>#
///
/// 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<Self, ParseError<'i>> {
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 <scroll-timeline-offset>.
/// value: auto | <length-percentage> | <element-offset>
///
/// 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 <element-offset-edge>.
///
/// 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 <element-offset>.
/// value: selector( <id-selector> ) [<element-offset-edge> || <number>]?
///
/// 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 <element-offset-edge>. If not provided, the default value is start.
edge: Option<ElementOffsetEdge>,
/// An optional value of threshold. If not provided, the default value is 0.
threshold: Option<Number>,
}
impl Parse for ElementOffset {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let target = ScrollTimelineSelector::parse(context, input)?;
// Parse `[<element-offset-edge> || <number>]?`
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<Self, ParseError<'i>> {
// Parse `selector(<id-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<W>(&self, dest: &mut CssWriter<W>) -> 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))
}
}

View file

@ -278,15 +278,8 @@ pub trait StylesheetInDocument: ::std::fmt::Debug {
rule_filter! { rule_filter! {
effective_style_rules(Style => StyleRule), effective_style_rules(Style => StyleRule),
effective_media_rules(Media => MediaRule),
effective_font_face_rules(FontFace => FontFaceRule), 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_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::Document(..) |
CssRule::Media(..) | CssRule::Media(..) |
CssRule::Supports(..) | 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, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true,
@ -375,7 +371,8 @@ impl SanitizationKind {
CssRule::Page(..) | CssRule::Page(..) |
CssRule::FontFeatureValues(..) | CssRule::FontFeatureValues(..) |
CssRule::Viewport(..) | CssRule::Viewport(..) |
CssRule::CounterStyle(..) => !is_standard, CssRule::CounterStyle(..) |
CssRule::ScrollTimeline(..) => !is_standard,
} }
} }
} }

View file

@ -25,11 +25,12 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
use crate::stylesheets::keyframes_rule::KeyframesAnimation; 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::viewport_rule::{self, MaybeNew, ViewportRule};
use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents};
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule}; 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::thread_state::{self, ThreadState};
use crate::{Atom, LocalName, Namespace, WeakAtom}; use crate::{Atom, LocalName, Namespace, WeakAtom};
use fallible::FallibleVec; use fallible::FallibleVec;
@ -292,6 +293,8 @@ impl CascadeDataCacheEntry for UserAgentCascadeData {
)?; )?;
} }
new_data.cascade_data.compute_layer_order();
Ok(Arc::new(new_data)) Ok(Arc::new(new_data))
} }
@ -1866,6 +1869,23 @@ impl PartElementAndPseudoRules {
} }
} }
#[derive(Clone, Debug, MallocSizeOf)]
struct CascadeLayer {
id: LayerId,
order: LayerOrder,
children: Vec<LayerId>,
}
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 /// Data resulting from performing the CSS cascade that is specific to a given
/// origin. /// origin.
/// ///
@ -1931,6 +1951,12 @@ pub struct CascadeData {
/// by name. /// by name.
animations: PrecomputedHashMap<Atom, KeyframesAnimation>, animations: PrecomputedHashMap<Atom, KeyframesAnimation>,
/// A map from cascade layer name to layer order.
layer_id: FxHashMap<LayerName, LayerId>,
/// 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 cached from the last rebuild.
effective_media_query_results: EffectiveMediaQueryResults, effective_media_query_results: EffectiveMediaQueryResults,
@ -1961,8 +1987,18 @@ impl CascadeData {
state_dependencies: ElementState::empty(), state_dependencies: ElementState::empty(),
document_state_dependencies: DocumentState::empty(), document_state_dependencies: DocumentState::empty(),
mapped_ids: PrecomputedHashSet::default(), 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(), animations: Default::default(),
layer_id: Default::default(),
layers: smallvec::smallvec![CascadeLayer::root()],
extra_data: ExtraStyleData::default(), extra_data: ExtraStyleData::default(),
effective_media_query_results: EffectiveMediaQueryResults::new(), effective_media_query_results: EffectiveMediaQueryResults::new(),
rules_source_order: 0, rules_source_order: 0,
@ -2009,6 +2045,8 @@ impl CascadeData {
result.is_ok() result.is_ok()
}); });
self.compute_layer_order();
result result
} }
@ -2070,6 +2108,43 @@ impl CascadeData {
self.part_rules.is_some() 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`. /// Collects all the applicable media query results into `results`.
/// ///
/// This duplicates part of the logic in `add_stylesheet`, which is /// This duplicates part of the logic in `add_stylesheet`, which is
@ -2117,31 +2192,25 @@ impl CascadeData {
} }
} }
// Returns Err(..) to signify OOM fn add_rule_list<S>(
fn add_stylesheet<S>(
&mut self, &mut self,
rules: std::slice::Iter<'_, CssRule>,
device: &Device, device: &Device,
quirks_mode: QuirksMode, quirks_mode: QuirksMode,
stylesheet: &S, stylesheet: &S,
guard: &SharedRwLockReadGuard, guard: &SharedRwLockReadGuard,
rebuild_kind: SheetRebuildKind, rebuild_kind: SheetRebuildKind,
mut current_layer: &mut LayerName,
current_layer_id: LayerId,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
) -> Result<(), FailedAllocationError> ) -> Result<(), FailedAllocationError>
where where
S: StylesheetInDocument + 'static, S: StylesheetInDocument + 'static,
{ {
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { for rule in rules {
return Ok(()); // 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;
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) {
match *rule { match *rule {
CssRule::Style(ref locked) => { CssRule::Style(ref locked) => {
let style_rule = locked.read_with(&guard); let style_rule = locked.read_with(&guard);
@ -2154,7 +2223,8 @@ impl CascadeData {
if let Some(pseudo) = pseudo_element { if let Some(pseudo) = pseudo_element {
if pseudo.is_precomputed() { if pseudo.is_precomputed() {
debug_assert!(selector.is_universal()); 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 precomputed_pseudo_element_decls
.as_mut() .as_mut()
@ -2165,6 +2235,7 @@ impl CascadeData {
self.rules_source_order, self.rules_source_order,
CascadeLevel::UANormal, CascadeLevel::UANormal,
selector.specificity(), selector.specificity(),
LayerOrder::root(),
)); ));
continue; continue;
} }
@ -2180,6 +2251,7 @@ impl CascadeData {
hashes, hashes,
locked.clone(), locked.clone(),
self.rules_source_order, self.rules_source_order,
current_layer_id,
); );
if rebuild_kind.should_rebuild_invalidation() { if rebuild_kind.should_rebuild_invalidation() {
@ -2243,42 +2315,51 @@ impl CascadeData {
} }
self.rules_source_order += 1; 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) => { 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); let keyframes_rule = keyframes_rule.read_with(guard);
debug!("Found valid keyframes rule: {:?}", *keyframes_rule); debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? {
// Don't let a prefixed keyframes animation override a non-prefixed one. Entry::Vacant(e) => {
let needs_insertion = keyframes_rule.vendor_prefix.is_none() || e.insert(KeyframesAnimation::from_keyframes(
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.keyframes,
keyframes_rule.vendor_prefix.clone(), keyframes_rule.vendor_prefix.clone(),
current_layer_id,
guard, guard,
); ));
debug!("Found valid keyframe animation: {:?}", animation); },
self.animations Entry::Occupied(mut e) => {
.try_insert(keyframes_rule.name.as_atom().clone(), animation)?; // 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")] #[cfg(feature = "gecko")]
CssRule::FontFace(ref rule) => { CssRule::FontFace(ref rule) => {
self.extra_data.add_font_face(rule); self.extra_data.add_font_face(rule);
@ -2295,11 +2376,220 @@ impl CascadeData {
CssRule::Page(ref rule) => { CssRule::Page(ref rule) => {
self.extra_data.add_page(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, &current_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. // 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<S>(
&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(()) Ok(())
} }
@ -2345,9 +2635,11 @@ impl CascadeData {
CssRule::CounterStyle(..) | CssRule::CounterStyle(..) |
CssRule::Supports(..) | CssRule::Supports(..) |
CssRule::Keyframes(..) | CssRule::Keyframes(..) |
CssRule::ScrollTimeline(..) |
CssRule::Page(..) | CssRule::Page(..) |
CssRule::Viewport(..) | CssRule::Viewport(..) |
CssRule::Document(..) | CssRule::Document(..) |
CssRule::Layer(..) |
CssRule::FontFeatureValues(..) => { CssRule::FontFeatureValues(..) => {
// Not affected by device changes. // Not affected by device changes.
continue; continue;
@ -2413,6 +2705,9 @@ impl CascadeData {
host_rules.clear(); host_rules.clear();
} }
self.animations.clear(); self.animations.clear();
self.layer_id.clear();
self.layers.clear();
self.layers.push(CascadeLayer::root());
self.extra_data.clear(); self.extra_data.clear();
self.rules_source_order = 0; self.rules_source_order = 0;
self.num_selectors = 0; self.num_selectors = 0;
@ -2501,6 +2796,9 @@ pub struct Rule {
/// we could repurpose that storage here if we needed to. /// we could repurpose that storage here if we needed to.
pub source_order: u32, pub source_order: u32,
/// The current layer id of this style rule.
pub layer_id: LayerId,
/// The actual style rule. /// The actual style rule.
#[cfg_attr( #[cfg_attr(
feature = "gecko", feature = "gecko",
@ -2527,9 +2825,16 @@ impl Rule {
pub fn to_applicable_declaration_block( pub fn to_applicable_declaration_block(
&self, &self,
level: CascadeLevel, level: CascadeLevel,
cascade_data: &CascadeData,
) -> ApplicableDeclarationBlock { ) -> ApplicableDeclarationBlock {
let source = StyleSource::from_rule(self.style_rule.clone()); 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. /// Creates a new Rule.
@ -2538,12 +2843,14 @@ impl Rule {
hashes: AncestorHashes, hashes: AncestorHashes,
style_rule: Arc<Locked<StyleRule>>, style_rule: Arc<Locked<StyleRule>>,
source_order: u32, source_order: u32,
layer_id: LayerId,
) -> Self { ) -> Self {
Rule { Rule {
selector: selector, selector,
hashes: hashes, hashes,
style_rule: style_rule, style_rule,
source_order: source_order, source_order,
layer_id,
} }
} }
} }

View file

@ -11,14 +11,12 @@ use crate::values::generics::box_::Perspective as GenericPerspective;
use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign; use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign;
use crate::values::specified::box_ as specified; 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_::{ 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. /// A computed value for the `vertical-align` property.
pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>; pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>;

View file

@ -439,6 +439,26 @@ pub enum GenericFontFamily {
MozEmoji, 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 { impl Parse for SingleFontFamily {
/// Parse a font-family value. /// Parse a font-family value.
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> { fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
@ -579,14 +599,14 @@ impl FontFamilyList {
self.list = crate::ArcSlice::from_iter(new_list.into_iter()); 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 /// If there's a generic font family on the list which is suitable for user
/// fantasy), then move it to the front of the list. Otherwise, prepend the /// font prioritization, then move it to the front of the list. Otherwise,
/// default generic. /// prepend the default generic.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) {
let index_of_first_generic = self.iter().position(|f| { let index_of_first_generic = self.iter().position(|f| {
match *f { match *f {
SingleFontFamily::Generic(f) => f != GenericFontFamily::Cursive && f != GenericFontFamily::Fantasy, SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(),
_ => false, _ => false,
} }
}); });

View file

@ -24,6 +24,8 @@ use std::f32::consts::PI;
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss}; use style_traits::{CssWriter, ToCss};
pub use specified::ImageRendering;
/// Computed values for an image according to CSS-IMAGES. /// Computed values for an image according to CSS-IMAGES.
/// <https://drafts.csswg.org/css-images/#image-values> /// <https://drafts.csswg.org/css-images/#image-values>
pub type Image = pub type Image =

View file

@ -189,7 +189,7 @@ impl Size {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
GenericSize::MinContent | GenericSize::MinContent |
GenericSize::MaxContent | GenericSize::MaxContent |
GenericSize::MozFitContent | GenericSize::FitContent |
GenericSize::MozAvailable | GenericSize::MozAvailable |
GenericSize::FitContentFunction(_) => false GenericSize::FitContentFunction(_) => false
} }

View file

@ -44,7 +44,7 @@ pub use self::basic_shape::FillRule;
pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderImageSlice, BorderImageWidth}; 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_::{Appearance, BreakBetween, BreakWithin, Clear, Float};
pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; 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::{FontVariantAlternates, FontWeight};
pub use self::font::{FontVariantEastAsian, FontVariationSettings}; pub use self::font::{FontVariantEastAsian, FontVariationSettings};
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; 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::{CSSPixelLength, NonNegativeLength};
pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber};
pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size};

View file

@ -157,7 +157,7 @@ pub enum GenericSize<LengthPercent> {
MinContent, MinContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[animation(error)] #[animation(error)]
MozFitContent, FitContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[animation(error)] #[animation(error)]
MozAvailable, MozAvailable,
@ -207,15 +207,13 @@ pub enum GenericMaxSize<LengthPercent> {
None, None,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[animation(error)] #[animation(error)]
#[parse(aliases = "-moz-max-content")]
MaxContent, MaxContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[animation(error)] #[animation(error)]
#[parse(aliases = "-moz-min-content")]
MinContent, MinContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[animation(error)] #[animation(error)]
MozFitContent, FitContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[animation(error)] #[animation(error)]
MozAvailable, MozAvailable,

View file

@ -687,7 +687,7 @@ pub trait IsParallelTo {
impl<Number, Angle> ToCss for Rotate<Number, Angle> impl<Number, Angle> ToCss for Rotate<Number, Angle>
where where
Number: Copy + ToCss, Number: Copy + ToCss + Zero,
Angle: ToCss, Angle: ToCss,
(Number, Number, Number): IsParallelTo, (Number, Number, Number): IsParallelTo,
{ {
@ -700,25 +700,41 @@ where
Rotate::None => dest.write_str("none"), Rotate::None => dest.write_str("none"),
Rotate::Rotate(ref angle) => angle.to_css(dest), Rotate::Rotate(ref angle) => angle.to_css(dest),
Rotate::Rotate3D(x, y, z, ref angle) => { Rotate::Rotate3D(x, y, z, ref angle) => {
// If a 3d rotation is specified, the property must serialize with an axis // If the axis is parallel with the x or y axes, it must serialize as the
// specified. If the axis is parallel with the x, y, or z axises, it must // appropriate keyword. If a rotation about the z axis (that is, in 2D) is
// serialize as the appropriate keyword. // specified, the property must serialize as just an <angle>
//
// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
let v = (x, y, z); let v = (x, y, z);
if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { let axis = if x.is_zero() && y.is_zero() && z.is_zero() {
dest.write_char('x')?; // 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.)) { } 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.)) { } 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 { } else {
None
};
match axis {
Some(a) => dest.write_str(a)?,
None => {
x.to_css(dest)?; x.to_css(dest)?;
dest.write_char(' ')?; dest.write_char(' ')?;
y.to_css(dest)?; y.to_css(dest)?;
dest.write_char(' ')?; dest.write_char(' ')?;
z.to_css(dest)?; z.to_css(dest)?;
}
dest.write_char(' ')?; dest.write_char(' ')?;
}
}
angle.to_css(dest) angle.to_css(dest)
}, },
} }

View file

@ -464,29 +464,34 @@ impl ToCss for CustomIdent {
} }
} }
/// The <timeline-name> or <keyframes-name>.
/// The definition of these two names are the same, so we use the same type for them.
///
/// <https://drafts.csswg.org/css-animations-2/#typedef-timeline-name>
/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name> /// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name>
#[derive( #[derive(
Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)] )]
pub enum KeyframesName { #[repr(C, u8)]
pub enum TimelineOrKeyframesName {
/// <custom-ident> /// <custom-ident>
Ident(CustomIdent), Ident(CustomIdent),
/// <string> /// <string>
QuotedString(Atom), QuotedString(Atom),
} }
impl KeyframesName { impl TimelineOrKeyframesName {
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name> /// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
pub fn from_ident(value: &str) -> Self { pub fn from_ident(value: &str) -> Self {
let location = SourceLocation { line: 0, column: 0 }; let location = SourceLocation { line: 0, column: 0 };
let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok(); let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok();
match custom_ident { match custom_ident {
Some(ident) => KeyframesName::Ident(ident), Some(ident) => Self::Ident(ident),
None => KeyframesName::QuotedString(value.into()), None => Self::QuotedString(value.into()),
} }
} }
/// Create a new KeyframesName from Atom. /// Create a new TimelineOrKeyframesName from Atom.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self { pub fn from_atom(atom: Atom) -> Self {
debug_assert_ne!(atom, atom!("")); debug_assert_ne!(atom, atom!(""));
@ -494,19 +499,19 @@ impl KeyframesName {
// FIXME: We might want to preserve <string>, but currently Gecko // FIXME: We might want to preserve <string>, but currently Gecko
// stores both of <custom-ident> and <string> into nsAtom, so // stores both of <custom-ident> and <string> into nsAtom, so
// we can't tell it. // we can't tell it.
KeyframesName::Ident(CustomIdent(atom)) Self::Ident(CustomIdent(atom))
} }
/// The name as an Atom /// The name as an Atom
pub fn as_atom(&self) -> &Atom { pub fn as_atom(&self) -> &Atom {
match *self { match *self {
KeyframesName::Ident(ref ident) => &ident.0, Self::Ident(ref ident) => &ident.0,
KeyframesName::QuotedString(ref atom) => atom, 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 /// 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`. /// only needed for background-size serialization, which special-cases `auto`.
@ -515,13 +520,13 @@ pub trait IsAuto {
fn is_auto(&self) -> bool; fn is_auto(&self) -> bool;
} }
impl PartialEq for KeyframesName { impl PartialEq for TimelineOrKeyframesName {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.as_atom() == other.as_atom() self.as_atom() == other.as_atom()
} }
} }
impl hash::Hash for KeyframesName { impl hash::Hash for TimelineOrKeyframesName {
fn hash<H>(&self, state: &mut H) fn hash<H>(&self, state: &mut H)
where where
H: hash::Hasher, H: hash::Hasher,
@ -530,32 +535,40 @@ impl hash::Hash for KeyframesName {
} }
} }
impl Parse for KeyframesName { impl Parse for TimelineOrKeyframesName {
fn parse<'i, 't>( fn parse<'i, 't>(
_context: &ParserContext, _context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location(); let location = input.current_source_location();
match *input.next()? { match *input.next()? {
Token::Ident(ref s) => Ok(KeyframesName::Ident(CustomIdent::from_ident( Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident(
location, location,
s, s,
&["none"], &["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())), ref t => Err(location.new_unexpected_token_error(t.clone())),
} }
} }
} }
impl ToCss for KeyframesName { impl ToCss for TimelineOrKeyframesName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where where
W: Write, W: Write,
{ {
match *self { match *self {
KeyframesName::Ident(ref ident) => ident.to_css(dest), Self::Ident(ref ident) => ident.to_css(dest),
KeyframesName::QuotedString(ref atom) => atom.to_string().to_css(dest), Self::QuotedString(ref atom) => atom.to_string().to_css(dest),
} }
} }
} }
/// The typedef of <timeline-name>.
pub type TimelineName = TimelineOrKeyframesName;
/// The typedef of <keyframes-name>.
pub type KeyframesName = TimelineOrKeyframesName;

View file

@ -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`. /// Return `0deg`.
pub fn zero() -> Self { pub fn zero() -> Self {
Self::from_degrees(0.0, false) Self::from_degrees(0.0, false)
@ -141,6 +150,13 @@ impl Angle {
self.value.degrees() 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. /// Whether this specified angle came from a `calc()` expression.
#[inline] #[inline]
pub fn was_calc(&self) -> bool { pub fn was_calc(&self) -> bool {

View file

@ -13,7 +13,7 @@ use crate::values::generics::box_::Perspective as GenericPerspective;
use crate::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword}; use crate::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
use crate::values::specified::length::{LengthPercentage, NonNegativeLength}; use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
use crate::values::specified::{AllowQuirks, Number}; use crate::values::specified::{AllowQuirks, Number};
use crate::values::{CustomIdent, KeyframesName}; use crate::values::{CustomIdent, KeyframesName, TimelineName};
use crate::Atom; use crate::Atom;
use cssparser::Parser; use cssparser::Parser;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
@ -110,8 +110,6 @@ pub enum DisplayInside {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
MozBox, MozBox,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
MozStack,
#[cfg(feature = "gecko")]
MozDeck, MozDeck,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
MozPopup, MozPopup,
@ -226,8 +224,6 @@ impl Display {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox); pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox);
#[cfg(feature = "gecko")] #[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); pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck);
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup); pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup);
@ -608,8 +604,6 @@ impl Parse for Display {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
"-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox, "-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox,
#[cfg(feature = "gecko")] #[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, "-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
"-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup, "-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup,
@ -768,6 +762,67 @@ impl Parse for AnimationName {
} }
} }
/// A value for the <single-animation-timeline>.
///
/// 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 animations 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<Self, ParseError<'i>> {
// 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 /// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis
#[allow(missing_docs)] #[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]

View file

@ -21,7 +21,7 @@ use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
/// The name of the mathematical function that we're parsing. /// The name of the mathematical function that we're parsing.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Parse)]
pub enum MathFunction { pub enum MathFunction {
/// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
Calc, Calc,
@ -31,6 +31,18 @@ pub enum MathFunction {
Max, Max,
/// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
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. /// 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. /// A calc node representation for specified values.
pub type CalcNode = generic::GenericCalcNode<Leaf>; pub type CalcNode = generic::GenericCalcNode<Leaf>;
@ -301,6 +320,17 @@ impl CalcNode {
let function = CalcNode::math_function(name, location)?; let function = CalcNode::math_function(name, location)?;
CalcNode::parse(context, input, function, expected_unit) 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())), (t, _) => Err(location.new_unexpected_token_error(t.clone())),
} }
} }
@ -350,6 +380,47 @@ impl CalcNode {
Ok(Self::MinMax(arguments.into(), op)) 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>, name: &CowRcStr<'i>,
location: cssparser::SourceLocation, location: cssparser::SourceLocation,
) -> Result<MathFunction, ParseError<'i>> { ) -> Result<MathFunction, ParseError<'i>> {
Ok(match_ignore_ascii_case! { &*name, use self::MathFunction::*;
"calc" => MathFunction::Calc,
"min" => MathFunction::Min, let function = match MathFunction::from_ident(&*name) {
"max" => MathFunction::Max, Ok(f) => f,
"clamp" => MathFunction::Clamp, Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
_ => 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. /// Convenience parsing function for integers.

View file

@ -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 /// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color { pub enum Color {
@ -222,10 +208,9 @@ pub enum Color {
}, },
/// A complex color value from computed value /// A complex color value from computed value
Complex(ComputedColor), Complex(ComputedColor),
/// Either a system color, or a `-moz-system-color(<system-color>, light|dark)` /// A system color.
/// function which allows chrome code to choose between color schemes.
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
System(SystemColor, SystemColorScheme), System(SystemColor),
/// A color mix. /// A color mix.
ColorMix(Box<ColorMix>), ColorMix(Box<ColorMix>),
/// Quirksmode-only rule for inheriting color from the body /// Quirksmode-only rule for inheriting color from the body
@ -233,36 +218,19 @@ pub enum Color {
InheritFromBodyQuirk, 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)] #[allow(missing_docs)]
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
#[repr(u8)] #[repr(u8)]
pub enum SystemColor { 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")] #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectBackgroundDisabled, TextSelectBackgroundDisabled,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
@ -310,6 +278,7 @@ pub enum SystemColor {
#[css(skip)] #[css(skip)]
ThemedScrollbarThumbInactive, ThemedScrollbarThumbInactive,
Activeborder, Activeborder,
/// Background in the (active) titlebar.
Activecaption, Activecaption,
Appworkspace, Appworkspace,
Background, Background,
@ -317,16 +286,23 @@ pub enum SystemColor {
Buttonhighlight, Buttonhighlight,
Buttonshadow, Buttonshadow,
Buttontext, Buttontext,
/// Text color in the (active) titlebar.
Captiontext, Captiontext,
#[parse(aliases = "-moz-field")] #[parse(aliases = "-moz-field")]
Field, Field,
/// Used for disabled field backgrounds.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozDisabledfield,
#[parse(aliases = "-moz-fieldtext")] #[parse(aliases = "-moz-fieldtext")]
Fieldtext, Fieldtext,
Graytext, Graytext,
Highlight, Highlight,
Highlighttext, Highlighttext,
Inactiveborder, Inactiveborder,
/// Background in the (inactive) titlebar.
Inactivecaption, Inactivecaption,
/// Text color in the (inactive) titlebar.
Inactivecaptiontext, Inactivecaptiontext,
Infobackground, Infobackground,
Infotext, Infotext,
@ -351,13 +327,15 @@ pub enum SystemColor {
/// Used to highlight valid regions to drop something onto. /// Used to highlight valid regions to drop something onto.
MozDragtargetzone, MozDragtargetzone,
/// Used for selected but not focused cell backgrounds. /// Used for selected but not focused cell backgrounds.
#[parse(aliases = "-moz-html-cellhighlight")]
MozCellhighlight, MozCellhighlight,
/// Used for selected but not focused cell text. /// Used for selected but not focused cell text.
#[parse(aliases = "-moz-html-cellhighlighttext")]
MozCellhighlighttext, MozCellhighlighttext,
/// Used for selected but not focused html cell backgrounds. /// Used for selected and focused html cell backgrounds.
MozHtmlCellhighlight, Selecteditem,
/// Used for selected but not focused html cell text. /// Used for selected and focused html cell text.
MozHtmlCellhighlighttext, Selecteditemtext,
/// Used to button text background when hovered. /// Used to button text background when hovered.
MozButtonhoverface, MozButtonhoverface,
/// Used to button text color when hovered. /// Used to button text color when hovered.
@ -378,10 +356,16 @@ pub enum SystemColor {
/// Used for button text when pressed. /// Used for button text when pressed.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] #[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. /// Background color of chrome toolbars in active windows.
MozMacChromeActive, MozMacChromeActive,
/// Background color of chrome toolbars in inactive windows. /// Background color of chrome toolbars in inactive windows.
@ -423,6 +407,10 @@ pub enum SystemColor {
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozAccentColorForeground, MozAccentColorForeground,
/// The background-color for :autofill-ed inputs.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozAutofillBackground,
/// Media rebar text. /// Media rebar text.
MozWinMediatext, MozWinMediatext,
/// Communications rebar text. /// Communications rebar text.
@ -457,14 +445,6 @@ pub enum SystemColor {
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozColheaderhovertext, 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)] #[css(skip)]
End, // Just for array-indexing purposes. End, // Just for array-indexing purposes.
} }
@ -472,31 +452,20 @@ pub enum SystemColor {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
impl SystemColor { impl SystemColor {
#[inline] #[inline]
fn compute(&self, cx: &Context, scheme: SystemColorScheme) -> ComputedColor { fn compute(&self, cx: &Context) -> ComputedColor {
use crate::gecko_bindings::bindings; use crate::gecko_bindings::bindings;
let colors = &cx.device().pref_sheet_prefs().mColors; // TODO: We should avoid cloning here most likely, though it's
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); // cheap-ish.
let style_color_scheme =
// TODO: At least Canvas / CanvasText should be color-scheme aware cx.style().get_inherited_ui().clone_color_scheme();
// (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 { let color = unsafe {
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme) bindings::Gecko_ComputeSystemColor(*self, cx.device().document(), &style_color_scheme)
}; };
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor(); return ComputedColor::currentcolor();
} }
color 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 { impl Parse for Color {
fn parse<'i, 't>( fn parse<'i, 't>(
context: &ParserContext, context: &ParserContext,
@ -610,15 +564,7 @@ impl Parse for Color {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
{ {
if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) { if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
return Ok(Color::System(system, SystemColorScheme::Default)); return Ok(Color::System(system));
}
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));
}
} }
} }
@ -657,17 +603,7 @@ impl ToCss for Color {
Color::Complex(_) => Ok(()), Color::Complex(_) => Ok(()),
Color::ColorMix(ref mix) => mix.to_css(dest), Color::ColorMix(ref mix) => mix.to_css(dest),
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
Color::System(system, scheme) => { Color::System(system) => system.to_css(dest),
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(')')
}
},
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => Ok(()), Color::InheritFromBodyQuirk => Ok(()),
} }
@ -835,7 +771,7 @@ impl Color {
)) ))
}, },
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
Color::System(system, scheme) => system.compute(context?, scheme), Color::System(system) => system.compute(context?),
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()), Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
}) })
@ -995,6 +931,11 @@ impl ColorScheme {
bits: ColorSchemeFlags::empty(), bits: ColorSchemeFlags::empty(),
} }
} }
/// Returns the raw bitfield.
pub fn raw_bits(&self) -> u8 {
self.bits.bits
}
} }
impl Parse for ColorScheme { impl Parse for ColorScheme {

View file

@ -49,6 +49,11 @@ impl Parse for IntersectionObserverRootMargin {
context: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
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)?; let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?;
Ok(IntersectionObserverRootMargin(rect)) Ok(IntersectionObserverRootMargin(rect))
} }

View file

@ -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,
}

View file

@ -1235,7 +1235,7 @@ macro_rules! parse_size_non_length {
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
"max-content" | "-moz-max-content" => $size::MaxContent, "max-content" | "-moz-max-content" => $size::MaxContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
"-moz-fit-content" => $size::MozFitContent, "fit-content" | "-moz-fit-content" => $size::FitContent,
#[cfg(feature = "gecko")] #[cfg(feature = "gecko")]
"-moz-available" => $size::MozAvailable, "-moz-available" => $size::MozAvailable,
$auto_or_none => $size::$auto_or_none_ident, $auto_or_none => $size::$auto_or_none_ident,

View file

@ -36,7 +36,7 @@ pub use self::basic_shape::FillRule;
pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; 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_::{Appearance, BreakBetween, BreakWithin};
pub use self::box_::{Clear, Float, Overflow, OverflowAnchor}; pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; 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::{FontVariantEastAsian, FontVariationSettings};
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
pub use self::image::{EndingShape as GradientEndingShape, Gradient}; 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::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};

View file

@ -82,7 +82,12 @@ files = [
# These are ignored to avoid diverging from Gecko # These are ignored to avoid diverging from Gecko
"./components/style/counter_style/mod.rs", "./components/style/counter_style/mod.rs",
"./components/style/properties/helpers.mako.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/rule_parser.rs",
"./components/style/stylesheets/scroll_timeline_rule.rs",
"./components/style/stylist.rs", "./components/style/stylist.rs",
"./components/style/values/computed/font.rs", "./components/style/values/computed/font.rs",
"./components/style/values/computed/image.rs", "./components/style/values/computed/image.rs",

View file

@ -15,6 +15,7 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
use style::selector_map::SelectorMap; use style::selector_map::SelectorMap;
use style::selector_parser::{SelectorImpl, SelectorParser}; use style::selector_parser::{SelectorImpl, SelectorParser};
use style::shared_lock::SharedRwLock; use style::shared_lock::SharedRwLock;
use style::stylesheets::layer_rule::LayerId;
use style::stylesheets::StyleRule; use style::stylesheets::StyleRule;
use style::stylist::needs_revalidation_for_testing; use style::stylist::needs_revalidation_for_testing;
use style::stylist::{Rule, Stylist}; use style::stylist::{Rule, Stylist};
@ -52,6 +53,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec<Vec<Rule>>, SharedRwLock) {
AncestorHashes::new(s, QuirksMode::NoQuirks), AncestorHashes::new(s, QuirksMode::NoQuirks),
locked.clone(), locked.clone(),
i as u32, i as u32,
LayerId::root(),
) )
}) })
.collect() .collect()

View file

@ -344,14 +344,5 @@
[Web Animations: property <rotate> 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\]] [Web Animations: property <rotate> 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 expected: FAIL
[CSS Transitions: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL
[CSS Transitions with transition: all: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL
[CSS Animations: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL
[Web Animations: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]] [Web Animations: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL expected: FAIL

View file

@ -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

View file

@ -29,8 +29,5 @@
[The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.] [The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.]
expected: FAIL 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.] [The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical.]
expected: FAIL expected: FAIL