From e47f35aa50df0743ac801a2b310227f1fde110b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 23 May 2023 20:47:47 +0200 Subject: [PATCH 01/94] style: Implement SelectedItem and SelectedItemText system colors Since Highlight / HighlightText are now equivalent to the text selection ones, remove those too. Differential Revision: https://phabricator.services.mozilla.com/D123964 --- components/style/values/specified/color.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index f84883702c7..371d69450b4 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -260,10 +260,6 @@ pub enum SystemColor { #[css(skip)] TextForeground, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectBackground, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectForeground, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] TextSelectBackgroundDisabled, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] TextSelectBackgroundAttention, @@ -355,9 +351,11 @@ pub enum SystemColor { /// Used for selected but not focused cell text. MozCellhighlighttext, /// Used for selected but not focused html cell backgrounds. - MozHtmlCellhighlight, + #[parse(aliases = "-moz-html-cellhighlight")] + Selecteditem, /// Used for selected but not focused html cell text. - MozHtmlCellhighlighttext, + #[parse(aliases = "-moz-html-cellhighlighttext")] + Selecteditemtext, /// Used to button text background when hovered. MozButtonhoverface, /// Used to button text color when hovered. From 5f5ea8603df3fd5dadbfcaaee2619a311af77b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 23 May 2023 20:49:05 +0200 Subject: [PATCH 02/94] style: Fix selecteditem color on macOS This is an oversight. I made selecteditem be -moz-html-cellhighlight, but that's for inactive cells. Use the inactive cell color everywhere (though android doesn't differentiate). This matches other browsers and what was reviewed on this bug. MANUAL PUSH: The semi-transparent text-selection-disabled color caused one test failure CLOSED TREE. --- components/style/values/specified/color.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 371d69450b4..332a5617805 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -347,14 +347,14 @@ pub enum SystemColor { /// Used to highlight valid regions to drop something onto. MozDragtargetzone, /// Used for selected but not focused cell backgrounds. + #[parse(aliases = "-moz-html-cellhighlight")] MozCellhighlight, /// Used for selected but not focused cell text. - MozCellhighlighttext, - /// Used for selected but not focused html cell backgrounds. - #[parse(aliases = "-moz-html-cellhighlight")] - Selecteditem, - /// Used for selected but not focused html cell text. #[parse(aliases = "-moz-html-cellhighlighttext")] + MozCellhighlighttext, + /// Used for selected and focused html cell backgrounds. + Selecteditem, + /// Used for selected and focused html cell text. Selecteditemtext, /// Used to button text background when hovered. MozButtonhoverface, From 036056d2a9478b46737792c0997b6fdcdf102c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 23 May 2023 20:51:22 +0200 Subject: [PATCH 03/94] style: Move image-rendering out of mako It's easier to touch in the future that way, even though the derive list is massive. Differential Revision: https://phabricator.services.mozilla.com/D124377 --- components/style/properties/data.py | 1 + .../longhands/inherited_box.mako.rs | 12 +++---- components/style/values/computed/image.rs | 2 ++ components/style/values/computed/mod.rs | 2 +- components/style/values/specified/image.rs | 35 +++++++++++++++++++ components/style/values/specified/mod.rs | 2 +- 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index b2ab520ad3d..49157860fbb 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -476,6 +476,7 @@ class Longhand(Property): "FontWeight", "GreaterThanOrEqualToOneNumber", "GridAutoFlow", + "ImageRendering", "InitialLetter", "Integer", "JustifyContent", diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs index cea3fde021a..294f36bd96f 100644 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ b/components/style/properties/longhands/inherited_box.mako.rs @@ -69,17 +69,13 @@ ${helpers.single_keyword( // According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` // And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337) -${helpers.single_keyword( +${helpers.predefined_type( "image-rendering", - "auto crisp-edges", + "ImageRendering", + "computed::ImageRendering::Auto", engines="gecko servo-2013 servo-2020", - extra_gecko_values="optimizespeed optimizequality", - extra_servo_2013_values="pixelated", - extra_servo_2020_values="pixelated", - gecko_aliases="-moz-crisp-edges=crisp-edges", - gecko_enum_prefix="StyleImageRendering", - animation_value_type="discrete", spec="https://drafts.csswg.org/css-images/#propdef-image-rendering", + animation_value_type="discrete", )} ${helpers.single_keyword( diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 6290ac68a5d..a0cb8f3fe45 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -24,6 +24,8 @@ use std::f32::consts::PI; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; +pub use specified::ImageRendering; + /// Computed values for an image according to CSS-IMAGES. /// pub type Image = diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index c87142ac1b5..49fc7b8bd33 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -62,7 +62,7 @@ pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis}; pub use self::font::{FontVariantAlternates, FontWeight}; pub use self::font::{FontVariantEastAsian, FontVariationSettings}; pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; -pub use self::image::{Gradient, Image, LineDirection, MozImageRect}; +pub use self::image::{Gradient, Image, LineDirection, MozImageRect, ImageRendering}; pub use self::length::{CSSPixelLength, NonNegativeLength}; pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index f2167e52800..db30fc460f7 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -1226,3 +1226,38 @@ 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, + #[parse(aliases = "-moz-crisp-edges")] + CrispEdges, + // 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. + Optimizespeed, + Optimizequality, + #[cfg(feature = "servo")] + Pixelated, +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 6cd952cd82f..d5ea7f8d9cc 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -55,7 +55,7 @@ pub use self::font::{FontVariantAlternates, FontWeight}; pub use self::font::{FontVariantEastAsian, FontVariationSettings}; pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom}; pub use self::image::{EndingShape as GradientEndingShape, Gradient}; -pub use self::image::{Image, MozImageRect}; +pub use self::image::{Image, MozImageRect, ImageRendering}; pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; From 212733900e45f6f0cd809aaa8637e28b9f9cd3eb Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Thu, 25 May 2023 20:01:12 +0200 Subject: [PATCH 04/94] Further changes required by Servo --- components/style/values/specified/image.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index db30fc460f7..984cc9b28a4 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -1256,7 +1256,9 @@ pub enum ImageRendering { // 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, #[cfg(feature = "servo")] Pixelated, From 693806b853de24b5113bb9b0cae1bd202dea5ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 25 May 2023 20:07:53 +0200 Subject: [PATCH 05/94] style: Implement image-rendering: smooth and image-rendering: pixelated Also, more directly go from StyleImageRendering to wr::ImageRendering. * image-rendering: smooth the non-deprecated version of OptimizeQuality, which maps to SamplingFilter::LINEAR / wr::ImageRendering::Auto (which uses gl::LINEAR). * image-rendering: pixelated maps to wr::ImageRendering::Pixelated / SamplingFilter::POINT which is the same crisp-edges does. Note that this uncovers that we were mapping image-rendering: crisp-edges to wr::ImageRendering::Pixelated. I'm going to preserve behavior on this patch but we should consider switching that to map to wr::ImageRendering::CrispEdges on a follow-up (filed bug 1728831 for this). Differential Revision: https://phabricator.services.mozilla.com/D124378 --- components/style/values/specified/image.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 984cc9b28a4..f5c5c80ef96 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -1247,8 +1247,10 @@ impl MozImageRect { #[repr(u8)] pub enum ImageRendering { Auto, + Smooth, #[parse(aliases = "-moz-crisp-edges")] CrispEdges, + Pixelated, // From the spec: // // This property previously accepted the values optimizeSpeed and @@ -1256,10 +1258,9 @@ pub enum ImageRendering { // 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, - #[cfg(feature = "servo")] - Pixelated, } From 8eb5fa21fd515267e5eff43cfdd4ba48f345ff11 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Thu, 25 May 2023 20:10:47 +0200 Subject: [PATCH 06/94] Further changes required by Servo --- components/style/values/specified/image.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index f5c5c80ef96..095ce20f4c7 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -1247,6 +1247,7 @@ impl MozImageRect { #[repr(u8)] pub enum ImageRendering { Auto, + #[cfg(feature = "gecko")] Smooth, #[parse(aliases = "-moz-crisp-edges")] CrispEdges, From 5fad3b4cde5a8f57dfc4cbeb52e555575529e209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 25 May 2023 20:12:51 +0200 Subject: [PATCH 07/94] style: Add some scaffolding for @layer rules Not hooked anywhere yet, so this doesn't change behavior, but adds the basic data model etc. Adding parsing support requires some changes to cssparser to allow the same at rule to be block and statement-like at the same time, so better done separately. Differential Revision: https://phabricator.services.mozilla.com/D124079 --- components/style/invalidation/stylesheets.rs | 4 +- components/style/stylesheets/layer_rule.rs | 171 ++++++++++++++++++ components/style/stylesheets/mod.rs | 23 ++- .../style/stylesheets/rules_iterator.rs | 9 + components/style/stylesheets/stylesheet.rs | 5 +- components/style/stylist.rs | 1 + 6 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 components/style/stylesheets/layer_rule.rs diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index edecc3fc243..f4dc0c5a443 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -555,7 +555,7 @@ impl StylesheetInvalidationSet { self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) }, - Document(..) | Import(..) | Media(..) | Supports(..) => { + Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { if !is_generic_change && !EffectiveRules::is_effective(guard, device, quirks_mode, rule) { @@ -596,7 +596,7 @@ impl StylesheetInvalidationSet { } } }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) => { + Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs new file mode 100644 index 00000000000..873c9e1275c --- /dev/null +++ b/components/style/stylesheets/layer_rule.rs @@ -0,0 +1,171 @@ +/* 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}; + +/// A ``: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name +#[derive(Clone, Debug, ToShmem)] +pub struct LayerName(SmallVec<[AtomIdent; 1]>); + +impl Parse for LayerName { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut result = SmallVec::new(); + result.push(AtomIdent::from(&**input.expect_ident()?)); + loop { + let next_name = input.try_parse(|input| -> Result> { + match input.next_including_whitespace()? { + Token::Delim('.') => {}, + other => { + let t = other.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + } + + let name = match input.next_including_whitespace()? { + Token::Ident(ref ident) => ident, + other => { + let t = other.clone(); + return Err(input.new_unexpected_token_error(t)); + }, + }; + + Ok(AtomIdent::from(&**name)) + }); + + match next_name { + Ok(name) => result.push(name), + Err(..) => break, + } + } + Ok(LayerName(result)) + } +} + +impl ToCss for LayerName { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + let mut first = true; + for name in self.0.iter() { + if !first { + dest.write_char('.')?; + } + first = false; + name.to_css(dest)?; + } + Ok(()) + } +} + +/// The kind of layer rule this is. +#[derive(Debug, ToShmem)] +pub enum LayerRuleKind { + /// A block `@layer ? { ... }` + Block { + /// The layer name, or `None` if anonymous. + name: Option, + /// The nested rules. + rules: Arc>, + }, + /// A statement `@layer , , ;` + Statement { + /// The list of layers to sort. + names: SmallVec<[LayerName; 3]>, + }, +} + +/// A [`@layer`][layer] urle. +/// +/// [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 { + name.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + } + 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_str(", ")?; + } + first = false; + name.to_css(&mut writer)?; + } + dest.write_char(';') + }, + } + } +} + +impl DeepCloneWithLock for LayerRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + Self { + kind: match self.kind { + LayerRuleKind::Block { + ref name, + ref rules, + } => LayerRuleKind::Block { + name: name.clone(), + rules: Arc::new( + lock.wrap( + rules + .read_with(guard) + .deep_clone_with_lock(lock, guard, params), + ), + ), + }, + LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement { + names: names.clone(), + }, + }, + source_location: self.source_location.clone(), + } + } +} diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index fd9be56b5b5..dbb42667b60 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -11,6 +11,7 @@ mod font_face_rule; pub mod font_feature_values_rule; pub mod import_rule; pub mod keyframes_rule; +mod layer_rule; mod loader; mod media_rule; mod namespace_rule; @@ -49,6 +50,7 @@ pub use self::font_face_rule::FontFaceRule; pub use self::font_feature_values_rule::FontFeatureValuesRule; pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; +pub use self::layer_rule::LayerRule; pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; @@ -257,6 +259,7 @@ pub enum CssRule { Supports(Arc>), Page(Arc>), Document(Arc>), + Layer(Arc>), } impl CssRule { @@ -297,11 +300,14 @@ impl CssRule { CssRule::Document(ref lock) => { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, + + // TODO(emilio): Add memory reporting for @layer rules. + CssRule::Layer(_) => 0, } } } -#[allow(missing_docs)] +/// https://drafts.csswg.org/cssom-1/#dom-cssrule-type #[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] pub enum CssRuleType { // https://drafts.csswg.org/cssom/#the-cssrule-interface @@ -323,10 +329,13 @@ pub enum CssRuleType { Supports = 12, // https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface Document = 13, - // https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues + // https://drafts.csswg.org/css-fonts/#om-fontfeaturevalues FontFeatureValues = 14, // https://drafts.csswg.org/css-device-adapt/#css-rule-interface Viewport = 15, + // After viewport, all rules should return 0 from the API, but we still need + // a constant somewhere. + Layer = 16, } #[allow(missing_docs)] @@ -353,6 +362,7 @@ impl CssRule { CssRule::Supports(_) => CssRuleType::Supports, CssRule::Page(_) => CssRuleType::Page, CssRule::Document(_) => CssRuleType::Document, + CssRule::Layer(_) => CssRuleType::Layer, } } @@ -361,6 +371,8 @@ impl CssRule { // CssRule::Charset(..) => State::Start, CssRule::Import(..) => State::Imports, CssRule::Namespace(..) => State::Namespaces, + // TODO(emilio): We'll need something here for non-block layer + // rules. _ => State::Body, } } @@ -485,6 +497,12 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, + CssRule::Layer(ref arc) => { + let rule = arc.read_with(guard); + CssRule::Layer(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), + )) + } } } } @@ -505,6 +523,7 @@ impl ToCssWithGuard for CssRule { CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::Layer(ref lock) => lock.read_with(guard).to_css(guard, dest), } } } diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index a7010ff066e..b1921e63e07 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -105,6 +105,15 @@ where } 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, + } + } } } diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index b8e7f246c19..91a407f5c32 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -367,7 +367,10 @@ impl SanitizationKind { CssRule::Document(..) | CssRule::Media(..) | CssRule::Supports(..) | - CssRule::Import(..) => false, + CssRule::Import(..) | + // TODO(emilio): Perhaps Layer should not be always sanitized? But + // we sanitize @media and co, so this seems safer for now. + CssRule::Layer(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, diff --git a/components/style/stylist.rs b/components/style/stylist.rs index fb7334d3600..643a1454e00 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2348,6 +2348,7 @@ impl CascadeData { CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | + CssRule::Layer(..) | CssRule::FontFeatureValues(..) => { // Not affected by device changes. continue; From 74787a9c464058509e09bc262b02eb2010968e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 26 May 2023 11:05:19 +0200 Subject: [PATCH 08/94] style: Restore an #[allow] that will otherwise cause warnings. --- components/style/stylesheets/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index dbb42667b60..dc3f39e03b0 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -308,6 +308,7 @@ impl CssRule { } /// https://drafts.csswg.org/cssom-1/#dom-cssrule-type +#[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] pub enum CssRuleType { // https://drafts.csswg.org/cssom/#the-cssrule-interface From 32b3cb291f96ab570a54788e0b6941d3da2a21ef Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 12 May 2023 22:06:12 +0200 Subject: [PATCH 09/94] Further changes required by Servo --- components/script/dom/cssrule.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index a541ee411c7..5945123ac51 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -105,6 +105,7 @@ impl CSSRule { }, StyleCssRule::Page(_) => unreachable!(), StyleCssRule::Document(_) => unimplemented!(), // TODO + StyleCssRule::Layer(_) => unimplemented!(), // TODO } } From 623c8d8d450baa167b13e6b5b245e93fab60e7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 26 May 2023 15:01:51 +0200 Subject: [PATCH 10/94] style: Tweak at-rule parsing APIs to support cascade layers This needs https://github.com/servo/rust-cssparser/pull/287 and a cssparser update. Differential Revision: https://phabricator.services.mozilla.com/D124216 --- .../stylesheets/font_feature_values_rule.rs | 11 ++--- components/style/stylesheets/rule_parser.rs | 41 ++++++++----------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/components/style/stylesheets/font_feature_values_rule.rs b/components/style/stylesheets/font_feature_values_rule.rs index dc9e9f333db..dc128e77fb0 100644 --- a/components/style/stylesheets/font_feature_values_rule.rs +++ b/components/style/stylesheets/font_feature_values_rule.rs @@ -396,13 +396,14 @@ macro_rules! font_feature_values_blocks { type AtRule = (); type Error = StyleParseErrorKind<'i>; - fn parse_prelude<'t>(&mut self, - name: CowRcStr<'i>, - input: &mut Parser<'i, 't>) - -> Result> { + fn parse_prelude<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result> { match_ignore_ascii_case! { &*name, $( - $name => Ok(Self::Prelude::$ident_camel), + $name => Ok(BlockType::$ident_camel), )* _ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 914af2cfb66..930d6360061 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -27,9 +27,7 @@ use crate::values::computed::font::FamilyName; use crate::values::{CssUrl, CustomIdent, KeyframesName}; use crate::{Namespace, Prefix}; use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser}; -use cssparser::{ - BasicParseError, BasicParseErrorKind, CowRcStr, ParseErrorKind, ParserState, SourcePosition, -}; +use cssparser::{BasicParseError, BasicParseErrorKind, CowRcStr, ParserState, SourcePosition}; use selectors::SelectorList; use servo_arc::Arc; use style_traits::{ParseError, StyleParseErrorKind}; @@ -182,7 +180,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, - ) -> Result> { + ) -> Result> { match_ignore_ascii_case! { &*name, "import" => { if !self.check_state(State::Imports) { @@ -206,9 +204,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { let media = MediaList::parse(&self.context, input); let media = Arc::new(self.shared_lock.wrap(media)); - let prelude = AtRulePrelude::Import(url, media); - - return Ok(prelude); + return Ok(AtRulePrelude::Import(url, media)); }, "namespace" => { if !self.check_state(State::Namespaces) { @@ -225,8 +221,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { Err(e) => return Err(e.into()), }; let url = Namespace::from(maybe_namespace.as_ref()); - let prelude = AtRulePrelude::Namespace(prefix, url); - return Ok(prelude); + return Ok(AtRulePrelude::Namespace(prefix, url)); }, // @charset is removed by rust-cssparser if it’s the first rule in the stylesheet // anything left is invalid. @@ -261,7 +256,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &mut self, prelude: AtRulePrelude, start: &ParserState, - ) -> Result { + ) -> Result { let rule = match prelude { AtRulePrelude::Import(url, media) => { let loader = self @@ -383,14 +378,14 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { "media" => { let media_queries = MediaList::parse(self.context, input); let arc = Arc::new(self.shared_lock.wrap(media_queries)); - Ok(Self::Prelude::Media(arc)) + Ok(AtRulePrelude::Media(arc)) }, "supports" => { let cond = SupportsCondition::parse(input)?; - Ok(Self::Prelude::Supports(cond)) + Ok(AtRulePrelude::Supports(cond)) }, "font-face" => { - Ok(Self::Prelude::FontFace) + Ok(AtRulePrelude::FontFace) }, "font-feature-values" => { if !cfg!(feature = "gecko") { @@ -398,7 +393,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) } let family_names = parse_family_name_list(self.context, input)?; - Ok(Self::Prelude::FontFeatureValues(family_names)) + Ok(AtRulePrelude::FontFeatureValues(family_names)) }, "counter-style" => { if !cfg!(feature = "gecko") { @@ -406,11 +401,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) } let name = parse_counter_style_name_definition(input)?; - Ok(Self::Prelude::CounterStyle(name)) + Ok(AtRulePrelude::CounterStyle(name)) }, "viewport" => { if viewport_rule::enabled() { - Ok(Self::Prelude::Viewport) + Ok(AtRulePrelude::Viewport) } else { Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) } @@ -430,7 +425,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { } let name = KeyframesName::parse(self.context, input)?; - Ok(Self::Prelude::Keyframes(name, prefix)) + Ok(AtRulePrelude::Keyframes(name, prefix)) }, "page" => { if cfg!(feature = "gecko") { @@ -447,7 +442,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { } let cond = DocumentCondition::parse(self.context, input)?; - Ok(Self::Prelude::Document(cond)) + Ok(AtRulePrelude::Document(cond)) }, _ => Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) } @@ -577,12 +572,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, - _ => Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(CowRcStr::from( - "Unsupported AtRule Prelude.", - ))), - location: start.source_location(), - }), + AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { + // These rules don't have blocks. + Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)) + }, } } } From dbb51abc62a8eb8e3e46549fb0b78038386b7bf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 26 May 2023 15:16:47 +0200 Subject: [PATCH 11/94] style: Hook up basic @layer rule parsing Disabled, and of course doing nothing for now still, but this is another piece that is useful to get reviewed separately. Don't allow layers to be interleaved with @import / @namespace rules as per https://github.com/w3c/csswg-drafts/issues/6522. Differential Revision: https://phabricator.services.mozilla.com/D124229 --- components/style/stylesheets/layer_rule.rs | 4 +- components/style/stylesheets/mod.rs | 3 +- components/style/stylesheets/rule_parser.rs | 110 ++++++++++++-------- 3 files changed, 68 insertions(+), 49 deletions(-) diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index 873c9e1275c..c8a04ca6563 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -90,11 +90,11 @@ pub enum LayerRuleKind { /// A statement `@layer , , ;` Statement { /// The list of layers to sort. - names: SmallVec<[LayerName; 3]>, + names: Vec, }, } -/// A [`@layer`][layer] urle. +/// A [`@layer`][layer] rule. /// /// [layer]: https://drafts.csswg.org/css-cascade-5/#layering #[derive(Debug, ToShmem)] diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index dc3f39e03b0..b4e10ee368e 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -372,8 +372,7 @@ impl CssRule { // CssRule::Charset(..) => State::Start, CssRule::Import(..) => State::Imports, CssRule::Namespace(..) => State::Namespaces, - // TODO(emilio): We'll need something here for non-block layer - // rules. + // TODO(emilio): Do we need something for EarlyLayers? _ => State::Body, } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 930d6360061..fa92351ae01 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -18,11 +18,12 @@ use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::keyframes_rule::parse_keyframe_list; use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; +use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind}; use crate::stylesheets::viewport_rule; use crate::stylesheets::AllowImportRules; use crate::stylesheets::{CorsMode, DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule}; use crate::stylesheets::{CssRule, CssRuleType, CssRules, RulesMutateError, StylesheetLoader}; -use crate::stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule}; +use crate::stylesheets::{LayerRule, NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule}; use crate::values::computed::font::FamilyName; use crate::values::{CssUrl, CustomIdent, KeyframesName}; use crate::{Namespace, Prefix}; @@ -128,12 +129,14 @@ impl<'b> TopLevelRuleParser<'b> { pub enum State { /// We haven't started parsing rules. Start = 1, - /// We're parsing `@import` rules. - Imports = 2, + /// We're parsing early `@layer` statement rules. + EarlyLayers = 2, + /// We're parsing `@import` and early `@layer` statement rules. + Imports = 3, /// We're parsing `@namespace` rules. - Namespaces = 3, + Namespaces = 4, /// We're parsing the main body of the stylesheet. - Body = 4, + Body = 5, } #[derive(Clone, Debug, MallocSizeOf, ToShmem)] @@ -169,6 +172,8 @@ pub enum AtRulePrelude { Import(CssUrl, Arc>), /// A @namespace rule prelude. Namespace(Option, Namespace), + /// A @layer rule prelude. + Layer(Vec), } impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { @@ -290,6 +295,20 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { source_location: start.source_location(), }))) }, + AtRulePrelude::Layer(names) => { + if names.is_empty() { + return Err(()); + } + if self.state <= State::EarlyLayers { + self.state = State::EarlyLayers; + } else { + self.state = State::Body; + } + CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { + kind: LayerRuleKind::Statement { names }, + source_location: start.source_location(), + }))) + }, _ => return Err(()), }; @@ -374,41 +393,37 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { name: CowRcStr<'i>, input: &mut Parser<'i, 't>, ) -> Result> { - match_ignore_ascii_case! { &*name, + Ok(match_ignore_ascii_case! { &*name, "media" => { let media_queries = MediaList::parse(self.context, input); let arc = Arc::new(self.shared_lock.wrap(media_queries)); - Ok(AtRulePrelude::Media(arc)) + AtRulePrelude::Media(arc) }, "supports" => { let cond = SupportsCondition::parse(input)?; - Ok(AtRulePrelude::Supports(cond)) + AtRulePrelude::Supports(cond) }, "font-face" => { - Ok(AtRulePrelude::FontFace) + AtRulePrelude::FontFace }, - "font-feature-values" => { - if !cfg!(feature = "gecko") { - // Support for this rule is not fully implemented in Servo yet. - return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "layer" if static_prefs::pref!("layout.css.cascade-layers.enabled") => { + let names = input.try_parse(|input| { + input.parse_comma_separated(|input| { + LayerName::parse(self.context, input) + }) + }).unwrap_or_default(); + AtRulePrelude::Layer(names) + }, + "font-feature-values" if cfg!(feature = "gecko") => { let family_names = parse_family_name_list(self.context, input)?; - Ok(AtRulePrelude::FontFeatureValues(family_names)) + AtRulePrelude::FontFeatureValues(family_names) }, - "counter-style" => { - if !cfg!(feature = "gecko") { - // Support for this rule is not fully implemented in Servo yet. - return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "counter-style" if cfg!(feature = "gecko") => { let name = parse_counter_style_name_definition(input)?; - Ok(AtRulePrelude::CounterStyle(name)) + AtRulePrelude::CounterStyle(name) }, - "viewport" => { - if viewport_rule::enabled() { - Ok(AtRulePrelude::Viewport) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "viewport" if viewport_rule::enabled() => { + AtRulePrelude::Viewport }, "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") { @@ -424,28 +439,17 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) } let name = KeyframesName::parse(self.context, input)?; - - Ok(AtRulePrelude::Keyframes(name, prefix)) + AtRulePrelude::Keyframes(name, prefix) }, - "page" => { - if cfg!(feature = "gecko") { - Ok(Self::Prelude::Page) - } else { - Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + "page" if cfg!(feature = "gecko") => { + AtRulePrelude::Page }, - "-moz-document" => { - if !cfg!(feature = "gecko") { - return Err(input.new_custom_error( - StyleParseErrorKind::UnsupportedAtRule(name.clone()) - )) - } - + "-moz-document" if cfg!(feature = "gecko") => { let cond = DocumentCondition::parse(self.context, input)?; - Ok(AtRulePrelude::Document(cond)) + AtRulePrelude::Document(cond) }, - _ => Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) - } + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone()))) + }) } fn parse_block<'t>( @@ -572,6 +576,22 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, + AtRulePrelude::Layer(names) => { + let name = match names.len() { + 0 => None, + 1 => Some(names.into_iter().next().unwrap()), + _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), + }; + Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap( + LayerRule { + kind: LayerRuleKind::Block { + name, + rules: self.parse_nested_rules(input, CssRuleType::Layer), + }, + source_location: start.source_location(), + }, + )))) + }, AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { // These rules don't have blocks. Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)) From 44e71dee2e7eecf69cdc550fd263d712a86ce1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 26 May 2023 21:43:29 +0200 Subject: [PATCH 12/94] style: Remove cascade layers pref These have been enabled by default for quite a while. Differential Revision: https://phabricator.services.mozilla.com/D175513 --- components/style/stylesheets/rule_parser.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index fa92351ae01..2998aba8fad 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -406,7 +406,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { "font-face" => { AtRulePrelude::FontFace }, - "layer" if static_prefs::pref!("layout.css.cascade-layers.enabled") => { + "layer" => { let names = input.try_parse(|input| { input.parse_comma_separated(|input| { LayerName::parse(self.context, input) From 017036dba854d35b1db6dbd2fdf04c8e53f9a3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 26 May 2023 21:49:57 +0200 Subject: [PATCH 13/94] style: Add attributes to the rule hash See the discussion here: https://twitter.com/Rich_Harris/status/1433153204678799365 This should make attribute selectors roughly as fast as class selectors. I think it's worth trying and see if perf bots complain on micro-benchmarks and stylebench and such. I made attributes more specific than local names, but less specific than classes, which I think makes sense. When doing something like foo[data-bar], filtering by data-bar seems likely to yield less elements than filtering by foo. While at it, remove the bloom filter pref since we shipped it in bug 1704551 for 87 and we haven't heard complaints. Differential Revision: https://phabricator.services.mozilla.com/D124383 --- components/style/bloom.rs | 13 +-- components/style/gecko/selector_parser.rs | 3 +- components/style/selector_map.rs | 110 ++++++++++++++++++---- components/style/sharing/mod.rs | 5 +- components/style/stylist.rs | 10 +- 5 files changed, 110 insertions(+), 31 deletions(-) diff --git a/components/style/bloom.rs b/components/style/bloom.rs index 1840c780506..87fd0cba5c2 100644 --- a/components/style/bloom.rs +++ b/components/style/bloom.rs @@ -126,14 +126,11 @@ where element.each_class(|class| f(class.get_hash())); - #[cfg(feature = "gecko")] - if static_prefs::pref!("layout.css.bloom-filter-attribute-names.enabled") { - element.each_attr_name(|name| { - if !is_attr_name_excluded_from_filter(name) { - f(name.get_hash()) - } - }); - } + element.each_attr_name(|name| { + if !is_attr_name_excluded_from_filter(name) { + f(name.get_hash()) + } + }); } impl Drop for StyleBloom { diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 5379454daa0..7028d554aa2 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -249,8 +249,7 @@ impl ::selectors::SelectorImpl for SelectorImpl { type NonTSPseudoClass = NonTSPseudoClass; fn should_collect_attr_hash(name: &AtomIdent) -> bool { - static_prefs::pref!("layout.css.bloom-filter-attribute-names.enabled") && - !crate::bloom::is_attr_name_excluded_from_filter(name) + !crate::bloom::is_attr_name_excluded_from_filter(name) } } diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 40806ed47af..48ab5cdbe66 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -104,10 +104,14 @@ pub struct SelectorMap { pub class_hash: MaybeCaseInsensitiveHashMap>, /// A hash from local name to rules which contain that local name selector. pub local_name_hash: PrecomputedHashMap>, + /// A hash from attributes to rules which contain that attribute selector. + pub attribute_hash: PrecomputedHashMap>, /// A hash from namespace to rules which contain that namespace selector. pub namespace_hash: PrecomputedHashMap>, /// All other rules. pub other: SmallVec<[T; 1]>, + /// Whether we should bucket by attribute names. + bucket_attributes: bool, /// The number of entries in this map. pub count: usize, } @@ -129,18 +133,29 @@ impl SelectorMap { root: SmallVec::new(), id_hash: MaybeCaseInsensitiveHashMap::new(), class_hash: MaybeCaseInsensitiveHashMap::new(), + attribute_hash: HashMap::default(), local_name_hash: HashMap::default(), namespace_hash: HashMap::default(), other: SmallVec::new(), + bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"), count: 0, } } + /// Trivially constructs an empty `SelectorMap`, with attribute bucketing + /// explicitly disabled. + pub fn new_without_attribute_bucketing() -> Self { + let mut ret = Self::new(); + ret.bucket_attributes = false; + ret + } + /// Clears the hashmap retaining storage. pub fn clear(&mut self) { self.root.clear(); self.id_hash.clear(); self.class_hash.clear(); + self.attribute_hash.clear(); self.local_name_hash.clear(); self.namespace_hash.clear(); self.other.clear(); @@ -218,6 +233,21 @@ impl SelectorMap { } }); + 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, + ) + } + }); + } + if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) { SelectorMap::get_matching_rules( element, @@ -302,6 +332,7 @@ impl SelectorMap { .class_hash .try_entry(class.clone(), quirks_mode)? .or_insert_with(SmallVec::new), + Bucket::Attribute { name, lower_name } | Bucket::LocalName { name, lower_name } => { // If the local name in the selector isn't lowercase, // insert it into the rule hash twice. This means that, @@ -316,13 +347,19 @@ impl SelectorMap { // selector, the rulehash lookup may produce superfluous // selectors, but the subsequent selector matching work // will filter them out. + let is_attribute = matches!($bucket, Bucket::Attribute { .. }); + let hash = if is_attribute { + &mut self.attribute_hash + } else { + &mut self.local_name_hash + }; if name != lower_name { - self.local_name_hash + hash .try_entry(lower_name.clone())? .or_insert_with(SmallVec::new) .try_push($entry.clone())?; } - self.local_name_hash + hash .try_entry(name.clone())? .or_insert_with(SmallVec::new) }, @@ -338,7 +375,7 @@ impl SelectorMap { let bucket = { let mut disjoint_buckets = SmallVec::new(); - let bucket = find_bucket(entry.selector(), &mut disjoint_buckets); + let bucket = find_bucket(entry.selector(), &mut disjoint_buckets, self.bucket_attributes); // See if inserting this selector in multiple entries in the // selector map would be worth it. Consider a case like: @@ -409,8 +446,29 @@ impl SelectorMap { let mut done = false; element.each_class(|class| { - if !done { - if let Some(v) = self.class_hash.get(class, quirks_mode) { + if done { + return; + } + if let Some(v) = self.class_hash.get(class, quirks_mode) { + for entry in v.iter() { + if !f(&entry) { + done = true; + return; + } + } + } + }); + + if done { + return false; + } + + if self.bucket_attributes { + element.each_attr_name(|name| { + if done { + return; + } + if let Some(v) = self.attribute_hash.get(name) { for entry in v.iter() { if !f(&entry) { done = true; @@ -418,10 +476,11 @@ impl SelectorMap { } } } + }); + + if done { + return false; } - }); - if done { - return false; } if let Some(v) = self.local_name_hash.get(element.local_name()) { @@ -507,6 +566,10 @@ enum Bucket<'a> { name: &'a LocalName, lower_name: &'a LocalName, }, + Attribute { + name: &'a LocalName, + lower_name: &'a LocalName, + }, Class(&'a Atom), ID(&'a Atom), Root, @@ -520,9 +583,10 @@ impl<'a> Bucket<'a> { Bucket::Universal => 0, Bucket::Namespace(..) => 1, Bucket::LocalName { .. } => 2, - Bucket::Class(..) => 3, - Bucket::ID(..) => 4, - Bucket::Root => 5, + Bucket::Attribute { .. } => 3, + Bucket::Class(..) => 4, + Bucket::ID(..) => 5, + Bucket::Root => 6, } } @@ -537,11 +601,24 @@ type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>; fn specific_bucket_for<'a>( component: &'a Component, disjoint_buckets: &mut DisjointBuckets<'a>, + bucket_attributes: bool, ) -> Bucket<'a> { match *component { Component::Root => Bucket::Root, Component::ID(ref id) => Bucket::ID(id), Component::Class(ref class) => Bucket::Class(class), + Component::AttributeInNoNamespace { ref local_name, .. } if bucket_attributes => Bucket::Attribute { + name: local_name, + lower_name: local_name, + }, + Component::AttributeInNoNamespaceExists { ref local_name, ref local_name_lower } if bucket_attributes => Bucket::Attribute { + name: local_name, + lower_name: local_name_lower, + }, + Component::AttributeOther(ref selector) if bucket_attributes => Bucket::Attribute { + name: &selector.local_name, + lower_name: &selector.local_name_lower, + }, Component::LocalName(ref selector) => Bucket::LocalName { name: &selector.name, lower_name: &selector.lower_name, @@ -567,14 +644,14 @@ fn specific_bucket_for<'a>( // // So inserting `span` in the rule hash makes sense since we want to // match the slotted . - Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets), - Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets), + Component::Slotted(ref selector) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), + Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), Component::Is(ref list) | Component::Where(ref list) => { if list.len() == 1 { - find_bucket(list[0].iter(), disjoint_buckets) + find_bucket(list[0].iter(), disjoint_buckets, bucket_attributes) } else { for selector in &**list { - let bucket = find_bucket(selector.iter(), disjoint_buckets); + let bucket = find_bucket(selector.iter(), disjoint_buckets, bucket_attributes); disjoint_buckets.push(bucket); } Bucket::Universal @@ -593,12 +670,13 @@ fn specific_bucket_for<'a>( fn find_bucket<'a>( mut iter: SelectorIter<'a, SelectorImpl>, disjoint_buckets: &mut DisjointBuckets<'a>, + bucket_attributes: bool, ) -> Bucket<'a> { let mut current_bucket = Bucket::Universal; loop { for ss in &mut iter { - let new_bucket = specific_bucket_for(ss, disjoint_buckets); + let new_bucket = specific_bucket_for(ss, disjoint_buckets, bucket_attributes); if new_bucket.more_specific_than(¤t_bucket) { current_bucket = new_bucket; } diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 49b34fbbbff..50eb51fba35 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -786,10 +786,7 @@ impl StyleSharingCache { } // It's possible that there are no styles for either id. - let may_match_different_id_rules = - checks::may_match_different_id_rules(shared, target.element, candidate.element); - - if may_match_different_id_rules { + if checks::may_match_different_id_rules(shared, target.element, candidate.element) { trace!("Miss: ID Attr"); return None; } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 643a1454e00..ef3d43ab737 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1961,7 +1961,15 @@ impl CascadeData { state_dependencies: ElementState::empty(), document_state_dependencies: DocumentState::empty(), mapped_ids: PrecomputedHashSet::default(), - selectors_for_cache_revalidation: SelectorMap::new(), + // NOTE: We disable attribute bucketing for revalidation because we + // rely on the buckets to match, but we don't want to just not share + // style across elements with different attributes. + // + // An alternative to this would be to perform a style sharing check + // like may_match_different_id_rules which would check that the + // attribute buckets match on all scopes. But that seems + // somewhat gnarly. + selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), animations: Default::default(), extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), From 9822db5d3cc680087042316184a937e0741cc9f8 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 26 May 2023 22:55:23 +0200 Subject: [PATCH 14/94] Further changes required by Servo --- components/script/layout_dom/element.rs | 11 +++++++++++ components/style/bloom.rs | 6 +++--- components/style/dom.rs | 3 +-- components/style/selector_map.rs | 3 +++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index adbeba7e39d..ebd54df0c31 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::attr::AttrHelpersForLayout; use crate::dom::bindings::inheritance::{ CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, }; @@ -264,6 +265,16 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement } } + #[inline(always)] + fn each_attr_name(&self, mut callback: F) + where + F: FnMut(&style::LocalName), + { + for attr in self.element.attrs() { + callback(style::values::GenericAtomIdent::cast(attr.local_name())) + } + } + fn has_dirty_descendants(&self) -> bool { unsafe { self.as_node() diff --git a/components/style/bloom.rs b/components/style/bloom.rs index 87fd0cba5c2..dc722b7bdba 100644 --- a/components/style/bloom.rs +++ b/components/style/bloom.rs @@ -8,6 +8,7 @@ #![deny(missing_docs)] use crate::dom::{SendElement, TElement}; +use crate::LocalName; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use owning_ref::OwningHandle; use selectors::bloom::BloomFilter; @@ -107,9 +108,8 @@ impl PushedElement { /// We do this for attributes that are very common but not commonly used in /// selectors. #[inline] -#[cfg(feature = "gecko")] -pub fn is_attr_name_excluded_from_filter(atom: &crate::Atom) -> bool { - *atom == atom!("class") || *atom == atom!("id") || *atom == atom!("style") +pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool { + return *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style") } fn each_relevant_element_hash(element: E, mut f: F) diff --git a/components/style/dom.rs b/components/style/dom.rs index c85ff1edaff..308006efd11 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -520,10 +520,9 @@ pub trait TElement: } /// Internal iterator for the attribute names of this element. - #[cfg(feature = "gecko")] fn each_attr_name(&self, callback: F) where - F: FnMut(&AtomIdent); + F: FnMut(&LocalName); /// Internal iterator for the part names that this element exports for a /// given part name. diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 48ab5cdbe66..d3dc48e36e4 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -137,7 +137,10 @@ impl SelectorMap { local_name_hash: HashMap::default(), namespace_hash: HashMap::default(), other: SmallVec::new(), + #[cfg(feature = "gecko")] bucket_attributes: static_prefs::pref!("layout.css.bucket-attribute-names.enabled"), + #[cfg(feature = "servo")] + bucket_attributes: false, count: 0, } } From 9976f9a589e94910047a841be2482a706845b075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:01:30 +0200 Subject: [PATCH 15/94] style: Factor out adding a rule in CascadeData::add_rule This shouldn't have any behavior change, but is necessary because for cascade layers we are going to need to handle the child rules / sheets ourselves, in order to handle nested layers properly. Differential Revision: https://phabricator.services.mozilla.com/D124334 --- components/style/stylist.rs | 340 +++++++++++++++++++----------------- 1 file changed, 183 insertions(+), 157 deletions(-) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index ef3d43ab737..677ba49729f 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2125,6 +2125,179 @@ impl CascadeData { } } + #[inline] + fn add_rule( + &mut self, + rule: &CssRule, + _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, + { + match *rule { + CssRule::Style(ref locked) => { + let style_rule = locked.read_with(&guard); + self.num_declarations += style_rule.block.read_with(&guard).len(); + for selector in &style_rule.selectors.0 { + self.num_selectors += 1; + + let pseudo_element = selector.pseudo_element(); + + if let Some(pseudo) = pseudo_element { + if pseudo.is_precomputed() { + debug_assert!(selector.is_universal()); + debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); + + precomputed_pseudo_element_decls + .as_mut() + .expect("Expected precomputed declarations for the UA level") + .get_or_insert_with(pseudo, Vec::new) + .push(ApplicableDeclarationBlock::new( + StyleSource::from_rule(locked.clone()), + self.rules_source_order, + CascadeLevel::UANormal, + selector.specificity(), + )); + continue; + } + if pseudo.is_unknown_webkit_pseudo_element() { + continue; + } + } + + let hashes = AncestorHashes::new(&selector, quirks_mode); + + let rule = Rule::new( + selector.clone(), + hashes, + locked.clone(), + self.rules_source_order, + ); + + if rebuild_kind.should_rebuild_invalidation() { + self.invalidation_map.note_selector(selector, quirks_mode)?; + let mut needs_revalidation = false; + let mut visitor = StylistSelectorVisitor { + needs_revalidation: &mut needs_revalidation, + passed_rightmost_selector: false, + attribute_dependencies: &mut self.attribute_dependencies, + state_dependencies: &mut self.state_dependencies, + document_state_dependencies: &mut self.document_state_dependencies, + mapped_ids: &mut self.mapped_ids, + }; + + rule.selector.visit(&mut visitor); + + if needs_revalidation { + self.selectors_for_cache_revalidation.insert( + RevalidationSelectorAndHashes::new( + rule.selector.clone(), + rule.hashes.clone(), + ), + quirks_mode, + )?; + } + } + + // Part is special, since given it doesn't have any + // selectors inside, it's not worth using a whole + // SelectorMap for it. + if let Some(parts) = selector.parts() { + // ::part() has all semantics, so we just need to + // put any of them in the selector map. + // + // We choose the last one quite arbitrarily, + // expecting it's slightly more likely to be more + // specific. + self.part_rules + .get_or_insert_with(|| Box::new(Default::default())) + .for_insertion(pseudo_element) + .try_entry(parts.last().unwrap().clone().0)? + .or_insert_with(SmallVec::new) + .try_push(rule)?; + } else { + // NOTE(emilio): It's fine to look at :host and then at + // ::slotted(..), since :host::slotted(..) could never + // possibly match, as is not a valid shadow host. + let rules = + if selector.is_featureless_host_selector_or_pseudo_element() { + self.host_rules + .get_or_insert_with(|| Box::new(Default::default())) + } else if selector.is_slotted() { + self.slotted_rules + .get_or_insert_with(|| Box::new(Default::default())) + } else { + &mut self.normal_rules + } + .for_insertion(pseudo_element); + rules.insert(rule, quirks_mode)?; + } + } + 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) => { + let keyframes_rule = keyframes_rule.read_with(guard); + debug!("Found valid keyframes rule: {:?}", *keyframes_rule); + + // Don't let a prefixed keyframes animation override a non-prefixed one. + let needs_insertion = keyframes_rule.vendor_prefix.is_none() || + self.animations + .get(keyframes_rule.name.as_atom()) + .map_or(true, |rule| rule.vendor_prefix.is_some()); + if needs_insertion { + let animation = KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + guard, + ); + debug!("Found valid keyframe animation: {:?}", animation); + self.animations + .try_insert(keyframes_rule.name.as_atom().clone(), animation)?; + } + }, + #[cfg(feature = "gecko")] + CssRule::FontFace(ref rule) => { + self.extra_data.add_font_face(rule); + }, + #[cfg(feature = "gecko")] + CssRule::FontFeatureValues(ref rule) => { + self.extra_data.add_font_feature_values(rule); + }, + #[cfg(feature = "gecko")] + CssRule::CounterStyle(ref rule) => { + self.extra_data.add_counter_style(guard, rule); + }, + #[cfg(feature = "gecko")] + CssRule::Page(ref rule) => { + self.extra_data.add_page(rule); + }, + // We don't care about any other rule. + _ => {}, + } + Ok(()) + } + // Returns Err(..) to signify OOM fn add_stylesheet( &mut self, @@ -2143,169 +2316,22 @@ impl CascadeData { } 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 { - CssRule::Style(ref locked) => { - let style_rule = locked.read_with(&guard); - self.num_declarations += style_rule.block.read_with(&guard).len(); - for selector in &style_rule.selectors.0 { - self.num_selectors += 1; - - let pseudo_element = selector.pseudo_element(); - - if let Some(pseudo) = pseudo_element { - if pseudo.is_precomputed() { - debug_assert!(selector.is_universal()); - debug_assert!(matches!(origin, Origin::UserAgent)); - - precomputed_pseudo_element_decls - .as_mut() - .expect("Expected precomputed declarations for the UA level") - .get_or_insert_with(pseudo, Vec::new) - .push(ApplicableDeclarationBlock::new( - StyleSource::from_rule(locked.clone()), - self.rules_source_order, - CascadeLevel::UANormal, - selector.specificity(), - )); - continue; - } - if pseudo.is_unknown_webkit_pseudo_element() { - continue; - } - } - - let hashes = AncestorHashes::new(&selector, quirks_mode); - - let rule = Rule::new( - selector.clone(), - hashes, - locked.clone(), - self.rules_source_order, - ); - - if rebuild_kind.should_rebuild_invalidation() { - self.invalidation_map.note_selector(selector, quirks_mode)?; - let mut needs_revalidation = false; - let mut visitor = StylistSelectorVisitor { - needs_revalidation: &mut needs_revalidation, - passed_rightmost_selector: false, - attribute_dependencies: &mut self.attribute_dependencies, - state_dependencies: &mut self.state_dependencies, - document_state_dependencies: &mut self.document_state_dependencies, - mapped_ids: &mut self.mapped_ids, - }; - - rule.selector.visit(&mut visitor); - - if needs_revalidation { - self.selectors_for_cache_revalidation.insert( - RevalidationSelectorAndHashes::new( - rule.selector.clone(), - rule.hashes.clone(), - ), - quirks_mode, - )?; - } - } - - // Part is special, since given it doesn't have any - // selectors inside, it's not worth using a whole - // SelectorMap for it. - if let Some(parts) = selector.parts() { - // ::part() has all semantics, so we just need to - // put any of them in the selector map. - // - // We choose the last one quite arbitrarily, - // expecting it's slightly more likely to be more - // specific. - self.part_rules - .get_or_insert_with(|| Box::new(Default::default())) - .for_insertion(pseudo_element) - .try_entry(parts.last().unwrap().clone().0)? - .or_insert_with(SmallVec::new) - .try_push(rule)?; - } else { - // NOTE(emilio): It's fine to look at :host and then at - // ::slotted(..), since :host::slotted(..) could never - // possibly match, as is not a valid shadow host. - let rules = - if selector.is_featureless_host_selector_or_pseudo_element() { - self.host_rules - .get_or_insert_with(|| Box::new(Default::default())) - } else if selector.is_slotted() { - self.slotted_rules - .get_or_insert_with(|| Box::new(Default::default())) - } else { - &mut self.normal_rules - } - .for_insertion(pseudo_element); - rules.insert(rule, quirks_mode)?; - } - } - 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) => { - let keyframes_rule = keyframes_rule.read_with(guard); - debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - - // Don't let a prefixed keyframes animation override a non-prefixed one. - let needs_insertion = keyframes_rule.vendor_prefix.is_none() || - self.animations - .get(keyframes_rule.name.as_atom()) - .map_or(true, |rule| rule.vendor_prefix.is_some()); - if needs_insertion { - let animation = KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - guard, - ); - debug!("Found valid keyframe animation: {:?}", animation); - self.animations - .try_insert(keyframes_rule.name.as_atom().clone(), animation)?; - } - }, - #[cfg(feature = "gecko")] - CssRule::FontFace(ref rule) => { - self.extra_data.add_font_face(rule); - }, - #[cfg(feature = "gecko")] - CssRule::FontFeatureValues(ref rule) => { - self.extra_data.add_font_feature_values(rule); - }, - #[cfg(feature = "gecko")] - CssRule::CounterStyle(ref rule) => { - self.extra_data.add_counter_style(guard, rule); - }, - #[cfg(feature = "gecko")] - CssRule::Page(ref rule) => { - self.extra_data.add_page(rule); - }, - // We don't care about any other rule. - _ => {}, - } + self.add_rule( + rule, + device, + quirks_mode, + stylesheet, + guard, + rebuild_kind, + precomputed_pseudo_element_decls.as_deref_mut(), + )?; } Ok(()) From 8705e3f39f3f6e374f222f5388c1febf02399fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:02:50 +0200 Subject: [PATCH 16/94] style: Compute layer order during CascadeData rebuild For that, deal with children in add_rule recursively, to keep the current layer name up-to-date in block layer rules. This is not the final version of this code, as right now something like this: @layer A { ... } @layer B { ... } @layer A.A { ... } Would give A.A more priority over B, which is not correct. There are tests for this incoming in wpt sync and such, but that can be tweaked later. Differential Revision: https://phabricator.services.mozilla.com/D124335 --- components/style/stylesheets/layer_rule.rs | 51 +++++- components/style/stylesheets/mod.rs | 2 +- components/style/stylesheets/rule_parser.rs | 7 +- .../style/stylesheets/rules_iterator.rs | 120 +++++++------- components/style/stylist.rs | 146 +++++++++++++++--- 5 files changed, 236 insertions(+), 90 deletions(-) diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index c8a04ca6563..8d51558eccd 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -20,8 +20,37 @@ use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; /// A ``: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name -#[derive(Clone, Debug, ToShmem)] -pub struct LayerName(SmallVec<[AtomIdent; 1]>); +#[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>( @@ -82,10 +111,13 @@ impl ToCss for LayerName { pub enum LayerRuleKind { /// A block `@layer ? { ... }` Block { - /// The layer name, or `None` if anonymous. - name: Option, + /// The layer name. + name: LayerName, /// The nested rules. rules: Arc>, + /// Whether the layer name is synthesized (and thus shouldn't be + /// serialized). + is_anonymous: bool, }, /// A statement `@layer , , ;` Statement { @@ -116,8 +148,9 @@ impl ToCssWithGuard for LayerRule { LayerRuleKind::Block { ref name, ref rules, + ref is_anonymous, } => { - if let Some(ref name) = *name { + if !*is_anonymous { name.to_css(&mut CssWriter::new(dest))?; dest.write_char(' ')?; } @@ -151,8 +184,13 @@ impl DeepCloneWithLock for LayerRule { LayerRuleKind::Block { ref name, ref rules, + ref is_anonymous, } => LayerRuleKind::Block { - name: name.clone(), + name: if *is_anonymous { + LayerName::new_anonymous() + } else { + name.clone() + }, rules: Arc::new( lock.wrap( rules @@ -160,6 +198,7 @@ impl DeepCloneWithLock for LayerRule { .deep_clone_with_lock(lock, guard, params), ), ), + is_anonymous: *is_anonymous, }, LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement { names: names.clone(), diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index b4e10ee368e..360a17b2f9a 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -11,7 +11,7 @@ mod font_face_rule; pub mod font_feature_values_rule; pub mod import_rule; pub mod keyframes_rule; -mod layer_rule; +pub mod layer_rule; mod loader; mod media_rule; mod namespace_rule; diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 2998aba8fad..7fb76a8d7a9 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -577,9 +577,9 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { )))) }, AtRulePrelude::Layer(names) => { - let name = match names.len() { - 0 => None, - 1 => Some(names.into_iter().next().unwrap()), + let (name, is_anonymous) = match names.len() { + 0 => (LayerName::new_anonymous(), true), + 1 => (names.into_iter().next().unwrap(), false), _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), }; Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap( @@ -587,6 +587,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { kind: LayerRuleKind::Block { name, rules: self.parse_nested_rules(input, CssRuleType::Layer), + is_anonymous, }, source_location: start.source_location(), }, diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index b1921e63e07..d98d70981df 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -51,67 +51,65 @@ where pub fn skip_children(&mut self) { self.stack.pop(); } -} -fn children_of_rule<'a, C>( - rule: &'a CssRule, - device: &'a Device, - quirks_mode: QuirksMode, - guard: &'a SharedRwLockReadGuard<'_>, - effective: &mut bool, -) -> Option> -where - C: NestedRuleIterationCondition + 'static, -{ - *effective = true; - match *rule { - CssRule::Namespace(_) | - CssRule::Style(_) | - CssRule::FontFace(_) | - CssRule::CounterStyle(_) | - CssRule::Viewport(_) | - CssRule::Keyframes(_) | - CssRule::Page(_) | - CssRule::FontFeatureValues(_) => None, - CssRule::Import(ref import_rule) => { - let import_rule = import_rule.read_with(guard); - if !C::process_import(guard, device, quirks_mode, import_rule) { - *effective = false; - return None; - } - Some(import_rule.stylesheet.rules(guard).iter()) - }, - CssRule::Document(ref doc_rule) => { - let doc_rule = doc_rule.read_with(guard); - if !C::process_document(guard, device, quirks_mode, doc_rule) { - *effective = false; - return None; - } - Some(doc_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Media(ref lock) => { - let media_rule = lock.read_with(guard); - if !C::process_media(guard, device, quirks_mode, media_rule) { - *effective = false; - return None; - } - Some(media_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Supports(ref lock) => { - let supports_rule = lock.read_with(guard); - if !C::process_supports(guard, device, quirks_mode, supports_rule) { - *effective = false; - return None; - } - Some(supports_rule.rules.read_with(guard).0.iter()) - }, - CssRule::Layer(ref lock) => { - use crate::stylesheets::layer_rule::LayerRuleKind; + /// Returns the children of `rule`, and whether `rule` is effective. + pub fn children( + rule: &'a CssRule, + device: &'a Device, + quirks_mode: QuirksMode, + guard: &'a SharedRwLockReadGuard<'_>, + effective: &mut bool, + ) -> Option> { + *effective = true; + match *rule { + CssRule::Namespace(_) | + CssRule::Style(_) | + CssRule::FontFace(_) | + CssRule::CounterStyle(_) | + CssRule::Viewport(_) | + CssRule::Keyframes(_) | + CssRule::Page(_) | + CssRule::FontFeatureValues(_) => None, + CssRule::Import(ref import_rule) => { + let import_rule = import_rule.read_with(guard); + if !C::process_import(guard, device, quirks_mode, import_rule) { + *effective = false; + return None; + } + Some(import_rule.stylesheet.rules(guard).iter()) + }, + CssRule::Document(ref doc_rule) => { + let doc_rule = doc_rule.read_with(guard); + if !C::process_document(guard, device, quirks_mode, doc_rule) { + *effective = false; + return None; + } + Some(doc_rule.rules.read_with(guard).0.iter()) + }, + CssRule::Media(ref lock) => { + let media_rule = lock.read_with(guard); + if !C::process_media(guard, device, quirks_mode, media_rule) { + *effective = false; + return None; + } + Some(media_rule.rules.read_with(guard).0.iter()) + }, + CssRule::Supports(ref lock) => { + let supports_rule = lock.read_with(guard); + if !C::process_supports(guard, device, quirks_mode, supports_rule) { + *effective = false; + return None; + } + Some(supports_rule.rules.read_with(guard).0.iter()) + }, + CssRule::Layer(ref lock) => { + use crate::stylesheets::layer_rule::LayerRuleKind; - let layer_rule = lock.read_with(guard); - match layer_rule.kind { - LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()), - LayerRuleKind::Statement { .. } => None, + 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, + } } } } @@ -138,7 +136,7 @@ where }; let mut effective = true; - let children = children_of_rule::( + let children = Self::children( rule, self.device, self.quirks_mode, @@ -324,7 +322,7 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { guard: &'a SharedRwLockReadGuard<'b>, rule: &'a CssRule, ) -> Self { - let children = children_of_rule::(rule, device, quirks_mode, guard, &mut false); + let children = RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 677ba49729f..71a230ab1d6 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -25,11 +25,12 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheets::keyframes_rule::KeyframesAnimation; +use crate::stylesheets::layer_rule::LayerName; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; #[cfg(feature = "gecko")] use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule}; -use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter}; +use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter, EffectiveRulesIterator}; use crate::thread_state::{self, ThreadState}; use crate::{Atom, LocalName, Namespace, WeakAtom}; use fallible::FallibleVec; @@ -1931,6 +1932,12 @@ pub struct CascadeData { /// by name. animations: PrecomputedHashMap, + /// A map from cascade layer name to layer order. + layer_order: FxHashMap, + + /// The next layer order for this cascade data. + next_layer_order: u32, + /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -1971,6 +1978,8 @@ impl CascadeData { // somewhat gnarly. selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), animations: Default::default(), + layer_order: Default::default(), + next_layer_order: 0, extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2129,16 +2138,20 @@ impl CascadeData { fn add_rule( &mut self, rule: &CssRule, - _device: &Device, + device: &Device, quirks_mode: QuirksMode, stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, + current_layer: &mut LayerName, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where S: StylesheetInDocument + 'static, { + // Handle leaf rules first, as those are by far the most common ones, + // and are always effective, so we can skip some checks. + let mut handled = true; match *rule { CssRule::Style(ref locked) => { let style_rule = locked.read_with(&guard); @@ -2240,22 +2253,6 @@ impl CascadeData { } self.rules_source_order += 1; }, - CssRule::Import(ref lock) => { - if rebuild_kind.should_rebuild_invalidation() { - let import_rule = lock.read_with(guard); - self.effective_media_query_results - .saw_effective(import_rule); - } - - // NOTE: effective_rules visits the inner stylesheet if - // appropriate. - }, - CssRule::Media(ref lock) => { - if rebuild_kind.should_rebuild_invalidation() { - let media_rule = lock.read_with(guard); - self.effective_media_query_results.saw_effective(media_rule); - } - }, CssRule::Keyframes(ref keyframes_rule) => { let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); @@ -2292,9 +2289,116 @@ impl CascadeData { CssRule::Page(ref rule) => { self.extra_data.add_page(rule); }, + CssRule::Viewport(..) => {}, + _ => { + handled = false; + }, + } + + if handled { + // Assert that there are no children, and that the rule is + // effective. + if cfg!(debug_assertions) { + let mut effective = false; + let children = EffectiveRulesIterator::children( + rule, + device, + quirks_mode, + guard, + &mut effective, + ); + debug_assert!(children.is_none()); + debug_assert!(effective); + } + return Ok(()); + } + + let mut effective = false; + let children = EffectiveRulesIterator::children( + rule, + device, + quirks_mode, + guard, + &mut effective, + ); + + if !effective { + return Ok(()); + } + + let mut layer_names_to_pop = 0; + match *rule { + 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); + } + + }, + 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; + + fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) { + // TODO: Measure what's more common / expensive, if + // layer.clone() or the double hash lookup in the insert + // case. + if data.layer_order.get(layer).is_some() { + return; + } + data.layer_order.insert(layer.clone(), data.next_layer_order); + data.next_layer_order += 1; + } + + let layer_rule = lock.read_with(guard); + match layer_rule.kind { + LayerRuleKind::Block { ref name, .. } => { + for name in name.layer_names() { + current_layer.0.push(name.clone()); + maybe_register_layer(self, ¤t_layer); + layer_names_to_pop += 1; + } + } + LayerRuleKind::Statement { ref names } => { + for name in &**names { + for name in name.layer_names() { + current_layer.0.push(name.clone()); + maybe_register_layer(self, ¤t_layer); + current_layer.0.pop(); + } + } + } + } + }, // We don't care about any other rule. _ => {}, } + + if let Some(children) = children { + for child in children { + self.add_rule( + child, + device, + quirks_mode, + stylesheet, + guard, + rebuild_kind, + current_layer, + precomputed_pseudo_element_decls.as_deref_mut(), + )?; + } + } + + for _ in 0..layer_names_to_pop { + current_layer.0.pop(); + } + Ok(()) } @@ -2321,8 +2425,9 @@ impl CascadeData { self.effective_media_query_results.saw_effective(contents); } + let mut current_layer = LayerName::new_empty(); - for rule in stylesheet.effective_rules(device, guard) { + for rule in contents.rules(guard).iter() { self.add_rule( rule, device, @@ -2330,6 +2435,7 @@ impl CascadeData { stylesheet, guard, rebuild_kind, + &mut current_layer, precomputed_pseudo_element_decls.as_deref_mut(), )?; } @@ -2448,6 +2554,8 @@ impl CascadeData { host_rules.clear(); } self.animations.clear(); + self.layer_order.clear(); + self.next_layer_order = 0; self.extra_data.clear(); self.rules_source_order = 0; self.num_selectors = 0; From 1f62a7144dba181afda999b985d08f5161bad786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:03:48 +0200 Subject: [PATCH 17/94] style: Tweak recursion in add_rule to only cause a function call per recursion level This code is really hot, and we've had perf regressions in the past for introducing function calls in the hot path. After the previous patch, add_rule is recursive and thus it can't be inlined, causing a function call for each CSS rule. This reduces the overhead by making the function take a rule list instead, causing a function call per rule _list_, which should be unnoticeable in practice. Depends on D124335 Differential Revision: https://phabricator.services.mozilla.com/D124336 --- components/style/stylist.rs | 466 ++++++++++++++++++------------------ 1 file changed, 231 insertions(+), 235 deletions(-) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 71a230ab1d6..8f9d2a9736e 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2134,10 +2134,9 @@ impl CascadeData { } } - #[inline] - fn add_rule( + fn add_rule_list( &mut self, - rule: &CssRule, + rules: std::slice::Iter<'_, CssRule>, device: &Device, quirks_mode: QuirksMode, stylesheet: &S, @@ -2149,241 +2148,241 @@ impl CascadeData { where S: StylesheetInDocument + 'static, { - // Handle leaf rules first, as those are by far the most common ones, - // and are always effective, so we can skip some checks. - let mut handled = true; - match *rule { - CssRule::Style(ref locked) => { - let style_rule = locked.read_with(&guard); - self.num_declarations += style_rule.block.read_with(&guard).len(); - for selector in &style_rule.selectors.0 { - self.num_selectors += 1; + for rule in rules { + // Handle leaf rules first, as those are by far the most common + // ones, and are always effective, so we can skip some checks. + let mut handled = true; + match *rule { + CssRule::Style(ref locked) => { + let style_rule = locked.read_with(&guard); + self.num_declarations += style_rule.block.read_with(&guard).len(); + for selector in &style_rule.selectors.0 { + self.num_selectors += 1; - let pseudo_element = selector.pseudo_element(); + let pseudo_element = selector.pseudo_element(); - if let Some(pseudo) = pseudo_element { - if pseudo.is_precomputed() { - debug_assert!(selector.is_universal()); - debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); + if let Some(pseudo) = pseudo_element { + if pseudo.is_precomputed() { + debug_assert!(selector.is_universal()); + debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); - precomputed_pseudo_element_decls - .as_mut() - .expect("Expected precomputed declarations for the UA level") - .get_or_insert_with(pseudo, Vec::new) - .push(ApplicableDeclarationBlock::new( - StyleSource::from_rule(locked.clone()), - self.rules_source_order, - CascadeLevel::UANormal, - selector.specificity(), - )); - continue; - } - if pseudo.is_unknown_webkit_pseudo_element() { - continue; - } - } - - let hashes = AncestorHashes::new(&selector, quirks_mode); - - let rule = Rule::new( - selector.clone(), - hashes, - locked.clone(), - self.rules_source_order, - ); - - if rebuild_kind.should_rebuild_invalidation() { - self.invalidation_map.note_selector(selector, quirks_mode)?; - let mut needs_revalidation = false; - let mut visitor = StylistSelectorVisitor { - needs_revalidation: &mut needs_revalidation, - passed_rightmost_selector: false, - attribute_dependencies: &mut self.attribute_dependencies, - state_dependencies: &mut self.state_dependencies, - document_state_dependencies: &mut self.document_state_dependencies, - mapped_ids: &mut self.mapped_ids, - }; - - rule.selector.visit(&mut visitor); - - if needs_revalidation { - self.selectors_for_cache_revalidation.insert( - RevalidationSelectorAndHashes::new( - rule.selector.clone(), - rule.hashes.clone(), - ), - quirks_mode, - )?; - } - } - - // Part is special, since given it doesn't have any - // selectors inside, it's not worth using a whole - // SelectorMap for it. - if let Some(parts) = selector.parts() { - // ::part() has all semantics, so we just need to - // put any of them in the selector map. - // - // We choose the last one quite arbitrarily, - // expecting it's slightly more likely to be more - // specific. - self.part_rules - .get_or_insert_with(|| Box::new(Default::default())) - .for_insertion(pseudo_element) - .try_entry(parts.last().unwrap().clone().0)? - .or_insert_with(SmallVec::new) - .try_push(rule)?; - } else { - // NOTE(emilio): It's fine to look at :host and then at - // ::slotted(..), since :host::slotted(..) could never - // possibly match, as is not a valid shadow host. - let rules = - if selector.is_featureless_host_selector_or_pseudo_element() { - self.host_rules - .get_or_insert_with(|| Box::new(Default::default())) - } else if selector.is_slotted() { - self.slotted_rules - .get_or_insert_with(|| Box::new(Default::default())) - } else { - &mut self.normal_rules + precomputed_pseudo_element_decls + .as_mut() + .expect("Expected precomputed declarations for the UA level") + .get_or_insert_with(pseudo, Vec::new) + .push(ApplicableDeclarationBlock::new( + StyleSource::from_rule(locked.clone()), + self.rules_source_order, + CascadeLevel::UANormal, + selector.specificity(), + )); + continue; } - .for_insertion(pseudo_element); - rules.insert(rule, quirks_mode)?; - } - } - self.rules_source_order += 1; - }, - CssRule::Keyframes(ref keyframes_rule) => { - let keyframes_rule = keyframes_rule.read_with(guard); - debug!("Found valid keyframes rule: {:?}", *keyframes_rule); + if pseudo.is_unknown_webkit_pseudo_element() { + continue; + } + } - // Don't let a prefixed keyframes animation override a non-prefixed one. - let needs_insertion = keyframes_rule.vendor_prefix.is_none() || - self.animations - .get(keyframes_rule.name.as_atom()) - .map_or(true, |rule| rule.vendor_prefix.is_some()); - if needs_insertion { - let animation = KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - guard, - ); - debug!("Found valid keyframe animation: {:?}", animation); - self.animations - .try_insert(keyframes_rule.name.as_atom().clone(), animation)?; - } - }, - #[cfg(feature = "gecko")] - CssRule::FontFace(ref rule) => { - self.extra_data.add_font_face(rule); - }, - #[cfg(feature = "gecko")] - CssRule::FontFeatureValues(ref rule) => { - self.extra_data.add_font_feature_values(rule); - }, - #[cfg(feature = "gecko")] - CssRule::CounterStyle(ref rule) => { - self.extra_data.add_counter_style(guard, rule); - }, - #[cfg(feature = "gecko")] - CssRule::Page(ref rule) => { - self.extra_data.add_page(rule); - }, - CssRule::Viewport(..) => {}, - _ => { - handled = false; - }, - } + let hashes = AncestorHashes::new(&selector, quirks_mode); - 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); - } - return Ok(()); - } + let rule = Rule::new( + selector.clone(), + hashes, + locked.clone(), + self.rules_source_order, + ); - let mut effective = false; - let children = EffectiveRulesIterator::children( - rule, - device, - quirks_mode, - guard, - &mut effective, - ); + if rebuild_kind.should_rebuild_invalidation() { + self.invalidation_map.note_selector(selector, quirks_mode)?; + let mut needs_revalidation = false; + let mut visitor = StylistSelectorVisitor { + needs_revalidation: &mut needs_revalidation, + passed_rightmost_selector: false, + attribute_dependencies: &mut self.attribute_dependencies, + state_dependencies: &mut self.state_dependencies, + document_state_dependencies: &mut self.document_state_dependencies, + mapped_ids: &mut self.mapped_ids, + }; - if !effective { - return Ok(()); - } + rule.selector.visit(&mut visitor); - let mut layer_names_to_pop = 0; - match *rule { - 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); - } + if needs_revalidation { + self.selectors_for_cache_revalidation.insert( + RevalidationSelectorAndHashes::new( + rule.selector.clone(), + rule.hashes.clone(), + ), + quirks_mode, + )?; + } + } - }, - 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; - - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) { - // TODO: Measure what's more common / expensive, if - // layer.clone() or the double hash lookup in the insert - // case. - if data.layer_order.get(layer).is_some() { - return; - } - data.layer_order.insert(layer.clone(), data.next_layer_order); - data.next_layer_order += 1; - } - - let layer_rule = lock.read_with(guard); - match layer_rule.kind { - LayerRuleKind::Block { ref name, .. } => { - for name in name.layer_names() { - current_layer.0.push(name.clone()); - maybe_register_layer(self, ¤t_layer); - layer_names_to_pop += 1; + // Part is special, since given it doesn't have any + // selectors inside, it's not worth using a whole + // SelectorMap for it. + if let Some(parts) = selector.parts() { + // ::part() has all semantics, so we just need to + // put any of them in the selector map. + // + // We choose the last one quite arbitrarily, + // expecting it's slightly more likely to be more + // specific. + self.part_rules + .get_or_insert_with(|| Box::new(Default::default())) + .for_insertion(pseudo_element) + .try_entry(parts.last().unwrap().clone().0)? + .or_insert_with(SmallVec::new) + .try_push(rule)?; + } else { + // NOTE(emilio): It's fine to look at :host and then at + // ::slotted(..), since :host::slotted(..) could never + // possibly match, as is not a valid shadow host. + let rules = + if selector.is_featureless_host_selector_or_pseudo_element() { + self.host_rules + .get_or_insert_with(|| Box::new(Default::default())) + } else if selector.is_slotted() { + self.slotted_rules + .get_or_insert_with(|| Box::new(Default::default())) + } else { + &mut self.normal_rules + } + .for_insertion(pseudo_element); + rules.insert(rule, quirks_mode)?; } } - LayerRuleKind::Statement { ref names } => { - for name in &**names { + self.rules_source_order += 1; + }, + CssRule::Keyframes(ref keyframes_rule) => { + let keyframes_rule = keyframes_rule.read_with(guard); + debug!("Found valid keyframes rule: {:?}", *keyframes_rule); + + // Don't let a prefixed keyframes animation override a non-prefixed one. + let needs_insertion = keyframes_rule.vendor_prefix.is_none() || + self.animations + .get(keyframes_rule.name.as_atom()) + .map_or(true, |rule| rule.vendor_prefix.is_some()); + if needs_insertion { + let animation = KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + guard, + ); + debug!("Found valid keyframe animation: {:?}", animation); + self.animations + .try_insert(keyframes_rule.name.as_atom().clone(), animation)?; + } + }, + #[cfg(feature = "gecko")] + CssRule::FontFace(ref rule) => { + self.extra_data.add_font_face(rule); + }, + #[cfg(feature = "gecko")] + CssRule::FontFeatureValues(ref rule) => { + self.extra_data.add_font_feature_values(rule); + }, + #[cfg(feature = "gecko")] + CssRule::CounterStyle(ref rule) => { + self.extra_data.add_counter_style(guard, rule); + }, + #[cfg(feature = "gecko")] + CssRule::Page(ref rule) => { + self.extra_data.add_page(rule); + }, + CssRule::Viewport(..) => {}, + _ => { + handled = false; + }, + } + + if handled { + // Assert that there are no children, and that the rule is + // effective. + if cfg!(debug_assertions) { + let mut effective = false; + let children = EffectiveRulesIterator::children( + rule, + device, + quirks_mode, + guard, + &mut effective, + ); + debug_assert!(children.is_none()); + debug_assert!(effective); + } + continue; + } + + let mut effective = false; + let children = EffectiveRulesIterator::children( + rule, + device, + quirks_mode, + guard, + &mut effective, + ); + + if !effective { + continue; + } + + let mut layer_names_to_pop = 0; + match *rule { + 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); + } + + }, + 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; + + fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) { + // TODO: Measure what's more common / expensive, if + // layer.clone() or the double hash lookup in the insert + // case. + if data.layer_order.get(layer).is_some() { + return; + } + data.layer_order.insert(layer.clone(), data.next_layer_order); + data.next_layer_order += 1; + } + + let layer_rule = lock.read_with(guard); + match layer_rule.kind { + LayerRuleKind::Block { ref name, .. } => { for name in name.layer_names() { current_layer.0.push(name.clone()); maybe_register_layer(self, ¤t_layer); - current_layer.0.pop(); + layer_names_to_pop += 1; + } + } + LayerRuleKind::Statement { ref names } => { + for name in &**names { + for name in name.layer_names() { + current_layer.0.push(name.clone()); + maybe_register_layer(self, ¤t_layer); + 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 { - for child in children { - self.add_rule( - child, + if let Some(children) = children { + self.add_rule_list( + children, device, quirks_mode, stylesheet, @@ -2393,10 +2392,10 @@ impl CascadeData { precomputed_pseudo_element_decls.as_deref_mut(), )?; } - } - for _ in 0..layer_names_to_pop { - current_layer.0.pop(); + for _ in 0..layer_names_to_pop { + current_layer.0.pop(); + } } Ok(()) @@ -2426,19 +2425,16 @@ impl CascadeData { } let mut current_layer = LayerName::new_empty(); - - for rule in contents.rules(guard).iter() { - self.add_rule( - rule, - device, - quirks_mode, - stylesheet, - guard, - rebuild_kind, - &mut current_layer, - precomputed_pseudo_element_decls.as_deref_mut(), - )?; - } + self.add_rule_list( + contents.rules(guard).iter(), + device, + quirks_mode, + stylesheet, + guard, + rebuild_kind, + &mut current_layer, + precomputed_pseudo_element_decls.as_deref_mut(), + )?; Ok(()) } From 874737d877d2e413ca9cfdfc6454b117adf9a8ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:04:56 +0200 Subject: [PATCH 18/94] style: Add layer_order to rules I want to land this separately because we might want to get smarter with the size of the Rule struct (maybe restricting layer order to a u8 per scope and packing it with the source order, since 255 layers seem plenty), but I'd rather do the obvious thing for now. Depends on D124336 Differential Revision: https://phabricator.services.mozilla.com/D124337 --- components/style/stylist.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 8f9d2a9736e..7298e0d2fc9 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1979,7 +1979,7 @@ impl CascadeData { selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), animations: Default::default(), layer_order: Default::default(), - next_layer_order: 0, + next_layer_order: 1, // 0 reserved for the root scope. extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2143,6 +2143,7 @@ impl CascadeData { guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, current_layer: &mut LayerName, + current_layer_order: u32, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where @@ -2190,6 +2191,7 @@ impl CascadeData { hashes, locked.clone(), self.rules_source_order, + current_layer_order, ); if rebuild_kind.should_rebuild_invalidation() { @@ -2327,6 +2329,7 @@ impl CascadeData { } let mut layer_names_to_pop = 0; + let mut children_layer_order = current_layer_order; match *rule { CssRule::Import(ref lock) => { if rebuild_kind.should_rebuild_invalidation() { @@ -2345,15 +2348,17 @@ impl CascadeData { CssRule::Layer(ref lock) => { use crate::stylesheets::layer_rule::LayerRuleKind; - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) { + fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> u32 { // TODO: Measure what's more common / expensive, if // layer.clone() or the double hash lookup in the insert // case. - if data.layer_order.get(layer).is_some() { - return; + if let Some(order) = data.layer_order.get(layer) { + return *order; } - data.layer_order.insert(layer.clone(), data.next_layer_order); + let order = data.next_layer_order; + data.layer_order.insert(layer.clone(), order); data.next_layer_order += 1; + order } let layer_rule = lock.read_with(guard); @@ -2361,7 +2366,7 @@ impl CascadeData { LayerRuleKind::Block { ref name, .. } => { for name in name.layer_names() { current_layer.0.push(name.clone()); - maybe_register_layer(self, ¤t_layer); + children_layer_order = maybe_register_layer(self, ¤t_layer); layer_names_to_pop += 1; } } @@ -2389,6 +2394,7 @@ impl CascadeData { guard, rebuild_kind, current_layer, + children_layer_order, precomputed_pseudo_element_decls.as_deref_mut(), )?; } @@ -2433,6 +2439,7 @@ impl CascadeData { guard, rebuild_kind, &mut current_layer, + /* current_layer_order = */ 0, precomputed_pseudo_element_decls.as_deref_mut(), )?; @@ -2551,7 +2558,7 @@ impl CascadeData { } self.animations.clear(); self.layer_order.clear(); - self.next_layer_order = 0; + self.next_layer_order = 1; self.extra_data.clear(); self.rules_source_order = 0; self.num_selectors = 0; @@ -2640,6 +2647,9 @@ pub struct Rule { /// we could repurpose that storage here if we needed to. pub source_order: u32, + /// The current layer order of this style rule. + pub layer_order: u32, + /// The actual style rule. #[cfg_attr( feature = "gecko", @@ -2677,12 +2687,14 @@ impl Rule { hashes: AncestorHashes, style_rule: Arc>, source_order: u32, + layer_order: u32, ) -> Self { Rule { - selector: selector, - hashes: hashes, - style_rule: style_rule, - source_order: source_order, + selector, + hashes, + style_rule, + source_order, + layer_order, } } } From 93fb8861ae40a499cb963032d60ac1aad9acfd3d Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 30 May 2023 23:03:46 +0200 Subject: [PATCH 19/94] Further changes required by Servo --- tests/unit/style/stylist.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 31b7869a258..3df3826d7d7 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -52,6 +52,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { AncestorHashes::new(s, QuirksMode::NoQuirks), locked.clone(), i as u32, + 0 ) }) .collect() From 3dc3fb9412d23769ab05e0cdb13f2f11b1fe1f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:05:52 +0200 Subject: [PATCH 20/94] style: Plumb layer order through ApplicableDeclarationBlock, and make it have an effect Same, I want to land this separately to see if it affects micro-benchmarks. If so, we might want to pack the layer order _somewhere_ (though in this case I'm not sure where, tbh). With this, layer rules should have an effect on the page. There are a few things missing before being able to enable them: * Fix nested layer order in some cases (when parent layers are declared out of order, see the previous commit mentioning this). * Some kind of OM representation, perhaps. * Tests of course, which are coming in bug 1728722 and bug 1727276. But this should be enough to allow playing with them. Depends on D124337 Differential Revision: https://phabricator.services.mozilla.com/D124338 --- components/style/applicable_declarations.rs | 10 +++++++--- components/style/rule_collector.rs | 2 +- components/style/stylist.rs | 3 ++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 6353fb4c5e5..168aae643cd 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -69,8 +69,10 @@ pub struct ApplicableDeclarationBlock { /// The bits containing the source order, cascade level, and shadow cascade /// order. bits: ApplicableDeclarationBits, - /// The specificity of the selector this block is represented by. + /// The specificity of the selector. pub specificity: u32, + /// The layer order of the selector. + pub layer_order: u32, } impl ApplicableDeclarationBlock { @@ -85,16 +87,18 @@ impl ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), bits: ApplicableDeclarationBits::new(0, level), specificity: 0, + layer_order: 0, } } /// Constructs an applicable declaration block from the given components #[inline] - pub fn new(source: StyleSource, order: u32, level: CascadeLevel, specificity: u32) -> Self { + pub fn new(source: StyleSource, source_order: u32, level: CascadeLevel, specificity: u32, layer_order: u32) -> Self { ApplicableDeclarationBlock { source, - bits: ApplicableDeclarationBits::new(order, level), + bits: ApplicableDeclarationBits::new(source_order, level), specificity, + layer_order, } } diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 86293e5c785..bb34396f9aa 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -148,7 +148,7 @@ where f(self); if start != self.rules.len() { self.rules[start..] - .sort_unstable_by_key(|block| (block.specificity, block.source_order())); + .sort_unstable_by_key(|block| (block.layer_order, block.specificity, block.source_order())); } self.context.current_host = old_host; self.in_sort_scope = false; diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 7298e0d2fc9..0a87da95bc8 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2176,6 +2176,7 @@ impl CascadeData { self.rules_source_order, CascadeLevel::UANormal, selector.specificity(), + current_layer_order, )); continue; } @@ -2678,7 +2679,7 @@ impl Rule { level: CascadeLevel, ) -> ApplicableDeclarationBlock { let source = StyleSource::from_rule(self.style_rule.clone()); - ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity()) + ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), self.layer_order) } /// Creates a new Rule. From 6bc198b7572c86d1592f279a703c89b4ce7e11a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:09:24 +0200 Subject: [PATCH 21/94] style: Implement @import layer|layer() This works modulo the existing nested layer order bug. Will be covered by WPT /css/css-cascade/layer-import.html once the feature is enabled (I can probably enable it right away for those tests, but I'd rather fix the obvious bugs first). Differential Revision: https://phabricator.services.mozilla.com/D124538 --- components/style/stylesheets/import_rule.rs | 57 ++++++++++++++++++--- components/style/stylesheets/loader.rs | 3 +- components/style/stylesheets/rule_parser.rs | 27 ++++++++-- components/style/stylist.rs | 36 ++++++++----- 4 files changed, 98 insertions(+), 25 deletions(-) diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index 8f518f29bac..d60802cf0ab 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -11,6 +11,7 @@ use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; use crate::stylesheets::{CssRule, StylesheetInDocument}; +use crate::stylesheets::layer_rule::LayerName; use crate::values::CssUrl; use cssparser::SourceLocation; use std::fmt::{self, Write}; @@ -135,6 +136,31 @@ impl DeepCloneWithLock for ImportSheet { } } +/// The layer keyword or function in an import rule. +#[derive(Debug)] +pub struct ImportLayer { + /// Whether the layer is anonymous. + pub is_anonymous: bool, + /// The layer name. + pub name: LayerName, +} + + +impl ToCss for ImportLayer { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_anonymous { + dest.write_str("layer") + } else { + dest.write_str("layer(")?; + self.name.to_css(dest)?; + dest.write_char(')') + } + } +} + /// The [`@import`][import] at-rule. /// /// [import]: https://drafts.csswg.org/css-cascade-3/#at-import @@ -148,6 +174,9 @@ pub struct ImportRule { /// ImportSheet just has stub behavior until it appears. pub stylesheet: ImportSheet, + /// A `layer()` function name. + pub layer: Option, + /// The line and column of the rule's source code. pub source_location: SourceLocation, } @@ -170,6 +199,16 @@ impl DeepCloneWithLock for ImportRule { ImportRule { url: self.url.clone(), stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params), + layer: self.layer.as_ref().map(|layer| { + ImportLayer { + is_anonymous: layer.is_anonymous, + name: if layer.is_anonymous { + LayerName::new_anonymous() + } else { + layer.name.clone() + }, + } + }), source_location: self.source_location.clone(), } } @@ -180,14 +219,18 @@ impl ToCssWithGuard for ImportRule { dest.write_str("@import ")?; self.url.to_css(&mut CssWriter::new(dest))?; - match self.stylesheet.media(guard) { - Some(media) if !media.is_empty() => { - dest.write_str(" ")?; + if let Some(media) = self.stylesheet.media(guard) { + if !media.is_empty() { + dest.write_char(' ')?; media.to_css(&mut CssWriter::new(dest))?; - }, - _ => {}, - }; + } + } - dest.write_str(";") + if let Some(ref layer) = self.layer { + dest.write_char(' ')?; + layer.to_css(&mut CssWriter::new(dest))?; + } + + dest.write_char(';') } } diff --git a/components/style/stylesheets/loader.rs b/components/style/stylesheets/loader.rs index ce7c563db05..c145bafdc04 100644 --- a/components/style/stylesheets/loader.rs +++ b/components/style/stylesheets/loader.rs @@ -8,7 +8,7 @@ use crate::media_queries::MediaList; use crate::parser::ParserContext; use crate::shared_lock::{Locked, SharedRwLock}; -use crate::stylesheets::import_rule::ImportRule; +use crate::stylesheets::import_rule::{ImportRule, ImportLayer}; use crate::values::CssUrl; use cssparser::SourceLocation; use servo_arc::Arc; @@ -25,5 +25,6 @@ pub trait StylesheetLoader { context: &ParserContext, lock: &SharedRwLock, media: Arc>, + layer: Option, ) -> Arc>; } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 7fb76a8d7a9..421431a8c10 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -18,6 +18,7 @@ use crate::stylesheets::font_feature_values_rule::parse_family_name_list; use crate::stylesheets::keyframes_rule::parse_keyframe_list; use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; +use crate::stylesheets::import_rule::ImportLayer; use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind}; use crate::stylesheets::viewport_rule; use crate::stylesheets::AllowImportRules; @@ -169,7 +170,7 @@ pub enum AtRulePrelude { /// A @document rule, with its conditional. Document(DocumentCondition), /// A @import rule prelude. - Import(CssUrl, Arc>), + Import(CssUrl, Arc>, Option), /// A @namespace rule prelude. Namespace(Option, Namespace), /// A @layer rule prelude. @@ -206,10 +207,29 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { let url_string = input.expect_url_or_string()?.as_ref().to_owned(); let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None); + let layer = if !static_prefs::pref!("layout.css.cascade-layers.enabled") { + None + } else if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() { + Some(ImportLayer { + is_anonymous: true, + name: LayerName::new_anonymous(), + }) + } else { + input.try_parse(|input| { + input.expect_function_matching("layer")?; + input.parse_nested_block(|input| { + LayerName::parse(&self.context, input) + }).map(|name| ImportLayer { + is_anonymous: false, + name, + }) + }).ok() + }; + let media = MediaList::parse(&self.context, input); let media = Arc::new(self.shared_lock.wrap(media)); - return Ok(AtRulePrelude::Import(url, media)); + return Ok(AtRulePrelude::Import(url, media, layer)); }, "namespace" => { if !self.check_state(State::Namespaces) { @@ -263,7 +283,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { start: &ParserState, ) -> Result { let rule = match prelude { - AtRulePrelude::Import(url, media) => { + AtRulePrelude::Import(url, media, layer) => { let loader = self .loader .expect("Expected a stylesheet loader for @import"); @@ -274,6 +294,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { &self.context, &self.shared_lock, media, + layer, ); self.state = State::Imports; diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 0a87da95bc8..1c90886e17d 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2329,15 +2329,36 @@ impl CascadeData { continue; } + + fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> u32 { + // TODO: Measure what's more common / expensive, if + // layer.clone() or the double hash lookup in the insert + // case. + if let Some(order) = data.layer_order.get(layer) { + return *order; + } + let order = data.next_layer_order; + data.layer_order.insert(layer.clone(), order); + data.next_layer_order += 1; + order + } + let mut layer_names_to_pop = 0; let mut children_layer_order = current_layer_order; match *rule { CssRule::Import(ref lock) => { + let import_rule = lock.read_with(guard); if rebuild_kind.should_rebuild_invalidation() { - let import_rule = lock.read_with(guard); self.effective_media_query_results .saw_effective(import_rule); } + if let Some(ref layer) = import_rule.layer { + for name in layer.name.layer_names() { + current_layer.0.push(name.clone()); + children_layer_order = maybe_register_layer(self, ¤t_layer); + layer_names_to_pop += 1; + } + } }, CssRule::Media(ref lock) => { @@ -2349,19 +2370,6 @@ impl CascadeData { CssRule::Layer(ref lock) => { use crate::stylesheets::layer_rule::LayerRuleKind; - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> u32 { - // TODO: Measure what's more common / expensive, if - // layer.clone() or the double hash lookup in the insert - // case. - if let Some(order) = data.layer_order.get(layer) { - return *order; - } - let order = data.next_layer_order; - data.layer_order.insert(layer.clone(), order); - data.next_layer_order += 1; - order - } - let layer_rule = lock.read_with(guard); match layer_rule.kind { LayerRuleKind::Block { ref name, .. } => { From f063308e338a2217dae5b71ea45cbaf473dc8ff3 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 06:22:32 +0200 Subject: [PATCH 22/94] Further changes required by Servo --- components/script/stylesheet_loader.rs | 6 ++++-- components/style/stylesheets/rule_parser.rs | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 48e3016458e..9657227f91a 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -38,7 +38,7 @@ use std::sync::Mutex; use style::media_queries::MediaList; use style::parser::ParserContext; use style::shared_lock::{Locked, SharedRwLock}; -use style::stylesheets::import_rule::ImportSheet; +use style::stylesheets::import_rule::{ImportLayer, ImportSheet}; use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents}; use style::values::CssUrl; @@ -358,6 +358,7 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> { context: &ParserContext, lock: &SharedRwLock, media: Arc>, + layer: Option, ) -> Arc> { let sheet = Arc::new(Stylesheet { contents: StylesheetContents::from_shared_data( @@ -374,8 +375,9 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> { let stylesheet = ImportSheet(sheet.clone()); let import = ImportRule { url, - source_location, stylesheet, + layer, + source_location, }; let url = match import.url.url().cloned() { diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 421431a8c10..9ee3301094d 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -207,7 +207,12 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { let url_string = input.expect_url_or_string()?.as_ref().to_owned(); let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None); - let layer = if !static_prefs::pref!("layout.css.cascade-layers.enabled") { + #[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 { From d299040d55b41e840d6e4499ee1b9ea8ee96fcac Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Sat, 27 May 2023 06:10:30 +0200 Subject: [PATCH 23/94] style: Fix the serialization order for the text-decoration shorthand to match the canonical order in the spec Differential Revision: https://phabricator.services.mozilla.com/D124536 --- .../style/properties/shorthands/text.mako.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/style/properties/shorthands/text.mako.rs b/components/style/properties/shorthands/text.mako.rs index 03637d557e5..5e71fc08cf3 100644 --- a/components/style/properties/shorthands/text.mako.rs +++ b/components/style/properties/shorthands/text.mako.rs @@ -88,6 +88,13 @@ has_value = true; } + if !is_auto_thickness { + if has_value { + dest.write_str(" ")?; + } + self.text_decoration_thickness.to_css(dest)?; + } + % if engine == "gecko": if !is_solid_style { if has_value { @@ -104,13 +111,6 @@ self.text_decoration_color.to_css(dest)?; has_value = true; } - - if !is_auto_thickness { - if has_value { - dest.write_str(" ")?; - } - self.text_decoration_thickness.to_css(dest)?; - } % endif Ok(()) From f351d955ad3a14962c6fccf6449144c9b7abd899 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 06:28:11 +0200 Subject: [PATCH 24/94] Further changes required by Servo --- components/style/properties/shorthands/text.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/shorthands/text.mako.rs b/components/style/properties/shorthands/text.mako.rs index 5e71fc08cf3..3f892affdf5 100644 --- a/components/style/properties/shorthands/text.mako.rs +++ b/components/style/properties/shorthands/text.mako.rs @@ -88,6 +88,7 @@ has_value = true; } + % if engine == "gecko": if !is_auto_thickness { if has_value { dest.write_str(" ")?; @@ -95,7 +96,6 @@ self.text_decoration_thickness.to_css(dest)?; } - % if engine == "gecko": if !is_solid_style { if has_value { dest.write_str(" ")?; From 92092bf0aa6bac32745ffcd27f698e75c2a59221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:25:01 +0200 Subject: [PATCH 25/94] style: Fix out of order child layer registration, and enable the tests This makes layer order use a fixed set of bits per nesting level, to "reserve" bits for children before they are registered. See the comment in LayerOrder for the implementation limits it imposes, and potential alternatives if these limits are not enough (but I think they should be). Enable the tests, as they mostly pass now (commit incoming to fix the remaining ones). Differential Revision: https://phabricator.services.mozilla.com/D124620 --- components/style/stylesheets/layer_rule.rs | 91 ++++++++++++++++++++++ components/style/stylist.rs | 59 +++++++++----- 2 files changed, 132 insertions(+), 18 deletions(-) diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index 8d51558eccd..a012c9c5474 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -19,6 +19,97 @@ use smallvec::SmallVec; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; +/// The order of a given layer. We encode in a 32-bit integer as follows: +/// +/// * 0 is reserved for the initial (top-level) layer. +/// * Top 7 bits are for top level layer order. +/// * The 25 remaining bits are split in 5 chunks of 5 bits each, for each +/// nesting level. +/// +/// This scheme this gives up to 127 layers in the top level, and up to 31 +/// children layers in nested levels, with a max of 6 nesting levels over all. +/// +/// This seemingly complicated scheme is to avoid fixing up layer orders after +/// the cascade data rebuild. +/// +/// An alternative approach that would allow improving those limits would be to +/// make layers have a sequential identifier, and sort layer order after the +/// fact. But that complicates incremental cascade data rebuild. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerOrder(u32); + +impl LayerOrder { + const FIRST_LEVEL_BITS: usize = 7; + const CHILD_BITS: usize = 5; + const FIRST_LEVEL_MASK: u32 = 0b11111110_00000000_00000000_00000000; + const CHILD_MASK: u32 = 0b00011111; + + /// Get the raw value. + pub fn raw(self) -> u32 { + self.0 + } + + /// The top level layer (implicit) is zero. + #[inline] + pub const fn top_level() -> Self { + Self(0) + } + + /// The first layer order. + #[inline] + pub const fn first() -> Self { + Self(1 << (32 - Self::FIRST_LEVEL_BITS)) + } + + fn child_bit_offset(self) -> usize { + if self.0 & (Self::CHILD_MASK << 5) != 0 { + return 0; // We're at the last or next-to-last level. + } + if self.0 & (Self::CHILD_MASK << 10) != 0 { + return 5; + } + if self.0 & (Self::CHILD_MASK << 15) != 0 { + return 10; + } + if self.0 & (Self::CHILD_MASK << 20) != 0 { + return 15; + } + if self.0 != 0 { + return 20; + } + return 25; + } + + fn sibling_bit_mask_max_and_offset(self) -> (u32, u32, u32) { + debug_assert_ne!(self.0, 0, "Top layer should have no siblings"); + for offset in &[0, 5, 10, 15, 20] { + let mask = Self::CHILD_MASK << *offset; + if self.0 & mask != 0 { + return (mask, (1 << Self::CHILD_BITS) - 1, *offset); + } + } + return (Self::FIRST_LEVEL_MASK, (1 << Self::FIRST_LEVEL_BITS) - 1, 25); + } + + /// Generate the layer order for our first child. + pub fn for_child(self) -> Self { + Self(self.0 | (1 << self.child_bit_offset())) + } + + /// Generate the layer order for our next sibling. Might return the same + /// order when our limits overflow. + pub fn for_next_sibling(self) -> Self { + let (mask, max_index, offset) = self.sibling_bit_mask_max_and_offset(); + let self_index = (self.0 & mask) >> offset; + let next_index = if self_index == max_index { + self_index + } else { + self_index + 1 + }; + Self((self.0 & !mask) | (next_index << offset)) + } +} + /// A ``: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] pub struct LayerName(pub SmallVec<[AtomIdent; 1]>); diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 1c90886e17d..cd05ee42f76 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -25,7 +25,7 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::LayerName; +use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; #[cfg(feature = "gecko")] @@ -1867,6 +1867,14 @@ impl PartElementAndPseudoRules { } } +#[derive(Debug, Clone, MallocSizeOf)] +struct LayerOrderState { + /// The order for this layer. + order: LayerOrder, + /// The order for the next registered child layer. + next_child: LayerOrder, +} + /// Data resulting from performing the CSS cascade that is specific to a given /// origin. /// @@ -1933,10 +1941,10 @@ pub struct CascadeData { animations: PrecomputedHashMap, /// A map from cascade layer name to layer order. - layer_order: FxHashMap, + layer_order: FxHashMap, - /// The next layer order for this cascade data. - next_layer_order: u32, + /// The next layer order for the top level cascade data. + next_layer_order: LayerOrder, /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -1979,7 +1987,7 @@ impl CascadeData { selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), animations: Default::default(), layer_order: Default::default(), - next_layer_order: 1, // 0 reserved for the root scope. + next_layer_order: LayerOrder::first(), extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2143,7 +2151,7 @@ impl CascadeData { guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, current_layer: &mut LayerName, - current_layer_order: u32, + current_layer_order: LayerOrder, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where @@ -2176,7 +2184,7 @@ impl CascadeData { self.rules_source_order, CascadeLevel::UANormal, selector.specificity(), - current_layer_order, + current_layer_order.raw(), )); continue; } @@ -2330,16 +2338,31 @@ impl CascadeData { } - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> u32 { + fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerOrder { // TODO: Measure what's more common / expensive, if // layer.clone() or the double hash lookup in the insert // case. - if let Some(order) = data.layer_order.get(layer) { - return *order; + if let Some(ref mut state) = data.layer_order.get(layer) { + return state.order; } - let order = data.next_layer_order; - data.layer_order.insert(layer.clone(), order); - data.next_layer_order += 1; + // If the layer is not top-level, find the relevant parent. + let order = if layer.layer_names().len() > 1 { + let mut parent = layer.clone(); + parent.0.pop(); + + let mut parent_state = data.layer_order.get_mut(&parent).expect("Parent layers should be registered before child layers"); + let order = parent_state.next_child; + parent_state.next_child = order.for_next_sibling(); + order + } else { + let order = data.next_layer_order; + data.next_layer_order = order.for_next_sibling(); + order + }; + data.layer_order.insert(layer.clone(), LayerOrderState { + order, + next_child: order.for_child(), + }); order } @@ -2448,7 +2471,7 @@ impl CascadeData { guard, rebuild_kind, &mut current_layer, - /* current_layer_order = */ 0, + LayerOrder::top_level(), precomputed_pseudo_element_decls.as_deref_mut(), )?; @@ -2567,7 +2590,7 @@ impl CascadeData { } self.animations.clear(); self.layer_order.clear(); - self.next_layer_order = 1; + self.next_layer_order = LayerOrder::first(); self.extra_data.clear(); self.rules_source_order = 0; self.num_selectors = 0; @@ -2657,7 +2680,7 @@ pub struct Rule { pub source_order: u32, /// The current layer order of this style rule. - pub layer_order: u32, + pub layer_order: LayerOrder, /// The actual style rule. #[cfg_attr( @@ -2687,7 +2710,7 @@ impl Rule { level: CascadeLevel, ) -> ApplicableDeclarationBlock { let source = StyleSource::from_rule(self.style_rule.clone()); - ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), self.layer_order) + ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), self.layer_order.raw()) } /// Creates a new Rule. @@ -2696,7 +2719,7 @@ impl Rule { hashes: AncestorHashes, style_rule: Arc>, source_order: u32, - layer_order: u32, + layer_order: LayerOrder, ) -> Self { Rule { selector, From a6f9a364dbdb4e19ee39633d85d1cc0fa6ddd8ed Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 30 May 2023 23:23:17 +0200 Subject: [PATCH 26/94] Further changes required by Servo --- tests/unit/style/stylist.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 3df3826d7d7..0c81a8e3d0f 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -15,6 +15,7 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::selector_map::SelectorMap; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::shared_lock::SharedRwLock; +use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::StyleRule; use style::stylist::needs_revalidation_for_testing; use style::stylist::{Rule, Stylist}; @@ -52,7 +53,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { AncestorHashes::new(s, QuirksMode::NoQuirks), locked.clone(), i as u32, - 0 + LayerOrder::top_level(), ) }) .collect() From 0cc049946eb9f1554bd3f5db00d65419a35f2f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:55:41 +0200 Subject: [PATCH 27/94] style: Fix layer statement with nested layer names When we had: @layer A.B; We were registering "A" and "B", not "A" and "A.B", which was the intention. Fix is trivial. Depends on D124620 Differential Revision: https://phabricator.services.mozilla.com/D124621 --- components/style/stylist.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index cd05ee42f76..4be6e035378 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2404,9 +2404,13 @@ impl CascadeData { } LayerRuleKind::Statement { ref names } => { for name in &**names { + let mut pushed = 0; for name in name.layer_names() { current_layer.0.push(name.clone()); maybe_register_layer(self, ¤t_layer); + pushed += 1; + } + for _ in 0..pushed { current_layer.0.pop(); } } From 7108be870d6b85d7d31243dd86bb434d44636ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 06:58:09 +0200 Subject: [PATCH 28/94] style: Add a simple CSSLayerRule implementation The specifics of how this is going to work are still getting spec'd / discussed in https://github.com/w3c/csswg-drafts/issues/6576, but this allows DevTools to work fine and the feature to be complete enough for Nightly experimentation (with the other in-flight patches). Otherwise devtools crashes when trying to inspect pages that use them. Differential Revision: https://phabricator.services.mozilla.com/D124656 --- components/style/gecko/arc_types.rs | 39 +++++++++++++---------------- components/style/stylesheets/mod.rs | 5 ++-- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 7aed10b6568..788f75f2b2f 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -9,34 +9,26 @@ #![allow(non_snake_case, missing_docs)] use crate::gecko::url::CssUrlData; -use crate::gecko_bindings::structs::RawServoAnimationValue; -use crate::gecko_bindings::structs::RawServoCounterStyleRule; -use crate::gecko_bindings::structs::RawServoCssUrlData; -use crate::gecko_bindings::structs::RawServoDeclarationBlock; -use crate::gecko_bindings::structs::RawServoFontFaceRule; -use crate::gecko_bindings::structs::RawServoFontFeatureValuesRule; -use crate::gecko_bindings::structs::RawServoImportRule; -use crate::gecko_bindings::structs::RawServoKeyframe; -use crate::gecko_bindings::structs::RawServoKeyframesRule; -use crate::gecko_bindings::structs::RawServoMediaList; -use crate::gecko_bindings::structs::RawServoMediaRule; -use crate::gecko_bindings::structs::RawServoMozDocumentRule; -use crate::gecko_bindings::structs::RawServoNamespaceRule; -use crate::gecko_bindings::structs::RawServoPageRule; -use crate::gecko_bindings::structs::RawServoStyleRule; -use crate::gecko_bindings::structs::RawServoStyleSheetContents; -use crate::gecko_bindings::structs::RawServoSupportsRule; -use crate::gecko_bindings::structs::ServoCssRules; +use crate::gecko_bindings::structs::{ + RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, + RawServoDeclarationBlock, RawServoFontFaceRule, + RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe, + RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, + RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, + RawServoPageRule, RawServoStyleRule, RawServoStyleSheetContents, + RawServoSupportsRule, ServoCssRules +}; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; use crate::properties::animated_properties::AnimationValue; use crate::properties::{ComputedValues, PropertyDeclarationBlock}; use crate::shared_lock::Locked; use crate::stylesheets::keyframes_rule::Keyframe; -use crate::stylesheets::{CounterStyleRule, CssRules, FontFaceRule, FontFeatureValuesRule}; -use crate::stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule}; -use crate::stylesheets::{NamespaceRule, PageRule}; -use crate::stylesheets::{StyleRule, StylesheetContents, SupportsRule}; +use crate::stylesheets::{ + CounterStyleRule, CssRules, FontFaceRule, FontFeatureValuesRule, + DocumentRule, ImportRule, KeyframesRule, LayerRule, MediaRule, + NamespaceRule, PageRule, StyleRule, StylesheetContents, SupportsRule +}; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -83,6 +75,9 @@ impl_arc_ffi!(Locked => RawServoKeyframe impl_arc_ffi!(Locked => RawServoKeyframesRule [Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]); +impl_arc_ffi!(Locked => RawServoLayerRule + [Servo_LayerRule_AddRef, Servo_LayerRule_Release]); + impl_arc_ffi!(Locked => RawServoMediaList [Servo_MediaList_AddRef, Servo_MediaList_Release]); diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 360a17b2f9a..a2f4a705b11 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -310,10 +310,11 @@ impl CssRule { /// https://drafts.csswg.org/cssom-1/#dom-cssrule-type #[allow(missing_docs)] #[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)] +#[repr(u8)] pub enum CssRuleType { // https://drafts.csswg.org/cssom/#the-cssrule-interface Style = 1, - Charset = 2, + // Charset = 2, // Historical Import = 3, Media = 4, FontFace = 5, @@ -322,7 +323,7 @@ pub enum CssRuleType { Keyframes = 7, Keyframe = 8, // https://drafts.csswg.org/cssom/#the-cssrule-interface - Margin = 9, + // Margin = 9, // Not implemented yet. Namespace = 10, // https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface CounterStyle = 11, From 1e5806610b47beb0344b73e45144f334e8be3e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 07:05:23 +0200 Subject: [PATCH 29/94] style: Don't consider system-ui valid for user font prioritization Since the user can't configure it, at least from the UI (we could add UI for it but it's unclear it'd be worth it). Differential Revision: https://phabricator.services.mozilla.com/D125182 --- components/style/properties/cascade.rs | 7 +----- components/style/values/computed/font.rs | 27 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index e3f919cc56e..b64ff4c6349 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -916,12 +916,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // we have a generic family to actually replace it with. let prioritize_user_fonts = !use_document_fonts && default_font_type != GenericFontFamily::None && - matches!( - generic, - GenericFontFamily::None | - GenericFontFamily::Fantasy | - GenericFontFamily::Cursive - ); + !generic.valid_for_user_font_prioritization(); if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback { // Nothing to do. diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index e2b29f61d5e..0306c88cb0f 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -439,6 +439,25 @@ pub enum GenericFontFamily { MozEmoji, } +impl GenericFontFamily { + /// When we disallow websites to override fonts, we ignore some generic + /// families that the website might specify, since they're not configured by + /// the user. See bug 789788 and bug 1730098. + pub (crate) fn valid_for_user_font_prioritization(self) -> bool { + match self { + Self::None | + Self::Fantasy | + Self::Cursive | + Self::SystemUi | + Self::MozEmoji => false, + + Self::Serif | + Self::SansSerif | + Self::Monospace => true, + } + } +} + impl Parse for SingleFontFamily { /// Parse a font-family value. fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { @@ -579,14 +598,14 @@ impl FontFamilyList { self.list = crate::ArcSlice::from_iter(new_list.into_iter()); } - /// If there's a generic font family on the list (which isn't cursive or - /// fantasy), then move it to the front of the list. Otherwise, prepend the - /// default generic. + /// If there's a generic font family on the list which is suitable for user + /// font prioritization, then move it to the front of the list. Otherwise, + /// prepend the default generic. #[cfg(feature = "gecko")] pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { let index_of_first_generic = self.iter().position(|f| { match *f { - SingleFontFamily::Generic(f) => f != GenericFontFamily::Cursive && f != GenericFontFamily::Fantasy, + SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(), _ => false, } }); From 4f193fbf4921cd48d7a00df0b3b734bb7090c4a4 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 07:10:32 +0200 Subject: [PATCH 30/94] Further changes required by Servo --- components/style/values/computed/font.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index 0306c88cb0f..18fbe8c6fd8 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -443,6 +443,7 @@ 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 | From 4522e7f94a05e15546ffd2bc1559fa0634e22f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 07:12:22 +0200 Subject: [PATCH 31/94] style: Add experimental support for "e", "pi", and various trigonometric functions in calc() I'll add some tests before enabling. Also, WebKit folks (who have implemented cos() / tan() / sin()) said they will upstream their tests to WPT, so I'll extend those with the inverse functions before landing as well. Differential Revision: https://phabricator.services.mozilla.com/D124990 --- components/style/values/specified/angle.rs | 16 ++++ components/style/values/specified/calc.rs | 85 ++++++++++++++++++++-- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/components/style/values/specified/angle.rs b/components/style/values/specified/angle.rs index fa60f66507d..08c6b443678 100644 --- a/components/style/values/specified/angle.rs +++ b/components/style/values/specified/angle.rs @@ -130,6 +130,15 @@ impl Angle { } } + /// Creates an angle with the given value in radians. + #[inline] + pub fn from_radians(value: CSSFloat) -> Self { + Angle { + value: AngleDimension::Rad(value), + was_calc: false, + } + } + /// Return `0deg`. pub fn zero() -> Self { Self::from_degrees(0.0, false) @@ -141,6 +150,13 @@ impl Angle { self.value.degrees() } + /// Returns the value of the angle in radians. + #[inline] + pub fn radians(&self) -> CSSFloat { + const RAD_PER_DEG: f32 = PI / 180.0; + self.value.degrees() * RAD_PER_DEG + } + /// Whether this specified angle came from a `calc()` expression. #[inline] pub fn was_calc(&self) -> bool { diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index a22d6fd5f18..39167a13647 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -21,7 +21,7 @@ use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; /// The name of the mathematical function that we're parsing. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Parse)] pub enum MathFunction { /// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc Calc, @@ -31,6 +31,18 @@ pub enum MathFunction { Max, /// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp Clamp, + /// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin + Sin, + /// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos + Cos, + /// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan + Tan, + /// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin + Asin, + /// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos + Acos, + /// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan + Atan, } /// A leaf node inside a `Calc` expression's AST. @@ -301,6 +313,17 @@ impl CalcNode { let function = CalcNode::math_function(name, location)?; CalcNode::parse(context, input, function, expected_unit) }, + (&Token::Ident(ref ident), _) => { + if !static_prefs::pref!("layout.css.trig.enabled") { + return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))); + } + let number = match_ignore_ascii_case! { &**ident, + "e" => std::f32::consts::E, + "pi" => std::f32::consts::PI, + _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))), + }; + Ok(CalcNode::Leaf(Leaf::Number(number))) + }, (t, _) => Err(location.new_unexpected_token_error(t.clone())), } } @@ -350,6 +373,47 @@ impl CalcNode { Ok(Self::MinMax(arguments.into(), op)) }, + MathFunction::Sin | + MathFunction::Cos | + MathFunction::Tan => { + let argument = Self::parse_argument(context, input, CalcUnit::Angle)?; + let radians = match argument.to_number() { + Ok(v) => v, + Err(()) => match argument.to_angle() { + Ok(angle) => angle.radians(), + Err(()) => return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ), + }, + }; + let number = match function { + MathFunction::Sin => radians.sin(), + MathFunction::Cos => radians.cos(), + MathFunction::Tan => radians.tan(), + _ => unsafe { debug_unreachable!("We just checked!"); }, + }; + Ok(Self::Leaf(Leaf::Number(number))) + }, + MathFunction::Asin | + MathFunction::Acos | + MathFunction::Atan => { + let argument = Self::parse_argument(context, input, CalcUnit::Number)?; + let number = match argument.to_number() { + Ok(v) => v, + Err(()) => return Err( + input.new_custom_error(StyleParseErrorKind::UnspecifiedError) + ), + }; + + let radians = match function { + MathFunction::Asin => number.asin(), + MathFunction::Acos => number.acos(), + MathFunction::Atan => number.atan(), + _ => unsafe { debug_unreachable!("We just checked!"); }, + }; + + Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) + }, } }) } @@ -522,13 +586,18 @@ impl CalcNode { name: &CowRcStr<'i>, location: cssparser::SourceLocation, ) -> Result> { - Ok(match_ignore_ascii_case! { &*name, - "calc" => MathFunction::Calc, - "min" => MathFunction::Min, - "max" => MathFunction::Max, - "clamp" => MathFunction::Clamp, - _ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), - }) + use self::MathFunction::*; + + let function = match MathFunction::from_ident(&*name) { + Ok(f) => f, + Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), + }; + + if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !static_prefs::pref!("layout.css.trig.enabled") { + return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); + } + + Ok(function) } /// Convenience parsing function for integers. From 35bf5f0b093b76cd03449a68fe7b89e3262b79b8 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 07:17:13 +0200 Subject: [PATCH 32/94] Further changes required by Servo --- components/style/values/specified/calc.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index 39167a13647..a043e4ea96e 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -243,6 +243,13 @@ impl generic::CalcNodeLeaf for Leaf { } } +fn trig_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.trig.enabled"); + #[cfg(feature = "servo")] + return false; +} + /// A calc node representation for specified values. pub type CalcNode = generic::GenericCalcNode; @@ -314,7 +321,7 @@ impl CalcNode { CalcNode::parse(context, input, function, expected_unit) }, (&Token::Ident(ref ident), _) => { - if !static_prefs::pref!("layout.css.trig.enabled") { + if !trig_enabled() { return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))); } let number = match_ignore_ascii_case! { &**ident, @@ -593,7 +600,7 @@ impl CalcNode { Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), }; - if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !static_prefs::pref!("layout.css.trig.enabled") { + if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !trig_enabled() { return Err(location.new_unexpected_token_error(Token::Function(name.clone()))); } From 5fe148d5f1de1284743cb2238a9bca3d1a127282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 07:20:11 +0200 Subject: [PATCH 33/94] style: Add some parsing and serialization tests for @layer This uncovers some serialization bugs, and some missing null-checks given the statement layer doesn't have a rule list. Differential Revision: https://phabricator.services.mozilla.com/D125176 --- components/style/stylesheets/layer_rule.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index a012c9c5474..c246d5e1b0f 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -234,7 +234,7 @@ impl ToCssWithGuard for LayerRule { guard: &SharedRwLockReadGuard, dest: &mut crate::str::CssStringWriter, ) -> fmt::Result { - dest.write_str("@layer ")?; + dest.write_str("@layer")?; match self.kind { LayerRuleKind::Block { ref name, @@ -242,8 +242,8 @@ impl ToCssWithGuard for LayerRule { ref is_anonymous, } => { if !*is_anonymous { - name.to_css(&mut CssWriter::new(dest))?; dest.write_char(' ')?; + name.to_css(&mut CssWriter::new(dest))?; } rules.read_with(guard).to_css_block(guard, dest) }, @@ -251,7 +251,9 @@ impl ToCssWithGuard for LayerRule { let mut writer = CssWriter::new(dest); let mut first = true; for name in &**names { - if !first { + if first { + writer.write_char(' ')?; + } else { writer.write_str(", ")?; } first = false; From 3e251f50fcf7f7755269510ca7fa3e57961e4793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 07:21:38 +0200 Subject: [PATCH 34/94] style: Fix anonymous name handling in presence of stylesheet sharing We need to compute the anonymous name on the fly while building the CascadeData, otherwise we may see the same layer rule in two places due to stylehseet sharing and make them incorrectly share a name. Differential Revision: https://phabricator.services.mozilla.com/D125175 --- components/style/stylesheets/import_rule.rs | 32 ++++------- components/style/stylesheets/layer_rule.rs | 18 ++---- components/style/stylesheets/rule_parser.rs | 12 ++-- components/style/stylist.rs | 63 +++++++++++++++------ 4 files changed, 65 insertions(+), 60 deletions(-) diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index d60802cf0ab..48473ff61cd 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -137,12 +137,10 @@ impl DeepCloneWithLock for ImportSheet { } /// The layer keyword or function in an import rule. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ImportLayer { - /// Whether the layer is anonymous. - pub is_anonymous: bool, - /// The layer name. - pub name: LayerName, + /// The layer name, or None for an anonymous layer. + pub name: Option, } @@ -151,12 +149,13 @@ impl ToCss for ImportLayer { where W: Write, { - if self.is_anonymous { - dest.write_str("layer") - } else { - dest.write_str("layer(")?; - self.name.to_css(dest)?; - dest.write_char(')') + match self.name { + None => dest.write_str("layer"), + Some(ref name) => { + dest.write_str("layer(")?; + name.to_css(dest)?; + dest.write_char(')') + }, } } } @@ -199,16 +198,7 @@ impl DeepCloneWithLock for ImportRule { ImportRule { url: self.url.clone(), stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params), - layer: self.layer.as_ref().map(|layer| { - ImportLayer { - is_anonymous: layer.is_anonymous, - name: if layer.is_anonymous { - LayerName::new_anonymous() - } else { - layer.name.clone() - }, - } - }), + layer: self.layer.clone(), source_location: self.source_location.clone(), } } diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index c246d5e1b0f..45649dcfece 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -202,13 +202,10 @@ impl ToCss for LayerName { pub enum LayerRuleKind { /// A block `@layer ? { ... }` Block { - /// The layer name. - name: LayerName, + /// The layer name, or `None` if anonymous. + name: Option, /// The nested rules. rules: Arc>, - /// Whether the layer name is synthesized (and thus shouldn't be - /// serialized). - is_anonymous: bool, }, /// A statement `@layer , , ;` Statement { @@ -239,9 +236,8 @@ impl ToCssWithGuard for LayerRule { LayerRuleKind::Block { ref name, ref rules, - ref is_anonymous, } => { - if !*is_anonymous { + if let Some(ref name) = *name { dest.write_char(' ')?; name.to_css(&mut CssWriter::new(dest))?; } @@ -277,13 +273,8 @@ impl DeepCloneWithLock for LayerRule { LayerRuleKind::Block { ref name, ref rules, - ref is_anonymous, } => LayerRuleKind::Block { - name: if *is_anonymous { - LayerName::new_anonymous() - } else { - name.clone() - }, + name: name.clone(), rules: Arc::new( lock.wrap( rules @@ -291,7 +282,6 @@ impl DeepCloneWithLock for LayerRule { .deep_clone_with_lock(lock, guard, params), ), ), - is_anonymous: *is_anonymous, }, LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement { names: names.clone(), diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 9ee3301094d..dd85144a45f 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -216,8 +216,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { None } else if input.try_parse(|input| input.expect_ident_matching("layer")).is_ok() { Some(ImportLayer { - is_anonymous: true, - name: LayerName::new_anonymous(), + name: None, }) } else { input.try_parse(|input| { @@ -225,8 +224,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { input.parse_nested_block(|input| { LayerName::parse(&self.context, input) }).map(|name| ImportLayer { - is_anonymous: false, - name, + name: Some(name), }) }).ok() }; @@ -603,9 +601,8 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { )))) }, AtRulePrelude::Layer(names) => { - let (name, is_anonymous) = match names.len() { - 0 => (LayerName::new_anonymous(), true), - 1 => (names.into_iter().next().unwrap(), false), + let name = match names.len() { + 0 | 1 => names.into_iter().next(), _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), }; Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap( @@ -613,7 +610,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { kind: LayerRuleKind::Block { name, rules: self.parse_nested_rules(input, CssRuleType::Layer), - is_anonymous, }, source_location: start.source_location(), }, diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 4be6e035378..d3d1a24a9d9 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2150,7 +2150,7 @@ impl CascadeData { stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, - current_layer: &mut LayerName, + mut current_layer: &mut LayerName, current_layer_order: LayerOrder, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> @@ -2337,7 +2337,6 @@ impl CascadeData { continue; } - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerOrder { // TODO: Measure what's more common / expensive, if // layer.clone() or the double hash lookup in the insert @@ -2366,6 +2365,31 @@ impl CascadeData { order } + fn maybe_register_layers( + data: &mut CascadeData, + name: Option<&LayerName>, + current_layer: &mut LayerName, + pushed_layers: &mut usize, + ) -> LayerOrder { + let anon_name; + let name = match name { + Some(name) => name, + None => { + anon_name = LayerName::new_anonymous(); + &anon_name + }, + }; + + let mut order = LayerOrder::top_level(); + for name in name.layer_names() { + current_layer.0.push(name.clone()); + order = maybe_register_layer(data, ¤t_layer); + *pushed_layers += 1; + } + debug_assert_ne!(order, LayerOrder::top_level()); + order + } + let mut layer_names_to_pop = 0; let mut children_layer_order = current_layer_order; match *rule { @@ -2376,11 +2400,12 @@ impl CascadeData { .saw_effective(import_rule); } if let Some(ref layer) = import_rule.layer { - for name in layer.name.layer_names() { - current_layer.0.push(name.clone()); - children_layer_order = maybe_register_layer(self, ¤t_layer); - layer_names_to_pop += 1; - } + children_layer_order = maybe_register_layers( + self, + layer.name.as_ref(), + &mut current_layer, + &mut layer_names_to_pop, + ); } }, @@ -2396,20 +2421,24 @@ impl CascadeData { let layer_rule = lock.read_with(guard); match layer_rule.kind { LayerRuleKind::Block { ref name, .. } => { - for name in name.layer_names() { - current_layer.0.push(name.clone()); - children_layer_order = maybe_register_layer(self, ¤t_layer); - layer_names_to_pop += 1; - } + children_layer_order = 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; - for name in name.layer_names() { - current_layer.0.push(name.clone()); - maybe_register_layer(self, ¤t_layer); - pushed += 1; - } + // 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(); } From 111c8d616fea68fa45fde2645c5e8cbf65431b8e Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 07:53:35 +0200 Subject: [PATCH 35/94] style: Part 1: Implement @scroll-timeline in style system Define the data structure for @scroll-timeline rule, the parsing code, and the serialization. Differential Revision: https://phabricator.services.mozilla.com/D125764 --- components/style/stylesheets/mod.rs | 1 + .../style/stylesheets/scroll_timeline_rule.rs | 315 ++++++++++++++++++ components/style/values/mod.rs | 46 ++- 3 files changed, 345 insertions(+), 17 deletions(-) create mode 100644 components/style/stylesheets/scroll_timeline_rule.rs diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index a2f4a705b11..d17bc3ffd05 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -20,6 +20,7 @@ mod page_rule; mod rule_list; mod rule_parser; mod rules_iterator; +pub mod scroll_timeline_rule; mod style_rule; mod stylesheet; pub mod supports_rule; diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs new file mode 100644 index 00000000000..f11e469f958 --- /dev/null +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -0,0 +1,315 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! scroll-timeline-at-rule: https://drafts.csswg.org/scroll-animations/#scroll-timeline-at-rule + +use crate::parser::{Parse, ParserContext}; +use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; +use crate::str::CssStringWriter; +use crate::values::specified::{LengthPercentage, Number}; +use crate::values::{AtomIdent, TimelineName}; +use cssparser::{AtRuleParser, CowRcStr, DeclarationParser, Parser, SourceLocation, Token}; +use selectors::parser::SelectorParseErrorKind; +use std::fmt::{self, Debug, Write}; +use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; + +/// A [`@scroll-timeline`][descriptors] rule. +/// +/// [descriptors] https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors +#[derive(Clone, Debug, ToShmem)] +pub struct ScrollTimelineRule { + /// The name of the current scroll timeline. + pub name: TimelineName, + /// The descriptors. + pub descriptors: ScrollTimelineDescriptors, + /// The line and column of the rule's source code. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for ScrollTimelineRule { + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + let mut dest = CssWriter::new(dest); + dest.write_str("@scroll-timeline ")?; + self.name.to_css(&mut dest)?; + dest.write_str(" { ")?; + self.descriptors.to_css(&mut dest)?; + dest.write_str("}") + } +} + +/// The descriptors of @scroll-timeline. +/// +/// https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors +#[derive(Clone, Debug, Default, ToShmem)] +pub struct ScrollTimelineDescriptors { + /// The source of the current scroll timeline. + pub source: Option, + /// The orientation of the current scroll timeline. + pub orientation: Option, + /// The scroll timeline's scrollOffsets. + pub offsets: Option, +} + +impl Parse for ScrollTimelineDescriptors { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + use crate::cssparser::DeclarationListParser; + use crate::error_reporting::ContextualParseError; + + let mut descriptors = ScrollTimelineDescriptors::default(); + let parser = ScrollTimelineDescriptorsParser { + context, + descriptors: &mut descriptors, + }; + let mut iter = DeclarationListParser::new(input, parser); + while let Some(declaration) = iter.next() { + if let Err((error, slice)) = declaration { + let location = error.location; + let error = ContextualParseError::UnsupportedRule(slice, error); + context.log_css_error(location, error) + } + } + + Ok(descriptors) + } +} + +// Basically, this is used for the serialization of CSSScrollTimelineRule, so we follow the +// instructions in https://drafts.csswg.org/scroll-animations-1/#serialize-a-cssscrolltimelinerule. +impl ToCss for ScrollTimelineDescriptors { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if let Some(ref value) = self.source { + dest.write_str("source: ")?; + value.to_css(dest)?; + dest.write_str("; ")?; + } + + if let Some(ref value) = self.orientation { + dest.write_str("orientation: ")?; + value.to_css(dest)?; + dest.write_str("; ")?; + } + + // https://github.com/w3c/csswg-drafts/issues/6617 + if let Some(ref value) = self.offsets { + dest.write_str("scroll-offsets: ")?; + value.to_css(dest)?; + dest.write_str("; ")?; + } + Ok(()) + } +} + +struct ScrollTimelineDescriptorsParser<'a, 'b: 'a> { + context: &'a ParserContext<'b>, + descriptors: &'a mut ScrollTimelineDescriptors, +} + +impl<'a, 'b, 'i> AtRuleParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> { + type Prelude = (); + type AtRule = (); + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> DeclarationParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> { + type Declaration = (); + type Error = StyleParseErrorKind<'i>; + + fn parse_value<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + macro_rules! parse_descriptor { + ( + $( $name: tt / $ident: ident, )* + ) => { + match_ignore_ascii_case! { &*name, + $( + $name => { + let value = input.parse_entirely(|i| Parse::parse(self.context, i))?; + self.descriptors.$ident = Some(value) + }, + )* + _ => { + return Err(input.new_custom_error( + SelectorParseErrorKind::UnexpectedIdent(name.clone()), + )) + } + } + } + } + parse_descriptor! { + "source" / source, + "orientation" / orientation, + "scroll-offsets" / offsets, + }; + Ok(()) + } +} + +/// The scroll-timeline source. +/// +/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-source +#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] +pub enum Source { + /// The scroll container. + Selector(ScrollTimelineSelector), + /// The initial value. The scrollingElement of the Document associated with the Window that is + /// the current global object. + Auto, + /// Null. However, it's not clear what is the expected behavior of this. See the spec issue: + /// https://drafts.csswg.org/scroll-animations/#issue-0d1e73bd + None, +} + +/// 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, +} + +/// Scroll-timeline offsets. We treat None as an empty vector. +/// value: none | # +/// +/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-scroll-offsets +#[derive(Clone, Debug, ToCss, ToShmem)] +#[css(comma)] +pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimelineOffset]>); + +impl Parse for ScrollOffsets { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(ScrollOffsets(Box::new([]))); + } + + Ok(ScrollOffsets( + input + .parse_comma_separated(|i| ScrollTimelineOffset::parse(context, i))? + .into_boxed_slice(), + )) + } +} + +/// A . +/// value: auto | | +/// +/// https://drafts.csswg.org/scroll-animations/#typedef-scroll-timeline-offset +#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] +pub enum ScrollTimelineOffset { + /// The initial value. A container-based offset. + Auto, + /// A container-based offset with the distance indicated by the value along source's scroll + /// range in orientation. + LengthPercentage(LengthPercentage), + /// An element-based offset. + ElementOffset(ElementOffset), +} + +/// An . +/// +/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset-edge +#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)] +pub enum ElementOffsetEdge { + /// Start edge + Start, + /// End edge. + End, +} + +/// An . +/// value: selector( ) [ || ]? +/// +/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset +#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] +pub struct ElementOffset { + /// The target whose intersection with source's scrolling box determines the concrete scroll + /// offset. + target: ScrollTimelineSelector, + /// An optional value of . If not provided, the default value is start. + edge: Option, + /// An optional value of threshold. If not provided, the default value is 0. + threshold: Option, +} + +impl Parse for ElementOffset { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let target = ScrollTimelineSelector::parse(context, input)?; + + // Parse `[ || ]?` + let mut edge = input.try_parse(ElementOffsetEdge::parse).ok(); + let threshold = input.try_parse(|i| Number::parse(context, i)).ok(); + if edge.is_none() { + edge = input.try_parse(ElementOffsetEdge::parse).ok(); + } + + Ok(ElementOffset { + target, + edge, + threshold, + }) + } +} + +/// The type of the selector ID. +#[derive(Clone, Eq, PartialEq, ToShmem)] +pub struct ScrollTimelineSelector(AtomIdent); + +impl Parse for ScrollTimelineSelector { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + // Parse `selector()`. + input.expect_function_matching("selector")?; + input.parse_nested_block(|i| match i.next()? { + Token::IDHash(id) => Ok(ScrollTimelineSelector(id.as_ref().into())), + _ => Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + }) + } +} + +impl ToCss for ScrollTimelineSelector { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + use crate::cssparser::ToCss as CssparserToCss; + dest.write_str("selector(")?; + dest.write_char('#')?; + self.0.to_css(dest)?; + dest.write_char(')') + } +} + +impl Debug for ScrollTimelineSelector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(&mut CssWriter::new(f)) + } +} diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 79ec424ab3a..777b81e1da8 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -464,29 +464,33 @@ impl ToCss for CustomIdent { } } +/// The or . +/// The definition of these two names are the same, so we use the same type for them. +/// +/// /// #[derive( Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] -pub enum KeyframesName { +pub enum TimelineOrKeyframesName { /// Ident(CustomIdent), /// QuotedString(Atom), } -impl KeyframesName { +impl TimelineOrKeyframesName { /// pub fn from_ident(value: &str) -> Self { let location = SourceLocation { line: 0, column: 0 }; let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok(); match custom_ident { - Some(ident) => KeyframesName::Ident(ident), - None => KeyframesName::QuotedString(value.into()), + Some(ident) => Self::Ident(ident), + None => Self::QuotedString(value.into()), } } - /// Create a new KeyframesName from Atom. + /// Create a new TimelineOrKeyframesName from Atom. #[cfg(feature = "gecko")] pub fn from_atom(atom: Atom) -> Self { debug_assert_ne!(atom, atom!("")); @@ -494,19 +498,19 @@ impl KeyframesName { // FIXME: We might want to preserve , but currently Gecko // stores both of and into nsAtom, so // we can't tell it. - KeyframesName::Ident(CustomIdent(atom)) + Self::Ident(CustomIdent(atom)) } /// The name as an Atom pub fn as_atom(&self) -> &Atom { match *self { - KeyframesName::Ident(ref ident) => &ident.0, - KeyframesName::QuotedString(ref atom) => atom, + Self::Ident(ref ident) => &ident.0, + Self::QuotedString(ref atom) => atom, } } } -impl Eq for KeyframesName {} +impl Eq for TimelineOrKeyframesName {} /// A trait that returns whether a given type is the `auto` value or not. So far /// only needed for background-size serialization, which special-cases `auto`. @@ -515,13 +519,13 @@ pub trait IsAuto { fn is_auto(&self) -> bool; } -impl PartialEq for KeyframesName { +impl PartialEq for TimelineOrKeyframesName { fn eq(&self, other: &Self) -> bool { self.as_atom() == other.as_atom() } } -impl hash::Hash for KeyframesName { +impl hash::Hash for TimelineOrKeyframesName { fn hash(&self, state: &mut H) where H: hash::Hasher, @@ -530,32 +534,40 @@ impl hash::Hash for KeyframesName { } } -impl Parse for KeyframesName { +impl Parse for TimelineOrKeyframesName { fn parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let location = input.current_source_location(); match *input.next()? { - Token::Ident(ref s) => Ok(KeyframesName::Ident(CustomIdent::from_ident( + Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident( location, s, &["none"], )?)), - Token::QuotedString(ref s) => Ok(KeyframesName::QuotedString(Atom::from(s.as_ref()))), + Token::QuotedString(ref s) => { + Ok(Self::QuotedString(Atom::from(s.as_ref()))) + }, ref t => Err(location.new_unexpected_token_error(t.clone())), } } } -impl ToCss for KeyframesName { +impl ToCss for TimelineOrKeyframesName { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { match *self { - KeyframesName::Ident(ref ident) => ident.to_css(dest), - KeyframesName::QuotedString(ref atom) => atom.to_string().to_css(dest), + Self::Ident(ref ident) => ident.to_css(dest), + Self::QuotedString(ref atom) => atom.to_string().to_css(dest), } } } + +/// The typedef of . +pub type TimelineName = TimelineOrKeyframesName; + +/// The typedef of . +pub type KeyframesName = TimelineOrKeyframesName; From bb703e303d5066a62b0b9c243509d77eeac10cc8 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 07:54:25 +0200 Subject: [PATCH 36/94] style: Part 2: Hook @scroll-timeline rule into style system We add scroll-timeline rule into the stylesheet rule type, and add a new perference to protect it: layout.css.scroll-linked-animations.enabled. We will use this perference for animation-timeline property as well. Differential Revision: https://phabricator.services.mozilla.com/D125765 --- components/style/invalidation/stylesheets.rs | 5 +++ components/style/stylesheets/mod.rs | 10 +++++ components/style/stylesheets/rule_parser.rs | 44 ++++++++++++++----- .../style/stylesheets/rules_iterator.rs | 1 + components/style/stylesheets/stylesheet.rs | 3 +- components/style/stylist.rs | 5 +++ 6 files changed, 57 insertions(+), 11 deletions(-) diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index f4dc0c5a443..fc39b8cca76 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -543,6 +543,7 @@ impl StylesheetInvalidationSet { FontFeatureValues(..) | FontFace(..) | Keyframes(..) | + ScrollTimeline(..) | Style(..) => { if is_generic_change { // TODO(emilio): We need to do this for selector / keyframe @@ -618,6 +619,10 @@ impl StylesheetInvalidationSet { // existing elements. } }, + ScrollTimeline(..) => { + // TODO: Bug 1676784: check if animation-timeline name is referenced. + // Now we do nothing. + }, CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => { debug!( " > Found unsupported rule, marking the whole subtree \ diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index d17bc3ffd05..762bca6033f 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -63,6 +63,7 @@ pub use self::rules_iterator::{AllRules, EffectiveRules}; pub use self::rules_iterator::{ EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator, }; +pub use self::scroll_timeline_rule::ScrollTimelineRule; pub use self::style_rule::StyleRule; pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; @@ -261,6 +262,7 @@ pub enum CssRule { Page(Arc>), Document(Arc>), Layer(Arc>), + ScrollTimeline(Arc>), } impl CssRule { @@ -304,6 +306,7 @@ impl CssRule { // TODO(emilio): Add memory reporting for @layer rules. CssRule::Layer(_) => 0, + CssRule::ScrollTimeline(_) => 0, } } } @@ -339,6 +342,7 @@ pub enum CssRuleType { // After viewport, all rules should return 0 from the API, but we still need // a constant somewhere. Layer = 16, + ScrollTimeline = 17, } #[allow(missing_docs)] @@ -366,6 +370,7 @@ impl CssRule { CssRule::Page(_) => CssRuleType::Page, CssRule::Document(_) => CssRuleType::Document, CssRule::Layer(_) => CssRuleType::Layer, + CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, } } @@ -505,6 +510,10 @@ impl DeepCloneWithLock for CssRule { 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()))) + } } } } @@ -526,6 +535,7 @@ impl ToCssWithGuard for CssRule { CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Layer(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest), } } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index dd85144a45f..fe4988044d3 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -15,21 +15,24 @@ use crate::shared_lock::{Locked, SharedRwLock}; use crate::str::starts_with_ignore_ascii_case; use crate::stylesheets::document_rule::DocumentCondition; use crate::stylesheets::font_feature_values_rule::parse_family_name_list; +use crate::stylesheets::import_rule::ImportLayer; use crate::stylesheets::keyframes_rule::parse_keyframe_list; +use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind}; +use crate::stylesheets::scroll_timeline_rule::ScrollTimelineDescriptors; use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; -use crate::stylesheets::import_rule::ImportLayer; -use crate::stylesheets::layer_rule::{LayerName, LayerRuleKind}; -use crate::stylesheets::viewport_rule; -use crate::stylesheets::AllowImportRules; -use crate::stylesheets::{CorsMode, DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule}; -use crate::stylesheets::{CssRule, CssRuleType, CssRules, RulesMutateError, StylesheetLoader}; -use crate::stylesheets::{LayerRule, NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule}; +use crate::stylesheets::{ + viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, + FontFeatureValuesRule, KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, + RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, +}; use crate::values::computed::font::FamilyName; -use crate::values::{CssUrl, CustomIdent, KeyframesName}; +use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName}; use crate::{Namespace, Prefix}; -use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser}; -use cssparser::{BasicParseError, BasicParseErrorKind, CowRcStr, ParserState, SourcePosition}; +use cssparser::{ + AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, Parser, ParserState, + QualifiedRuleParser, RuleListParser, SourcePosition, +}; use selectors::SelectorList; use servo_arc::Arc; use style_traits::{ParseError, StyleParseErrorKind}; @@ -175,6 +178,8 @@ pub enum AtRulePrelude { Namespace(Option, Namespace), /// A @layer rule prelude. Layer(Vec), + /// A @scroll-timeline rule prelude. + ScrollTimeline(TimelineName), } impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { @@ -472,6 +477,10 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let cond = DocumentCondition::parse(self.context, input)?; AtRulePrelude::Document(cond) }, + "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()))) }) } @@ -619,6 +628,21 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { // 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(), + }, + )))) + }, } } } diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index d98d70981df..32851dd2cca 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -68,6 +68,7 @@ where CssRule::CounterStyle(_) | CssRule::Viewport(_) | CssRule::Keyframes(_) | + CssRule::ScrollTimeline(_) | CssRule::Page(_) | CssRule::FontFeatureValues(_) => None, CssRule::Import(ref import_rule) => { diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 91a407f5c32..7cdea20e8bf 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -378,7 +378,8 @@ impl SanitizationKind { CssRule::Page(..) | CssRule::FontFeatureValues(..) | CssRule::Viewport(..) | - CssRule::CounterStyle(..) => !is_standard, + CssRule::CounterStyle(..) | + CssRule::ScrollTimeline(..) => !is_standard, } } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index d3d1a24a9d9..6dc80e03ff5 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2285,6 +2285,10 @@ impl CascadeData { } }, #[cfg(feature = "gecko")] + CssRule::ScrollTimeline(..) => { + // TODO: Bug 1676784: set the timeline into animation. + } + #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { self.extra_data.add_font_face(rule); }, @@ -2553,6 +2557,7 @@ impl CascadeData { CssRule::CounterStyle(..) | CssRule::Supports(..) | CssRule::Keyframes(..) | + CssRule::ScrollTimeline(..) | CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | From 2a11460915c2eaf001cfb24def0654976e63dcf5 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 08:10:05 +0200 Subject: [PATCH 37/94] Further changes required by Servo --- components/script/dom/cssrule.rs | 1 + components/style/stylesheets/rule_parser.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 5945123ac51..853a9c9b643 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -106,6 +106,7 @@ impl CSSRule { StyleCssRule::Page(_) => unreachable!(), StyleCssRule::Document(_) => unimplemented!(), // TODO StyleCssRule::Layer(_) => unimplemented!(), // TODO + StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index fe4988044d3..40ae7fb6188 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -477,6 +477,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let cond = DocumentCondition::parse(self.context, input)?; AtRulePrelude::Document(cond) }, + #[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) From b297c10fbf03382b067073532513393a4a0cd9f8 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 07:58:22 +0200 Subject: [PATCH 38/94] style: Part 3: Add CSSScrollTimelineRule for CSSOM Implement CSSScrollTimelineRule CSSOM API. https://drafts.csswg.org/scroll-animations-1/#the-css-scroll-timeline-rule-interface We rely on the CSSOM API for testing. However, the wpt doesn't match the current spec and it has some errors. We update the wpt and enable the preference for testing in the next patch. Differential Revision: https://phabricator.services.mozilla.com/D125766 --- components/style/gecko/arc_types.rs | 21 ++++++++++--------- .../style/stylesheets/scroll_timeline_rule.rs | 14 ++++++++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 788f75f2b2f..05fcdb5b12d 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -10,13 +10,11 @@ use crate::gecko::url::CssUrlData; use crate::gecko_bindings::structs::{ - RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, - RawServoDeclarationBlock, RawServoFontFaceRule, - RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe, - RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, - RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, - RawServoPageRule, RawServoStyleRule, RawServoStyleSheetContents, - RawServoSupportsRule, ServoCssRules + RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, RawServoDeclarationBlock, + RawServoFontFaceRule, RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe, + RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, RawServoMediaRule, + RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule, + RawServoStyleRule, RawServoStyleSheetContents, RawServoSupportsRule, ServoCssRules, }; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; @@ -25,9 +23,9 @@ use crate::properties::{ComputedValues, PropertyDeclarationBlock}; use crate::shared_lock::Locked; use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ - CounterStyleRule, CssRules, FontFaceRule, FontFeatureValuesRule, - DocumentRule, ImportRule, KeyframesRule, LayerRule, MediaRule, - NamespaceRule, PageRule, StyleRule, StylesheetContents, SupportsRule + CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule, + KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, ScrollTimelineRule, StyleRule, + StylesheetContents, SupportsRule, }; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -90,6 +88,9 @@ impl_arc_ffi!(Locked => RawServoNamespaceRule impl_arc_ffi!(Locked => RawServoPageRule [Servo_PageRule_AddRef, Servo_PageRule_Release]); +impl_arc_ffi!(Locked => RawServoScrollTimelineRule + [Servo_ScrollTimelineRule_AddRef, Servo_ScrollTimelineRule_Release]); + impl_arc_ffi!(Locked => RawServoSupportsRule [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]); diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index f11e469f958..bbc5d9caf8f 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -169,6 +169,12 @@ pub enum Source { None, } +impl Default for Source { + fn default() -> Self { + Source::Auto + } +} + /// The scroll-timeline orientation. /// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-orientation /// @@ -189,11 +195,17 @@ pub enum Orientation { Vertical, } +impl Default for Orientation { + fn default() -> Self { + Orientation::Auto + } +} + /// Scroll-timeline offsets. We treat None as an empty vector. /// value: none | # /// /// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-scroll-offsets -#[derive(Clone, Debug, ToCss, ToShmem)] +#[derive(Clone, Default, Debug, ToCss, ToShmem)] #[css(comma)] pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimelineOffset]>); From 9e4535cb00b42a4dc8a1ed451cf6bc1e60ad4146 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 07:59:56 +0200 Subject: [PATCH 39/94] style: Part 5: Drop the unused effective_xxx_rules It seems only effective_style_rules() and effective_viewport_rules() are used. Let's drop the unused ones. Differential Revision: https://phabricator.services.mozilla.com/D125905 --- components/style/stylesheets/stylesheet.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 7cdea20e8bf..cff97ea8fc6 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -278,15 +278,7 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { rule_filter! { effective_style_rules(Style => StyleRule), - effective_media_rules(Media => MediaRule), - effective_font_face_rules(FontFace => FontFaceRule), - effective_font_face_feature_values_rules(FontFeatureValues => FontFeatureValuesRule), - effective_counter_style_rules(CounterStyle => CounterStyleRule), effective_viewport_rules(Viewport => ViewportRule), - effective_keyframes_rules(Keyframes => KeyframesRule), - effective_supports_rules(Supports => SupportsRule), - effective_page_rules(Page => PageRule), - effective_document_rules(Document => DocumentRule), } } From 4de990e41ac29ed50df7aa23b18a760c47c3048c Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 14:53:37 +0200 Subject: [PATCH 40/94] Further changes required by Servo --- components/style/stylesheets/stylesheet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index cff97ea8fc6..1b6b59bac78 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -278,6 +278,7 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { rule_filter! { effective_style_rules(Style => StyleRule), + effective_font_face_rules(FontFace => FontFaceRule), effective_viewport_rules(Viewport => ViewportRule), } } From 75acb72256df67e380b8c5b6f10936869ef0c100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 08:01:06 +0200 Subject: [PATCH 41/94] style: Get rid of nsStackFrame There are no legacy-stack uses anymore. Differential Revision: https://phabricator.services.mozilla.com/D126077 --- components/style/values/specified/box.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 76dd263e831..35552513cab 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -110,8 +110,6 @@ pub enum DisplayInside { #[cfg(feature = "gecko")] MozBox, #[cfg(feature = "gecko")] - MozStack, - #[cfg(feature = "gecko")] MozDeck, #[cfg(feature = "gecko")] MozPopup, @@ -226,8 +224,6 @@ impl Display { #[cfg(feature = "gecko")] pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox); #[cfg(feature = "gecko")] - pub const MozStack: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozStack); - #[cfg(feature = "gecko")] pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck); #[cfg(feature = "gecko")] pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup); @@ -608,8 +604,6 @@ impl Parse for Display { #[cfg(feature = "gecko")] "-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox, #[cfg(feature = "gecko")] - "-moz-stack" if moz_display_values_enabled(context) => Display::MozStack, - #[cfg(feature = "gecko")] "-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck, #[cfg(feature = "gecko")] "-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup, From 2a42be3cc83fb45bbbaaa5dbfe60635526ba1675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 14:57:02 +0200 Subject: [PATCH 42/94] style: Print lock address on assert Note that the crash reason is sanitized so we're not exposing anything sensitive. I think my patch just changed the signature of the stack, as it didn't change anything related to guards or what not. But without knowing why is failing or a repro it's hard to know what's going on. Printing the address at list would give us some indication of what might be going wrong (perhaps we're using a static lock when we don't expect one or such?). Differential Revision: https://phabricator.services.mozilla.com/D125948 --- components/style/shared_lock.rs | 35 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/components/style/shared_lock.rs b/components/style/shared_lock.rs index 5bfa7d19737..d524f0c6cdf 100644 --- a/components/style/shared_lock.rs +++ b/components/style/shared_lock.rs @@ -94,6 +94,12 @@ impl SharedRwLock { SharedRwLock { cell: None } } + #[cfg(feature = "gecko")] + #[inline] + fn ptr(&self) -> *const SomethingZeroSizedButTyped { + self.cell.as_ref().map(|cell| cell.as_ptr() as *const _).unwrap_or(ptr::null()) + } + /// Wrap the given data to make its access protected by this lock. pub fn wrap(&self, data: T) -> Locked { Locked { @@ -144,6 +150,14 @@ impl<'a> Drop for SharedRwLockReadGuard<'a> { } } +impl<'a> SharedRwLockReadGuard<'a> { + #[inline] + #[cfg(feature = "gecko")] + fn ptr(&self) -> *const SomethingZeroSizedButTyped { + self.0.as_ref().map(|r| &**r as *const _).unwrap_or(ptr::null()) + } +} + /// Proof that a shared lock was obtained for writing (servo). #[cfg(feature = "servo")] pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock); @@ -190,25 +204,18 @@ impl Locked { } #[cfg(feature = "gecko")] - fn same_lock_as(&self, derefed_guard: Option<&SomethingZeroSizedButTyped>) -> bool { - ptr::eq( - self.shared_lock - .cell - .as_ref() - .map(|cell| cell.as_ptr()) - .unwrap_or(ptr::null_mut()), - derefed_guard - .map(|guard| guard as *const _ as *mut _) - .unwrap_or(ptr::null_mut()), - ) + fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool { + ptr::eq(self.shared_lock.ptr(), ptr) } /// Access the data for reading. pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T { #[cfg(feature = "gecko")] assert!( - self.is_read_only_lock() || self.same_lock_as(guard.0.as_ref().map(|r| &**r)), - "Locked::read_with called with a guard from an unrelated SharedRwLock" + self.is_read_only_lock() || self.same_lock_as(guard.ptr()), + "Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}", + self.shared_lock.ptr(), + guard.ptr(), ); #[cfg(not(feature = "gecko"))] assert!(self.same_lock_as(&guard.0)); @@ -235,7 +242,7 @@ impl Locked { pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T { #[cfg(feature = "gecko")] assert!( - !self.is_read_only_lock() && self.same_lock_as(Some(&guard.0)), + !self.is_read_only_lock() && self.same_lock_as(&*guard.0), "Locked::write_with called with a guard from a read only or unrelated SharedRwLock" ); #[cfg(not(feature = "gecko"))] From d830ec8562f0b00c8738bd6e403e4bf43ef3c0b0 Mon Sep 17 00:00:00 2001 From: Stephen A Pohl Date: Sat, 27 May 2023 14:58:02 +0200 Subject: [PATCH 43/94] style: Place window control buttons on the right side on macOS when using an RTL system locale, regardless of the Firefox locale Differential Revision: https://phabricator.services.mozilla.com/D116921 --- components/style/gecko/media_features.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 664a12f4108..7e087e3fd0a 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -680,7 +680,7 @@ macro_rules! bool_pref_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 61] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -913,6 +913,7 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme), + lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL), lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar), lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), From 6785ffea7bda860d2e498f0455e284e2ebe1882e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 15:02:42 +0200 Subject: [PATCH 44/94] style: Unprefix -moz-fit-content I'm not aware of any reason we shouldn't do this, as it is interoperable with other browsers, and it causes compat issues from sites that forget to use the prefixed version. Note this removes some #[parse(aliases)]. These only affect devtools autocomplete behavior, and we avoid autocompleting -moz-prefixed keywords when possible, so even though it's a slight behavior change, it's worth it. Differential Revision: https://phabricator.services.mozilla.com/D126718 --- components/style/values/computed/length.rs | 2 +- components/style/values/generics/length.rs | 6 ++---- components/style/values/specified/length.rs | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 3c4f7a2dbdc..02b42b48241 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -189,7 +189,7 @@ impl Size { #[cfg(feature = "gecko")] GenericSize::MinContent | GenericSize::MaxContent | - GenericSize::MozFitContent | + GenericSize::FitContent | GenericSize::MozAvailable | GenericSize::FitContentFunction(_) => false } diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs index dfb3504e3cf..8d2596fffae 100644 --- a/components/style/values/generics/length.rs +++ b/components/style/values/generics/length.rs @@ -157,7 +157,7 @@ pub enum GenericSize { MinContent, #[cfg(feature = "gecko")] #[animation(error)] - MozFitContent, + FitContent, #[cfg(feature = "gecko")] #[animation(error)] MozAvailable, @@ -207,15 +207,13 @@ pub enum GenericMaxSize { None, #[cfg(feature = "gecko")] #[animation(error)] - #[parse(aliases = "-moz-max-content")] MaxContent, #[cfg(feature = "gecko")] #[animation(error)] - #[parse(aliases = "-moz-min-content")] MinContent, #[cfg(feature = "gecko")] #[animation(error)] - MozFitContent, + FitContent, #[cfg(feature = "gecko")] #[animation(error)] MozAvailable, diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index e73a9ddfde0..6105155d81d 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -1235,7 +1235,7 @@ macro_rules! parse_size_non_length { #[cfg(feature = "gecko")] "max-content" | "-moz-max-content" => $size::MaxContent, #[cfg(feature = "gecko")] - "-moz-fit-content" => $size::MozFitContent, + "fit-content" | "-moz-fit-content" => $size::FitContent, #[cfg(feature = "gecko")] "-moz-available" => $size::MozAvailable, $auto_or_none => $size::$auto_or_none_ident, From b0a356e3b3a5a621cb497ab00b7908cb5724428a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 15:03:43 +0200 Subject: [PATCH 45/94] style: Order keyframe rules by layer @counter-style/@font-face/@scroll-timeline need similar fixes, but tests for those haven't been synced yet so waiting for that before writing them. Differential Revision: https://phabricator.services.mozilla.com/D126616 --- .../style/stylesheets/keyframes_rule.rs | 5 +++ components/style/stylist.rs | 42 ++++++++++++------- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/components/style/stylesheets/keyframes_rule.rs b/components/style/stylesheets/keyframes_rule.rs index 374f6f1d8ec..5172262014b 100644 --- a/components/style/stylesheets/keyframes_rule.rs +++ b/components/style/stylesheets/keyframes_rule.rs @@ -14,6 +14,7 @@ use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::str::CssStringWriter; +use crate::stylesheets::layer_rule::LayerOrder; use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::values::{serialize_percentage, KeyframesName}; @@ -357,6 +358,8 @@ pub struct KeyframesAnimation { pub properties_changed: LonghandIdSet, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, + /// The order of the cascade layer the keyframe rule was in. + pub layer_order: LayerOrder, } /// Get all the animated properties in a keyframes animation. @@ -409,12 +412,14 @@ impl KeyframesAnimation { pub fn from_keyframes( keyframes: &[Arc>], vendor_prefix: Option, + layer_order: LayerOrder, guard: &SharedRwLockReadGuard, ) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: LonghandIdSet::new(), vendor_prefix, + layer_order, }; if keyframes.is_empty() { diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 6dc80e03ff5..489a9f5c6d4 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2265,23 +2265,35 @@ impl CascadeData { self.rules_source_order += 1; }, CssRule::Keyframes(ref keyframes_rule) => { + use hashglobe::hash_map::Entry; + let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - - // Don't let a prefixed keyframes animation override a non-prefixed one. - let needs_insertion = keyframes_rule.vendor_prefix.is_none() || - self.animations - .get(keyframes_rule.name.as_atom()) - .map_or(true, |rule| rule.vendor_prefix.is_some()); - if needs_insertion { - let animation = KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - guard, - ); - debug!("Found valid keyframe animation: {:?}", animation); - self.animations - .try_insert(keyframes_rule.name.as_atom().clone(), animation)?; + match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? { + Entry::Vacant(e) => { + e.insert(KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + current_layer_order, + guard, + )); + }, + Entry::Occupied(mut e) => { + // Don't let a prefixed keyframes animation override + // a non-prefixed one on the same layer. + let needs_insert = + current_layer_order > e.get().layer_order || + (current_layer_order == e.get().layer_order && + (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_order, + guard, + )); + } + }, } }, #[cfg(feature = "gecko")] From 7d8a87cd01cd0edd396afe5be9a09adad87f2e0f Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 15:30:36 +0200 Subject: [PATCH 46/94] Further changes required by Servo --- components/style/stylist.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 489a9f5c6d4..9ef10073de9 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2265,7 +2265,10 @@ impl CascadeData { self.rules_source_order += 1; }, CssRule::Keyframes(ref keyframes_rule) => { + #[cfg(feature = "gecko")] use hashglobe::hash_map::Entry; + #[cfg(feature = "servo")] + use hashglobe::fake::Entry; let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); From 2a7436481c9ee90c146db78477ffef5060cc2115 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 15:04:42 +0200 Subject: [PATCH 47/94] style: Part 1: Add animation-timeline longhand property in style system This patch adds the animation-timeline longhand property. For shorthand, we will do that in the next patch. This patch includes the aut-generated code in devtools/shared/css/generated/properties-db.js, by `./mach devtools-css-db`. Note: 1. we will use this property in Bug 1676791. For now, only make sure we parse it and serialize it correctly. 2. The syntax of animation-timeline may be updated, based on the spec issue: https://github.com/w3c/csswg-drafts/issues/6674. However, it's not a big problem to update it later, so we still can prototype this property based on the current version of spec. Differential Revision: https://phabricator.services.mozilla.com/D126450 --- components/style/properties/gecko.mako.rs | 25 +++++++- .../style/properties/longhands/box.mako.rs | 14 +++++ .../style/properties/properties.mako.rs | 2 +- components/style/stylist.rs | 8 ++- components/style/values/computed/box.rs | 10 ++-- components/style/values/computed/mod.rs | 2 +- components/style/values/mod.rs | 1 + components/style/values/specified/box.rs | 58 ++++++++++++++++++- components/style/values/specified/mod.rs | 2 +- 9 files changed, 108 insertions(+), 14 deletions(-) diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 86cdeefa982..bc0a78183a8 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -1049,7 +1049,7 @@ fn static_assert() { % if member: ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member}; % else: - ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}; + ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone(); % endif } } @@ -1183,7 +1183,7 @@ fn static_assert() { <% skip_box_longhands= """display animation-name animation-delay animation-duration animation-direction animation-fill-mode animation-play-state - animation-iteration-count animation-timing-function + animation-iteration-count animation-timeline animation-timing-function clear transition-duration transition-delay transition-timing-function transition-property -webkit-line-clamp""" %> @@ -1445,6 +1445,27 @@ fn static_assert() { ${impl_copy_animation_value('iteration_count', 'IterationCount')} ${impl_animation_or_transition_timing_function('animation')} + pub fn set_animation_timeline(&mut self, v: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator + { + let v = v.into_iter(); + debug_assert_ne!(v.len(), 0); + let input_len = v.len(); + self.gecko.mAnimations.ensure_len(input_len); + + self.gecko.mAnimationTimelineCount = input_len as u32; + for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) { + gecko.mTimeline = servo; + } + } + pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline { + self.gecko.mAnimations[index].mTimeline.clone() + } + ${impl_animation_count('timeline', 'Timeline')} + ${impl_copy_animation_value('timeline', 'Timeline')} + #[allow(non_snake_case)] pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) { self.gecko.mLineClamp = match v { diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index c8942fdc6ff..bcb51524e12 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -320,6 +320,20 @@ ${helpers.predefined_type( rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, )} +${helpers.predefined_type( + "animation-timeline", + "AnimationTimeline", + "computed::AnimationTimeline::auto()", + engines="gecko", + initial_specified_value="specified::AnimationTimeline::auto()", + vector=True, + need_index=True, + animation_value_type="none", + gecko_pref="layout.css.scroll-linked-animations.enabled", + spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline", + rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME, +)} + <% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %> ${helpers.predefined_type( diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 67183062e2f..7d19785ea80 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -2921,7 +2921,7 @@ pub mod style_structs { } /// Returns true if animation properties are equal between styles, but without - /// considering keyframe data. + /// considering keyframe data and animation-timeline. #[cfg(feature = "servo")] pub fn animations_equals(&self, other: &Self) -> bool { self.animation_name_iter().eq(other.animation_name_iter()) && diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 9ef10073de9..ddcf77140e7 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2301,8 +2301,12 @@ impl CascadeData { }, #[cfg(feature = "gecko")] CssRule::ScrollTimeline(..) => { - // TODO: Bug 1676784: set the timeline into animation. - } + // TODO: Bug 1676791: set the timeline into animation. + // https://phabricator.services.mozilla.com/D126452 + // + // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue + // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. + }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { self.extra_data.add_font_face(rule); diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index 22f5e306976..ce467343005 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -11,14 +11,12 @@ use crate::values::generics::box_::Perspective as GenericPerspective; use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign; use crate::values::specified::box_ as specified; -pub use crate::values::specified::box_::Clear as SpecifiedClear; -pub use crate::values::specified::box_::{AnimationName, Appearance, BreakBetween, BreakWithin}; -pub use crate::values::specified::box_::{Contain, Display, Float as SpecifiedFloat, Overflow}; -pub use crate::values::specified::box_::{OverflowAnchor, OverflowClipBox, OverscrollBehavior}; pub use crate::values::specified::box_::{ - ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType, + AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, + Clear as SpecifiedClear, Contain, Display, Float as SpecifiedFloat, Overflow, OverflowAnchor, + OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, + ScrollSnapType, TouchAction, TransitionProperty, WillChange, }; -pub use crate::values::specified::box_::{TouchAction, TransitionProperty, WillChange}; /// A computed value for the `vertical-align` property. pub type VerticalAlign = GenericVerticalAlign; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 49fc7b8bd33..955a1b93877 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -44,7 +44,7 @@ pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderImageSlice, BorderImageWidth}; -pub use self::box_::{AnimationIterationCount, AnimationName, Contain}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain}; pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 777b81e1da8..b8e7f1f3b8b 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -472,6 +472,7 @@ impl ToCss for CustomIdent { #[derive( Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] +#[repr(C, u8)] pub enum TimelineOrKeyframesName { /// Ident(CustomIdent), diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 35552513cab..c81a3913b54 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -13,7 +13,7 @@ use crate::values::generics::box_::Perspective as GenericPerspective; use crate::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword}; use crate::values::specified::length::{LengthPercentage, NonNegativeLength}; use crate::values::specified::{AllowQuirks, Number}; -use crate::values::{CustomIdent, KeyframesName}; +use crate::values::{CustomIdent, KeyframesName, TimelineName}; use crate::Atom; use cssparser::Parser; use num_traits::FromPrimitive; @@ -762,6 +762,62 @@ impl Parse for AnimationName { } } +/// A value for the . +/// +/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline +/// cbindgen:private-default-tagged-enum-constructor=false +#[derive( + Clone, + Debug, + Eq, + Hash, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum AnimationTimeline { + /// Use default timeline. The animation’s timeline is a DocumentTimeline. + Auto, + /// The animation is not associated with a timeline. + None, + /// The scroll-timeline name + Timeline(TimelineName), +} + +impl AnimationTimeline { + /// Returns the `auto` value. + pub fn auto() -> Self { + Self::Auto + } +} + +impl Parse for AnimationTimeline { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + // We are using the same parser for TimelineName and KeyframesName, but animation-timeline + // accepts "auto", so need to manually parse this. (We can not derive Parse because + // TimelineName excludes only "none" keyword.) + // FIXME: Bug 1733260: we may drop None based on the spec issue: + // Note: https://github.com/w3c/csswg-drafts/issues/6674. + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(Self::Auto); + } + + if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { + return Ok(Self::None); + } + + TimelineName::parse(context, input).map(AnimationTimeline::Timeline) + } +} + /// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis #[allow(missing_docs)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index d5ea7f8d9cc..64bc063960e 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -36,7 +36,7 @@ pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; -pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display}; pub use self::box_::{Appearance, BreakBetween, BreakWithin}; pub use self::box_::{Clear, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; From c21a0dcc9654af45c37ecb836e897dce853ddaa3 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 15:05:39 +0200 Subject: [PATCH 48/94] style: Part 2: Add animation-timeline into animation shorthand We use the same rule as blink: serialize the animation-timeline if it is not the default value. Differential Revision: https://phabricator.services.mozilla.com/D126451 --- .../style/properties/shorthands/box.mako.rs | 35 ++++++++++++++++--- components/style/values/specified/box.rs | 5 +++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index 4beef9a0d95..b056b58c69c 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -189,11 +189,11 @@ macro_rules! try_parse_one { sub_properties="animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction - animation-fill-mode animation-play-state" + animation-fill-mode animation-play-state animation-timeline" rule_types_allowed="Style" spec="https://drafts.csswg.org/css-animations/#propdef-animation"> <% - props = "name duration timing_function delay iteration_count \ + props = "name timeline duration timing_function delay iteration_count \ direction fill_mode play_state".split() %> % for prop in props: @@ -234,6 +234,9 @@ macro_rules! try_parse_one { try_parse_one!(context, input, fill_mode, animation_fill_mode); try_parse_one!(context, input, play_state, animation_play_state); try_parse_one!(context, input, name, animation_name); + if static_prefs::pref!("layout.css.scroll-linked-animations.enabled") { + try_parse_one!(context, input, timeline, animation_timeline); + } parsed -= 1; break @@ -280,22 +283,46 @@ macro_rules! try_parse_one { // If any value list length is differs then we don't do a shorthand serialization // either. - % for name in props[1:]: + % for name in props[2:]: if len != self.animation_${name}.0.len() { return Ok(()) } % endfor + // If the preference of animation-timeline is disabled, `self.animation_timeline` is + // None. + if self.animation_timeline.map_or(false, |v| len != v.0.len()) { + return Ok(()); + } + for i in 0..len { if i != 0 { dest.write_str(", ")?; } - % for name in props[1:]: + % for name in props[2:]: self.animation_${name}.0[i].to_css(dest)?; dest.write_str(" ")?; % endfor + self.animation_name.0[i].to_css(dest)?; + + // Based on the spec, the default values of other properties must be output in at + // least the cases necessary to distinguish an animation-name. The serialization + // order of animation-timeline is always later than animation-name, so it's fine + // to not serialize it if it is the default value. It's still possible to + // distinguish them (because we always serialize animation-name). + // https://drafts.csswg.org/css-animations-1/#animation + // https://drafts.csswg.org/css-animations-2/#typedef-single-animation + // + // Note: it's also fine to always serialize this. However, it seems Blink + // doesn't serialize default animation-timeline now, so we follow the same rule. + if let Some(ref timeline) = self.animation_timeline { + if !timeline.0[i].is_auto() { + dest.write_char(' ')?; + timeline.0[i].to_css(dest)?; + } + } } Ok(()) } diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index c81a3913b54..7d5543cf7fc 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -794,6 +794,11 @@ impl AnimationTimeline { 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 { From 3d54b27a503847f31f2ba61fd61bbadec0217a57 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 15:54:14 +0200 Subject: [PATCH 49/94] Further changes required by Servo --- components/style/properties/longhands/box.mako.rs | 4 +++- components/style/properties/shorthands/box.mako.rs | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index bcb51524e12..62c3da725f5 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -324,7 +324,9 @@ ${helpers.predefined_type( "animation-timeline", "AnimationTimeline", "computed::AnimationTimeline::auto()", - engines="gecko", + 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, diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index b056b58c69c..5cd52902ec9 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -210,6 +210,13 @@ macro_rules! try_parse_one { % endfor } + fn scroll_linked_animations_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.scroll-linked-animations.enabled"); + #[cfg(feature = "servo")] + return false; + } + fn parse_one_animation<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, @@ -234,7 +241,7 @@ macro_rules! try_parse_one { try_parse_one!(context, input, fill_mode, animation_fill_mode); try_parse_one!(context, input, play_state, animation_play_state); try_parse_one!(context, input, name, animation_name); - if static_prefs::pref!("layout.css.scroll-linked-animations.enabled") { + if scroll_linked_animations_enabled() { try_parse_one!(context, input, timeline, animation_timeline); } From 3c0a1f2cb2821e4921e91437c86fa121884025a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 15:08:02 +0200 Subject: [PATCH 50/94] style: Remove NS_AUTHOR_SPECIFIED_PADDING There's only one meaningful usage of it, which is to disable native appearance of the (the windows native theme is no longer exposed to content). is inconsistent with every other native widget, which only disables native appearance if the author specifies backgrounds or borders. So make it match literally all other widgets and simplify a bit the code. We had no tests for this special behavior, let me know if you think it's worth adding one (but I don't feel very strongly about it). Differential Revision: https://phabricator.services.mozilla.com/D127082 --- components/style/properties/cascade.rs | 9 +-------- components/style/properties/computed_value_flags.rs | 6 ------ components/style/properties/properties.mako.rs | 12 ------------ 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index b64ff4c6349..e9b63fe8c72 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -794,12 +794,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { { builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND); } - if self - .author_specified - .contains_any(LonghandIdSet::padding_properties()) - { - builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING); - } if self .author_specified @@ -863,8 +857,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // // Note that all the properties involved are non-inherited, so we don't // need to do anything else other than just copying the bits over. - let reset_props_bits = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | - ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING; + let reset_props_bits = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND; builder.add_flags(cached_style.flags & reset_props_bits); true diff --git a/components/style/properties/computed_value_flags.rs b/components/style/properties/computed_value_flags.rs index 0942bbd0d13..25adc3327ca 100644 --- a/components/style/properties/computed_value_flags.rs +++ b/components/style/properties/computed_value_flags.rs @@ -84,12 +84,6 @@ bitflags! { /// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845 const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14; - /// Whether there are author-specified rules for padding-* properties. - /// - /// FIXME(emilio): Try to merge this with BORDER_BACKGROUND, see - /// https://github.com/w3c/csswg-drafts/issues/4777 - const HAS_AUTHOR_SPECIFIED_PADDING = 1 << 15; - /// Whether there are author-specified rules for `font-family`. const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16; diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 7d19785ea80..10b13618a94 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -945,18 +945,6 @@ impl LonghandIdSet { &HAS_NO_EFFECT_ON_SCROLLBARS } - /// Returns the set of padding properties for the purpose of disabling - /// native appearance. - #[inline] - pub fn padding_properties() -> &'static Self { - <% assert "padding" in logical_groups %> - ${static_longhand_id_set( - "PADDING_PROPERTIES", - lambda p: p.logical_group == "padding" - )} - &PADDING_PROPERTIES - } - /// Returns the set of border properties for the purpose of disabling native /// appearance. #[inline] From f17c42110c584857b1f310cab8295087d29bba61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 15:55:40 +0200 Subject: [PATCH 51/94] style: Clean up button active system colors This patch does three things: * Unifies mac and gtk's buttonactivetext system colors (unships mac's from content, but it was never meant to be exposed). * Simplifies the forms.css rules, since 's color property value doesn't affect its rendering in any meaningful way. * Adds a buttonactiveface color, which we'll use to provide dark backgrounds for buttons in Windows dark mode (and is good practice, since generally every text system color should have a corresponding background). So as-is it shouldn't change content-exposed behavior (except we stop exposing the -moz-mac-buttonactivetext to content), but it's a worthy cleanup. Depends on D127246 Differential Revision: https://phabricator.services.mozilla.com/D127259 --- components/style/values/specified/color.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 332a5617805..6de2464d470 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -376,10 +376,12 @@ pub enum SystemColor { /// Used for button text when pressed. #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozGtkButtonactivetext, + MozButtonactivetext, + + /// Used for button background when pressed. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozButtonactiveface, - /// Used for button text when pressed. - MozMacButtonactivetext, /// Background color of chrome toolbars in active windows. MozMacChromeActive, /// Background color of chrome toolbars in inactive windows. From 3bf9bf696a9793966775b58f8d0f62ae881f10ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 15:56:55 +0200 Subject: [PATCH 52/94] style: Ensure to copy over the viewport/font units invalidation bits when reusing reset properties Differential Revision: https://phabricator.services.mozilla.com/D127262 --- components/style/properties/cascade.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index e9b63fe8c72..0834da93e89 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -853,12 +853,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { // We're using the same reset style as another element, and we'll skip // applying the relevant properties. So we need to do the relevant - // bookkeeping here to keep these two bits correct. + // bookkeeping here to keep these bits correct. // - // Note that all the properties involved are non-inherited, so we don't - // need to do anything else other than just copying the bits over. - let reset_props_bits = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND; - builder.add_flags(cached_style.flags & reset_props_bits); + // Note that the border/background properties are non-inherited, so we + // don't need to do anything else other than just copying the bits over. + // + // When using this optimization, we also need to copy whether the old + // style specified viewport units / used font-relative lengths, this one + // would as well. It matches the same rules, so it is the right thing + // to do anyways, even if it's only used on inherited properties. + let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND | + ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS | + ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS | + ComputedValueFlags::USES_VIEWPORT_UNITS; + builder.add_flags(cached_style.flags & bits_to_copy); true } From b6fa162955c7087a74bab86339aec36db4da6554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 15:57:49 +0200 Subject: [PATCH 53/94] style: Part 1 - Enable color-scheme CSS property in chrome sheets Differential Revision: https://phabricator.services.mozilla.com/D127300 --- components/style/properties/longhands/inherited_ui.mako.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index badcc3f2012..4fb702a97b4 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -104,6 +104,7 @@ ${helpers.predefined_type( gecko_pref="layout.css.color-scheme.enabled", animation_value_type="discrete", has_effect_on_gecko_scrollbars=False, + enabled_in="chrome", )} ${helpers.predefined_type( From 5766329ffe2eb93ccf8c178c3fa381adcedc057c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:09:29 +0200 Subject: [PATCH 54/94] style: Add basic support for dark form controls to nsNativeBasicTheme For that: * Tweak the standin system colors to match the non-native theme. * Use those system colors for button and field backgrounds. * Rename the "should use system colors" bit to "is high contrast", which is what it really is (specially now that we use system colors also in non-high-contrast). Border colors and other colors like the and such might need some extra tweaking perhaps, but this is a decent start and looks good in https://crisal.io/tmp/form-controls.html afaict (dark mode toggle needs the color-scheme pref enabled of course). Differential Revision: https://phabricator.services.mozilla.com/D127533 --- components/style/values/specified/color.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 6de2464d470..c5288cb1f6b 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -316,8 +316,12 @@ pub enum SystemColor { Captiontext, #[parse(aliases = "-moz-field")] Field, + /// Used for disabled field backgrounds. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozDisabledfield, #[parse(aliases = "-moz-fieldtext")] Fieldtext, + Graytext, Highlight, Highlighttext, @@ -382,6 +386,10 @@ pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozButtonactiveface, + /// Used for button background when disabled. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozButtondisabledface, + /// Background color of chrome toolbars in active windows. MozMacChromeActive, /// Background color of chrome toolbars in inactive windows. From b3deee4cc8d56938522b7711a1fc1bcf84b73cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:13:46 +0200 Subject: [PATCH 55/94] style: Remove support for -moz-system-color() as chrome code no longer uses it (in favor of color-scheme) Differential Revision: https://phabricator.services.mozilla.com/D127802 --- components/style/values/specified/color.rs | 62 +++------------------- 1 file changed, 7 insertions(+), 55 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index c5288cb1f6b..95563d4f9ee 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -194,20 +194,6 @@ impl ToCss for ColorMix { } } -/// The color scheme for a specific system color. -#[cfg(feature = "gecko")] -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] -#[repr(u8)] -pub enum SystemColorScheme { - /// The default color-scheme for the document. - #[css(skip)] - Default, - /// A light color scheme. - Light, - /// A dark color scheme. - Dark, -} - /// Specified color value #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] pub enum Color { @@ -222,10 +208,9 @@ pub enum Color { }, /// A complex color value from computed value Complex(ComputedColor), - /// Either a system color, or a `-moz-system-color(, light|dark)` - /// function which allows chrome code to choose between color schemes. + /// A system color. #[cfg(feature = "gecko")] - System(SystemColor, SystemColorScheme), + System(SystemColor), /// A color mix. ColorMix(Box), /// Quirksmode-only rule for inheriting color from the body @@ -480,7 +465,7 @@ pub enum SystemColor { #[cfg(feature = "gecko")] impl SystemColor { #[inline] - fn compute(&self, cx: &Context, scheme: SystemColorScheme) -> ComputedColor { + fn compute(&self, cx: &Context) -> ComputedColor { use crate::gecko_bindings::bindings; let colors = &cx.device().pref_sheet_prefs().mColors; @@ -497,7 +482,7 @@ impl SystemColor { _ => { let color = unsafe { - bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme) + bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), &style_color_scheme) }; if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { return ComputedColor::currentcolor(); @@ -578,21 +563,6 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen } } -#[cfg(feature = "gecko")] -fn parse_moz_system_color<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, -) -> Result<(SystemColor, SystemColorScheme), ParseError<'i>> { - debug_assert!(context.chrome_rules_enabled()); - input.expect_function_matching("-moz-system-color")?; - input.parse_nested_block(|input| { - let color = SystemColor::parse(context, input)?; - input.expect_comma()?; - let scheme = SystemColorScheme::parse(input)?; - Ok((color, scheme)) - }) -} - impl Parse for Color { fn parse<'i, 't>( context: &ParserContext, @@ -618,15 +588,7 @@ impl Parse for Color { #[cfg(feature = "gecko")] { if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) { - return Ok(Color::System(system, SystemColorScheme::Default)); - } - - if context.chrome_rules_enabled() { - if let Ok((color, scheme)) = - input.try_parse(|i| parse_moz_system_color(context, i)) - { - return Ok(Color::System(color, scheme)); - } + return Ok(Color::System(system)); } } @@ -665,17 +627,7 @@ impl ToCss for Color { Color::Complex(_) => Ok(()), Color::ColorMix(ref mix) => mix.to_css(dest), #[cfg(feature = "gecko")] - Color::System(system, scheme) => { - if scheme == SystemColorScheme::Default { - system.to_css(dest) - } else { - dest.write_str("-moz-system-color(")?; - system.to_css(dest)?; - dest.write_str(", ")?; - scheme.to_css(dest)?; - dest.write_char(')') - } - }, + Color::System(system) => system.to_css(dest), #[cfg(feature = "gecko")] Color::InheritFromBodyQuirk => Ok(()), } @@ -843,7 +795,7 @@ impl Color { )) }, #[cfg(feature = "gecko")] - Color::System(system, scheme) => system.compute(context?, scheme), + Color::System(system) => system.compute(context?), #[cfg(feature = "gecko")] Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()), }) From 6899931303e14fd02a1fe67ae1577fac0905e6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:17:48 +0200 Subject: [PATCH 56/94] style: Make inert not modify the computed style Differential Revision: https://phabricator.services.mozilla.com/D127422 --- components/style/style_adjuster.rs | 58 ------------------------------ 1 file changed, 58 deletions(-) diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 5e66e91e894..31028f98571 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -152,63 +152,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { } } - /// https://html.spec.whatwg.org/multipage/#inert-subtrees - /// - /// If -moz-inert is applied then add: - /// -moz-user-focus: none; - /// -moz-user-input: none; - /// -moz-user-modify: read-only; - /// user-select: none; - /// pointer-events: none; - /// cursor: default; - /// - /// NOTE: dialog:-moz-topmost-modal-dialog is used to override above - /// rules to remove the inertness for the topmost modal dialog. - /// - /// NOTE: If this or the pointer-events tweak is removed, then - /// minimal-xul.css and the scrollbar style caching need to be tweaked. - #[cfg(feature = "gecko")] - fn adjust_for_inert(&mut self) { - use crate::values::specified::ui::CursorKind; - use crate::values::specified::ui::UserSelect; - use properties::longhands::_moz_inert::computed_value::T as Inert; - use properties::longhands::_moz_user_focus::computed_value::T as UserFocus; - use properties::longhands::_moz_user_input::computed_value::T as UserInput; - use properties::longhands::_moz_user_modify::computed_value::T as UserModify; - use properties::longhands::cursor::computed_value::T as Cursor; - use properties::longhands::pointer_events::computed_value::T as PointerEvents; - - let needs_update = { - let ui = self.style.get_inherited_ui(); - if ui.clone__moz_inert() == Inert::None { - return; - } - - ui.clone__moz_user_focus() != UserFocus::None || - ui.clone__moz_user_input() != UserInput::None || - ui.clone__moz_user_modify() != UserModify::ReadOnly || - ui.clone_pointer_events() != PointerEvents::None || - ui.clone_cursor().keyword != CursorKind::Default || - ui.clone_cursor().images != Default::default() - }; - - if needs_update { - let ui = self.style.mutate_inherited_ui(); - ui.set__moz_user_focus(UserFocus::None); - ui.set__moz_user_input(UserInput::None); - ui.set__moz_user_modify(UserModify::ReadOnly); - ui.set_pointer_events(PointerEvents::None); - ui.set_cursor(Cursor { - images: Default::default(), - keyword: CursorKind::Default, - }); - } - - if self.style.get_ui().clone_user_select() != UserSelect::None { - self.style.mutate_ui().set_user_select(UserSelect::None); - } - } - /// Whether we should skip any item-based display property blockification on /// this element. fn skip_item_display_fixup(&self, element: Option) -> bool @@ -909,7 +852,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { #[cfg(feature = "gecko")] { self.adjust_for_appearance(element); - self.adjust_for_inert(); self.adjust_for_marker_pseudo(); } self.set_bits(); From f00818b8ec4a722847be319f2977c3c22af22cba Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Sat, 27 May 2023 16:19:05 +0200 Subject: [PATCH 57/94] style: Tweak the serialization of rotate property The serialization of rotate should be updated to match the current spec. 1. If a rotation about the z axis (that is, in 2D) is specified, the property must serialize as just an . 2. If any other rotation is specified, the property must serialize with an axis specified. If the axis is parallel with the x or y axes, it must serialize as the appropriate keyword. Also, we need to handle the zero length vector separately because it is parallel to every other vector but different from x axis, y axis, or z axis. Differential Revision: https://phabricator.services.mozilla.com/D127747 --- components/style/values/generics/transform.rs | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 92987f89fe4..604872ba5dc 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -687,7 +687,7 @@ pub trait IsParallelTo { impl ToCss for Rotate where - Number: Copy + ToCss, + Number: Copy + ToCss + Zero, Angle: ToCss, (Number, Number, Number): IsParallelTo, { @@ -700,25 +700,41 @@ where Rotate::None => dest.write_str("none"), Rotate::Rotate(ref angle) => angle.to_css(dest), Rotate::Rotate3D(x, y, z, ref angle) => { - // If a 3d rotation is specified, the property must serialize with an axis - // specified. If the axis is parallel with the x, y, or z axises, it must - // serialize as the appropriate keyword. + // If the axis is parallel with the x or y axes, it must serialize as the + // appropriate keyword. If a rotation about the z axis (that is, in 2D) is + // specified, the property must serialize as just an + // // https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization let v = (x, y, z); - if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { - dest.write_char('x')?; + let axis = if x.is_zero() && y.is_zero() && z.is_zero() { + // The zero length vector is parallel to every other vector, so + // is_parallel_to() returns true for it. However, it is definitely different + // from x axis, y axis, or z axis, and it's meaningless to perform a rotation + // using that direction vector. So we *have* to serialize it using that same + // vector - we can't simplify to some theoretically parallel axis-aligned + // vector. + None + } else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) { + Some("x ") } else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) { - dest.write_char('y')?; + Some("y ") } else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) { - dest.write_char('z')?; + // When we're parallel to the z-axis, we can just serialize the angle. + return angle.to_css(dest); } else { - x.to_css(dest)?; - dest.write_char(' ')?; - y.to_css(dest)?; - dest.write_char(' ')?; - z.to_css(dest)?; + None + }; + match axis { + Some(a) => dest.write_str(a)?, + None => { + x.to_css(dest)?; + dest.write_char(' ')?; + y.to_css(dest)?; + dest.write_char(' ')?; + z.to_css(dest)?; + dest.write_char(' ')?; + } } - dest.write_char(' ')?; angle.to_css(dest) }, } From 750c9ee814c339dd267adea3eb39492ba74bd145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:20:01 +0200 Subject: [PATCH 58/94] style: Clean up GTK titlebar colors Apparently ActiveCaption/CaptionText/InactiveCaption/InactiveCaptionText are supposed to be used for titlebars, so implement them properly and remove the GTK-specific colors. Differential Revision: https://phabricator.services.mozilla.com/D128028 --- components/style/values/specified/color.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 95563d4f9ee..44d290bcb0e 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -218,7 +218,14 @@ pub enum Color { InheritFromBodyQuirk, } -/// System colors. +/// System colors. A bunch of these are ad-hoc, others come from Windows: +/// +/// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor +/// +/// Others are HTML/CSS specific. Spec is: +/// +/// https://drafts.csswg.org/css-color/#css-system-colors +/// https://drafts.csswg.org/css-color/#deprecated-system-colors #[allow(missing_docs)] #[cfg(feature = "gecko")] #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] @@ -291,6 +298,7 @@ pub enum SystemColor { #[css(skip)] ThemedScrollbarThumbInactive, Activeborder, + /// Background in the (active) titlebar. Activecaption, Appworkspace, Background, @@ -298,6 +306,7 @@ pub enum SystemColor { Buttonhighlight, Buttonshadow, Buttontext, + /// Text color in the (active) titlebar. Captiontext, #[parse(aliases = "-moz-field")] Field, @@ -311,7 +320,9 @@ pub enum SystemColor { Highlight, Highlighttext, Inactiveborder, + /// Background in the (inactive) titlebar. Inactivecaption, + /// Text color in the (inactive) titlebar. Inactivecaptiontext, Infobackground, Infotext, @@ -450,14 +461,6 @@ pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozColheaderhovertext, - /// Color of text in the (active) titlebar. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozGtkTitlebarText, - - /// Color of text in the (inactive) titlebar. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - MozGtkTitlebarInactiveText, - #[css(skip)] End, // Just for array-indexing purposes. } From 8c6af822b052e40019c67c060b658c3ea1e13dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:21:05 +0200 Subject: [PATCH 59/94] style: Make anonymous scrollbar caching work on mac by default, and unify scrollbars.css across platforms We make it work on macOS by setting pointer-events: none + opacity: 0 rather than visibility: hidden, and tweaking the caching setup to be Android-like. Now that the scrollbars sheet is the same across platforms, move it to where the rest of the UA sheets are. This way we guarantee that the RDM vs. Android difference is less (just the ifdef at the top of the sheet). Depends on D128084 Differential Revision: https://phabricator.services.mozilla.com/D128085 --- components/style/gecko/media_features.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 7e087e3fd0a..93dc085cd69 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -607,6 +607,16 @@ fn eval_moz_windows_non_native_menus( query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus) } +fn eval_moz_overlay_scrollbars( + device: &Device, + query_value: Option, + _: Option, +) -> bool { + let use_overlay = + unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) }; + query_value.map_or(use_overlay, |v| v == use_overlay) +} + fn get_lnf_int(int_id: i32) -> i32 { unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) } } @@ -902,13 +912,18 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 61] = [ Evaluator::BoolInteger(eval_moz_windows_non_native_menus), ParsingRequirements::CHROME_AND_UA_ONLY, ), + feature!( + atom!("-moz-overlay-scrollbars"), + AllowsRanges::No, + Evaluator::BoolInteger(eval_moz_overlay_scrollbars), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), lnf_int_feature!(atom!("-moz-scrollbar-start-backward"), ScrollArrowStyle, get_scrollbar_start_backward), lnf_int_feature!(atom!("-moz-scrollbar-start-forward"), ScrollArrowStyle, get_scrollbar_start_forward), lnf_int_feature!(atom!("-moz-scrollbar-end-backward"), ScrollArrowStyle, get_scrollbar_end_backward), lnf_int_feature!(atom!("-moz-scrollbar-end-forward"), ScrollArrowStyle, get_scrollbar_end_forward), lnf_int_feature!(atom!("-moz-scrollbar-thumb-proportional"), ScrollSliderStyle), - lnf_int_feature!(atom!("-moz-overlay-scrollbars"), UseOverlayScrollbars), lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag), lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), From 685b2cd29a370367307ed024c2ca67179e2b4a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:21:52 +0200 Subject: [PATCH 60/94] style: Remove -moz-toolbar-prefers-color-scheme By making prefers-color-scheme return the ColorSchemeForChrome(), which accounts for the Firefox theme. Differential Revision: https://phabricator.services.mozilla.com/D128611 --- components/style/gecko/media_features.rs | 47 +----------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 93dc085cd69..9f36a3ec846 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -406,45 +406,6 @@ fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { - let toolbar_value = match static_prefs::pref!("browser.theme.toolbar-theme") { - 0 => ToolbarPrefersColorScheme::Dark, - 1 => ToolbarPrefersColorScheme::Light, - _ => ToolbarPrefersColorScheme::System, - }; - - let query_value = match query_value { - Some(v) => v, - None => return true, - }; - - if query_value == toolbar_value { - return true; - } - - if toolbar_value != ToolbarPrefersColorScheme::System { - return false; - } - - // System might match light and dark as well. - match query_value { - ToolbarPrefersColorScheme::Dark => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Dark)), - ToolbarPrefersColorScheme::Light => eval_prefers_color_scheme(d, Some(PrefersColorScheme::Light)), - ToolbarPrefersColorScheme::System => true, - } -} - bitflags! { /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction struct PointerCapabilities: u8 { @@ -690,7 +651,7 @@ macro_rules! bool_pref_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [MediaFeatureDescription; 61] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -900,12 +861,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 61] = [ Evaluator::BoolInteger(eval_moz_non_native_content_theme), ParsingRequirements::CHROME_AND_UA_ONLY, ), - feature!( - atom!("-moz-toolbar-prefers-color-scheme"), - AllowsRanges::No, - keyword_evaluator!(eval_toolbar_prefers_color_scheme, ToolbarPrefersColorScheme), - ParsingRequirements::CHROME_AND_UA_ONLY, - ), feature!( atom!("-moz-windows-non-native-menus"), AllowsRanges::No, From e74b41cafece287a1b14f39aaffa57d37f649a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:22:35 +0200 Subject: [PATCH 61/94] style: Add support for chrome-only environment variables This bit is taken straight from D73454 (I reviewed it but I guess another pair of eyes is ok, it's really straight-forward). Co-authored-by: Nicklas Boman Differential Revision: https://phabricator.services.mozilla.com/D128679 --- components/style/custom_properties.rs | 11 ++++++++++- components/style/gecko/media_queries.rs | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index df3f9660dd4..a6908bb4cfa 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -71,10 +71,19 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), ]; +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 0] = [ +]; + impl CssEnvironment { #[inline] fn get(&self, name: &Atom, device: &Device) -> Option { - let var = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; + if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) { + return Some((var.evaluator)(device)); + } + if !device.is_chrome_document() { + return None; + } + let var = CHROME_ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; Some((var.evaluator)(device)) } } diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 2f5a9673d4c..2966363a8eb 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -439,4 +439,10 @@ impl Device { bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32) } } + + /// Return whether the document is a chrome document. + #[inline] + pub fn is_chrome_document(&self) -> bool { + self.pref_sheet_prefs().mIsChrome + } } From 5c861b90c55a0e659ad89e3888be26b25ff70ad0 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 17:14:09 +0200 Subject: [PATCH 62/94] Further changes required by Servo --- components/style/servo/media_queries.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index cee76053ec0..132e8200164 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -220,6 +220,12 @@ impl Device { _ => false, } } + + /// Return whether the document is a chrome document. + #[inline] + pub fn is_chrome_document(&self) -> bool { + false + } } /// https://drafts.csswg.org/mediaqueries-4/#width From 07e5ea696665076436ce45db6dfffabe8c767666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:23:42 +0200 Subject: [PATCH 63/94] style: Expose titlebar radius as a chrome-only CSS environment variable Mostly plumbing. Differential Revision: https://phabricator.services.mozilla.com/D128680 --- components/style/custom_properties.rs | 7 ++++++- components/style/gecko/media_queries.rs | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index a6908bb4cfa..7e8a1e37f7f 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -71,7 +71,12 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), ]; -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 0] = [ +fn get_titlebar_radius(device: &Device) -> VariableValue { + VariableValue::pixel(device.titlebar_radius()) +} + +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 1] = [ + make_variable!(atom!("-moz-gtk-csd-titlebar-radius"), get_titlebar_radius), ]; impl CssEnvironment { diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 2966363a8eb..a560211d2f3 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -440,6 +440,13 @@ impl Device { } } + /// Returns the gtk titlebar radius in CSS pixels. + pub fn titlebar_radius(&self) -> f32 { + unsafe { + bindings::Gecko_GetLookAndFeelFloat(bindings::LookAndFeel_FloatID::TitlebarRadius as i32) + } + } + /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { From c204f3a96d03724af6a97c092418bbf7447c1860 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 17:30:04 +0200 Subject: [PATCH 64/94] Further changes required by Servo --- components/atoms/static_atoms.txt | 1 + components/style/servo/media_queries.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 8409639055f..5a0cc47ed4f 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,3 +1,4 @@ +-moz-gtk-csd-titlebar-radius DOMContentLoaded abort activate diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index 132e8200164..6ac0cf54505 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -221,6 +221,12 @@ impl Device { } } + /// Returns the gtk titlebar radius in CSS pixels. + /// TODO: implement this method. + pub fn titlebar_radius(&self) -> f32 { + 0.0 + } + /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { From 0be968fb4c798fbfedd2e2c22e07ab9b4085e081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:24:24 +0200 Subject: [PATCH 65/94] style: Remove -moz-gtk-csd-transparent-background We always use alpha visual for WebRender, and appearance: none is unnecessary (root element has no intrinsic appearance). Differential Revision: https://phabricator.services.mozilla.com/D128682 --- components/style/gecko/media_features.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 9f36a3ec846..82b9f6c5532 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -891,7 +891,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled), lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable), lnf_int_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default"), GTKCSDHideTitlebarByDefault), - lnf_int_feature!(atom!("-moz-gtk-csd-transparent-background"), GTKCSDTransparentBackground), lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), From efb5a8cfe574f60419029bf11dce4ac937f3e936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 16:25:28 +0200 Subject: [PATCH 66/94] style: Fix rebase mistake MANUAL PUSH: Bustage fix CLOSED TREE --- components/style/gecko/media_features.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 82b9f6c5532..34bcda32e10 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -651,7 +651,7 @@ macro_rules! bool_pref_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 59] = [ feature!( atom!("width"), AllowsRanges::Yes, From 00d2cec62659bcee8bcc1dfac1836df62a207cf4 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Sat, 27 May 2023 17:01:54 +0200 Subject: [PATCH 67/94] style: Remove unused import_rule::PendingSheet Differential Revision: https://phabricator.services.mozilla.com/D128823 --- components/style/stylesheets/import_rule.rs | 22 +++++---------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index 48473ff61cd..7352dea0b9e 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -18,15 +18,6 @@ use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; use to_shmem::{self, SharedMemoryBuilder, ToShmem}; -/// With asynchronous stylesheet parsing, we can't synchronously create a -/// GeckoStyleSheet. So we use this placeholder instead. -#[cfg(feature = "gecko")] -#[derive(Clone, Debug)] -pub struct PendingSheet { - origin: Origin, - quirks_mode: QuirksMode, -} - /// A sheet that is held from an import rule. #[cfg(feature = "gecko")] #[derive(Debug)] @@ -35,7 +26,7 @@ pub enum ImportSheet { Sheet(crate::gecko::data::GeckoStyleSheet), /// An @import created while parsing off-main-thread, whose Gecko sheet has /// yet to be created and attached. - Pending(PendingSheet), + Pending, } #[cfg(feature = "gecko")] @@ -46,11 +37,8 @@ impl ImportSheet { } /// Creates a pending ImportSheet for a load that has not started yet. - pub fn new_pending(origin: Origin, quirks_mode: QuirksMode) -> Self { - ImportSheet::Pending(PendingSheet { - origin, - quirks_mode, - }) + pub fn new_pending() -> Self { + ImportSheet::Pending } /// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it @@ -64,7 +52,7 @@ impl ImportSheet { } Some(s) }, - ImportSheet::Pending(_) => None, + ImportSheet::Pending => None, } } @@ -99,7 +87,7 @@ impl DeepCloneWithLock for ImportSheet { }; ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) }) }, - ImportSheet::Pending(ref p) => ImportSheet::Pending(p.clone()), + ImportSheet::Pending => ImportSheet::Pending, } } } From c3c3dacb9727f5885f3fdb3abf01f15fee19d8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 17:32:07 +0200 Subject: [PATCH 68/94] style: Don't use nsContentUtils::sNamespaceManager in servo Differential Revision: https://phabricator.services.mozilla.com/D129030 --- components/style/gecko/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 476e710795d..95da8b986f6 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1016,7 +1016,7 @@ impl<'le> TElement for GeckoElement<'le> { #[inline] fn namespace(&self) -> &WeakNamespace { unsafe { - let namespace_manager = structs::nsContentUtils_sNameSpaceManager; + let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr; WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr) } } From 578ecdba793467f3bfe9a610e62e7a4b568db07b Mon Sep 17 00:00:00 2001 From: stransky Date: Sat, 27 May 2023 17:33:09 +0200 Subject: [PATCH 69/94] style: [Linux] Export titlebar radius as int from LookAndFeel Titlebar radius is exported as integer from Gtk so there's no need to use floats here. Differential Revision: https://phabricator.services.mozilla.com/D128993 --- components/style/gecko/media_queries.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index a560211d2f3..b41280d2112 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -443,7 +443,7 @@ impl Device { /// Returns the gtk titlebar radius in CSS pixels. pub fn titlebar_radius(&self) -> f32 { unsafe { - bindings::Gecko_GetLookAndFeelFloat(bindings::LookAndFeel_FloatID::TitlebarRadius as i32) + bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) as f32 } } From 77078dd6601672d86d32a165ed321d594214e05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 17:34:24 +0200 Subject: [PATCH 70/94] style: Make browser.tabs.drawInTitlebar a tri-state To more properly support Linux having a different default at runtime. Expose the resolved value in appinfo for convenience, and use it in the front-end as needed. Differential Revision: https://phabricator.services.mozilla.com/D129004 --- components/style/gecko/media_features.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 34bcda32e10..40a34095d6f 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -651,7 +651,7 @@ macro_rules! bool_pref_feature { /// to support new types in these entries and (2) ensuring that either /// nsPresContext::MediaFeatureValuesChanged is called when the value that /// would be returned by the evaluator function could change. -pub static MEDIA_FEATURES: [MediaFeatureDescription; 59] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -890,7 +890,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 59] = [ lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled), lnf_int_feature!(atom!("-moz-gtk-csd-available"), GTKCSDAvailable), - lnf_int_feature!(atom!("-moz-gtk-csd-hide-titlebar-by-default"), GTKCSDHideTitlebarByDefault), lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), From 5f2a29659faf2a5ff459af5676860337170d1b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 17:35:10 +0200 Subject: [PATCH 71/94] style: Store a LayerOrder in ApplicableDeclarationBlock This shouldn't change behavior jut yet. Differential Revision: https://phabricator.services.mozilla.com/D129380 --- components/style/applicable_declarations.rs | 7 ++++--- components/style/stylist.rs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 168aae643cd..a704741ff0b 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -6,6 +6,7 @@ use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StyleSource}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::shared_lock::Locked; use servo_arc::Arc; use smallvec::SmallVec; @@ -72,7 +73,7 @@ pub struct ApplicableDeclarationBlock { /// The specificity of the selector. pub specificity: u32, /// The layer order of the selector. - pub layer_order: u32, + pub layer_order: LayerOrder, } impl ApplicableDeclarationBlock { @@ -87,13 +88,13 @@ impl ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), bits: ApplicableDeclarationBits::new(0, level), specificity: 0, - layer_order: 0, + layer_order: LayerOrder::first(), } } /// Constructs an applicable declaration block from the given components #[inline] - pub fn new(source: StyleSource, source_order: u32, level: CascadeLevel, specificity: u32, layer_order: u32) -> Self { + pub fn new(source: StyleSource, source_order: u32, level: CascadeLevel, specificity: u32, layer_order: LayerOrder) -> Self { ApplicableDeclarationBlock { source, bits: ApplicableDeclarationBits::new(source_order, level), diff --git a/components/style/stylist.rs b/components/style/stylist.rs index ddcf77140e7..7e2b820498e 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2184,7 +2184,7 @@ impl CascadeData { self.rules_source_order, CascadeLevel::UANormal, selector.specificity(), - current_layer_order.raw(), + current_layer_order, )); continue; } @@ -2767,7 +2767,7 @@ impl Rule { level: CascadeLevel, ) -> ApplicableDeclarationBlock { let source = StyleSource::from_rule(self.style_rule.clone()); - ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), self.layer_order.raw()) + ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), self.layer_order) } /// Creates a new Rule. From 1b2ef21c8c6a0532428977242dce38f3a6036646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 17:36:01 +0200 Subject: [PATCH 72/94] style: Update layer ordering to follow the spec The bitfield approach worked when the layer order was in pre-order, but the spec was changed to make it work like post-order and I don't think there's a way to keep it working like that, so keep the layer order in a separate data structure that we look up when going from Rule to ApplicableDeclarationBlock. This is just a vector index operation so hopefully shouldn't be too bad. This patch intentionally regresses @keyframe handling to some extent, since we need a bit more complicated approach and it seemed worth implementing in a separate patch. Depends on D129380 Differential Revision: https://phabricator.services.mozilla.com/D129381 --- components/style/applicable_declarations.rs | 12 +- components/style/rule_collector.rs | 54 +++--- components/style/selector_map.rs | 13 +- .../style/stylesheets/keyframes_rule.rs | 10 +- components/style/stylesheets/layer_rule.rs | 92 ++-------- components/style/stylist.rs | 173 ++++++++++++------ 6 files changed, 190 insertions(+), 164 deletions(-) diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index a704741ff0b..db0f7fce99f 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -88,13 +88,19 @@ impl ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), bits: ApplicableDeclarationBits::new(0, level), specificity: 0, - layer_order: LayerOrder::first(), + layer_order: LayerOrder::root(), } } - /// Constructs an applicable declaration block from the given components + /// Constructs an applicable declaration block from the given components. #[inline] - pub fn new(source: StyleSource, source_order: u32, level: CascadeLevel, specificity: u32, layer_order: LayerOrder) -> Self { + pub fn new( + source: StyleSource, + source_order: u32, + level: CascadeLevel, + specificity: u32, + layer_order: LayerOrder, + ) -> Self { ApplicableDeclarationBlock { source, bits: ApplicableDeclarationBits::new(source_order, level), diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index bb34396f9aa..65f55ea887a 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -12,7 +12,7 @@ use crate::selector_map::SelectorMap; use crate::selector_parser::PseudoElement; use crate::shared_lock::Locked; use crate::stylesheets::Origin; -use crate::stylist::{AuthorStylesEnabled, Rule, RuleInclusion, Stylist}; +use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; use smallvec::SmallVec; @@ -173,7 +173,7 @@ where }; self.in_tree(None, |collector| { - collector.collect_rules_in_map(map, cascade_level); + collector.collect_rules_in_map(map, cascade_level, cascade_data); }); } @@ -214,7 +214,7 @@ where } #[inline] - fn collect_rules_in_list(&mut self, part_rules: &[Rule], cascade_level: CascadeLevel) { + fn collect_rules_in_list(&mut self, part_rules: &[Rule], cascade_level: CascadeLevel, cascade_data: &CascadeData) { debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); SelectorMap::get_matching_rules( self.element, @@ -223,11 +223,12 @@ where &mut self.context, &mut self.flags_setter, cascade_level, + cascade_data, ); } #[inline] - fn collect_rules_in_map(&mut self, map: &SelectorMap, cascade_level: CascadeLevel) { + fn collect_rules_in_map(&mut self, map: &SelectorMap, cascade_level: CascadeLevel, cascade_data: &CascadeData) { debug_assert!(self.in_sort_scope, "Rules gotta be sorted"); map.get_all_matching_rules( self.element, @@ -236,6 +237,7 @@ where &mut self.context, &mut self.flags_setter, cascade_level, + cascade_data, ); } @@ -277,7 +279,7 @@ where let cascade_level = CascadeLevel::AuthorNormal { shadow_cascade_order, }; - collector.collect_rules_in_map(slotted_rules, cascade_level); + collector.collect_rules_in_map(slotted_rules, cascade_level, data); }); } } @@ -303,7 +305,7 @@ where let cascade_level = CascadeLevel::same_tree_author_normal(); self.in_shadow_tree(containing_shadow.host(), |collector| { if let Some(map) = cascade_data.normal_rules(collector.pseudo_element) { - collector.collect_rules_in_map(map, cascade_level); + collector.collect_rules_in_map(map, cascade_level, cascade_data); } // Collect rules from :host::part() and such @@ -319,7 +321,7 @@ where hash_target.each_part(|part| { if let Some(part_rules) = part_rules.get(&part.0) { - collector.collect_rules_in_list(part_rules, cascade_level); + collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); } }); }); @@ -352,7 +354,7 @@ where let cascade_level = CascadeLevel::AuthorNormal { shadow_cascade_order, }; - collector.collect_rules_in_map(host_rules, cascade_level); + collector.collect_rules_in_map(host_rules, cascade_level, style_data); }); } @@ -386,30 +388,30 @@ where let inner_shadow_host = inner_shadow.host(); let outer_shadow = inner_shadow_host.containing_shadow(); - let part_rules = match outer_shadow { - Some(shadow) => shadow - .style_data() - .and_then(|data| data.part_rules(self.pseudo_element)), - None => self + let cascade_data = match outer_shadow { + Some(shadow) => shadow.style_data(), + None => Some(self .stylist .cascade_data() .borrow_for_origin(Origin::Author) - .part_rules(self.pseudo_element), + ), }; - if let Some(part_rules) = part_rules { - let containing_host = outer_shadow.map(|s| s.host()); - let cascade_level = CascadeLevel::AuthorNormal { - shadow_cascade_order, - }; - self.in_tree(containing_host, |collector| { - for p in &parts { - if let Some(part_rules) = part_rules.get(&p.0) { - collector.collect_rules_in_list(part_rules, cascade_level); + if let Some(cascade_data) = cascade_data { + if let Some(part_rules) = cascade_data.part_rules(self.pseudo_element) { + let containing_host = outer_shadow.map(|s| s.host()); + let cascade_level = CascadeLevel::AuthorNormal { + shadow_cascade_order, + }; + self.in_tree(containing_host, |collector| { + for p in &parts { + if let Some(part_rules) = part_rules.get(&p.0) { + collector.collect_rules_in_list(part_rules, cascade_level, cascade_data); + } } - } - }); - shadow_cascade_order.inc(); + }); + shadow_cascade_order.inc(); + } } inner_shadow = match outer_shadow { diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index d3dc48e36e4..a2b5d6bb6b6 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -12,7 +12,7 @@ use crate::hash::map as hash_map; use crate::hash::{HashMap, HashSet}; use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; -use crate::stylist::Rule; +use crate::stylist::{Rule, CascadeData}; use crate::{Atom, LocalName, Namespace, WeakAtom}; use fallible::FallibleVec; use hashglobe::FailedAllocationError; @@ -189,6 +189,7 @@ impl SelectorMap { context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel, + cascade_data: &CascadeData, ) where E: TElement, F: FnMut(&E, ElementSelectorFlags), @@ -207,6 +208,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ); } @@ -219,6 +221,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } } @@ -232,6 +235,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } }); @@ -246,6 +250,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } }); @@ -259,6 +264,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } @@ -270,6 +276,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ) } @@ -280,6 +287,7 @@ impl SelectorMap { context, flags_setter, cascade_level, + cascade_data, ); } @@ -291,6 +299,7 @@ impl SelectorMap { context: &mut MatchingContext, flags_setter: &mut F, cascade_level: CascadeLevel, + cascade_data: &CascadeData, ) where E: TElement, F: FnMut(&E, ElementSelectorFlags), @@ -304,7 +313,7 @@ impl SelectorMap { context, flags_setter, ) { - matching_rules.push(rule.to_applicable_declaration_block(cascade_level)); + matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } } diff --git a/components/style/stylesheets/keyframes_rule.rs b/components/style/stylesheets/keyframes_rule.rs index 5172262014b..8b721b7a52c 100644 --- a/components/style/stylesheets/keyframes_rule.rs +++ b/components/style/stylesheets/keyframes_rule.rs @@ -14,7 +14,7 @@ use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::str::CssStringWriter; -use crate::stylesheets::layer_rule::LayerOrder; +use crate::stylesheets::layer_rule::LayerId; use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::values::{serialize_percentage, KeyframesName}; @@ -358,8 +358,8 @@ pub struct KeyframesAnimation { pub properties_changed: LonghandIdSet, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, - /// The order of the cascade layer the keyframe rule was in. - pub layer_order: LayerOrder, + /// The id of the cascade layer the keyframe rule was in. + pub layer_id: LayerId, } /// Get all the animated properties in a keyframes animation. @@ -412,14 +412,14 @@ impl KeyframesAnimation { pub fn from_keyframes( keyframes: &[Arc>], vendor_prefix: Option, - layer_order: LayerOrder, + layer_id: LayerId, guard: &SharedRwLockReadGuard, ) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: LonghandIdSet::new(), vendor_prefix, - layer_order, + layer_id, }; if keyframes.is_empty() { diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index 45649dcfece..ee066d813e2 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -19,94 +19,36 @@ use smallvec::SmallVec; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; -/// The order of a given layer. We encode in a 32-bit integer as follows: -/// -/// * 0 is reserved for the initial (top-level) layer. -/// * Top 7 bits are for top level layer order. -/// * The 25 remaining bits are split in 5 chunks of 5 bits each, for each -/// nesting level. -/// -/// This scheme this gives up to 127 layers in the top level, and up to 31 -/// children layers in nested levels, with a max of 6 nesting levels over all. -/// -/// This seemingly complicated scheme is to avoid fixing up layer orders after -/// the cascade data rebuild. -/// -/// An alternative approach that would allow improving those limits would be to -/// make layers have a sequential identifier, and sort layer order after the -/// fact. But that complicates incremental cascade data rebuild. +/// The order of a given layer. #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] pub struct LayerOrder(u32); impl LayerOrder { - const FIRST_LEVEL_BITS: usize = 7; - const CHILD_BITS: usize = 5; - const FIRST_LEVEL_MASK: u32 = 0b11111110_00000000_00000000_00000000; - const CHILD_MASK: u32 = 0b00011111; - - /// Get the raw value. - pub fn raw(self) -> u32 { - self.0 + /// The order of the root layer. + pub const fn root() -> Self { + Self(std::u32::MAX) } - /// The top level layer (implicit) is zero. - #[inline] - pub const fn top_level() -> Self { + /// The first cascade layer order. + pub const fn first() -> Self { Self(0) } - /// The first layer order. + /// Increment the cascade layer order. #[inline] - pub const fn first() -> Self { - Self(1 << (32 - Self::FIRST_LEVEL_BITS)) + pub fn inc(&mut self) { + self.0 += 1; } +} - fn child_bit_offset(self) -> usize { - if self.0 & (Self::CHILD_MASK << 5) != 0 { - return 0; // We're at the last or next-to-last level. - } - if self.0 & (Self::CHILD_MASK << 10) != 0 { - return 5; - } - if self.0 & (Self::CHILD_MASK << 15) != 0 { - return 10; - } - if self.0 & (Self::CHILD_MASK << 20) != 0 { - return 15; - } - if self.0 != 0 { - return 20; - } - return 25; - } +/// The id of a given layer, a sequentially-increasing identifier. +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerId(pub u32); - fn sibling_bit_mask_max_and_offset(self) -> (u32, u32, u32) { - debug_assert_ne!(self.0, 0, "Top layer should have no siblings"); - for offset in &[0, 5, 10, 15, 20] { - let mask = Self::CHILD_MASK << *offset; - if self.0 & mask != 0 { - return (mask, (1 << Self::CHILD_BITS) - 1, *offset); - } - } - return (Self::FIRST_LEVEL_MASK, (1 << Self::FIRST_LEVEL_BITS) - 1, 25); - } - - /// Generate the layer order for our first child. - pub fn for_child(self) -> Self { - Self(self.0 | (1 << self.child_bit_offset())) - } - - /// Generate the layer order for our next sibling. Might return the same - /// order when our limits overflow. - pub fn for_next_sibling(self) -> Self { - let (mask, max_index, offset) = self.sibling_bit_mask_max_and_offset(); - let self_index = (self.0 & mask) >> offset; - let next_index = if self_index == max_index { - self_index - } else { - self_index + 1 - }; - Self((self.0 & !mask) | (next_index << offset)) +impl LayerId { + /// The id of the root layer. + pub const fn root() -> Self { + Self(0) } } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 7e2b820498e..0663ea440d9 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -25,7 +25,7 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; +use crate::stylesheets::layer_rule::{LayerName, LayerId, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; #[cfg(feature = "gecko")] @@ -293,6 +293,8 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { )?; } + new_data.cascade_data.compute_layer_order(); + Ok(Arc::new(new_data)) } @@ -1867,12 +1869,21 @@ impl PartElementAndPseudoRules { } } -#[derive(Debug, Clone, MallocSizeOf)] -struct LayerOrderState { - /// The order for this layer. +#[derive(Clone, Debug, MallocSizeOf)] +struct CascadeLayer { + id: LayerId, order: LayerOrder, - /// The order for the next registered child layer. - next_child: LayerOrder, + children: Vec, +} + +impl CascadeLayer { + const fn root() -> Self { + Self { + id: LayerId::root(), + order: LayerOrder::root(), + children: vec![], + } + } } /// Data resulting from performing the CSS cascade that is specific to a given @@ -1941,10 +1952,10 @@ pub struct CascadeData { animations: PrecomputedHashMap, /// A map from cascade layer name to layer order. - layer_order: FxHashMap, + layer_id: FxHashMap, - /// The next layer order for the top level cascade data. - next_layer_order: LayerOrder, + /// The list of cascade layers, indexed by their layer id. + layers: SmallVec<[CascadeLayer; 1]>, /// Effective media query results cached from the last rebuild. effective_media_query_results: EffectiveMediaQueryResults, @@ -1986,8 +1997,8 @@ impl CascadeData { // somewhat gnarly. selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(), animations: Default::default(), - layer_order: Default::default(), - next_layer_order: LayerOrder::first(), + layer_id: Default::default(), + layers: smallvec::smallvec![CascadeLayer::root()], extra_data: ExtraStyleData::default(), effective_media_query_results: EffectiveMediaQueryResults::new(), rules_source_order: 0, @@ -2034,6 +2045,8 @@ impl CascadeData { result.is_ok() }); + self.compute_layer_order(); + result } @@ -2095,6 +2108,43 @@ impl CascadeData { self.part_rules.is_some() } + #[inline] + fn layer_order_for(&self, id: LayerId) -> LayerOrder { + self.layers[id.0 as usize].order + } + + fn compute_layer_order(&mut self) { + debug_assert_ne!(self.layers.len(), 0, "There should be at least the root layer!"); + if self.layers.len() == 1 { + return; // Nothing to do + } + let (first, remaining) = self.layers.split_at_mut(1); + let root = &mut first[0]; + let mut order = LayerOrder::first(); + compute_layer_order_for_subtree(root, remaining, &mut order); + + // NOTE(emilio): This is a bit trickier than it should to avoid having + // to clone() around layer indices. + fn compute_layer_order_for_subtree( + parent: &mut CascadeLayer, + remaining_layers: &mut [CascadeLayer], + order: &mut LayerOrder, + ) { + for child in parent.children.iter() { + debug_assert!(parent.id < *child, "Children are always registered after parents"); + let child_index = (child.0 - parent.id.0 - 1) as usize; + let (first, remaining) = remaining_layers.split_at_mut(child_index + 1); + let child = &mut first[child_index]; + compute_layer_order_for_subtree(child, remaining, order); + } + + if parent.id != LayerId::root() { + parent.order = *order; + order.inc(); + } + } + } + /// Collects all the applicable media query results into `results`. /// /// This duplicates part of the logic in `add_stylesheet`, which is @@ -2151,7 +2201,7 @@ impl CascadeData { guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, mut current_layer: &mut LayerName, - current_layer_order: LayerOrder, + current_layer_id: LayerId, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), FailedAllocationError> where @@ -2174,6 +2224,7 @@ impl CascadeData { if pseudo.is_precomputed() { debug_assert!(selector.is_universal()); debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); + debug_assert_eq!(current_layer_id, LayerId::root()); precomputed_pseudo_element_decls .as_mut() @@ -2184,7 +2235,7 @@ impl CascadeData { self.rules_source_order, CascadeLevel::UANormal, selector.specificity(), - current_layer_order, + LayerOrder::root(), )); continue; } @@ -2200,7 +2251,7 @@ impl CascadeData { hashes, locked.clone(), self.rules_source_order, - current_layer_order, + current_layer_id, ); if rebuild_kind.should_rebuild_invalidation() { @@ -2277,22 +2328,24 @@ impl CascadeData { e.insert(KeyframesAnimation::from_keyframes( &keyframes_rule.keyframes, keyframes_rule.vendor_prefix.clone(), - current_layer_order, + current_layer_id, guard, )); }, Entry::Occupied(mut e) => { // Don't let a prefixed keyframes animation override - // a non-prefixed one on the same layer. + // a non-prefixed one. + // + // TODO(emilio): This will need to be harder for + // layers. let needs_insert = - current_layer_order > e.get().layer_order || - (current_layer_order == e.get().layer_order && - (keyframes_rule.vendor_prefix.is_none() || e.get().vendor_prefix.is_some())); + 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_order, + current_layer_id, guard, )); } @@ -2360,32 +2413,38 @@ impl CascadeData { continue; } - fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerOrder { + 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(ref mut state) = data.layer_order.get(layer) { - return state.order; + if let Some(id) = data.layer_id.get(layer) { + return *id; } - // If the layer is not top-level, find the relevant parent. - let order = if layer.layer_names().len() > 1 { + 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(); - let mut parent_state = data.layer_order.get_mut(&parent).expect("Parent layers should be registered before child layers"); - let order = parent_state.next_child; - parent_state.next_child = order.for_next_sibling(); - order + *data.layer_id + .get_mut(&parent) + .expect("Parent layers should be registered before child layers") } else { - let order = data.next_layer_order; - data.next_layer_order = order.for_next_sibling(); - order + LayerId::root() }; - data.layer_order.insert(layer.clone(), LayerOrderState { - order, - next_child: order.for_child(), + + 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![], }); - order + + data.layer_id.insert(layer.clone(), id); + + id } fn maybe_register_layers( @@ -2393,7 +2452,7 @@ impl CascadeData { name: Option<&LayerName>, current_layer: &mut LayerName, pushed_layers: &mut usize, - ) -> LayerOrder { + ) -> LayerId { let anon_name; let name = match name { Some(name) => name, @@ -2403,18 +2462,18 @@ impl CascadeData { }, }; - let mut order = LayerOrder::top_level(); + let mut id = LayerId::root(); for name in name.layer_names() { current_layer.0.push(name.clone()); - order = maybe_register_layer(data, ¤t_layer); + id = maybe_register_layer(data, ¤t_layer); *pushed_layers += 1; } - debug_assert_ne!(order, LayerOrder::top_level()); - order + debug_assert_ne!(id, LayerId::root()); + id } let mut layer_names_to_pop = 0; - let mut children_layer_order = current_layer_order; + let mut children_layer_id = current_layer_id; match *rule { CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); @@ -2423,7 +2482,7 @@ impl CascadeData { .saw_effective(import_rule); } if let Some(ref layer) = import_rule.layer { - children_layer_order = maybe_register_layers( + children_layer_id = maybe_register_layers( self, layer.name.as_ref(), &mut current_layer, @@ -2444,7 +2503,7 @@ impl CascadeData { let layer_rule = lock.read_with(guard); match layer_rule.kind { LayerRuleKind::Block { ref name, .. } => { - children_layer_order = maybe_register_layers( + children_layer_id = maybe_register_layers( self, name.as_ref(), &mut current_layer, @@ -2482,7 +2541,7 @@ impl CascadeData { guard, rebuild_kind, current_layer, - children_layer_order, + children_layer_id, precomputed_pseudo_element_decls.as_deref_mut(), )?; } @@ -2527,7 +2586,7 @@ impl CascadeData { guard, rebuild_kind, &mut current_layer, - LayerOrder::top_level(), + LayerId::root(), precomputed_pseudo_element_decls.as_deref_mut(), )?; @@ -2646,8 +2705,9 @@ impl CascadeData { host_rules.clear(); } self.animations.clear(); - self.layer_order.clear(); - self.next_layer_order = LayerOrder::first(); + self.layer_id.clear(); + self.layers.clear(); + self.layers.push(CascadeLayer::root()); self.extra_data.clear(); self.rules_source_order = 0; self.num_selectors = 0; @@ -2736,8 +2796,8 @@ pub struct Rule { /// we could repurpose that storage here if we needed to. pub source_order: u32, - /// The current layer order of this style rule. - pub layer_order: LayerOrder, + /// The current layer id of this style rule. + pub layer_id: LayerId, /// The actual style rule. #[cfg_attr( @@ -2765,9 +2825,16 @@ impl Rule { pub fn to_applicable_declaration_block( &self, level: CascadeLevel, + cascade_data: &CascadeData, ) -> ApplicableDeclarationBlock { let source = StyleSource::from_rule(self.style_rule.clone()); - ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity(), self.layer_order) + ApplicableDeclarationBlock::new( + source, + self.source_order, + level, + self.specificity(), + cascade_data.layer_order_for(self.layer_id), + ) } /// Creates a new Rule. @@ -2776,14 +2843,14 @@ impl Rule { hashes: AncestorHashes, style_rule: Arc>, source_order: u32, - layer_order: LayerOrder, + layer_id: LayerId, ) -> Self { Rule { selector, hashes, style_rule, source_order, - layer_order, + layer_id, } } } From 2d8866d6c852f5789edc9d759430420d69f2375a Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 30 May 2023 23:35:42 +0200 Subject: [PATCH 73/94] Further changes required by Servo --- tests/unit/style/stylist.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 0c81a8e3d0f..c89a401bbc0 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -15,7 +15,7 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::selector_map::SelectorMap; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::shared_lock::SharedRwLock; -use style::stylesheets::layer_rule::LayerOrder; +use style::stylesheets::layer_rule::LayerId; use style::stylesheets::StyleRule; use style::stylist::needs_revalidation_for_testing; use style::stylist::{Rule, Stylist}; @@ -53,7 +53,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { AncestorHashes::new(s, QuirksMode::NoQuirks), locked.clone(), i as u32, - LayerOrder::top_level(), + LayerId::root(), ) }) .collect() From 9868cd52ba7986a3efec802993f9a75d3069afd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 17:36:53 +0200 Subject: [PATCH 74/94] style: Allow parsing layer statements inside nested rules This fixes https://wpt.live/css/css-cascade/layer-media-query.html once it syncs. Differential Revision: https://phabricator.services.mozilla.com/D129424 --- components/style/stylesheets/rule_parser.rs | 30 ++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 40ae7fb6188..7e3e4f9f86c 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -324,7 +324,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { source_location: start.source_location(), }))) }, - AtRulePrelude::Layer(names) => { + AtRulePrelude::Layer(ref names) => { if names.is_empty() { return Err(()); } @@ -333,12 +333,10 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { } else { self.state = State::Body; } - CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { - kind: LayerRuleKind::Statement { names }, - source_location: start.source_location(), - }))) + AtRuleParser::rule_without_block(&mut self.nested(), prelude, start) + .expect("All validity checks on the nested parser should be done before changing self.state") }, - _ => return Err(()), + _ => AtRuleParser::rule_without_block(&mut self.nested(), prelude, start)?, }; Ok((start.position(), rule)) @@ -646,6 +644,26 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, } } + + #[inline] + fn rule_without_block( + &mut self, + prelude: AtRulePrelude, + start: &ParserState, + ) -> Result { + Ok(match prelude { + AtRulePrelude::Layer(names) => { + if names.is_empty() { + return Err(()); + } + CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { + kind: LayerRuleKind::Statement { names }, + source_location: start.source_location(), + }))) + }, + _ => return Err(()), + }) + } } #[inline(never)] From 1b048382783a769e4dd0f7c3e88e893de7c732e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 27 May 2023 17:37:42 +0200 Subject: [PATCH 75/94] style: Use GTK menu radius on native context menus and panels Differential Revision: https://phabricator.services.mozilla.com/D129439 --- components/style/custom_properties.rs | 7 ++++++- components/style/gecko/media_queries.rs | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 7e8a1e37f7f..96328d9a51a 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -75,8 +75,13 @@ fn get_titlebar_radius(device: &Device) -> VariableValue { VariableValue::pixel(device.titlebar_radius()) } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 1] = [ +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 { diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index b41280d2112..6e82404de1d 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -447,6 +447,13 @@ impl Device { } } + /// 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 { From 40b7d87bbbd7731f19f01300066192879ee1e67f Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Sat, 27 May 2023 17:39:58 +0200 Subject: [PATCH 76/94] Further changes required by Servo --- components/atoms/static_atoms.txt | 1 + components/style/servo/media_queries.rs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 5a0cc47ed4f..2d0ef951067 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,4 +1,5 @@ -moz-gtk-csd-titlebar-radius +-moz-gtk-menu-radius DOMContentLoaded abort activate diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index 6ac0cf54505..62a0729a8e5 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -227,6 +227,12 @@ impl Device { 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 { From 9c6bf6d23bdce78a5e1d3db05f2eb70340d7f532 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Sat, 27 May 2023 17:42:00 +0200 Subject: [PATCH 77/94] style: Fix broken 'list-style' serialization Differential Revision: https://phabricator.services.mozilla.com/D129847 --- .../style/properties/shorthands/list.mako.rs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/components/style/properties/shorthands/list.mako.rs b/components/style/properties/shorthands/list.mako.rs index f757c368cfa..7f8c04026db 100644 --- a/components/style/properties/shorthands/list.mako.rs +++ b/components/style/properties/shorthands/list.mako.rs @@ -7,7 +7,6 @@ <%helpers:shorthand name="list-style" engines="gecko servo-2013 servo-2020" sub_properties="list-style-position list-style-image list-style-type" - derive_serialize="True" spec="https://drafts.csswg.org/css-lists/#propdef-list-style"> use crate::properties::longhands::{list_style_image, list_style_position, list_style_type}; use crate::values::specified::Image; @@ -104,4 +103,35 @@ _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write { + use longhands::list_style_position::SpecifiedValue as ListStylePosition; + use longhands::list_style_type::SpecifiedValue as ListStyleType; + use longhands::list_style_image::SpecifiedValue as ListStyleImage; + let mut have_one_non_initial_value = false; + if self.list_style_position != &ListStylePosition::Outside { + 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; + } + if self.list_style_type != &ListStyleType::disc() { + 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(()) + } + } From 3a23598080289afb8ccb2dcf096b739b1aa44136 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 30 May 2023 11:00:52 +0200 Subject: [PATCH 78/94] Further changes required by Servo --- components/style/properties/shorthands/list.mako.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/style/properties/shorthands/list.mako.rs b/components/style/properties/shorthands/list.mako.rs index 7f8c04026db..0ba42d3fca4 100644 --- a/components/style/properties/shorthands/list.mako.rs +++ b/components/style/properties/shorthands/list.mako.rs @@ -110,7 +110,11 @@ use longhands::list_style_type::SpecifiedValue as ListStyleType; use longhands::list_style_image::SpecifiedValue as ListStyleImage; let mut have_one_non_initial_value = false; - if self.list_style_position != &ListStylePosition::Outside { + #[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; } @@ -121,7 +125,11 @@ self.list_style_image.to_css(dest)?; have_one_non_initial_value = true; } - if self.list_style_type != &ListStyleType::disc() { + #[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(" ")?; } From 6c16c019fc7b3f2329d572f3ff83a345e595c706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:35:36 +0200 Subject: [PATCH 79/94] style: Implement There are still tests failing because https://bugzilla.mozilla.org/show_bug.cgi?id=1736034 hasn't been synced yet. Once that lands, they will still fail because we don't change Canvas/CanvasText based on color-scheme, but that I'm attaching patches for after this one. Differential Revision: https://phabricator.services.mozilla.com/D129743 --- components/style/values/specified/color.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 44d290bcb0e..2fd586864ae 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -958,6 +958,11 @@ impl ColorScheme { bits: ColorSchemeFlags::empty(), } } + + /// Returns the raw bitfield. + pub fn raw_bits(&self) -> u8 { + self.bits.bits + } } impl Parse for ColorScheme { From c3322938f2b3d6f48dfc0b2049b8016a91d16ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:36:40 +0200 Subject: [PATCH 80/94] style: Move Canvas/Link color computation to C++-land This doesn't change behavior but will allow us to deduplicate some logic given we compute the effective color-scheme in C++. Differential Revision: https://phabricator.services.mozilla.com/D129744 --- components/style/values/specified/color.rs | 33 ++++++++-------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 2fd586864ae..d0f3ef80dcf 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -471,28 +471,17 @@ impl SystemColor { fn compute(&self, cx: &Context) -> ComputedColor { use crate::gecko_bindings::bindings; - let colors = &cx.device().pref_sheet_prefs().mColors; - let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); - - // TODO: At least Canvas / CanvasText should be color-scheme aware - // (probably the link colors too). - convert_nscolor_to_computedcolor(match *self { - SystemColor::Canvastext => colors.mDefault, - SystemColor::Canvas => colors.mDefaultBackground, - SystemColor::Linktext => colors.mLink, - SystemColor::Activetext => colors.mActiveLink, - SystemColor::Visitedtext => colors.mVisitedLink, - - _ => { - let color = unsafe { - bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), &style_color_scheme) - }; - if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { - return ComputedColor::currentcolor(); - } - color - }, - }) + // TODO: We should avoid cloning here most likely, though it's + // cheap-ish. + let style_color_scheme = + cx.style().get_inherited_ui().clone_color_scheme(); + let color = unsafe { + bindings::Gecko_ComputeSystemColor(*self, cx.device().document(), &style_color_scheme) + }; + if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { + return ComputedColor::currentcolor(); + } + convert_nscolor_to_computedcolor(color) } } From f70b0e7108a68420840e93938aeb55c0bccca885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:41:21 +0200 Subject: [PATCH 81/94] style: Make Canvas/CanvasText and Link colors color-scheme-aware For that, add `.dark` version of the browser.display* prefs that control the light version of these colors. The default for background/foreground colors are taken from the GenericDarkColors used in LookAndFeel. The defaults for links are based on this discussion: https://github.com/whatwg/html/issues/5426#issuecomment-904021675 (So they effectively match Chrome). Whether the dark colors should be exposed in about:preferences (like the light colors are) is TBD. With this patch, we pass all the tests in: /html/semantics/document-metadata/the-meta-element/color-scheme/ Use the colors to paint the default canvas background and the default colors. There are three "regressions", though they are really progressions: we now render the reference as the test expects (before we rendered a light canvas background even for the reference). Apart of these iframe tests (which we should look into, I filed https://bugzilla.mozilla.org/show_bug.cgi?id=1738380), there are three remaining test failures. Two of them are due to `color: initial` not changing based on the color-scheme. Safari also fails these tests, and the thing they're really testing is whether system colors are preserved at computed-value time: https://github.com/w3c/csswg-drafts/issues/3847 Regarding that change, I'm not so sure the trade-offs there are worth it, as that not only complicates interpolation (we wouldn't be able to use system colors in color-mix among others, see https://github.com/w3c/csswg-drafts/issues/5780) plus it changes inheritance behavior in sorta unexpected ways, see: https://github.com/w3c/csswg-drafts/issues/6773 Which I just filed because apparently no browser implements this correctly. So for now will punt on those (keep matching Safari). There's an svg-as-image test: https://searchfox.org/mozilla-central/rev/f8576fec48d866c5f988baaf1fa8d2f8cce2a82f/testing/web-platform/tests/css/css-color-adjust/rendering/dark-color-scheme/svg-as-image.html Which isn't using the feature at all and I'm not sure why is it supposed to pass (why prefers-color-scheme: dark is supposed to match that SVG image). This test fails in all browsers apparently: https://wpt.fyi/results/css/css-color-adjust/rendering/dark-color-scheme/svg-as-image.html?label=master&label=experimental&aligned I sent https://github.com/web-platform-tests/wpt/pull/31407 to remove it and hopefully get it reviewed by some Chromium folks. Differential Revision: https://phabricator.services.mozilla.com/D129746 --- components/style/gecko/media_queries.rs | 17 ++++++++++++----- components/style/properties/cascade.rs | 4 ++-- .../properties/longhands/inherited_ui.mako.rs | 1 + 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 6e82404de1d..7bab0b002d1 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -93,7 +93,9 @@ impl Device { document, default_values: ComputedValues::default_values(doc), root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()), - body_text_color: AtomicUsize::new(prefs.mColors.mDefault as usize), + // This gets updated when we see the , so it doesn't really + // matter which color-scheme we look at here. + body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize), used_root_font_size: AtomicBool::new(false), used_font_metrics: AtomicBool::new(false), used_viewport_size: AtomicBool::new(false), @@ -386,13 +388,18 @@ impl Device { } /// Returns the default background color. - pub fn default_background_color(&self) -> RGBA { - convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefaultBackground) + /// + /// This is only for forced-colors/high-contrast, so looking at light colors + /// is ok. + pub fn default_background_color_for_forced_colors(&self) -> RGBA { + convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefaultBackground) } /// Returns the default foreground color. - pub fn default_color(&self) -> RGBA { - convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefault) + /// + /// See above for looking at light colors only. + pub fn default_color_for_forced_colors(&self) -> RGBA { + convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefault) } /// Returns the current effective text zoom. diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 0834da93e89..fe5bae33fe8 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -430,7 +430,7 @@ fn tweak_when_ignoring_colors( // widget background color's rgb channels but not alpha... let alpha = alpha_channel(color, context); if alpha != 0 { - let mut color = context.builder.device.default_background_color(); + let mut color = context.builder.device.default_background_color_for_forced_colors(); color.alpha = alpha; declarations_to_apply_unless_overriden .push(PropertyDeclaration::BackgroundColor(color.into())) @@ -448,7 +448,7 @@ fn tweak_when_ignoring_colors( // override this with a non-transparent color, then override it with // the default color. Otherwise just let it inherit through. if context.builder.get_parent_inherited_text().clone_color().alpha == 0 { - let color = context.builder.device.default_color(); + let color = context.builder.device.default_color_for_forced_colors(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), )) diff --git a/components/style/properties/longhands/inherited_ui.mako.rs b/components/style/properties/longhands/inherited_ui.mako.rs index 4fb702a97b4..44dfdd74a2a 100644 --- a/components/style/properties/longhands/inherited_ui.mako.rs +++ b/components/style/properties/longhands/inherited_ui.mako.rs @@ -104,6 +104,7 @@ ${helpers.predefined_type( gecko_pref="layout.css.color-scheme.enabled", animation_value_type="discrete", has_effect_on_gecko_scrollbars=False, + ignored_when_colors_disabled=True, enabled_in="chrome", )} From 5f46c027eaa9ebe0796f94f6d5708467e6d5c4d2 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 30 May 2023 20:57:42 +0200 Subject: [PATCH 82/94] Further changes required by Servo --- components/style/servo/media_queries.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index 62a0729a8e5..d575b7c2df7 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -190,12 +190,12 @@ impl Device { } /// Returns the default background color. - pub fn default_background_color(&self) -> RGBA { + pub fn default_background_color_for_forced_colors(&self) -> RGBA { RGBA::new(255, 255, 255, 255) } - /// Returns the default color color. - pub fn default_color(&self) -> RGBA { + /// Returns the default foreground color. + pub fn default_color_for_forced_colors(&self) -> RGBA { RGBA::new(0, 0, 0, 255) } From 285c645b7877c03e11ad322dd33acf2b89c3fea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:42:22 +0200 Subject: [PATCH 83/94] style: Make autofill background configurable via system color prefs And remove the autofill.background pref for 95 (or 96, depending on when this lands) assuming nothing terrible causes us to turn it off on 94. Differential Revision: https://phabricator.services.mozilla.com/D129988 --- components/style/values/specified/color.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index d0f3ef80dcf..866a33273c8 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -427,6 +427,10 @@ pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozAccentColorForeground, + /// The background-color for :autofill-ed inputs. + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] + MozAutofillBackground, + /// Media rebar text. MozWinMediatext, /// Communications rebar text. From 04776cd116dfa198e52520a50870ae9884905bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:43:32 +0200 Subject: [PATCH 84/94] style: Treat empty root margin as zero as per spec Differential Revision: https://phabricator.services.mozilla.com/D130131 --- components/style/values/specified/gecko.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs index 3e3085c8849..9b01cc2a6c4 100644 --- a/components/style/values/specified/gecko.rs +++ b/components/style/values/specified/gecko.rs @@ -49,6 +49,11 @@ impl Parse for IntersectionObserverRootMargin { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { + use crate::Zero; + if input.is_exhausted() { + // If there are zero elements in tokens, set tokens to ["0px"]. + return Ok(IntersectionObserverRootMargin(Rect::all(LengthPercentage::zero()))); + } let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?; Ok(IntersectionObserverRootMargin(rect)) } From b702426c994d0fec6f164b62351c7224d6d95c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:44:25 +0200 Subject: [PATCH 85/94] style: Generalize resolved style code to deal with all shorthands This removes the various assumptions that the animation code does. Code size might be a concern, we can optimize if it is a problem, but let's do the obvious thing for now. Differential Revision: https://phabricator.services.mozilla.com/D130017 --- .../style/properties/properties.mako.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 10b13618a94..0916260adb3 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -3086,7 +3086,10 @@ impl ComputedValues { /// /// Note that the value will usually be the computed value, except for /// colors, where it's resolved. - pub fn get_longhand_property_value( + /// + /// TODO(emilio): We should move all the special resolution from + /// nsComputedDOMStyle to ToResolvedValue instead. + pub fn get_resolved_value( &self, property_id: LonghandId, dest: &mut CssWriter @@ -3112,6 +3115,31 @@ impl ComputedValues { } } + /// Returns the given longhand's resolved value as a property declaration. + pub fn resolved_declaration(&self, property_id: LonghandId) -> PropertyDeclaration { + use crate::values::resolved::ToResolvedValue; + use crate::values::computed::ToComputedValue; + + let context = resolved::Context { + style: self, + }; + + match property_id { + % for prop in data.longhands: + LonghandId::${prop.camel_case} => { + let value = self.clone_${prop.ident}(); + let resolved = value.to_resolved_value(&context); + %if prop.boxed: + let resolved = Box::new(resolved); + %endif + let computed = ToResolvedValue::from_resolved_value(resolved); + let specified = ToComputedValue::from_computed_value(&computed); + PropertyDeclaration::${prop.camel_case}(specified) + } + % endfor + } + } + /// Resolves the currentColor keyword. /// /// Any color value from computed values (except for the 'color' property From b3e40479f8aace5579d8c07810b560108178aff2 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 30 May 2023 21:03:52 +0200 Subject: [PATCH 86/94] Further changes required by Servo --- components/style/properties/properties.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 0916260adb3..3bed01019ea 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -3211,7 +3211,7 @@ impl ComputedValues { match property { PropertyDeclarationId::Longhand(id) => { let mut s = String::new(); - self.get_longhand_property_value( + self.get_resolved_value( id, &mut CssWriter::new(&mut s) ).unwrap(); From b1ef020aa2ac3f1574313f5cd4e161ca7b143285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:46:04 +0200 Subject: [PATCH 87/94] style: Serialize text-decoration using Servo The code introduced in the preceding patch deals with currentColor correctly, so we should be able to do this now. This uncovers a bug in the existing serialization code when a non-auto text-decoration-thickness was used, caught by css/css-text-decor/parsing/text-decoration-computed.html. Differential Revision: https://phabricator.services.mozilla.com/D130018 --- components/style/properties/shorthands/text.mako.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/properties/shorthands/text.mako.rs b/components/style/properties/shorthands/text.mako.rs index 3f892affdf5..eb0c5dc4003 100644 --- a/components/style/properties/shorthands/text.mako.rs +++ b/components/style/properties/shorthands/text.mako.rs @@ -94,6 +94,7 @@ dest.write_str(" ")?; } self.text_decoration_thickness.to_css(dest)?; + has_value = true; } if !is_solid_style { From 2fd66ea5884d30cd07a445303af6f13a09bccfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:47:54 +0200 Subject: [PATCH 88/94] style: Remove WindowBackground/Foreground system colors There's no reason for these to be different to the CSS-exposed Window/WindowText. Differential Revision: https://phabricator.services.mozilla.com/D129990 --- components/style/values/specified/color.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 866a33273c8..867536a0230 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -231,10 +231,6 @@ pub enum Color { #[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)] #[repr(u8)] pub enum SystemColor { - #[css(skip)] - WindowBackground, - #[css(skip)] - WindowForeground, #[css(skip)] WidgetBackground, #[css(skip)] From 4ed53833ea560792afe2e369de2515339afcffc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:48:32 +0200 Subject: [PATCH 89/94] style: Remove TextForeground/Background system colors Same thing, there's nor reason these should be different from other CSS-exposed colors. Differential Revision: https://phabricator.services.mozilla.com/D129991 --- components/style/values/specified/color.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 867536a0230..84d8ea4c8b4 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -243,10 +243,6 @@ pub enum SystemColor { Widget3DHighlight, #[css(skip)] Widget3DShadow, - #[css(skip)] - TextBackground, - #[css(skip)] - TextForeground, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] TextSelectBackgroundDisabled, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] From b9d2ae624bdeeb162484815eba51a83015e9235e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 30 May 2023 20:49:16 +0200 Subject: [PATCH 90/94] style: Remove other Widget* colors These are only used for frameset painting and the non-e10s