From 155fbf8804a675d5d851d194cc5de0e217d9ebd5 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Fri, 9 Jun 2023 10:20:04 +0200 Subject: [PATCH 01/81] style: Make #[css(represents_keyword)] convert underscore in the field name to dash Differential Revision: https://phabricator.services.mozilla.com/D128668 --- components/style_derive/to_css.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs index 1f94751f889..19c7c1ac2bc 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -208,7 +208,7 @@ fn derive_single_field_expr( .ident .as_ref() .expect("Unnamed field with represents_keyword?"); - let ident = cg::to_css_identifier(&ident.to_string()); + let ident = cg::to_css_identifier(&ident.to_string()).replace("_", "-"); quote! { if *#field { writer.raw_item(#ident)?; From 327812e3ebc2b46e566f536df24020d1cb27eaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 7 Jun 2023 14:03:41 +0200 Subject: [PATCH 02/81] style: Make #[css(field_bound)] and #[css(iterable)] work properly For now, use IntoIterator to figure the right type to add the bound. If we need this on types that are iterable but don't provide IntoIterator, we can add another attribute field or something. Differential Revision: https://phabricator.services.mozilla.com/D129962 --- components/style_derive/to_css.rs | 16 ++++++++++++---- components/style_traits/owned_slice.rs | 16 ++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs index 19c7c1ac2bc..40e219c374b 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -140,11 +140,19 @@ fn derive_variant_fields_expr( Some(pair) => pair, None => return quote! { Ok(()) }, }; + if attrs.field_bound { + let ty = &first.ast().ty; + // TODO(emilio): IntoIterator might not be enough for every type of + // iterable thing (like ArcSlice<> or what not). We might want to expose + // an `item = "T"` attribute to handle that in the future. + let predicate = if attrs.iterable { + parse_quote!(<#ty as IntoIterator>::Item: style_traits::ToCss) + } else { + parse_quote!(#ty: style_traits::ToCss) + }; + cg::add_predicate(where_clause, predicate); + } if !attrs.iterable && iter.peek().is_none() { - if attrs.field_bound { - let ty = &first.ast().ty; - cg::add_predicate(where_clause, parse_quote!(#ty: style_traits::ToCss)); - } let mut expr = quote! { style_traits::ToCss::to_css(#first, dest) }; if let Some(condition) = attrs.skip_if { expr = quote! { diff --git a/components/style_traits/owned_slice.rs b/components/style_traits/owned_slice.rs index f6068365360..36ba3162e59 100644 --- a/components/style_traits/owned_slice.rs +++ b/components/style_traits/owned_slice.rs @@ -93,12 +93,6 @@ impl OwnedSlice { ret } - /// Iterate over all the elements in the slice taking ownership of them. - #[inline] - pub fn into_iter(self) -> impl Iterator + ExactSizeIterator { - self.into_vec().into_iter() - } - /// Convert the regular slice into an owned slice. #[inline] pub fn from_slice(s: &[T]) -> Self @@ -109,6 +103,16 @@ impl OwnedSlice { } } +impl IntoIterator for OwnedSlice { + type Item = T; + type IntoIter = as IntoIterator>::IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.into_vec().into_iter() + } +} + impl Deref for OwnedSlice { type Target = [T]; From 5b68241958408eb390c7d634ee77544c0d7ef861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:37:02 +0200 Subject: [PATCH 03/81] style: Simplify :dir() implementation This I noticed while working on the following patches. Shouldn't have any behavior change: the behavior does in fact match the element state flag semantics correctly if we do this. We did split the dir flags into two element bits a while ago. :not(:dir()) still behaves correctly of course, and we have tests for that. Differential Revision: https://phabricator.services.mozilla.com/D130734 --- components/style/gecko/selector_parser.rs | 8 +++---- components/style/gecko/wrapper.rs | 4 ++-- .../invalidation/element/element_wrapper.rs | 22 ------------------- .../invalidation/element/invalidation_map.rs | 6 +---- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 7028d554aa2..e9d2c28ccfb 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -162,7 +162,7 @@ impl NonTSPseudoClass { ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { match *self { $(NonTSPseudoClass::$name => flag!($state),)* - NonTSPseudoClass::Dir(..) | + NonTSPseudoClass::Dir(ref dir) => dir.element_state(), NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::Lang(..) => ElementState::empty(), } @@ -186,10 +186,8 @@ impl NonTSPseudoClass { self.state_flag().is_empty() && !matches!( *self, - // :dir() depends on state only, but doesn't use state_flag - // because its semantics don't quite match. Nevertheless, it - // doesn't need cache revalidation, because we already compare - // states for elements and candidates. + // :dir() depends on state only, but may have an empty + // state_flag for invalid arguments. NonTSPseudoClass::Dir(_) | // :-moz-is-html only depends on the state of the document and // the namespace of the element; the former is invariant diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 95da8b986f6..09dbc6f06c1 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1979,7 +1979,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozTopmostModalDialog | NonTSPseudoClass::Active | NonTSPseudoClass::Hover | - NonTSPseudoClass::MozAutofillPreview => { + NonTSPseudoClass::MozAutofillPreview | + NonTSPseudoClass::Dir(..) => { self.state().intersects(pseudo_class.state_flag()) }, NonTSPseudoClass::AnyLink => self.is_link(), @@ -2065,7 +2066,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { None => false, } }, - NonTSPseudoClass::Dir(ref dir) => self.state().intersects(dir.element_state()), } } diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index d79e1402228..2aa5749fdee 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -178,28 +178,6 @@ where // Some pseudo-classes need special handling to evaluate them against // the snapshot. match *pseudo_class { - // :dir is implemented in terms of state flags, but which state flag - // it maps to depends on the argument to :dir. That means we can't - // just add its state flags to the NonTSPseudoClass, because if we - // added all of them there, and tested via intersects() here, we'd - // get incorrect behavior for :not(:dir()) cases. - // - // FIXME(bz): How can I set this up so once Servo adds :dir() - // support we don't forget to update this code? - #[cfg(feature = "gecko")] - NonTSPseudoClass::Dir(ref dir) => { - let selector_flag = dir.element_state(); - if selector_flag.is_empty() { - // :dir() with some random argument; does not match. - return false; - } - let state = match self.snapshot().and_then(|s| s.state()) { - Some(snapshot_state) => snapshot_state, - None => self.element.state(), - }; - return state.contains(selector_flag); - }, - // For :link and :visited, we don't actually want to test the // element state directly. // diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index a7c2b04df13..42a991e8c15 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -494,11 +494,7 @@ impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> { } }, Component::NonTSPseudoClass(ref pc) => { - self.compound_state.element_state |= match *pc { - #[cfg(feature = "gecko")] - NonTSPseudoClass::Dir(ref dir) => dir.element_state(), - _ => pc.state_flag(), - }; + self.compound_state.element_state |= pc.state_flag(); *self.document_state |= pc.document_state_flag(); let attr_name = match *pc { From a0e29d70328a3d29b4c9abd90fc7ad9276004ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:37:50 +0200 Subject: [PATCH 04/81] style: Refactor :-moz-lwtheme pseudo-classes to get invalidated correctly Use the same document state mechanism we have for :moz-locale-dir. Also, simplify the setup of the later to be the same as :dir(), allowing the matching code to be less repetitive. This should fix some flakiness in chrome mochitests, but we have no existing tests for these pseudo-classes more generally and since they're just chrome-only I'm not super-excited about adding more. Differential Revision: https://phabricator.services.mozilla.com/D130735 --- components/style/element_state.rs | 14 +++++-- components/style/gecko/selector_parser.rs | 15 ++++++-- components/style/gecko/wrapper.rs | 46 ++++++----------------- 3 files changed, 35 insertions(+), 40 deletions(-) diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 8a9f4065b8b..774e3936d2a 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -137,9 +137,17 @@ bitflags! { /// dom/base/Document.h. #[derive(MallocSizeOf)] pub struct DocumentState: u64 { - /// RTL locale: specific to the XUL localedir attribute - const NS_DOCUMENT_STATE_RTL_LOCALE = 1 << 0; /// Window activation status - const NS_DOCUMENT_STATE_WINDOW_INACTIVE = 1 << 1; + const WINDOW_INACTIVE = 1 << 0; + /// RTL locale: specific to the XUL localedir attribute + const RTL_LOCALE = 1 << 1; + /// LTR locale: specific to the XUL localedir attribute + const LTR_LOCALE = 1 << 2; + /// LWTheme status + const LWTHEME = 1 << 3; + /// LWTheme status + const LWTHEME_BRIGHTTEXT = 1 << 4; + /// LWTheme status + const LWTHEME_DARKTEXT = 1 << 5; } } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index e9d2c28ccfb..751a38d8465 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -8,7 +8,7 @@ use crate::element_state::{DocumentState, ElementState}; use crate::gecko_bindings::structs::RawServoSelectorList; use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::invalidation::element::document_state::InvalidationMatchingData; -use crate::selector_parser::{Direction, SelectorParser}; +use crate::selector_parser::{Direction, HorizontalDirection, SelectorParser}; use crate::str::starts_with_ignore_ascii_case; use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use crate::values::{AtomIdent, AtomString}; @@ -174,8 +174,17 @@ impl NonTSPseudoClass { /// Get the document state flag associated with a pseudo-class, if any. pub fn document_state_flag(&self) -> DocumentState { match *self { - NonTSPseudoClass::MozLocaleDir(..) => DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE, - NonTSPseudoClass::MozWindowInactive => DocumentState::NS_DOCUMENT_STATE_WINDOW_INACTIVE, + NonTSPseudoClass::MozLocaleDir(ref dir) => { + match dir.as_horizontal_direction() { + Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, + Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, + None => DocumentState::empty(), + } + }, + NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, + NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, + NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT, + NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT, _ => DocumentState::empty(), } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 09dbc6f06c1..d2a86caf100 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -39,11 +39,10 @@ use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; use crate::gecko_bindings::bindings::Gecko_MatchLang; use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; -use crate::gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentLWTheme}; +use crate::gecko_bindings::bindings::Gecko_ElementState; use crate::gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::nsChangeHint; -use crate::gecko_bindings::structs::Document_DocumentTheme as DocumentTheme; use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; use crate::gecko_bindings::structs::ELEMENT_HANDLED_SNAPSHOT; use crate::gecko_bindings::structs::ELEMENT_HAS_ANIMATION_ONLY_DIRTY_DESCENDANTS_FOR_SERVO; @@ -62,7 +61,7 @@ use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; use crate::properties::{ComputedValues, LonghandId}; use crate::properties::{Importance, PropertyDeclaration, PropertyDeclarationBlock}; use crate::rule_tree::CascadeLevel as ServoCascadeLevel; -use crate::selector_parser::{AttrValue, HorizontalDirection, Lang}; +use crate::selector_parser::{AttrValue, Lang}; use crate::shared_lock::{Locked, SharedRwLock}; use crate::string_cache::{Atom, Namespace, WeakAtom, WeakNamespace}; use crate::stylist::CascadeData; @@ -752,12 +751,6 @@ impl<'le> GeckoElement<'le> { .get_bool_flag(nsINode_BooleanFlag::ElementMayHaveStyle) } - #[inline] - fn document_theme(&self) -> DocumentTheme { - let node = self.as_node(); - unsafe { Gecko_GetDocumentLWTheme(node.owner_doc().0) } - } - /// Only safe to call on the main thread, with exclusive access to the /// element and its ancestors. /// @@ -2033,39 +2026,24 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { bindings::Gecko_IsSelectListBox(self.0) }, NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(), - NonTSPseudoClass::MozLWTheme => self.document_theme() != DocumentTheme::Doc_Theme_None, - NonTSPseudoClass::MozLWThemeBrightText => { - self.document_theme() == DocumentTheme::Doc_Theme_Bright - }, - NonTSPseudoClass::MozLWThemeDarkText => { - self.document_theme() == DocumentTheme::Doc_Theme_Dark - }, + + NonTSPseudoClass::MozLWTheme | + NonTSPseudoClass::MozLWThemeBrightText | + NonTSPseudoClass::MozLWThemeDarkText | + NonTSPseudoClass::MozLocaleDir(..) | NonTSPseudoClass::MozWindowInactive => { - let state_bit = DocumentState::NS_DOCUMENT_STATE_WINDOW_INACTIVE; + let state_bit = pseudo_class.document_state_flag(); + if state_bit.is_empty() { + debug_assert!(matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), "Only moz-locale-dir should ever return an empty state"); + return false; + } if context.extra_data.document_state.intersects(state_bit) { return !context.in_negation(); } - self.document_state().contains(state_bit) }, NonTSPseudoClass::MozPlaceholder => false, NonTSPseudoClass::Lang(ref lang_arg) => self.match_element_lang(None, lang_arg), - NonTSPseudoClass::MozLocaleDir(ref dir) => { - let state_bit = DocumentState::NS_DOCUMENT_STATE_RTL_LOCALE; - if context.extra_data.document_state.intersects(state_bit) { - // NOTE(emilio): We could still return false for values - // other than "ltr" and "rtl", but we don't bother. - return !context.in_negation(); - } - - let doc_is_rtl = self.document_state().contains(state_bit); - - match dir.as_horizontal_direction() { - Some(HorizontalDirection::Ltr) => !doc_is_rtl, - Some(HorizontalDirection::Rtl) => doc_is_rtl, - None => false, - } - }, } } From 04282ff04c4bf2c632f37d25cf980e13cb7c8c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:39:33 +0200 Subject: [PATCH 05/81] style: Allow matches() / querySelector() / etc on chrome docs to access chrome-only selectors. r=boris Without this some tests fail with the previous patch because code like: https://searchfox.org/mozilla-central/rev/267682a8f45221bf0bfe999d4a0239706a43bc56/browser/base/content/browser-gestureSupport.js#651 starts throwing. Unfortunately I had missed that on my try run, because the error message didn't include that exception (it seemed like an intermittent browser-chrome failure instead). We could expose a ChromeOnly API for this, but this seems better. This fixes it trivially, and also removes the "no url data" situation from the selector parser, which is nice. Differential Revision: https://phabricator.services.mozilla.com/D130818 --- components/style/selector_parser.rs | 13 +++++++------ components/style/stylesheets/rule_parser.rs | 2 +- components/style/stylesheets/supports_rule.rs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index 67d4d4f6981..8e0e3412d4b 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -46,7 +46,7 @@ pub struct SelectorParser<'a> { pub namespaces: &'a Namespaces, /// The extra URL data of the stylesheet, which is used to look up /// whether we are parsing a chrome:// URL style sheet. - pub url_data: Option<&'a UrlExtraData>, + pub url_data: &'a UrlExtraData, } impl<'a> SelectorParser<'a> { @@ -54,14 +54,15 @@ impl<'a> SelectorParser<'a> { /// account namespaces. /// /// This is used for some DOM APIs like `querySelector`. - pub fn parse_author_origin_no_namespace( - input: &str, - ) -> Result, ParseError> { + pub fn parse_author_origin_no_namespace<'i>( + input: &'i str, + url_data: &UrlExtraData, + ) -> Result, ParseError<'i>> { let namespaces = Namespaces::default(); let parser = SelectorParser { stylesheet_origin: Origin::Author, namespaces: &namespaces, - url_data: None, + url_data, }; let mut input = ParserInput::new(input); SelectorList::parse(&parser, &mut CssParser::new(&mut input)) @@ -75,7 +76,7 @@ impl<'a> SelectorParser<'a> { /// Whether we're parsing selectors in a stylesheet that has chrome /// privilege. pub fn chrome_rules_enabled(&self) -> bool { - self.url_data.map_or(false, |d| d.chrome_rules_enabled()) || + self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 7e3e4f9f86c..995ee801e40 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -710,7 +710,7 @@ impl<'a, 'b, 'i> QualifiedRuleParser<'i> for NestedRuleParser<'a, 'b> { let selector_parser = SelectorParser { stylesheet_origin: self.context.stylesheet_origin, namespaces: self.namespaces, - url_data: Some(self.context.url_data), + url_data: self.context.url_data, }; let selectors = SelectorList::parse(&selector_parser, input)?; if self.context.error_reporting_enabled() { diff --git a/components/style/stylesheets/supports_rule.rs b/components/style/stylesheets/supports_rule.rs index d0aaa0a03d8..23d79fd1cc2 100644 --- a/components/style/stylesheets/supports_rule.rs +++ b/components/style/stylesheets/supports_rule.rs @@ -335,7 +335,7 @@ impl RawSelector { let parser = SelectorParser { namespaces, stylesheet_origin: context.stylesheet_origin, - url_data: Some(context.url_data), + url_data: context.url_data, }; #[allow(unused_variables)] From 07dbd9d6379fc2bc1c32bb0957bb2e40c6c24049 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 31 May 2023 12:17:20 +0200 Subject: [PATCH 06/81] Further changes required by Servo --- components/script/dom/cssstylerule.rs | 12 ++++-------- components/script/dom/element.rs | 12 ++++++++---- components/script/dom/node.rs | 14 ++++++-------- tests/unit/style/parsing/selectors.rs | 4 +++- tests/unit/style/stylist.rs | 7 +++++-- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/components/script/dom/cssstylerule.rs b/components/script/dom/cssstylerule.rs index a84c5c598b7..f81f30ab258 100644 --- a/components/script/dom/cssstylerule.rs +++ b/components/script/dom/cssstylerule.rs @@ -96,19 +96,15 @@ impl CSSStyleRuleMethods for CSSStyleRule { // https://drafts.csswg.org/cssom/#dom-cssstylerule-selectortext fn SetSelectorText(&self, value: DOMString) { + let contents = &self.cssrule.parent_stylesheet().style_stylesheet().contents; // It's not clear from the spec if we should use the stylesheet's namespaces. // https://github.com/w3c/csswg-drafts/issues/1511 - let namespaces = self - .cssrule - .parent_stylesheet() - .style_stylesheet() - .contents - .namespaces - .read(); + let namespaces = contents.namespaces.read(); + let url_data = contents.url_data.read(); let parser = SelectorParser { stylesheet_origin: Origin::Author, namespaces: &namespaces, - url_data: None, + url_data: &url_data, }; let mut css_parser = CssParserInput::new(&*value); let mut css_parser = CssParser::new(&mut css_parser); diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index f2de9aaa1a1..8a7b98a8756 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -2700,12 +2700,14 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-matches fn Matches(&self, selectors: DOMString) -> Fallible { - let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let doc = document_from_node(self); + let url = doc.url(); + let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors, &url) { Err(_) => return Err(Error::Syntax), Ok(selectors) => selectors, }; - let quirks_mode = document_from_node(self).quirks_mode(); + let quirks_mode = doc.quirks_mode(); let element = DomRoot::from_ref(self); Ok(dom_apis::element_matches(&element, &selectors, quirks_mode)) @@ -2718,12 +2720,14 @@ impl ElementMethods for Element { // https://dom.spec.whatwg.org/#dom-element-closest fn Closest(&self, selectors: DOMString) -> Fallible>> { - let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let doc = document_from_node(self); + let url = doc.url(); + let selectors = match SelectorParser::parse_author_origin_no_namespace(&selectors, &url) { Err(_) => return Err(Error::Syntax), Ok(selectors) => selectors, }; - let quirks_mode = document_from_node(self).quirks_mode(); + let quirks_mode = doc.quirks_mode(); Ok(dom_apis::element_closest( DomRoot::from_ref(self), &selectors, diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index c98e9b93bfe..f419dcab485 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -949,18 +949,15 @@ impl Node { // https://dom.spec.whatwg.org/#dom-parentnode-queryselector pub fn query_selector(&self, selectors: DOMString) -> Fallible>> { // Step 1. - match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let doc = self.owner_doc(); + match SelectorParser::parse_author_origin_no_namespace(&selectors, &doc.url()) { // Step 2. Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { // FIXME(bholley): Consider an nth-index cache here. - let mut ctx = MatchingContext::new( - MatchingMode::Normal, - None, - None, - self.owner_doc().quirks_mode(), - ); + let mut ctx = + MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode()); Ok(self .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast) @@ -975,7 +972,8 @@ impl Node { /// whilst iterating, otherwise the iterator may be invalidated. pub fn query_selector_iter(&self, selectors: DOMString) -> Fallible { // Step 1. - match SelectorParser::parse_author_origin_no_namespace(&selectors) { + let url = self.owner_doc().url(); + match SelectorParser::parse_author_origin_no_namespace(&selectors, &url) { // Step 2. Err(_) => Err(Error::Syntax), // Step 3. diff --git a/tests/unit/style/parsing/selectors.rs b/tests/unit/style/parsing/selectors.rs index 7aa77200732..cefcf6e69e2 100644 --- a/tests/unit/style/parsing/selectors.rs +++ b/tests/unit/style/parsing/selectors.rs @@ -4,6 +4,7 @@ use cssparser::{Parser, ParserInput, ToCss}; use selectors::parser::SelectorList; +use servo_url::ServoUrl; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::stylesheets::{Namespaces, Origin}; use style_traits::ParseError; @@ -14,10 +15,11 @@ fn parse_selector<'i, 't>( let mut ns = Namespaces::default(); ns.prefixes .insert("svg".into(), style::Namespace::new(ns!(svg))); + let dummy_url = ServoUrl::parse("about:blank").unwrap(); let parser = SelectorParser { stylesheet_origin: Origin::UserAgent, namespaces: &ns, - url_data: None, + url_data: &dummy_url, }; SelectorList::parse(&parser, input) } diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index c89a401bbc0..46d83fca317 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -8,6 +8,7 @@ use euclid::Size2D; use selectors::parser::{AncestorHashes, Selector}; use servo_arc::Arc; use servo_atoms::Atom; +use servo_url::ServoUrl; use style::context::QuirksMode; use style::media_queries::{Device, MediaType}; use style::properties::{longhands, Importance}; @@ -24,6 +25,7 @@ use style::thread_state::{self, ThreadState}; /// Helper method to get some Rules from selector strings. /// Each sublist of the result contains the Rules for one StyleRule. fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { + let dummy_url = &ServoUrl::parse("about:blank").unwrap(); let shared_lock = SharedRwLock::new(); ( css_selectors @@ -31,7 +33,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { .enumerate() .map(|(i, selectors)| { let selectors = - SelectorParser::parse_author_origin_no_namespace(selectors).unwrap(); + SelectorParser::parse_author_origin_no_namespace(selectors, dummy_url).unwrap(); let locked = Arc::new(shared_lock.wrap(StyleRule { selectors: selectors, @@ -64,10 +66,11 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { } fn parse_selectors(selectors: &[&str]) -> Vec> { + let dummy_url = &ServoUrl::parse("about:blank").unwrap(); selectors .iter() .map(|x| { - SelectorParser::parse_author_origin_no_namespace(x) + SelectorParser::parse_author_origin_no_namespace(x, dummy_url) .unwrap() .0 .into_iter() From 6d887b96bc5d609b6cd74e25fe75537809a10223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:41:27 +0200 Subject: [PATCH 07/81] style: Unexpose :-moz-locale-dir and :-moz-lwtheme* from content Behind a pref for now. Given these selectors do nothing on non-chrome documents (they just don't match) it seems worth trying. A cursory search seems to indicate they're not used for UA detection or something like that (or at least I haven't found such an usage). Differential Revision: https://phabricator.services.mozilla.com/D130736 --- components/style/gecko/non_ts_pseudo_class_list.rs | 6 +++--- components/style/gecko/selector_parser.rs | 8 +++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index 317cbfb83fe..14a74d3e570 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -90,9 +90,9 @@ macro_rules! apply_non_ts_list { ("-moz-use-shadow-tree-root", MozUseShadowTreeRoot, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-is-html", MozIsHTML, _, _), ("-moz-placeholder", MozPlaceholder, _, _), - ("-moz-lwtheme", MozLWTheme, _, _), - ("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, _), - ("-moz-lwtheme-darktext", MozLWThemeDarkText, _, _), + ("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), ("-moz-window-inactive", MozWindowInactive, _, _), ] } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 751a38d8465..f13df17a986 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -127,7 +127,7 @@ impl NonTSPseudoClass { ([$(($css:expr, $name:ident, $state:tt, $flags:tt),)*]) => { match *self { $(NonTSPseudoClass::$name => check_flag!($flags),)* - NonTSPseudoClass::MozLocaleDir(_) | + NonTSPseudoClass::MozLocaleDir(_) => check_flag!(PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), NonTSPseudoClass::Lang(_) | NonTSPseudoClass::Dir(_) => false, } @@ -145,6 +145,12 @@ impl NonTSPseudoClass { if let NonTSPseudoClass::MozSubmitInvalid = *self { return static_prefs::pref!("layout.css.moz-submit-invalid.enabled"); } + if matches!(*self, Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText) { + return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled"); + } + if let NonTSPseudoClass::MozLocaleDir(..) = *self { + return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled"); + } !self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME) } From 063d736837b500877eb7bd94fc5b948efa28749f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:42:25 +0200 Subject: [PATCH 08/81] style: Remove some prefs for pseudo-classes that we have shipped/unshipped successfully And remove code for :-moz-submit-invalid completely. Differential Revision: https://phabricator.services.mozilla.com/D130737 --- components/style/element_state.rs | 2 -- components/style/gecko/non_ts_pseudo_class_list.rs | 3 +-- components/style/gecko/selector_parser.rs | 6 ------ components/style/gecko/wrapper.rs | 1 - 4 files changed, 1 insertion(+), 11 deletions(-) diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 774e3936d2a..930c459b830 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -80,8 +80,6 @@ bitflags! { const IN_READWRITE_STATE = 1 << 25; /// const IN_DEFAULT_STATE = 1 << 26; - /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-submit-invalid - const IN_MOZ_SUBMITINVALID_STATE = 1 << 27; /// Non-standard & undocumented. const IN_OPTIMUM_STATE = 1 << 28; /// Non-standard & undocumented. diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index 14a74d3e570..c92f7ae8db2 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -37,7 +37,7 @@ macro_rules! apply_non_ts_list { ("any-link", AnyLink, IN_VISITED_OR_UNVISITED_STATE, _), ("visited", Visited, IN_VISITED_STATE, _), ("active", Active, IN_ACTIVE_STATE, _), - ("autofill", Autofill, IN_AUTOFILL_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("autofill", Autofill, IN_AUTOFILL_STATE, _), ("checked", Checked, IN_CHECKED_STATE, _), ("defined", Defined, IN_DEFINED_STATE, _), ("disabled", Disabled, IN_DISABLED_STATE, _), @@ -76,7 +76,6 @@ macro_rules! apply_non_ts_list { ("placeholder-shown", PlaceholderShown, IN_PLACEHOLDER_SHOWN_STATE, _), ("read-only", ReadOnly, IN_READONLY_STATE, _), ("read-write", ReadWrite, IN_READWRITE_STATE, _), - ("-moz-submit-invalid", MozSubmitInvalid, IN_MOZ_SUBMITINVALID_STATE, _), ("user-valid", UserValid, IN_MOZ_UI_VALID_STATE, _), ("user-invalid", UserInvalid, IN_MOZ_UI_INVALID_STATE, _), ("-moz-meter-optimum", MozMeterOptimum, IN_OPTIMUM_STATE, _), diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index f13df17a986..47b2ce5e412 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -139,12 +139,6 @@ impl NonTSPseudoClass { /// Returns whether the pseudo-class is enabled in content sheets. #[inline] fn is_enabled_in_content(&self) -> bool { - if let NonTSPseudoClass::Autofill = *self { - return static_prefs::pref!("layout.css.autofill.enabled"); - } - if let NonTSPseudoClass::MozSubmitInvalid = *self { - return static_prefs::pref!("layout.css.moz-submit-invalid.enabled"); - } if matches!(*self, Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText) { return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled"); } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index d2a86caf100..e9c67eac554 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1958,7 +1958,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::InRange | NonTSPseudoClass::OutOfRange | NonTSPseudoClass::Default | - NonTSPseudoClass::MozSubmitInvalid | NonTSPseudoClass::UserValid | NonTSPseudoClass::UserInvalid | NonTSPseudoClass::MozMeterOptimum | From 53b657e97bfead75fe9d3a75fa9e990e8d70451c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:43:21 +0200 Subject: [PATCH 09/81] style: Add a foreground color to attention selection In some platforms (like macOS, windows dark mode, android, and some gtk themes) the foreground selection color might be `currentcolor`, and that doesn't generally guarantee enough contrast with the attention background. Remove HeadlessLookAndFeelGTK's handling of this color since it's useless (always overridden by prefs in all.js) Differential Revision: https://phabricator.services.mozilla.com/D130617 --- 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 fe22266ab23..89762777f67 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -232,9 +232,11 @@ pub enum Color { #[repr(u8)] pub enum SystemColor { #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectBackgroundDisabled, - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - TextSelectBackgroundAttention, + TextSelectDisabledBackground, + #[css(skip)] + TextSelectAttentionBackground, + #[css(skip)] + TextSelectAttentionForeground, #[css(skip)] TextHighlightBackground, #[css(skip)] From 82c06738814578e1b4803a775b7f9f90dc586e34 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Wed, 31 May 2023 11:44:35 +0200 Subject: [PATCH 10/81] style: [css-lists] Style system changes to support 'reversed()' Differential Revision: https://phabricator.services.mozilla.com/D129955 --- .../properties/longhands/counters.mako.rs | 4 +- components/style/values/computed/counters.rs | 10 ++- components/style/values/computed/mod.rs | 2 +- components/style/values/generics/counters.rs | 83 ++++++++++++++++--- components/style/values/specified/counters.rs | 63 +++++++++++--- components/style/values/specified/mod.rs | 8 +- 6 files changed, 141 insertions(+), 29 deletions(-) diff --git a/components/style/properties/longhands/counters.mako.rs b/components/style/properties/longhands/counters.mako.rs index db035568c71..2ad0f72cd4a 100644 --- a/components/style/properties/longhands/counters.mako.rs +++ b/components/style/properties/longhands/counters.mako.rs @@ -29,7 +29,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "counter-reset", - "CounterSetOrReset", + "CounterReset", engines="gecko servo-2013", initial_value="Default::default()", animation_value_type="discrete", @@ -39,7 +39,7 @@ ${helpers.predefined_type( ${helpers.predefined_type( "counter-set", - "CounterSetOrReset", + "CounterSet", engines="gecko", initial_value="Default::default()", animation_value_type="discrete", diff --git a/components/style/values/computed/counters.rs b/components/style/values/computed/counters.rs index 1ae46c772ab..fd5e915c4a8 100644 --- a/components/style/values/computed/counters.rs +++ b/components/style/values/computed/counters.rs @@ -7,13 +7,17 @@ use crate::values::computed::image::Image; use crate::values::generics::counters as generics; use crate::values::generics::counters::CounterIncrement as GenericCounterIncrement; -use crate::values::generics::counters::CounterSetOrReset as GenericCounterSetOrReset; +use crate::values::generics::counters::CounterReset as GenericCounterReset; +use crate::values::generics::counters::CounterSet as GenericCounterSet; /// A computed value for the `counter-increment` property. pub type CounterIncrement = GenericCounterIncrement; -/// A computed value for the `counter-set` and `counter-reset` properties. -pub type CounterSetOrReset = GenericCounterSetOrReset; +/// A computed value for the `counter-reset` property. +pub type CounterReset = GenericCounterReset; + +/// A computed value for the `counter-set` property. +pub type CounterSet = GenericCounterSet; /// A computed value for the `content` property. pub type Content = generics::GenericContent; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 955a1b93877..25103b34772 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -52,7 +52,7 @@ pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, Scro pub use self::box_::{TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; pub use self::column::ColumnCount; -pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset}; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; pub use self::easing::TimingFunction; pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs index 2a8f70c8f44..e5656faea56 100644 --- a/components/style/values/generics/counters.rs +++ b/components/style/values/generics/counters.rs @@ -12,6 +12,8 @@ use crate::values::generics::CounterStyle; use crate::values::specified::Attr; use crate::values::CustomIdent; use std::ops::Deref; +use std::fmt::{self, Write}; +use style_traits::{CssWriter, ToCss}; /// A name / value pair for counters. #[derive( @@ -21,7 +23,6 @@ use std::ops::Deref; PartialEq, SpecifiedValueInfo, ToComputedValue, - ToCss, ToResolvedValue, ToShmem, )] @@ -31,9 +32,35 @@ pub struct GenericCounterPair { pub name: CustomIdent, /// The value of the counter / increment / etc. pub value: Integer, + /// If true, then this represents `reversed(name)`. + /// NOTE: It can only be true on `counter-reset` values. + pub is_reversed: bool, } pub use self::GenericCounterPair as CounterPair; +impl ToCss for CounterPair +where + Integer: ToCss + PartialEq, +{ + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_reversed { + dest.write_str("reversed(")?; + } + self.name.to_css(dest)?; + if self.is_reversed { + dest.write_str(")")?; + if self.value == i32::min_value() { + return Ok(()); + } + } + dest.write_str(" ")?; + self.value.to_css(dest) + } +} + /// A generic value for the `counter-increment` property. #[derive( Clone, @@ -48,7 +75,7 @@ pub use self::GenericCounterPair as CounterPair; ToShmem, )] #[repr(transparent)] -pub struct GenericCounterIncrement(pub GenericCounters); +pub struct GenericCounterIncrement(#[css(field_bound)] pub GenericCounters); pub use self::GenericCounterIncrement as CounterIncrement; impl CounterIncrement { @@ -68,7 +95,7 @@ impl Deref for CounterIncrement { } } -/// A generic value for the `counter-set` and `counter-reset` properties. +/// A generic value for the `counter-set` property. #[derive( Clone, Debug, @@ -82,18 +109,52 @@ impl Deref for CounterIncrement { ToShmem, )] #[repr(transparent)] -pub struct GenericCounterSetOrReset(pub GenericCounters); -pub use self::GenericCounterSetOrReset as CounterSetOrReset; +pub struct GenericCounterSet(#[css(field_bound)] pub GenericCounters); +pub use self::GenericCounterSet as CounterSet; -impl CounterSetOrReset { - /// Returns a new value for `counter-set` / `counter-reset`. +impl CounterSet { + /// Returns a new value for `counter-set`. #[inline] pub fn new(counters: Vec>) -> Self { - CounterSetOrReset(Counters(counters.into())) + CounterSet(Counters(counters.into())) } } -impl Deref for CounterSetOrReset { +impl Deref for CounterSet { + type Target = [CounterPair]; + + #[inline] + fn deref(&self) -> &Self::Target { + &(self.0).0 + } +} + +/// A generic value for the `counter-reset` property. +#[derive( + Clone, + Debug, + Default, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +pub struct GenericCounterReset(#[css(field_bound)] pub GenericCounters); +pub use self::GenericCounterReset as CounterReset; + +impl CounterReset { + /// Returns a new value for `counter-reset`. + #[inline] + pub fn new(counters: Vec>) -> Self { + CounterReset(Counters(counters.into())) + } +} + +impl Deref for CounterReset { type Target = [CounterPair]; #[inline] @@ -119,7 +180,9 @@ impl Deref for CounterSetOrReset { )] #[repr(transparent)] pub struct GenericCounters( - #[css(iterable, if_empty = "none")] crate::OwnedSlice>, + #[css(field_bound)] + #[css(iterable, if_empty = "none")] + crate::OwnedSlice>, ); pub use self::GenericCounters as Counters; diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index aa442549fa5..c9507ce8383 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -21,6 +21,18 @@ use cssparser::{Parser, Token}; use selectors::parser::SelectorParseErrorKind; use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind}; +#[derive(PartialEq)] +enum CounterType { Increment, Set, Reset, } + +impl CounterType { + fn default_value(&self) -> i32 { + match *self { + Self::Increment => 1, + Self::Reset | Self::Set => 0, + } + } +} + /// A specified value for the `counter-increment` property. pub type CounterIncrement = generics::GenericCounterIncrement; @@ -29,26 +41,38 @@ impl Parse for CounterIncrement { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Ok(Self::new(parse_counters(context, input, 1)?)) + Ok(Self::new(parse_counters(context, input, CounterType::Increment)?)) } } -/// A specified value for the `counter-set` and `counter-reset` properties. -pub type CounterSetOrReset = generics::GenericCounterSetOrReset; +/// A specified value for the `counter-set` property. +pub type CounterSet = generics::GenericCounterSet; -impl Parse for CounterSetOrReset { +impl Parse for CounterSet { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Ok(Self::new(parse_counters(context, input, 0)?)) + Ok(Self::new(parse_counters(context, input, CounterType::Set)?)) + } +} + +/// A specified value for the `counter-reset` property. +pub type CounterReset = generics::GenericCounterReset; + +impl Parse for CounterReset { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(Self::new(parse_counters(context, input, CounterType::Reset)?)) } } fn parse_counters<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, - default_value: i32, + counter_type: CounterType, ) -> Result>, ParseError<'i>> { if input .try_parse(|input| input.expect_ident_matching("none")) @@ -60,8 +84,14 @@ fn parse_counters<'i, 't>( let mut counters = Vec::new(); loop { let location = input.current_source_location(); - let name = match input.next() { - Ok(&Token::Ident(ref ident)) => CustomIdent::from_ident(location, ident, &["none"])?, + let (name, is_reversed) = match input.next() { + Ok(&Token::Ident(ref ident)) => (CustomIdent::from_ident(location, ident, &["none"])?, false), + Ok(&Token::Function(ref name)) if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => { + input.parse_nested_block(|input| { + let location = input.current_source_location(); + Ok((CustomIdent::from_ident(location, input.expect_ident()?, &["none"])?, true)) + })? + } Ok(t) => { let t = t.clone(); return Err(location.new_unexpected_token_error(t)); @@ -69,10 +99,19 @@ fn parse_counters<'i, 't>( Err(_) => break, }; - let value = input - .try_parse(|input| Integer::parse(context, input)) - .unwrap_or(Integer::new(default_value)); - counters.push(CounterPair { name, value }); + let value = match input.try_parse(|input| Integer::parse(context, input)) { + Ok(start) => + if start.value == i32::min_value() { + // The spec says that values must be clamped to the valid range, + // and we reserve i32::min_value() as an internal magic value. + // https://drafts.csswg.org/css-lists/#auto-numbering + Integer::new(i32::min_value() + 1) + } else { + start + }, + _ => Integer::new(if is_reversed { i32::min_value() } else { counter_type.default_value() }), + }; + counters.push(CounterPair { name, value, is_reversed }); } if !counters.is_empty() { diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 64bc063960e..4aeee691566 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -44,7 +44,7 @@ pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, Scro pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; pub use self::column::ColumnCount; -pub use self::counters::{Content, ContentItem, CounterIncrement, CounterSetOrReset}; +pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; pub use self::easing::TimingFunction; pub use self::effects::{BoxShadow, Filter, SimpleShadow}; pub use self::flex::FlexBasis; @@ -577,6 +577,12 @@ impl One for Integer { } } +impl PartialEq for Integer { + fn eq(&self, value: &i32) -> bool { + self.value() == *value + } +} + impl Integer { /// Trivially constructs a new `Integer` value. pub fn new(val: CSSInteger) -> Self { From 71c04d1d3cf24a29a3c7c697981be81ddf2ff069 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Wed, 31 May 2023 11:45:33 +0200 Subject: [PATCH 11/81] style: [css-lists] Remove the internal '-moz-list-reversed' CSS property that is no longer needed Differential Revision: https://phabricator.services.mozilla.com/D129957 --- components/style/properties/data.py | 1 - .../style/properties/longhands/list.mako.rs | 11 --------- components/style/values/computed/list.rs | 1 - components/style/values/computed/mod.rs | 1 - components/style/values/specified/list.rs | 24 ------------------- components/style/values/specified/mod.rs | 1 - 6 files changed, 39 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 49157860fbb..019899ecde1 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -486,7 +486,6 @@ class Longhand(Property): "MasonryAutoFlow", "MozForceBrokenImageIcon", "text::MozControlCharacterVisibility", - "MozListReversed", "MathDepth", "MozScriptMinSize", "MozScriptSizeMultiplier", diff --git a/components/style/properties/longhands/list.mako.rs b/components/style/properties/longhands/list.mako.rs index 99d848d9db6..dcaa1bcac29 100644 --- a/components/style/properties/longhands/list.mako.rs +++ b/components/style/properties/longhands/list.mako.rs @@ -84,14 +84,3 @@ ${helpers.predefined_type( boxed=True, spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-image-region)", )} - -${helpers.predefined_type( - "-moz-list-reversed", - "MozListReversed", - "computed::MozListReversed::False", - engines="gecko", - animation_value_type="discrete", - enabled_in="ua", - spec="Internal implementation detail for
    ", - servo_restyle_damage="rebuild_and_reflow", -)} diff --git a/components/style/values/computed/list.rs b/components/style/values/computed/list.rs index 2ae3776041b..3e5d1eb220d 100644 --- a/components/style/values/computed/list.rs +++ b/components/style/values/computed/list.rs @@ -6,7 +6,6 @@ #[cfg(feature = "gecko")] pub use crate::values::specified::list::ListStyleType; -pub use crate::values::specified::list::MozListReversed; pub use crate::values::specified::list::Quotes; impl Quotes { diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 25103b34772..9d775b45cd4 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -69,7 +69,6 @@ pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size}; pub use self::length::{NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto}; #[cfg(feature = "gecko")] pub use self::list::ListStyleType; -pub use self::list::MozListReversed; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; diff --git a/components/style/values/specified/list.rs b/components/style/values/specified/list.rs index 14004e65cc8..ff21eb5115e 100644 --- a/components/style/values/specified/list.rs +++ b/components/style/values/specified/list.rs @@ -199,27 +199,3 @@ impl Parse for Quotes { } } } - -/// Specified and computed `-moz-list-reversed` property (for UA sheets only). -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, -)] -#[repr(u8)] -pub enum MozListReversed { - /// the initial value - False, - /// exclusively used for
      in our html.css UA sheet - True, -} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 4aeee691566..8923a868a7c 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -66,7 +66,6 @@ pub use self::length::{ }; #[cfg(feature = "gecko")] pub use self::list::ListStyleType; -pub use self::list::MozListReversed; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; From c232cd49b4904598c39664ee9d44af8534ff8420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 31 May 2023 11:49:24 +0200 Subject: [PATCH 12/81] style: Use preferred color scheme when forcing colors with system colors (except windows HCM) This causes (among other things) pages to be dark when using regular windows system colors and forcing colors to "always", which is nice. Differential Revision: https://phabricator.services.mozilla.com/D131165 --- components/style/gecko/media_queries.rs | 20 +++++++++++++++----- components/style/properties/cascade.rs | 4 ++-- components/style/values/specified/color.rs | 15 +++------------ 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 7bab0b002d1..9c10c69609d 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -14,7 +14,8 @@ use crate::media_queries::MediaType; use crate::properties::ComputedValues; use crate::string_cache::Atom; use crate::values::computed::font::GenericFontFamily; -use crate::values::computed::Length; +use crate::values::computed::{Length, ColorScheme}; +use crate::values::specified::color::SystemColor; use crate::values::specified::font::FONT_MEDIUM_PX; use crate::values::{CustomIdent, KeyframesName}; use app_units::{Au, AU_PER_PX}; @@ -387,19 +388,28 @@ impl Device { self.pref_sheet_prefs().mUseDocumentColors } + /// Computes a system color and returns it as an nscolor. + pub(crate) fn system_nscolor(&self, system_color: SystemColor, color_scheme: &ColorScheme) -> u32 { + unsafe { + bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) + } + } + /// Returns the default background color. /// /// 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) + pub fn default_background_color(&self) -> RGBA { + let normal = ColorScheme::normal(); + convert_nscolor_to_rgba(self.system_nscolor(SystemColor::Canvas, &normal)) } /// Returns the default foreground color. /// /// 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) + pub fn default_color(&self) -> RGBA { + let normal = ColorScheme::normal(); + convert_nscolor_to_rgba(self.system_nscolor(SystemColor::Canvastext, &normal)) } /// Returns the current effective text zoom. diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index fe5bae33fe8..0834da93e89 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_for_forced_colors(); + let mut color = context.builder.device.default_background_color(); 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_for_forced_colors(); + let color = context.builder.device.default_color(); declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color( specified::ColorPropertyValue(color.into()), )) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 89762777f67..c2f5df4bf32 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -5,8 +5,6 @@ //! Specified color values. use super::AllowQuirks; -#[cfg(feature = "gecko")] -use crate::gecko_bindings::structs::nscolor; use crate::parser::{Parse, ParserContext}; use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue}; use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto}; @@ -456,18 +454,17 @@ impl SystemColor { #[inline] fn compute(&self, cx: &Context) -> ComputedColor { use crate::gecko_bindings::bindings; + use crate::gecko::values::convert_nscolor_to_rgba; // 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) - }; + let color = cx.device().system_nscolor(*self, &style_color_scheme); if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { return ComputedColor::currentcolor(); } - convert_nscolor_to_computedcolor(color) + ComputedColor::rgba(convert_nscolor_to_rgba(color)) } } @@ -741,12 +738,6 @@ impl Color { } } -#[cfg(feature = "gecko")] -fn convert_nscolor_to_computedcolor(color: nscolor) -> ComputedColor { - use crate::gecko::values::convert_nscolor_to_rgba; - ComputedColor::rgba(convert_nscolor_to_rgba(color)) -} - impl Color { /// Converts this Color into a ComputedColor. /// From 7797a19c70a9eedca0607ca0611dc3775d1d26de Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 2 Jun 2023 01:22:40 +0200 Subject: [PATCH 13/81] Further changes required by Servo --- components/style/servo/media_queries.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index d575b7c2df7..19134eff089 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_for_forced_colors(&self) -> RGBA { + pub fn default_background_color(&self) -> RGBA { RGBA::new(255, 255, 255, 255) } /// Returns the default foreground color. - pub fn default_color_for_forced_colors(&self) -> RGBA { + pub fn default_color(&self) -> RGBA { RGBA::new(0, 0, 0, 255) } From 71ec52f140785086ac8c619b306fc60990465079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 2 Jun 2023 01:45:30 +0200 Subject: [PATCH 14/81] style: Partially back out bug 1105364 Apparently using transparent borders and outlines is a common accessibility technique to make those visible in HCM. Bug 1740924 comment 9 seemed to indicate we were the only browser rendering those browsers, but I just confirmed that Edge at least does show them. Keep respecting system colors as that's per spec. Differential Revision: https://phabricator.services.mozilla.com/D131412 --- components/style/properties/cascade.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 0834da93e89..3921a4034ed 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -465,17 +465,20 @@ fn tweak_when_ignoring_colors( } }, _ => { - // We honor transparent and system colors more generally for all - // colors. + // We honor system colors more generally for all colors. // - // NOTE(emilio): This doesn't handle caret-color and - // accent-color because those use a slightly different syntax - // ( | auto for example). That's probably fine though, as - // using a system color for caret-color doesn't make sense (using - // currentColor is fine), and we ignore accent-color in - // high-contrast-mode anyways. + // We used to honor transparent but that causes accessibility + // regressions like bug 1740924. + // + // NOTE(emilio): This doesn't handle caret-color and accent-color + // because those use a slightly different syntax ( | auto for + // example). + // + // That's probably fine though, as using a system color for + // caret-color doesn't make sense (using currentColor is fine), and + // we ignore accent-color in high-contrast-mode anyways. if let Some(color) = declaration.color_value() { - if color.is_system() || alpha_channel(color, context) == 0 { + if color.is_system() { return; } } From 33ad82c3da6e2aabca811dae30ee3f5b6a05abf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 2 Jun 2023 01:58:40 +0200 Subject: [PATCH 15/81] style: Make custom properties that are IACVT guaranteed-invalid This effectively backs out bug 1623396. See: https://github.com/w3c/csswg-drafts/pull/6006 https://drafts.csswg.org/css-variables/#guaranteed-invalid-value And related discussion. Matches Chrome stable as per https://groups.google.com/a/chromium.org/g/blink-dev/c/0xrbzYe_vxU/m/7bsL76n9CgAJ Depends on D116459 Differential Revision: https://phabricator.services.mozilla.com/D116460 --- components/style/custom_properties.rs | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 96328d9a51a..641a421bd5c 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -597,8 +597,7 @@ impl<'a> CustomPropertiesBuilder<'a> { match result { Ok(new_value) => new_value, Err(..) => { - // Don't touch the map, this has the same effect as - // making it compute to the inherited one. + map.remove(name); return; }, } @@ -672,8 +671,7 @@ impl<'a> CustomPropertiesBuilder<'a> { None => return self.inherited.cloned(), }; if self.may_have_cycles { - let inherited = self.inherited.as_ref().map(|m| &***m); - substitute_all(&mut map, inherited, self.device); + substitute_all(&mut map, self.device); } map.shrink_to_fit(); Some(Arc::new(map)) @@ -686,7 +684,6 @@ impl<'a> CustomPropertiesBuilder<'a> { /// It does cycle dependencies removal at the same time as substitution. fn substitute_all( custom_properties_map: &mut CustomPropertiesMap, - inherited: Option<&CustomPropertiesMap>, device: &Device, ) { // The cycle dependencies removal in this function is a variant @@ -724,10 +721,7 @@ fn substitute_all( /// all unfinished strong connected components. stack: SmallVec<[usize; 5]>, map: &'a mut CustomPropertiesMap, - /// The inherited variables. We may need to restore some if we fail - /// substitution. - inherited: Option<&'a CustomPropertiesMap>, - /// to resolve the environment to substitute `env()` variables. + /// To resolve the environment to substitute `env()` variables. device: &'a Device, } @@ -869,16 +863,8 @@ fn substitute_all( context.map.insert(name, computed_value); }, Err(..) => { - // This is invalid, reset it to the unset (inherited) value. - let inherited = context.inherited.and_then(|m| m.get(&name)).cloned(); - match inherited { - Some(computed_value) => { - context.map.insert(name, computed_value); - }, - None => { - context.map.remove(&name); - }, - }; + // This is invalid, reset it to the guaranteed-invalid value. + context.map.remove(&name); }, } @@ -896,7 +882,6 @@ fn substitute_all( stack: SmallVec::new(), var_info: SmallVec::new(), map: custom_properties_map, - inherited, device, }; traverse(name, &mut context); From a0617bff0de3a4de70cc15123d0d6ffd6047f98f Mon Sep 17 00:00:00 2001 From: Ting-Yu Lin Date: Fri, 2 Jun 2023 02:26:03 +0200 Subject: [PATCH 16/81] style: Run rustfmt on servo/components/style and servo/ports/geckolib This patch is generated by running `cargo +nightly fmt` under `servo/components/style/` and `servo/ports/geckolib` against mozilla-central https://hg.mozilla.org/mozilla-central/rev/b193f2e7a6a5d1f042c957ea4acd5c89bf210512 My nightly version is: 1.58.0-nightly (c9c4b5d72 2021-11-17) Manually remove the redundant braces in author_styles.rs to fix a warning. Differential Revision: https://phabricator.services.mozilla.com/D131556 --- components/style/applicable_declarations.rs | 2 +- components/style/author_styles.rs | 13 +-- components/style/counter_style/mod.rs | 12 +-- components/style/custom_properties.rs | 13 +-- components/style/data.rs | 11 ++- components/style/error_reporting.rs | 2 +- components/style/gecko/media_features.rs | 48 +++++++-- components/style/gecko/media_queries.rs | 18 ++-- components/style/gecko/selector_parser.rs | 15 +-- components/style/gecko/wrapper.rs | 17 ++-- .../media_queries/media_feature_expression.rs | 16 +-- components/style/rule_collector.rs | 33 +++++-- components/style/selector_map.rs | 39 +++++--- components/style/selector_parser.rs | 3 +- components/style/shared_lock.rs | 10 +- components/style/style_adjuster.rs | 46 +++++++-- components/style/stylesheets/import_rule.rs | 3 +- components/style/stylesheets/loader.rs | 2 +- components/style/stylesheets/mod.rs | 4 +- components/style/stylesheets/rule_parser.rs | 23 ++--- .../style/stylesheets/rules_iterator.rs | 5 +- components/style/stylesheets/stylesheet.rs | 8 +- components/style/stylist.rs | 97 +++++++++---------- components/style/values/animated/transform.rs | 9 +- components/style/values/computed/color.rs | 2 +- components/style/values/computed/font.rs | 60 ++++++------ components/style/values/computed/image.rs | 1 - components/style/values/computed/length.rs | 6 +- components/style/values/computed/mod.rs | 2 +- components/style/values/computed/text.rs | 4 +- components/style/values/computed/transform.rs | 2 +- components/style/values/generics/counters.rs | 12 +-- components/style/values/generics/image.rs | 7 +- components/style/values/generics/mod.rs | 2 +- components/style/values/generics/transform.rs | 2 +- components/style/values/generics/ui.rs | 10 +- components/style/values/mod.rs | 4 +- components/style/values/resolved/color.rs | 4 +- components/style/values/resolved/counters.rs | 17 ++-- components/style/values/specified/box.rs | 10 +- components/style/values/specified/calc.rs | 36 ++++--- components/style/values/specified/color.rs | 10 +- components/style/values/specified/counters.rs | 50 +++++++--- components/style/values/specified/font.rs | 46 +++++---- components/style/values/specified/gecko.rs | 4 +- components/style/values/specified/image.rs | 39 ++++++-- components/style/values/specified/mod.rs | 6 +- components/style/values/specified/svg_path.rs | 18 ++-- components/style/values/specified/text.rs | 19 ++-- components/style/values/specified/ui.rs | 4 +- 50 files changed, 486 insertions(+), 340 deletions(-) diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index db0f7fce99f..7f2d3928d2d 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -6,8 +6,8 @@ use crate::properties::PropertyDeclarationBlock; use crate::rule_tree::{CascadeLevel, StyleSource}; -use crate::stylesheets::layer_rule::LayerOrder; use crate::shared_lock::Locked; +use crate::stylesheets::layer_rule::LayerOrder; use servo_arc::Arc; use smallvec::SmallVec; diff --git a/components/style/author_styles.rs b/components/style/author_styles.rs index dfd33711ed2..83bd0ad2854 100644 --- a/components/style/author_styles.rs +++ b/components/style/author_styles.rs @@ -10,10 +10,10 @@ use crate::dom::TElement; use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; use crate::invalidation::media_queries::ToMediaListKey; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylist::Stylist; use crate::stylesheet_set::AuthorStylesheetSet; use crate::stylesheets::StylesheetInDocument; use crate::stylist::CascadeData; +use crate::stylist::Stylist; use servo_arc::Arc; /// A set of author stylesheets and their computed representation, such as the @@ -32,9 +32,7 @@ where } lazy_static! { - static ref EMPTY_CASCADE_DATA: Arc = { - Arc::new_leaked(CascadeData::new()) - }; + static ref EMPTY_CASCADE_DATA: Arc = Arc::new_leaked(CascadeData::new()); } impl AuthorStyles @@ -55,11 +53,8 @@ where /// TODO(emilio): Need a host element and a snapshot map to do invalidation /// properly. #[inline] - pub fn flush( - &mut self, - stylist: &mut Stylist, - guard: &SharedRwLockReadGuard, - ) where + pub fn flush(&mut self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard) + where E: TElement, S: ToMediaListKey, { diff --git a/components/style/counter_style/mod.rs b/components/style/counter_style/mod.rs index e470e53ae1f..daad5145e1c 100644 --- a/components/style/counter_style/mod.rs +++ b/components/style/counter_style/mod.rs @@ -55,12 +55,12 @@ pub fn parse_counter_style_name<'i, 't>( } fn is_valid_name_definition(ident: &CustomIdent) -> bool { - ident.0 != atom!("decimal") - && ident.0 != atom!("disc") - && ident.0 != atom!("circle") - && ident.0 != atom!("square") - && ident.0 != atom!("disclosure-closed") - && ident.0 != atom!("disclosure-open") + ident.0 != atom!("decimal") && + ident.0 != atom!("disc") && + ident.0 != atom!("circle") && + ident.0 != atom!("square") && + ident.0 != atom!("disclosure-closed") && + ident.0 != atom!("disclosure-open") } /// Parse the prelude of an @counter-style rule diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 641a421bd5c..931bf32a00c 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -93,7 +93,9 @@ impl CssEnvironment { if !device.is_chrome_document() { return None; } - let var = CHROME_ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?; + let var = CHROME_ENVIRONMENT_VARIABLES + .iter() + .find(|var| var.name == *name)?; Some((var.evaluator)(device)) } } @@ -682,10 +684,7 @@ impl<'a> CustomPropertiesBuilder<'a> { /// (meaning we should use the inherited value). /// /// It does cycle dependencies removal at the same time as substitution. -fn substitute_all( - custom_properties_map: &mut CustomPropertiesMap, - device: &Device, -) { +fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Device) { // The cycle dependencies removal in this function is a variant // of Tarjan's algorithm. It is mostly based on the pseudo-code // listed in @@ -999,7 +998,9 @@ fn substitute_block<'i>( let first_token_type = input .next_including_whitespace_and_comments() .ok() - .map_or_else(TokenSerializationType::nothing, |t| t.serialization_type()); + .map_or_else(TokenSerializationType::nothing, |t| { + t.serialization_type() + }); input.reset(&after_comma); let mut position = (after_comma.position(), first_token_type); last_token_type = substitute_block( diff --git a/components/style/data.rs b/components/style/data.rs index f486b24dc58..758adf57a68 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -179,13 +179,20 @@ impl ElementStyles { pub fn uses_viewport_units(&self) -> bool { use crate::computed_value_flags::ComputedValueFlags; - if self.primary().flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { + if self + .primary() + .flags + .intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) + { return true; } for pseudo_style in self.pseudos.as_array() { if let Some(ref pseudo_style) = pseudo_style { - if pseudo_style.flags.intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) { + if pseudo_style + .flags + .intersects(ComputedValueFlags::USES_VIEWPORT_UNITS) + { return true; } } diff --git a/components/style/error_reporting.rs b/components/style/error_reporting.rs index eebb0901309..752f68b5bdc 100644 --- a/components/style/error_reporting.rs +++ b/components/style/error_reporting.rs @@ -214,7 +214,7 @@ impl<'a> fmt::Display for ContextualParseError<'a> { ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f), ContextualParseError::NeverMatchingHostSelector(ref selector) => { write!(f, ":host selector is not featureless: {}", selector) - } + }, } } } diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 40a34095d6f..d314500b88f 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -556,7 +556,8 @@ fn eval_moz_windows_non_native_menus( query_value: Option, _: Option, ) -> bool { - let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") { + let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") + { 0 => false, 1 => true, _ => { @@ -873,18 +874,39 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ 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-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-menubar-drag"), MenuBarDrag), lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme), lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme), lnf_int_feature!(atom!("-moz-mac-big-sur-theme"), MacBigSurTheme), lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL), - lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar), + lnf_int_feature!( + atom!("-moz-windows-accent-color-in-titlebar"), + WindowsAccentColorInTitlebar + ), lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor), lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic), lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass), @@ -893,8 +915,14 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ lnf_int_feature!(atom!("-moz-gtk-csd-minimize-button"), GTKCSDMinimizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton), lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton), - lnf_int_feature!(atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement), + lnf_int_feature!( + atom!("-moz-gtk-csd-reversed-placement"), + GTKCSDReversedPlacement + ), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), bool_pref_feature!(atom!("-moz-proton"), "browser.proton.enabled"), - bool_pref_feature!(atom!("-moz-proton-places-tooltip"), "browser.proton.places-tooltip.enabled"), + bool_pref_feature!( + atom!("-moz-proton-places-tooltip"), + "browser.proton.places-tooltip.enabled" + ), ]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 9c10c69609d..19ed6444f07 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -14,7 +14,7 @@ use crate::media_queries::MediaType; use crate::properties::ComputedValues; use crate::string_cache::Atom; use crate::values::computed::font::GenericFontFamily; -use crate::values::computed::{Length, ColorScheme}; +use crate::values::computed::{ColorScheme, Length}; use crate::values::specified::color::SystemColor; use crate::values::specified::font::FONT_MEDIUM_PX; use crate::values::{CustomIdent, KeyframesName}; @@ -389,10 +389,12 @@ impl Device { } /// Computes a system color and returns it as an nscolor. - pub(crate) fn system_nscolor(&self, system_color: SystemColor, color_scheme: &ColorScheme) -> u32 { - unsafe { - bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) - } + pub(crate) fn system_nscolor( + &self, + system_color: SystemColor, + color_scheme: &ColorScheme, + ) -> u32 { + unsafe { bindings::Gecko_ComputeSystemColor(system_color, self.document(), color_scheme) } } /// Returns the default background color. @@ -460,14 +462,16 @@ impl Device { /// Returns the gtk titlebar radius in CSS pixels. pub fn titlebar_radius(&self) -> f32 { unsafe { - bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) as f32 + bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) + as f32 } } /// Returns the gtk menu radius in CSS pixels. pub fn menu_radius(&self) -> f32 { unsafe { - bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) as f32 + bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) + as f32 } } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 47b2ce5e412..35c5a5c454d 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -139,7 +139,10 @@ impl NonTSPseudoClass { /// Returns whether the pseudo-class is enabled in content sheets. #[inline] fn is_enabled_in_content(&self) -> bool { - if matches!(*self, Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText) { + if matches!( + *self, + Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText + ) { return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled"); } if let NonTSPseudoClass::MozLocaleDir(..) = *self { @@ -174,12 +177,10 @@ impl NonTSPseudoClass { /// Get the document state flag associated with a pseudo-class, if any. pub fn document_state_flag(&self) -> DocumentState { match *self { - NonTSPseudoClass::MozLocaleDir(ref dir) => { - match dir.as_horizontal_direction() { - Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, - Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, - None => DocumentState::empty(), - } + NonTSPseudoClass::MozLocaleDir(ref dir) => match dir.as_horizontal_direction() { + Some(HorizontalDirection::Ltr) => DocumentState::LTR_LOCALE, + Some(HorizontalDirection::Rtl) => DocumentState::RTL_LOCALE, + None => DocumentState::empty(), }, NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index e9c67eac554..c41d2ff1e85 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -27,6 +27,7 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::bindings::Gecko_ElementHasAnimations; use crate::gecko_bindings::bindings::Gecko_ElementHasCSSAnimations; use crate::gecko_bindings::bindings::Gecko_ElementHasCSSTransitions; +use crate::gecko_bindings::bindings::Gecko_ElementState; use crate::gecko_bindings::bindings::Gecko_GetActiveLinkAttrDeclarationBlock; use crate::gecko_bindings::bindings::Gecko_GetAnimationEffectCount; use crate::gecko_bindings::bindings::Gecko_GetAnimationRule; @@ -39,7 +40,6 @@ use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; use crate::gecko_bindings::bindings::Gecko_MatchLang; use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; -use crate::gecko_bindings::bindings::Gecko_ElementState; use crate::gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::nsChangeHint; @@ -1196,7 +1196,11 @@ impl<'le> TElement for GeckoElement<'le> { where F: FnMut(&AtomIdent), { - for attr in self.non_mapped_attrs().iter().chain(self.mapped_attrs().iter()) { + for attr in self + .non_mapped_attrs() + .iter() + .chain(self.mapped_attrs().iter()) + { let is_nodeinfo = attr.mName.mBits & 1 != 0; unsafe { let atom = if is_nodeinfo { @@ -1972,9 +1976,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::MozAutofillPreview | - NonTSPseudoClass::Dir(..) => { - self.state().intersects(pseudo_class.state_flag()) - }, + NonTSPseudoClass::Dir(..) => self.state().intersects(pseudo_class.state_flag()), NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Link => { self.is_link() && context.visited_handling().matches_unvisited() @@ -2033,7 +2035,10 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozWindowInactive => { let state_bit = pseudo_class.document_state_flag(); if state_bit.is_empty() { - debug_assert!(matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), "Only moz-locale-dir should ever return an empty state"); + debug_assert!( + matches!(pseudo_class, NonTSPseudoClass::MozLocaleDir(..)), + "Only moz-locale-dir should ever return an empty state" + ); return false; } if context.extra_data.document_state.intersects(state_bit) { diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs index 1658439da21..80827af401c 100644 --- a/components/style/media_queries/media_feature_expression.rs +++ b/components/style/media_queries/media_feature_expression.rs @@ -211,13 +211,15 @@ fn consume_operation_or_colon(input: &mut Parser) -> Result, () // // TODO(emilio): Maybe we should ignore comments as well? // https://github.com/w3c/csswg-drafts/issues/6248 - let parsed_equal = input.try_parse(|i| { - let t = i.next_including_whitespace().map_err(|_| ())?; - if !matches!(t, Token::Delim('=')) { - return Err(()) - } - Ok(()) - }).is_ok(); + let parsed_equal = input + .try_parse(|i| { + let t = i.next_including_whitespace().map_err(|_| ())?; + if !matches!(t, Token::Delim('=')) { + return Err(()); + } + Ok(()) + }) + .is_ok(); if !parsed_equal { return Ok(Some(operator)); diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 65f55ea887a..021c0176416 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -147,8 +147,9 @@ where self.context.current_host = host.map(|e| e.opaque()); f(self); if start != self.rules.len() { - self.rules[start..] - .sort_unstable_by_key(|block| (block.layer_order, block.specificity, block.source_order())); + self.rules[start..].sort_unstable_by_key(|block| { + (block.layer_order, block.specificity, block.source_order()) + }); } self.context.current_host = old_host; self.in_sort_scope = false; @@ -214,7 +215,12 @@ where } #[inline] - fn collect_rules_in_list(&mut self, part_rules: &[Rule], cascade_level: CascadeLevel, cascade_data: &CascadeData) { + 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, @@ -228,7 +234,12 @@ where } #[inline] - fn collect_rules_in_map(&mut self, map: &SelectorMap, cascade_level: CascadeLevel, cascade_data: &CascadeData) { + 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, @@ -390,10 +401,10 @@ where let outer_shadow = inner_shadow_host.containing_shadow(); let cascade_data = match outer_shadow { Some(shadow) => shadow.style_data(), - None => Some(self - .stylist - .cascade_data() - .borrow_for_origin(Origin::Author) + None => Some( + self.stylist + .cascade_data() + .borrow_for_origin(Origin::Author), ), }; @@ -406,7 +417,11 @@ where 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); + collector.collect_rules_in_list( + part_rules, + cascade_level, + cascade_data, + ); } } }); diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index a2b5d6bb6b6..ff7e93ae9c0 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, CascadeData}; +use crate::stylist::{CascadeData, Rule}; use crate::{Atom, LocalName, Namespace, WeakAtom}; use fallible::FallibleVec; use hashglobe::FailedAllocationError; @@ -313,7 +313,8 @@ impl SelectorMap { context, flags_setter, ) { - matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); + matching_rules + .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); } } } @@ -366,14 +367,11 @@ impl SelectorMap { &mut self.local_name_hash }; if name != lower_name { - hash - .try_entry(lower_name.clone())? + hash.try_entry(lower_name.clone())? .or_insert_with(SmallVec::new) .try_push($entry.clone())?; } - hash - .try_entry(name.clone())? - .or_insert_with(SmallVec::new) + hash.try_entry(name.clone())?.or_insert_with(SmallVec::new) }, Bucket::Namespace(url) => self .namespace_hash @@ -387,7 +385,11 @@ impl SelectorMap { let bucket = { let mut disjoint_buckets = SmallVec::new(); - let bucket = find_bucket(entry.selector(), &mut disjoint_buckets, self.bucket_attributes); + 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: @@ -619,11 +621,16 @@ fn specific_bucket_for<'a>( 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::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 { + Component::AttributeInNoNamespaceExists { + ref local_name, + ref local_name_lower, + } if bucket_attributes => Bucket::Attribute { name: local_name, lower_name: local_name_lower, }, @@ -656,8 +663,12 @@ 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, bucket_attributes), - Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes), + 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, bucket_attributes) diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index 8e0e3412d4b..d28e333da04 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -76,8 +76,7 @@ impl<'a> SelectorParser<'a> { /// Whether we're parsing selectors in a stylesheet that has chrome /// privilege. pub fn chrome_rules_enabled(&self) -> bool { - self.url_data.chrome_rules_enabled() || - self.stylesheet_origin == Origin::User + self.url_data.chrome_rules_enabled() || self.stylesheet_origin == Origin::User } } diff --git a/components/style/shared_lock.rs b/components/style/shared_lock.rs index d524f0c6cdf..55708a9f7bb 100644 --- a/components/style/shared_lock.rs +++ b/components/style/shared_lock.rs @@ -97,7 +97,10 @@ impl SharedRwLock { #[cfg(feature = "gecko")] #[inline] fn ptr(&self) -> *const SomethingZeroSizedButTyped { - self.cell.as_ref().map(|cell| cell.as_ptr() as *const _).unwrap_or(ptr::null()) + 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. @@ -154,7 +157,10 @@ 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()) + self.0 + .as_ref() + .map(|r| &**r as *const _) + .unwrap_or(ptr::null()) } } diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index 31028f98571..d12864155dd 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -760,9 +760,9 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { /// the same font as its fallback ('list-style-type') in case it fails to load. #[cfg(feature = "gecko")] fn adjust_for_marker_pseudo(&mut self) { + use crate::values::computed::counters::Content; use crate::values::computed::font::{FontFamily, FontSynthesis}; use crate::values::computed::text::{LetterSpacing, WordSpacing}; - use crate::values::computed::counters::{Content}; let is_legacy_marker = self.style.pseudo.map_or(false, |p| p.is_marker()) && self.style.get_list().clone_list_style_type().is_bullet() && @@ -770,21 +770,49 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { if !is_legacy_marker { return; } - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) { - self.style.mutate_font().set_font_family(FontFamily::moz_bullet().clone()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_FAMILY) + { + self.style + .mutate_font() + .set_font_family(FontFamily::moz_bullet().clone()); // FIXME(mats): We can remove this if support for font-synthesis is added to @font-face rules. // Then we can add it to the @font-face rule in html.css instead. // https://github.com/w3c/csswg-drafts/issues/6081 - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS) { - self.style.mutate_font().set_font_synthesis(FontSynthesis::none()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_FONT_SYNTHESIS) + { + self.style + .mutate_font() + .set_font_synthesis(FontSynthesis::none()); } } - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) { - self.style.mutate_inherited_text().set_letter_spacing(LetterSpacing::normal()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_LETTER_SPACING) + { + self.style + .mutate_inherited_text() + .set_letter_spacing(LetterSpacing::normal()); } - if !self.style.flags.get().contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) { - self.style.mutate_inherited_text().set_word_spacing(WordSpacing::normal()); + if !self + .style + .flags + .get() + .contains(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_WORD_SPACING) + { + self.style + .mutate_inherited_text() + .set_word_spacing(WordSpacing::normal()); } } diff --git a/components/style/stylesheets/import_rule.rs b/components/style/stylesheets/import_rule.rs index 7352dea0b9e..ccf0bb11e51 100644 --- a/components/style/stylesheets/import_rule.rs +++ b/components/style/stylesheets/import_rule.rs @@ -10,8 +10,8 @@ use crate::media_queries::MediaList; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; -use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::stylesheets::layer_rule::LayerName; +use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::values::CssUrl; use cssparser::SourceLocation; use std::fmt::{self, Write}; @@ -131,7 +131,6 @@ pub struct ImportLayer { pub name: Option, } - impl ToCss for ImportLayer { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where diff --git a/components/style/stylesheets/loader.rs b/components/style/stylesheets/loader.rs index c145bafdc04..45f7d22a55c 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, ImportLayer}; +use crate::stylesheets::import_rule::{ImportLayer, ImportRule}; use crate::values::CssUrl; use cssparser::SourceLocation; use servo_arc::Arc; diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index 762bca6033f..f7098e0c151 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -509,11 +509,11 @@ impl DeepCloneWithLock for CssRule { CssRule::Layer(Arc::new( lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) - } + }, CssRule::ScrollTimeline(ref arc) => { let rule = arc.read_with(guard); CssRule::ScrollTimeline(Arc::new(lock.wrap(rule.clone()))) - } + }, } } } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 995ee801e40..12ec7226bfc 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -289,7 +289,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, layer) => { let loader = self @@ -613,15 +613,13 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { 0 | 1 => names.into_iter().next(), _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), }; - Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap( - LayerRule { - kind: LayerRuleKind::Block { - name, - rules: self.parse_nested_rules(input, CssRuleType::Layer), - }, - source_location: start.source_location(), + 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. @@ -650,7 +648,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { &mut self, prelude: AtRulePrelude, start: &ParserState, - ) -> Result { + ) -> Result { Ok(match prelude { AtRulePrelude::Layer(names) => { if names.is_empty() { @@ -687,7 +685,10 @@ fn check_for_useless_selector( } if found_host && found_non_host { let location = input.current_source_location(); - context.log_css_error(location, ContextualParseError::NeverMatchingHostSelector(selector.to_css_string())); + context.log_css_error( + location, + ContextualParseError::NeverMatchingHostSelector(selector.to_css_string()), + ); continue 'selector_loop; } } diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index 32851dd2cca..0cbc7327441 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -111,7 +111,7 @@ where LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()), LayerRuleKind::Statement { .. } => None, } - } + }, } } } @@ -323,7 +323,8 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { guard: &'a SharedRwLockReadGuard<'b>, rule: &'a CssRule, ) -> Self { - let children = RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); + let children = + RulesIterator::::children(rule, device, quirks_mode, guard, &mut false); EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) } } diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index 1b6b59bac78..e63d8187365 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -591,9 +591,11 @@ impl Clone for Stylesheet { // Make a deep clone of the media, using the new lock. let media = self.media.read_with(&guard).clone(); let media = Arc::new(lock.wrap(media)); - let contents = Arc::new(self - .contents - .deep_clone_with_lock(&lock, &guard, &DeepCloneParams)); + let contents = Arc::new(self.contents.deep_clone_with_lock( + &lock, + &guard, + &DeepCloneParams, + )); Stylesheet { contents, diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 0663ea440d9..a5538aa1189 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -11,7 +11,9 @@ use crate::element_state::{DocumentState, ElementState}; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::{ServoStyleSetSizes, StyleRuleInclusion}; use crate::invalidation::element::invalidation_map::InvalidationMap; -use crate::invalidation::media_queries::{EffectiveMediaQueryResults, MediaListKey, ToMediaListKey}; +use crate::invalidation::media_queries::{ + EffectiveMediaQueryResults, MediaListKey, ToMediaListKey, +}; use crate::invalidation::stylesheets::RuleChangeKind; use crate::media_queries::Device; use crate::properties::{self, CascadeMode, ComputedValues}; @@ -25,15 +27,18 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards}; use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind}; use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher}; use crate::stylesheets::keyframes_rule::KeyframesAnimation; -use crate::stylesheets::layer_rule::{LayerName, LayerId, LayerOrder}; +use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; -use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents}; #[cfg(feature = "gecko")] use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule}; -use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter, EffectiveRulesIterator}; +use crate::stylesheets::{ + CssRule, EffectiveRulesIterator, Origin, OriginSet, PerOrigin, PerOriginIter, +}; +use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; use crate::thread_state::{self, ThreadState}; use crate::{Atom, LocalName, Namespace, WeakAtom}; use fallible::FallibleVec; +use fxhash::FxHashMap; use hashglobe::FailedAllocationError; use malloc_size_of::MallocSizeOf; #[cfg(feature = "gecko")] @@ -48,11 +53,10 @@ use selectors::NthIndexCache; use servo_arc::{Arc, ArcBorrow}; use smallbitvec::SmallBitVec; use smallvec::SmallVec; +use std::hash::{Hash, Hasher}; use std::sync::Mutex; use std::{mem, ops}; -use std::hash::{Hash, Hasher}; use style_traits::viewport::ViewportConstraints; -use fxhash::FxHashMap; /// The type of the stylesheets that the stylist contains. #[cfg(feature = "servo")] @@ -93,7 +97,7 @@ struct CascadeDataCacheKey { unsafe impl Send for CascadeDataCacheKey {} unsafe impl Sync for CascadeDataCacheKey {} -trait CascadeDataCacheEntry : Sized { +trait CascadeDataCacheEntry: Sized { /// Returns a reference to the cascade data. fn cascade_data(&self) -> &CascadeData; /// Rebuilds the cascade data for the new stylesheet collection. The @@ -121,7 +125,9 @@ where Entry: CascadeDataCacheEntry, { fn new() -> Self { - Self { entries: Default::default() } + Self { + entries: Default::default(), + } } fn len(&self) -> usize { @@ -165,15 +171,9 @@ where match self.entries.entry(key) { HashMapEntry::Vacant(e) => { debug!("> Picking the slow path (not in the cache)"); - new_entry = Entry::rebuild( - device, - quirks_mode, - collection, - guard, - old_entry, - )?; + new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?; e.insert(new_entry.clone()); - } + }, HashMapEntry::Occupied(mut e) => { // Avoid reusing our old entry (this can happen if we get // invalidated due to CSSOM mutations and our old stylesheet @@ -192,15 +192,9 @@ where } debug!("> Picking the slow path due to same entry as old"); - new_entry = Entry::rebuild( - device, - quirks_mode, - collection, - guard, - old_entry, - )?; + new_entry = Entry::rebuild(device, quirks_mode, collection, guard, old_entry)?; e.insert(new_entry.clone()); - } + }, } Ok(Some(new_entry)) @@ -272,7 +266,7 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { _old: &Self, ) -> Result, FailedAllocationError> where - S: StylesheetInDocument + PartialEq + 'static + S: StylesheetInDocument + PartialEq + 'static, { // TODO: Maybe we should support incremental rebuilds, though they seem // uncommon and rebuild() doesn't deal with @@ -604,13 +598,8 @@ impl Stylist { where S: StylesheetInDocument + PartialEq + 'static, { - self.author_data_cache.lookup( - &self.device, - self.quirks_mode, - collection, - guard, - old_data, - ) + self.author_data_cache + .lookup(&self.device, self.quirks_mode, collection, guard, old_data) } /// Iterate over the extra data in origin order. @@ -2114,7 +2103,11 @@ impl CascadeData { } fn compute_layer_order(&mut self) { - debug_assert_ne!(self.layers.len(), 0, "There should be at least the root layer!"); + debug_assert_ne!( + self.layers.len(), + 0, + "There should be at least the root layer!" + ); if self.layers.len() == 1 { return; // Nothing to do } @@ -2131,7 +2124,10 @@ impl CascadeData { order: &mut LayerOrder, ) { for child in parent.children.iter() { - debug_assert!(parent.id < *child, "Children are always registered after parents"); + 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]; @@ -2323,7 +2319,10 @@ impl CascadeData { let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? { + match self + .animations + .try_entry(keyframes_rule.name.as_atom().clone())? + { Entry::Vacant(e) => { e.insert(KeyframesAnimation::from_keyframes( &keyframes_rule.keyframes, @@ -2338,9 +2337,8 @@ impl CascadeData { // // TODO(emilio): This will need to be harder for // layers. - let needs_insert = - keyframes_rule.vendor_prefix.is_none() || - e.get().vendor_prefix.is_some(); + let needs_insert = keyframes_rule.vendor_prefix.is_none() || + e.get().vendor_prefix.is_some(); if needs_insert { e.insert(KeyframesAnimation::from_keyframes( &keyframes_rule.keyframes, @@ -2401,13 +2399,8 @@ impl CascadeData { } let mut effective = false; - let children = EffectiveRulesIterator::children( - rule, - device, - quirks_mode, - guard, - &mut effective, - ); + let children = + EffectiveRulesIterator::children(rule, device, quirks_mode, guard, &mut effective); if !effective { continue; @@ -2426,7 +2419,8 @@ impl CascadeData { let mut parent = layer.clone(); parent.0.pop(); - *data.layer_id + *data + .layer_id .get_mut(&parent) .expect("Parent layers should be registered before child layers") } else { @@ -2489,7 +2483,6 @@ impl CascadeData { &mut layer_names_to_pop, ); } - }, CssRule::Media(ref lock) => { if rebuild_kind.should_rebuild_invalidation() { @@ -2509,7 +2502,7 @@ impl CascadeData { &mut current_layer, &mut layer_names_to_pop, ); - } + }, LayerRuleKind::Statement { ref names } => { for name in &**names { let mut pushed = 0; @@ -2525,7 +2518,7 @@ impl CascadeData { current_layer.0.pop(); } } - } + }, } }, // We don't care about any other rule. @@ -2609,7 +2602,9 @@ impl CascadeData { let effective_now = stylesheet.is_effective_for_device(device, guard); - let effective_then = self.effective_media_query_results.was_effective(stylesheet.contents()); + let effective_then = self + .effective_media_query_results + .was_effective(stylesheet.contents()); if effective_now != effective_then { debug!( @@ -2739,7 +2734,7 @@ impl CascadeDataCacheEntry for CascadeData { old: &Self, ) -> Result, FailedAllocationError> where - S: StylesheetInDocument + PartialEq + 'static + S: StylesheetInDocument + PartialEq + 'static, { debug_assert!(collection.dirty(), "We surely need to do something?"); // If we're doing a full rebuild anyways, don't bother cloning the data. diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs index 598bf9b59a7..542b4c31023 100644 --- a/components/style/values/animated/transform.rs +++ b/components/style/values/animated/transform.rs @@ -1122,7 +1122,7 @@ impl Animate for ComputedTransformOperation { 1. } else { -1. / perspective_z - } + }, )) }; Ok(TransformOperation::Perspective(used_value)) @@ -1204,10 +1204,9 @@ impl ComputeSquaredDistance for ComputedTransformOperation { ( &TransformOperation::Perspective(ref fd), &TransformOperation::Perspective(ref td), - ) => { - fd.infinity_or(|l| l.px()) - .compute_squared_distance(&td.infinity_or(|l| l.px())) - }, + ) => fd + .infinity_or(|l| l.px()) + .compute_squared_distance(&td.infinity_or(|l| l.px())), (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) | (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => { // FIXME(emilio): Is this right? Why interpolating this with diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 36cd1db4a42..6b970181d85 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -6,7 +6,7 @@ use crate::values::animated::color::RGBA as AnimatedRGBA; use crate::values::animated::ToAnimatedValue; -use crate::values::generics::color::{GenericColor, GenericColorOrAuto, GenericCaretColor}; +use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto}; use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index 18fbe8c6fd8..ab6755d0951 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -202,7 +202,6 @@ macro_rules! static_font_family { }; } - impl FontFamily { #[inline] /// Get default font family as `serif` which is a generic font-family @@ -213,10 +212,13 @@ impl FontFamily { /// Returns the font family for `-moz-bullet-font`. #[cfg(feature = "gecko")] pub(crate) fn moz_bullet() -> &'static Self { - static_font_family!(MOZ_BULLET, SingleFontFamily::FamilyName(FamilyName { - name: atom!("-moz-bullet-font"), - syntax: FontFamilyNameSyntax::Identifiers, - })); + static_font_family!( + MOZ_BULLET, + SingleFontFamily::FamilyName(FamilyName { + name: atom!("-moz-bullet-font"), + syntax: FontFamilyNameSyntax::Identifiers, + }) + ); &*MOZ_BULLET } @@ -226,10 +228,12 @@ impl FontFamily { pub fn for_system_font(name: &str) -> Self { Self { families: FontFamilyList { - list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName(FamilyName { - name: Atom::from(name), - syntax: FontFamilyNameSyntax::Identifiers, - }))), + list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName( + FamilyName { + name: Atom::from(name), + syntax: FontFamilyNameSyntax::Identifiers, + }, + ))), fallback: GenericFontFamily::None, }, is_system_font: true, @@ -240,8 +244,11 @@ impl FontFamily { pub fn generic(generic: GenericFontFamily) -> &'static Self { macro_rules! generic_font_family { ($ident:ident, $family:ident) => { - static_font_family!($ident, SingleFontFamily::Generic(GenericFontFamily::$family)) - } + static_font_family!( + $ident, + SingleFontFamily::Generic(GenericFontFamily::$family) + ) + }; } generic_font_family!(SERIF, Serif); @@ -257,7 +264,7 @@ impl FontFamily { GenericFontFamily::None => { debug_assert!(false, "Bogus caller!"); &*SERIF - } + }, GenericFontFamily::Serif => &*SERIF, GenericFontFamily::SansSerif => &*SANS_SERIF, GenericFontFamily::Monospace => &*MONOSPACE, @@ -444,24 +451,21 @@ impl GenericFontFamily { /// 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 { + pub(crate) fn valid_for_user_font_prioritization(self) -> bool { match self { - Self::None | - Self::Fantasy | - Self::Cursive | - Self::SystemUi | - Self::MozEmoji => false, + Self::None | Self::Fantasy | Self::Cursive | Self::SystemUi | Self::MozEmoji => false, - Self::Serif | - Self::SansSerif | - Self::Monospace => true, + 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> { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) { return Ok(SingleFontFamily::FamilyName(FamilyName { name: Atom::from(&*value), @@ -603,13 +607,11 @@ impl FontFamilyList { /// 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.valid_for_user_font_prioritization(), - _ => false, - } - }); + 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.valid_for_user_font_prioritization(), + _ => false, + }); if let Some(0) = index_of_first_generic { return; // Already first diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index a0cb8f3fe45..980017b2822 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -84,7 +84,6 @@ impl ToComputedValue for specified::ImageSet { let mut selected_resolution = items[0].resolution.dppx(); for (i, item) in items.iter().enumerate() { - // If the MIME type is not supported, we discard the ImageSetItem if item.has_mime_type && !context.device().is_supported_mime_type(&item.mime_type) { continue; diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index f455da5f676..b8ff80587e6 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -37,7 +37,9 @@ impl ToComputedValue for specified::NoCalcLength { length.to_computed_value(context, FontBaseSize::CurrentStyle) }, specified::NoCalcLength::ViewportPercentage(length) => { - context.builder.add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); + context + .builder + .add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); length.to_computed_value(context.viewport_size_for_viewport_unit_resolution()) }, specified::NoCalcLength::ServoCharacterWidth(length) => { @@ -191,7 +193,7 @@ impl Size { GenericSize::MaxContent | GenericSize::FitContent | GenericSize::MozAvailable | - GenericSize::FitContentFunction(_) => false + GenericSize::FitContentFunction(_) => false, } } } diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 9d775b45cd4..ad7663804be 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, ImageRendering}; +pub use self::image::{Gradient, Image, ImageRendering, LineDirection, MozImageRect}; 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/computed/text.rs b/components/style/values/computed/text.rs index 629626b06b3..0920156108f 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -18,7 +18,9 @@ use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; -pub use crate::values::specified::text::{TextAlignLast, TextUnderlinePosition, MozControlCharacterVisibility}; +pub use crate::values::specified::text::{ + MozControlCharacterVisibility, TextAlignLast, TextUnderlinePosition, +}; pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; diff --git a/components/style/values/computed/transform.rs b/components/style/values/computed/transform.rs index 5eafa0cd627..d70349ee0fe 100644 --- a/components/style/values/computed/transform.rs +++ b/components/style/values/computed/transform.rs @@ -520,7 +520,7 @@ impl ToAnimatedZero for TransformOperation { Ok(generic::TransformOperation::Rotate(Angle::zero())) }, generic::TransformOperation::Perspective(_) => Ok( - generic::TransformOperation::Perspective(generic::PerspectiveFunction::None) + generic::TransformOperation::Perspective(generic::PerspectiveFunction::None), ), generic::TransformOperation::AccumulateMatrix { .. } | generic::TransformOperation::InterpolateMatrix { .. } => { diff --git a/components/style/values/generics/counters.rs b/components/style/values/generics/counters.rs index e5656faea56..2d9948cef4a 100644 --- a/components/style/values/generics/counters.rs +++ b/components/style/values/generics/counters.rs @@ -11,8 +11,8 @@ use crate::values::generics::CounterStyle; #[cfg(any(feature = "gecko", feature = "servo-layout-2020"))] use crate::values::specified::Attr; use crate::values::CustomIdent; -use std::ops::Deref; use std::fmt::{self, Write}; +use std::ops::Deref; use style_traits::{CssWriter, ToCss}; /// A name / value pair for counters. @@ -238,15 +238,7 @@ impl Content { /// Items for the `content` property. #[derive( - Clone, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, + Clone, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum GenericContentItem { diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index 014ae46db02..fa53dd6ac8b 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -131,9 +131,7 @@ pub struct GenericImageSet { } /// An optional percent and a cross fade image. -#[derive( - Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, -)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct GenericImageSetItem { /// ``. `` is converted to `Image::Url` at parse time. @@ -151,8 +149,7 @@ pub struct GenericImageSetItem { pub has_mime_type: bool, } -impl ToCss for GenericImageSetItem -{ +impl ToCss for GenericImageSetItem { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index c0cdf04b71d..b15d4f01887 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -106,7 +106,7 @@ impl CounterStyle { name == &atom!("square") || name == &atom!("disclosure-closed") || name == &atom!("disclosure-open") - } + }, _ => false, } } diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 604872ba5dc..816dde92e4f 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -733,7 +733,7 @@ where dest.write_char(' ')?; z.to_css(dest)?; dest.write_char(' ')?; - } + }, } angle.to_css(dest) }, diff --git a/components/style/values/generics/ui.rs b/components/style/values/generics/ui.rs index ff6aefdc63e..4d9515199ad 100644 --- a/components/style/values/generics/ui.rs +++ b/components/style/values/generics/ui.rs @@ -56,15 +56,7 @@ impl ToCss for Cursor { } /// A generic value for item of `image cursors`. -#[derive( - Clone, - Debug, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, -)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem)] #[repr(C)] pub struct GenericCursorImage { /// The url to parse images from. diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index b8e7f1f3b8b..626fc32ff18 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -547,9 +547,7 @@ impl Parse for TimelineOrKeyframesName { s, &["none"], )?)), - Token::QuotedString(ref s) => { - Ok(Self::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())), } } diff --git a/components/style/values/resolved/color.rs b/components/style/values/resolved/color.rs index dbf6375e5bf..ea912945c5a 100644 --- a/components/style/values/resolved/color.rs +++ b/components/style/values/resolved/color.rs @@ -40,6 +40,8 @@ impl ToResolvedValue for computed::CaretColor { #[inline] fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { - generics::CaretColor(generics::ColorOrAuto::Color(computed::Color::from_resolved_value(resolved))) + generics::CaretColor(generics::ColorOrAuto::Color( + computed::Color::from_resolved_value(resolved), + )) } } diff --git a/components/style/values/resolved/counters.rs b/components/style/values/resolved/counters.rs index cdaebcdea3e..e246dd567f9 100644 --- a/components/style/values/resolved/counters.rs +++ b/components/style/values/resolved/counters.rs @@ -31,19 +31,22 @@ impl ToResolvedValue for computed::Content { #[inline] fn to_resolved_value(self, context: &Context) -> Self { - let (is_pseudo, is_before_or_after, is_marker) = - match context.style.pseudo() { - Some(ref pseudo) => (true, pseudo.is_before_or_after(), pseudo.is_marker()), - None => (false, false, false) - }; + let (is_pseudo, is_before_or_after, is_marker) = match context.style.pseudo() { + Some(ref pseudo) => (true, pseudo.is_before_or_after(), pseudo.is_marker()), + None => (false, false, false), + }; match self { Self::Normal if is_before_or_after => Self::None, // For now, make `content: none` compute to `normal` for pseudos // other than ::before, ::after and ::marker, as we don't respect it. // https://github.com/w3c/csswg-drafts/issues/6124 // Ditto for non-pseudo elements if the pref is disabled. - Self::None if (is_pseudo && !is_before_or_after && !is_marker) || - (!is_pseudo && !allow_element_content_none()) => Self::Normal, + Self::None + if (is_pseudo && !is_before_or_after && !is_marker) || + (!is_pseudo && !allow_element_content_none()) => + { + Self::Normal + }, other => other, } } diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 7d5543cf7fc..67e90c0a8a2 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -1954,7 +1954,10 @@ impl BreakBetween { /// See https://drafts.csswg.org/css-break/#page-break-properties. #[cfg(feature = "gecko")] #[inline] - pub(crate) fn parse_legacy<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result> { + pub(crate) fn parse_legacy<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result> { let break_value = BreakBetween::parse(input)?; match break_value { BreakBetween::Always => Ok(BreakBetween::Page), @@ -2018,7 +2021,10 @@ impl BreakWithin { /// See https://drafts.csswg.org/css-break/#page-break-properties. #[cfg(feature = "gecko")] #[inline] - pub(crate) fn parse_legacy<'i>(_: &ParserContext, input: &mut Parser<'i, '_>) -> Result> { + pub(crate) fn parse_legacy<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result> { let break_value = BreakWithin::parse(input)?; match break_value { BreakWithin::Auto | BreakWithin::Avoid => Ok(break_value), diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index a043e4ea96e..d6997934554 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -380,43 +380,47 @@ impl CalcNode { Ok(Self::MinMax(arguments.into(), op)) }, - MathFunction::Sin | - MathFunction::Cos | - MathFunction::Tan => { + 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) - ), + 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!"); }, + _ => unsafe { + debug_unreachable!("We just checked!"); + }, }; Ok(Self::Leaf(Leaf::Number(number))) }, - MathFunction::Asin | - MathFunction::Acos | - MathFunction::Atan => { + 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) - ), + 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!"); }, + _ => unsafe { + debug_unreachable!("We just checked!"); + }, }; Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians)))) @@ -597,7 +601,9 @@ impl CalcNode { let function = match MathFunction::from_ident(&*name) { Ok(f) => f, - Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))), + Err(()) => { + return Err(location.new_unexpected_token_error(Token::Function(name.clone()))) + }, }; if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !trig_enabled() { diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index c2f5df4bf32..3629b17058f 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -453,13 +453,12 @@ pub enum SystemColor { impl SystemColor { #[inline] fn compute(&self, cx: &Context) -> ComputedColor { - use crate::gecko_bindings::bindings; use crate::gecko::values::convert_nscolor_to_rgba; + use crate::gecko_bindings::bindings; // 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 style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme(); let color = cx.device().system_nscolor(*self, &style_color_scheme); if color == bindings::NS_SAME_AS_FOREGROUND_COLOR { return ComputedColor::currentcolor(); @@ -932,7 +931,10 @@ impl ColorScheme { } impl Parse for ColorScheme { - fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + fn parse<'i, 't>( + _: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { let mut idents = vec![]; let mut bits = ColorSchemeFlags::empty(); diff --git a/components/style/values/specified/counters.rs b/components/style/values/specified/counters.rs index c9507ce8383..91fd7e77078 100644 --- a/components/style/values/specified/counters.rs +++ b/components/style/values/specified/counters.rs @@ -22,7 +22,11 @@ use selectors::parser::SelectorParseErrorKind; use style_traits::{KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind}; #[derive(PartialEq)] -enum CounterType { Increment, Set, Reset, } +enum CounterType { + Increment, + Set, + Reset, +} impl CounterType { fn default_value(&self) -> i32 { @@ -41,7 +45,11 @@ impl Parse for CounterIncrement { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Ok(Self::new(parse_counters(context, input, CounterType::Increment)?)) + Ok(Self::new(parse_counters( + context, + input, + CounterType::Increment, + )?)) } } @@ -65,7 +73,11 @@ impl Parse for CounterReset { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Ok(Self::new(parse_counters(context, input, CounterType::Reset)?)) + Ok(Self::new(parse_counters( + context, + input, + CounterType::Reset, + )?)) } } @@ -85,13 +97,20 @@ fn parse_counters<'i, 't>( loop { let location = input.current_source_location(); let (name, is_reversed) = match input.next() { - Ok(&Token::Ident(ref ident)) => (CustomIdent::from_ident(location, ident, &["none"])?, false), - Ok(&Token::Function(ref name)) if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => { + Ok(&Token::Ident(ref ident)) => { + (CustomIdent::from_ident(location, ident, &["none"])?, false) + }, + Ok(&Token::Function(ref name)) + if counter_type == CounterType::Reset && name.eq_ignore_ascii_case("reversed") => + { input.parse_nested_block(|input| { let location = input.current_source_location(); - Ok((CustomIdent::from_ident(location, input.expect_ident()?, &["none"])?, true)) + Ok(( + CustomIdent::from_ident(location, input.expect_ident()?, &["none"])?, + true, + )) })? - } + }, Ok(t) => { let t = t.clone(); return Err(location.new_unexpected_token_error(t)); @@ -100,7 +119,7 @@ fn parse_counters<'i, 't>( }; let value = match input.try_parse(|input| Integer::parse(context, input)) { - Ok(start) => + Ok(start) => { if start.value == i32::min_value() { // The spec says that values must be clamped to the valid range, // and we reserve i32::min_value() as an internal magic value. @@ -108,10 +127,19 @@ fn parse_counters<'i, 't>( Integer::new(i32::min_value() + 1) } else { start - }, - _ => Integer::new(if is_reversed { i32::min_value() } else { counter_type.default_value() }), + } + }, + _ => Integer::new(if is_reversed { + i32::min_value() + } else { + counter_type.default_value() + }), }; - counters.push(CounterPair { name, value, is_reversed }); + counters.push(CounterPair { + name, + value, + is_reversed, + }); } if !counters.is_empty() { diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index e78fec45d02..d11a2411a5d 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -8,16 +8,18 @@ use crate::context::QuirksMode; use crate::parser::{Parse, ParserContext}; use crate::values::computed::font::{FamilyName, FontFamilyList, FontStyleAngle, SingleFontFamily}; +use crate::values::computed::FontSizeAdjust as ComputedFontSizeAdjust; use crate::values::computed::{font as computed, Length, NonNegativeLength}; use crate::values::computed::{Angle as ComputedAngle, Percentage as ComputedPercentage}; use crate::values::computed::{CSSPixelLength, Context, ToComputedValue}; -use crate::values::computed::FontSizeAdjust as ComputedFontSizeAdjust; use crate::values::generics::font::VariationValue; -use crate::values::generics::font::{self as generics, FeatureTagValue, FontSettings, FontTag, GenericFontSizeAdjust}; +use crate::values::generics::font::{ + self as generics, FeatureTagValue, FontSettings, FontTag, GenericFontSizeAdjust, +}; use crate::values::generics::NonNegative; use crate::values::specified::length::{FontBaseSize, PX_PER_PT}; use crate::values::specified::{AllowQuirks, Angle, Integer, LengthPercentage}; -use crate::values::specified::{NoCalcLength, NonNegativeNumber, Number, NonNegativePercentage}; +use crate::values::specified::{NoCalcLength, NonNegativeNumber, NonNegativePercentage, Number}; use crate::values::CustomIdent; use crate::Atom; use cssparser::{Parser, Token}; @@ -406,7 +408,9 @@ impl ToComputedValue for FontStyle { /// /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop #[allow(missing_docs)] -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +#[derive( + Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, +)] #[repr(u8)] pub enum FontStretch { Stretch(NonNegativePercentage), @@ -510,7 +514,9 @@ impl ToComputedValue for FontStretch { } fn from_computed_value(computed: &Self::ComputedValue) -> Self { - FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative((computed.0).0))) + FontStretch::Stretch(NonNegativePercentage::from_computed_value(&NonNegative( + (computed.0).0, + ))) } } @@ -723,7 +729,8 @@ impl Parse for FontFamily { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - let values = input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?; + let values = + input.parse_comma_separated(|input| SingleFontFamily::parse(context, input))?; Ok(FontFamily::Values(FontFamilyList { #[cfg(feature = "gecko")] list: crate::ArcSlice::from_iter(values.into_iter()), @@ -755,9 +762,7 @@ impl Parse for FamilyName { } /// Preserve the readability of text when font fallback occurs -#[derive( - Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, -)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] #[allow(missing_docs)] pub enum FontSizeAdjust { Value(GenericFontSizeAdjust), @@ -804,7 +809,9 @@ impl Parse for FontSizeAdjust { } // Without a basis keyword, the number refers to the 'ex-height' metric. let value = NonNegativeNumber::parse(context, input)?; - Ok(FontSizeAdjust::Value(GenericFontSizeAdjust::ExHeight(value))) + Ok(FontSizeAdjust::Value(GenericFontSizeAdjust::ExHeight( + value, + ))) } } @@ -1985,14 +1992,7 @@ impl Parse for FontFeatureSettings { } #[derive( - Clone, - Copy, - Debug, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, + Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] /// Whether user agents are allowed to synthesize bold or oblique font faces /// when a font family lacks those faces, or a small-caps variant when this is @@ -2102,11 +2102,7 @@ impl ToCss for FontSynthesis { impl SpecifiedValueInfo for FontSynthesis { fn collect_completion_keywords(f: KeywordsCollectFn) { - f(&[ - "none", - "weight", - "style", - ]); + f(&["none", "weight", "style"]); if allow_font_synthesis_small_caps() { f(&["small-caps"]); } @@ -2319,7 +2315,9 @@ impl Parse for VariationValue { /// A metrics override value for a @font-face descriptor /// /// https://drafts.csswg.org/css-fonts/#font-metrics-override-desc -#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] +#[derive( + Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, +)] pub enum MetricsOverride { /// A non-negative `` of the computed font size Override(NonNegativePercentage), diff --git a/components/style/values/specified/gecko.rs b/components/style/values/specified/gecko.rs index 9b01cc2a6c4..e721add59cf 100644 --- a/components/style/values/specified/gecko.rs +++ b/components/style/values/specified/gecko.rs @@ -52,7 +52,9 @@ impl Parse for IntersectionObserverRootMargin { 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()))); + return Ok(IntersectionObserverRootMargin(Rect::all( + LengthPercentage::zero(), + ))); } let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?; Ok(IntersectionObserverRootMargin(rect)) diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index 095ce20f4c7..f027c69f03a 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -181,7 +181,13 @@ impl Parse for Image { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - Image::parse_with_cors_mode(context, input, CorsMode::None, /* allow_none = */ true, /* only_url = */ false) + Image::parse_with_cors_mode( + context, + input, + CorsMode::None, + /* allow_none = */ true, + /* only_url = */ false, + ) } } @@ -334,7 +340,10 @@ impl CrossFadeImage { cors_mode: CorsMode, ) -> Result> { if let Ok(image) = input.try_parse(|input| { - Image::parse_with_cors_mode(context, input, cors_mode, /* allow_none = */ false, /* only_url = */ false) + Image::parse_with_cors_mode( + context, input, cors_mode, /* allow_none = */ false, + /* only_url = */ false, + ) }) { return Ok(Self::Image(image)); } @@ -374,7 +383,9 @@ impl ImageSet { } } let items = input.parse_nested_block(|input| { - input.parse_comma_separated(|input| ImageSetItem::parse(context, input, cors_mode, only_url)) + input.parse_comma_separated(|input| { + ImageSetItem::parse(context, input, cors_mode, only_url) + }) })?; Ok(Self { selected_index: 0, @@ -386,9 +397,7 @@ impl ImageSet { impl ImageSetItem { fn parse_type<'i>(p: &mut Parser<'i, '_>) -> Result> { p.expect_function_matching("type")?; - p.parse_nested_block(|input| { - Ok(input.expect_string()?.as_ref().to_owned().into()) - }) + p.parse_nested_block(|input| Ok(input.expect_string()?.as_ref().to_owned().into())) } fn parse<'i, 't>( @@ -404,23 +413,33 @@ impl ImageSetItem { cors_mode, )), Err(..) => Image::parse_with_cors_mode( - context, input, cors_mode, /* allow_none = */ false, /* only_url = */ only_url + context, input, cors_mode, /* allow_none = */ false, + /* only_url = */ only_url, )?, }; - let mut resolution = input.try_parse(|input| Resolution::parse(context, input)).ok(); + let mut resolution = input + .try_parse(|input| Resolution::parse(context, input)) + .ok(); let mime_type = input.try_parse(Self::parse_type).ok(); // Try to parse resolution after type(). if mime_type.is_some() && resolution.is_none() { - resolution = input.try_parse(|input| Resolution::parse(context, input)).ok(); + resolution = input + .try_parse(|input| Resolution::parse(context, input)) + .ok(); } let resolution = resolution.unwrap_or(Resolution::X(1.0)); let has_mime_type = mime_type.is_some(); let mime_type = mime_type.unwrap_or_default(); - Ok(Self { image, resolution, has_mime_type, mime_type }) + Ok(Self { + image, + resolution, + has_mime_type, + mime_type, + }) } } diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 8923a868a7c..7cb34ca9a23 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, ImageRendering}; +pub use self::image::{Image, ImageRendering, MozImageRect}; pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth}; pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber}; pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; @@ -70,7 +70,7 @@ pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; pub use self::page::{Orientation, PageSize, PaperSize}; -pub use self::percentage::{Percentage, NonNegativePercentage}; +pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, @@ -83,9 +83,9 @@ pub use self::svg::{DProperty, MozContextProperties}; pub use self::svg::{SVGLength, SVGOpacity, SVGPaint}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; pub use self::svg_path::SVGPathData; +pub use self::text::RubyPosition; pub use self::text::TextAlignLast; pub use self::text::TextUnderlinePosition; -pub use self::text::RubyPosition; pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAlign}; pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; diff --git a/components/style/values/specified/svg_path.rs b/components/style/values/specified/svg_path.rs index a443a443326..b251ce900ce 100644 --- a/components/style/values/specified/svg_path.rs +++ b/components/style/values/specified/svg_path.rs @@ -85,7 +85,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_MOVETO_ABS), }); i = i + 2; - } + }, PATHSEG_LINETO_ABS | PATHSEG_LINETO_REL => { debug_assert!(i + 1 < path.len()); result.push(PathCommand::LineTo { @@ -93,7 +93,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_ABS), }); i = i + 2; - } + }, PATHSEG_CURVETO_CUBIC_ABS | PATHSEG_CURVETO_CUBIC_REL => { debug_assert!(i + 5 < path.len()); result.push(PathCommand::CurveTo { @@ -103,7 +103,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_ABS), }); i = i + 6; - } + }, PATHSEG_CURVETO_QUADRATIC_ABS | PATHSEG_CURVETO_QUADRATIC_REL => { debug_assert!(i + 3 < path.len()); result.push(PathCommand::QuadBezierCurveTo { @@ -112,7 +112,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_ABS), }); i = i + 4; - } + }, PATHSEG_ARC_ABS | PATHSEG_ARC_REL => { debug_assert!(i + 6 < path.len()); result.push(PathCommand::EllipticalArc { @@ -125,7 +125,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_ARC_ABS), }); i = i + 7; - } + }, PATHSEG_LINETO_HORIZONTAL_ABS | PATHSEG_LINETO_HORIZONTAL_REL => { debug_assert!(i < path.len()); result.push(PathCommand::HorizontalLineTo { @@ -133,7 +133,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_HORIZONTAL_ABS), }); i = i + 1; - } + }, PATHSEG_LINETO_VERTICAL_ABS | PATHSEG_LINETO_VERTICAL_REL => { debug_assert!(i < path.len()); result.push(PathCommand::VerticalLineTo { @@ -141,7 +141,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_LINETO_VERTICAL_ABS), }); i = i + 1; - } + }, PATHSEG_CURVETO_CUBIC_SMOOTH_ABS | PATHSEG_CURVETO_CUBIC_SMOOTH_REL => { debug_assert!(i + 3 < path.len()); result.push(PathCommand::SmoothCurveTo { @@ -150,7 +150,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_CUBIC_SMOOTH_ABS), }); i = i + 4; - } + }, PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS | PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL => { debug_assert!(i + 1 < path.len()); result.push(PathCommand::SmoothQuadBezierCurveTo { @@ -158,7 +158,7 @@ impl SVGPathData { absolute: IsAbsolute::new(seg_type == PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS), }); i = i + 2; - } + }, PATHSEG_UNKNOWN | _ => return Err(()), } } diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index ce652734060..893c5232c5e 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -1061,7 +1061,6 @@ impl Default for MozControlCharacterVisibility { } } - /// Values for the `line-break` property. #[repr(u8)] #[derive( @@ -1259,15 +1258,7 @@ impl ToCss for TextUnderlinePosition { /// Values for `ruby-position` property #[repr(u8)] #[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - PartialEq, - ToComputedValue, - ToResolvedValue, - ToShmem, + Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[allow(missing_docs)] pub enum RubyPosition { @@ -1283,7 +1274,9 @@ impl Parse for RubyPosition { input: &mut Parser<'i, 't>, ) -> Result> { // Parse alternate before - let alternate = input.try_parse(|i| i.expect_ident_matching("alternate")).is_ok(); + let alternate = input + .try_parse(|i| i.expect_ident_matching("alternate")) + .is_ok(); if alternate && input.is_exhausted() { return Ok(RubyPosition::AlternateOver); } @@ -1294,7 +1287,9 @@ impl Parse for RubyPosition { }; // Parse alternate after let alternate = alternate || - input.try_parse(|i| i.expect_ident_matching("alternate")).is_ok(); + input + .try_parse(|i| i.expect_ident_matching("alternate")) + .is_ok(); Ok(match (over, alternate) { (true, true) => RubyPosition::AlternateOver, diff --git a/components/style/values/specified/ui.rs b/components/style/values/specified/ui.rs index 4594cbfcc77..07463efe66a 100644 --- a/components/style/values/specified/ui.rs +++ b/components/style/values/specified/ui.rs @@ -11,7 +11,9 @@ use crate::values::specified::image::Image; use crate::values::specified::Number; use cssparser::Parser; use std::fmt::{self, Write}; -use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; +use style_traits::{ + CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss, +}; /// A specified value for the `cursor` property. pub type Cursor = generics::GenericCursor; From 2dbc89d1f5159bc8040c9857c65fca9361bb7ca2 Mon Sep 17 00:00:00 2001 From: Ting-Yu Lin Date: Fri, 2 Jun 2023 02:37:03 +0200 Subject: [PATCH 17/81] style: Support scrollbar-gutter in the style system This patch adds `scrollbar-gutter` property in CSS Overflow level 3 [1] to the style system. `devtools/shared/css/generated/properties-db.js` is generated by `./mach devtools-css-db`. [1] https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property Differential Revision: https://phabricator.services.mozilla.com/D131460 --- components/style/properties/data.py | 1 + .../style/properties/longhands/box.mako.rs | 10 +++ components/style/values/computed/box.rs | 2 +- components/style/values/computed/mod.rs | 2 +- components/style/values/specified/box.rs | 70 +++++++++++++++++++ components/style/values/specified/mod.rs | 2 +- 6 files changed, 84 insertions(+), 3 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 019899ecde1..abcffe68e95 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -505,6 +505,7 @@ class Longhand(Property): "RubyPosition", "SVGOpacity", "SVGPaintOrder", + "ScrollbarGutter", "ScrollSnapAlign", "ScrollSnapAxis", "ScrollSnapStrictness", diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 3c3532bea7a..2bfd4015334 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -706,3 +706,13 @@ ${helpers.predefined_type( animation_value_type="Integer", spec="https://drafts.csswg.org/css-overflow-3/#line-clamp", )} + +${helpers.predefined_type( + "scrollbar-gutter", + "ScrollbarGutter", + "computed::ScrollbarGutter::AUTO", + engines="gecko", + gecko_pref="layout.css.scrollbar-gutter.enabled", + animation_value_type="discrete", + spec="https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property", +)} diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index ce467343005..2364ac69045 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -15,7 +15,7 @@ pub use crate::values::specified::box_::{ AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, Clear as SpecifiedClear, Contain, Display, Float as SpecifiedFloat, Overflow, OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, - ScrollSnapType, TouchAction, TransitionProperty, WillChange, + ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, }; /// A computed value for the `vertical-align` property. diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index ad7663804be..9beab014ff5 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -47,7 +47,7 @@ pub use self::border::{BorderImageSlice, BorderImageWidth}; 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}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 67e90c0a8a2..1628b396730 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -2095,3 +2095,73 @@ impl Overflow { } } } + +bitflags! { + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[value_info(other_values = "auto,stable,both-edges")] + #[repr(C)] + /// Values for scrollbar-gutter: + /// + pub struct ScrollbarGutter: u8 { + /// `auto` variant. Just for convenience if there is no flag set. + const AUTO = 0; + /// `stable` variant. + const STABLE = 1 << 0; + /// `both-edges` variant. + const BOTH_EDGES = 1 << 1; + } +} + +impl ToCss for ScrollbarGutter { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_empty() { + return dest.write_str("auto"); + } + + debug_assert!( + self.contains(ScrollbarGutter::STABLE), + "We failed to parse the syntax!" + ); + dest.write_str("stable")?; + if self.contains(ScrollbarGutter::BOTH_EDGES) { + dest.write_str(" both-edges")?; + } + + Ok(()) + } +} + +impl Parse for ScrollbarGutter { + /// auto | stable && both-edges? + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { + return Ok(ScrollbarGutter::AUTO); + } + + let mut result = ScrollbarGutter::empty(); + while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { + let flag = match_ignore_ascii_case! { &ident, + "stable" => Some(ScrollbarGutter::STABLE), + "both-edges" => Some(ScrollbarGutter::BOTH_EDGES), + _ => None + }; + + match flag { + Some(flag) if !result.contains(flag) => result.insert(flag), + _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), + } + } + + if result.contains(ScrollbarGutter::STABLE) { + Ok(result) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 7cb34ca9a23..3c3c9b8d1ce 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -39,7 +39,7 @@ pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle 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}; +pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; From 88b82f569b8b5e778578af932e152c6436c3778d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 2 Jun 2023 02:39:05 +0200 Subject: [PATCH 18/81] style: Top layer elements that are display:contents should be display: block As per https://fullscreen.spec.whatwg.org/#new-stacking-layer: If its specified display property is contents, it computes to block. Differential Revision: https://phabricator.services.mozilla.com/D131585 --- components/style/style_adjuster.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/style/style_adjuster.rs b/components/style/style_adjuster.rs index d12864155dd..ca5bbe9bb00 100644 --- a/components/style/style_adjuster.rs +++ b/components/style/style_adjuster.rs @@ -136,9 +136,15 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> { /// computed to 'absolute' if the element is in a top layer. /// fn adjust_for_top_layer(&mut self) { - if !self.style.is_absolutely_positioned() && self.style.in_top_layer() { + if !self.style.in_top_layer() { + return; + } + if !self.style.is_absolutely_positioned() { self.style.mutate_box().set_position(Position::Absolute); } + if self.style.get_box().clone_display().is_contents() { + self.style.mutate_box().set_display(Display::Block); + } } /// CSS 2.1 section 9.7: From a9baf5fe3d403b7b3ade5d7a3e31b6f14f103eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 2 Jun 2023 02:40:04 +0200 Subject: [PATCH 19/81] style: Use first generic rather than only generic to determine fallback font family This seems like more sensible behavior. We have another use of only_generic(), but that affects font sizing and other browsers agree with us there:
      Should be 13px
      Should be 16px
      So not touching that one. Differential Revision: https://phabricator.services.mozilla.com/D130732 --- components/style/properties/cascade.rs | 12 ++++++------ components/style/values/computed/font.rs | 10 +++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 3921a4034ed..06db99fd1cc 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -890,7 +890,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { return; } - let use_document_fonts = static_prefs::pref!("browser.display.use_document_fonts") != 0; let builder = &mut self.context.builder; let (default_font_type, prioritize_user_fonts) = { let font = builder.get_font().gecko(); @@ -905,22 +904,23 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { return; } - let generic = font.mFont.family.families.single_generic().unwrap_or(GenericFontFamily::None); let default_font_type = unsafe { - bindings::Gecko_nsStyleFont_ComputeDefaultFontType( + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( builder.device.document(), - generic, font.mLanguage.mRawPtr, ) }; + let use_document_fonts = static_prefs::pref!("browser.display.use_document_fonts") != 0; + // We prioritize user fonts over document fonts if the pref is set, // and we don't have a generic family already (or we're using // cursive or fantasy, since they're ignored, see bug 789788), and // we have a generic family to actually replace it with. - let prioritize_user_fonts = !use_document_fonts && + let prioritize_user_fonts = + !use_document_fonts && default_font_type != GenericFontFamily::None && - !generic.valid_for_user_font_prioritization(); + font.mFont.family.families.needs_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 ab6755d0951..33c2dc469ca 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -627,12 +627,20 @@ impl FontFamilyList { self.list = crate::ArcSlice::from_iter(new_list.into_iter()); } + /// Returns whether we need to prioritize user fonts. + pub (crate) fn needs_user_font_prioritization(&self) -> bool { + self.iter().next().map_or(true, |f| match f { + SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), + _ => true, + }) + } + /// Return the generic ID if it is a single generic font pub fn single_generic(&self) -> Option { let mut iter = self.iter(); if let Some(SingleFontFamily::Generic(f)) = iter.next() { if iter.next().is_none() { - return Some(f.clone()); + return Some(*f); } } None From f911fb4f0ff7163777d896c3c888f25f1c45ff31 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 6 Jun 2023 12:42:34 +0200 Subject: [PATCH 20/81] 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 33c2dc469ca..4e06d4e6efa 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -628,6 +628,7 @@ impl FontFamilyList { } /// Returns whether we need to prioritize user fonts. + #[cfg(feature = "gecko")] pub (crate) fn needs_user_font_prioritization(&self) -> bool { self.iter().next().map_or(true, |f| match f { SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), From 19aa8842c253c7a5859a81051556c4e1295706d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 12:53:09 +0200 Subject: [PATCH 21/81] style: Simplify language-dependent font fallback code .fallback is always the default font for the lang group unless we're a system font (in which case it's "none"). The only reason we need that is because we need to react to language changes (which affect the initial font). Simplify the model a bit doing the language lookup in gfxTextRun (this should avoid allocating a few nsStyleFont structs too. Depends on D130732 Differential Revision: https://phabricator.services.mozilla.com/D131312 --- components/style/properties/cascade.rs | 84 +++++++++++++---------- components/style/values/computed/font.rs | 25 ++----- components/style/values/specified/font.rs | 3 +- 3 files changed, 57 insertions(+), 55 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 06db99fd1cc..0f3daec3039 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -874,33 +874,23 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { true } - /// The default font type (which is stored in FontFamilyList's - /// `mDefaultFontType`) depends on the current lang group and generic font - /// family, so we may need to recompute it if or the family changed. - /// - /// Also, we prioritize non-document fonts here if we need to (see the pref - /// `browser.display.use_document_fonts`). + /// The initial font depends on the current lang group so we may need to + /// recompute it if the language changed. #[inline] #[cfg(feature = "gecko")] - fn recompute_default_font_family_type_if_needed(&mut self) { + fn recompute_initial_font_family_if_needed(&mut self) { use crate::gecko_bindings::bindings; - use crate::values::computed::font::GenericFontFamily; + use crate::values::computed::font::FontFamily; - if !self.seen.contains(LonghandId::XLang) && !self.seen.contains(LonghandId::FontFamily) { + if !self.seen.contains(LonghandId::XLang) { return; } let builder = &mut self.context.builder; - let (default_font_type, prioritize_user_fonts) = { + let default_font_type = { let font = builder.get_font().gecko(); - // System fonts are all right, and should have the default font type - // set to none already, so bail out early. - if font.mFont.family.is_system_font { - debug_assert_eq!( - font.mFont.family.families.fallback, - GenericFontFamily::None - ); + if !font.mFont.family.is_initial { return; } @@ -911,29 +901,52 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { ) }; - let use_document_fonts = static_prefs::pref!("browser.display.use_document_fonts") != 0; - - // We prioritize user fonts over document fonts if the pref is set, - // and we don't have a generic family already (or we're using - // cursive or fantasy, since they're ignored, see bug 789788), and - // we have a generic family to actually replace it with. - let prioritize_user_fonts = - !use_document_fonts && - default_font_type != GenericFontFamily::None && - font.mFont.family.families.needs_user_font_prioritization(); - - if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback { - // Nothing to do. + let initial_generic = font.mFont.family.families.single_generic(); + debug_assert!(initial_generic.is_some(), "Initial font should be just one generic font"); + if initial_generic == Some(default_font_type) { return; } - (default_font_type, prioritize_user_fonts) + + default_font_type }; let font = builder.mutate_font().gecko_mut(); - font.mFont.family.families.fallback = default_font_type; - if prioritize_user_fonts { - font.mFont.family.families.prioritize_first_generic_or_prepend(default_font_type); + // NOTE: Leaves is_initial untouched. + font.mFont.family.families = FontFamily::generic(default_font_type).families.clone(); + } + + /// Prioritize user fonts if needed by pref. + #[inline] + #[cfg(feature = "gecko")] + fn prioritize_user_fonts_if_needed(&mut self) { + use crate::gecko_bindings::bindings; + + if !self.seen.contains(LonghandId::FontFamily) { + return; } + + if static_prefs::pref!("browser.display.use_document_fonts") != 0 { + return; + } + + let builder = &mut self.context.builder; + let default_font_type = { + let font = builder.get_font().gecko(); + + if !font.mFont.family.families.needs_user_font_prioritization() { + return; + } + + unsafe { + bindings::Gecko_nsStyleFont_ComputeFallbackFontTypeForLanguage( + builder.device.document(), + font.mLanguage.mRawPtr, + ) + } + }; + + let font = builder.mutate_font().gecko_mut(); + font.mFont.family.families.prioritize_first_generic_or_prepend(default_font_type); } /// Some keyword sizes depend on the font family and language. @@ -1107,7 +1120,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { #[cfg(feature = "gecko")] { self.unzoom_fonts_if_needed(); - self.recompute_default_font_family_type_if_needed(); + self.recompute_initial_font_family_if_needed(); + self.prioritize_user_fonts_if_needed(); self.recompute_keyword_font_size_if_needed(); self.handle_mathml_scriptlevel_if_needed(); self.constrain_font_size_if_needed() diff --git a/components/style/values/computed/font.rs b/components/style/values/computed/font.rs index 4e06d4e6efa..275bbbc0eba 100644 --- a/components/style/values/computed/font.rs +++ b/components/style/values/computed/font.rs @@ -182,6 +182,9 @@ pub struct FontFamily { pub families: FontFamilyList, /// Whether this font-family came from a specified system-font. pub is_system_font: bool, + /// Whether this is the initial font-family that might react to language + /// changes. + pub is_initial: bool, } macro_rules! static_font_family { @@ -193,10 +196,9 @@ macro_rules! static_font_family { list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)), #[cfg(feature = "servo")] list: Box::new([$family]), - #[cfg(feature = "gecko")] - fallback: GenericFontFamily::None, }, is_system_font: false, + is_initial: false, }; } }; @@ -234,9 +236,9 @@ impl FontFamily { syntax: FontFamilyNameSyntax::Identifiers, }, ))), - fallback: GenericFontFamily::None, }, is_system_font: true, + is_initial: false, } } @@ -303,7 +305,7 @@ impl ToCss for FontFamily { Some(f) => f.to_css(dest)?, None => { #[cfg(feature = "gecko")] - return self.families.fallback.to_css(dest); + return return Ok(()); #[cfg(feature = "servo")] unreachable!(); }, @@ -562,8 +564,6 @@ impl SingleFontFamily { pub struct FontFamilyList { /// The actual list of font families specified. pub list: crate::ArcSlice, - /// A fallback font type (none, serif, or sans-serif, generally). - pub fallback: GenericFontFamily, } /// A list of font families. @@ -592,17 +592,6 @@ impl FontFamilyList { self.list.iter() } - /// Puts the fallback in the list if needed. - #[cfg(feature = "gecko")] - pub fn normalize(&mut self) { - if self.fallback == GenericFontFamily::None { - return; - } - let mut new_list = self.list.iter().cloned().collect::>(); - new_list.push(SingleFontFamily::Generic(self.fallback)); - self.list = crate::ArcSlice::from_iter(new_list.into_iter()); - } - /// 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. @@ -629,7 +618,7 @@ impl FontFamilyList { /// Returns whether we need to prioritize user fonts. #[cfg(feature = "gecko")] - pub (crate) fn needs_user_font_prioritization(&self) -> bool { + pub(crate) fn needs_user_font_prioritization(&self) -> bool { self.iter().next().map_or(true, |f| match f { SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), _ => true, diff --git a/components/style/values/specified/font.rs b/components/style/values/specified/font.rs index d11a2411a5d..40a559a20be 100644 --- a/components/style/values/specified/font.rs +++ b/components/style/values/specified/font.rs @@ -697,6 +697,7 @@ impl ToComputedValue for FontFamily { FontFamily::Values(ref list) => computed::FontFamily { families: list.clone(), is_system_font: false, + is_initial: false, }, FontFamily::System(_) => self.compute_system(context), } @@ -736,8 +737,6 @@ impl Parse for FontFamily { list: crate::ArcSlice::from_iter(values.into_iter()), #[cfg(feature = "servo")] list: values.into_boxed_slice(), - #[cfg(feature = "gecko")] - fallback: computed::GenericFontFamily::None, })) } } From 1d18b3a028c39bb04ea6fa00c0474f5fd4bb45ee Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 11:08:52 +0200 Subject: [PATCH 22/81] Further changes required by Servo --- components/gfx/tests/font_context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/components/gfx/tests/font_context.rs b/components/gfx/tests/font_context.rs index 0cda196f12d..796f693f418 100644 --- a/components/gfx/tests/font_context.rs +++ b/components/gfx/tests/font_context.rs @@ -124,6 +124,7 @@ fn font_family(names: Vec<&str>) -> FontFamily { list: names.into_boxed_slice(), }, is_system_font: false, + is_initial: false, } } From 204317b1e66704166f2842b6bafcece10c0d6cd9 Mon Sep 17 00:00:00 2001 From: lamoure6 Date: Tue, 6 Jun 2023 13:00:42 +0200 Subject: [PATCH 23/81] style: Removed remaining (non-negated) -moz-proton media queries Differential Revision: https://phabricator.services.mozilla.com/D125328 --- 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 d314500b88f..c80ace2015b 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -652,7 +652,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; 58] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 57] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -920,7 +920,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ GTKCSDReversedPlacement ), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), - bool_pref_feature!(atom!("-moz-proton"), "browser.proton.enabled"), bool_pref_feature!( atom!("-moz-proton-places-tooltip"), "browser.proton.places-tooltip.enabled" From 4904a9711182d6eaef2dd6b1f8b017e3323a3a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 13:01:45 +0200 Subject: [PATCH 24/81] style: Correctly report animation status of pseudo-elements that are not stored in the parent element We allow animating pseudo-elements like ::-moz-progress-bar (and we treat them like regular elements). Ideally we should store animations for these in the parent element as well, so they survive reframes and such. But treating them as regular elements right now means that we do animate them, but we never update animations for them correctly because wrapper.rs assumed them to be non-animatable. Since it seems reasonable to keep allowing the animations to happen, let's just correct the update code and add a test. Differential Revision: https://phabricator.services.mozilla.com/D131794 --- components/style/gecko/pseudo_element.rs | 5 +++-- components/style/gecko/wrapper.rs | 15 +++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs index 5a9b955b1c8..be8d0def121 100644 --- a/components/style/gecko/pseudo_element.rs +++ b/components/style/gecko/pseudo_element.rs @@ -93,9 +93,10 @@ impl PseudoElement { EAGER_PSEUDOS[i].clone() } - /// Whether the current pseudo element is animatable. + /// Whether animations for the current pseudo element are stored in the + /// parent element. #[inline] - pub fn is_animatable(&self) -> bool { + pub fn animations_stored_in_parent(&self) -> bool { matches!(*self, Self::Before | Self::After | Self::Marker) } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index c41d2ff1e85..27f3eefe62e 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1390,15 +1390,14 @@ impl<'le> TElement for GeckoElement<'le> { #[inline] fn may_have_animations(&self) -> bool { if let Some(pseudo) = self.implemented_pseudo_element() { - if !pseudo.is_animatable() { - return false; + if pseudo.animations_stored_in_parent() { + // FIXME(emilio): When would the parent of a ::before / ::after + // pseudo-element be null? + return self.parent_element().map_or(false, |p| { + p.as_node() + .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) + }); } - // FIXME(emilio): When would the parent of a ::before / ::after - // pseudo-element be null? - return self.parent_element().map_or(false, |p| { - p.as_node() - .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) - }); } self.as_node() .get_bool_flag(nsINode_BooleanFlag::ElementHasAnimations) From b08701529f650a255ed7ba77b171d63bd28153b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 13:03:08 +0200 Subject: [PATCH 25/81] style: Keep system fonts when prioritizing user fonts This was a subtle behavior change in bug 1739464. The early-return here: https://hg.mozilla.org/mozilla-central/rev/2fb74c67b0c5#l11.40 Meant we also bailed out from user font prioritization for system fonts. It's unclear whether that's really the best behavior but since the intention of the regressing patch was not to change behavior, preserving the old behavior seems better. Differential Revision: https://phabricator.services.mozilla.com/D131899 --- components/style/properties/cascade.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 0f3daec3039..6b8c4cf4d37 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -933,6 +933,10 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { let default_font_type = { let font = builder.get_font().gecko(); + if font.mFont.family.is_system_font { + return; + } + if !font.mFont.family.families.needs_user_font_prioritization() { return; } From 09a0edefb8b29c3b3bc58884f2b1e9507d5a8053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 13:04:11 +0200 Subject: [PATCH 26/81] style: Add a Show Password button to controls It's controlled by the pref: layout.forms.input-type-show-password-button.enabled Differential Revision: https://phabricator.services.mozilla.com/D130407 --- components/style/element_state.rs | 5 +++++ components/style/gecko/non_ts_pseudo_class_list.rs | 2 ++ components/style/gecko/wrapper.rs | 2 ++ 3 files changed, 9 insertions(+) diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 930c459b830..30c65dc9feb 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -125,6 +125,11 @@ bitflags! { const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45; /// Used for the devtools style editor. Probably should go away. const IN_STYLEEDITOR_TRANSITIONING_STATE = 1 << 46; + /// For :-moz-value-empty (to show widgets like the reveal password + /// button or the clear button). + const IN_VALUE_EMPTY_STATE = 1 << 47; + /// For :-moz-revealed. + const IN_REVEALED_STATE = 1 << 48; } } diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index c92f7ae8db2..feb28feb0df 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -63,6 +63,8 @@ macro_rules! apply_non_ts_list { ("-moz-dir-attr-like-auto", MozDirAttrLikeAuto, IN_HAS_DIR_ATTR_LIKE_AUTO_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-autofill-preview", MozAutofillPreview, IN_AUTOFILL_PREVIEW_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME), + ("-moz-value-empty", MozValueEmpty, IN_VALUE_EMPTY_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), + ("-moz-revealed", MozRevealed, IN_REVEALED_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS), ("-moz-math-increment-script-level", MozMathIncrementScriptLevel, IN_INCREMENT_SCRIPT_LEVEL_STATE, _), diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 27f3eefe62e..4350135a4f2 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1975,6 +1975,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::MozAutofillPreview | + NonTSPseudoClass::MozRevealed | + NonTSPseudoClass::MozValueEmpty | NonTSPseudoClass::Dir(..) => self.state().intersects(pseudo_class.state_flag()), NonTSPseudoClass::AnyLink => self.is_link(), NonTSPseudoClass::Link => { From bcd0b74838376564c6dded6ad142a6b632e31ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 13:05:08 +0200 Subject: [PATCH 27/81] style: Honor GTK button layout This is based off work by smurfd. But this patch doesn't support buttons both at the left and right, which simplifies a lot the implementation. Also, clean-up the existing env variables while at it. Co-authored-by: Nicklas Boman Differential Revision: https://phabricator.services.mozilla.com/D132073 --- components/style/custom_properties.rs | 82 ++++++++++++++++++++----- components/style/gecko/media_queries.rs | 16 ----- 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 931bf32a00c..76688afcea1 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -49,19 +49,19 @@ macro_rules! make_variable { } fn get_safearea_inset_top(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().top) + VariableValue::pixels(device.safe_area_insets().top) } fn get_safearea_inset_bottom(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().bottom) + VariableValue::pixels(device.safe_area_insets().bottom) } fn get_safearea_inset_left(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().left) + VariableValue::pixels(device.safe_area_insets().left) } fn get_safearea_inset_right(device: &Device) -> VariableValue { - VariableValue::pixel(device.safe_area_insets().right) + VariableValue::pixels(device.safe_area_insets().right) } static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ @@ -71,17 +71,47 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), ]; -fn get_titlebar_radius(device: &Device) -> VariableValue { - VariableValue::pixel(device.titlebar_radius()) +macro_rules! lnf_int { + ($id:ident) => { + unsafe { + crate::gecko_bindings::bindings::Gecko_GetLookAndFeelInt( + crate::gecko_bindings::bindings::LookAndFeel_IntID::$id as i32, + ) + } + }; } -fn get_menu_radius(device: &Device) -> VariableValue { - VariableValue::pixel(device.menu_radius()) +macro_rules! lnf_int_variable { + ($atom:expr, $id:ident, $ctor:ident) => {{ + fn __eval(_: &Device) -> VariableValue { + VariableValue::$ctor(lnf_int!($id)) + } + make_variable!($atom, __eval) + }}; } -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), +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ + lnf_int_variable!( + atom!("-moz-gtk-csd-titlebar-radius"), + TitlebarRadius, + int_pixels + ), + lnf_int_variable!(atom!("-moz-gtk-csd-menu-radius"), GtkMenuRadius, int_pixels), + lnf_int_variable!( + atom!("-moz-gtk-csd-close-button-position"), + GTKCSDCloseButtonPosition, + integer + ), + lnf_int_variable!( + atom!("-moz-gtk-csd-minimize-button-position"), + GTKCSDMinimizeButtonPosition, + integer + ), + lnf_int_variable!( + atom!("-moz-gtk-csd-maximize-button-position"), + GTKCSDMaximizeButtonPosition, + integer + ), ]; impl CssEnvironment { @@ -280,17 +310,39 @@ impl VariableValue { })) } - /// Create VariableValue from css pixel value - pub fn pixel(number: f32) -> Self { + /// Create VariableValue from an int. + fn integer(number: i32) -> Self { + Self::from_token(Token::Number { + has_sign: false, + value: number as f32, + int_value: Some(number), + }) + } + + /// Create VariableValue from a float amount of CSS pixels. + fn pixels(number: f32) -> Self { // FIXME (https://github.com/servo/rust-cssparser/issues/266): // No way to get TokenSerializationType::Dimension without creating // Token object. - let token = Token::Dimension { + Self::from_token(Token::Dimension { has_sign: false, value: number, int_value: None, unit: CowRcStr::from("px"), - }; + }) + } + + /// Create VariableValue from an integer amount of CSS pixels. + fn int_pixels(number: i32) -> Self { + Self::from_token(Token::Dimension { + has_sign: false, + value: number as f32, + int_value: Some(number), + unit: CowRcStr::from("px"), + }) + } + + fn from_token(token: Token) -> Self { let token_type = token.serialization_type(); let mut css = token.to_css_string(); css.shrink_to_fit(); diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index 19ed6444f07..e4634d7eab9 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -459,22 +459,6 @@ impl Device { } } - /// Returns the gtk titlebar radius in CSS pixels. - pub fn titlebar_radius(&self) -> f32 { - unsafe { - bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) - as f32 - } - } - - /// Returns the gtk menu radius in CSS pixels. - pub fn menu_radius(&self) -> f32 { - unsafe { - bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) - as f32 - } - } - /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { From a95a742599229140af03d2b07cde2cf3d1baa25e Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 6 Jun 2023 13:28:20 +0200 Subject: [PATCH 28/81] Further changes required by Servo --- components/atoms/static_atoms.txt | 4 ++++ components/style/custom_properties.rs | 9 +++++++++ components/style/servo/media_queries.rs | 12 ------------ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 2d0ef951067..7e4f385169e 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,3 +1,7 @@ +-moz-gtk-csd-close-button-position +-moz-gtk-csd-maximize-button-position +-moz-gtk-csd-menu-radius +-moz-gtk-csd-minimize-button-position -moz-gtk-csd-titlebar-radius -moz-gtk-menu-radius DOMContentLoaded diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 76688afcea1..427fe03439c 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -71,6 +71,7 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [ make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right), ]; +#[cfg(feature = "gecko")] macro_rules! lnf_int { ($id:ident) => { unsafe { @@ -81,6 +82,14 @@ macro_rules! lnf_int { }; } +#[cfg(feature = "servo")] +macro_rules! lnf_int { + ($id:ident) => { + // TODO: implement this. + 0 + }; +} + macro_rules! lnf_int_variable { ($atom:expr, $id:ident, $ctor:ident) => {{ fn __eval(_: &Device) -> VariableValue { diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index 19134eff089..e1822332ff1 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -221,18 +221,6 @@ impl Device { } } - /// Returns the gtk titlebar radius in CSS pixels. - /// TODO: implement this method. - pub fn titlebar_radius(&self) -> f32 { - 0.0 - } - - /// Returns the gtk menu radius in CSS pixels. - /// TODO: implement this method. - pub fn menu_radius(&self) -> f32 { - 0.0 - } - /// Return whether the document is a chrome document. #[inline] pub fn is_chrome_document(&self) -> bool { From a8bcfc7f77eb056425e7509c59a092362edc9c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 13:06:12 +0200 Subject: [PATCH 29/81] style: Remove unnecessary button-focus appearance value on Linux This always draws transparent. Only real thing it does is forcing 0px padding. Differential Revision: https://phabricator.services.mozilla.com/D132563 --- components/style/values/specified/box.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 1628b396730..83fa2c8bd51 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -1658,9 +1658,6 @@ pub enum Appearance { ButtonArrowPrevious, #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] ButtonArrowUp, - /// The focus outline box inside of a button. - #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] - ButtonFocus, /// A dual toolbar button (e.g., a Back button with a dropdown) #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] Dualbutton, From 4fe31d5d846b4a5a55c16820a268fe0b089d06cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 13:07:36 +0200 Subject: [PATCH 30/81] style: Use titlebar radius on Linux and make titlebar set-up work for lightweight themes To do this, we always draw the native titlebar behind the toolbox, and then make the toolbox adapt to it by using the titlebar radius. This makes us preserve the shadow properly. On Wayland we'd double-draw the shadow (see bug 1509931 comment 4) so this fixes it by trimming it as well using border-radius. Differential Revision: https://phabricator.services.mozilla.com/D128681 --- 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 c80ace2015b..be30e5f0b6f 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -652,7 +652,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; 57] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -919,6 +919,7 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 57] = [ atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement ), + lnf_int_feature!(atom!("-moz-gtk-wayland"), GTKWayland), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), bool_pref_feature!( atom!("-moz-proton-places-tooltip"), From d1bb131acc1ed6702f250553256926e1e5a35d56 Mon Sep 17 00:00:00 2001 From: Emily McDonough Date: Tue, 6 Jun 2023 15:23:07 +0200 Subject: [PATCH 31/81] style: Implement parsing of the page property Differential Revision: https://phabricator.services.mozilla.com/D131531 --- .../properties/counted_unknown_properties.py | 1 - .../style/properties/longhands/page.mako.rs | 10 +++++ components/style/values/computed/mod.rs | 2 +- components/style/values/computed/page.rs | 3 +- components/style/values/specified/mod.rs | 2 +- components/style/values/specified/page.rs | 44 ++++++++++++++++++- 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index 6a562a9abab..fffac5ff589 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -38,7 +38,6 @@ COUNTED_UNKNOWN_PROPERTIES = [ "-webkit-writing-mode", "baseline-shift", "-webkit-hyphenate-character", - "page", "-webkit-highlight", "background-repeat-x", "-webkit-padding-end", diff --git a/components/style/properties/longhands/page.mako.rs b/components/style/properties/longhands/page.mako.rs index 298456cb753..ec41989a137 100644 --- a/components/style/properties/longhands/page.mako.rs +++ b/components/style/properties/longhands/page.mako.rs @@ -19,3 +19,13 @@ ${helpers.predefined_type( animation_value_type="none", rule_types_allowed=PAGE_RULE, )} + +${helpers.predefined_type( + "page", + "PageName", + "computed::PageName::auto()", + engines="gecko", + gecko_pref="layout.css.named-pages.enabled", + spec="https://drafts.csswg.org/css-page-3/#using-named-pages", + animation_value_type="discrete", +)} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 9beab014ff5..6990fc65fe6 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -72,7 +72,7 @@ pub use self::list::ListStyleType; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{Orientation, PageSize, PaperSize}; +pub use self::page::{Orientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs index 27b16d0af15..080681e008f 100644 --- a/components/style/values/computed/page.rs +++ b/components/style/values/computed/page.rs @@ -2,7 +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/. */ -//! Computed @page at-rule properties +//! Computed @page at-rule properties and named-page style properties use crate::values::computed::length::NonNegativeLength; use crate::values::computed::{Context, ToComputedValue}; @@ -13,6 +13,7 @@ use crate::values::specified::page as specified; pub use generics::page::GenericPageSize; pub use generics::page::Orientation; pub use generics::page::PaperSize; +pub use specified::PageName; /// Computed value of the @page size descriptor /// diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 3c3c9b8d1ce..c776af5f201 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -69,7 +69,7 @@ pub use self::list::ListStyleType; pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; -pub use self::page::{Orientation, PageSize, PaperSize}; +pub use self::page::{Orientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs index 4d96b532689..e6d5347e378 100644 --- a/components/style/values/specified/page.rs +++ b/components/style/values/specified/page.rs @@ -2,10 +2,10 @@ * 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/. */ -//! Specified @page at-rule properties +//! Specified @page at-rule properties and named-page style properties use crate::parser::{Parse, ParserContext}; -use crate::values::generics; +use crate::values::{generics, CustomIdent}; use crate::values::generics::size::Size2D; use crate::values::specified::length::NonNegativeLength; use cssparser::Parser; @@ -46,3 +46,43 @@ impl Parse for PageSize { Ok(PageSize::Auto) } } + +/// Page name value. +/// +/// https://drafts.csswg.org/css-page-3/#using-named-pages +#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(C, u8)] +pub enum PageName { + /// `auto` value. + Auto, + /// Page name value + PageName(CustomIdent), +} + +impl Parse for PageName { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + Ok(match_ignore_ascii_case! { ident, + "auto" => PageName::auto(), + _ => PageName::PageName(CustomIdent::from_ident(location, ident, &[])?), + }) + } +} + +impl PageName { + /// `auto` value. + #[inline] + pub fn auto() -> Self { + PageName::Auto + } + + /// Whether this is the `auto` value. + #[inline] + pub fn is_auto(&self) -> bool { + matches!(*self, PageName::Auto) + } +} From 84cd22c3e0496e7cf72b474fbee56dd91ddf2246 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 6 Jun 2023 15:24:47 +0200 Subject: [PATCH 32/81] style: Part 1: Add ScrollTimeline class Define a simple version of ScrollTimeline. Differential Revision: https://phabricator.services.mozilla.com/D129099 --- components/style/stylesheets/scroll_timeline_rule.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index bbc5d9caf8f..5a6f3ad1cc0 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -182,7 +182,8 @@ impl Default for Source { /// 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 { +#[repr(u8)] +pub enum ScrollDirection { /// The initial value. Auto, /// The direction along the block axis. This is the default value. @@ -195,12 +196,15 @@ pub enum Orientation { Vertical, } -impl Default for Orientation { +impl Default for ScrollDirection { fn default() -> Self { - Orientation::Auto + ScrollDirection::Auto } } +// Avoid name collision in cbindgen with StyleOrientation. +pub use self::ScrollDirection as Orientation; + /// Scroll-timeline offsets. We treat None as an empty vector. /// value: none | # /// From e66bcf2cc589633b212fa70153d823262cf49f36 Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 6 Jun 2023 15:26:17 +0200 Subject: [PATCH 33/81] style: Part 8: Hook scroll-timeline rule into Cascade data and use it for CSS animations We hook the rule into cascade data, and so we can look it up by timeline name. Now we only use StyleScrollDirection from @scroll-timeline rule. `source` and `scroll-offsets` are skipped now and use the default values instead because I'm pretty sure the syntax will be changed in Bug 1733260, and `scroll-offsets` may be obsolete because the spec proposal intents to make it be always 0% ~ 100%. Also, add some reftests for the default `source` and `scroll-offsets`, and different `orientation`s. Besides, we disable at-scroll-timeline-start-end.html in Gecko because we don't support start/end descriptors, and there are too many intermittents in it. Differential Revision: https://phabricator.services.mozilla.com/D126452 --- components/style/invalidation/stylesheets.rs | 11 ++++---- components/style/matching.rs | 8 +++--- components/style/stylist.rs | 28 ++++++++++++++++---- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index fc39b8cca76..1d1111170ed 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -619,11 +619,12 @@ impl StylesheetInvalidationSet { // existing elements. } }, - ScrollTimeline(..) => { - // TODO: Bug 1676784: check if animation-timeline name is referenced. - // Now we do nothing. - }, - CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => { + // TODO: Check if timeline name is referenced, though this might go away in bug 1737918. + ScrollTimeline(..) | + CounterStyle(..) | + Page(..) | + Viewport(..) | + FontFeatureValues(..) => { debug!( " > Found unsupported rule, marking the whole subtree \ invalid." diff --git a/components/style/matching.rs b/components/style/matching.rs index f81f9c3a233..9b267cd8b46 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -277,7 +277,7 @@ trait PrivateMatchMethods: TElement { let old_box_style = old_style.get_box(); - let keyframes_could_have_changed = context + let keyframes_or_timeline_could_have_changed = context .shared .traversal_flags .contains(TraversalFlags::ForCSSRuleChanges); @@ -287,9 +287,9 @@ trait PrivateMatchMethods: TElement { // element has or will have CSS animation style regardless of whether // the animation is running or not. // - // TODO: We should check which @keyframes were added/changed/deleted and - // update only animations corresponding to those @keyframes. - if keyframes_could_have_changed { + // TODO: We should check which @keyframes/@scroll-timeline were added/changed/deleted and + // update only animations corresponding to those @keyframes/@scroll-timeline. + if keyframes_or_timeline_could_have_changed { return true; } diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a5538aa1189..a48a7103f84 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -30,7 +30,9 @@ use crate::stylesheets::keyframes_rule::KeyframesAnimation; use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; #[cfg(feature = "gecko")] -use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule}; +use crate::stylesheets::{ + CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule, ScrollTimelineRule, +}; use crate::stylesheets::{ CssRule, EffectiveRulesIterator, Origin, OriginSet, PerOrigin, PerOriginIter, }; @@ -1542,6 +1544,10 @@ pub struct ExtraStyleData { /// A map of effective page rules. #[cfg(feature = "gecko")] pub pages: Vec>>, + + /// A map of effective scroll-timeline rules. + #[cfg(feature = "gecko")] + pub scroll_timelines: PrecomputedHashMap>>, } #[cfg(feature = "gecko")] @@ -1570,6 +1576,18 @@ impl ExtraStyleData { fn add_page(&mut self, rule: &Arc>) { self.pages.push(rule.clone()); } + + /// Add the given @scroll-timeline rule. + fn add_scroll_timeline( + &mut self, + guard: &SharedRwLockReadGuard, + rule: &Arc>, + )-> Result<(), FailedAllocationError> { + let name = rule.read_with(guard).name.as_atom().clone(); + self.scroll_timelines + .try_insert(name, rule.clone()) + .map(|_| {}) + } } impl ExtraStyleData { @@ -1580,6 +1598,7 @@ impl ExtraStyleData { self.font_feature_values.clear(); self.counter_styles.clear(); self.pages.clear(); + self.scroll_timelines.clear(); } } } @@ -1604,6 +1623,7 @@ impl MallocSizeOf for ExtraStyleData { n += self.font_feature_values.shallow_size_of(ops); n += self.counter_styles.shallow_size_of(ops); n += self.pages.shallow_size_of(ops); + n += self.scroll_timelines.shallow_size_of(ops); n } } @@ -2351,12 +2371,10 @@ impl CascadeData { } }, #[cfg(feature = "gecko")] - CssRule::ScrollTimeline(..) => { - // TODO: Bug 1676791: set the timeline into animation. - // https://phabricator.services.mozilla.com/D126452 - // + CssRule::ScrollTimeline(ref rule) => { // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. + self.extra_data.add_scroll_timeline(guard, rule)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { From 94302871836a91b90d0fccc37d39e68cc41d27cc Mon Sep 17 00:00:00 2001 From: Boris Chiou Date: Tue, 6 Jun 2023 15:27:39 +0200 Subject: [PATCH 34/81] style: Part 10: Make source and scroll-offsets accept only default value Based on our previous patches, we only support default behavior for source and scroll-offsets: 1. source:auto 2. scroll-offsets: none 3. scroll-offsets: auto, auto, ... So update the parser for them. We expect to remove whole @scroll-timeline in Bug 1733260, so now only do a tiny update in parser. Differential Revision: https://phabricator.services.mozilla.com/D132417 --- components/style/stylesheets/scroll_timeline_rule.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index 5a6f3ad1cc0..589814eff20 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -157,15 +157,19 @@ impl<'a, 'b, 'i> DeclarationParser<'i> for ScrollTimelineDescriptorsParser<'a, ' /// The scroll-timeline source. /// /// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-source +// FIXME: Bug 1733260 may drop the entire @scroll-timeline, and now we don't support source other +// than the default value (so use #[css(skip)]). #[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)] pub enum Source { /// The scroll container. + #[css(skip)] 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 + #[css(skip)] None, } @@ -215,7 +219,7 @@ pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimeline impl Parse for ScrollOffsets { fn parse<'i, 't>( - context: &ParserContext, + _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { @@ -224,7 +228,7 @@ impl Parse for ScrollOffsets { Ok(ScrollOffsets( input - .parse_comma_separated(|i| ScrollTimelineOffset::parse(context, i))? + .parse_comma_separated(|i| ScrollTimelineOffset::parse(i))? .into_boxed_slice(), )) } @@ -234,14 +238,18 @@ impl Parse for ScrollOffsets { /// value: auto | | /// /// https://drafts.csswg.org/scroll-animations/#typedef-scroll-timeline-offset +// FIXME: Bug 1733260 may drop the entire @scroll-timeline, and now we don't support +// other than the default value (so use #[css(skip)]). #[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. + #[css(skip)] LengthPercentage(LengthPercentage), /// An element-based offset. + #[css(skip)] ElementOffset(ElementOffset), } From 26c10339e332dbfe3607be57d509e25e30267523 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Tue, 6 Jun 2023 16:46:36 +0200 Subject: [PATCH 35/81] style: Add support for the 'ic' font-relative unit This is a "simplified" implementation of 'ic', similar to what Safari Preview currently supports: it only considers the advance of U+6C34 if found in the first available font, and otherwise falls back to the default of 1em. (The spec allows for this "in cases where it is impossible or impractical to determine the ideographic advance measure".) Differential Revision: https://phabricator.services.mozilla.com/D132818 --- components/style/font_metrics.rs | 3 +++ components/style/values/generics/calc.rs | 1 + components/style/values/specified/calc.rs | 1 + components/style/values/specified/length.rs | 27 +++++++++++++++++++-- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/components/style/font_metrics.rs b/components/style/font_metrics.rs index 13a4e9e182b..259e5693942 100644 --- a/components/style/font_metrics.rs +++ b/components/style/font_metrics.rs @@ -18,6 +18,8 @@ pub struct FontMetrics { pub zero_advance_measure: Option, /// The cap-height of the font. pub cap_height: Option, + /// The ideographic-width of the font. + pub ic_width: Option, /// The ascent of the font (a value is always available for this). pub ascent: Length, } @@ -28,6 +30,7 @@ impl Default for FontMetrics { x_height: None, zero_advance_measure: None, cap_height: None, + ic_width: None, ascent: Length::new(0.0), } } diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index d2bc2a85523..d9044bbb818 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -47,6 +47,7 @@ pub enum SortKey { Deg, Em, Ex, + Ic, Px, Rem, Sec, diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index d6997934554..f5448591f21 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -185,6 +185,7 @@ impl generic::CalcNodeLeaf for Leaf { FontRelativeLength::Em(..) => SortKey::Em, FontRelativeLength::Ex(..) => SortKey::Ex, FontRelativeLength::Cap(..) => SortKey::Cap, + FontRelativeLength::Ic(..) => SortKey::Ic, FontRelativeLength::Rem(..) => SortKey::Rem, }, NoCalcLength::ViewportPercentage(ref vp) => match *vp { diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 6105155d81d..600d65a531c 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -59,6 +59,9 @@ pub enum FontRelativeLength { /// A "cap" value: https://drafts.csswg.org/css-values/#cap #[css(dimension)] Cap(CSSFloat), + /// An "ic" value: https://drafts.csswg.org/css-values/#ic + #[css(dimension)] + Ic(CSSFloat), /// A "rem" value: https://drafts.csswg.org/css-values/#rem #[css(dimension)] Rem(CSSFloat), @@ -93,6 +96,7 @@ impl FontRelativeLength { FontRelativeLength::Ex(v) | FontRelativeLength::Ch(v) | FontRelativeLength::Cap(v) | + FontRelativeLength::Ic(v) | FontRelativeLength::Rem(v) => v == 0., } } @@ -103,6 +107,7 @@ impl FontRelativeLength { FontRelativeLength::Ex(v) | FontRelativeLength::Ch(v) | FontRelativeLength::Cap(v) | + FontRelativeLength::Ic(v) | FontRelativeLength::Rem(v) => v < 0., } } @@ -119,12 +124,13 @@ impl FontRelativeLength { (&Ex(one), &Ex(other)) => Ex(one + other), (&Ch(one), &Ch(other)) => Ch(one + other), (&Cap(one), &Cap(other)) => Cap(one + other), + (&Ic(one), &Ic(other)) => Ic(one + other), (&Rem(one), &Rem(other)) => Rem(one + other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Em(..) | Ex(..) | Ch(..) | Cap(..) | Rem(..) => {}, + Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, } debug_unreachable!("Forgot to handle unit in try_sum()") }, @@ -237,6 +243,20 @@ impl FontRelativeLength { }); (reference_size, length) }, + FontRelativeLength::Ic(length) => { + let metrics = + query_font_metrics(context, base_size, FontMetricsOrientation::MatchContextPreferVertical); + let reference_size = metrics.ic_width.unwrap_or_else(|| { + // https://drafts.csswg.org/css-values/#ic + // + // In the cases where it is impossible or impractical to + // determine the ideographic advance measure, it must be + // assumed to be 1em. + // + reference_font_size + }); + (reference_size, length) + }, FontRelativeLength::Rem(length) => { // https://drafts.csswg.org/css-values/#rem: // @@ -549,6 +569,7 @@ impl NoCalcLength { "ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)), "ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)), "cap" => NoCalcLength::FontRelative(FontRelativeLength::Cap(value)), + "ic" => NoCalcLength::FontRelative(FontRelativeLength::Ic(value)), "rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)), // viewport percentages "vw" if !context.in_page_rule() => { @@ -709,12 +730,13 @@ impl PartialOrd for FontRelativeLength { (&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other), (&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other), (&Cap(ref one), &Cap(ref other)) => one.partial_cmp(other), + (&Ic(ref one), &Ic(ref other)) => one.partial_cmp(other), (&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other), // See https://github.com/rust-lang/rust/issues/68867. rustc isn't // able to figure it own on its own so we help. _ => unsafe { match *self { - Em(..) | Ex(..) | Ch(..) | Cap(..) | Rem(..) => {}, + Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {}, } debug_unreachable!("Forgot an arm in partial_cmp?") }, @@ -732,6 +754,7 @@ impl Mul for FontRelativeLength { FontRelativeLength::Ex(v) => FontRelativeLength::Ex(v * scalar), FontRelativeLength::Ch(v) => FontRelativeLength::Ch(v * scalar), FontRelativeLength::Cap(v) => FontRelativeLength::Cap(v * scalar), + FontRelativeLength::Ic(v) => FontRelativeLength::Ic(v * scalar), FontRelativeLength::Rem(v) => FontRelativeLength::Rem(v * scalar), } } From 8bb7d98f0ce2fdfa49f71c1379dd2bcde3aeac29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 16:49:13 +0200 Subject: [PATCH 36/81] style: Add support for the revert-layer keyword This patch looks bigger than it is, but it's mostly because of plumbing. To implement revert-layer we need not only the cascade origin of the declaration, but the whole cascade level, plus also the layer order. In order to do this, encapsulate these two things inside a 32-bit `CascadePriority` struct and plumb it through the rule tree and so on. This allows us to remove the packing and unpacking of CascadeLevel, though I kept the ShadowCascadeOrder limit for now in case we need to reintroduce it. Fix `!important` behavior of layers while at it (implementing it in `CascadeLevel::cmp`, spec quote included since it was tricky to find) since some revert-layer tests were depending on it. The style attribute test is failing now, but follow-up commit fixes it, see spec issue. In terms of the actual keyword implementation, it's sort of straight-forward: We implement revert and revert-layer in a shared way, by storing the cascade priority that reverted it. Differential Revision: https://phabricator.services.mozilla.com/D133372 --- components/style/animation.rs | 2 + components/style/applicable_declarations.rs | 133 ++++++++++++++---- components/style/custom_properties.rs | 19 +-- components/style/matching.rs | 10 ++ components/style/properties/cascade.rs | 97 +++++++------ .../style/properties/declaration_block.rs | 6 +- components/style/properties/helpers.mako.rs | 1 + .../helpers/animated_properties.mako.rs | 8 +- .../style/properties/properties.mako.rs | 24 ++-- components/style/rule_collector.rs | 2 +- components/style/rule_tree/core.rs | 49 ++++--- components/style/rule_tree/level.rs | 118 ++++++---------- components/style/rule_tree/mod.rs | 85 ++++++----- components/style/stylesheets/layer_rule.rs | 14 +- components/style/stylesheets/origin.rs | 2 +- components/style/stylist.rs | 18 ++- components/style/values/mod.rs | 17 ++- 17 files changed, 354 insertions(+), 251 deletions(-) diff --git a/components/style/animation.rs b/components/style/animation.rs index 57ce98141a7..7f0c7e6a588 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -24,6 +24,7 @@ use crate::selector_parser::PseudoElement; use crate::shared_lock::{Locked, SharedRwLock}; use crate::style_resolver::StyleResolverForElement; use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::values::animated::{Animate, Procedure}; use crate::values::computed::{Time, TimingFunction}; use crate::values::generics::box_::AnimationIterationCount; @@ -290,6 +291,7 @@ impl IntermediateComputedKeyframe { let rule_node = base_style.rules().clone(); let new_node = context.stylist.rule_tree().update_rule_at_level( CascadeLevel::Animations, + LayerOrder::root(), Some(locked_block.borrow_arc()), &rule_node, &context.guards, diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 7f2d3928d2d..cdb78d05405 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -24,36 +24,102 @@ pub type ApplicableDeclarationList = SmallVec<[ApplicableDeclarationBlock; 16]>; /// That's a limit that could be reached in realistic webpages, so we use /// 24 bits and enforce defined behavior in the overflow case. /// +/// Note that right now this restriction could be lifted if wanted (because we +/// no longer stash the cascade level in the remaining bits), but we keep it in +/// place in case we come up with a use-case for them, lacking reports of the +/// current limit being too small. +/// /// [1] https://cs.chromium.org/chromium/src/third_party/WebKit/Source/core/css/ /// RuleSet.h?l=128&rcl=90140ab80b84d0f889abc253410f44ed54ae04f3 -const SOURCE_ORDER_SHIFT: usize = 0; const SOURCE_ORDER_BITS: usize = 24; const SOURCE_ORDER_MAX: u32 = (1 << SOURCE_ORDER_BITS) - 1; -const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX << SOURCE_ORDER_SHIFT; +const SOURCE_ORDER_MASK: u32 = SOURCE_ORDER_MAX; -/// We pack the cascade level in a single byte, see CascadeLevel::to_byte_lossy -/// for the different trade-offs there. -const CASCADE_LEVEL_SHIFT: usize = SOURCE_ORDER_BITS; +/// The cascade-level+layer order of this declaration. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq)] +pub struct CascadePriority { + cascade_level: CascadeLevel, + layer_order: LayerOrder, +} -/// Stores the source order of a block, the cascade level it belongs to, and the -/// counter needed to handle Shadow DOM cascade order properly. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)] -struct ApplicableDeclarationBits(u32); +#[allow(dead_code)] +fn size_assert() { + #[allow(unsafe_code)] + unsafe { std::mem::transmute::(0u32) }; +} -impl ApplicableDeclarationBits { - fn new(source_order: u32, cascade_level: CascadeLevel) -> Self { - Self( - (source_order & SOURCE_ORDER_MASK) | - ((cascade_level.to_byte_lossy() as u32) << CASCADE_LEVEL_SHIFT), - ) +impl PartialOrd for CascadePriority { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for CascadePriority { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.cascade_level + .cmp(&other.cascade_level) + .then_with(|| { + let ordering = self.layer_order.cmp(&other.layer_order); + // https://drafts.csswg.org/css-cascade-5/#cascade-layering + // + // Cascade layers (like declarations) are ordered by order + // of appearance. When comparing declarations that belong to + // different layers, then for normal rules the declaration + // whose cascade layer is last wins, and for important rules + // the declaration whose cascade layer is first wins. + // + // FIXME: This creates somewhat surprising behavior for the + // style attribute, see + // https://github.com/w3c/csswg-drafts/issues/6872 + if self.cascade_level.is_important() { + ordering.reverse() + } else { + ordering + } + }) + } +} + +impl CascadePriority { + /// Construct a new CascadePriority for a given (level, order) pair. + pub fn new(cascade_level: CascadeLevel, layer_order: LayerOrder) -> Self { + Self { cascade_level, layer_order } } - fn source_order(&self) -> u32 { - self.0 & SOURCE_ORDER_MASK + /// Returns the layer order. + #[inline] + pub fn layer_order(&self) -> LayerOrder { + self.layer_order } - fn level(&self) -> CascadeLevel { - CascadeLevel::from_byte((self.0 >> CASCADE_LEVEL_SHIFT) as u8) + /// Returns the cascade level. + #[inline] + pub fn cascade_level(&self) -> CascadeLevel { + self.cascade_level + } + + /// Whether this declaration should be allowed if `revert` or `revert-layer` + /// have been specified on a given origin. + /// + /// `self` is the priority at which the `revert` or `revert-layer` keyword + /// have been specified. + pub fn allows_when_reverted(&self, other: &Self, origin_revert: bool) -> bool { + if origin_revert { + other.cascade_level.origin() < self.cascade_level.origin() + } else { + other.unimportant() < self.unimportant() + } + } + + /// Convert this priority from "important" to "non-important", if needed. + pub fn unimportant(&self) -> Self { + Self::new(self.cascade_level().unimportant(), self.layer_order()) + } + + /// Convert this priority from "non-important" to "important", if needed. + pub fn important(&self) -> Self { + Self::new(self.cascade_level().important(), self.layer_order()) } } @@ -69,11 +135,11 @@ pub struct ApplicableDeclarationBlock { pub source: StyleSource, /// The bits containing the source order, cascade level, and shadow cascade /// order. - bits: ApplicableDeclarationBits, + source_order: u32, /// The specificity of the selector. pub specificity: u32, - /// The layer order of the selector. - pub layer_order: LayerOrder, + /// The cascade priority of the rule. + pub cascade_priority: CascadePriority, } impl ApplicableDeclarationBlock { @@ -86,9 +152,9 @@ impl ApplicableDeclarationBlock { ) -> Self { ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), - bits: ApplicableDeclarationBits::new(0, level), + source_order: 0, specificity: 0, - layer_order: LayerOrder::root(), + cascade_priority: CascadePriority::new(level, LayerOrder::root()), } } @@ -103,29 +169,34 @@ impl ApplicableDeclarationBlock { ) -> Self { ApplicableDeclarationBlock { source, - bits: ApplicableDeclarationBits::new(source_order, level), + source_order: source_order & SOURCE_ORDER_MASK, specificity, - layer_order, + cascade_priority: CascadePriority::new(level, layer_order), } } /// Returns the source order of the block. #[inline] pub fn source_order(&self) -> u32 { - self.bits.source_order() + self.source_order } /// Returns the cascade level of the block. #[inline] pub fn level(&self) -> CascadeLevel { - self.bits.level() + self.cascade_priority.cascade_level() + } + + /// Returns the cascade level of the block. + #[inline] + pub fn layer_order(&self) -> LayerOrder { + self.cascade_priority.layer_order() } /// Convenience method to consume self and return the right thing for the /// rule tree to iterate over. #[inline] - pub fn for_rule_tree(self) -> (StyleSource, CascadeLevel) { - let level = self.level(); - (self.source, level) + pub fn for_rule_tree(self) -> (StyleSource, CascadePriority) { + (self.source, self.cascade_priority) } } diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 427fe03439c..de887396c55 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -6,11 +6,11 @@ //! //! [custom]: https://drafts.csswg.org/css-variables/ +use crate::applicable_declarations::CascadePriority; use crate::hash::map::Entry; use crate::media_queries::Device; use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue}; use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher}; -use crate::stylesheets::{Origin, PerOrigin}; use crate::Atom; use cssparser::{ CowRcStr, Delimiter, Parser, ParserInput, SourcePosition, Token, TokenSerializationType, @@ -599,10 +599,10 @@ fn parse_env_function<'i, 't>( /// properties. pub struct CustomPropertiesBuilder<'a> { seen: PrecomputedHashSet<&'a Name>, - reverted: PerOrigin>, may_have_cycles: bool, custom_properties: Option, inherited: Option<&'a Arc>, + reverted: PrecomputedHashMap<&'a Name, (CascadePriority, bool)>, device: &'a Device, } @@ -620,14 +620,16 @@ impl<'a> CustomPropertiesBuilder<'a> { } /// Cascade a given custom property declaration. - pub fn cascade(&mut self, declaration: &'a CustomDeclaration, origin: Origin) { + pub fn cascade(&mut self, declaration: &'a CustomDeclaration, priority: CascadePriority) { let CustomDeclaration { ref name, ref value, } = *declaration; - if self.reverted.borrow_for_origin(&origin).contains(&name) { - return; + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&name) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + return; + } } let was_already_present = !self.seen.insert(name); @@ -670,11 +672,10 @@ impl<'a> CustomPropertiesBuilder<'a> { map.insert(name.clone(), value); }, CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword { - CSSWideKeyword::Revert => { + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; self.seen.remove(name); - for origin in origin.following_including() { - self.reverted.borrow_mut_for_origin(&origin).insert(name); - } + self.reverted.insert(name, (priority, origin_revert)); }, CSSWideKeyword::Initial => { map.remove(name); diff --git a/components/style/matching.rs b/components/style/matching.rs index 9b267cd8b46..92fdee3f97b 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -23,6 +23,7 @@ use crate::selector_parser::{PseudoElement, RestyleDamage}; use crate::shared_lock::Locked; use crate::style_resolver::ResolvedElementStyles; use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement}; +use crate::stylesheets::layer_rule::LayerOrder; use crate::stylist::RuleInclusion; use crate::traversal_flags::TraversalFlags; use selectors::matching::ElementSelectorFlags; @@ -92,6 +93,7 @@ trait PrivateMatchMethods: TElement { fn replace_single_rule_node( context: &SharedStyleContext, level: CascadeLevel, + layer_order: LayerOrder, pdb: Option>>, path: &mut StrongRuleNode, ) -> bool { @@ -101,6 +103,7 @@ trait PrivateMatchMethods: TElement { let mut important_rules_changed = false; let new_node = stylist.rule_tree().update_rule_at_level( level, + layer_order, pdb, path, guards, @@ -145,12 +148,14 @@ trait PrivateMatchMethods: TElement { result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_normal(), + LayerOrder::root(), style_attribute, primary_rules, ); result |= Self::replace_single_rule_node( context.shared, CascadeLevel::same_tree_author_important(), + LayerOrder::root(), style_attribute, primary_rules, ); @@ -172,6 +177,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::SMILOverride, + LayerOrder::root(), self.smil_override(), primary_rules, ); @@ -181,6 +187,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::Transitions, + LayerOrder::root(), self.transition_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), @@ -192,6 +199,7 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( context.shared, CascadeLevel::Animations, + LayerOrder::root(), self.animation_rule(&context.shared) .as_ref() .map(|a| a.borrow_arc()), @@ -589,12 +597,14 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, + LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, + LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 6b8c4cf4d37..dfdb2a2b1c8 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -4,6 +4,7 @@ //! The main cascading algorithm of the style system. +use crate::applicable_declarations::CascadePriority; use crate::context::QuirksMode; use crate::custom_properties::CustomPropertiesBuilder; use crate::dom::TElement; @@ -15,12 +16,13 @@ use crate::properties::{ ShorthandsWithPropertyReferencesCache, StyleBuilder, CASCADE_PROPERTY, }; use crate::rule_cache::{RuleCache, RuleCacheConditions}; -use crate::rule_tree::StrongRuleNode; +use crate::rule_tree::{StrongRuleNode, CascadeLevel}; use crate::selector_parser::PseudoElement; use crate::shared_lock::StylesheetGuards; use crate::style_adjuster::StyleAdjuster; -use crate::stylesheets::{Origin, PerOrigin}; +use crate::stylesheets::{Origin, layer_rule::LayerOrder}; use crate::values::{computed, specified}; +use fxhash::FxHashMap; use servo_arc::Arc; use smallvec::SmallVec; use std::borrow::Cow; @@ -115,6 +117,7 @@ struct DeclarationIterator<'a> { declarations: DeclarationImportanceIterator<'a>, origin: Origin, importance: Importance, + priority: CascadePriority, } impl<'a> DeclarationIterator<'a> { @@ -128,8 +131,9 @@ impl<'a> DeclarationIterator<'a> { let mut iter = Self { guards, current_rule_node: Some(rule_node), - origin: Origin::Author, + origin: Origin::UserAgent, importance: Importance::Normal, + priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), declarations: DeclarationImportanceIterator::default(), restriction, }; @@ -138,10 +142,11 @@ impl<'a> DeclarationIterator<'a> { } fn update_for_node(&mut self, node: &'a StrongRuleNode) { - let origin = node.cascade_level().origin(); - self.origin = origin; - self.importance = node.importance(); - let guard = match origin { + self.priority = node.cascade_priority(); + let level = self.priority.cascade_level(); + self.origin = level.origin(); + self.importance = level.importance(); + let guard = match self.origin { Origin::Author => self.guards.author, Origin::User | Origin::UserAgent => self.guards.ua_or_user, }; @@ -153,7 +158,7 @@ impl<'a> DeclarationIterator<'a> { } impl<'a> Iterator for DeclarationIterator<'a> { - type Item = (&'a PropertyDeclaration, Origin); + type Item = (&'a PropertyDeclaration, CascadePriority); #[inline] fn next(&mut self) -> Option { @@ -163,20 +168,19 @@ impl<'a> Iterator for DeclarationIterator<'a> { continue; } - let origin = self.origin; if let Some(restriction) = self.restriction { // decl.id() is either a longhand or a custom // property. Custom properties are always allowed, but // longhands are only allowed if they have our // restriction flag set. if let PropertyDeclarationId::Longhand(id) = decl.id() { - if !id.flags().contains(restriction) && origin != Origin::UserAgent { + if !id.flags().contains(restriction) && self.origin != Origin::UserAgent { continue; } } } - return Some((decl, origin)); + return Some((decl, self.priority)); } let next_node = self.current_rule_node.take()?.parent()?; @@ -259,7 +263,7 @@ pub fn apply_declarations<'a, E, I>( ) -> Arc where E: TElement, - I: Iterator, + I: Iterator, { debug_assert!(layout_parent_style.is_none() || parent_style.is_some()); debug_assert_eq!( @@ -278,14 +282,14 @@ where let inherited_style = parent_style.unwrap_or(device.default_computed_values()); - let mut declarations = SmallVec::<[(&_, Origin); 32]>::new(); + let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new(); let custom_properties = { let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device); - for (declaration, origin) in iter { - declarations.push((declaration, origin)); + for (declaration, priority) in iter { + declarations.push((declaration, priority)); if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, origin); + builder.cascade(declaration, priority); } } @@ -494,7 +498,8 @@ struct Cascade<'a, 'b: 'a> { cascade_mode: CascadeMode<'a>, seen: LonghandIdSet, author_specified: LonghandIdSet, - reverted: PerOrigin, + reverted_set: LonghandIdSet, + reverted: FxHashMap, } impl<'a, 'b: 'a> Cascade<'a, 'b> { @@ -504,6 +509,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { cascade_mode, seen: LonghandIdSet::default(), author_specified: LonghandIdSet::default(), + reverted_set: Default::default(), reverted: Default::default(), } } @@ -575,7 +581,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, ) where Phase: CascadePhase, - I: Iterator, + I: Iterator, { let apply_reset = apply_reset == ApplyResetProperties::Yes; @@ -589,7 +595,9 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { let ignore_colors = !self.context.builder.device.use_document_colors(); let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); - for (declaration, origin) in declarations { + for (declaration, priority) in declarations { + let origin = priority.cascade_level().origin(); + let declaration_id = declaration.id(); let longhand_id = match declaration_id { PropertyDeclarationId::Longhand(id) => id, @@ -616,12 +624,12 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; } - if self - .reverted - .borrow_for_origin(&origin) - .contains(physical_longhand_id) - { - continue; + if self.reverted_set.contains(physical_longhand_id) { + if let Some(&(reverted_priority, is_origin_revert)) = self.reverted.get(&physical_longhand_id) { + if !reverted_priority.allows_when_reverted(&priority, is_origin_revert) { + continue; + } + } } // Only a few properties are allowed to depend on the visited state @@ -653,32 +661,31 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { ); } - let css_wide_keyword = declaration.get_css_wide_keyword(); - if let Some(CSSWideKeyword::Revert) = css_wide_keyword { - // We intentionally don't want to insert it into `self.seen`, - // `reverted` takes care of rejecting other declarations as - // needed. - for origin in origin.following_including() { - self.reverted - .borrow_mut_for_origin(&origin) - .insert(physical_longhand_id); - } - continue; - } + let is_unset = match declaration.get_css_wide_keyword() { + Some(keyword) => match keyword { + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Revert => { + let origin_revert = keyword == CSSWideKeyword::Revert; + // We intentionally don't want to insert it into + // `self.seen`, `reverted` takes care of rejecting other + // declarations as needed. + self.reverted_set.insert(physical_longhand_id); + self.reverted.insert(physical_longhand_id, (priority, origin_revert)); + continue; + }, + CSSWideKeyword::Unset => true, + CSSWideKeyword::Inherit => inherited, + CSSWideKeyword::Initial => !inherited, + }, + None => false, + }; self.seen.insert(physical_longhand_id); if origin == Origin::Author { self.author_specified.insert(physical_longhand_id); } - let unset = css_wide_keyword.map_or(false, |css_wide_keyword| match css_wide_keyword { - CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => inherited, - CSSWideKeyword::Initial => !inherited, - CSSWideKeyword::Revert => unreachable!(), - }); - - if unset { + if is_unset { continue; } diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 980d3de11ff..01b390f0fca 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -7,15 +7,17 @@ #![deny(missing_docs)] use super::*; +use crate::applicable_declarations::CascadePriority; use crate::context::QuirksMode; use crate::custom_properties::CustomPropertiesBuilder; use crate::error_reporting::{ContextualParseError, ParseErrorReporter}; use crate::parser::ParserContext; use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; +use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; use crate::shared_lock::Locked; use crate::str::{CssString, CssStringWriter}; -use crate::stylesheets::{CssRuleType, Origin, UrlExtraData}; +use crate::stylesheets::{CssRuleType, Origin, UrlExtraData, layer_rule::LayerOrder}; use crate::values::computed::Context; use cssparser::{parse_important, CowRcStr, DeclarationListParser, ParserInput}; use cssparser::{AtRuleParser, DeclarationParser, Delimiter, ParseErrorKind, Parser}; @@ -898,7 +900,7 @@ impl PropertyDeclarationBlock { for declaration in self.normal_declaration_iter() { if let PropertyDeclaration::Custom(ref declaration) = *declaration { - builder.cascade(declaration, Origin::Author); + builder.cascade(declaration, CascadePriority::new(CascadeLevel::same_tree_author_normal(), LayerOrder::root())); } } diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index e46024feba1..91be2d6011b 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -476,6 +476,7 @@ context.builder.inherit_${property.ident}(); % endif } + CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => unreachable!("Should never get here"), } return; diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 2f9e692f740..f9067c0e9a8 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -311,9 +311,9 @@ impl AnimationValue { % for prop in data.longhands: % if prop.animatable: LonghandId::${prop.camel_case} => { - // FIXME(emilio, bug 1533327): I think - // CSSWideKeyword::Revert handling is not fine here, but - // what to do instead? + // FIXME(emilio, bug 1533327): I think revert (and + // revert-layer) handling is not fine here, but what to + // do instead? // // Seems we'd need the computed value as if it was // revert, somehow. Treating it as `unset` seems fine @@ -321,6 +321,7 @@ impl AnimationValue { let style_struct = match declaration.keyword { % if not prop.style_struct.inherited: CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | CSSWideKeyword::Unset | % endif CSSWideKeyword::Initial => { @@ -328,6 +329,7 @@ impl AnimationValue { }, % if prop.style_struct.inherited: CSSWideKeyword::Revert | + CSSWideKeyword::RevertLayer | CSSWideKeyword::Unset | % endif CSSWideKeyword::Inherit => { diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 4ee388dfe6c..cdecfaae895 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1051,6 +1051,8 @@ pub enum CSSWideKeyword { Unset, /// The `revert` keyword. Revert, + /// The `revert-layer` keyword. + RevertLayer, } impl CSSWideKeyword { @@ -1060,22 +1062,28 @@ impl CSSWideKeyword { CSSWideKeyword::Inherit => "inherit", CSSWideKeyword::Unset => "unset", CSSWideKeyword::Revert => "revert", + CSSWideKeyword::RevertLayer => "revert-layer", } } } impl CSSWideKeyword { + /// Parses a CSS wide keyword from a CSS identifier. + pub fn from_ident(ident: &str) -> Result { + Ok(match_ignore_ascii_case! { ident, + "initial" => CSSWideKeyword::Initial, + "inherit" => CSSWideKeyword::Inherit, + "unset" => CSSWideKeyword::Unset, + "revert" => CSSWideKeyword::Revert, + "revert-layer" if static_prefs::pref!("layout.css.cascade-layers.enabled") => CSSWideKeyword::RevertLayer, + _ => return Err(()), + }) + } + fn parse(input: &mut Parser) -> Result { let keyword = { let ident = input.expect_ident().map_err(|_| ())?; - match_ignore_ascii_case! { ident, - // If modifying this set of keyword, also update values::CustomIdent::from_ident - "initial" => CSSWideKeyword::Initial, - "inherit" => CSSWideKeyword::Inherit, - "unset" => CSSWideKeyword::Unset, - "revert" => CSSWideKeyword::Revert, - _ => return Err(()), - } + Self::from_ident(ident)? }; input.expect_exhausted().map_err(|_| ())?; Ok(keyword) diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 021c0176416..513c2d91566 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.layer_order, block.specificity, block.source_order()) + (block.layer_order(), block.specificity, block.source_order()) }); } self.context.current_host = old_host; diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index ae1ba7bed94..fe1214faf65 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -4,8 +4,9 @@ #![allow(unsafe_code)] -use crate::properties::Importance; +use crate::applicable_declarations::CascadePriority; use crate::shared_lock::StylesheetGuards; +use crate::stylesheets::layer_rule::LayerOrder; use malloc_size_of::{MallocShallowSizeOf, MallocSizeOf, MallocSizeOfOps}; use parking_lot::RwLock; use smallvec::SmallVec; @@ -66,7 +67,7 @@ impl MallocSizeOf for RuleTree { } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -struct ChildKey(CascadeLevel, ptr::NonNull<()>); +struct ChildKey(CascadePriority, ptr::NonNull<()>); unsafe impl Send for ChildKey {} unsafe impl Sync for ChildKey {} @@ -219,8 +220,8 @@ struct RuleNode { /// None for the root node. source: Option, - /// The cascade level this rule is positioned at. - level: CascadeLevel, + /// The cascade level + layer order this rule is positioned at. + cascade_priority: CascadePriority, /// The refcount of this node. /// @@ -316,14 +317,14 @@ impl RuleNode { root: WeakRuleNode, parent: StrongRuleNode, source: StyleSource, - level: CascadeLevel, + cascade_priority: CascadePriority, ) -> Self { debug_assert!(root.p.parent.is_none()); RuleNode { root: Some(root), parent: Some(parent), source: Some(source), - level: level, + cascade_priority, refcount: AtomicUsize::new(1), children: Default::default(), approximate_free_count: AtomicUsize::new(0), @@ -336,7 +337,7 @@ impl RuleNode { root: None, parent: None, source: None, - level: CascadeLevel::UANormal, + cascade_priority: CascadePriority::new(CascadeLevel::UANormal, LayerOrder::root()), refcount: AtomicUsize::new(1), approximate_free_count: AtomicUsize::new(0), children: Default::default(), @@ -346,7 +347,7 @@ impl RuleNode { fn key(&self) -> ChildKey { ChildKey( - self.level, + self.cascade_priority, self.source .as_ref() .expect("Called key() on the root node") @@ -554,20 +555,20 @@ impl StrongRuleNode { &self, root: &StrongRuleNode, source: StyleSource, - level: CascadeLevel, + cascade_priority: CascadePriority, ) -> StrongRuleNode { use parking_lot::RwLockUpgradableReadGuard; debug_assert!( - self.p.level <= level, + self.p.cascade_priority <= cascade_priority, "Should be ordered (instead {:?} > {:?}), from {:?} and {:?}", - self.p.level, - level, + self.p.cascade_priority, + cascade_priority, self.p.source, source, ); - let key = ChildKey(level, source.key()); + let key = ChildKey(cascade_priority, source.key()); let children = self.p.children.upgradable_read(); if let Some(child) = children.get(&key, |node| node.p.key()) { // Sound to call because we read-locked the parent's children. @@ -584,7 +585,7 @@ impl StrongRuleNode { root.downgrade(), self.clone(), source, - level, + cascade_priority, ))); // Sound to call because we still own a strong reference to // this node, through the `node` variable itself that we are @@ -602,14 +603,22 @@ impl StrongRuleNode { self.p.source.as_ref() } - /// The cascade level for this node - pub fn cascade_level(&self) -> CascadeLevel { - self.p.level + /// The cascade priority. + #[inline] + pub fn cascade_priority(&self) -> CascadePriority { + self.p.cascade_priority } - /// Get the importance that this rule node represents. - pub fn importance(&self) -> Importance { - self.p.level.importance() + /// The cascade level. + #[inline] + pub fn cascade_level(&self) -> CascadeLevel { + self.cascade_priority().cascade_level() + } + + /// The importance. + #[inline] + pub fn importance(&self) -> crate::properties::Importance { + self.cascade_level().importance() } /// Returns whether this node has any child, only intended for testing diff --git a/components/style/rule_tree/level.rs b/components/style/rule_tree/level.rs index c46e63796ad..b8cbe55ed9c 100644 --- a/components/style/rule_tree/level.rs +++ b/components/style/rule_tree/level.rs @@ -29,7 +29,7 @@ use crate::stylesheets::Origin; /// [3]: https://html.spec.whatwg.org/multipage/#presentational-hints /// [4]: https://drafts.csswg.org/css-scoping/#shadow-cascading #[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)] pub enum CascadeLevel { /// Normal User-Agent rules. UANormal, @@ -69,82 +69,44 @@ pub enum CascadeLevel { } impl CascadeLevel { - /// Pack this cascade level in a single byte. - /// - /// We have 10 levels, which we can represent with 4 bits, and then a - /// cascade order optionally, which we can clamp to three bits max, and - /// represent with a fourth bit for the sign. - /// - /// So this creates: SOOODDDD - /// - /// Where `S` is the sign of the order (one if negative, 0 otherwise), `O` - /// is the absolute value of the order, and `D`s are the discriminant. - #[inline] - pub fn to_byte_lossy(&self) -> u8 { - let (discriminant, order) = match *self { - Self::UANormal => (0, 0), - Self::UserNormal => (1, 0), - Self::PresHints => (2, 0), + /// Convert this level from "unimportant" to "important". + pub fn important(&self) -> Self { + match *self { + Self::UANormal => Self::UAImportant, + Self::UserNormal => Self::UserImportant, Self::AuthorNormal { shadow_cascade_order, - } => (3, shadow_cascade_order.0), - Self::SMILOverride => (4, 0), - Self::Animations => (5, 0), - Self::AuthorImportant { - shadow_cascade_order, - } => (6, shadow_cascade_order.0), - Self::UserImportant => (7, 0), - Self::UAImportant => (8, 0), - Self::Transitions => (9, 0), - }; - - debug_assert_eq!(discriminant & 0xf, discriminant); - if order == 0 { - return discriminant; + } => Self::AuthorImportant { + shadow_cascade_order: -shadow_cascade_order, + }, + Self::PresHints | + Self::SMILOverride | + Self::Animations | + Self::AuthorImportant { .. } | + Self::UserImportant | + Self::UAImportant | + Self::Transitions => *self, } - - let negative = order < 0; - let value = std::cmp::min(order.abs() as u8, 0b111); - (negative as u8) << 7 | value << 4 | discriminant } - /// Convert back from the single-byte representation of the cascade level - /// explained above. - #[inline] - pub fn from_byte(b: u8) -> Self { - let order = { - let abs = ((b & 0b01110000) >> 4) as i8; - let negative = b & 0b10000000 != 0; - if negative { - -abs - } else { - abs - } - }; - let discriminant = b & 0xf; - let level = match discriminant { - 0 => Self::UANormal, - 1 => Self::UserNormal, - 2 => Self::PresHints, - 3 => { - return Self::AuthorNormal { - shadow_cascade_order: ShadowCascadeOrder(order), - } + /// Convert this level from "important" to "non-important". + pub fn unimportant(&self) -> Self { + match *self { + Self::UAImportant => Self::UANormal, + Self::UserImportant => Self::UserNormal, + Self::AuthorImportant { + shadow_cascade_order, + } => Self::AuthorNormal { + shadow_cascade_order: -shadow_cascade_order, }, - 4 => Self::SMILOverride, - 5 => Self::Animations, - 6 => { - return Self::AuthorImportant { - shadow_cascade_order: ShadowCascadeOrder(order), - } - }, - 7 => Self::UserImportant, - 8 => Self::UAImportant, - 9 => Self::Transitions, - _ => unreachable!("Didn't expect {} as a discriminant", discriminant), - }; - debug_assert_eq!(order, 0, "Didn't expect an order value for {:?}", level); - level + Self::PresHints | + Self::SMILOverride | + Self::Animations | + Self::AuthorNormal { .. } | + Self::UserNormal | + Self::UANormal | + Self::Transitions => *self, + } } /// Select a lock guard for this level @@ -231,6 +193,12 @@ impl CascadeLevel { pub struct ShadowCascadeOrder(i8); impl ShadowCascadeOrder { + /// We keep a maximum of 3 bits of order as a limit so that we can pack + /// CascadeLevel in one byte by using half of it for the order, if that ends + /// up being necessary. + const MAX: i8 = 0b111; + const MIN: i8 = -Self::MAX; + /// A level for the outermost shadow tree (the shadow tree we own, and the /// ones from the slots we're slotted in). #[inline] @@ -256,7 +224,9 @@ impl ShadowCascadeOrder { #[inline] pub fn dec(&mut self) { debug_assert!(self.0 < 0); - self.0 = self.0.saturating_sub(1); + if self.0 != Self::MIN { + self.0 -= 1; + } } /// The level, moving inwards. We should only move inwards if we're @@ -264,7 +234,9 @@ impl ShadowCascadeOrder { #[inline] pub fn inc(&mut self) { debug_assert_ne!(self.0, -1); - self.0 = self.0.saturating_add(1); + if self.0 != Self::MAX { + self.0 += 1; + } } } diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index e50382255ca..c8705165776 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -6,9 +6,10 @@ //! The rule tree. -use crate::applicable_declarations::ApplicableDeclarationList; +use crate::applicable_declarations::{ApplicableDeclarationList, CascadePriority}; use crate::properties::{LonghandIdSet, PropertyDeclarationBlock}; use crate::shared_lock::{Locked, StylesheetGuards}; +use crate::stylesheets::layer_rule::LayerOrder; use servo_arc::{Arc, ArcBorrow}; use smallvec::SmallVec; use std::io::{self, Write}; @@ -47,21 +48,22 @@ impl RuleTree { guards: &StylesheetGuards, ) -> StrongRuleNode where - I: Iterator, + I: Iterator, { use self::CascadeLevel::*; let mut current = self.root().clone(); let mut found_important = false; - let mut important_author = SmallVec::<[(StyleSource, ShadowCascadeOrder); 4]>::new(); - - let mut important_user = SmallVec::<[StyleSource; 4]>::new(); - let mut important_ua = SmallVec::<[StyleSource; 4]>::new(); + let mut important_author = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut important_user = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); + let mut important_ua = SmallVec::<[(StyleSource, CascadePriority); 4]>::new(); let mut transition = None; - for (source, level) in iter { + for (source, priority) in iter { + let level = priority.cascade_level(); debug_assert!(!level.is_important(), "Important levels handled internally"); + let any_important = { let pdb = source.read(level.guard(guards)); pdb.any_important() @@ -70,13 +72,9 @@ impl RuleTree { if any_important { found_important = true; match level { - AuthorNormal { - shadow_cascade_order, - } => { - important_author.push((source.clone(), shadow_cascade_order)); - }, - UANormal => important_ua.push(source.clone()), - UserNormal => important_user.push(source.clone()), + AuthorNormal { .. } => important_author.push((source.clone(), priority.important())), + UANormal => important_ua.push((source.clone(), priority.important())), + UserNormal => important_user.push((source.clone(), priority.important())), _ => {}, }; } @@ -98,7 +96,7 @@ impl RuleTree { debug_assert!(transition.is_none()); transition = Some(source); } else { - current = current.ensure_child(self.root(), source, level); + current = current.ensure_child(self.root(), source, priority); } } @@ -110,10 +108,8 @@ impl RuleTree { // Insert important declarations, in order of increasing importance, // followed by any transition rule. // - // Inner shadow wins over same-tree, which wins over outer-shadow. - // - // We negate the shadow cascade order to preserve the right PartialOrd - // behavior. + // Important rules are sorted differently from unimportant ones by + // shadow order and cascade order. if !important_author.is_empty() && important_author.first().unwrap().1 != important_author.last().unwrap().1 { @@ -129,29 +125,27 @@ impl RuleTree { // inside the same chunk already sorted. Seems like we could try to // keep a SmallVec-of-SmallVecs with the chunks and just iterate the // outer in reverse. - important_author.sort_by_key(|&(_, order)| -order); + important_author.sort_by_key(|&(_, priority)| priority); } - for (source, shadow_cascade_order) in important_author.drain(..) { - current = current.ensure_child( - self.root(), - source, - AuthorImportant { - shadow_cascade_order: -shadow_cascade_order, - }, - ); + for (source, priority) in important_author.drain(..) { + current = current.ensure_child(self.root(), source, priority); } - for source in important_user.drain(..) { - current = current.ensure_child(self.root(), source, UserImportant); + for (source, priority) in important_user.drain(..) { + current = current.ensure_child(self.root(), source, priority); } - for source in important_ua.drain(..) { - current = current.ensure_child(self.root(), source, UAImportant); + for (source, priority) in important_ua.drain(..) { + current = current.ensure_child(self.root(), source, priority); } if let Some(source) = transition { - current = current.ensure_child(self.root(), source, Transitions); + current = current.ensure_child( + self.root(), + source, + CascadePriority::new(Transitions, LayerOrder::root()), + ); } current @@ -174,18 +168,18 @@ impl RuleTree { /// return the corresponding rule node representing the last inserted one. pub fn insert_ordered_rules<'a, I>(&self, iter: I) -> StrongRuleNode where - I: Iterator, + I: Iterator, { self.insert_ordered_rules_from(self.root().clone(), iter) } fn insert_ordered_rules_from<'a, I>(&self, from: StrongRuleNode, iter: I) -> StrongRuleNode where - I: Iterator, + I: Iterator, { let mut current = from; - for (source, level) in iter { - current = current.ensure_child(self.root(), source, level); + for (source, priority) in iter { + current = current.ensure_child(self.root(), source, priority); } current } @@ -197,6 +191,7 @@ impl RuleTree { pub fn update_rule_at_level( &self, level: CascadeLevel, + layer_order: LayerOrder, pdb: Option>>, path: &StrongRuleNode, guards: &StylesheetGuards, @@ -209,10 +204,10 @@ impl RuleTree { // First walk up until the first less-or-equally specific rule. let mut children = SmallVec::<[_; 10]>::new(); - while current.cascade_level() > level { + while current.cascade_priority().cascade_level() > level { children.push(( current.style_source().unwrap().clone(), - current.cascade_level(), + current.cascade_priority(), )); current = current.parent().unwrap().clone(); } @@ -227,7 +222,7 @@ impl RuleTree { // to special-case (isn't hard, it's just about removing the `if` and // special cases, and replacing them for a `while` loop, avoiding the // optimizations). - if current.cascade_level() == level { + if current.cascade_priority().cascade_level() == level { *important_rules_changed |= level.is_important(); let current_decls = current.style_source().unwrap().as_declarations(); @@ -267,7 +262,7 @@ impl RuleTree { current = current.ensure_child( self.root(), StyleSource::from_declarations(pdb.clone_arc()), - level, + CascadePriority::new(level, layer_order), ); *important_rules_changed = true; } @@ -276,7 +271,7 @@ impl RuleTree { current = current.ensure_child( self.root(), StyleSource::from_declarations(pdb.clone_arc()), - level, + CascadePriority::new(level, layer_order), ); } } @@ -312,7 +307,10 @@ impl RuleTree { let mut children = SmallVec::<[_; 10]>::new(); for node in iter { if !node.cascade_level().is_animation() { - children.push((node.style_source().unwrap().clone(), node.cascade_level())); + children.push(( + node.style_source().unwrap().clone(), + node.cascade_priority(), + )); } last = node; } @@ -336,6 +334,7 @@ impl RuleTree { let mut dummy = false; self.update_rule_at_level( CascadeLevel::Transitions, + LayerOrder::root(), Some(pdb.borrow_arc()), path, guards, diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index ee066d813e2..1b7b0cb169e 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -19,14 +19,16 @@ use smallvec::SmallVec; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; -/// The order of a given layer. -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)] -pub struct LayerOrder(u32); +/// The order of a given layer. We use 16 bits so that we can pack LayerOrder +/// and CascadeLevel in a single 32-bit struct. If we need more bits we can go +/// back to packing CascadeLevel in a single byte as we did before. +#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, Ord)] +pub struct LayerOrder(u16); impl LayerOrder { /// The order of the root layer. pub const fn root() -> Self { - Self(std::u32::MAX) + Self(std::u16::MAX) } /// The first cascade layer order. @@ -37,7 +39,9 @@ impl LayerOrder { /// Increment the cascade layer order. #[inline] pub fn inc(&mut self) { - self.0 += 1; + if self.0 != std::u16::MAX { + self.0 += 1; + } } } diff --git a/components/style/stylesheets/origin.rs b/components/style/stylesheets/origin.rs index a65b61fca13..27ad3fa184a 100644 --- a/components/style/stylesheets/origin.rs +++ b/components/style/stylesheets/origin.rs @@ -10,7 +10,7 @@ use std::ops::BitOrAssign; /// Each style rule has an origin, which determines where it enters the cascade. /// /// -#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)] +#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, PartialOrd, Ord)] #[repr(u8)] pub enum Origin { /// diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a48a7103f84..c0cc2f238bb 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -4,7 +4,9 @@ //! Selector matching. -use crate::applicable_declarations::{ApplicableDeclarationBlock, ApplicableDeclarationList}; +use crate::applicable_declarations::{ + ApplicableDeclarationBlock, ApplicableDeclarationList, CascadePriority, +}; use crate::context::{CascadeInputs, QuirksMode}; use crate::dom::{TElement, TShadowRoot}; use crate::element_state::{DocumentState, ElementState}; @@ -1474,9 +1476,15 @@ impl Stylist { /* pseudo = */ None, self.rule_tree.root(), guards, - block - .declaration_importance_iter() - .map(|(declaration, _)| (declaration, Origin::Author)), + block.declaration_importance_iter().map(|(declaration, _)| { + ( + declaration, + CascadePriority::new( + CascadeLevel::same_tree_author_normal(), + LayerOrder::root(), + ), + ) + }), Some(parent_style), Some(parent_style), Some(parent_style), @@ -1582,7 +1590,7 @@ impl ExtraStyleData { &mut self, guard: &SharedRwLockReadGuard, rule: &Arc>, - )-> Result<(), FailedAllocationError> { + ) -> Result<(), FailedAllocationError> { let name = rule.read_with(guard).name.as_atom().clone(); self.scroll_timelines .try_insert(name, rule.clone()) diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 626fc32ff18..ad09320c42b 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -438,15 +438,22 @@ impl CustomIdent { ident: &CowRcStr<'i>, excluding: &[&str], ) -> Result> { - let valid = match_ignore_ascii_case! { ident, - "initial" | "inherit" | "unset" | "default" | "revert" => false, - _ => true - }; - if !valid { + use crate::properties::CSSWideKeyword; + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // The CSS-wide keywords are not valid s. The default + // keyword is reserved and is also not a valid . + // + if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { return Err( location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())) ); } + + // https://drafts.csswg.org/css-values-4/#custom-idents: + // + // Excluded keywords are excluded in all ASCII case permutations. + // if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) { Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } else { From 5c0f044d39276a263bf363a754fe431c366ca884 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 6 Jun 2023 17:11:32 +0200 Subject: [PATCH 37/81] Further changes required by Servo --- components/style/matching.rs | 2 ++ components/style/properties/properties.mako.rs | 10 +++++++++- tests/unit/style/custom_properties.rs | 9 +++++++-- tests/unit/style/rule_tree/bench.rs | 9 ++++++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/components/style/matching.rs b/components/style/matching.rs index 92fdee3f97b..f2d300df4b2 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -513,12 +513,14 @@ trait PrivateMatchMethods: TElement { Self::replace_single_rule_node( &context.shared, CascadeLevel::Transitions, + LayerOrder::root(), declarations.transitions.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); Self::replace_single_rule_node( &context.shared, CascadeLevel::Animations, + LayerOrder::root(), declarations.animations.as_ref().map(|a| a.borrow_arc()), &mut rule_node, ); diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index cdecfaae895..dee66cd59b0 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1067,6 +1067,14 @@ impl CSSWideKeyword { } } +#[inline] +fn cascade_layes_enabled() -> bool { + #[cfg(feature = "gecko")] + return static_prefs::pref!("layout.css.cascade-layers.enabled"); + #[cfg(feature = "servo")] + return false; +} + impl CSSWideKeyword { /// Parses a CSS wide keyword from a CSS identifier. pub fn from_ident(ident: &str) -> Result { @@ -1075,7 +1083,7 @@ impl CSSWideKeyword { "inherit" => CSSWideKeyword::Inherit, "unset" => CSSWideKeyword::Unset, "revert" => CSSWideKeyword::Revert, - "revert-layer" if static_prefs::pref!("layout.css.cascade-layers.enabled") => CSSWideKeyword::RevertLayer, + "revert-layer" if cascade_layes_enabled() => CSSWideKeyword::RevertLayer, _ => return Err(()), }) } diff --git a/tests/unit/style/custom_properties.rs b/tests/unit/style/custom_properties.rs index 1e4ab0a7282..21ce2ba3f86 100644 --- a/tests/unit/style/custom_properties.rs +++ b/tests/unit/style/custom_properties.rs @@ -5,13 +5,15 @@ use cssparser::{Parser, ParserInput}; use euclid::{Scale, Size2D}; use servo_arc::Arc; +use style::applicable_declarations::CascadePriority; use style::context::QuirksMode; use style::custom_properties::{ CustomPropertiesBuilder, CustomPropertiesMap, Name, SpecifiedValue, }; use style::media_queries::{Device, MediaType}; use style::properties::{CustomDeclaration, CustomDeclarationValue}; -use style::stylesheets::Origin; +use style::rule_tree::CascadeLevel; +use style::stylesheets::layer_rule::LayerOrder; use test::{self, Bencher}; fn cascade( @@ -38,7 +40,10 @@ fn cascade( let mut builder = CustomPropertiesBuilder::new(inherited, &device); for declaration in &declarations { - builder.cascade(declaration, Origin::Author); + builder.cascade( + declaration, + CascadePriority::new(CascadeLevel::same_tree_author_normal(), LayerOrder::root()), + ); } builder.build() diff --git a/tests/unit/style/rule_tree/bench.rs b/tests/unit/style/rule_tree/bench.rs index 9da1eb698e3..d7de685fbd8 100644 --- a/tests/unit/style/rule_tree/bench.rs +++ b/tests/unit/style/rule_tree/bench.rs @@ -6,12 +6,14 @@ use cssparser::SourceLocation; use rayon; use servo_arc::Arc; use servo_url::ServoUrl; +use style::applicable_declarations::CascadePriority; use style::context::QuirksMode; use style::error_reporting::{ContextualParseError, ParseErrorReporter}; use style::media_queries::MediaList; use style::properties::{longhands, Importance, PropertyDeclaration, PropertyDeclarationBlock}; use style::rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource}; use style::shared_lock::{SharedRwLock, StylesheetGuards}; +use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::{AllowImportRules, CssRule, Origin, Stylesheet}; use style::thread_state::{self, ThreadState}; use test::{self, Bencher}; @@ -85,7 +87,12 @@ fn parse_rules(lock: &SharedRwLock, css: &str) -> Vec<(StyleSource, CascadeLevel } fn test_insertion(rule_tree: &RuleTree, rules: Vec<(StyleSource, CascadeLevel)>) -> StrongRuleNode { - rule_tree.insert_ordered_rules(rules.into_iter()) + rule_tree.insert_ordered_rules(rules.into_iter().map(|(style_source, cascade_level)| { + ( + style_source, + CascadePriority::new(cascade_level, LayerOrder::root()), + ) + })) } fn test_insertion_style_attribute( From 50510715a2980693bea92e7dcaf583da4156fcbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 17:28:49 +0200 Subject: [PATCH 38/81] style: Fix style attribute important and revert-layer behavior By modeling it as a separate layer that behaves somewhat specially. See https://github.com/w3c/csswg-drafts/issues/6872. The remaining revert-layer tests that we fail are because either we don't implement a feature (like @property) or because it's used in keyframes (where revert is a bit unspecified and we have existing issues with it). Differential Revision: https://phabricator.services.mozilla.com/D133373 --- components/style/applicable_declarations.rs | 56 ++++++++++++--------- components/style/gecko/wrapper.rs | 14 ++++-- components/style/rule_collector.rs | 6 ++- components/style/rule_tree/mod.rs | 4 +- components/style/stylesheets/layer_rule.rs | 18 ++++++- 5 files changed, 67 insertions(+), 31 deletions(-) diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index cdb78d05405..5849e7c2250 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -45,7 +45,9 @@ pub struct CascadePriority { #[allow(dead_code)] fn size_assert() { #[allow(unsafe_code)] - unsafe { std::mem::transmute::(0u32) }; + unsafe { + std::mem::transmute::(0u32) + }; } impl PartialOrd for CascadePriority { @@ -57,34 +59,39 @@ impl PartialOrd for CascadePriority { impl Ord for CascadePriority { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.cascade_level - .cmp(&other.cascade_level) - .then_with(|| { - let ordering = self.layer_order.cmp(&other.layer_order); - // https://drafts.csswg.org/css-cascade-5/#cascade-layering - // - // Cascade layers (like declarations) are ordered by order - // of appearance. When comparing declarations that belong to - // different layers, then for normal rules the declaration - // whose cascade layer is last wins, and for important rules - // the declaration whose cascade layer is first wins. - // - // FIXME: This creates somewhat surprising behavior for the - // style attribute, see - // https://github.com/w3c/csswg-drafts/issues/6872 - if self.cascade_level.is_important() { - ordering.reverse() - } else { - ordering - } - }) + self.cascade_level.cmp(&other.cascade_level).then_with(|| { + let ordering = self.layer_order.cmp(&other.layer_order); + if ordering == std::cmp::Ordering::Equal { + return ordering; + } + // https://drafts.csswg.org/css-cascade-5/#cascade-layering + // + // Cascade layers (like declarations) are ordered by order + // of appearance. When comparing declarations that belong to + // different layers, then for normal rules the declaration + // whose cascade layer is last wins, and for important rules + // the declaration whose cascade layer is first wins. + // + // But the style attribute layer for some reason is special. + if self.cascade_level.is_important() && + !self.layer_order.is_style_attribute_layer() && + !other.layer_order.is_style_attribute_layer() + { + ordering.reverse() + } else { + ordering + } + }) } } impl CascadePriority { /// Construct a new CascadePriority for a given (level, order) pair. pub fn new(cascade_level: CascadeLevel, layer_order: LayerOrder) -> Self { - Self { cascade_level, layer_order } + Self { + cascade_level, + layer_order, + } } /// Returns the layer order. @@ -149,12 +156,13 @@ impl ApplicableDeclarationBlock { pub fn from_declarations( declarations: Arc>, level: CascadeLevel, + layer_order: LayerOrder, ) -> Self { ApplicableDeclarationBlock { source: StyleSource::from_declarations(declarations), source_order: 0, specificity: 0, - cascade_priority: CascadePriority::new(level, LayerOrder::root()), + cascade_priority: CascadePriority::new(level, layer_order), } } diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 4350135a4f2..2418591963b 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1579,6 +1579,7 @@ impl<'le> TElement for GeckoElement<'le> { use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign; use crate::values::specified::color::Color; + use crate::stylesheets::layer_rule::LayerOrder; lazy_static! { static ref TH_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1587,7 +1588,7 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) }; static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1596,7 +1597,7 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) }; static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1605,7 +1606,7 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) }; static ref SVG_TEXT_DISABLE_ZOOM_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1614,7 +1615,7 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints) + ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) }; }; @@ -1642,6 +1643,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } let declarations = unsafe { Gecko_GetExtraContentStyleDeclarations(self.0).as_ref() }; @@ -1651,6 +1653,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } @@ -1678,6 +1681,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } @@ -1693,6 +1697,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( decl.clone_arc(), ServoCascadeLevel::PresHints, + LayerOrder::root(), )); } } @@ -1714,6 +1719,7 @@ impl<'le> TElement for GeckoElement<'le> { hints.push(ApplicableDeclarationBlock::from_declarations( arc, ServoCascadeLevel::PresHints, + LayerOrder::root(), )) } // MathML's default lang has precedence over both `lang` and `xml:lang` diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index 513c2d91566..dfd4d2bd316 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -11,7 +11,7 @@ use crate::rule_tree::{CascadeLevel, ShadowCascadeOrder}; use crate::selector_map::SelectorMap; use crate::selector_parser::PseudoElement; use crate::shared_lock::Locked; -use crate::stylesheets::Origin; +use crate::stylesheets::{layer_rule::LayerOrder, Origin}; use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist}; use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; @@ -450,6 +450,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( sa.clone_arc(), CascadeLevel::same_tree_author_normal(), + LayerOrder::style_attribute(), )); } } @@ -460,6 +461,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( so.clone_arc(), CascadeLevel::SMILOverride, + LayerOrder::root(), )); } @@ -471,6 +473,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( anim, CascadeLevel::Animations, + LayerOrder::root(), )); } @@ -481,6 +484,7 @@ where .push(ApplicableDeclarationBlock::from_declarations( anim, CascadeLevel::Transitions, + LayerOrder::root(), )); } } diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index c8705165776..c2339ee9907 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -72,7 +72,9 @@ impl RuleTree { if any_important { found_important = true; match level { - AuthorNormal { .. } => important_author.push((source.clone(), priority.important())), + AuthorNormal { .. } => { + important_author.push((source.clone(), priority.important())) + }, UANormal => important_ua.push((source.clone(), priority.important())), UserNormal => important_user.push((source.clone(), priority.important())), _ => {}, diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index 1b7b0cb169e..f77a1e7c50e 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -28,9 +28,25 @@ pub struct LayerOrder(u16); impl LayerOrder { /// The order of the root layer. pub const fn root() -> Self { + Self(std::u16::MAX - 1) + } + + /// The order of the style attribute layer. + pub const fn style_attribute() -> Self { Self(std::u16::MAX) } + /// Returns whether this layer is for the style attribute, which behaves + /// differently in terms of !important, see + /// https://github.com/w3c/csswg-drafts/issues/6872 + /// + /// (This is a bit silly, mind-you, but it's needed so that revert-layer + /// behaves correctly). + #[inline] + pub fn is_style_attribute_layer(&self) -> bool { + *self == Self::style_attribute() + } + /// The first cascade layer order. pub const fn first() -> Self { Self(0) @@ -39,7 +55,7 @@ impl LayerOrder { /// Increment the cascade layer order. #[inline] pub fn inc(&mut self) { - if self.0 != std::u16::MAX { + if self.0 != std::u16::MAX - 1 { self.0 += 1; } } From 1ac55889bbbdaf504cf77f230a9c93cd2f40f428 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 6 Jun 2023 21:27:52 +0200 Subject: [PATCH 39/81] Further changes required by Servo --- components/script/dom/element.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 8a7b98a8756..2e94e635b26 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -130,6 +130,7 @@ use style::selector_parser::{ NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser, }; use style::shared_lock::{Locked, SharedRwLock}; +use style::stylesheets::layer_rule::LayerOrder; use style::stylesheets::CssRuleType; use style::thread_state; use style::values::generics::NonNegative; @@ -665,6 +666,7 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> { Importance::Normal, ))), CascadeLevel::PresHints, + LayerOrder::root(), ) } From dff8f78c4201f7ba799e00ae6cd179658e6b626b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 17:30:03 +0200 Subject: [PATCH 40/81] style: Update CSSOM for layer rules to the spec Pretty mechanical. Tests are in https://wpt.live/css/css-cascade/layer-rules-cssom.html which (with a fix for @import tests which I'll submit separately) we pass. Sync for that is bug 1743936. Differential Revision: https://phabricator.services.mozilla.com/D133387 --- components/style/gecko/arc_types.rs | 18 ++- components/style/gecko/wrapper.rs | 26 +++- components/style/invalidation/stylesheets.rs | 6 +- components/style/stylesheets/layer_rule.rs | 133 +++++++++--------- components/style/stylesheets/mod.rs | 31 ++-- components/style/stylesheets/rule_parser.rs | 20 +-- .../style/stylesheets/rules_iterator.rs | 10 +- components/style/stylesheets/stylesheet.rs | 3 +- components/style/stylist.rs | 49 +++---- 9 files changed, 155 insertions(+), 141 deletions(-) diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 05fcdb5b12d..4fb08c3a309 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -12,9 +12,10 @@ 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, RawServoScrollTimelineRule, - RawServoStyleRule, RawServoStyleSheetContents, RawServoSupportsRule, ServoCssRules, + RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList, + RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, + RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents, + RawServoSupportsRule, ServoCssRules, }; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong}; use crate::media_queries::MediaList; @@ -24,8 +25,8 @@ use crate::shared_lock::Locked; use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule, - KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, ScrollTimelineRule, StyleRule, - StylesheetContents, SupportsRule, + KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule, + ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, }; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -73,8 +74,11 @@ 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 => RawServoLayerBlockRule + [Servo_LayerBlockRule_AddRef, Servo_LayerBlockRule_Release]); + +impl_arc_ffi!(Locked => RawServoLayerStatementRule + [Servo_LayerStatementRule_AddRef, Servo_LayerStatementRule_Release]); impl_arc_ffi!(Locked => RawServoMediaList [Servo_MediaList_AddRef, Servo_MediaList_Release]); diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 2418591963b..b289fefb4bd 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -1578,8 +1578,8 @@ impl<'le> TElement for GeckoElement<'le> { use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom; use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor; use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign; - use crate::values::specified::color::Color; use crate::stylesheets::layer_rule::LayerOrder; + use crate::values::specified::color::Color; lazy_static! { static ref TH_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1588,7 +1588,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1597,7 +1601,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; static ref MATHML_LANG_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1606,7 +1614,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; static ref SVG_TEXT_DISABLE_ZOOM_RULE: ApplicableDeclarationBlock = { let global_style_data = &*GLOBAL_STYLE_DATA; @@ -1615,7 +1627,11 @@ impl<'le> TElement for GeckoElement<'le> { Importance::Normal, ); let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb)); - ApplicableDeclarationBlock::from_declarations(arc, ServoCascadeLevel::PresHints, LayerOrder::root()) + ApplicableDeclarationBlock::from_declarations( + arc, + ServoCascadeLevel::PresHints, + LayerOrder::root(), + ) }; }; diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 1d1111170ed..3a9017582b2 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -541,6 +541,7 @@ impl StylesheetInvalidationSet { Page(..) | Viewport(..) | FontFeatureValues(..) | + LayerStatement(..) | FontFace(..) | Keyframes(..) | ScrollTimeline(..) | @@ -556,7 +557,7 @@ impl StylesheetInvalidationSet { self.collect_invalidations_for_rule(rule, guard, device, quirks_mode) }, - Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { + Document(..) | Import(..) | Media(..) | Supports(..) | LayerBlock(..) => { if !is_generic_change && !EffectiveRules::is_effective(guard, device, quirks_mode, rule) { @@ -597,7 +598,8 @@ impl StylesheetInvalidationSet { } } }, - Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => { + Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | + LayerStatement(..) | LayerBlock(..) => { // 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 index f77a1e7c50e..3607e9f2ebf 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -159,71 +159,34 @@ impl ToCss for LayerName { } } -/// The kind of layer rule this is. #[derive(Debug, ToShmem)] -pub enum LayerRuleKind { - /// A block `@layer ? { ... }` - Block { - /// The layer name, or `None` if anonymous. - name: Option, - /// The nested rules. - rules: Arc>, - }, - /// A statement `@layer , , ;` - Statement { - /// The list of layers to sort. - names: Vec, - }, -} - -/// A [`@layer`][layer] rule. -/// -/// [layer]: https://drafts.csswg.org/css-cascade-5/#layering -#[derive(Debug, ToShmem)] -pub struct LayerRule { - /// The kind of layer rule we are. - pub kind: LayerRuleKind, - /// The source position where this media rule was found. +/// A block `@layer ? { ... }` +/// https://drafts.csswg.org/css-cascade-5/#layer-block +pub struct LayerBlockRule { + /// The layer name, or `None` if anonymous. + pub name: Option, + /// The nested rules. + pub rules: Arc>, + /// The source position where this rule was found. pub source_location: SourceLocation, } -impl ToCssWithGuard for LayerRule { +impl ToCssWithGuard for LayerBlockRule { fn to_css( &self, guard: &SharedRwLockReadGuard, dest: &mut crate::str::CssStringWriter, ) -> fmt::Result { dest.write_str("@layer")?; - match self.kind { - LayerRuleKind::Block { - ref name, - ref rules, - } => { - if let Some(ref name) = *name { - dest.write_char(' ')?; - name.to_css(&mut CssWriter::new(dest))?; - } - rules.read_with(guard).to_css_block(guard, dest) - }, - LayerRuleKind::Statement { ref names } => { - let mut writer = CssWriter::new(dest); - let mut first = true; - for name in &**names { - if first { - writer.write_char(' ')?; - } else { - writer.write_str(", ")?; - } - first = false; - name.to_css(&mut writer)?; - } - dest.write_char(';') - }, + if let Some(ref name) = self.name { + dest.write_char(' ')?; + name.to_css(&mut CssWriter::new(dest))?; } + self.rules.read_with(guard).to_css_block(guard, dest) } } -impl DeepCloneWithLock for LayerRule { +impl DeepCloneWithLock for LayerBlockRule { fn deep_clone_with_lock( &self, lock: &SharedRwLock, @@ -231,25 +194,57 @@ impl DeepCloneWithLock for LayerRule { 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(), - }, - }, + name: self.name.clone(), + rules: Arc::new( + lock.wrap( + self.rules + .read_with(guard) + .deep_clone_with_lock(lock, guard, params), + ), + ), source_location: self.source_location.clone(), } } } + +/// A statement `@layer , , ;` +/// +/// https://drafts.csswg.org/css-cascade-5/#layer-empty +#[derive(Clone, Debug, ToShmem)] +pub struct LayerStatementRule { + /// The list of layers to sort. + pub names: Vec, + /// The source position where this rule was found. + pub source_location: SourceLocation, +} + +impl ToCssWithGuard for LayerStatementRule { + fn to_css( + &self, + _: &SharedRwLockReadGuard, + dest: &mut crate::str::CssStringWriter, + ) -> fmt::Result { + let mut writer = CssWriter::new(dest); + writer.write_str("@layer ")?; + let mut first = true; + for name in &*self.names { + if !first { + writer.write_str(", ")?; + } + first = false; + name.to_css(&mut writer)?; + } + writer.write_char(';') + } +} + +impl DeepCloneWithLock for LayerStatementRule { + fn deep_clone_with_lock( + &self, + _: &SharedRwLock, + _: &SharedRwLockReadGuard, + _: &DeepCloneParams, + ) -> Self { + self.clone() + } +} diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index f7098e0c151..f6348c6190d 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -51,7 +51,7 @@ pub use self::font_face_rule::FontFaceRule; pub use self::font_feature_values_rule::FontFeatureValuesRule; pub use self::import_rule::ImportRule; pub use self::keyframes_rule::KeyframesRule; -pub use self::layer_rule::LayerRule; +pub use self::layer_rule::{LayerBlockRule, LayerStatementRule}; pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; @@ -261,7 +261,8 @@ pub enum CssRule { Supports(Arc>), Page(Arc>), Document(Arc>), - Layer(Arc>), + LayerBlock(Arc>), + LayerStatement(Arc>), ScrollTimeline(Arc>), } @@ -304,9 +305,8 @@ impl CssRule { lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops) }, - // TODO(emilio): Add memory reporting for @layer rules. - CssRule::Layer(_) => 0, - CssRule::ScrollTimeline(_) => 0, + // TODO(emilio): Add memory reporting for these rules. + CssRule::LayerBlock(_) | CssRule::LayerStatement(_) | CssRule::ScrollTimeline(_) => 0, } } } @@ -341,8 +341,9 @@ pub enum CssRuleType { Viewport = 15, // After viewport, all rules should return 0 from the API, but we still need // a constant somewhere. - Layer = 16, - ScrollTimeline = 17, + LayerBlock = 16, + LayerStatement = 17, + ScrollTimeline = 18, } #[allow(missing_docs)] @@ -369,7 +370,8 @@ impl CssRule { CssRule::Supports(_) => CssRuleType::Supports, CssRule::Page(_) => CssRuleType::Page, CssRule::Document(_) => CssRuleType::Document, - CssRule::Layer(_) => CssRuleType::Layer, + CssRule::LayerBlock(_) => CssRuleType::LayerBlock, + CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, } } @@ -504,9 +506,15 @@ impl DeepCloneWithLock for CssRule { lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, - CssRule::Layer(ref arc) => { + CssRule::LayerStatement(ref arc) => { let rule = arc.read_with(guard); - CssRule::Layer(Arc::new( + CssRule::LayerStatement(Arc::new( + lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), + )) + }, + CssRule::LayerBlock(ref arc) => { + let rule = arc.read_with(guard); + CssRule::LayerBlock(Arc::new( lock.wrap(rule.deep_clone_with_lock(lock, guard, params)), )) }, @@ -534,7 +542,8 @@ impl ToCssWithGuard for CssRule { CssRule::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Document(ref lock) => lock.read_with(guard).to_css(guard, dest), - CssRule::Layer(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest), + CssRule::LayerStatement(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 12ec7226bfc..3c0d8a5c231 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -17,14 +17,14 @@ 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::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule}; use crate::stylesheets::scroll_timeline_rule::ScrollTimelineDescriptors; use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::{ viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, - FontFeatureValuesRule, KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, - RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, + FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, RulesMutateError, + ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, }; use crate::values::computed::font::FamilyName; use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName}; @@ -613,13 +613,13 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { 0 | 1 => names.into_iter().next(), _ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)), }; - Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { - kind: LayerRuleKind::Block { + Ok(CssRule::LayerBlock(Arc::new(self.shared_lock.wrap( + LayerBlockRule { name, - rules: self.parse_nested_rules(input, CssRuleType::Layer), + rules: self.parse_nested_rules(input, CssRuleType::LayerBlock), + source_location: start.source_location(), }, - source_location: start.source_location(), - })))) + )))) }, AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { // These rules don't have blocks. @@ -654,8 +654,8 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { if names.is_empty() { return Err(()); } - CssRule::Layer(Arc::new(self.shared_lock.wrap(LayerRule { - kind: LayerRuleKind::Statement { names }, + CssRule::LayerStatement(Arc::new(self.shared_lock.wrap(LayerStatementRule { + names, source_location: start.source_location(), }))) }, diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index 0cbc7327441..417185953a0 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -70,6 +70,7 @@ where CssRule::Keyframes(_) | CssRule::ScrollTimeline(_) | CssRule::Page(_) | + CssRule::LayerStatement(_) | CssRule::FontFeatureValues(_) => None, CssRule::Import(ref import_rule) => { let import_rule = import_rule.read_with(guard); @@ -103,14 +104,9 @@ where } Some(supports_rule.rules.read_with(guard).0.iter()) }, - CssRule::Layer(ref lock) => { - use crate::stylesheets::layer_rule::LayerRuleKind; - + CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - match layer_rule.kind { - LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()), - LayerRuleKind::Statement { .. } => None, - } + Some(layer_rule.rules.read_with(guard).0.iter()) }, } } diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index e63d8187365..ab7df841d7f 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -363,7 +363,8 @@ impl SanitizationKind { 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::LayerStatement(..) | + CssRule::LayerBlock(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, diff --git a/components/style/stylist.rs b/components/style/stylist.rs index c0cc2f238bb..a99de9d2a87 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -2516,35 +2516,25 @@ impl CascadeData { self.effective_media_query_results.saw_effective(media_rule); } }, - CssRule::Layer(ref lock) => { - use crate::stylesheets::layer_rule::LayerRuleKind; - + CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - match layer_rule.kind { - LayerRuleKind::Block { ref name, .. } => { - children_layer_id = maybe_register_layers( - self, - name.as_ref(), - &mut current_layer, - &mut layer_names_to_pop, - ); - }, - LayerRuleKind::Statement { ref names } => { - for name in &**names { - let mut pushed = 0; - // There are no children, so we can ignore the - // return value. - maybe_register_layers( - self, - Some(name), - &mut current_layer, - &mut pushed, - ); - for _ in 0..pushed { - current_layer.0.pop(); - } - } - }, + children_layer_id = maybe_register_layers( + self, + layer_rule.name.as_ref(), + &mut current_layer, + &mut layer_names_to_pop, + ); + }, + CssRule::LayerStatement(ref lock) => { + let layer_rule = lock.read_with(guard); + for name in &*layer_rule.names { + let mut pushed = 0; + // There are no children, so we can ignore the + // return value. + maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed); + for _ in 0..pushed { + current_layer.0.pop(); + } } }, // We don't care about any other rule. @@ -2660,7 +2650,8 @@ impl CascadeData { CssRule::Page(..) | CssRule::Viewport(..) | CssRule::Document(..) | - CssRule::Layer(..) | + CssRule::LayerBlock(..) | + CssRule::LayerStatement(..) | CssRule::FontFeatureValues(..) => { // Not affected by device changes. continue; From 98b49f8023453f5d77b715601e6323287f6ece63 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 6 Jun 2023 21:49:08 +0200 Subject: [PATCH 41/81] Further changes required by Servo --- components/script/dom/cssrule.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 853a9c9b643..e23e6721301 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -105,7 +105,8 @@ impl CSSRule { }, StyleCssRule::Page(_) => unreachable!(), StyleCssRule::Document(_) => unimplemented!(), // TODO - StyleCssRule::Layer(_) => unimplemented!(), // TODO + StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO + StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO } } From 9e7b8e92022cdb63871a8c4c4c904f2ccff1ee32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 17:31:12 +0200 Subject: [PATCH 42/81] style: Don't draw window decorations when painting headerbar on wayland This is a better fix for the double decorations than clipping them using CSS. Differential Revision: https://phabricator.services.mozilla.com/D133871 --- 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 be30e5f0b6f..c80ace2015b 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -652,7 +652,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; 58] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 57] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -919,7 +919,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("-moz-gtk-csd-reversed-placement"), GTKCSDReversedPlacement ), - lnf_int_feature!(atom!("-moz-gtk-wayland"), GTKWayland), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), bool_pref_feature!( atom!("-moz-proton-places-tooltip"), From da29cade57858d7f1d721afd3a452005940bdbfb Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Tue, 6 Jun 2023 17:33:44 +0200 Subject: [PATCH 43/81] style: Add CSS support for the hyphenate-character property Differential Revision: https://phabricator.services.mozilla.com/D133889 --- .../longhands/inherited_text.mako.rs | 12 +++++++++++ components/style/values/computed/mod.rs | 1 + components/style/values/computed/text.rs | 1 + components/style/values/specified/mod.rs | 1 + components/style/values/specified/text.rs | 21 +++++++++++++++++++ 5 files changed, 36 insertions(+) diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index 3a66eee9a3f..9065de3145c 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -373,3 +373,15 @@ ${helpers.predefined_type( animation_value_type="discrete", spec="https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property", )} + +// hyphenation character +${helpers.predefined_type( + "hyphenate-character", + "HyphenateCharacter", + "computed::HyphenateCharacter::Auto", + engines="gecko", + gecko_pref="layout.css.hyphenate-character.enabled", + has_effect_on_gecko_scrollbars=False, + animation_value_type="discrete", + spec="https://www.w3.org/TR/css-text-4/#hyphenate-character", +)} diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 6990fc65fe6..c18b9e09b71 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -89,6 +89,7 @@ pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight}; pub use self::text::{OverflowWrap, RubyPosition, TextOverflow, WordBreak, WordSpacing}; pub use self::text::{TextAlign, TextAlignLast, TextEmphasisPosition, TextEmphasisStyle}; pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify}; +pub use self::text::HyphenateCharacter; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform, TransformOperation}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index 0920156108f..c9e67e137f3 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -24,6 +24,7 @@ pub use crate::values::specified::text::{ pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; +pub use crate::values::specified::HyphenateCharacter; /// A computed value for the `initial-letter` property. pub type InitialLetter = GenericInitialLetter; diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index c776af5f201..c3388344dab 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -90,6 +90,7 @@ pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAl pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform}; +pub use self::text::HyphenateCharacter; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 893c5232c5e..299f4eb4774 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -37,6 +37,27 @@ pub type WordSpacing = Spacing; /// A specified value for the `line-height` property. pub type LineHeight = GenericLineHeight; +/// A value for the `hyphenate-character` property. +#[derive( + Clone, + Debug, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(C, u8)] +pub enum HyphenateCharacter { + /// `auto` + Auto, + /// `` + String(crate::OwnedStr), +} + impl Parse for InitialLetter { fn parse<'i, 't>( context: &ParserContext, From 454a9777b33f0c3e374835c72f23083510a7d0b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 17:42:45 +0200 Subject: [PATCH 44/81] style: Deal with layers and at-rules Differential Revision: https://phabricator.services.mozilla.com/D134010 --- .../style/stylesheets/keyframes_rule.rs | 9 +- components/style/stylist.rs | 182 ++++++++++++------ 2 files changed, 124 insertions(+), 67 deletions(-) diff --git a/components/style/stylesheets/keyframes_rule.rs b/components/style/stylesheets/keyframes_rule.rs index 8b721b7a52c..e84339a3371 100644 --- a/components/style/stylesheets/keyframes_rule.rs +++ b/components/style/stylesheets/keyframes_rule.rs @@ -14,7 +14,6 @@ use crate::properties::{PropertyDeclarationId, SourcePropertyDeclaration}; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard}; use crate::shared_lock::{Locked, ToCssWithGuard}; use crate::str::CssStringWriter; -use crate::stylesheets::layer_rule::LayerId; use crate::stylesheets::rule_parser::VendorPrefix; use crate::stylesheets::{CssRuleType, StylesheetContents}; use crate::values::{serialize_percentage, KeyframesName}; @@ -358,8 +357,6 @@ pub struct KeyframesAnimation { pub properties_changed: LonghandIdSet, /// Vendor prefix type the @keyframes has. pub vendor_prefix: Option, - /// The id of the cascade layer the keyframe rule was in. - pub layer_id: LayerId, } /// Get all the animated properties in a keyframes animation. @@ -412,14 +409,12 @@ impl KeyframesAnimation { pub fn from_keyframes( keyframes: &[Arc>], vendor_prefix: Option, - layer_id: LayerId, guard: &SharedRwLockReadGuard, ) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: LonghandIdSet::new(), vendor_prefix, - layer_id, }; if keyframes.is_empty() { @@ -500,8 +495,8 @@ pub fn parse_keyframe_list( RuleListParser::new_for_nested_rule( input, KeyframeListParser { - context: context, - shared_lock: shared_lock, + context, + shared_lock, declarations: &mut declarations, }, ) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a99de9d2a87..eb513d4f107 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -57,6 +57,7 @@ use selectors::NthIndexCache; use servo_arc::{Arc, ArcBorrow}; use smallbitvec::SmallBitVec; use smallvec::SmallVec; +use std::cmp::Ordering; use std::hash::{Hash, Hasher}; use std::sync::Mutex; use std::{mem, ops}; @@ -1532,6 +1533,75 @@ impl Stylist { } } +/// A vector that is sorted in layer order. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct LayerOrderedVec(Vec<(T, LayerId)>); +impl Default for LayerOrderedVec { + fn default() -> Self { + Self(Default::default()) + } +} + +/// A map that is sorted in layer order. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct LayerOrderedMap(PrecomputedHashMap>); +impl Default for LayerOrderedMap { + fn default() -> Self { + Self(Default::default()) + } +} + +impl LayerOrderedVec { + fn clear(&mut self) { + self.0.clear(); + } + fn push(&mut self, v: T, id: LayerId) { + self.0.push((v, id)); + } + fn sort(&mut self, layers: &[CascadeLayer]) { + self.0.sort_by_key(|&(_, ref id)| layers[id.0 as usize].order) + } +} + +impl LayerOrderedMap { + fn clear(&mut self) { + self.0.clear(); + } + fn try_insert(&mut self, name: Atom, v: T, id: LayerId) -> Result<(), FailedAllocationError> { + self.try_insert_with(name, v, id, |_, _| Ordering::Equal) + } + fn try_insert_with(&mut self, name: Atom, v: T, id: LayerId, cmp: impl Fn(&T, &T) -> Ordering) -> Result<(), FailedAllocationError> { + let vec = self.0.try_entry(name)?.or_insert_with(Default::default); + if let Some(&mut (ref mut val, ref last_id)) = vec.last_mut() { + if *last_id == id { + if cmp(&val, &v) != Ordering::Greater { + *val = v; + } + return Ok(()) + } + } + vec.push((v, id)); + Ok(()) + } + fn sort(&mut self, layers: &[CascadeLayer]) { + self.sort_with(layers, |_, _| Ordering::Equal) + } + fn sort_with(&mut self, layers: &[CascadeLayer], cmp: impl Fn(&T, &T) -> Ordering) { + for (_, v) in self.0.iter_mut() { + v.sort_by(|&(ref v1, ref id1), &(ref v2, ref id2)| { + let order1 = layers[id1.0 as usize].order; + let order2 = layers[id2.0 as usize].order; + order1.cmp(&order2).then_with(|| cmp(v1, v2)) + }) + } + } + /// Get an entry on the LayerOrderedMap by name. + pub fn get(&self, name: &Atom) -> Option<&T> { + let vec = self.0.get(name)?; + Some(&vec.last()?.0) + } +} + /// This struct holds data which users of Stylist may want to extract /// from stylesheets which can be done at the same time as updating. #[derive(Clone, Debug, Default)] @@ -1539,35 +1609,35 @@ impl Stylist { pub struct ExtraStyleData { /// A list of effective font-face rules and their origin. #[cfg(feature = "gecko")] - pub font_faces: Vec>>, + pub font_faces: LayerOrderedVec>>, /// A list of effective font-feature-values rules. #[cfg(feature = "gecko")] - pub font_feature_values: Vec>>, + pub font_feature_values: LayerOrderedVec>>, /// A map of effective counter-style rules. #[cfg(feature = "gecko")] - pub counter_styles: PrecomputedHashMap>>, + pub counter_styles: LayerOrderedMap>>, /// A map of effective page rules. #[cfg(feature = "gecko")] - pub pages: Vec>>, + pub pages: LayerOrderedVec>>, /// A map of effective scroll-timeline rules. #[cfg(feature = "gecko")] - pub scroll_timelines: PrecomputedHashMap>>, + pub scroll_timelines: LayerOrderedMap>>, } #[cfg(feature = "gecko")] impl ExtraStyleData { /// Add the given @font-face rule. - fn add_font_face(&mut self, rule: &Arc>) { - self.font_faces.push(rule.clone()); + fn add_font_face(&mut self, rule: &Arc>, layer: LayerId) { + self.font_faces.push(rule.clone(), layer); } /// Add the given @font-feature-values rule. - fn add_font_feature_values(&mut self, rule: &Arc>) { - self.font_feature_values.push(rule.clone()); + fn add_font_feature_values(&mut self, rule: &Arc>, layer: LayerId) { + self.font_feature_values.push(rule.clone(), layer); } /// Add the given @counter-style rule. @@ -1575,14 +1645,15 @@ impl ExtraStyleData { &mut self, guard: &SharedRwLockReadGuard, rule: &Arc>, - ) { + layer: LayerId, + ) -> Result<(), FailedAllocationError> { let name = rule.read_with(guard).name().0.clone(); - self.counter_styles.insert(name, rule.clone()); + self.counter_styles.try_insert(name, rule.clone(), layer) } /// Add the given @page rule. - fn add_page(&mut self, rule: &Arc>) { - self.pages.push(rule.clone()); + fn add_page(&mut self, rule: &Arc>, layer: LayerId) { + self.pages.push(rule.clone(), layer); } /// Add the given @scroll-timeline rule. @@ -1590,15 +1661,20 @@ impl ExtraStyleData { &mut self, guard: &SharedRwLockReadGuard, rule: &Arc>, + layer: LayerId, ) -> Result<(), FailedAllocationError> { let name = rule.read_with(guard).name.as_atom().clone(); - self.scroll_timelines - .try_insert(name, rule.clone()) - .map(|_| {}) + self.scroll_timelines.try_insert(name, rule.clone(), layer) + } + + fn sort_by_layer(&mut self, layers: &[CascadeLayer]) { + self.font_faces.sort(layers); + self.font_feature_values.sort(layers); + self.counter_styles.sort(layers); + self.pages.sort(layers); + self.scroll_timelines.sort(layers); } -} -impl ExtraStyleData { fn clear(&mut self) { #[cfg(feature = "gecko")] { @@ -1611,6 +1687,18 @@ impl ExtraStyleData { } } +// Don't let a prefixed keyframes animation override +// a non-prefixed one. +fn compare_keyframes_in_same_layer(v1: &KeyframesAnimation, v2: &KeyframesAnimation) -> Ordering { + if v1.vendor_prefix.is_some() == v2.vendor_prefix.is_some() { + Ordering::Equal + } else if v2.vendor_prefix.is_some() { + Ordering::Greater + } else { + Ordering::Less + } +} + /// An iterator over the different ExtraStyleData. pub struct ExtraStyleDataIterator<'a>(DocumentCascadeDataIter<'a>); @@ -1966,7 +2054,7 @@ pub struct CascadeData { /// A map with all the animations at this `CascadeData`'s origin, indexed /// by name. - animations: PrecomputedHashMap, + animations: LayerOrderedMap, /// A map from cascade layer name to layer order. layer_id: FxHashMap, @@ -2167,6 +2255,8 @@ impl CascadeData { order.inc(); } } + self.extra_data.sort_by_layer(&self.layers); + self.animations.sort_with(&self.layers, compare_keyframes_in_same_layer); } /// Collects all the applicable media query results into `results`. @@ -2340,65 +2430,37 @@ 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); - match self - .animations - .try_entry(keyframes_rule.name.as_atom().clone())? - { - Entry::Vacant(e) => { - e.insert(KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - current_layer_id, - guard, - )); - }, - Entry::Occupied(mut e) => { - // Don't let a prefixed keyframes animation override - // a non-prefixed one. - // - // TODO(emilio): This will need to be harder for - // layers. - let needs_insert = keyframes_rule.vendor_prefix.is_none() || - e.get().vendor_prefix.is_some(); - if needs_insert { - e.insert(KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, - keyframes_rule.vendor_prefix.clone(), - current_layer_id, - guard, - )); - } - }, - } + let keyframes_rule = keyframes_rule.read_with(guard); + let name = keyframes_rule.name.as_atom().clone(); + let animation = KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, + keyframes_rule.vendor_prefix.clone(), + guard, + ); + self.animations.try_insert_with(name, animation, current_layer_id, compare_keyframes_in_same_layer)?; }, #[cfg(feature = "gecko")] CssRule::ScrollTimeline(ref rule) => { // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. - self.extra_data.add_scroll_timeline(guard, rule)?; + self.extra_data.add_scroll_timeline(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { - self.extra_data.add_font_face(rule); + self.extra_data.add_font_face(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::FontFeatureValues(ref rule) => { - self.extra_data.add_font_feature_values(rule); + self.extra_data.add_font_feature_values(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::CounterStyle(ref rule) => { - self.extra_data.add_counter_style(guard, rule); + self.extra_data.add_counter_style(guard, rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(rule); + self.extra_data.add_page(rule, current_layer_id); }, CssRule::Viewport(..) => {}, _ => { From 07d1bd560b15aab2c08e6e79007dc439c224de34 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Tue, 6 Jun 2023 22:23:35 +0200 Subject: [PATCH 45/81] Further changes required by Servo --- components/style/stylist.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index eb513d4f107..0ead08ccdcc 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1551,6 +1551,7 @@ impl Default for LayerOrderedMap { } } +#[cfg(feature = "gecko")] impl LayerOrderedVec { fn clear(&mut self) { self.0.clear(); @@ -1567,6 +1568,7 @@ impl LayerOrderedMap { fn clear(&mut self) { self.0.clear(); } + #[cfg(feature = "gecko")] fn try_insert(&mut self, name: Atom, v: T, id: LayerId) -> Result<(), FailedAllocationError> { self.try_insert_with(name, v, id, |_, _| Ordering::Equal) } @@ -1583,6 +1585,7 @@ impl LayerOrderedMap { vec.push((v, id)); Ok(()) } + #[cfg(feature = "gecko")] fn sort(&mut self, layers: &[CascadeLayer]) { self.sort_with(layers, |_, _| Ordering::Equal) } @@ -2255,7 +2258,10 @@ impl CascadeData { order.inc(); } } - self.extra_data.sort_by_layer(&self.layers); + #[cfg(feature = "gecko")] + { + self.extra_data.sort_by_layer(&self.layers); + } self.animations.sort_with(&self.layers, compare_keyframes_in_same_layer); } @@ -2782,7 +2788,10 @@ impl CascadeData { self.layer_id.clear(); self.layers.clear(); self.layers.push(CascadeLayer::root()); - self.extra_data.clear(); + #[cfg(feature = "gecko")] + { + self.extra_data.clear(); + } self.rules_source_order = 0; self.num_selectors = 0; self.num_declarations = 0; From 2b6fce1e57902254a72d6d4148f6d996fce29345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 18:07:21 +0200 Subject: [PATCH 46/81] style: Remove servo/components/{hashglobe,fallible} in favor of try_reserve Differential Revision: https://phabricator.services.mozilla.com/D134194 --- components/fallible/Cargo.toml | 26 - components/fallible/lib.rs | 160 - components/hashglobe/Cargo.toml | 16 - components/hashglobe/LICENSE-APACHE | 201 -- components/hashglobe/LICENSE-MIT | 23 - components/hashglobe/README.md | 17 - components/hashglobe/src/alloc.rs | 160 - components/hashglobe/src/fake.rs | 269 -- components/hashglobe/src/hash_map.rs | 3087 ----------------- components/hashglobe/src/hash_set.rs | 1648 --------- components/hashglobe/src/lib.rs | 71 - components/hashglobe/src/shim.rs | 62 - components/hashglobe/src/table.rs | 1231 ------- components/malloc_size_of/Cargo.toml | 1 - components/malloc_size_of/lib.rs | 4 - components/style/Cargo.toml | 4 +- components/style/custom_properties.rs | 2 +- components/style/gecko/wrapper.rs | 2 +- components/style/hash.rs | 31 - .../invalidation/element/invalidation_map.rs | 50 +- components/style/invalidation/stylesheets.rs | 16 +- components/style/lib.rs | 25 +- .../helpers/animated_properties.mako.rs | 2 +- .../style/properties/properties.mako.rs | 2 +- components/style/rule_tree/core.rs | 2 +- components/style/selector_map.rs | 47 +- components/style/stylesheets/stylesheet.rs | 4 +- components/style/stylist.rs | 80 +- components/style/values/computed/mod.rs | 2 +- components/style/values/computed/text.rs | 2 +- components/style/values/specified/length.rs | 7 +- components/style/values/specified/mod.rs | 2 +- components/style/values/specified/page.rs | 14 +- 33 files changed, 157 insertions(+), 7113 deletions(-) delete mode 100644 components/fallible/Cargo.toml delete mode 100644 components/fallible/lib.rs delete mode 100644 components/hashglobe/Cargo.toml delete mode 100644 components/hashglobe/LICENSE-APACHE delete mode 100644 components/hashglobe/LICENSE-MIT delete mode 100644 components/hashglobe/README.md delete mode 100644 components/hashglobe/src/alloc.rs delete mode 100644 components/hashglobe/src/fake.rs delete mode 100644 components/hashglobe/src/hash_map.rs delete mode 100644 components/hashglobe/src/hash_set.rs delete mode 100644 components/hashglobe/src/lib.rs delete mode 100644 components/hashglobe/src/shim.rs delete mode 100644 components/hashglobe/src/table.rs delete mode 100644 components/style/hash.rs diff --git a/components/fallible/Cargo.toml b/components/fallible/Cargo.toml deleted file mode 100644 index 3d4f9f5549c..00000000000 --- a/components/fallible/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "fallible" -version = "0.0.1" -authors = ["The Servo Project Developers"] -license = "MPL-2.0" -edition = "2018" -publish = false - -[lib] -name = "fallible" -path = "lib.rs" - -[dependencies] -hashglobe = { path = "../hashglobe" } -smallvec = { workspace = true } - -# This crate effectively does nothing except if the `known_system_malloc` -# feature is specified. -# -# In that case, we actually call the system malloc functions to reserve space, -# otherwise we just let Rust do its thing (aborting on OOM). -# -# This is effectively a stop-gap measure until we can do this properly in -# stable Rust. -[features] -known_system_malloc = [] diff --git a/components/fallible/lib.rs b/components/fallible/lib.rs deleted file mode 100644 index f7506afd510..00000000000 --- a/components/fallible/lib.rs +++ /dev/null @@ -1,160 +0,0 @@ -/* 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/. */ - -#[cfg(feature = "known_system_malloc")] -use hashglobe::alloc; -use hashglobe::FailedAllocationError; -use smallvec::Array; -use smallvec::SmallVec; -use std::vec::Vec; - -pub trait FallibleVec { - /// Append |val| to the end of |vec|. Returns Ok(()) on success, - /// Err(reason) if it fails, with |reason| describing the failure. - fn try_push(&mut self, value: T) -> Result<(), FailedAllocationError>; -} - -///////////////////////////////////////////////////////////////// -// Vec - -impl FallibleVec for Vec { - #[inline(always)] - fn try_push(&mut self, val: T) -> Result<(), FailedAllocationError> { - #[cfg(feature = "known_system_malloc")] - { - if self.capacity() == self.len() { - try_double_vec(self)?; - debug_assert!(self.capacity() > self.len()); - } - } - self.push(val); - Ok(()) - } -} - -// Double the capacity of |vec|, or fail to do so due to lack of memory. -// Returns Ok(()) on success, Err(..) on failure. -#[cfg(feature = "known_system_malloc")] -#[inline(never)] -#[cold] -fn try_double_vec(vec: &mut Vec) -> Result<(), FailedAllocationError> { - use std::mem; - - let old_ptr = vec.as_mut_ptr(); - let old_len = vec.len(); - - let old_cap: usize = vec.capacity(); - let new_cap: usize = if old_cap == 0 { - 4 - } else { - old_cap - .checked_mul(2) - .ok_or(FailedAllocationError::new("capacity overflow for Vec"))? - }; - - let new_size_bytes = new_cap - .checked_mul(mem::size_of::()) - .ok_or(FailedAllocationError::new("capacity overflow for Vec"))?; - - let new_ptr = unsafe { - if old_cap == 0 { - alloc::alloc(new_size_bytes, 0) - } else { - alloc::realloc(old_ptr as *mut u8, new_size_bytes) - } - }; - - if new_ptr.is_null() { - return Err(FailedAllocationError::new( - "out of memory when allocating Vec", - )); - } - - let new_vec = unsafe { Vec::from_raw_parts(new_ptr as *mut T, old_len, new_cap) }; - - mem::forget(mem::replace(vec, new_vec)); - Ok(()) -} - -///////////////////////////////////////////////////////////////// -// SmallVec - -impl FallibleVec for SmallVec { - #[inline(always)] - fn try_push(&mut self, val: T::Item) -> Result<(), FailedAllocationError> { - #[cfg(feature = "known_system_malloc")] - { - if self.capacity() == self.len() { - try_double_small_vec(self)?; - debug_assert!(self.capacity() > self.len()); - } - } - self.push(val); - Ok(()) - } -} - -// Double the capacity of |svec|, or fail to do so due to lack of memory. -// Returns Ok(()) on success, Err(..) on failure. -#[cfg(feature = "known_system_malloc")] -#[inline(never)] -#[cold] -fn try_double_small_vec(svec: &mut SmallVec) -> Result<(), FailedAllocationError> -where - T: Array, -{ - use std::mem; - use std::ptr::copy_nonoverlapping; - - let old_ptr = svec.as_mut_ptr(); - let old_len = svec.len(); - - let old_cap: usize = svec.capacity(); - let new_cap: usize = if old_cap == 0 { - 4 - } else { - old_cap - .checked_mul(2) - .ok_or(FailedAllocationError::new("capacity overflow for SmallVec"))? - }; - - // This surely shouldn't fail, if |old_cap| was previously accepted as a - // valid value. But err on the side of caution. - let old_size_bytes = old_cap - .checked_mul(mem::size_of::()) - .ok_or(FailedAllocationError::new("capacity overflow for SmallVec"))?; - - let new_size_bytes = new_cap - .checked_mul(mem::size_of::()) - .ok_or(FailedAllocationError::new("capacity overflow for SmallVec"))?; - - let new_ptr; - if svec.spilled() { - // There's an old block to free, and, presumably, old contents to - // copy. realloc takes care of both aspects. - unsafe { - new_ptr = alloc::realloc(old_ptr as *mut u8, new_size_bytes); - } - } else { - // There's no old block to free. There may be old contents to copy. - unsafe { - new_ptr = alloc::alloc(new_size_bytes, 0); - if !new_ptr.is_null() && old_size_bytes > 0 { - copy_nonoverlapping(old_ptr as *const u8, new_ptr as *mut u8, old_size_bytes); - } - } - } - - if new_ptr.is_null() { - return Err(FailedAllocationError::new( - "out of memory when allocating SmallVec", - )); - } - - let new_vec = unsafe { Vec::from_raw_parts(new_ptr as *mut T::Item, old_len, new_cap) }; - - let new_svec = SmallVec::from_vec(new_vec); - mem::forget(mem::replace(svec, new_svec)); - Ok(()) -} diff --git a/components/hashglobe/Cargo.toml b/components/hashglobe/Cargo.toml deleted file mode 100644 index e453136c43f..00000000000 --- a/components/hashglobe/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "hashglobe" -version = "0.1.0" -authors = ["The Rust Project Developers", "Manish Goregaokar "] -license = "MIT OR Apache-2.0" -description = "Fork of std::HashMap with stable fallible allocation." -documentation = "https://docs.rs/hashglobe" -repository = "https://github.com/Manishearth/hashglobe" -readme = "README.md" -edition = "2018" - -[dependencies] -libc = { workspace = true } - -[dev-dependencies] -rand = { workspace = true } diff --git a/components/hashglobe/LICENSE-APACHE b/components/hashglobe/LICENSE-APACHE deleted file mode 100644 index 16fe87b06e8..00000000000 --- a/components/hashglobe/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/components/hashglobe/LICENSE-MIT b/components/hashglobe/LICENSE-MIT deleted file mode 100644 index 31aa79387f2..00000000000 --- a/components/hashglobe/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/components/hashglobe/README.md b/components/hashglobe/README.md deleted file mode 100644 index e2f1df4fa97..00000000000 --- a/components/hashglobe/README.md +++ /dev/null @@ -1,17 +0,0 @@ -hashglobe -======== - - -This is a fork of Rust's `std::HashMap`. It works on stable out of the stdlib and has fallible APIs. - -We intend to diverge as little as possible from the original hashmap. - - -Dual licensed Apache/MIT, the same as the stdlib. - - -## Should I use this? - -No. - -Wait for https://github.com/rust-lang/rfcs/pull/2116 instead. diff --git a/components/hashglobe/src/alloc.rs b/components/hashglobe/src/alloc.rs deleted file mode 100644 index 50e86ee4af6..00000000000 --- a/components/hashglobe/src/alloc.rs +++ /dev/null @@ -1,160 +0,0 @@ -// FORK NOTE: Copied from liballoc_system, removed unnecessary APIs, -// APIs take size/align directly instead of Layout - -// The minimum alignment guaranteed by the architecture. This value is used to -// add fast paths for low alignment values. In practice, the alignment is a -// constant at the call site and the branch will be optimized out. -#[cfg(all(any( - target_arch = "x86", - target_arch = "arm", - target_arch = "mips", - target_arch = "powerpc", - target_arch = "powerpc64", - target_arch = "asmjs", - target_arch = "wasm32" -)))] -const MIN_ALIGN: usize = 8; -#[cfg(all(any( - target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "mips64", - target_arch = "s390x", - target_arch = "sparc64" -)))] -const MIN_ALIGN: usize = 16; - -pub use self::platform::{alloc, dealloc, realloc}; - -#[cfg(any(unix, target_os = "redox"))] -mod platform { - use libc; - - #[cfg(not(any(target_os = "android")))] - use std::ptr; - - use super::MIN_ALIGN; - - #[inline] - pub unsafe fn alloc(size: usize, align: usize) -> *mut u8 { - if align <= MIN_ALIGN { - libc::malloc(size) as *mut u8 - } else { - aligned_malloc(size, align) - } - } - - #[inline] - pub unsafe fn dealloc(ptr: *mut u8, _align: usize) { - libc::free(ptr as *mut libc::c_void) - } - - #[inline] - pub unsafe fn realloc(ptr: *mut u8, new_size: usize) -> *mut u8 { - libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 - } - - #[cfg(any(target_os = "android", target_os = "redox"))] - #[inline] - unsafe fn aligned_malloc(size: usize, align: usize) -> *mut u8 { - // On android we currently target API level 9 which unfortunately - // doesn't have the `posix_memalign` API used below. Instead we use - // `memalign`, but this unfortunately has the property on some systems - // where the memory returned cannot be deallocated by `free`! - // - // Upon closer inspection, however, this appears to work just fine with - // Android, so for this platform we should be fine to call `memalign` - // (which is present in API level 9). Some helpful references could - // possibly be chromium using memalign [1], attempts at documenting that - // memalign + free is ok [2] [3], or the current source of chromium - // which still uses memalign on android [4]. - // - // [1]: https://codereview.chromium.org/10796020/ - // [2]: https://code.google.com/p/android/issues/detail?id=35391 - // [3]: https://bugs.chromium.org/p/chromium/issues/detail?id=138579 - // [4]: https://chromium.googlesource.com/chromium/src/base/+/master/ - // /memory/aligned_memory.cc - libc::memalign(align, size) as *mut u8 - } - - #[cfg(not(any(target_os = "android", target_os = "redox")))] - #[inline] - unsafe fn aligned_malloc(size: usize, align: usize) -> *mut u8 { - let mut out = ptr::null_mut(); - let ret = libc::posix_memalign(&mut out, align, size); - if ret != 0 { - ptr::null_mut() - } else { - out as *mut u8 - } - } -} - -#[cfg(windows)] -#[allow(bad_style)] -mod platform { - - use super::MIN_ALIGN; - type LPVOID = *mut u8; - type HANDLE = LPVOID; - type SIZE_T = usize; - type DWORD = u32; - type BOOL = i32; - - extern "system" { - fn GetProcessHeap() -> HANDLE; - fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) -> LPVOID; - fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID, dwBytes: SIZE_T) -> LPVOID; - fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: LPVOID) -> BOOL; - fn GetLastError() -> DWORD; - } - - #[repr(C)] - struct Header(*mut u8); - - unsafe fn get_header<'a>(ptr: *mut u8) -> &'a mut Header { - &mut *(ptr as *mut Header).offset(-1) - } - - unsafe fn align_ptr(ptr: *mut u8, align: usize) -> *mut u8 { - let aligned = ptr.offset((align - (ptr as usize & (align - 1))) as isize); - *get_header(aligned) = Header(ptr); - aligned - } - - #[inline] - unsafe fn allocate_with_flags(size: usize, align: usize, flags: DWORD) -> *mut u8 { - if align <= MIN_ALIGN { - HeapAlloc(GetProcessHeap(), flags, size) - } else { - let size = size + align; - let ptr = HeapAlloc(GetProcessHeap(), flags, size); - if ptr.is_null() { - ptr - } else { - align_ptr(ptr, align) - } - } - } - - #[inline] - pub unsafe fn alloc(size: usize, align: usize) -> *mut u8 { - allocate_with_flags(size, align, 0) - } - - #[inline] - pub unsafe fn dealloc(ptr: *mut u8, align: usize) { - if align <= MIN_ALIGN { - let err = HeapFree(GetProcessHeap(), 0, ptr as LPVOID); - debug_assert!(err != 0, "Failed to free heap memory: {}", GetLastError()); - } else { - let header = get_header(ptr); - let err = HeapFree(GetProcessHeap(), 0, header.0 as LPVOID); - debug_assert!(err != 0, "Failed to free heap memory: {}", GetLastError()); - } - } - - #[inline] - pub unsafe fn realloc(ptr: *mut u8, new_size: usize) -> *mut u8 { - HeapReAlloc(GetProcessHeap(), 0, ptr as LPVOID, new_size) as *mut u8 - } -} diff --git a/components/hashglobe/src/fake.rs b/components/hashglobe/src/fake.rs deleted file mode 100644 index d544721a1a2..00000000000 --- a/components/hashglobe/src/fake.rs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! This module contains shims around the stdlib HashMap -//! that add fallible methods -//! -//! These methods are a lie. They are not actually fallible. This is just to make -//! it smooth to switch between hashmap impls in a codebase. - -use std::collections::HashMap as StdMap; -use std::collections::HashSet as StdSet; -use std::fmt; -use std::hash::{BuildHasher, Hash}; -use std::ops::{Deref, DerefMut}; - -pub use std::collections::hash_map::{Entry, Iter as MapIter, IterMut as MapIterMut, RandomState}; -pub use std::collections::hash_set::{IntoIter as SetIntoIter, Iter as SetIter}; - -#[derive(Clone)] -pub struct HashMap(StdMap); - -use crate::FailedAllocationError; - -impl Deref for HashMap { - type Target = StdMap; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for HashMap { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - #[inline] - pub fn try_with_hasher(hash_builder: S) -> Result, FailedAllocationError> { - Ok(HashMap(StdMap::with_hasher(hash_builder))) - } - - #[inline] - pub fn try_with_capacity_and_hasher( - capacity: usize, - hash_builder: S, - ) -> Result, FailedAllocationError> { - Ok(HashMap(StdMap::with_capacity_and_hasher( - capacity, - hash_builder, - ))) - } - - pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashMap { - HashMap(StdMap::with_capacity_and_hasher(capacity, hash_builder)) - } - - #[inline] - pub fn try_reserve(&mut self, additional: usize) -> Result<(), FailedAllocationError> { - Ok(self.reserve(additional)) - } - - pub fn try_shrink_to_fit(&mut self) -> Result<(), FailedAllocationError> { - Ok(self.shrink_to_fit()) - } - - pub fn try_entry(&mut self, key: K) -> Result, FailedAllocationError> { - Ok(self.entry(key)) - } - - #[inline] - pub fn try_insert(&mut self, k: K, v: V) -> Result, FailedAllocationError> { - Ok(self.insert(k, v)) - } -} - -#[derive(Clone)] -pub struct HashSet(StdSet); - -impl Deref for HashSet { - type Target = StdSet; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for HashSet { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl HashSet { - #[inline] - pub fn new() -> HashSet { - HashSet(StdSet::new()) - } - - #[inline] - pub fn with_capacity(capacity: usize) -> HashSet { - HashSet(StdSet::with_capacity(capacity)) - } -} - -impl HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - #[inline] - pub fn with_hasher(hasher: S) -> HashSet { - HashSet(StdSet::with_hasher(hasher)) - } - - #[inline] - pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashSet { - HashSet(StdSet::with_capacity_and_hasher(capacity, hasher)) - } - - #[inline] - pub fn try_reserve(&mut self, additional: usize) -> Result<(), FailedAllocationError> { - Ok(self.reserve(additional)) - } - - #[inline] - pub fn try_shrink_to_fit(&mut self) -> Result<(), FailedAllocationError> { - Ok(self.shrink_to_fit()) - } - - #[inline] - pub fn try_insert(&mut self, value: T) -> Result { - Ok(self.insert(value)) - } -} - -// Pass through trait impls -// We can't derive these since the bounds are not obvious to the derive macro - -impl Default for HashMap { - fn default() -> Self { - HashMap(Default::default()) - } -} - -impl fmt::Debug for HashMap -where - K: Eq + Hash + fmt::Debug, - V: fmt::Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for HashMap -where - K: Eq + Hash, - V: PartialEq, - S: BuildHasher, -{ - fn eq(&self, other: &HashMap) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for HashMap -where - K: Eq + Hash, - V: Eq, - S: BuildHasher, -{ -} - -impl<'a, K, V, S> IntoIterator for &'a HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a V); - type IntoIter = MapIter<'a, K, V>; - - fn into_iter(self) -> MapIter<'a, K, V> { - self.0.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a mut V); - type IntoIter = MapIterMut<'a, K, V>; - - fn into_iter(self) -> MapIterMut<'a, K, V> { - self.0.iter_mut() - } -} - -impl Default for HashSet { - fn default() -> Self { - HashSet(Default::default()) - } -} - -impl fmt::Debug for HashSet -where - T: Eq + Hash + fmt::Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - fn eq(&self, other: &HashSet) -> bool { - self.0.eq(&other.0) - } -} - -impl Eq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl<'a, T, S> IntoIterator for &'a HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - type IntoIter = SetIter<'a, T>; - - fn into_iter(self) -> SetIter<'a, T> { - self.0.iter() - } -} - -impl IntoIterator for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = T; - type IntoIter = SetIntoIter; - - fn into_iter(self) -> SetIntoIter { - self.0.into_iter() - } -} diff --git a/components/hashglobe/src/hash_map.rs b/components/hashglobe/src/hash_map.rs deleted file mode 100644 index e122a82aabb..00000000000 --- a/components/hashglobe/src/hash_map.rs +++ /dev/null @@ -1,3087 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use self::Entry::*; -use self::VacantEntryState::*; - -use std::borrow::Borrow; -use std::cmp::max; -use std::fmt::{self, Debug}; -#[allow(deprecated)] -use std::hash::{BuildHasher, Hash}; -use std::iter::FromIterator; -use std::mem::{self, replace}; -use std::ops::{Deref, Index}; - -use super::table::BucketState::{Empty, Full}; -use super::table::{self, Bucket, EmptyBucket, FullBucket, FullBucketMut, RawTable, SafeHash}; - -use crate::FailedAllocationError; - -const MIN_NONZERO_RAW_CAPACITY: usize = 32; // must be a power of two - -/// The default behavior of HashMap implements a maximum load factor of 90.9%. -#[derive(Clone)] -struct DefaultResizePolicy; - -impl DefaultResizePolicy { - fn new() -> DefaultResizePolicy { - DefaultResizePolicy - } - - /// A hash map's "capacity" is the number of elements it can hold without - /// being resized. Its "raw capacity" is the number of slots required to - /// provide that capacity, accounting for maximum loading. The raw capacity - /// is always zero or a power of two. - #[inline] - fn raw_capacity(&self, len: usize) -> usize { - if len == 0 { - 0 - } else { - // 1. Account for loading: `raw_capacity >= len * 1.1`. - // 2. Ensure it is a power of two. - // 3. Ensure it is at least the minimum size. - let mut raw_cap = len * 11 / 10; - assert!(raw_cap >= len, "raw_cap overflow"); - raw_cap = raw_cap - .checked_next_power_of_two() - .expect("raw_capacity overflow"); - raw_cap = max(MIN_NONZERO_RAW_CAPACITY, raw_cap); - raw_cap - } - } - - /// The capacity of the given raw capacity. - #[inline] - fn capacity(&self, raw_cap: usize) -> usize { - // This doesn't have to be checked for overflow since allocation size - // in bytes will overflow earlier than multiplication by 10. - // - // As per https://github.com/rust-lang/rust/pull/30991 this is updated - // to be: (raw_cap * den + den - 1) / num - (raw_cap * 10 + 10 - 1) / 11 - } -} - -// The main performance trick in this hashmap is called Robin Hood Hashing. -// It gains its excellent performance from one essential operation: -// -// If an insertion collides with an existing element, and that element's -// "probe distance" (how far away the element is from its ideal location) -// is higher than how far we've already probed, swap the elements. -// -// This massively lowers variance in probe distance, and allows us to get very -// high load factors with good performance. The 90% load factor I use is rather -// conservative. -// -// > Why a load factor of approximately 90%? -// -// In general, all the distances to initial buckets will converge on the mean. -// At a load factor of α, the odds of finding the target bucket after k -// probes is approximately 1-α^k. If we set this equal to 50% (since we converge -// on the mean) and set k=8 (64-byte cache line / 8-byte hash), α=0.92. I round -// this down to make the math easier on the CPU and avoid its FPU. -// Since on average we start the probing in the middle of a cache line, this -// strategy pulls in two cache lines of hashes on every lookup. I think that's -// pretty good, but if you want to trade off some space, it could go down to one -// cache line on average with an α of 0.84. -// -// > Wait, what? Where did you get 1-α^k from? -// -// On the first probe, your odds of a collision with an existing element is α. -// The odds of doing this twice in a row is approximately α^2. For three times, -// α^3, etc. Therefore, the odds of colliding k times is α^k. The odds of NOT -// colliding after k tries is 1-α^k. -// -// The paper from 1986 cited below mentions an implementation which keeps track -// of the distance-to-initial-bucket histogram. This approach is not suitable -// for modern architectures because it requires maintaining an internal data -// structure. This allows very good first guesses, but we are most concerned -// with guessing entire cache lines, not individual indexes. Furthermore, array -// accesses are no longer linear and in one direction, as we have now. There -// is also memory and cache pressure that this would entail that would be very -// difficult to properly see in a microbenchmark. -// -// ## Future Improvements (FIXME!) -// -// Allow the load factor to be changed dynamically and/or at initialization. -// -// Also, would it be possible for us to reuse storage when growing the -// underlying table? This is exactly the use case for 'realloc', and may -// be worth exploring. -// -// ## Future Optimizations (FIXME!) -// -// Another possible design choice that I made without any real reason is -// parameterizing the raw table over keys and values. Technically, all we need -// is the size and alignment of keys and values, and the code should be just as -// efficient (well, we might need one for power-of-two size and one for not...). -// This has the potential to reduce code bloat in rust executables, without -// really losing anything except 4 words (key size, key alignment, val size, -// val alignment) which can be passed in to every call of a `RawTable` function. -// This would definitely be an avenue worth exploring if people start complaining -// about the size of rust executables. -// -// Annotate exceedingly likely branches in `table::make_hash` -// and `search_hashed` to reduce instruction cache pressure -// and mispredictions once it becomes possible (blocked on issue #11092). -// -// Shrinking the table could simply reallocate in place after moving buckets -// to the first half. -// -// The growth algorithm (fragment of the Proof of Correctness) -// -------------------- -// -// The growth algorithm is basically a fast path of the naive reinsertion- -// during-resize algorithm. Other paths should never be taken. -// -// Consider growing a robin hood hashtable of capacity n. Normally, we do this -// by allocating a new table of capacity `2n`, and then individually reinsert -// each element in the old table into the new one. This guarantees that the -// new table is a valid robin hood hashtable with all the desired statistical -// properties. Remark that the order we reinsert the elements in should not -// matter. For simplicity and efficiency, we will consider only linear -// reinsertions, which consist of reinserting all elements in the old table -// into the new one by increasing order of index. However we will not be -// starting our reinsertions from index 0 in general. If we start from index -// i, for the purpose of reinsertion we will consider all elements with real -// index j < i to have virtual index n + j. -// -// Our hash generation scheme consists of generating a 64-bit hash and -// truncating the most significant bits. When moving to the new table, we -// simply introduce a new bit to the front of the hash. Therefore, if an -// elements has ideal index i in the old table, it can have one of two ideal -// locations in the new table. If the new bit is 0, then the new ideal index -// is i. If the new bit is 1, then the new ideal index is n + i. Intuitively, -// we are producing two independent tables of size n, and for each element we -// independently choose which table to insert it into with equal probability. -// However the rather than wrapping around themselves on overflowing their -// indexes, the first table overflows into the first, and the first into the -// second. Visually, our new table will look something like: -// -// [yy_xxx_xxxx_xxx|xx_yyy_yyyy_yyy] -// -// Where x's are elements inserted into the first table, y's are elements -// inserted into the second, and _'s are empty sections. We now define a few -// key concepts that we will use later. Note that this is a very abstract -// perspective of the table. A real resized table would be at least half -// empty. -// -// Theorem: A linear robin hood reinsertion from the first ideal element -// produces identical results to a linear naive reinsertion from the same -// element. -// -// FIXME(Gankro, pczarn): review the proof and put it all in a separate README.md -// -// Adaptive early resizing -// ---------------------- -// To protect against degenerate performance scenarios (including DOS attacks), -// the implementation includes an adaptive behavior that can resize the map -// early (before its capacity is exceeded) when suspiciously long probe sequences -// are encountered. -// -// With this algorithm in place it would be possible to turn a CPU attack into -// a memory attack due to the aggressive resizing. To prevent that the -// adaptive behavior only triggers when the map is at least half full. -// This reduces the effectiveness of the algorithm but also makes it completely safe. -// -// The previous safety measure also prevents degenerate interactions with -// really bad quality hash algorithms that can make normal inputs look like a -// DOS attack. -// -const DISPLACEMENT_THRESHOLD: usize = 128; -// -// The threshold of 128 is chosen to minimize the chance of exceeding it. -// In particular, we want that chance to be less than 10^-8 with a load of 90%. -// For displacement, the smallest constant that fits our needs is 90, -// so we round that up to 128. -// -// At a load factor of α, the odds of finding the target bucket after exactly n -// unsuccessful probes[1] are -// -// Pr_α{displacement = n} = -// (1 - α) / α * ∑_{k≥1} e^(-kα) * (kα)^(k+n) / (k + n)! * (1 - kα / (k + n + 1)) -// -// We use this formula to find the probability of triggering the adaptive behavior -// -// Pr_0.909{displacement > 128} = 1.601 * 10^-11 -// -// 1. Alfredo Viola (2005). Distributional analysis of Robin Hood linear probing -// hashing with buckets. - -/// A hash map implemented with linear probing and Robin Hood bucket stealing. -/// -/// By default, `HashMap` uses a hashing algorithm selected to provide -/// resistance against HashDoS attacks. The algorithm is randomly seeded, and a -/// reasonable best-effort is made to generate this seed from a high quality, -/// secure source of randomness provided by the host without blocking the -/// program. Because of this, the randomness of the seed depends on the output -/// quality of the system's random number generator when the seed is created. -/// In particular, seeds generated when the system's entropy pool is abnormally -/// low such as during system boot may be of a lower quality. -/// -/// The default hashing algorithm is currently SipHash 1-3, though this is -/// subject to change at any point in the future. While its performance is very -/// competitive for medium sized keys, other hashing algorithms will outperform -/// it for small keys such as integers as well as large keys such as long -/// strings, though those algorithms will typically *not* protect against -/// attacks such as HashDoS. -/// -/// The hashing algorithm can be replaced on a per-`HashMap` basis using the -/// [`default`], [`with_hasher`], and [`with_capacity_and_hasher`] methods. Many -/// alternative algorithms are available on crates.io, such as the [`fnv`] crate. -/// -/// It is required that the keys implement the [`Eq`] and [`Hash`] traits, although -/// this can frequently be achieved by using `#[derive(PartialEq, Eq, Hash)]`. -/// If you implement these yourself, it is important that the following -/// property holds: -/// -/// ```text -/// k1 == k2 -> hash(k1) == hash(k2) -/// ``` -/// -/// In other words, if two keys are equal, their hashes must be equal. -/// -/// It is a logic error for a key to be modified in such a way that the key's -/// hash, as determined by the [`Hash`] trait, or its equality, as determined by -/// the [`Eq`] trait, changes while it is in the map. This is normally only -/// possible through [`Cell`], [`RefCell`], global state, I/O, or unsafe code. -/// -/// Relevant papers/articles: -/// -/// 1. Pedro Celis. ["Robin Hood Hashing"](https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf) -/// 2. Emmanuel Goossaert. ["Robin Hood -/// hashing"](http://codecapsule.com/2013/11/11/robin-hood-hashing/) -/// 3. Emmanuel Goossaert. ["Robin Hood hashing: backward shift -/// deletion"](http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/) -/// -/// # Examples -/// -/// ``` -/// use std::collections::HashMap; -/// -/// // type inference lets us omit an explicit type signature (which -/// // would be `HashMap<&str, &str>` in this example). -/// let mut book_reviews = HashMap::new(); -/// -/// // review some books. -/// book_reviews.insert("Adventures of Huckleberry Finn", "My favorite book."); -/// book_reviews.insert("Grimms' Fairy Tales", "Masterpiece."); -/// book_reviews.insert("Pride and Prejudice", "Very enjoyable."); -/// book_reviews.insert("The Adventures of Sherlock Holmes", "Eye lyked it alot."); -/// -/// // check for a specific one. -/// if !book_reviews.contains_key("Les Misérables") { -/// println!("We've got {} reviews, but Les Misérables ain't one.", -/// book_reviews.len()); -/// } -/// -/// // oops, this review has a lot of spelling mistakes, let's delete it. -/// book_reviews.remove("The Adventures of Sherlock Holmes"); -/// -/// // look up the values associated with some keys. -/// let to_find = ["Pride and Prejudice", "Alice's Adventure in Wonderland"]; -/// for book in &to_find { -/// match book_reviews.get(book) { -/// Some(review) => println!("{}: {}", book, review), -/// None => println!("{} is unreviewed.", book) -/// } -/// } -/// -/// // iterate over everything. -/// for (book, review) in &book_reviews { -/// println!("{}: \"{}\"", book, review); -/// } -/// ``` -/// -/// `HashMap` also implements an [`Entry API`](#method.entry), which allows -/// for more complex methods of getting, setting, updating and removing keys and -/// their values: -/// -/// ``` -/// use std::collections::HashMap; -/// -/// // type inference lets us omit an explicit type signature (which -/// // would be `HashMap<&str, u8>` in this example). -/// let mut player_stats = HashMap::new(); -/// -/// fn random_stat_buff() -> u8 { -/// // could actually return some random value here - let's just return -/// // some fixed value for now -/// 42 -/// } -/// -/// // insert a key only if it doesn't already exist -/// player_stats.entry("health").or_insert(100); -/// -/// // insert a key using a function that provides a new value only if it -/// // doesn't already exist -/// player_stats.entry("defence").or_insert_with(random_stat_buff); -/// -/// // update a key, guarding against the key possibly not being set -/// let stat = player_stats.entry("attack").or_insert(100); -/// *stat += random_stat_buff(); -/// ``` -/// -/// The easiest way to use `HashMap` with a custom type as key is to derive [`Eq`] and [`Hash`]. -/// We must also derive [`PartialEq`]. -/// -/// [`Eq`]: ../../std/cmp/trait.Eq.html -/// [`Hash`]: ../../std/hash/trait.Hash.html -/// [`PartialEq`]: ../../std/cmp/trait.PartialEq.html -/// [`RefCell`]: ../../std/cell/struct.RefCell.html -/// [`Cell`]: ../../std/cell/struct.Cell.html -/// [`default`]: #method.default -/// [`with_hasher`]: #method.with_hasher -/// [`with_capacity_and_hasher`]: #method.with_capacity_and_hasher -/// [`fnv`]: https://crates.io/crates/fnv -/// -/// ``` -/// use std::collections::HashMap; -/// -/// #[derive(Hash, Eq, PartialEq, Debug)] -/// struct Viking { -/// name: String, -/// country: String, -/// } -/// -/// impl Viking { -/// /// Create a new Viking. -/// fn new(name: &str, country: &str) -> Viking { -/// Viking { name: name.to_string(), country: country.to_string() } -/// } -/// } -/// -/// // Use a HashMap to store the vikings' health points. -/// let mut vikings = HashMap::new(); -/// -/// vikings.insert(Viking::new("Einar", "Norway"), 25); -/// vikings.insert(Viking::new("Olaf", "Denmark"), 24); -/// vikings.insert(Viking::new("Harald", "Iceland"), 12); -/// -/// // Use derived implementation to print the status of the vikings. -/// for (viking, health) in &vikings { -/// println!("{:?} has {} hp", viking, health); -/// } -/// ``` -/// -/// A `HashMap` with fixed list of elements can be initialized from an array: -/// -/// ``` -/// use std::collections::HashMap; -/// -/// fn main() { -/// let timber_resources: HashMap<&str, i32> = -/// [("Norway", 100), -/// ("Denmark", 50), -/// ("Iceland", 10)] -/// .iter().cloned().collect(); -/// // use the values stored in map -/// } -/// ``` - -#[derive(Clone)] -pub struct HashMap { - // All hashes are keyed on these values, to prevent hash collision attacks. - hash_builder: S, - - table: RawTable, - - resize_policy: DefaultResizePolicy, -} - -/// Search for a pre-hashed key. -#[inline] -fn search_hashed(table: M, hash: SafeHash, mut is_match: F) -> InternalEntry -where - M: Deref>, - F: FnMut(&K) -> bool, -{ - // This is the only function where capacity can be zero. To avoid - // undefined behavior when Bucket::new gets the raw bucket in this - // case, immediately return the appropriate search result. - if table.capacity() == 0 { - return InternalEntry::TableIsEmpty; - } - - let size = table.size(); - let mut probe = Bucket::new(table, hash); - let mut displacement = 0; - - loop { - let full = match probe.peek() { - Empty(bucket) => { - // Found a hole! - return InternalEntry::Vacant { - hash, - elem: NoElem(bucket, displacement), - }; - }, - Full(bucket) => bucket, - }; - - let probe_displacement = full.displacement(); - - if probe_displacement < displacement { - // Found a luckier bucket than me. - // We can finish the search early if we hit any bucket - // with a lower distance to initial bucket than we've probed. - return InternalEntry::Vacant { - hash, - elem: NeqElem(full, probe_displacement), - }; - } - - // If the hash doesn't match, it can't be this one.. - if hash == full.hash() { - // If the key doesn't match, it can't be this one.. - if is_match(full.read().0) { - return InternalEntry::Occupied { elem: full }; - } - } - displacement += 1; - probe = full.next(); - debug_assert!(displacement <= size); - } -} - -fn pop_internal(starting_bucket: FullBucketMut<'_, K, V>) -> (K, V, &mut RawTable) { - let (empty, retkey, retval) = starting_bucket.take(); - let mut gap = match empty.gap_peek() { - Ok(b) => b, - Err(b) => return (retkey, retval, b.into_table()), - }; - - while gap.full().displacement() != 0 { - gap = match gap.shift() { - Ok(b) => b, - Err(b) => { - return (retkey, retval, b.into_table()); - }, - }; - } - - // Now we've done all our shifting. Return the value we grabbed earlier. - (retkey, retval, gap.into_table()) -} - -/// Perform robin hood bucket stealing at the given `bucket`. You must -/// also pass that bucket's displacement so we don't have to recalculate it. -/// -/// `hash`, `key`, and `val` are the elements to "robin hood" into the hashtable. -fn robin_hood<'a, K: 'a, V: 'a>( - bucket: FullBucketMut<'a, K, V>, - mut displacement: usize, - mut hash: SafeHash, - mut key: K, - mut val: V, -) -> FullBucketMut<'a, K, V> { - let size = bucket.table().size(); - let raw_capacity = bucket.table().capacity(); - // There can be at most `size - dib` buckets to displace, because - // in the worst case, there are `size` elements and we already are - // `displacement` buckets away from the initial one. - let idx_end = (bucket.index() + size - bucket.displacement()) % raw_capacity; - // Save the *starting point*. - let mut bucket = bucket.stash(); - - loop { - let (old_hash, old_key, old_val) = bucket.replace(hash, key, val); - hash = old_hash; - key = old_key; - val = old_val; - - loop { - displacement += 1; - let probe = bucket.next(); - debug_assert_ne!(probe.index(), idx_end); - - let full_bucket = match probe.peek() { - Empty(bucket) => { - // Found a hole! - let bucket = bucket.put(hash, key, val); - // Now that it's stolen, just read the value's pointer - // right out of the table! Go back to the *starting point*. - // - // This use of `into_table` is misleading. It turns the - // bucket, which is a FullBucket on top of a - // FullBucketMut, into just one FullBucketMut. The "table" - // refers to the inner FullBucketMut in this context. - return bucket.into_table(); - }, - Full(bucket) => bucket, - }; - - let probe_displacement = full_bucket.displacement(); - - bucket = full_bucket; - - // Robin hood! Steal the spot. - if probe_displacement < displacement { - displacement = probe_displacement; - break; - } - } - } -} - -impl HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - fn make_hash(&self, x: &X) -> SafeHash - where - X: Hash, - { - table::make_hash(&self.hash_builder, x) - } - - /// Search for a key, yielding the index if it's found in the hashtable. - /// If you already have the hash for the key lying around, use - /// search_hashed. - #[inline] - fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry> - where - K: Borrow, - Q: Eq + Hash, - { - let hash = self.make_hash(q); - search_hashed(&self.table, hash, |k| q.eq(k.borrow())) - } - - #[inline] - fn search_mut<'a, Q: ?Sized>(&'a mut self, q: &Q) -> InternalEntry> - where - K: Borrow, - Q: Eq + Hash, - { - let hash = self.make_hash(q); - search_hashed(&mut self.table, hash, |k| q.eq(k.borrow())) - } - - // The caller should ensure that invariants by Robin Hood Hashing hold - // and that there's space in the underlying table. - fn insert_hashed_ordered(&mut self, hash: SafeHash, k: K, v: V) { - let mut buckets = Bucket::new(&mut self.table, hash); - let start_index = buckets.index(); - - loop { - // We don't need to compare hashes for value swap. - // Not even DIBs for Robin Hood. - buckets = match buckets.peek() { - Empty(empty) => { - empty.put(hash, k, v); - return; - }, - Full(b) => b.into_bucket(), - }; - buckets.next(); - debug_assert_ne!(buckets.index(), start_index); - } - } -} - -impl HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - /// Creates an empty `HashMap` which will use the given hash builder to hash - /// keys. - /// - /// The created map has the default initial capacity. - /// - /// Warning: `hash_builder` is normally randomly generated, and - /// is designed to allow HashMaps to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut map = HashMap::with_hasher(s); - /// map.insert(1, 2); - /// ``` - #[inline] - pub fn try_with_hasher(hash_builder: S) -> Result, FailedAllocationError> { - Ok(HashMap { - hash_builder, - resize_policy: DefaultResizePolicy::new(), - table: RawTable::new(0)?, - }) - } - - #[inline] - pub fn with_hasher(hash_builder: S) -> HashMap { - Self::try_with_hasher(hash_builder).unwrap() - } - - /// Creates an empty `HashMap` with the specified capacity, using `hash_builder` - /// to hash the keys. - /// - /// The hash map will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash map will not allocate. - /// - /// Warning: `hash_builder` is normally randomly generated, and - /// is designed to allow HashMaps to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut map = HashMap::with_capacity_and_hasher(10, s); - /// map.insert(1, 2); - /// ``` - #[inline] - pub fn try_with_capacity_and_hasher( - capacity: usize, - hash_builder: S, - ) -> Result, FailedAllocationError> { - let resize_policy = DefaultResizePolicy::new(); - let raw_cap = resize_policy.raw_capacity(capacity); - Ok(HashMap { - hash_builder, - resize_policy, - table: RawTable::new(raw_cap)?, - }) - } - - pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashMap { - Self::try_with_capacity_and_hasher(capacity, hash_builder).unwrap() - } - - /// Returns a reference to the map's [`BuildHasher`]. - /// - /// [`BuildHasher`]: ../../std/hash/trait.BuildHasher.html - pub fn hasher(&self) -> &S { - &self.hash_builder - } - - /// Returns the number of elements the map can hold without reallocating. - /// - /// This number is a lower bound; the `HashMap` might be able to hold - /// more, but is guaranteed to be able to hold at least this many. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// let map: HashMap = HashMap::with_capacity(100); - /// assert!(map.capacity() >= 100); - /// ``` - #[inline] - pub fn capacity(&self) -> usize { - self.resize_policy.capacity(self.raw_capacity()) - } - - /// Returns the hash map's raw capacity. - #[inline] - fn raw_capacity(&self) -> usize { - self.table.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// in the `HashMap`. The collection may reserve more space to avoid - /// frequent reallocations. - /// - /// # Panics - /// - /// Panics if the new allocation size overflows [`usize`]. - /// - /// [`usize`]: ../../std/primitive.usize.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// let mut map: HashMap<&str, isize> = HashMap::new(); - /// map.reserve(10); - /// ``` - pub fn reserve(&mut self, additional: usize) { - self.try_reserve(additional).unwrap(); - } - - #[inline] - pub fn try_reserve(&mut self, additional: usize) -> Result<(), FailedAllocationError> { - let remaining = self.capacity() - self.len(); // this can't overflow - if remaining < additional { - let min_cap = self - .len() - .checked_add(additional) - .expect("reserve overflow"); - let raw_cap = self.resize_policy.raw_capacity(min_cap); - self.try_resize(raw_cap)?; - } else if self.table.tag() && remaining <= self.len() { - // Probe sequence is too long and table is half full, - // resize early to reduce probing length. - let new_capacity = self.table.capacity() * 2; - self.try_resize(new_capacity)?; - } - Ok(()) - } - - #[cold] - #[inline(never)] - fn try_resize(&mut self, new_raw_cap: usize) -> Result<(), FailedAllocationError> { - assert!(self.table.size() <= new_raw_cap); - assert!(new_raw_cap.is_power_of_two() || new_raw_cap == 0); - - let mut old_table = replace(&mut self.table, RawTable::new(new_raw_cap)?); - let old_size = old_table.size(); - - if old_table.size() == 0 { - return Ok(()); - } - - let mut bucket = Bucket::head_bucket(&mut old_table); - - // This is how the buckets might be laid out in memory: - // ($ marks an initialized bucket) - // ________________ - // |$$$_$$$$$$_$$$$$| - // - // But we've skipped the entire initial cluster of buckets - // and will continue iteration in this order: - // ________________ - // |$$$$$$_$$$$$ - // ^ wrap around once end is reached - // ________________ - // $$$_____________| - // ^ exit once table.size == 0 - loop { - bucket = match bucket.peek() { - Full(bucket) => { - let h = bucket.hash(); - let (b, k, v) = bucket.take(); - self.insert_hashed_ordered(h, k, v); - if b.table().size() == 0 { - break; - } - b.into_bucket() - }, - Empty(b) => b.into_bucket(), - }; - bucket.next(); - } - - assert_eq!(self.table.size(), old_size); - Ok(()) - } - - /// Shrinks the capacity of the map as much as possible. It will drop - /// down as much as possible while maintaining the internal rules - /// and possibly leaving some space in accordance with the resize policy. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap = HashMap::with_capacity(100); - /// map.insert(1, 2); - /// map.insert(3, 4); - /// assert!(map.capacity() >= 100); - /// map.shrink_to_fit(); - /// assert!(map.capacity() >= 2); - /// ``` - pub fn shrink_to_fit(&mut self) { - self.try_shrink_to_fit().unwrap(); - } - - pub fn try_shrink_to_fit(&mut self) -> Result<(), FailedAllocationError> { - let new_raw_cap = self.resize_policy.raw_capacity(self.len()); - if self.raw_capacity() != new_raw_cap { - let old_table = replace(&mut self.table, RawTable::new(new_raw_cap)?); - let old_size = old_table.size(); - - // Shrink the table. Naive algorithm for resizing: - for (h, k, v) in old_table.into_iter() { - self.insert_hashed_nocheck(h, k, v); - } - - debug_assert_eq!(self.table.size(), old_size); - } - Ok(()) - } - - /// Insert a pre-hashed key-value pair, without first checking - /// that there's enough room in the buckets. Returns a reference to the - /// newly insert value. - /// - /// If the key already exists, the hashtable will be returned untouched - /// and a reference to the existing element will be returned. - fn insert_hashed_nocheck(&mut self, hash: SafeHash, k: K, v: V) -> Option { - let entry = search_hashed(&mut self.table, hash, |key| *key == k).into_entry(k); - match entry { - Some(Occupied(mut elem)) => Some(elem.insert(v)), - Some(Vacant(elem)) => { - elem.insert(v); - None - }, - None => unreachable!(), - } - } - - /// An iterator visiting all keys in arbitrary order. - /// The iterator element type is `&'a K`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for key in map.keys() { - /// println!("{}", key); - /// } - /// ``` - pub fn keys(&self) -> Keys<'_, K, V> { - Keys { inner: self.iter() } - } - - /// An iterator visiting all values in arbitrary order. - /// The iterator element type is `&'a V`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for val in map.values() { - /// println!("{}", val); - /// } - /// ``` - pub fn values(&self) -> Values<'_, K, V> { - Values { inner: self.iter() } - } - - /// An iterator visiting all values mutably in arbitrary order. - /// The iterator element type is `&'a mut V`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for val in map.values_mut() { - /// *val = *val + 10; - /// } - /// - /// for val in map.values() { - /// println!("{}", val); - /// } - /// ``` - pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { - ValuesMut { - inner: self.iter_mut(), - } - } - - /// An iterator visiting all key-value pairs in arbitrary order. - /// The iterator element type is `(&'a K, &'a V)`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// for (key, val) in map.iter() { - /// println!("key: {} val: {}", key, val); - /// } - /// ``` - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - inner: self.table.iter(), - } - } - - /// An iterator visiting all key-value pairs in arbitrary order, - /// with mutable references to the values. - /// The iterator element type is `(&'a K, &'a mut V)`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// // Update all values - /// for (_, val) in map.iter_mut() { - /// *val *= 2; - /// } - /// - /// for (key, val) in &map { - /// println!("key: {} val: {}", key, val); - /// } - /// ``` - pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { - IterMut { - inner: self.table.iter_mut(), - } - } - - /// Gets the given key's corresponding entry in the map for in-place manipulation. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut letters = HashMap::new(); - /// - /// for ch in "a short treatise on fungi".chars() { - /// let counter = letters.entry(ch).or_insert(0); - /// *counter += 1; - /// } - /// - /// assert_eq!(letters[&'s'], 2); - /// assert_eq!(letters[&'t'], 3); - /// assert_eq!(letters[&'u'], 1); - /// assert_eq!(letters.get(&'y'), None); - /// ``` - pub fn entry(&mut self, key: K) -> Entry<'_, K, V> { - self.try_entry(key).unwrap() - } - - #[inline(always)] - pub fn try_entry(&mut self, key: K) -> Result, FailedAllocationError> { - // Gotta resize now. - self.try_reserve(1)?; - let hash = self.make_hash(&key); - Ok(search_hashed(&mut self.table, hash, |q| q.eq(&key)) - .into_entry(key) - .expect("unreachable")) - } - - /// Returns the number of elements in the map. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// assert_eq!(a.len(), 0); - /// a.insert(1, "a"); - /// assert_eq!(a.len(), 1); - /// ``` - pub fn len(&self) -> usize { - self.table.size() - } - - /// Returns true if the map contains no elements. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// assert!(a.is_empty()); - /// a.insert(1, "a"); - /// assert!(!a.is_empty()); - /// ``` - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Clears the map, returning all key-value pairs as an iterator. Keeps the - /// allocated memory for reuse. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// a.insert(1, "a"); - /// a.insert(2, "b"); - /// - /// for (k, v) in a.drain().take(1) { - /// assert!(k == 1 || k == 2); - /// assert!(v == "a" || v == "b"); - /// } - /// - /// assert!(a.is_empty()); - /// ``` - #[inline] - pub fn drain(&mut self) -> Drain<'_, K, V> - where - K: 'static, - V: 'static, - { - Drain { - inner: self.table.drain(), - } - } - - /// Clears the map, removing all key-value pairs. Keeps the allocated memory - /// for reuse. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut a = HashMap::new(); - /// a.insert(1, "a"); - /// a.clear(); - /// assert!(a.is_empty()); - /// ``` - #[inline] - pub fn clear(&mut self) - where - K: 'static, - V: 'static, - { - self.drain(); - } - - /// Returns a reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// assert_eq!(map.get(&1), Some(&"a")); - /// assert_eq!(map.get(&2), None); - /// ``` - pub fn get(&self, k: &Q) -> Option<&V> - where - K: Borrow, - Q: Hash + Eq, - { - self.search(k) - .into_occupied_bucket() - .map(|bucket| bucket.into_refs().1) - } - - /// Returns true if the map contains a value for the specified key. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// assert_eq!(map.contains_key(&1), true); - /// assert_eq!(map.contains_key(&2), false); - /// ``` - pub fn contains_key(&self, k: &Q) -> bool - where - K: Borrow, - Q: Hash + Eq, - { - self.search(k).into_occupied_bucket().is_some() - } - - /// Returns a mutable reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// if let Some(x) = map.get_mut(&1) { - /// *x = "b"; - /// } - /// assert_eq!(map[&1], "b"); - /// ``` - pub fn get_mut(&mut self, k: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Hash + Eq, - { - self.search_mut(k) - .into_occupied_bucket() - .map(|bucket| bucket.into_mut_refs().1) - } - - /// Inserts a key-value pair into the map. - /// - /// If the map did not have this key present, [`None`] is returned. - /// - /// If the map did have this key present, the value is updated, and the old - /// value is returned. The key is not updated, though; this matters for - /// types that can be `==` without being identical. See the [module-level - /// documentation] for more. - /// - /// [`None`]: ../../std/option/enum.Option.html#variant.None - /// [module-level documentation]: index.html#insert-and-complex-keys - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// assert_eq!(map.insert(37, "a"), None); - /// assert_eq!(map.is_empty(), false); - /// - /// map.insert(37, "b"); - /// assert_eq!(map.insert(37, "c"), Some("b")); - /// assert_eq!(map[&37], "c"); - /// ``` - pub fn insert(&mut self, k: K, v: V) -> Option { - self.try_insert(k, v).unwrap() - } - - #[inline] - pub fn try_insert(&mut self, k: K, v: V) -> Result, FailedAllocationError> { - let hash = self.make_hash(&k); - self.try_reserve(1)?; - Ok(self.insert_hashed_nocheck(hash, k, v)) - } - - /// Removes a key from the map, returning the value at the key if the key - /// was previously in the map. - /// - /// The key may be any borrowed form of the map's key type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the key type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert(1, "a"); - /// assert_eq!(map.remove(&1), Some("a")); - /// assert_eq!(map.remove(&1), None); - /// ``` - pub fn remove(&mut self, k: &Q) -> Option - where - K: Borrow, - Q: Hash + Eq, - { - if self.table.size() == 0 { - return None; - } - - self.search_mut(k) - .into_occupied_bucket() - .map(|bucket| pop_internal(bucket).1) - } - - /// Retains only the elements specified by the predicate. - /// - /// In other words, remove all pairs `(k, v)` such that `f(&k,&mut v)` returns `false`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap = (0..8).map(|x|(x, x*10)).collect(); - /// map.retain(|&k, _| k % 2 == 0); - /// assert_eq!(map.len(), 4); - /// ``` - pub fn retain(&mut self, mut f: F) - where - F: FnMut(&K, &mut V) -> bool, - { - if self.table.size() == 0 { - return; - } - let mut elems_left = self.table.size(); - let mut bucket = Bucket::head_bucket(&mut self.table); - bucket.prev(); - let start_index = bucket.index(); - while elems_left != 0 { - bucket = match bucket.peek() { - Full(mut full) => { - elems_left -= 1; - let should_remove = { - let (k, v) = full.read_mut(); - !f(k, v) - }; - if should_remove { - let prev_raw = full.raw(); - let (_, _, t) = pop_internal(full); - Bucket::new_from(prev_raw, t) - } else { - full.into_bucket() - } - }, - Empty(b) => b.into_bucket(), - }; - bucket.prev(); // reverse iteration - debug_assert!(elems_left == 0 || bucket.index() != start_index); - } - } -} - -impl PartialEq for HashMap -where - K: Eq + Hash, - V: PartialEq, - S: BuildHasher, -{ - fn eq(&self, other: &HashMap) -> bool { - if self.len() != other.len() { - return false; - } - - self.iter() - .all(|(key, value)| other.get(key).map_or(false, |v| *value == *v)) - } -} - -impl Eq for HashMap -where - K: Eq + Hash, - V: Eq, - S: BuildHasher, -{ -} - -impl Debug for HashMap -where - K: Eq + Hash + Debug, - V: Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map().entries(self.iter()).finish() - } -} - -impl Default for HashMap -where - K: Eq + Hash, - S: BuildHasher + Default, -{ - /// Creates an empty `HashMap`, with the `Default` value for the hasher. - fn default() -> HashMap { - HashMap::with_hasher(Default::default()) - } -} - -impl<'a, K, Q: ?Sized, V, S> Index<&'a Q> for HashMap -where - K: Eq + Hash + Borrow, - Q: Eq + Hash, - S: BuildHasher, -{ - type Output = V; - - #[inline] - fn index(&self, index: &Q) -> &V { - self.get(index).expect("no entry found for key") - } -} - -/// An iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`iter`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`iter`]: struct.HashMap.html#method.iter -/// [`HashMap`]: struct.HashMap.html -pub struct Iter<'a, K, V> { - inner: table::Iter<'a, K, V>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Iter<'a, K, V> { - fn clone(&self) -> Iter<'a, K, V> { - Iter { - inner: self.inner.clone(), - } - } -} - -impl<'a, K: Debug, V: Debug> fmt::Debug for Iter<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A mutable iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`iter_mut`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`iter_mut`]: struct.HashMap.html#method.iter_mut -/// [`HashMap`]: struct.HashMap.html -pub struct IterMut<'a, K, V> { - inner: table::IterMut<'a, K, V>, -} - -/// An owning iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`into_iter`] method on [`HashMap`][`HashMap`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`into_iter`]: struct.HashMap.html#method.into_iter -/// [`HashMap`]: struct.HashMap.html -pub struct IntoIter { - pub(super) inner: table::IntoIter, -} - -/// An iterator over the keys of a `HashMap`. -/// -/// This `struct` is created by the [`keys`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`keys`]: struct.HashMap.html#method.keys -/// [`HashMap`]: struct.HashMap.html -pub struct Keys<'a, K, V> { - inner: Iter<'a, K, V>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Keys<'a, K, V> { - fn clone(&self) -> Keys<'a, K, V> { - Keys { - inner: self.inner.clone(), - } - } -} - -impl<'a, K: Debug, V> fmt::Debug for Keys<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// An iterator over the values of a `HashMap`. -/// -/// This `struct` is created by the [`values`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`values`]: struct.HashMap.html#method.values -/// [`HashMap`]: struct.HashMap.html -pub struct Values<'a, K, V> { - inner: Iter<'a, K, V>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Values<'a, K, V> { - fn clone(&self) -> Values<'a, K, V> { - Values { - inner: self.inner.clone(), - } - } -} - -impl<'a, K, V: Debug> fmt::Debug for Values<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -/// A draining iterator over the entries of a `HashMap`. -/// -/// This `struct` is created by the [`drain`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`drain`]: struct.HashMap.html#method.drain -/// [`HashMap`]: struct.HashMap.html -pub struct Drain<'a, K: 'static, V: 'static> { - pub(super) inner: table::Drain<'a, K, V>, -} - -/// A mutable iterator over the values of a `HashMap`. -/// -/// This `struct` is created by the [`values_mut`] method on [`HashMap`]. See its -/// documentation for more. -/// -/// [`values_mut`]: struct.HashMap.html#method.values_mut -/// [`HashMap`]: struct.HashMap.html -pub struct ValuesMut<'a, K, V> { - inner: IterMut<'a, K, V>, -} - -enum InternalEntry { - Occupied { - elem: FullBucket, - }, - Vacant { - hash: SafeHash, - elem: VacantEntryState, - }, - TableIsEmpty, -} - -impl InternalEntry { - #[inline] - fn into_occupied_bucket(self) -> Option> { - match self { - InternalEntry::Occupied { elem } => Some(elem), - _ => None, - } - } -} - -impl<'a, K, V> InternalEntry> { - #[inline] - fn into_entry(self, key: K) -> Option> { - match self { - InternalEntry::Occupied { elem } => Some(Occupied(OccupiedEntry { - key: Some(key), - elem, - })), - InternalEntry::Vacant { hash, elem } => Some(Vacant(VacantEntry { hash, key, elem })), - InternalEntry::TableIsEmpty => None, - } - } -} - -/// A view into a single entry in a map, which may either be vacant or occupied. -/// -/// This `enum` is constructed from the [`entry`] method on [`HashMap`]. -/// -/// [`HashMap`]: struct.HashMap.html -/// [`entry`]: struct.HashMap.html#method.entry -pub enum Entry<'a, K, V> { - /// An occupied entry. - Occupied(OccupiedEntry<'a, K, V>), - - /// A vacant entry. - Vacant(VacantEntry<'a, K, V>), -} - -impl<'a, K: 'a + Debug, V: 'a + Debug> Debug for Entry<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Vacant(ref v) => f.debug_tuple("Entry").field(v).finish(), - Occupied(ref o) => f.debug_tuple("Entry").field(o).finish(), - } - } -} - -/// A view into an occupied entry in a `HashMap`. -/// It is part of the [`Entry`] enum. -/// -/// [`Entry`]: enum.Entry.html -pub struct OccupiedEntry<'a, K, V> { - key: Option, - elem: FullBucket>, -} - -impl<'a, K: 'a + Debug, V: 'a + Debug> Debug for OccupiedEntry<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("OccupiedEntry") - .field("key", self.key()) - .field("value", self.get()) - .finish() - } -} - -/// A view into a vacant entry in a `HashMap`. -/// It is part of the [`Entry`] enum. -/// -/// [`Entry`]: enum.Entry.html -pub struct VacantEntry<'a, K, V> { - hash: SafeHash, - key: K, - elem: VacantEntryState>, -} - -impl<'a, K: 'a + Debug, V: 'a> Debug for VacantEntry<'a, K, V> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("VacantEntry").field(self.key()).finish() - } -} - -/// Possible states of a VacantEntry. -enum VacantEntryState { - /// The index is occupied, but the key to insert has precedence, - /// and will kick the current one out on insertion. - NeqElem(FullBucket, usize), - /// The index is genuinely vacant. - NoElem(EmptyBucket, usize), -} - -impl<'a, K, V, S> IntoIterator for &'a HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a V); - type IntoIter = Iter<'a, K, V>; - - fn into_iter(self) -> Iter<'a, K, V> { - self.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (&'a K, &'a mut V); - type IntoIter = IterMut<'a, K, V>; - - fn into_iter(self) -> IterMut<'a, K, V> { - self.iter_mut() - } -} - -impl IntoIterator for HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - type Item = (K, V); - type IntoIter = IntoIter; - - /// Creates a consuming iterator, that is, one that moves each key-value - /// pair out of the map in arbitrary order. The map cannot be used after - /// calling this. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map = HashMap::new(); - /// map.insert("a", 1); - /// map.insert("b", 2); - /// map.insert("c", 3); - /// - /// // Not possible with .iter() - /// let vec: Vec<(&str, isize)> = map.into_iter().collect(); - /// ``` - fn into_iter(self) -> IntoIter { - IntoIter { - inner: self.table.into_iter(), - } - } -} - -impl<'a, K, V> Iterator for Iter<'a, K, V> { - type Item = (&'a K, &'a V); - - #[inline] - fn next(&mut self) -> Option<(&'a K, &'a V)> { - self.inner.next() - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> Iterator for IterMut<'a, K, V> { - type Item = (&'a K, &'a mut V); - - #[inline] - fn next(&mut self) -> Option<(&'a K, &'a mut V)> { - self.inner.next() - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> fmt::Debug for IterMut<'a, K, V> -where - K: fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.iter()).finish() - } -} - -impl Iterator for IntoIter { - type Item = (K, V); - - #[inline] - fn next(&mut self) -> Option<(K, V)> { - self.inner.next().map(|(_, k, v)| (k, v)) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl ExactSizeIterator for IntoIter { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl fmt::Debug for IntoIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.iter()).finish() - } -} - -impl<'a, K, V> Iterator for Keys<'a, K, V> { - type Item = &'a K; - - #[inline] - fn next(&mut self) -> Option<&'a K> { - self.inner.next().map(|(k, _)| k) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Keys<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> Iterator for Values<'a, K, V> { - type Item = &'a V; - - #[inline] - fn next(&mut self) -> Option<&'a V> { - self.inner.next().map(|(_, v)| v) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Values<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} -impl<'a, K, V> Iterator for ValuesMut<'a, K, V> { - type Item = &'a mut V; - - #[inline] - fn next(&mut self) -> Option<&'a mut V> { - self.inner.next().map(|(_, v)| v) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for ValuesMut<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> fmt::Debug for ValuesMut<'a, K, V> -where - K: fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.inner.iter()).finish() - } -} - -impl<'a, K, V> Iterator for Drain<'a, K, V> { - type Item = (K, V); - - #[inline] - fn next(&mut self) -> Option<(K, V)> { - self.inner.next().map(|(_, k, v)| (k, v)) - } - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.inner.size_hint() - } -} -impl<'a, K, V> ExactSizeIterator for Drain<'a, K, V> { - #[inline] - fn len(&self) -> usize { - self.inner.len() - } -} - -impl<'a, K, V> fmt::Debug for Drain<'a, K, V> -where - K: fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.inner.iter()).finish() - } -} - -// FORK NOTE: Removed Placer impl - -impl<'a, K, V> Entry<'a, K, V> { - /// Ensures a value is in the entry by inserting the default if empty, and returns - /// a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// assert_eq!(map["poneyland"], 12); - /// - /// *map.entry("poneyland").or_insert(12) += 10; - /// assert_eq!(map["poneyland"], 22); - /// ``` - pub fn or_insert(self, default: V) -> &'a mut V { - match self { - Occupied(entry) => entry.into_mut(), - Vacant(entry) => entry.insert(default), - } - } - - /// Ensures a value is in the entry by inserting the result of the default function if empty, - /// and returns a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, String> = HashMap::new(); - /// let s = "hoho".to_string(); - /// - /// map.entry("poneyland").or_insert_with(|| s); - /// - /// assert_eq!(map["poneyland"], "hoho".to_string()); - /// ``` - pub fn or_insert_with V>(self, default: F) -> &'a mut V { - match self { - Occupied(entry) => entry.into_mut(), - Vacant(entry) => entry.insert(default()), - } - } - - /// Returns a reference to this entry's key. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); - /// ``` - pub fn key(&self) -> &K { - match *self { - Occupied(ref entry) => entry.key(), - Vacant(ref entry) => entry.key(), - } - } -} - -impl<'a, K, V> OccupiedEntry<'a, K, V> { - /// Gets a reference to the key in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); - /// ``` - pub fn key(&self) -> &K { - self.elem.read().0 - } - - /// Take the ownership of the key and value from the map. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// // We delete the entry from the map. - /// o.remove_entry(); - /// } - /// - /// assert_eq!(map.contains_key("poneyland"), false); - /// ``` - pub fn remove_entry(self) -> (K, V) { - let (k, v, _) = pop_internal(self.elem); - (k, v) - } - - /// Gets a reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// assert_eq!(o.get(), &12); - /// } - /// ``` - pub fn get(&self) -> &V { - self.elem.read().1 - } - - /// Gets a mutable reference to the value in the entry. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// assert_eq!(map["poneyland"], 12); - /// if let Entry::Occupied(mut o) = map.entry("poneyland") { - /// *o.get_mut() += 10; - /// } - /// - /// assert_eq!(map["poneyland"], 22); - /// ``` - pub fn get_mut(&mut self) -> &mut V { - self.elem.read_mut().1 - } - - /// Converts the OccupiedEntry into a mutable reference to the value in the entry - /// with a lifetime bound to the map itself. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// assert_eq!(map["poneyland"], 12); - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// *o.into_mut() += 10; - /// } - /// - /// assert_eq!(map["poneyland"], 22); - /// ``` - pub fn into_mut(self) -> &'a mut V { - self.elem.into_mut_refs().1 - } - - /// Sets the value of the entry, and returns the entry's old value. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(mut o) = map.entry("poneyland") { - /// assert_eq!(o.insert(15), 12); - /// } - /// - /// assert_eq!(map["poneyland"], 15); - /// ``` - pub fn insert(&mut self, mut value: V) -> V { - let old_value = self.get_mut(); - mem::swap(&mut value, old_value); - value - } - - /// Takes the value out of the entry, and returns it. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// map.entry("poneyland").or_insert(12); - /// - /// if let Entry::Occupied(o) = map.entry("poneyland") { - /// assert_eq!(o.remove(), 12); - /// } - /// - /// assert_eq!(map.contains_key("poneyland"), false); - /// ``` - pub fn remove(self) -> V { - pop_internal(self.elem).1 - } - - /// Returns a key that was used for search. - /// - /// The key was retained for further use. - fn take_key(&mut self) -> Option { - self.key.take() - } -} - -impl<'a, K: 'a, V: 'a> VacantEntry<'a, K, V> { - /// Gets a reference to the key that would be used when inserting a value - /// through the `VacantEntry`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// assert_eq!(map.entry("poneyland").key(), &"poneyland"); - /// ``` - pub fn key(&self) -> &K { - &self.key - } - - /// Take ownership of the key. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// - /// if let Entry::Vacant(v) = map.entry("poneyland") { - /// v.into_key(); - /// } - /// ``` - pub fn into_key(self) -> K { - self.key - } - - /// Sets the value of the entry with the VacantEntry's key, - /// and returns a mutable reference to it. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashMap; - /// use std::collections::hash_map::Entry; - /// - /// let mut map: HashMap<&str, u32> = HashMap::new(); - /// - /// if let Entry::Vacant(o) = map.entry("poneyland") { - /// o.insert(37); - /// } - /// assert_eq!(map["poneyland"], 37); - /// ``` - pub fn insert(self, value: V) -> &'a mut V { - let b = match self.elem { - NeqElem(mut bucket, disp) => { - if disp >= DISPLACEMENT_THRESHOLD { - bucket.table_mut().set_tag(true); - } - robin_hood(bucket, disp, self.hash, self.key, value) - }, - NoElem(mut bucket, disp) => { - if disp >= DISPLACEMENT_THRESHOLD { - bucket.table_mut().set_tag(true); - } - bucket.put(self.hash, self.key, value) - }, - }; - b.into_mut_refs().1 - } -} - -impl FromIterator<(K, V)> for HashMap -where - K: Eq + Hash, - S: BuildHasher + Default, -{ - fn from_iter>(iter: T) -> HashMap { - let mut map = HashMap::with_hasher(Default::default()); - map.extend(iter); - map - } -} - -impl Extend<(K, V)> for HashMap -where - K: Eq + Hash, - S: BuildHasher, -{ - fn extend>(&mut self, iter: T) { - // Keys may be already present or show multiple times in the iterator. - // Reserve the entire hint lower bound if the map is empty. - // Otherwise reserve half the hint (rounded up), so the map - // will only resize twice in the worst case. - let iter = iter.into_iter(); - let reserve = if self.is_empty() { - iter.size_hint().0 - } else { - (iter.size_hint().0 + 1) / 2 - }; - self.reserve(reserve); - for (k, v) in iter { - self.insert(k, v); - } - } -} - -impl<'a, K, V, S> Extend<(&'a K, &'a V)> for HashMap -where - K: Eq + Hash + Copy, - V: Copy, - S: BuildHasher, -{ - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(|(&key, &value)| (key, value))); - } -} - -// FORK NOTE: These can be reused -pub use std::collections::hash_map::{DefaultHasher, RandomState}; - -impl super::Recover for HashMap -where - K: Eq + Hash + Borrow, - S: BuildHasher, - Q: Eq + Hash, -{ - type Key = K; - - fn get(&self, key: &Q) -> Option<&K> { - self.search(key) - .into_occupied_bucket() - .map(|bucket| bucket.into_refs().0) - } - - fn take(&mut self, key: &Q) -> Option { - if self.table.size() == 0 { - return None; - } - - self.search_mut(key) - .into_occupied_bucket() - .map(|bucket| pop_internal(bucket).0) - } - - fn replace(&mut self, key: K) -> Option { - self.reserve(1); - - match self.entry(key) { - Occupied(mut occupied) => { - let key = occupied.take_key().unwrap(); - Some(mem::replace(occupied.elem.read_mut().0, key)) - }, - Vacant(vacant) => { - vacant.insert(()); - None - }, - } - } -} - -#[allow(dead_code)] -fn assert_covariance() { - fn map_key<'new>(v: HashMap<&'static str, u8>) -> HashMap<&'new str, u8> { - v - } - fn map_val<'new>(v: HashMap) -> HashMap { - v - } - fn iter_key<'a, 'new>(v: Iter<'a, &'static str, u8>) -> Iter<'a, &'new str, u8> { - v - } - fn iter_val<'a, 'new>(v: Iter<'a, u8, &'static str>) -> Iter<'a, u8, &'new str> { - v - } - fn into_iter_key<'new>(v: IntoIter<&'static str, u8>) -> IntoIter<&'new str, u8> { - v - } - fn into_iter_val<'new>(v: IntoIter) -> IntoIter { - v - } - fn keys_key<'a, 'new>(v: Keys<'a, &'static str, u8>) -> Keys<'a, &'new str, u8> { - v - } - fn keys_val<'a, 'new>(v: Keys<'a, u8, &'static str>) -> Keys<'a, u8, &'new str> { - v - } - fn values_key<'a, 'new>(v: Values<'a, &'static str, u8>) -> Values<'a, &'new str, u8> { - v - } - fn values_val<'a, 'new>(v: Values<'a, u8, &'static str>) -> Values<'a, u8, &'new str> { - v - } - fn drain<'new>( - d: Drain<'static, &'static str, &'static str>, - ) -> Drain<'new, &'new str, &'new str> { - d - } -} - -#[cfg(test)] -mod test_map { - extern crate rand; - use self::rand::{thread_rng, Rng}; - use super::Entry::{Occupied, Vacant}; - use super::HashMap; - use super::RandomState; - use std::cell::RefCell; - - #[test] - fn test_zero_capacities() { - type HM = HashMap; - - let m = HM::new(); - assert_eq!(m.capacity(), 0); - - let m = HM::default(); - assert_eq!(m.capacity(), 0); - - let m = HM::with_hasher(RandomState::new()); - assert_eq!(m.capacity(), 0); - - let m = HM::with_capacity(0); - assert_eq!(m.capacity(), 0); - - let m = HM::with_capacity_and_hasher(0, RandomState::new()); - assert_eq!(m.capacity(), 0); - - let mut m = HM::new(); - m.insert(1, 1); - m.insert(2, 2); - m.remove(&1); - m.remove(&2); - m.shrink_to_fit(); - assert_eq!(m.capacity(), 0); - - let mut m = HM::new(); - m.reserve(0); - assert_eq!(m.capacity(), 0); - } - - #[test] - fn test_create_capacity_zero() { - let mut m = HashMap::with_capacity(0); - - assert!(m.insert(1, 1).is_none()); - - assert!(m.contains_key(&1)); - assert!(!m.contains_key(&0)); - } - - #[test] - fn test_insert() { - let mut m = HashMap::new(); - assert_eq!(m.len(), 0); - assert!(m.insert(1, 2).is_none()); - assert_eq!(m.len(), 1); - assert!(m.insert(2, 4).is_none()); - assert_eq!(m.len(), 2); - assert_eq!(*m.get(&1).unwrap(), 2); - assert_eq!(*m.get(&2).unwrap(), 4); - } - - #[test] - fn test_clone() { - let mut m = HashMap::new(); - assert_eq!(m.len(), 0); - assert!(m.insert(1, 2).is_none()); - assert_eq!(m.len(), 1); - assert!(m.insert(2, 4).is_none()); - assert_eq!(m.len(), 2); - let m2 = m.clone(); - assert_eq!(*m2.get(&1).unwrap(), 2); - assert_eq!(*m2.get(&2).unwrap(), 4); - assert_eq!(m2.len(), 2); - } - - thread_local! { static DROP_VECTOR: RefCell> = RefCell::new(Vec::new()) } - - #[derive(Hash, PartialEq, Eq)] - struct Dropable { - k: usize, - } - - impl Dropable { - fn new(k: usize) -> Dropable { - DROP_VECTOR.with(|slot| { - slot.borrow_mut()[k] += 1; - }); - - Dropable { k: k } - } - } - - impl Drop for Dropable { - fn drop(&mut self) { - DROP_VECTOR.with(|slot| { - slot.borrow_mut()[self.k] -= 1; - }); - } - } - - impl Clone for Dropable { - fn clone(&self) -> Dropable { - Dropable::new(self.k) - } - } - - #[test] - fn test_drops() { - DROP_VECTOR.with(|slot| { - *slot.borrow_mut() = vec![0; 200]; - }); - - { - let mut m = HashMap::new(); - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - - for i in 0..100 { - let d1 = Dropable::new(i); - let d2 = Dropable::new(i + 100); - m.insert(d1, d2); - } - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 1); - } - }); - - for i in 0..50 { - let k = Dropable::new(i); - let v = m.remove(&k); - - assert!(v.is_some()); - - DROP_VECTOR.with(|v| { - assert_eq!(v.borrow()[i], 1); - assert_eq!(v.borrow()[i + 100], 1); - }); - } - - DROP_VECTOR.with(|v| { - for i in 0..50 { - assert_eq!(v.borrow()[i], 0); - assert_eq!(v.borrow()[i + 100], 0); - } - - for i in 50..100 { - assert_eq!(v.borrow()[i], 1); - assert_eq!(v.borrow()[i + 100], 1); - } - }); - } - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - } - - #[test] - fn test_into_iter_drops() { - DROP_VECTOR.with(|v| { - *v.borrow_mut() = vec![0; 200]; - }); - - let hm = { - let mut hm = HashMap::new(); - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - - for i in 0..100 { - let d1 = Dropable::new(i); - let d2 = Dropable::new(i + 100); - hm.insert(d1, d2); - } - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 1); - } - }); - - hm - }; - - // By the way, ensure that cloning doesn't screw up the dropping. - drop(hm.clone()); - - { - let mut half = hm.into_iter().take(50); - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 1); - } - }); - - for _ in half.by_ref() {} - - DROP_VECTOR.with(|v| { - let nk = (0..100).filter(|&i| v.borrow()[i] == 1).count(); - - let nv = (0..100).filter(|&i| v.borrow()[i + 100] == 1).count(); - - assert_eq!(nk, 50); - assert_eq!(nv, 50); - }); - }; - - DROP_VECTOR.with(|v| { - for i in 0..200 { - assert_eq!(v.borrow()[i], 0); - } - }); - } - - #[test] - fn test_empty_remove() { - let mut m: HashMap = HashMap::new(); - assert_eq!(m.remove(&0), None); - } - - #[test] - fn test_empty_entry() { - let mut m: HashMap = HashMap::new(); - match m.entry(0) { - Occupied(_) => panic!(), - Vacant(_) => {}, - } - assert!(*m.entry(0).or_insert(true)); - assert_eq!(m.len(), 1); - } - - #[test] - fn test_empty_iter() { - let mut m: HashMap = HashMap::new(); - assert_eq!(m.drain().next(), None); - assert_eq!(m.keys().next(), None); - assert_eq!(m.values().next(), None); - assert_eq!(m.values_mut().next(), None); - assert_eq!(m.iter().next(), None); - assert_eq!(m.iter_mut().next(), None); - assert_eq!(m.len(), 0); - assert!(m.is_empty()); - assert_eq!(m.into_iter().next(), None); - } - - #[test] - fn test_lots_of_insertions() { - let mut m = HashMap::new(); - - // Try this a few times to make sure we never screw up the hashmap's - // internal state. - for _ in 0..10 { - assert!(m.is_empty()); - - for i in 1..1001 { - assert!(m.insert(i, i).is_none()); - - for j in 1..i + 1 { - let r = m.get(&j); - assert_eq!(r, Some(&j)); - } - - for j in i + 1..1001 { - let r = m.get(&j); - assert_eq!(r, None); - } - } - - for i in 1001..2001 { - assert!(!m.contains_key(&i)); - } - - // remove forwards - for i in 1..1001 { - assert!(m.remove(&i).is_some()); - - for j in 1..i + 1 { - assert!(!m.contains_key(&j)); - } - - for j in i + 1..1001 { - assert!(m.contains_key(&j)); - } - } - - for i in 1..1001 { - assert!(!m.contains_key(&i)); - } - - for i in 1..1001 { - assert!(m.insert(i, i).is_none()); - } - - // remove backwards - for i in (1..1001).rev() { - assert!(m.remove(&i).is_some()); - - for j in i..1001 { - assert!(!m.contains_key(&j)); - } - - for j in 1..i { - assert!(m.contains_key(&j)); - } - } - } - } - - #[test] - fn test_find_mut() { - let mut m = HashMap::new(); - assert!(m.insert(1, 12).is_none()); - assert!(m.insert(2, 8).is_none()); - assert!(m.insert(5, 14).is_none()); - let new = 100; - match m.get_mut(&5) { - None => panic!(), - Some(x) => *x = new, - } - assert_eq!(m.get(&5), Some(&new)); - } - - #[test] - fn test_insert_overwrite() { - let mut m = HashMap::new(); - assert!(m.insert(1, 2).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert!(!m.insert(1, 3).is_none()); - assert_eq!(*m.get(&1).unwrap(), 3); - } - - #[test] - fn test_insert_conflicts() { - let mut m = HashMap::with_capacity(4); - assert!(m.insert(1, 2).is_none()); - assert!(m.insert(5, 3).is_none()); - assert!(m.insert(9, 4).is_none()); - assert_eq!(*m.get(&9).unwrap(), 4); - assert_eq!(*m.get(&5).unwrap(), 3); - assert_eq!(*m.get(&1).unwrap(), 2); - } - - #[test] - fn test_conflict_remove() { - let mut m = HashMap::with_capacity(4); - assert!(m.insert(1, 2).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert!(m.insert(5, 3).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert_eq!(*m.get(&5).unwrap(), 3); - assert!(m.insert(9, 4).is_none()); - assert_eq!(*m.get(&1).unwrap(), 2); - assert_eq!(*m.get(&5).unwrap(), 3); - assert_eq!(*m.get(&9).unwrap(), 4); - assert!(m.remove(&1).is_some()); - assert_eq!(*m.get(&9).unwrap(), 4); - assert_eq!(*m.get(&5).unwrap(), 3); - } - - #[test] - fn test_is_empty() { - let mut m = HashMap::with_capacity(4); - assert!(m.insert(1, 2).is_none()); - assert!(!m.is_empty()); - assert!(m.remove(&1).is_some()); - assert!(m.is_empty()); - } - - #[test] - fn test_pop() { - let mut m = HashMap::new(); - m.insert(1, 2); - assert_eq!(m.remove(&1), Some(2)); - assert_eq!(m.remove(&1), None); - } - - #[test] - fn test_iterate() { - let mut m = HashMap::with_capacity(4); - for i in 0..32 { - assert!(m.insert(i, i * 2).is_none()); - } - assert_eq!(m.len(), 32); - - let mut observed: u32 = 0; - - for (k, v) in &m { - assert_eq!(*v, *k * 2); - observed |= 1 << *k; - } - assert_eq!(observed, 0xFFFF_FFFF); - } - - #[test] - fn test_keys() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: HashMap<_, _> = vec.into_iter().collect(); - let keys: Vec<_> = map.keys().cloned().collect(); - assert_eq!(keys.len(), 3); - assert!(keys.contains(&1)); - assert!(keys.contains(&2)); - assert!(keys.contains(&3)); - } - - #[test] - fn test_values() { - let vec = vec![(1, 'a'), (2, 'b'), (3, 'c')]; - let map: HashMap<_, _> = vec.into_iter().collect(); - let values: Vec<_> = map.values().cloned().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&'a')); - assert!(values.contains(&'b')); - assert!(values.contains(&'c')); - } - - #[test] - fn test_values_mut() { - let vec = vec![(1, 1), (2, 2), (3, 3)]; - let mut map: HashMap<_, _> = vec.into_iter().collect(); - for value in map.values_mut() { - *value = (*value) * 2 - } - let values: Vec<_> = map.values().cloned().collect(); - assert_eq!(values.len(), 3); - assert!(values.contains(&2)); - assert!(values.contains(&4)); - assert!(values.contains(&6)); - } - - #[test] - fn test_find() { - let mut m = HashMap::new(); - assert!(m.get(&1).is_none()); - m.insert(1, 2); - match m.get(&1) { - None => panic!(), - Some(v) => assert_eq!(*v, 2), - } - } - - #[test] - fn test_eq() { - let mut m1 = HashMap::new(); - m1.insert(1, 2); - m1.insert(2, 3); - m1.insert(3, 4); - - let mut m2 = HashMap::new(); - m2.insert(1, 2); - m2.insert(2, 3); - - assert_ne!(m1, m2); - - m2.insert(3, 4); - - assert_eq!(m1, m2); - } - - #[test] - fn test_show() { - let mut map = HashMap::new(); - let empty: HashMap = HashMap::new(); - - map.insert(1, 2); - map.insert(3, 4); - - let map_str = format!("{:?}", map); - - assert!(map_str == "{1: 2, 3: 4}" || map_str == "{3: 4, 1: 2}"); - assert_eq!(format!("{:?}", empty), "{}"); - } - - #[test] - fn test_expand() { - let mut m = HashMap::new(); - - assert_eq!(m.len(), 0); - assert!(m.is_empty()); - - let mut i = 0; - let old_raw_cap = m.raw_capacity(); - while old_raw_cap == m.raw_capacity() { - m.insert(i, i); - i += 1; - } - - assert_eq!(m.len(), i); - assert!(!m.is_empty()); - } - - #[test] - fn test_behavior_resize_policy() { - let mut m = HashMap::new(); - - assert_eq!(m.len(), 0); - assert_eq!(m.raw_capacity(), 0); - assert!(m.is_empty()); - - m.insert(0, 0); - m.remove(&0); - assert!(m.is_empty()); - let initial_raw_cap = m.raw_capacity(); - m.reserve(initial_raw_cap); - let raw_cap = m.raw_capacity(); - - assert_eq!(raw_cap, initial_raw_cap * 2); - - let mut i = 0; - for _ in 0..raw_cap * 3 / 4 { - m.insert(i, i); - i += 1; - } - // three quarters full - - assert_eq!(m.len(), i); - assert_eq!(m.raw_capacity(), raw_cap); - - for _ in 0..raw_cap / 4 { - m.insert(i, i); - i += 1; - } - // half full - - let new_raw_cap = m.raw_capacity(); - assert_eq!(new_raw_cap, raw_cap * 2); - - for _ in 0..raw_cap / 2 - 1 { - i -= 1; - m.remove(&i); - assert_eq!(m.raw_capacity(), new_raw_cap); - } - // A little more than one quarter full. - m.shrink_to_fit(); - assert_eq!(m.raw_capacity(), raw_cap); - // again, a little more than half full - for _ in 0..raw_cap / 2 - 1 { - i -= 1; - m.remove(&i); - } - m.shrink_to_fit(); - - assert_eq!(m.len(), i); - assert!(!m.is_empty()); - assert_eq!(m.raw_capacity(), initial_raw_cap); - } - - #[test] - fn test_reserve_shrink_to_fit() { - let mut m = HashMap::new(); - m.insert(0, 0); - m.remove(&0); - assert!(m.capacity() >= m.len()); - for i in 0..128 { - m.insert(i, i); - } - m.reserve(256); - - let usable_cap = m.capacity(); - for i in 128..(128 + 256) { - m.insert(i, i); - assert_eq!(m.capacity(), usable_cap); - } - - for i in 100..(128 + 256) { - assert_eq!(m.remove(&i), Some(i)); - } - m.shrink_to_fit(); - - assert_eq!(m.len(), 100); - assert!(!m.is_empty()); - assert!(m.capacity() >= m.len()); - - for i in 0..100 { - assert_eq!(m.remove(&i), Some(i)); - } - m.shrink_to_fit(); - m.insert(0, 0); - - assert_eq!(m.len(), 1); - assert!(m.capacity() >= m.len()); - assert_eq!(m.remove(&0), Some(0)); - } - - #[test] - fn test_from_iter() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let map: HashMap<_, _> = xs.iter().cloned().collect(); - - for &(k, v) in &xs { - assert_eq!(map.get(&k), Some(&v)); - } - } - - #[test] - fn test_size_hint() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.size_hint(), (3, Some(3))); - } - - #[test] - fn test_iter_len() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.len(), 3); - } - - #[test] - fn test_mut_size_hint() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let mut map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter_mut(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.size_hint(), (3, Some(3))); - } - - #[test] - fn test_iter_mut_len() { - let xs = [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]; - - let mut map: HashMap<_, _> = xs.iter().cloned().collect(); - - let mut iter = map.iter_mut(); - - for _ in iter.by_ref().take(3) {} - - assert_eq!(iter.len(), 3); - } - - #[test] - fn test_index() { - let mut map = HashMap::new(); - - map.insert(1, 2); - map.insert(2, 1); - map.insert(3, 4); - - assert_eq!(map[&2], 1); - } - - #[test] - #[should_panic] - fn test_index_nonexistent() { - let mut map = HashMap::new(); - - map.insert(1, 2); - map.insert(2, 1); - map.insert(3, 4); - - map[&4]; - } - - #[test] - fn test_entry() { - let xs = [(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)]; - - let mut map: HashMap<_, _> = xs.iter().cloned().collect(); - - // Existing key (insert) - match map.entry(1) { - Vacant(_) => unreachable!(), - Occupied(mut view) => { - assert_eq!(view.get(), &10); - assert_eq!(view.insert(100), 10); - }, - } - assert_eq!(map.get(&1).unwrap(), &100); - assert_eq!(map.len(), 6); - - // Existing key (update) - match map.entry(2) { - Vacant(_) => unreachable!(), - Occupied(mut view) => { - let v = view.get_mut(); - let new_v = (*v) * 10; - *v = new_v; - }, - } - assert_eq!(map.get(&2).unwrap(), &200); - assert_eq!(map.len(), 6); - - // Existing key (take) - match map.entry(3) { - Vacant(_) => unreachable!(), - Occupied(view) => { - assert_eq!(view.remove(), 30); - }, - } - assert_eq!(map.get(&3), None); - assert_eq!(map.len(), 5); - - // Inexistent key (insert) - match map.entry(10) { - Occupied(_) => unreachable!(), - Vacant(view) => { - assert_eq!(*view.insert(1000), 1000); - }, - } - assert_eq!(map.get(&10).unwrap(), &1000); - assert_eq!(map.len(), 6); - } - - #[test] - fn test_entry_take_doesnt_corrupt() { - #![allow(deprecated)] //rand - // Test for #19292 - fn check(m: &HashMap) { - for k in m.keys() { - assert!(m.contains_key(k), "{} is in keys() but not in the map?", k); - } - } - - let mut m = HashMap::new(); - let mut rng = thread_rng(); - - // Populate the map with some items. - for _ in 0..50 { - let x = rng.gen_range(-10, 10); - m.insert(x, ()); - } - - for i in 0..1000 { - let x = rng.gen_range(-10, 10); - match m.entry(x) { - Vacant(_) => {}, - Occupied(e) => { - println!("{}: remove {}", i, x); - e.remove(); - }, - } - - check(&m); - } - } - - #[test] - fn test_extend_ref() { - let mut a = HashMap::new(); - a.insert(1, "one"); - let mut b = HashMap::new(); - b.insert(2, "two"); - b.insert(3, "three"); - - a.extend(&b); - - assert_eq!(a.len(), 3); - assert_eq!(a[&1], "one"); - assert_eq!(a[&2], "two"); - assert_eq!(a[&3], "three"); - } - - #[test] - fn test_capacity_not_less_than_len() { - let mut a = HashMap::new(); - let mut item = 0; - - for _ in 0..116 { - a.insert(item, 0); - item += 1; - } - - assert!(a.capacity() > a.len()); - - let free = a.capacity() - a.len(); - for _ in 0..free { - a.insert(item, 0); - item += 1; - } - - assert_eq!(a.len(), a.capacity()); - - // Insert at capacity should cause allocation. - a.insert(item, 0); - assert!(a.capacity() > a.len()); - } - - #[test] - fn test_occupied_entry_key() { - let mut a = HashMap::new(); - let key = "hello there"; - let value = "value goes here"; - assert!(a.is_empty()); - a.insert(key.clone(), value.clone()); - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - - match a.entry(key.clone()) { - Vacant(_) => panic!(), - Occupied(e) => assert_eq!(key, *e.key()), - } - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - } - - #[test] - fn test_vacant_entry_key() { - let mut a = HashMap::new(); - let key = "hello there"; - let value = "value goes here"; - - assert!(a.is_empty()); - match a.entry(key.clone()) { - Occupied(_) => panic!(), - Vacant(e) => { - assert_eq!(key, *e.key()); - e.insert(value.clone()); - }, - } - assert_eq!(a.len(), 1); - assert_eq!(a[key], value); - } - - #[test] - fn test_retain() { - let mut map: HashMap = (0..100).map(|x| (x, x * 10)).collect(); - - map.retain(|&k, _| k % 2 == 0); - assert_eq!(map.len(), 50); - assert_eq!(map[&2], 20); - assert_eq!(map[&4], 40); - assert_eq!(map[&6], 60); - } - - #[test] - fn test_adaptive() { - const TEST_LEN: usize = 5000; - // by cloning we get maps with the same hasher seed - let mut first = HashMap::new(); - let mut second = first.clone(); - first.extend((0..TEST_LEN).map(|i| (i, i))); - second.extend((TEST_LEN..TEST_LEN * 2).map(|i| (i, i))); - - for (&k, &v) in &second { - let prev_cap = first.capacity(); - let expect_grow = first.len() == prev_cap; - first.insert(k, v); - if !expect_grow && first.capacity() != prev_cap { - return; - } - } - panic!("Adaptive early resize failed"); - } -} diff --git a/components/hashglobe/src/hash_set.rs b/components/hashglobe/src/hash_set.rs deleted file mode 100644 index e5fca180c77..00000000000 --- a/components/hashglobe/src/hash_set.rs +++ /dev/null @@ -1,1648 +0,0 @@ -// Copyright 2014 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use std::borrow::Borrow; -use std::fmt; -use std::hash::{BuildHasher, Hash}; -use std::iter::{Chain, FromIterator}; -use std::ops::{BitAnd, BitOr, BitXor, Sub}; - -use super::hash_map::{self, HashMap, Keys, RandomState}; -use super::Recover; - -use crate::FailedAllocationError; - -// Future Optimization (FIXME!) -// ============================= -// -// Iteration over zero sized values is a noop. There is no need -// for `bucket.val` in the case of HashSet. I suppose we would need HKT -// to get rid of it properly. - -/// A hash set implemented as a `HashMap` where the value is `()`. -/// -/// As with the [`HashMap`] type, a `HashSet` requires that the elements -/// implement the [`Eq`] and [`Hash`] traits. This can frequently be achieved by -/// using `#[derive(PartialEq, Eq, Hash)]`. If you implement these yourself, -/// it is important that the following property holds: -/// -/// ```text -/// k1 == k2 -> hash(k1) == hash(k2) -/// ``` -/// -/// In other words, if two keys are equal, their hashes must be equal. -/// -/// -/// It is a logic error for an item to be modified in such a way that the -/// item's hash, as determined by the [`Hash`] trait, or its equality, as -/// determined by the [`Eq`] trait, changes while it is in the set. This is -/// normally only possible through [`Cell`], [`RefCell`], global state, I/O, or -/// unsafe code. -/// -/// # Examples -/// -/// ``` -/// use std::collections::HashSet; -/// // Type inference lets us omit an explicit type signature (which -/// // would be `HashSet<&str>` in this example). -/// let mut books = HashSet::new(); -/// -/// // Add some books. -/// books.insert("A Dance With Dragons"); -/// books.insert("To Kill a Mockingbird"); -/// books.insert("The Odyssey"); -/// books.insert("The Great Gatsby"); -/// -/// // Check for a specific one. -/// if !books.contains("The Winds of Winter") { -/// println!("We have {} books, but The Winds of Winter ain't one.", -/// books.len()); -/// } -/// -/// // Remove a book. -/// books.remove("The Odyssey"); -/// -/// // Iterate over everything. -/// for book in &books { -/// println!("{}", book); -/// } -/// ``` -/// -/// The easiest way to use `HashSet` with a custom type is to derive -/// [`Eq`] and [`Hash`]. We must also derive [`PartialEq`], this will in the -/// future be implied by [`Eq`]. -/// -/// ``` -/// use std::collections::HashSet; -/// #[derive(Hash, Eq, PartialEq, Debug)] -/// struct Viking<'a> { -/// name: &'a str, -/// power: usize, -/// } -/// -/// let mut vikings = HashSet::new(); -/// -/// vikings.insert(Viking { name: "Einar", power: 9 }); -/// vikings.insert(Viking { name: "Einar", power: 9 }); -/// vikings.insert(Viking { name: "Olaf", power: 4 }); -/// vikings.insert(Viking { name: "Harald", power: 8 }); -/// -/// // Use derived implementation to print the vikings. -/// for x in &vikings { -/// println!("{:?}", x); -/// } -/// ``` -/// -/// A `HashSet` with fixed list of elements can be initialized from an array: -/// -/// ``` -/// use std::collections::HashSet; -/// -/// fn main() { -/// let viking_names: HashSet<&str> = -/// [ "Einar", "Olaf", "Harald" ].iter().cloned().collect(); -/// // use the values stored in the set -/// } -/// ``` -/// -/// [`Cell`]: ../../std/cell/struct.Cell.html -/// [`Eq`]: ../../std/cmp/trait.Eq.html -/// [`Hash`]: ../../std/hash/trait.Hash.html -/// [`HashMap`]: struct.HashMap.html -/// [`PartialEq`]: ../../std/cmp/trait.PartialEq.html -/// [`RefCell`]: ../../std/cell/struct.RefCell.html -#[derive(Clone)] -pub struct HashSet { - map: HashMap, -} - -impl HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - /// Creates a new empty hash set which will use the given hasher to hash - /// keys. - /// - /// The hash set is also created with the default initial capacity. - /// - /// Warning: `hasher` is normally randomly generated, and - /// is designed to allow `HashSet`s to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut set = HashSet::with_hasher(s); - /// set.insert(2); - /// ``` - #[inline] - pub fn with_hasher(hasher: S) -> HashSet { - HashSet { - map: HashMap::with_hasher(hasher), - } - } - - /// Creates an empty `HashSet` with with the specified capacity, using - /// `hasher` to hash the keys. - /// - /// The hash set will be able to hold at least `capacity` elements without - /// reallocating. If `capacity` is 0, the hash set will not allocate. - /// - /// Warning: `hasher` is normally randomly generated, and - /// is designed to allow `HashSet`s to be resistant to attacks that - /// cause many collisions and very poor performance. Setting it - /// manually using this function can expose a DoS attack vector. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// use std::collections::hash_map::RandomState; - /// - /// let s = RandomState::new(); - /// let mut set = HashSet::with_capacity_and_hasher(10, s); - /// set.insert(1); - /// ``` - #[inline] - pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> HashSet { - HashSet { - map: HashMap::with_capacity_and_hasher(capacity, hasher), - } - } - - /// Returns a reference to the set's [`BuildHasher`]. - /// - /// [`BuildHasher`]: ../../std/hash/trait.BuildHasher.html - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// use std::collections::hash_map::RandomState; - /// - /// let hasher = RandomState::new(); - /// let set: HashSet = HashSet::with_hasher(hasher); - /// let hasher: &RandomState = set.hasher(); - /// ``` - pub fn hasher(&self) -> &S { - self.map.hasher() - } - - /// Returns the number of elements the set can hold without reallocating. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let set: HashSet = HashSet::with_capacity(100); - /// assert!(set.capacity() >= 100); - /// ``` - #[inline] - pub fn capacity(&self) -> usize { - self.map.capacity() - } - - /// Reserves capacity for at least `additional` more elements to be inserted - /// in the `HashSet`. The collection may reserve more space to avoid - /// frequent reallocations. - /// - /// # Panics - /// - /// Panics if the new allocation size overflows `usize`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let mut set: HashSet = HashSet::new(); - /// set.reserve(10); - /// assert!(set.capacity() >= 10); - /// ``` - pub fn reserve(&mut self, additional: usize) { - self.map.reserve(additional) - } - - /// Shrinks the capacity of the set as much as possible. It will drop - /// down as much as possible while maintaining the internal rules - /// and possibly leaving some space in accordance with the resize policy. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set = HashSet::with_capacity(100); - /// set.insert(1); - /// set.insert(2); - /// assert!(set.capacity() >= 100); - /// set.shrink_to_fit(); - /// assert!(set.capacity() >= 2); - /// ``` - pub fn shrink_to_fit(&mut self) { - self.map.shrink_to_fit() - } - - /// An iterator visiting all elements in arbitrary order. - /// The iterator element type is `&'a T`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let mut set = HashSet::new(); - /// set.insert("a"); - /// set.insert("b"); - /// - /// // Will print in an arbitrary order. - /// for x in set.iter() { - /// println!("{}", x); - /// } - /// ``` - pub fn iter(&self) -> Iter<'_, T> { - Iter { - iter: self.map.keys(), - } - } - - /// Visits the values representing the difference, - /// i.e. the values that are in `self` but not in `other`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Can be seen as `a - b`. - /// for x in a.difference(&b) { - /// println!("{}", x); // Print 1 - /// } - /// - /// let diff: HashSet<_> = a.difference(&b).collect(); - /// assert_eq!(diff, [1].iter().collect()); - /// - /// // Note that difference is not symmetric, - /// // and `b - a` means something else: - /// let diff: HashSet<_> = b.difference(&a).collect(); - /// assert_eq!(diff, [4].iter().collect()); - /// ``` - pub fn difference<'a>(&'a self, other: &'a HashSet) -> Difference<'a, T, S> { - Difference { - iter: self.iter(), - other, - } - } - - /// Visits the values representing the symmetric difference, - /// i.e. the values that are in `self` or in `other` but not in both. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Print 1, 4 in arbitrary order. - /// for x in a.symmetric_difference(&b) { - /// println!("{}", x); - /// } - /// - /// let diff1: HashSet<_> = a.symmetric_difference(&b).collect(); - /// let diff2: HashSet<_> = b.symmetric_difference(&a).collect(); - /// - /// assert_eq!(diff1, diff2); - /// assert_eq!(diff1, [1, 4].iter().collect()); - /// ``` - pub fn symmetric_difference<'a>( - &'a self, - other: &'a HashSet, - ) -> SymmetricDifference<'a, T, S> { - SymmetricDifference { - iter: self.difference(other).chain(other.difference(self)), - } - } - - /// Visits the values representing the intersection, - /// i.e. the values that are both in `self` and `other`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Print 2, 3 in arbitrary order. - /// for x in a.intersection(&b) { - /// println!("{}", x); - /// } - /// - /// let intersection: HashSet<_> = a.intersection(&b).collect(); - /// assert_eq!(intersection, [2, 3].iter().collect()); - /// ``` - pub fn intersection<'a>(&'a self, other: &'a HashSet) -> Intersection<'a, T, S> { - Intersection { - iter: self.iter(), - other, - } - } - - /// Visits the values representing the union, - /// i.e. all the values in `self` or `other`, without duplicates. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let b: HashSet<_> = [4, 2, 3, 4].iter().cloned().collect(); - /// - /// // Print 1, 2, 3, 4 in arbitrary order. - /// for x in a.union(&b) { - /// println!("{}", x); - /// } - /// - /// let union: HashSet<_> = a.union(&b).collect(); - /// assert_eq!(union, [1, 2, 3, 4].iter().collect()); - /// ``` - pub fn union<'a>(&'a self, other: &'a HashSet) -> Union<'a, T, S> { - Union { - iter: self.iter().chain(other.difference(self)), - } - } - - /// Returns the number of elements in the set. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut v = HashSet::new(); - /// assert_eq!(v.len(), 0); - /// v.insert(1); - /// assert_eq!(v.len(), 1); - /// ``` - pub fn len(&self) -> usize { - self.map.len() - } - - /// Returns true if the set contains no elements. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut v = HashSet::new(); - /// assert!(v.is_empty()); - /// v.insert(1); - /// assert!(!v.is_empty()); - /// ``` - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - /// Clears the set, returning all elements in an iterator. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// assert!(!set.is_empty()); - /// - /// // print 1, 2, 3 in an arbitrary order - /// for i in set.drain() { - /// println!("{}", i); - /// } - /// - /// assert!(set.is_empty()); - /// ``` - #[inline] - pub fn drain(&mut self) -> Drain<'_, T> { - Drain { - iter: self.map.drain(), - } - } - - /// Clears the set, removing all values. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut v = HashSet::new(); - /// v.insert(1); - /// v.clear(); - /// assert!(v.is_empty()); - /// ``` - pub fn clear(&mut self) - where - T: 'static, - { - self.map.clear() - } - - /// Returns `true` if the set contains a value. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let set: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// assert_eq!(set.contains(&1), true); - /// assert_eq!(set.contains(&4), false); - /// ``` - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn contains(&self, value: &Q) -> bool - where - T: Borrow, - Q: Hash + Eq, - { - self.map.contains_key(value) - } - - /// Returns a reference to the value in the set, if any, that is equal to the given value. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn get(&self, value: &Q) -> Option<&T> - where - T: Borrow, - Q: Hash + Eq, - { - Recover::get(&self.map, value) - } - - /// Returns `true` if `self` has no elements in common with `other`. - /// This is equivalent to checking for an empty intersection. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let mut b = HashSet::new(); - /// - /// assert_eq!(a.is_disjoint(&b), true); - /// b.insert(4); - /// assert_eq!(a.is_disjoint(&b), true); - /// b.insert(1); - /// assert_eq!(a.is_disjoint(&b), false); - /// ``` - pub fn is_disjoint(&self, other: &HashSet) -> bool { - self.iter().all(|v| !other.contains(v)) - } - - /// Returns `true` if the set is a subset of another, - /// i.e. `other` contains at least all the values in `self`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let sup: HashSet<_> = [1, 2, 3].iter().cloned().collect(); - /// let mut set = HashSet::new(); - /// - /// assert_eq!(set.is_subset(&sup), true); - /// set.insert(2); - /// assert_eq!(set.is_subset(&sup), true); - /// set.insert(4); - /// assert_eq!(set.is_subset(&sup), false); - /// ``` - pub fn is_subset(&self, other: &HashSet) -> bool { - self.iter().all(|v| other.contains(v)) - } - - /// Returns `true` if the set is a superset of another, - /// i.e. `self` contains at least all the values in `other`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let sub: HashSet<_> = [1, 2].iter().cloned().collect(); - /// let mut set = HashSet::new(); - /// - /// assert_eq!(set.is_superset(&sub), false); - /// - /// set.insert(0); - /// set.insert(1); - /// assert_eq!(set.is_superset(&sub), false); - /// - /// set.insert(2); - /// assert_eq!(set.is_superset(&sub), true); - /// ``` - #[inline] - pub fn is_superset(&self, other: &HashSet) -> bool { - other.is_subset(self) - } - - /// Adds a value to the set. - /// - /// If the set did not have this value present, `true` is returned. - /// - /// If the set did have this value present, `false` is returned. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set = HashSet::new(); - /// - /// assert_eq!(set.insert(2), true); - /// assert_eq!(set.insert(2), false); - /// assert_eq!(set.len(), 1); - /// ``` - pub fn insert(&mut self, value: T) -> bool { - self.map.insert(value, ()).is_none() - } - - /// Fallible version of `insert`. - #[inline] - pub fn try_insert(&mut self, value: T) -> Result { - Ok(self.map.try_insert(value, ())?.is_none()) - } - - /// Adds a value to the set, replacing the existing value, if any, that is equal to the given - /// one. Returns the replaced value. - pub fn replace(&mut self, value: T) -> Option { - Recover::replace(&mut self.map, value) - } - - /// Removes a value from the set. Returns `true` if the value was - /// present in the set. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let mut set = HashSet::new(); - /// - /// set.insert(2); - /// assert_eq!(set.remove(&2), true); - /// assert_eq!(set.remove(&2), false); - /// ``` - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn remove(&mut self, value: &Q) -> bool - where - T: Borrow, - Q: Hash + Eq, - { - self.map.remove(value).is_some() - } - - /// Removes and returns the value in the set, if any, that is equal to the given one. - /// - /// The value may be any borrowed form of the set's value type, but - /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for - /// the value type. - /// - /// [`Eq`]: ../../std/cmp/trait.Eq.html - /// [`Hash`]: ../../std/hash/trait.Hash.html - pub fn take(&mut self, value: &Q) -> Option - where - T: Borrow, - Q: Hash + Eq, - { - Recover::take(&mut self.map, value) - } - - /// Retains only the elements specified by the predicate. - /// - /// In other words, remove all elements `e` such that `f(&e)` returns `false`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let xs = [1,2,3,4,5,6]; - /// let mut set: HashSet = xs.iter().cloned().collect(); - /// set.retain(|&k| k % 2 == 0); - /// assert_eq!(set.len(), 3); - /// ``` - pub fn retain(&mut self, mut f: F) - where - F: FnMut(&T) -> bool, - { - self.map.retain(|k, _| f(k)); - } -} - -impl PartialEq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - fn eq(&self, other: &HashSet) -> bool { - if self.len() != other.len() { - return false; - } - - self.iter().all(|key| other.contains(key)) - } -} - -impl Eq for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ -} - -impl fmt::Debug for HashSet -where - T: Eq + Hash + fmt::Debug, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_set().entries(self.iter()).finish() - } -} - -impl FromIterator for HashSet -where - T: Eq + Hash, - S: BuildHasher + Default, -{ - fn from_iter>(iter: I) -> HashSet { - let mut set = HashSet::with_hasher(Default::default()); - set.extend(iter); - set - } -} - -impl Extend for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - fn extend>(&mut self, iter: I) { - self.map.extend(iter.into_iter().map(|k| (k, ()))); - } -} - -impl<'a, T, S> Extend<&'a T> for HashSet -where - T: 'a + Eq + Hash + Copy, - S: BuildHasher, -{ - fn extend>(&mut self, iter: I) { - self.extend(iter.into_iter().cloned()); - } -} - -impl Default for HashSet -where - T: Eq + Hash, - S: BuildHasher + Default, -{ - /// Creates an empty `HashSet` with the `Default` value for the hasher. - fn default() -> HashSet { - HashSet { - map: HashMap::default(), - } - } -} - -impl<'a, 'b, T, S> BitOr<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the union of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); - /// - /// let set = &a | &b; - /// - /// let mut i = 0; - /// let expected = [1, 2, 3, 4, 5]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn bitor(self, rhs: &HashSet) -> HashSet { - self.union(rhs).cloned().collect() - } -} - -impl<'a, 'b, T, S> BitAnd<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the intersection of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect(); - /// - /// let set = &a & &b; - /// - /// let mut i = 0; - /// let expected = [2, 3]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn bitand(self, rhs: &HashSet) -> HashSet { - self.intersection(rhs).cloned().collect() - } -} - -impl<'a, 'b, T, S> BitXor<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the symmetric difference of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); - /// - /// let set = &a ^ &b; - /// - /// let mut i = 0; - /// let expected = [1, 2, 4, 5]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn bitxor(self, rhs: &HashSet) -> HashSet { - self.symmetric_difference(rhs).cloned().collect() - } -} - -impl<'a, 'b, T, S> Sub<&'b HashSet> for &'a HashSet -where - T: Eq + Hash + Clone, - S: BuildHasher + Default, -{ - type Output = HashSet; - - /// Returns the difference of `self` and `rhs` as a new `HashSet`. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// - /// let a: HashSet<_> = vec![1, 2, 3].into_iter().collect(); - /// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect(); - /// - /// let set = &a - &b; - /// - /// let mut i = 0; - /// let expected = [1, 2]; - /// for x in &set { - /// assert!(expected.contains(x)); - /// i += 1; - /// } - /// assert_eq!(i, expected.len()); - /// ``` - fn sub(self, rhs: &HashSet) -> HashSet { - self.difference(rhs).cloned().collect() - } -} - -/// An iterator over the items of a `HashSet`. -/// -/// This `struct` is created by the [`iter`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`iter`]: struct.HashSet.html#method.iter -pub struct Iter<'a, K> { - iter: Keys<'a, K, ()>, -} - -/// An owning iterator over the items of a `HashSet`. -/// -/// This `struct` is created by the [`into_iter`] method on [`HashSet`][`HashSet`] -/// (provided by the `IntoIterator` trait). See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`into_iter`]: struct.HashSet.html#method.into_iter -pub struct IntoIter { - iter: hash_map::IntoIter, -} - -/// A draining iterator over the items of a `HashSet`. -/// -/// This `struct` is created by the [`drain`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`drain`]: struct.HashSet.html#method.drain -pub struct Drain<'a, K: 'static> { - iter: hash_map::Drain<'a, K, ()>, -} - -/// A lazy iterator producing elements in the intersection of `HashSet`s. -/// -/// This `struct` is created by the [`intersection`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`intersection`]: struct.HashSet.html#method.intersection -pub struct Intersection<'a, T, S> { - // iterator of the first set - iter: Iter<'a, T>, - // the second set - other: &'a HashSet, -} - -/// A lazy iterator producing elements in the difference of `HashSet`s. -/// -/// This `struct` is created by the [`difference`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`difference`]: struct.HashSet.html#method.difference -pub struct Difference<'a, T, S> { - // iterator of the first set - iter: Iter<'a, T>, - // the second set - other: &'a HashSet, -} - -/// A lazy iterator producing elements in the symmetric difference of `HashSet`s. -/// -/// This `struct` is created by the [`symmetric_difference`] method on -/// [`HashSet`]. See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`symmetric_difference`]: struct.HashSet.html#method.symmetric_difference -pub struct SymmetricDifference<'a, T, S> { - iter: Chain, Difference<'a, T, S>>, -} - -/// A lazy iterator producing elements in the union of `HashSet`s. -/// -/// This `struct` is created by the [`union`] method on [`HashSet`]. -/// See its documentation for more. -/// -/// [`HashSet`]: struct.HashSet.html -/// [`union`]: struct.HashSet.html#method.union -pub struct Union<'a, T, S> { - iter: Chain, Difference<'a, T, S>>, -} - -impl<'a, T, S> IntoIterator for &'a HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - type IntoIter = Iter<'a, T>; - - fn into_iter(self) -> Iter<'a, T> { - self.iter() - } -} - -impl IntoIterator for HashSet -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = T; - type IntoIter = IntoIter; - - /// Creates a consuming iterator, that is, one that moves each value out - /// of the set in arbitrary order. The set cannot be used after calling - /// this. - /// - /// # Examples - /// - /// ``` - /// use std::collections::HashSet; - /// let mut set = HashSet::new(); - /// set.insert("a".to_string()); - /// set.insert("b".to_string()); - /// - /// // Not possible to collect to a Vec with a regular `.iter()`. - /// let v: Vec = set.into_iter().collect(); - /// - /// // Will print in an arbitrary order. - /// for x in &v { - /// println!("{}", x); - /// } - /// ``` - fn into_iter(self) -> IntoIter { - IntoIter { - iter: self.map.into_iter(), - } - } -} - -impl<'a, K> Clone for Iter<'a, K> { - fn clone(&self) -> Iter<'a, K> { - Iter { - iter: self.iter.clone(), - } - } -} -impl<'a, K> Iterator for Iter<'a, K> { - type Item = &'a K; - - fn next(&mut self) -> Option<&'a K> { - self.iter.next() - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} -impl<'a, K> ExactSizeIterator for Iter<'a, K> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K: fmt::Debug> fmt::Debug for Iter<'a, K> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl Iterator for IntoIter { - type Item = K; - - fn next(&mut self) -> Option { - self.iter.next().map(|(k, _)| k) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} -impl ExactSizeIterator for IntoIter { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl fmt::Debug for IntoIter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let entries_iter = self.iter.inner.iter().map(|(k, _)| k); - f.debug_list().entries(entries_iter).finish() - } -} - -impl<'a, K> Iterator for Drain<'a, K> { - type Item = K; - - fn next(&mut self) -> Option { - self.iter.next().map(|(k, _)| k) - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} -impl<'a, K> ExactSizeIterator for Drain<'a, K> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K: fmt::Debug> fmt::Debug for Drain<'a, K> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let entries_iter = self.iter.inner.iter().map(|(k, _)| k); - f.debug_list().entries(entries_iter).finish() - } -} - -impl<'a, T, S> Clone for Intersection<'a, T, S> { - fn clone(&self) -> Intersection<'a, T, S> { - Intersection { - iter: self.iter.clone(), - ..*self - } - } -} - -impl<'a, T, S> Iterator for Intersection<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - loop { - let elt = self.iter.next()?; - if self.other.contains(elt) { - return Some(elt); - } - } - } - - fn size_hint(&self) -> (usize, Option) { - let (_, upper) = self.iter.size_hint(); - (0, upper) - } -} - -impl<'a, T, S> fmt::Debug for Intersection<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Clone for Difference<'a, T, S> { - fn clone(&self) -> Difference<'a, T, S> { - Difference { - iter: self.iter.clone(), - ..*self - } - } -} - -impl<'a, T, S> Iterator for Difference<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - loop { - let elt = self.iter.next()?; - if !self.other.contains(elt) { - return Some(elt); - } - } - } - - fn size_hint(&self) -> (usize, Option) { - let (_, upper) = self.iter.size_hint(); - (0, upper) - } -} - -impl<'a, T, S> fmt::Debug for Difference<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Clone for SymmetricDifference<'a, T, S> { - fn clone(&self) -> SymmetricDifference<'a, T, S> { - SymmetricDifference { - iter: self.iter.clone(), - } - } -} - -impl<'a, T, S> Iterator for SymmetricDifference<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - self.iter.next() - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, T, S> fmt::Debug for SymmetricDifference<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Clone for Union<'a, T, S> { - fn clone(&self) -> Union<'a, T, S> { - Union { - iter: self.iter.clone(), - } - } -} - -impl<'a, T, S> fmt::Debug for Union<'a, T, S> -where - T: fmt::Debug + Eq + Hash, - S: BuildHasher, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_list().entries(self.clone()).finish() - } -} - -impl<'a, T, S> Iterator for Union<'a, T, S> -where - T: Eq + Hash, - S: BuildHasher, -{ - type Item = &'a T; - - fn next(&mut self) -> Option<&'a T> { - self.iter.next() - } - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -#[allow(dead_code)] -fn assert_covariance() { - fn set<'new>(v: HashSet<&'static str>) -> HashSet<&'new str> { - v - } - fn iter<'a, 'new>(v: Iter<'a, &'static str>) -> Iter<'a, &'new str> { - v - } - fn into_iter<'new>(v: IntoIter<&'static str>) -> IntoIter<&'new str> { - v - } - fn difference<'a, 'new>( - v: Difference<'a, &'static str, RandomState>, - ) -> Difference<'a, &'new str, RandomState> { - v - } - fn symmetric_difference<'a, 'new>( - v: SymmetricDifference<'a, &'static str, RandomState>, - ) -> SymmetricDifference<'a, &'new str, RandomState> { - v - } - fn intersection<'a, 'new>( - v: Intersection<'a, &'static str, RandomState>, - ) -> Intersection<'a, &'new str, RandomState> { - v - } - fn union<'a, 'new>( - v: Union<'a, &'static str, RandomState>, - ) -> Union<'a, &'new str, RandomState> { - v - } - fn drain<'new>(d: Drain<'static, &'static str>) -> Drain<'new, &'new str> { - d - } -} - -#[cfg(test)] -mod test_set { - use super::hash_map::RandomState; - use super::HashSet; - - #[test] - fn test_zero_capacities() { - type HS = HashSet; - - let s = HS::new(); - assert_eq!(s.capacity(), 0); - - let s = HS::default(); - assert_eq!(s.capacity(), 0); - - let s = HS::with_hasher(RandomState::new()); - assert_eq!(s.capacity(), 0); - - let s = HS::with_capacity(0); - assert_eq!(s.capacity(), 0); - - let s = HS::with_capacity_and_hasher(0, RandomState::new()); - assert_eq!(s.capacity(), 0); - - let mut s = HS::new(); - s.insert(1); - s.insert(2); - s.remove(&1); - s.remove(&2); - s.shrink_to_fit(); - assert_eq!(s.capacity(), 0); - - let mut s = HS::new(); - s.reserve(0); - assert_eq!(s.capacity(), 0); - } - - #[test] - fn test_disjoint() { - let mut xs = HashSet::new(); - let mut ys = HashSet::new(); - assert!(xs.is_disjoint(&ys)); - assert!(ys.is_disjoint(&xs)); - assert!(xs.insert(5)); - assert!(ys.insert(11)); - assert!(xs.is_disjoint(&ys)); - assert!(ys.is_disjoint(&xs)); - assert!(xs.insert(7)); - assert!(xs.insert(19)); - assert!(xs.insert(4)); - assert!(ys.insert(2)); - assert!(ys.insert(-11)); - assert!(xs.is_disjoint(&ys)); - assert!(ys.is_disjoint(&xs)); - assert!(ys.insert(7)); - assert!(!xs.is_disjoint(&ys)); - assert!(!ys.is_disjoint(&xs)); - } - - #[test] - fn test_subset_and_superset() { - let mut a = HashSet::new(); - assert!(a.insert(0)); - assert!(a.insert(5)); - assert!(a.insert(11)); - assert!(a.insert(7)); - - let mut b = HashSet::new(); - assert!(b.insert(0)); - assert!(b.insert(7)); - assert!(b.insert(19)); - assert!(b.insert(250)); - assert!(b.insert(11)); - assert!(b.insert(200)); - - assert!(!a.is_subset(&b)); - assert!(!a.is_superset(&b)); - assert!(!b.is_subset(&a)); - assert!(!b.is_superset(&a)); - - assert!(b.insert(5)); - - assert!(a.is_subset(&b)); - assert!(!a.is_superset(&b)); - assert!(!b.is_subset(&a)); - assert!(b.is_superset(&a)); - } - - #[test] - fn test_iterate() { - let mut a = HashSet::new(); - for i in 0..32 { - assert!(a.insert(i)); - } - let mut observed: u32 = 0; - for k in &a { - observed |= 1 << *k; - } - assert_eq!(observed, 0xFFFF_FFFF); - } - - #[test] - fn test_intersection() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(11)); - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(77)); - assert!(a.insert(103)); - assert!(a.insert(5)); - assert!(a.insert(-5)); - - assert!(b.insert(2)); - assert!(b.insert(11)); - assert!(b.insert(77)); - assert!(b.insert(-9)); - assert!(b.insert(-42)); - assert!(b.insert(5)); - assert!(b.insert(3)); - - let mut i = 0; - let expected = [3, 5, 11, 77]; - for x in a.intersection(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_difference() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(5)); - assert!(a.insert(9)); - assert!(a.insert(11)); - - assert!(b.insert(3)); - assert!(b.insert(9)); - - let mut i = 0; - let expected = [1, 5, 11]; - for x in a.difference(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_symmetric_difference() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(5)); - assert!(a.insert(9)); - assert!(a.insert(11)); - - assert!(b.insert(-2)); - assert!(b.insert(3)); - assert!(b.insert(9)); - assert!(b.insert(14)); - assert!(b.insert(22)); - - let mut i = 0; - let expected = [-2, 1, 5, 11, 14, 22]; - for x in a.symmetric_difference(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_union() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - assert!(a.insert(1)); - assert!(a.insert(3)); - assert!(a.insert(5)); - assert!(a.insert(9)); - assert!(a.insert(11)); - assert!(a.insert(16)); - assert!(a.insert(19)); - assert!(a.insert(24)); - - assert!(b.insert(-2)); - assert!(b.insert(1)); - assert!(b.insert(5)); - assert!(b.insert(9)); - assert!(b.insert(13)); - assert!(b.insert(19)); - - let mut i = 0; - let expected = [-2, 1, 3, 5, 9, 11, 13, 16, 19, 24]; - for x in a.union(&b) { - assert!(expected.contains(x)); - i += 1 - } - assert_eq!(i, expected.len()); - } - - #[test] - fn test_from_iter() { - let xs = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - - let set: HashSet<_> = xs.iter().cloned().collect(); - - for x in &xs { - assert!(set.contains(x)); - } - } - - #[test] - fn test_move_iter() { - let hs = { - let mut hs = HashSet::new(); - - hs.insert('a'); - hs.insert('b'); - - hs - }; - - let v = hs.into_iter().collect::>(); - assert!(v == ['a', 'b'] || v == ['b', 'a']); - } - - #[test] - fn test_eq() { - // These constants once happened to expose a bug in insert(). - // I'm keeping them around to prevent a regression. - let mut s1 = HashSet::new(); - - s1.insert(1); - s1.insert(2); - s1.insert(3); - - let mut s2 = HashSet::new(); - - s2.insert(1); - s2.insert(2); - - assert_ne!(s1, s2); - - s2.insert(3); - - assert_eq!(s1, s2); - } - - #[test] - fn test_show() { - let mut set = HashSet::new(); - let empty = HashSet::::new(); - - set.insert(1); - set.insert(2); - - let set_str = format!("{:?}", set); - - assert!(set_str == "{1, 2}" || set_str == "{2, 1}"); - assert_eq!(format!("{:?}", empty), "{}"); - } - - #[test] - fn test_trivial_drain() { - let mut s = HashSet::::new(); - for _ in s.drain() {} - assert!(s.is_empty()); - drop(s); - - let mut s = HashSet::::new(); - drop(s.drain()); - assert!(s.is_empty()); - } - - #[test] - fn test_drain() { - let mut s: HashSet<_> = (1..100).collect(); - - // try this a bunch of times to make sure we don't screw up internal state. - for _ in 0..20 { - assert_eq!(s.len(), 99); - - { - let mut last_i = 0; - let mut d = s.drain(); - for (i, x) in d.by_ref().take(50).enumerate() { - last_i = i; - assert_ne!(x, 0); - } - assert_eq!(last_i, 49); - } - - for _ in &s { - panic!("s should be empty!"); - } - - // reset to try again. - s.extend(1..100); - } - } - - #[test] - fn test_replace() { - use std::hash; - - #[derive(Debug)] - struct Foo(&'static str, i32); - - impl PartialEq for Foo { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - - impl Eq for Foo {} - - impl hash::Hash for Foo { - fn hash(&self, h: &mut H) { - self.0.hash(h); - } - } - - let mut s = HashSet::new(); - assert_eq!(s.replace(Foo("a", 1)), None); - assert_eq!(s.len(), 1); - assert_eq!(s.replace(Foo("a", 2)), Some(Foo("a", 1))); - assert_eq!(s.len(), 1); - - let mut it = s.iter(); - assert_eq!(it.next(), Some(&Foo("a", 2))); - assert_eq!(it.next(), None); - } - - #[test] - fn test_extend_ref() { - let mut a = HashSet::new(); - a.insert(1); - - a.extend(&[2, 3, 4]); - - assert_eq!(a.len(), 4); - assert!(a.contains(&1)); - assert!(a.contains(&2)); - assert!(a.contains(&3)); - assert!(a.contains(&4)); - - let mut b = HashSet::new(); - b.insert(5); - b.insert(6); - - a.extend(&b); - - assert_eq!(a.len(), 6); - assert!(a.contains(&1)); - assert!(a.contains(&2)); - assert!(a.contains(&3)); - assert!(a.contains(&4)); - assert!(a.contains(&5)); - assert!(a.contains(&6)); - } - - #[test] - fn test_retain() { - let xs = [1, 2, 3, 4, 5, 6]; - let mut set: HashSet = xs.iter().cloned().collect(); - set.retain(|&k| k % 2 == 0); - assert_eq!(set.len(), 3); - assert!(set.contains(&2)); - assert!(set.contains(&4)); - assert!(set.contains(&6)); - } -} diff --git a/components/hashglobe/src/lib.rs b/components/hashglobe/src/lib.rs deleted file mode 100644 index 865a4dab113..00000000000 --- a/components/hashglobe/src/lib.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -pub mod alloc; -pub mod hash_map; -pub mod hash_set; -mod shim; -mod table; - -pub mod fake; - -use std::{error, fmt}; - -trait Recover { - type Key; - - fn get(&self, key: &Q) -> Option<&Self::Key>; - fn take(&mut self, key: &Q) -> Option; - fn replace(&mut self, key: Self::Key) -> Option; -} - -#[derive(Debug)] -pub struct AllocationInfo { - /// The size we are requesting. - size: usize, - /// The alignment we are requesting. - alignment: usize, -} - -#[derive(Debug)] -pub struct FailedAllocationError { - reason: &'static str, - /// The allocation info we are requesting, if needed. - allocation_info: Option, -} - -impl FailedAllocationError { - #[inline] - pub fn new(reason: &'static str) -> Self { - Self { - reason, - allocation_info: None, - } - } -} - -impl error::Error for FailedAllocationError { - fn description(&self) -> &str { - self.reason - } -} - -impl fmt::Display for FailedAllocationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.allocation_info { - Some(ref info) => write!( - f, - "{}, allocation: (size: {}, alignment: {})", - self.reason, info.size, info.alignment - ), - None => self.reason.fmt(f), - } - } -} diff --git a/components/hashglobe/src/shim.rs b/components/hashglobe/src/shim.rs deleted file mode 100644 index 8b08af871d6..00000000000 --- a/components/hashglobe/src/shim.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::marker::PhantomData; - -// FIXME: remove this and use std::ptr::NonNull when Firefox requires Rust 1.25+ -pub struct NonZeroPtr(&'static T); - -impl NonZeroPtr { - pub unsafe fn new_unchecked(ptr: *mut T) -> Self { - NonZeroPtr(&*ptr) - } - pub fn as_ptr(&self) -> *mut T { - self.0 as *const T as *mut T - } -} - -pub struct Unique { - ptr: NonZeroPtr, - _marker: PhantomData, -} - -impl Unique { - pub unsafe fn new_unchecked(ptr: *mut T) -> Self { - Unique { - ptr: NonZeroPtr::new_unchecked(ptr), - _marker: PhantomData, - } - } - pub fn as_ptr(&self) -> *mut T { - self.ptr.as_ptr() - } -} - -unsafe impl Send for Unique {} - -unsafe impl Sync for Unique {} - -pub struct Shared { - ptr: NonZeroPtr, - _marker: PhantomData, - // force it to be !Send/!Sync - _marker2: PhantomData<*const u8>, -} - -impl Shared { - pub unsafe fn new_unchecked(ptr: *mut T) -> Self { - Shared { - ptr: NonZeroPtr::new_unchecked(ptr), - _marker: PhantomData, - _marker2: PhantomData, - } - } - - #[allow(clippy::mut_from_ref)] - pub unsafe fn as_mut(&self) -> &mut T { - &mut *self.ptr.as_ptr() - } -} - -impl<'a, T> From<&'a mut T> for Shared { - fn from(reference: &'a mut T) -> Self { - unsafe { Shared::new_unchecked(reference) } - } -} diff --git a/components/hashglobe/src/table.rs b/components/hashglobe/src/table.rs deleted file mode 100644 index 06c87e863dc..00000000000 --- a/components/hashglobe/src/table.rs +++ /dev/null @@ -1,1231 +0,0 @@ -// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use crate::alloc::{alloc, dealloc}; -use crate::shim::{Shared, Unique}; -use std::cmp; -use std::hash::{BuildHasher, Hash, Hasher}; -use std::marker; -use std::mem::{self, align_of, size_of}; -use std::ops::{Deref, DerefMut}; -use std::ptr; - -use self::BucketState::*; -use crate::FailedAllocationError; - -/// Integer type used for stored hash values. -/// -/// No more than bit_width(usize) bits are needed to select a bucket. -/// -/// The most significant bit is ours to use for tagging `SafeHash`. -/// -/// (Even if we could have usize::MAX bytes allocated for buckets, -/// each bucket stores at least a `HashUint`, so there can be no more than -/// usize::MAX / size_of(usize) buckets.) -type HashUint = usize; - -const EMPTY_BUCKET: HashUint = 0; -const EMPTY: usize = 1; - -/// Special `Unique` that uses the lower bit of the pointer -/// to expose a boolean tag. -/// Note: when the pointer is initialized to EMPTY `.ptr()` will return -/// null and the tag functions shouldn't be used. -struct TaggedHashUintPtr(Unique); - -impl TaggedHashUintPtr { - #[inline] - unsafe fn new(ptr: *mut HashUint) -> Self { - debug_assert!(ptr as usize & 1 == 0 || ptr as usize == EMPTY as usize); - TaggedHashUintPtr(Unique::new_unchecked(ptr)) - } - - #[inline] - fn set_tag(&mut self, value: bool) { - let mut usize_ptr = self.0.as_ptr() as usize; - unsafe { - if value { - usize_ptr |= 1; - } else { - usize_ptr &= !1; - } - self.0 = Unique::new_unchecked(usize_ptr as *mut HashUint) - } - } - - #[inline] - fn tag(&self) -> bool { - (self.0.as_ptr() as usize) & 1 == 1 - } - - #[inline] - fn ptr(&self) -> *mut HashUint { - (self.0.as_ptr() as usize & !1) as *mut HashUint - } -} - -/// The raw hashtable, providing safe-ish access to the unzipped and highly -/// optimized arrays of hashes, and key-value pairs. -/// -/// This design is a lot faster than the naive -/// `Vec>`, because we don't pay for the overhead of an -/// option on every element, and we get a generally more cache-aware design. -/// -/// Essential invariants of this structure: -/// -/// - if t.hashes[i] == EMPTY_BUCKET, then `Bucket::at_index(&t, i).raw` -/// points to 'undefined' contents. Don't read from it. This invariant is -/// enforced outside this module with the `EmptyBucket`, `FullBucket`, -/// and `SafeHash` types. -/// -/// - An `EmptyBucket` is only constructed at an index with -/// a hash of EMPTY_BUCKET. -/// -/// - A `FullBucket` is only constructed at an index with a -/// non-EMPTY_BUCKET hash. -/// -/// - A `SafeHash` is only constructed for non-`EMPTY_BUCKET` hash. We get -/// around hashes of zero by changing them to 0x8000_0000_0000_0000, -/// which will likely map to the same bucket, while not being confused -/// with "empty". -/// -/// - Both "arrays represented by pointers" are the same length: -/// `capacity`. This is set at creation and never changes. The arrays -/// are unzipped and are more cache aware (scanning through 8 hashes -/// brings in at most 2 cache lines, since they're all right beside each -/// other). This layout may waste space in padding such as in a map from -/// u64 to u8, but is a more cache conscious layout as the key-value pairs -/// are only very shortly probed and the desired value will be in the same -/// or next cache line. -/// -/// You can kind of think of this module/data structure as a safe wrapper -/// around just the "table" part of the hashtable. It enforces some -/// invariants at the type level and employs some performance trickery, -/// but in general is just a tricked out `Vec>`. -/// -/// The hashtable also exposes a special boolean tag. The tag defaults to false -/// when the RawTable is created and is accessible with the `tag` and `set_tag` -/// functions. -pub struct RawTable { - capacity_mask: usize, - size: usize, - hashes: TaggedHashUintPtr, - - // Because K/V do not appear directly in any of the types in the struct, - // inform rustc that in fact instances of K and V are reachable from here. - marker: marker::PhantomData<(K, V)>, -} - -unsafe impl Send for RawTable {} -unsafe impl Sync for RawTable {} - -// An unsafe view of a RawTable bucket -// Valid indexes are within [0..table_capacity) -pub struct RawBucket { - hash_start: *mut HashUint, - // We use *const to ensure covariance with respect to K and V - pair_start: *const (K, V), - idx: usize, - _marker: marker::PhantomData<(K, V)>, -} - -impl Copy for RawBucket {} -impl Clone for RawBucket { - fn clone(&self) -> RawBucket { - *self - } -} - -pub struct Bucket { - raw: RawBucket, - table: M, -} - -impl Copy for Bucket {} -impl Clone for Bucket { - fn clone(&self) -> Bucket { - *self - } -} - -pub struct EmptyBucket { - raw: RawBucket, - table: M, -} - -pub struct FullBucket { - raw: RawBucket, - table: M, -} - -pub type FullBucketMut<'table, K, V> = FullBucket>; - -pub enum BucketState { - Empty(EmptyBucket), - Full(FullBucket), -} - -// A GapThenFull encapsulates the state of two consecutive buckets at once. -// The first bucket, called the gap, is known to be empty. -// The second bucket is full. -pub struct GapThenFull { - gap: EmptyBucket, - full: FullBucket, -} - -/// A hash that is not zero, since we use a hash of zero to represent empty -/// buckets. -#[derive(PartialEq, Copy, Clone)] -pub struct SafeHash { - hash: HashUint, -} - -impl SafeHash { - /// Peek at the hash value, which is guaranteed to be non-zero. - #[inline(always)] - pub fn inspect(&self) -> HashUint { - self.hash - } - - #[inline(always)] - pub fn new(hash: u64) -> Self { - // We need to avoid 0 in order to prevent collisions with - // EMPTY_HASH. We can maintain our precious uniform distribution - // of initial indexes by unconditionally setting the MSB, - // effectively reducing the hashes by one bit. - // - // Truncate hash to fit in `HashUint`. - let hash_bits = HashUint::BITS; - SafeHash { - hash: (1 << (hash_bits - 1)) | (hash as HashUint), - } - } -} - -/// We need to remove hashes of 0. That's reserved for empty buckets. -/// This function wraps up `hash_keyed` to be the only way outside this -/// module to generate a SafeHash. -pub fn make_hash(hash_state: &S, t: &T) -> SafeHash -where - T: Hash, - S: BuildHasher, -{ - let mut state = hash_state.build_hasher(); - t.hash(&mut state); - SafeHash::new(state.finish()) -} - -// `replace` casts a `*HashUint` to a `*SafeHash`. Since we statically -// ensure that a `FullBucket` points to an index with a non-zero hash, -// and a `SafeHash` is just a `HashUint` with a different name, this is -// safe. -// -// This test ensures that a `SafeHash` really IS the same size as a -// `HashUint`. If you need to change the size of `SafeHash` (and -// consequently made this test fail), `replace` needs to be -// modified to no longer assume this. -#[test] -fn can_alias_safehash_as_hash() { - assert_eq!(size_of::(), size_of::()) -} - -// RawBucket methods are unsafe as it's possible to -// make a RawBucket point to invalid memory using safe code. -impl RawBucket { - unsafe fn hash(&self) -> *mut HashUint { - self.hash_start.offset(self.idx as isize) - } - unsafe fn pair(&self) -> *mut (K, V) { - self.pair_start.add(self.idx) as *mut (K, V) - } - unsafe fn hash_pair(&self) -> (*mut HashUint, *mut (K, V)) { - (self.hash(), self.pair()) - } -} - -// Buckets hold references to the table. -impl FullBucket { - /// Borrow a reference to the table. - pub fn table(&self) -> &M { - &self.table - } - /// Borrow a mutable reference to the table. - pub fn table_mut(&mut self) -> &mut M { - &mut self.table - } - /// Move out the reference to the table. - pub fn into_table(self) -> M { - self.table - } - /// Get the raw index. - pub fn index(&self) -> usize { - self.raw.idx - } - /// Get the raw bucket. - pub fn raw(&self) -> RawBucket { - self.raw - } -} - -impl EmptyBucket { - /// Borrow a reference to the table. - pub fn table(&self) -> &M { - &self.table - } - /// Borrow a mutable reference to the table. - pub fn table_mut(&mut self) -> &mut M { - &mut self.table - } -} - -impl Bucket { - /// Get the raw index. - pub fn index(&self) -> usize { - self.raw.idx - } - /// get the table. - pub fn into_table(self) -> M { - self.table - } -} - -impl Deref for FullBucket -where - M: Deref>, -{ - type Target = RawTable; - fn deref(&self) -> &RawTable { - &self.table - } -} - -/// `Put` is implemented for types which provide access to a table and cannot be invalidated -/// by filling a bucket. A similar implementation for `Take` is possible. -pub trait Put { - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable; -} - -impl<'t, K, V> Put for &'t mut RawTable { - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable { - *self - } -} - -impl Put for Bucket -where - M: Put, -{ - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable { - self.table.borrow_table_mut() - } -} - -impl Put for FullBucket -where - M: Put, -{ - unsafe fn borrow_table_mut(&mut self) -> &mut RawTable { - self.table.borrow_table_mut() - } -} - -impl>> Bucket { - pub fn new(table: M, hash: SafeHash) -> Bucket { - Bucket::at_index(table, hash.inspect() as usize) - } - - pub fn new_from(r: RawBucket, t: M) -> Bucket { - Bucket { raw: r, table: t } - } - - pub fn at_index(table: M, ib_index: usize) -> Bucket { - // if capacity is 0, then the RawBucket will be populated with bogus pointers. - // This is an uncommon case though, so avoid it in release builds. - debug_assert!( - table.capacity() > 0, - "Table should have capacity at this point" - ); - let ib_index = ib_index & table.capacity_mask; - Bucket { - raw: table.raw_bucket_at(ib_index), - table, - } - } - - pub fn first(table: M) -> Bucket { - Bucket { - raw: table.raw_bucket_at(0), - table, - } - } - - // "So a few of the first shall be last: for many be called, - // but few chosen." - // - // We'll most likely encounter a few buckets at the beginning that - // have their initial buckets near the end of the table. They were - // placed at the beginning as the probe wrapped around the table - // during insertion. We must skip forward to a bucket that won't - // get reinserted too early and won't unfairly steal others spot. - // This eliminates the need for robin hood. - pub fn head_bucket(table: M) -> Bucket { - let mut bucket = Bucket::first(table); - - loop { - bucket = match bucket.peek() { - Full(full) => { - if full.displacement() == 0 { - // This bucket occupies its ideal spot. - // It indicates the start of another "cluster". - bucket = full.into_bucket(); - break; - } - // Leaving this bucket in the last cluster for later. - full.into_bucket() - }, - Empty(b) => { - // Encountered a hole between clusters. - b.into_bucket() - }, - }; - bucket.next(); - } - bucket - } - - /// Reads a bucket at a given index, returning an enum indicating whether - /// it's initialized or not. You need to match on this enum to get - /// the appropriate types to call most of the other functions in - /// this module. - pub fn peek(self) -> BucketState { - match unsafe { *self.raw.hash() } { - EMPTY_BUCKET => Empty(EmptyBucket { - raw: self.raw, - table: self.table, - }), - _ => Full(FullBucket { - raw: self.raw, - table: self.table, - }), - } - } - - /// Modifies the bucket in place to make it point to the next slot. - pub fn next(&mut self) { - self.raw.idx = self.raw.idx.wrapping_add(1) & self.table.capacity_mask; - } - - /// Modifies the bucket in place to make it point to the previous slot. - pub fn prev(&mut self) { - self.raw.idx = self.raw.idx.wrapping_sub(1) & self.table.capacity_mask; - } -} - -impl>> EmptyBucket { - #[inline] - pub fn next(self) -> Bucket { - let mut bucket = self.into_bucket(); - bucket.next(); - bucket - } - - #[inline] - pub fn into_bucket(self) -> Bucket { - Bucket { - raw: self.raw, - table: self.table, - } - } - - pub fn gap_peek(self) -> Result, Bucket> { - let gap = EmptyBucket { - raw: self.raw, - table: (), - }; - - match self.next().peek() { - Full(bucket) => Ok(GapThenFull { gap, full: bucket }), - Empty(e) => Err(e.into_bucket()), - } - } -} - -impl EmptyBucket -where - M: Put, -{ - /// Puts given key and value pair, along with the key's hash, - /// into this bucket in the hashtable. Note how `self` is 'moved' into - /// this function, because this slot will no longer be empty when - /// we return! A `FullBucket` is returned for later use, pointing to - /// the newly-filled slot in the hashtable. - /// - /// Use `make_hash` to construct a `SafeHash` to pass to this function. - pub fn put(mut self, hash: SafeHash, key: K, value: V) -> FullBucket { - unsafe { - *self.raw.hash() = hash.inspect(); - ptr::write(self.raw.pair(), (key, value)); - - self.table.borrow_table_mut().size += 1; - } - - FullBucket { - raw: self.raw, - table: self.table, - } - } -} - -impl>> FullBucket { - #[inline] - pub fn next(self) -> Bucket { - let mut bucket = self.into_bucket(); - bucket.next(); - bucket - } - - #[inline] - pub fn into_bucket(self) -> Bucket { - Bucket { - raw: self.raw, - table: self.table, - } - } - - /// Duplicates the current position. This can be useful for operations - /// on two or more buckets. - pub fn stash(self) -> FullBucket { - FullBucket { - raw: self.raw, - table: self, - } - } - - /// Get the distance between this bucket and the 'ideal' location - /// as determined by the key's hash stored in it. - /// - /// In the cited blog posts above, this is called the "distance to - /// initial bucket", or DIB. Also known as "probe count". - pub fn displacement(&self) -> usize { - // Calculates the distance one has to travel when going from - // `hash mod capacity` onwards to `idx mod capacity`, wrapping around - // if the destination is not reached before the end of the table. - (self.raw.idx.wrapping_sub(self.hash().inspect() as usize)) & self.table.capacity_mask - } - - #[inline] - pub fn hash(&self) -> SafeHash { - unsafe { - SafeHash { - hash: *self.raw.hash(), - } - } - } - - /// Gets references to the key and value at a given index. - pub fn read(&self) -> (&K, &V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&(*pair_ptr).0, &(*pair_ptr).1) - } - } -} - -// We take a mutable reference to the table instead of accepting anything that -// implements `DerefMut` to prevent fn `take` from being called on `stash`ed -// buckets. -impl<'t, K, V> FullBucket> { - /// Removes this bucket's key and value from the hashtable. - /// - /// This works similarly to `put`, building an `EmptyBucket` out of the - /// taken bucket. - pub fn take(self) -> (EmptyBucket>, K, V) { - self.table.size -= 1; - - unsafe { - *self.raw.hash() = EMPTY_BUCKET; - let (k, v) = ptr::read(self.raw.pair()); - ( - EmptyBucket { - raw: self.raw, - table: self.table, - }, - k, - v, - ) - } - } -} - -// This use of `Put` is misleading and restrictive, but safe and sufficient for our use cases -// where `M` is a full bucket or table reference type with mutable access to the table. -impl FullBucket -where - M: Put, -{ - pub fn replace(&mut self, h: SafeHash, k: K, v: V) -> (SafeHash, K, V) { - unsafe { - let old_hash = ptr::replace(self.raw.hash() as *mut SafeHash, h); - let (old_key, old_val) = ptr::replace(self.raw.pair(), (k, v)); - - (old_hash, old_key, old_val) - } - } -} - -impl FullBucket -where - M: Deref> + DerefMut, -{ - /// Gets mutable references to the key and value at a given index. - pub fn read_mut(&mut self) -> (&mut K, &mut V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&mut (*pair_ptr).0, &mut (*pair_ptr).1) - } - } -} - -impl<'t, K, V, M> FullBucket -where - M: Deref> + 't, -{ - /// Exchange a bucket state for immutable references into the table. - /// Because the underlying reference to the table is also consumed, - /// no further changes to the structure of the table are possible; - /// in exchange for this, the returned references have a longer lifetime - /// than the references returned by `read()`. - pub fn into_refs(self) -> (&'t K, &'t V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&(*pair_ptr).0, &(*pair_ptr).1) - } - } -} - -impl<'t, K, V, M> FullBucket -where - M: Deref> + DerefMut + 't, -{ - /// This works similarly to `into_refs`, exchanging a bucket state - /// for mutable references into the table. - pub fn into_mut_refs(self) -> (&'t mut K, &'t mut V) { - unsafe { - let pair_ptr = self.raw.pair(); - (&mut (*pair_ptr).0, &mut (*pair_ptr).1) - } - } -} - -impl GapThenFull -where - M: Deref>, -{ - #[inline] - pub fn full(&self) -> &FullBucket { - &self.full - } - - pub fn into_table(self) -> M { - self.full.into_table() - } - - pub fn shift(mut self) -> Result, Bucket> { - unsafe { - let (gap_hash, gap_pair) = self.gap.raw.hash_pair(); - let (full_hash, full_pair) = self.full.raw.hash_pair(); - *gap_hash = mem::replace(&mut *full_hash, EMPTY_BUCKET); - ptr::copy_nonoverlapping(full_pair, gap_pair, 1); - } - - let FullBucket { raw: prev_raw, .. } = self.full; - - match self.full.next().peek() { - Full(bucket) => { - self.gap.raw = prev_raw; - - self.full = bucket; - - Ok(self) - }, - Empty(b) => Err(b.into_bucket()), - } - } -} - -/// Rounds up to a multiple of a power of two. Returns the closest multiple -/// of `target_alignment` that is higher or equal to `unrounded`. -/// -/// # Panics -/// -/// Panics if `target_alignment` is not a power of two. -#[inline] -fn round_up_to_next(unrounded: usize, target_alignment: usize) -> usize { - assert!(target_alignment.is_power_of_two()); - (unrounded + target_alignment - 1) & !(target_alignment - 1) -} - -#[test] -fn test_rounding() { - assert_eq!(round_up_to_next(0, 4), 0); - assert_eq!(round_up_to_next(1, 4), 4); - assert_eq!(round_up_to_next(2, 4), 4); - assert_eq!(round_up_to_next(3, 4), 4); - assert_eq!(round_up_to_next(4, 4), 4); - assert_eq!(round_up_to_next(5, 4), 8); -} - -// Returns a tuple of (pairs_offset, end_of_pairs_offset), -// from the start of a mallocated array. -#[inline] -fn calculate_offsets( - hashes_size: usize, - pairs_size: usize, - pairs_align: usize, -) -> (usize, usize, bool) { - let pairs_offset = round_up_to_next(hashes_size, pairs_align); - let (end_of_pairs, oflo) = pairs_offset.overflowing_add(pairs_size); - - (pairs_offset, end_of_pairs, oflo) -} - -// Returns a tuple of (minimum required malloc alignment, hash_offset, -// array_size), from the start of a mallocated array. -fn calculate_allocation( - hash_size: usize, - hash_align: usize, - pairs_size: usize, - pairs_align: usize, -) -> (usize, usize, usize, bool) { - let hash_offset = 0; - let (_, end_of_pairs, oflo) = calculate_offsets(hash_size, pairs_size, pairs_align); - - let align = cmp::max(hash_align, pairs_align); - - (align, hash_offset, end_of_pairs, oflo) -} - -#[test] -fn test_offset_calculation() { - assert_eq!(calculate_allocation(128, 8, 16, 8), (8, 0, 144, false)); - assert_eq!(calculate_allocation(3, 1, 2, 1), (1, 0, 5, false)); - assert_eq!(calculate_allocation(6, 2, 12, 4), (4, 0, 20, false)); - assert_eq!(calculate_offsets(128, 15, 4), (128, 143, false)); - assert_eq!(calculate_offsets(3, 2, 4), (4, 6, false)); - assert_eq!(calculate_offsets(6, 12, 4), (8, 20, false)); -} - -impl RawTable { - unsafe fn new_uninitialized(capacity: usize) -> RawTable { - if let Ok(table) = Self::try_new_uninitialized(capacity) { - table - } else { - libc::abort(); - } - } - - /// Does not initialize the buckets. The caller should ensure they, - /// at the very least, set every hash to EMPTY_BUCKET. - unsafe fn try_new_uninitialized( - capacity: usize, - ) -> Result, FailedAllocationError> { - if capacity == 0 { - return Ok(RawTable { - size: 0, - capacity_mask: capacity.wrapping_sub(1), - hashes: TaggedHashUintPtr::new(EMPTY as *mut HashUint), - marker: marker::PhantomData, - }); - } - - // No need for `checked_mul` before a more restrictive check performed - // later in this method. - let hashes_size = capacity.wrapping_mul(size_of::()); - let pairs_size = capacity.wrapping_mul(size_of::<(K, V)>()); - - // Allocating hashmaps is a little tricky. We need to allocate two - // arrays, but since we know their sizes and alignments up front, - // we just allocate a single array, and then have the subarrays - // point into it. - // - // This is great in theory, but in practice getting the alignment - // right is a little subtle. Therefore, calculating offsets has been - // factored out into a different function. - let (alignment, hash_offset, size, oflo) = calculate_allocation( - hashes_size, - align_of::(), - pairs_size, - align_of::<(K, V)>(), - ); - - if oflo { - return Err(FailedAllocationError::new( - "capacity overflow when allocating RawTable", - )); - } - - // One check for overflow that covers calculation and rounding of size. - let size_of_bucket = size_of::() - .checked_add(size_of::<(K, V)>()) - .unwrap(); - - let cap_bytes = capacity.checked_mul(size_of_bucket); - - if let Some(cap_bytes) = cap_bytes { - if size < cap_bytes { - return Err(FailedAllocationError::new( - "capacity overflow when allocating RawTable", - )); - } - } else { - return Err(FailedAllocationError::new( - "capacity overflow when allocating RawTable", - )); - } - - // FORK NOTE: Uses alloc shim instead of Heap.alloc - let buffer = alloc(size, alignment); - - if buffer.is_null() { - use crate::AllocationInfo; - return Err(FailedAllocationError { - reason: "out of memory when allocating RawTable", - allocation_info: Some(AllocationInfo { size, alignment }), - }); - } - - let hashes = buffer.offset(hash_offset as isize) as *mut HashUint; - - Ok(RawTable { - capacity_mask: capacity.wrapping_sub(1), - size: 0, - hashes: TaggedHashUintPtr::new(hashes), - marker: marker::PhantomData, - }) - } - - fn raw_bucket_at(&self, index: usize) -> RawBucket { - let hashes_size = self.capacity() * size_of::(); - let pairs_size = self.capacity() * size_of::<(K, V)>(); - - let (pairs_offset, _, oflo) = - calculate_offsets(hashes_size, pairs_size, align_of::<(K, V)>()); - debug_assert!(!oflo, "capacity overflow"); - - let buffer = self.hashes.ptr() as *mut u8; - unsafe { - RawBucket { - hash_start: buffer as *mut HashUint, - pair_start: buffer.add(pairs_offset) as *const (K, V), - idx: index, - _marker: marker::PhantomData, - } - } - } - - /// Creates a new raw table from a given capacity. All buckets are - /// initially empty. - pub fn new(capacity: usize) -> Result, FailedAllocationError> { - unsafe { - let ret = RawTable::try_new_uninitialized(capacity)?; - if capacity > 0 { - ptr::write_bytes(ret.hashes.ptr(), 0, capacity); - } - Ok(ret) - } - } - - /// The hashtable's capacity, similar to a vector's. - pub fn capacity(&self) -> usize { - self.capacity_mask.wrapping_add(1) - } - - /// The number of elements ever `put` in the hashtable, minus the number - /// of elements ever `take`n. - pub fn size(&self) -> usize { - self.size - } - - fn raw_buckets(&self) -> RawBuckets<'_, K, V> { - RawBuckets { - raw: self.raw_bucket_at(0), - elems_left: self.size, - marker: marker::PhantomData, - } - } - - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.raw_buckets(), - } - } - - pub fn iter_mut(&mut self) -> IterMut<'_, K, V> { - IterMut { - iter: self.raw_buckets(), - _marker: marker::PhantomData, - } - } - - pub fn into_iter(self) -> IntoIter { - let RawBuckets { - raw, elems_left, .. - } = self.raw_buckets(); - // Replace the marker regardless of lifetime bounds on parameters. - IntoIter { - iter: RawBuckets { - raw, - elems_left, - marker: marker::PhantomData, - }, - table: self, - } - } - - pub fn drain(&mut self) -> Drain<'_, K, V> { - let RawBuckets { - raw, elems_left, .. - } = self.raw_buckets(); - // Replace the marker regardless of lifetime bounds on parameters. - Drain { - iter: RawBuckets { - raw, - elems_left, - marker: marker::PhantomData, - }, - table: Shared::from(self), - marker: marker::PhantomData, - } - } - - /// Drops buckets in reverse order. It leaves the table in an inconsistent - /// state and should only be used for dropping the table's remaining - /// entries. It's used in the implementation of Drop. - unsafe fn rev_drop_buckets(&mut self) { - // initialize the raw bucket past the end of the table - let mut raw = self.raw_bucket_at(self.capacity()); - let mut elems_left = self.size; - - while elems_left != 0 { - raw.idx -= 1; - - if *raw.hash() != EMPTY_BUCKET { - elems_left -= 1; - ptr::drop_in_place(raw.pair()); - } - } - } - - /// Set the table tag - pub fn set_tag(&mut self, value: bool) { - self.hashes.set_tag(value) - } - - /// Get the table tag - pub fn tag(&self) -> bool { - self.hashes.tag() - } -} - -/// A raw iterator. The basis for some other iterators in this module. Although -/// this interface is safe, it's not used outside this module. -struct RawBuckets<'a, K, V> { - raw: RawBucket, - elems_left: usize, - - // Strictly speaking, this should be &'a (K,V), but that would - // require that K:'a, and we often use RawBuckets<'static...> for - // move iterations, so that messes up a lot of other things. So - // just use `&'a (K,V)` as this is not a publicly exposed type - // anyway. - marker: marker::PhantomData<&'a ()>, -} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for RawBuckets<'a, K, V> { - fn clone(&self) -> RawBuckets<'a, K, V> { - RawBuckets { - raw: self.raw, - elems_left: self.elems_left, - marker: marker::PhantomData, - } - } -} - -impl<'a, K, V> Iterator for RawBuckets<'a, K, V> { - type Item = RawBucket; - - fn next(&mut self) -> Option> { - if self.elems_left == 0 { - return None; - } - - loop { - unsafe { - let item = self.raw; - self.raw.idx += 1; - if *item.hash() != EMPTY_BUCKET { - self.elems_left -= 1; - return Some(item); - } - } - } - } - - fn size_hint(&self) -> (usize, Option) { - (self.elems_left, Some(self.elems_left)) - } -} - -impl<'a, K, V> ExactSizeIterator for RawBuckets<'a, K, V> { - fn len(&self) -> usize { - self.elems_left - } -} - -/// Iterator over shared references to entries in a table. -pub struct Iter<'a, K: 'a, V: 'a> { - iter: RawBuckets<'a, K, V>, -} - -unsafe impl<'a, K: Sync, V: Sync> Sync for Iter<'a, K, V> {} -unsafe impl<'a, K: Sync, V: Sync> Send for Iter<'a, K, V> {} - -// FIXME(#19839) Remove in favor of `#[derive(Clone)]` -impl<'a, K, V> Clone for Iter<'a, K, V> { - fn clone(&self) -> Iter<'a, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -/// Iterator over mutable references to entries in a table. -pub struct IterMut<'a, K: 'a, V> { - iter: RawBuckets<'a, K, V>, - // To ensure invariance with respect to V - _marker: marker::PhantomData<&'a mut V>, -} - -unsafe impl<'a, K: Sync, V: Sync> Sync for IterMut<'a, K, V> {} -// Both K: Sync and K: Send are correct for IterMut's Send impl, -// but Send is the more useful bound -unsafe impl<'a, K: Send, V: Send> Send for IterMut<'a, K, V> {} - -impl<'a, K: 'a, V: 'a> IterMut<'a, K, V> { - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -/// Iterator over the entries in a table, consuming the table. -pub struct IntoIter { - table: RawTable, - iter: RawBuckets<'static, K, V>, -} - -unsafe impl Sync for IntoIter {} -unsafe impl Send for IntoIter {} - -impl IntoIter { - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -/// Iterator over the entries in a table, clearing the table. -pub struct Drain<'a, K: 'static, V: 'static> { - table: Shared>, - iter: RawBuckets<'static, K, V>, - marker: marker::PhantomData<&'a RawTable>, -} - -unsafe impl<'a, K: Sync, V: Sync> Sync for Drain<'a, K, V> {} -unsafe impl<'a, K: Send, V: Send> Send for Drain<'a, K, V> {} - -impl<'a, K, V> Drain<'a, K, V> { - pub fn iter(&self) -> Iter<'_, K, V> { - Iter { - iter: self.iter.clone(), - } - } -} - -impl<'a, K, V> Iterator for Iter<'a, K, V> { - type Item = (&'a K, &'a V); - - fn next(&mut self) -> Option<(&'a K, &'a V)> { - self.iter.next().map(|raw| unsafe { - let pair_ptr = raw.pair(); - (&(*pair_ptr).0, &(*pair_ptr).1) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, K, V> ExactSizeIterator for Iter<'a, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K, V> Iterator for IterMut<'a, K, V> { - type Item = (&'a K, &'a mut V); - - fn next(&mut self) -> Option<(&'a K, &'a mut V)> { - self.iter.next().map(|raw| unsafe { - let pair_ptr = raw.pair(); - (&(*pair_ptr).0, &mut (*pair_ptr).1) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, K, V> ExactSizeIterator for IterMut<'a, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl Iterator for IntoIter { - type Item = (SafeHash, K, V); - - fn next(&mut self) -> Option<(SafeHash, K, V)> { - self.iter.next().map(|raw| { - self.table.size -= 1; - unsafe { - let (k, v) = ptr::read(raw.pair()); - (SafeHash { hash: *raw.hash() }, k, v) - } - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl ExactSizeIterator for IntoIter { - fn len(&self) -> usize { - self.iter().len() - } -} - -impl<'a, K, V> Iterator for Drain<'a, K, V> { - type Item = (SafeHash, K, V); - - #[inline] - fn next(&mut self) -> Option<(SafeHash, K, V)> { - self.iter.next().map(|raw| unsafe { - self.table.as_mut().size -= 1; - let (k, v) = ptr::read(raw.pair()); - ( - SafeHash { - hash: ptr::replace(&mut *raw.hash(), EMPTY_BUCKET), - }, - k, - v, - ) - }) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, K, V> ExactSizeIterator for Drain<'a, K, V> { - fn len(&self) -> usize { - self.iter.len() - } -} - -impl<'a, K: 'static, V: 'static> Drop for Drain<'a, K, V> { - fn drop(&mut self) { - for _ in self {} - } -} - -impl Clone for RawTable { - fn clone(&self) -> RawTable { - unsafe { - let cap = self.capacity(); - let mut new_ht = RawTable::new_uninitialized(cap); - - let mut new_buckets = new_ht.raw_bucket_at(0); - let mut buckets = self.raw_bucket_at(0); - while buckets.idx < cap { - *new_buckets.hash() = *buckets.hash(); - if *new_buckets.hash() != EMPTY_BUCKET { - let pair_ptr = buckets.pair(); - let kv = ((*pair_ptr).0.clone(), (*pair_ptr).1.clone()); - ptr::write(new_buckets.pair(), kv); - } - buckets.idx += 1; - new_buckets.idx += 1; - } - - new_ht.size = self.size(); - - new_ht - } - } -} - -// FORK NOTE: There may be lifetime errors that do not occur on std::HashMap -// since we removed the may_dangle (which allows more things to compile but has stricter guarantees). -// Generally we should be fine as long as no borrowed data is stuck into the map. -impl Drop for RawTable { - fn drop(&mut self) { - if self.capacity() == 0 { - return; - } - - // This is done in reverse because we've likely partially taken - // some elements out with `.into_iter()` from the front. - // Check if the size is 0, so we don't do a useless scan when - // dropping empty tables such as on resize. - // Also avoid double drop of elements that have been already moved out. - unsafe { - // FORK NOTE: Can't needs_drop on stable - // if needs_drop::<(K, V)>() { - // avoid linear runtime for types that don't need drop - self.rev_drop_buckets(); - // } - } - - let hashes_size = self.capacity() * size_of::(); - let pairs_size = self.capacity() * size_of::<(K, V)>(); - let (align, _, _, oflo) = calculate_allocation( - hashes_size, - align_of::(), - pairs_size, - align_of::<(K, V)>(), - ); - - debug_assert!(!oflo, "should be impossible"); - - unsafe { - dealloc(self.hashes.ptr() as *mut u8, align); - // Remember how everything was allocated out of one buffer - // during initialization? We only need one call to free here. - } - } -} diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 86f475609fa..43a82c9ed8d 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -34,7 +34,6 @@ content-security-policy = { workspace = true, optional = true } crossbeam-channel = { workspace = true, optional = true } cssparser = { workspace = true } euclid = { workspace = true } -hashglobe = { path = "../hashglobe" } http = { workspace = true, optional = true } hyper_serde = { workspace = true, optional = true } keyboard-types = { workspace = true, optional = true } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 6822a70702b..fa5a9b1e0ce 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -448,8 +448,6 @@ macro_rules! malloc_size_of_hash_set { } malloc_size_of_hash_set!(std::collections::HashSet); -malloc_size_of_hash_set!(hashglobe::hash_set::HashSet); -malloc_size_of_hash_set!(hashglobe::fake::HashSet); macro_rules! malloc_size_of_hash_map { ($ty:ty) => { @@ -489,8 +487,6 @@ macro_rules! malloc_size_of_hash_map { } malloc_size_of_hash_map!(std::collections::HashMap); -malloc_size_of_hash_map!(hashglobe::hash_map::HashMap); -malloc_size_of_hash_map!(hashglobe::fake::HashMap); impl MallocShallowSizeOf for std::collections::BTreeMap where diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index b4eb6dc55c5..6c49fcf5889 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -17,7 +17,7 @@ path = "lib.rs" doctest = false [features] -gecko = ["style_traits/gecko", "fallible/known_system_malloc", "bindgen", "regex", "toml", +gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "num_cpus", "thin-slice"] servo = ["serde", "style_traits/servo", "servo_atoms", "servo_config", "html5ever", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "servo_url", @@ -38,9 +38,7 @@ cssparser = "0.29" derive_more = "0.99" encoding_rs = { version = "0.8", optional = true } euclid = "0.22" -fallible = { path = "../fallible" } fxhash = "0.2" -hashglobe = { path = "../hashglobe" } html5ever = { version = "0.26", optional = true } indexmap = "1.0" itertools = "0.8" diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index de887396c55..07240879438 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -7,7 +7,6 @@ //! [custom]: https://drafts.csswg.org/css-variables/ use crate::applicable_declarations::CascadePriority; -use crate::hash::map::Entry; use crate::media_queries::Device; use crate::properties::{CSSWideKeyword, CustomDeclaration, CustomDeclarationValue}; use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet, PrecomputedHasher}; @@ -21,6 +20,7 @@ use servo_arc::Arc; use smallvec::SmallVec; use std::borrow::Cow; use std::cmp; +use std::collections::hash_map::Entry; use std::fmt::{self, Write}; use std::hash::BuildHasherDefault; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index b289fefb4bd..d89cac962ee 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -54,7 +54,6 @@ use crate::gecko_bindings::structs::{nsAtom, nsIContent, nsINode_BooleanFlag}; use crate::gecko_bindings::structs::{nsINode as RawGeckoNode, Element as RawGeckoElement}; use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasSimpleFFI}; use crate::global_style_data::GLOBAL_STYLE_DATA; -use crate::hash::FxHashMap; use crate::invalidation::element::restyle_hints::RestyleHint; use crate::media_queries::Device; use crate::properties::animated_properties::{AnimationValue, AnimationValueMap}; @@ -69,6 +68,7 @@ use crate::values::{AtomIdent, AtomString}; use crate::CaseSensitivityExt; use crate::LocalName; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; +use fxhash::FxHashMap; use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator}; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::matching::VisitedHandlingMode; diff --git a/components/style/hash.rs b/components/style/hash.rs deleted file mode 100644 index 197c5c12832..00000000000 --- a/components/style/hash.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* 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/. */ - -//! Reexports of hashglobe types in Gecko mode, and stdlib hashmap shims in Servo mode -//! -//! Can go away when the stdlib gets fallible collections -//! https://github.com/rust-lang/rfcs/pull/2116 - -use fxhash; - -#[cfg(feature = "gecko")] -pub use hashglobe::hash_map::HashMap; -#[cfg(feature = "gecko")] -pub use hashglobe::hash_set::HashSet; - -#[cfg(feature = "servo")] -pub use hashglobe::fake::{HashMap, HashSet}; - -/// Appropriate reexports of hash_map types -pub mod map { - #[cfg(feature = "gecko")] - pub use hashglobe::hash_map::{Entry, Iter}; - #[cfg(feature = "servo")] - pub use std::collections::hash_map::{Entry, Iter}; -} - -/// Hash map that uses the Fx hasher -pub type FxHashMap = HashMap; -/// Hash set that uses the Fx hasher -pub type FxHashSet = HashSet; diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index 42a991e8c15..153c13bc149 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -10,9 +10,8 @@ use crate::selector_map::{ MaybeCaseInsensitiveHashMap, PrecomputedHashMap, SelectorMap, SelectorMapEntry, }; use crate::selector_parser::SelectorImpl; +use crate::AllocErr; use crate::{Atom, LocalName, Namespace}; -use fallible::FallibleVec; -use hashglobe::FailedAllocationError; use selectors::attr::NamespaceConstraint; use selectors::parser::{Combinator, Component}; use selectors::parser::{Selector, SelectorIter}; @@ -244,7 +243,7 @@ impl InvalidationMap { &mut self, selector: &Selector, quirks_mode: QuirksMode, - ) -> Result<(), FailedAllocationError> { + ) -> Result<(), AllocErr> { debug!("InvalidationMap::note_selector({:?})", selector); let mut document_state = DocumentState::empty(); @@ -274,7 +273,8 @@ impl InvalidationMap { state: document_state, dependency: Dependency::for_full_selector_invalidation(selector.clone()), }; - self.document_state_selectors.try_push(dep)?; + self.document_state_selectors.try_reserve(1)?; + self.document_state_selectors.push(dep); } Ok(()) @@ -325,7 +325,7 @@ struct SelectorDependencyCollector<'a> { compound_state: PerCompoundState, /// The allocation error, if we OOM. - alloc_error: &'a mut Option, + alloc_error: &'a mut Option, } impl<'a> SelectorDependencyCollector<'a> { @@ -361,7 +361,7 @@ impl<'a> SelectorDependencyCollector<'a> { self.quirks_mode, ); if let Err(alloc_error) = result { - *self.alloc_error = Some(alloc_error); + *self.alloc_error = Some(alloc_error.into()); return false; } } @@ -378,21 +378,17 @@ impl<'a> SelectorDependencyCollector<'a> { let dependency = self.dependency(); let map = &mut self.map.other_attribute_affecting_selectors; - let entry = match map.try_entry(name) { - Ok(entry) => entry, - Err(err) => { - *self.alloc_error = Some(err); - return false; - }, - }; - - match entry.or_insert_with(SmallVec::new).try_push(dependency) { - Ok(..) => true, - Err(err) => { - *self.alloc_error = Some(err); - return false; - }, + if let Err(err) = map.try_reserve(1) { + *self.alloc_error = Some(err.into()); + return false; } + let vec = map.entry(name).or_default(); + if let Err(err) = vec.try_reserve(1) { + *self.alloc_error = Some(err.into()); + return false; + } + vec.push(dependency); + true } fn dependency(&self) -> Dependency { @@ -481,17 +477,17 @@ impl<'a> SelectorVisitor for SelectorDependencyCollector<'a> { let entry = match map.try_entry(atom.0.clone(), self.quirks_mode) { Ok(entry) => entry, Err(err) => { - *self.alloc_error = Some(err); + *self.alloc_error = Some(err.into()); return false; }, }; - match entry.or_insert_with(SmallVec::new).try_push(dependency) { - Ok(..) => true, - Err(err) => { - *self.alloc_error = Some(err); - return false; - }, + let vec = entry.or_insert_with(SmallVec::new); + if let Err(err) = vec.try_reserve(1) { + *self.alloc_error = Some(err.into()); + return false; } + vec.push(dependency); + true }, Component::NonTSPseudoClass(ref pc) => { self.compound_state.element_state |= pc.state_flag(); diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 3a9017582b2..c18cc3cd242 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -484,16 +484,16 @@ impl StylesheetInvalidationSet { }, Invalidation::LocalName { name, lower_name } => { let insert_lower = name != lower_name; - let entry = match self.local_names.try_entry(name) { - Ok(e) => e, - Err(..) => return false, - }; + if self.local_names.try_reserve(1).is_err() { + return false; + } + let entry = self.local_names.entry(name); *entry.or_insert(InvalidationKind::None) |= kind; if insert_lower { - let entry = match self.local_names.try_entry(lower_name) { - Ok(e) => e, - Err(..) => return false, - }; + if self.local_names.try_reserve(1).is_err() { + return false; + } + let entry = self.local_names.entry(lower_name); *entry.or_insert(InvalidationKind::None) |= kind; } }, diff --git a/components/style/lib.rs b/components/style/lib.rs index b721ea0d952..00c8359b5f4 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -97,7 +97,6 @@ pub mod font_metrics; #[allow(unsafe_code)] pub mod gecko_bindings; pub mod global_style_data; -pub mod hash; pub mod invalidation; #[allow(missing_docs)] // TODO. pub mod logical_geometry; @@ -263,3 +262,27 @@ where *self == One::one() } } + +/// An allocation error. +/// +/// TODO(emilio): Would be nice to have more information here, or for SmallVec +/// to return the standard error type (and then we can just return that). +/// +/// But given we use these mostly to bail out and ignore them, it's not a big +/// deal. +#[derive(Debug)] +pub struct AllocErr; + +impl From for AllocErr { + #[inline] + fn from(_: smallvec::CollectionAllocErr) -> Self { + Self + } +} + +impl From for AllocErr { + #[inline] + fn from(_: std::collections::TryReserveError) -> Self { + Self + } +} diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index f9067c0e9a8..5ffea97ba50 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -19,7 +19,7 @@ use servo_arc::Arc; use smallvec::SmallVec; use std::ptr; use std::mem; -use crate::hash::FxHashMap; +use fxhash::FxHashMap; use super::ComputedValues; use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; use crate::values::animated::effects::AnimatedFilter; diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index dee66cd59b0..dd4f72f831f 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -29,7 +29,7 @@ use crate::context::QuirksMode; use crate::logical_geometry::WritingMode; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use crate::computed_value_flags::*; -use crate::hash::FxHashMap; +use fxhash::FxHashMap; use crate::media_queries::Device; use crate::parser::ParserContext; use crate::selector_parser::PseudoElement; diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index fe1214faf65..e4632ffa711 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -128,7 +128,7 @@ impl RuleTree { return; } - let mut children_count = crate::hash::FxHashMap::default(); + let mut children_count = fxhash::FxHashMap::default(); let mut stack = SmallVec::<[_; 32]>::new(); stack.push(self.root.clone()); diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index ff7e93ae9c0..0aa8cefc406 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -8,18 +8,17 @@ use crate::applicable_declarations::ApplicableDeclarationList; use crate::context::QuirksMode; use crate::dom::TElement; -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::{CascadeData, Rule}; +use crate::AllocErr; use crate::{Atom, LocalName, Namespace, WeakAtom}; -use fallible::FallibleVec; -use hashglobe::FailedAllocationError; use precomputed_hash::PrecomputedHash; use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext}; use selectors::parser::{Combinator, Component, SelectorIter}; use smallvec::SmallVec; +use std::collections::hash_map; +use std::collections::{HashMap, HashSet}; use std::hash::{BuildHasherDefault, Hash, Hasher}; /// A hasher implementation that doesn't hash anything, because it expects its @@ -322,11 +321,7 @@ impl SelectorMap { impl SelectorMap { /// Inserts an entry into the correct bucket(s). - pub fn insert( - &mut self, - entry: T, - quirks_mode: QuirksMode, - ) -> Result<(), FailedAllocationError> { + pub fn insert(&mut self, entry: T, quirks_mode: QuirksMode) -> Result<(), AllocErr> { self.count += 1; // NOTE(emilio): It'd be nice for this to be a separate function, but @@ -335,16 +330,16 @@ impl SelectorMap { // common path. macro_rules! insert_into_bucket { ($entry:ident, $bucket:expr) => {{ - match $bucket { + let vec = match $bucket { Bucket::Root => &mut self.root, Bucket::ID(id) => self .id_hash .try_entry(id.clone(), quirks_mode)? - .or_insert_with(SmallVec::new), + .or_default(), Bucket::Class(class) => self .class_hash .try_entry(class.clone(), quirks_mode)? - .or_insert_with(SmallVec::new), + .or_default(), Bucket::Attribute { name, lower_name } | Bucket::LocalName { name, lower_name } => { // If the local name in the selector isn't lowercase, @@ -367,19 +362,22 @@ impl SelectorMap { &mut self.local_name_hash }; if name != lower_name { - hash.try_entry(lower_name.clone())? - .or_insert_with(SmallVec::new) - .try_push($entry.clone())?; + hash.try_reserve(1)?; + let vec = hash.entry(lower_name.clone()).or_default(); + vec.try_reserve(1)?; + vec.push($entry.clone()); } - hash.try_entry(name.clone())?.or_insert_with(SmallVec::new) + hash.try_reserve(1)?; + hash.entry(name.clone()).or_default() + }, + Bucket::Namespace(url) => { + self.namespace_hash.try_reserve(1)?; + self.namespace_hash.entry(url.clone()).or_default() }, - Bucket::Namespace(url) => self - .namespace_hash - .try_entry(url.clone())? - .or_insert_with(SmallVec::new), Bucket::Universal => &mut self.other, - } - .try_push($entry)?; + }; + vec.try_reserve(1)?; + vec.push($entry); }}; } @@ -742,11 +740,12 @@ impl MaybeCaseInsensitiveHashMap { &mut self, mut key: Atom, quirks_mode: QuirksMode, - ) -> Result, FailedAllocationError> { + ) -> Result, AllocErr> { if quirks_mode == QuirksMode::Quirks { key = key.to_ascii_lowercase() } - self.0.try_entry(key) + self.0.try_reserve(1)?; + Ok(self.0.entry(key)) } /// HashMap::is_empty diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index ab7df841d7f..9d3aaedeff5 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -16,7 +16,6 @@ use crate::stylesheets::{CssRule, CssRules, Origin, UrlExtraData}; use crate::use_counters::UseCounters; use crate::{Namespace, Prefix}; use cssparser::{Parser, ParserInput, RuleListParser}; -use fallible::FallibleVec; use fxhash::FxHashMap; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; @@ -506,9 +505,10 @@ impl Stylesheet { // Use a fallible push here, and if it fails, just fall // out of the loop. This will cause the page to be // shown incorrectly, but it's better than OOMing. - if rules.try_push(rule).is_err() { + if rules.try_reserve(1).is_err() { break; } + rules.push(rule); }, Err((error, slice)) => { let location = error.location; diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 0ead08ccdcc..3b170e0e339 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -40,10 +40,9 @@ use crate::stylesheets::{ }; use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; use crate::thread_state::{self, ThreadState}; +use crate::AllocErr; use crate::{Atom, LocalName, Namespace, WeakAtom}; -use fallible::FallibleVec; use fxhash::FxHashMap; -use hashglobe::FailedAllocationError; use malloc_size_of::MallocSizeOf; #[cfg(feature = "gecko")] use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; @@ -113,7 +112,7 @@ trait CascadeDataCacheEntry: Sized { collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, old_entry: &Self, - ) -> Result, FailedAllocationError> + ) -> Result, AllocErr> where S: StylesheetInDocument + PartialEq + 'static; /// Measures heap memory usage. @@ -150,7 +149,7 @@ where collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, old_entry: &Entry, - ) -> Result>, FailedAllocationError> + ) -> Result>, AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -269,7 +268,7 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, _old: &Self, - ) -> Result, FailedAllocationError> + ) -> Result, AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -385,7 +384,7 @@ impl DocumentCascadeData { quirks_mode: QuirksMode, mut flusher: DocumentStylesheetFlusher<'a, S>, guards: &StylesheetGuards, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -599,7 +598,7 @@ impl Stylist { old_data: &CascadeData, collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, - ) -> Result>, FailedAllocationError> + ) -> Result>, AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -1560,7 +1559,8 @@ impl LayerOrderedVec { self.0.push((v, id)); } fn sort(&mut self, layers: &[CascadeLayer]) { - self.0.sort_by_key(|&(_, ref id)| layers[id.0 as usize].order) + self.0 + .sort_by_key(|&(_, ref id)| layers[id.0 as usize].order) } } @@ -1569,17 +1569,24 @@ impl LayerOrderedMap { self.0.clear(); } #[cfg(feature = "gecko")] - fn try_insert(&mut self, name: Atom, v: T, id: LayerId) -> Result<(), FailedAllocationError> { + fn try_insert(&mut self, name: Atom, v: T, id: LayerId) -> Result<(), AllocErr> { self.try_insert_with(name, v, id, |_, _| Ordering::Equal) } - fn try_insert_with(&mut self, name: Atom, v: T, id: LayerId, cmp: impl Fn(&T, &T) -> Ordering) -> Result<(), FailedAllocationError> { - let vec = self.0.try_entry(name)?.or_insert_with(Default::default); + fn try_insert_with( + &mut self, + name: Atom, + v: T, + id: LayerId, + cmp: impl Fn(&T, &T) -> Ordering, + ) -> Result<(), AllocErr> { + self.0.try_reserve(1)?; + let vec = self.0.entry(name).or_default(); if let Some(&mut (ref mut val, ref last_id)) = vec.last_mut() { if *last_id == id { if cmp(&val, &v) != Ordering::Greater { *val = v; } - return Ok(()) + return Ok(()); } } vec.push((v, id)); @@ -1639,7 +1646,11 @@ impl ExtraStyleData { } /// Add the given @font-feature-values rule. - fn add_font_feature_values(&mut self, rule: &Arc>, layer: LayerId) { + fn add_font_feature_values( + &mut self, + rule: &Arc>, + layer: LayerId, + ) { self.font_feature_values.push(rule.clone(), layer); } @@ -1649,7 +1660,7 @@ impl ExtraStyleData { guard: &SharedRwLockReadGuard, rule: &Arc>, layer: LayerId, - ) -> Result<(), FailedAllocationError> { + ) -> Result<(), AllocErr> { let name = rule.read_with(guard).name().0.clone(); self.counter_styles.try_insert(name, rule.clone(), layer) } @@ -1665,7 +1676,7 @@ impl ExtraStyleData { guard: &SharedRwLockReadGuard, rule: &Arc>, layer: LayerId, - ) -> Result<(), FailedAllocationError> { + ) -> Result<(), AllocErr> { let name = rule.read_with(guard).name.as_atom().clone(); self.scroll_timelines.try_insert(name, rule.clone(), layer) } @@ -2123,7 +2134,7 @@ impl CascadeData { quirks_mode: QuirksMode, collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { @@ -2262,7 +2273,8 @@ impl CascadeData { { self.extra_data.sort_by_layer(&self.layers); } - self.animations.sort_with(&self.layers, compare_keyframes_in_same_layer); + self.animations + .sort_with(&self.layers, compare_keyframes_in_same_layer); } /// Collects all the applicable media query results into `results`. @@ -2323,7 +2335,7 @@ impl CascadeData { mut current_layer: &mut LayerName, current_layer_id: LayerId, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + 'static, { @@ -2409,12 +2421,14 @@ impl CascadeData { // We choose the last one quite arbitrarily, // expecting it's slightly more likely to be more // specific. - self.part_rules + let map = 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)?; + .for_insertion(pseudo_element); + map.try_reserve(1)?; + let vec = map.entry(parts.last().unwrap().clone().0).or_default(); + vec.try_reserve(1)?; + vec.push(rule); } else { // NOTE(emilio): It's fine to look at :host and then at // ::slotted(..), since :host::slotted(..) could never @@ -2444,13 +2458,19 @@ impl CascadeData { keyframes_rule.vendor_prefix.clone(), guard, ); - self.animations.try_insert_with(name, animation, current_layer_id, compare_keyframes_in_same_layer)?; + self.animations.try_insert_with( + name, + animation, + current_layer_id, + compare_keyframes_in_same_layer, + )?; }, #[cfg(feature = "gecko")] CssRule::ScrollTimeline(ref rule) => { // Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue // https://github.com/w3c/csswg-drafts/issues/6674 gets landed. - self.extra_data.add_scroll_timeline(guard, rule, current_layer_id)?; + self.extra_data + .add_scroll_timeline(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { @@ -2458,11 +2478,13 @@ impl CascadeData { }, #[cfg(feature = "gecko")] CssRule::FontFeatureValues(ref rule) => { - self.extra_data.add_font_feature_values(rule, current_layer_id); + self.extra_data + .add_font_feature_values(rule, current_layer_id); }, #[cfg(feature = "gecko")] CssRule::CounterStyle(ref rule) => { - self.extra_data.add_counter_style(guard, rule, current_layer_id); + self.extra_data + .add_counter_style(guard, rule, current_layer_id)?; }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { @@ -2640,7 +2662,7 @@ impl CascadeData { guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, - ) -> Result<(), FailedAllocationError> + ) -> Result<(), AllocErr> where S: StylesheetInDocument + 'static, { @@ -2820,7 +2842,7 @@ impl CascadeDataCacheEntry for CascadeData { collection: SheetCollectionFlusher, guard: &SharedRwLockReadGuard, old: &Self, - ) -> Result, FailedAllocationError> + ) -> Result, AllocErr> where S: StylesheetInDocument + PartialEq + 'static, { diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index c18b9e09b71..054ec1ff410 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -84,12 +84,12 @@ pub use self::resolution::Resolution; pub use self::svg::{DProperty, MozContextProperties}; pub use self::svg::{SVGLength, SVGOpacity, SVGPaint, SVGPaintKind}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; +pub use self::text::HyphenateCharacter; pub use self::text::TextUnderlinePosition; pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight}; pub use self::text::{OverflowWrap, RubyPosition, TextOverflow, WordBreak, WordSpacing}; pub use self::text::{TextAlign, TextAlignLast, TextEmphasisPosition, TextEmphasisStyle}; pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify}; -pub use self::text::HyphenateCharacter; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform, TransformOperation}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index c9e67e137f3..c2b017f3973 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -21,10 +21,10 @@ use style_traits::{CssWriter, ToCss}; pub use crate::values::specified::text::{ MozControlCharacterVisibility, TextAlignLast, TextUnderlinePosition, }; +pub use crate::values::specified::HyphenateCharacter; pub use crate::values::specified::{LineBreak, OverflowWrap, RubyPosition, WordBreak}; pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; pub use crate::values::specified::{TextDecorationSkipInk, TextJustify, TextTransform}; -pub use crate::values::specified::HyphenateCharacter; /// A computed value for the `initial-letter` property. pub type InitialLetter = GenericInitialLetter; diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index 600d65a531c..e092f254bd4 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -244,8 +244,11 @@ impl FontRelativeLength { (reference_size, length) }, FontRelativeLength::Ic(length) => { - let metrics = - query_font_metrics(context, base_size, FontMetricsOrientation::MatchContextPreferVertical); + let metrics = query_font_metrics( + context, + base_size, + FontMetricsOrientation::MatchContextPreferVertical, + ); let reference_size = metrics.ic_width.unwrap_or_else(|| { // https://drafts.csswg.org/css-values/#ic // diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index c3388344dab..78da87d4ceb 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -83,6 +83,7 @@ pub use self::svg::{DProperty, MozContextProperties}; pub use self::svg::{SVGLength, SVGOpacity, SVGPaint}; pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth}; pub use self::svg_path::SVGPathData; +pub use self::text::HyphenateCharacter; pub use self::text::RubyPosition; pub use self::text::TextAlignLast; pub use self::text::TextUnderlinePosition; @@ -90,7 +91,6 @@ pub use self::text::{InitialLetter, LetterSpacing, LineBreak, LineHeight, TextAl pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; pub use self::text::{TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform}; -pub use self::text::HyphenateCharacter; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs index e6d5347e378..883f529d867 100644 --- a/components/style/values/specified/page.rs +++ b/components/style/values/specified/page.rs @@ -5,9 +5,9 @@ //! Specified @page at-rule properties and named-page style properties use crate::parser::{Parse, ParserContext}; -use crate::values::{generics, CustomIdent}; use crate::values::generics::size::Size2D; use crate::values::specified::length::NonNegativeLength; +use crate::values::{generics, CustomIdent}; use cssparser::Parser; use style_traits::ParseError; @@ -50,7 +50,17 @@ impl Parse for PageSize { /// Page name value. /// /// https://drafts.csswg.org/css-page-3/#using-named-pages -#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] +#[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToCss, + ToComputedValue, + ToResolvedValue, + ToShmem, +)] #[repr(C, u8)] pub enum PageName { /// `auto` value. From 89041ac33035318fb0507191e5df31adbc758d1b Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 00:50:20 +0200 Subject: [PATCH 47/81] Further changes required by Servo --- Cargo.lock | 19 ------------------- servo-tidy.toml | 2 -- 2 files changed, 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8ce282dcf2..aedcc003f21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1601,14 +1601,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -[[package]] -name = "fallible" -version = "0.0.1" -dependencies = [ - "hashglobe", - "smallvec", -] - [[package]] name = "fastrand" version = "1.7.0" @@ -2598,14 +2590,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -[[package]] -name = "hashglobe" -version = "0.1.0" -dependencies = [ - "libc", - "rand 0.7.3", -] - [[package]] name = "headers" version = "0.3.8" @@ -3461,7 +3445,6 @@ dependencies = [ "crossbeam-channel 0.4.4", "cssparser", "euclid", - "hashglobe", "http", "hyper_serde", "keyboard-types", @@ -5970,9 +5953,7 @@ dependencies = [ "derive_more", "encoding_rs", "euclid", - "fallible", "fxhash", - "hashglobe", "html5ever", "indexmap", "itertools", diff --git a/servo-tidy.toml b/servo-tidy.toml index 8254b04bd91..17b924c0dc0 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -11,7 +11,6 @@ lint-scripts = ["./python/servo/lints/wpt_lint.py"] [blocked-packages] num = [] rand = [ - "hashglobe", # Only used in tests "ipc-channel", "phf_generator", "quickcheck", # Only used in tests @@ -135,7 +134,6 @@ directories = [ "./components/script/dom/bindings/codegen/ply", "./python/_virtualenv*", "./python/mach", - "./components/hashglobe/src", # Generated and upstream code combined with our own. Could use cleanup "./target", ] From 4229ace432ec3c6ae14c2abe4ddaa228aced6610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:09:44 +0200 Subject: [PATCH 48/81] style: Add hwb/a to devtools autocomplete lists The changes to the devtools directory were written automatically via ./mach devtools-css-db Differential Revision: https://phabricator.services.mozilla.com/D134197 --- components/style/values/specified/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 3629b17058f..11acfe63c84 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -829,7 +829,7 @@ impl SpecifiedValueInfo for Color { // should probably be handled that way as well. // XXX `currentColor` should really be `currentcolor`. But let's // keep it consistent with the old system for now. - f(&["rgb", "rgba", "hsl", "hsla", "currentColor", "transparent"]); + f(&["rgb", "rgba", "hsl", "hsla", "hwb", "hwba", "currentColor", "transparent"]); } } From d0475b75d42e5cd7cbcbd3803e3161544bce51bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:11:36 +0200 Subject: [PATCH 49/81] style: Remove hwba since it's not a thing (hwb function supports alpha) Partially backs out the previous patch. Differential Revision: https://phabricator.services.mozilla.com/D134256 --- components/style/values/specified/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 11acfe63c84..550ad207578 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -829,7 +829,7 @@ impl SpecifiedValueInfo for Color { // should probably be handled that way as well. // XXX `currentColor` should really be `currentcolor`. But let's // keep it consistent with the old system for now. - f(&["rgb", "rgba", "hsl", "hsla", "hwb", "hwba", "currentColor", "transparent"]); + f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]); } } From fc4d185079ea17cfc973e37e6f871c4927071b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:14:56 +0200 Subject: [PATCH 50/81] style: Use ThreadPool::scope_fifo in style It does the same, but it saves an indentation level: https://searchfox.org/mozilla-central/rev/a11b63915bd7810a03635d733123448ab5bfcad3/third_party/rust/rayon-core/src/thread_pool/mod.rs#217 Differential Revision: https://phabricator.services.mozilla.com/D134321 --- components/style/driver.rs | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/components/style/driver.rs b/components/style/driver.rs index 82ed2a66386..b7e33454f41 100644 --- a/components/style/driver.rs +++ b/components/style/driver.rs @@ -133,29 +133,27 @@ where let tls = ScopedTLS::>::new(pool); let root_opaque = root.as_node().opaque(); let drain = discovered.drain(..); - pool.install(|| { + pool.scope_fifo(|scope| { // Enable a breadth-first rayon traversal. This causes the work // queue to be always FIFO, rather than FIFO for stealers and // FILO for the owner (which is what rayon does by default). This // ensures that we process all the elements at a given depth before // proceeding to the next depth, which is important for style sharing. - rayon::scope_fifo(|scope| { - #[cfg(feature = "gecko")] - gecko_profiler_label!(Layout, StyleComputation); - parallel::traverse_nodes( - drain, - DispatchMode::TailCall, - /* recursion_ok = */ true, - root_opaque, - PerLevelTraversalData { - current_dom_depth: depth, - }, - scope, - pool, - traversal, - &tls, - ); - }); + #[cfg(feature = "gecko")] + gecko_profiler_label!(Layout, StyleComputation); + parallel::traverse_nodes( + drain, + DispatchMode::TailCall, + /* recursion_ok = */ true, + root_opaque, + PerLevelTraversalData { + current_dom_depth: depth, + }, + scope, + pool, + traversal, + &tls, + ); }); tls_slots = Some(tls.into_slots()); From 5b62f66f6e60e57d025f3a8709469227832b3bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:25:18 +0200 Subject: [PATCH 51/81] style: Remove ThinBoxedSlice The only remaining consumers are ::-moz-tree pseudo-elements (we used to use ThinBoxedSlice for other data structures in the past). Those are not particularly performance sensitive so I think just double-boxing is fine. In the future, if we wanted to avoid the double indirection, we could probably use the "thin" crate (https://docs.rs/thin) or similar, which stores the length of the slice along with the allocation, making the pointer thin in all configurations, much like "ThinArc" does: https://searchfox.org/mozilla-central/rev/1ce2eea39442190a71a1f8f650d098f286bf4a01/servo/components/servo_arc/lib.rs#891 In practice though, I don't think it's particularly worth it for this specific case. Differential Revision: https://phabricator.services.mozilla.com/D134672 --- components/malloc_size_of/Cargo.toml | 1 - components/malloc_size_of/lib.rs | 18 ------------------ components/style/Cargo.toml | 4 +--- components/style/gecko/pseudo_element.rs | 1 - .../gecko/pseudo_element_definition.mako.rs | 5 ++++- components/to_shmem/Cargo.toml | 1 - components/to_shmem/lib.rs | 18 ------------------ 7 files changed, 5 insertions(+), 43 deletions(-) diff --git a/components/malloc_size_of/Cargo.toml b/components/malloc_size_of/Cargo.toml index 43a82c9ed8d..23d7a79d371 100644 --- a/components/malloc_size_of/Cargo.toml +++ b/components/malloc_size_of/Cargo.toml @@ -44,7 +44,6 @@ servo_arc = { path = "../servo_arc" } smallbitvec = { workspace = true } smallvec = { workspace = true } string_cache = { workspace = true, optional = true } -thin-slice = { workspace = true } time = { workspace = true, optional = true } tokio = { workspace = true } url = { workspace = true, optional = true } diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index fa5a9b1e0ce..2a684ac73d1 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -213,24 +213,6 @@ impl MallocSizeOf for Box { } } -impl MallocShallowSizeOf for thin_slice::ThinBoxedSlice { - fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let mut n = 0; - unsafe { - n += thin_slice::ThinBoxedSlice::spilled_storage(self) - .map_or(0, |ptr| ops.malloc_size_of(ptr)); - n += ops.malloc_size_of(&**self); - } - n - } -} - -impl MallocSizeOf for thin_slice::ThinBoxedSlice { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.shallow_size_of(ops) + (**self).size_of(ops) - } -} - impl MallocSizeOf for () { fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { 0 diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 6c49fcf5889..17af89e9e34 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -17,8 +17,7 @@ path = "lib.rs" doctest = false [features] -gecko = ["style_traits/gecko", "bindgen", "regex", "toml", - "num_cpus", "thin-slice"] +gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "num_cpus"] servo = ["serde", "style_traits/servo", "servo_atoms", "servo_config", "html5ever", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "servo_url", "string_cache", "to_shmem/servo", @@ -68,7 +67,6 @@ smallvec = "1.0" string_cache = { version = "0.8", optional = true } style_derive = { path = "../style_derive" } style_traits = { path = "../style_traits" } -thin-slice = { version = "0.1.0", optional = true } time = "0.1" to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } diff --git a/components/style/gecko/pseudo_element.rs b/components/style/gecko/pseudo_element.rs index be8d0def121..855fed9a0d8 100644 --- a/components/style/gecko/pseudo_element.rs +++ b/components/style/gecko/pseudo_element.rs @@ -17,7 +17,6 @@ use crate::string_cache::Atom; use crate::values::serialize_atom_identifier; use cssparser::ToCss; use std::fmt; -use thin_slice::ThinBoxedSlice; include!(concat!( env!("OUT_DIR"), diff --git a/components/style/gecko/pseudo_element_definition.mako.rs b/components/style/gecko/pseudo_element_definition.mako.rs index 41dff1a9468..dd6d1aad227 100644 --- a/components/style/gecko/pseudo_element_definition.mako.rs +++ b/components/style/gecko/pseudo_element_definition.mako.rs @@ -3,12 +3,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /// Gecko's pseudo-element definition. +/// +/// We intentionally double-box legacy ::-moz-tree pseudo-elements to keep the +/// size of PseudoElement (and thus selector components) small. #[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)] pub enum PseudoElement { % for pseudo in PSEUDOS: /// ${pseudo.value} % if pseudo.is_tree_pseudo_element(): - ${pseudo.capitalized_pseudo()}(ThinBoxedSlice), + ${pseudo.capitalized_pseudo()}(Box>), % else: ${pseudo.capitalized_pseudo()}, % endif diff --git a/components/to_shmem/Cargo.toml b/components/to_shmem/Cargo.toml index a87b441fa10..9ef2cefe112 100644 --- a/components/to_shmem/Cargo.toml +++ b/components/to_shmem/Cargo.toml @@ -20,4 +20,3 @@ servo_arc = { path = "../servo_arc" } smallbitvec = { workspace = true } smallvec = { workspace = true } string_cache = { workspace = true, optional = true } -thin-slice = { workspace = true } diff --git a/components/to_shmem/lib.rs b/components/to_shmem/lib.rs index 9188346eb59..6de65ff440e 100644 --- a/components/to_shmem/lib.rs +++ b/components/to_shmem/lib.rs @@ -32,7 +32,6 @@ use std::os::raw::c_void; use std::ptr::{self, NonNull}; use std::slice; use std::str; -use thin_slice::ThinBoxedSlice; /// Result type for ToShmem::to_shmem. /// @@ -334,23 +333,6 @@ impl ToShmem for Box<[T]> { } } -impl ToShmem for ThinBoxedSlice { - fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { - // We could support this if we needed but in practice we will never - // need to handle such big ThinBoxedSlices. - assert!( - self.spilled_storage().is_none(), - "ToShmem failed for ThinBoxedSlice: too many entries ({})", - self.len(), - ); - - unsafe { - let dest = to_shmem_slice(self.iter(), builder)?; - Ok(ManuallyDrop::new(ThinBoxedSlice::from_raw(dest))) - } - } -} - impl ToShmem for Box { fn to_shmem(&self, builder: &mut SharedMemoryBuilder) -> Result { // Reserve space for the string bytes. From f9610e5898ee3474ad91720bc43540165e56cacd Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 00:54:17 +0200 Subject: [PATCH 52/81] Further changes required by Servo --- Cargo.lock | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aedcc003f21..d8b8b2b5e0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3455,7 +3455,6 @@ dependencies = [ "smallbitvec", "smallvec", "string_cache", - "thin-slice", "time 0.1.45", "tokio", "url", @@ -5984,7 +5983,6 @@ dependencies = [ "string_cache", "style_derive", "style_traits", - "thin-slice", "time 0.1.45", "to_shmem", "to_shmem_derive", @@ -6197,12 +6195,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.38" @@ -6352,7 +6344,6 @@ dependencies = [ "smallbitvec", "smallvec", "string_cache", - "thin-slice", ] [[package]] From fcc55f2156132efeb7d127a3559d5687f4e8945d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:27:20 +0200 Subject: [PATCH 53/81] style: Shrink maps if needed after stylist rebuilds Hashbrown grows a lot sometimes making us waste a lot of memory. Shrink some of these maps after CascadeData rebuild / stylesheet collection invalidation. Differential Revision: https://phabricator.services.mozilla.com/D134716 --- .../invalidation/element/invalidation_map.rs | 10 ++++- components/style/invalidation/stylesheets.rs | 13 +++++- components/style/lib.rs | 43 +++++++++++++++++++ components/style/selector_map.rs | 30 ++++++++----- components/style/selector_parser.rs | 7 ++- components/style/stylist.rs | 36 ++++++++++++++-- 6 files changed, 122 insertions(+), 17 deletions(-) diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index 153c13bc149..0bba423a17f 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -11,7 +11,7 @@ use crate::selector_map::{ }; use crate::selector_parser::SelectorImpl; use crate::AllocErr; -use crate::{Atom, LocalName, Namespace}; +use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded}; use selectors::attr::NamespaceConstraint; use selectors::parser::{Combinator, Component}; use selectors::parser::{Selector, SelectorIter}; @@ -237,6 +237,14 @@ impl InvalidationMap { self.other_attribute_affecting_selectors.clear(); } + /// Shrink the capacity of hash maps if needed. + pub fn shrink_if_needed(&mut self) { + self.class_to_selector.shrink_if_needed(); + self.id_to_selector.shrink_if_needed(); + self.state_affecting_selectors.shrink_if_needed(); + self.other_attribute_affecting_selectors.shrink_if_needed(); + } + /// Adds a selector to this `InvalidationMap`. Returns Err(..) to /// signify OOM. pub fn note_selector( diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index c18cc3cd242..53130de7a2e 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -18,7 +18,7 @@ use crate::shared_lock::SharedRwLockReadGuard; use crate::stylesheets::{CssRule, StylesheetInDocument}; use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator}; use crate::values::AtomIdent; -use crate::Atom; +use crate::{Atom, ShrinkIfNeeded}; use crate::LocalName as SelectorLocalName; use selectors::parser::{Component, LocalName, Selector}; @@ -119,6 +119,15 @@ impl StylesheetInvalidationSet { self.fully_invalid = true; } + fn shrink_if_needed(&mut self) { + if self.fully_invalid { + return; + } + self.classes.shrink_if_needed(); + self.ids.shrink_if_needed(); + self.local_names.shrink_if_needed(); + } + /// Analyze the given stylesheet, and collect invalidations from their /// rules, in order to avoid doing a full restyle when we style the document /// next time. @@ -149,6 +158,8 @@ impl StylesheetInvalidationSet { } } + self.shrink_if_needed(); + debug!(" > resulting class invalidations: {:?}", self.classes); debug!(" > resulting id invalidations: {:?}", self.ids); debug!( diff --git a/components/style/lib.rs b/components/style/lib.rs index 00c8359b5f4..2967fbcf7ce 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -157,6 +157,8 @@ pub use style_traits::arc_slice::ArcSlice; pub use style_traits::owned_slice::OwnedSlice; pub use style_traits::owned_str::OwnedStr; +use std::hash::{Hash, BuildHasher}; + /// The CSS properties supported by the style system. /// Generated from the properties.mako.rs template by build.rs #[macro_use] @@ -286,3 +288,44 @@ impl From for AllocErr { Self } } + +/// Shrink the capacity of the collection if needed. +pub (crate) trait ShrinkIfNeeded { + fn shrink_if_needed(&mut self); +} + +/// We shrink the capacity of a collection if we're wasting more than a 25% of +/// its capacity, and if the collection is arbitrarily big enough +/// (>= CAPACITY_THRESHOLD entries). +#[inline] +fn should_shrink(len: usize, capacity: usize) -> bool { + const CAPACITY_THRESHOLD: usize = 64; + capacity >= CAPACITY_THRESHOLD && len + capacity / 4 < capacity +} + +impl ShrinkIfNeeded for std::collections::HashMap +where + K: Eq + Hash, + H: BuildHasher, +{ + fn shrink_if_needed(&mut self) { + if should_shrink(self.len(), self.capacity()) { + self.shrink_to_fit(); + } + } +} + +impl ShrinkIfNeeded for std::collections::HashSet +where + T: Eq + Hash, + H: BuildHasher, +{ + fn shrink_if_needed(&mut self) { + if should_shrink(self.len(), self.capacity()) { + self.shrink_to_fit(); + } + } +} + +// TODO(emilio): Measure and see if we're wasting a lot of memory on Vec / +// SmallVec, and if so consider shrinking those as well. diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 0aa8cefc406..8a575ca8386 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -12,7 +12,7 @@ use crate::rule_tree::CascadeLevel; use crate::selector_parser::SelectorImpl; use crate::stylist::{CascadeData, Rule}; use crate::AllocErr; -use crate::{Atom, LocalName, Namespace, WeakAtom}; +use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use precomputed_hash::PrecomputedHash; use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext}; use selectors::parser::{Combinator, Component, SelectorIter}; @@ -122,10 +122,7 @@ impl Default for SelectorMap { } } -// FIXME(Manishearth) the 'static bound can be removed when -// our HashMap fork (hashglobe) is able to use NonZero, -// or when stdlib gets fallible collections -impl SelectorMap { +impl SelectorMap { /// Trivially constructs an empty `SelectorMap`. pub fn new() -> Self { SelectorMap { @@ -152,6 +149,15 @@ impl SelectorMap { ret } + /// Shrink the capacity of the map if needed. + pub fn shrink_if_needed(&mut self) { + self.id_hash.shrink_if_needed(); + self.class_hash.shrink_if_needed(); + self.attribute_hash.shrink_if_needed(); + self.local_name_hash.shrink_if_needed(); + self.namespace_hash.shrink_if_needed(); + } + /// Clears the hashmap retaining storage. pub fn clear(&mut self) { self.root.clear(); @@ -715,26 +721,28 @@ fn find_bucket<'a>( /// Wrapper for PrecomputedHashMap that does ASCII-case-insensitive lookup in quirks mode. #[derive(Clone, Debug, MallocSizeOf)] -pub struct MaybeCaseInsensitiveHashMap( +pub struct MaybeCaseInsensitiveHashMap( PrecomputedHashMap, ); -impl Default for MaybeCaseInsensitiveHashMap { +impl Default for MaybeCaseInsensitiveHashMap { #[inline] fn default() -> Self { MaybeCaseInsensitiveHashMap(PrecomputedHashMap::default()) } } -// FIXME(Manishearth) the 'static bound can be removed when -// our HashMap fork (hashglobe) is able to use NonZero, -// or when stdlib gets fallible collections -impl MaybeCaseInsensitiveHashMap { +impl MaybeCaseInsensitiveHashMap { /// Empty map pub fn new() -> Self { Self::default() } + /// Shrink the capacity of the map if needed. + pub fn shrink_if_needed(&mut self) { + self.0.shrink_if_needed() + } + /// HashMap::try_entry pub fn try_entry( &mut self, diff --git a/components/style/selector_parser.rs b/components/style/selector_parser.rs index d28e333da04..f29cab9735d 100644 --- a/components/style/selector_parser.rs +++ b/components/style/selector_parser.rs @@ -170,9 +170,14 @@ impl PerPseudoElementMap { } /// Get an iterator for the entries. - pub fn iter(&self) -> ::std::slice::Iter> { + pub fn iter(&self) -> std::slice::Iter> { self.entries.iter() } + + /// Get a mutable iterator for the entries. + pub fn iter_mut(&mut self) -> std::slice::IterMut> { + self.entries.iter_mut() + } } /// Values for the :dir() pseudo class diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 3b170e0e339..14f88c4fe22 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -41,7 +41,7 @@ use crate::stylesheets::{ use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; use crate::thread_state::{self, ThreadState}; use crate::AllocErr; -use crate::{Atom, LocalName, Namespace, WeakAtom}; +use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use fxhash::FxHashMap; use malloc_size_of::MallocSizeOf; #[cfg(feature = "gecko")] @@ -291,7 +291,7 @@ impl CascadeDataCacheEntry for UserAgentCascadeData { )?; } - new_data.cascade_data.compute_layer_order(); + new_data.cascade_data.did_finish_rebuild(); Ok(Arc::new(new_data)) } @@ -1978,6 +1978,15 @@ impl ElementAndPseudoRules { self.element_map.clear(); self.pseudos_map.clear(); } + + fn shrink_if_needed(&mut self) { + self.element_map.shrink_if_needed(); + for pseudo in self.pseudos_map.iter_mut() { + if let Some(ref mut pseudo) = pseudo { + pseudo.shrink_if_needed(); + } + } + } } impl PartElementAndPseudoRules { @@ -2164,7 +2173,7 @@ impl CascadeData { result.is_ok() }); - self.compute_layer_order(); + self.did_finish_rebuild(); result } @@ -2232,6 +2241,27 @@ impl CascadeData { self.layers[id.0 as usize].order } + fn did_finish_rebuild(&mut self) { + self.shrink_maps_if_needed(); + self.compute_layer_order(); + } + + fn shrink_maps_if_needed(&mut self) { + self.normal_rules.shrink_if_needed(); + if let Some(ref mut host_rules) = self.host_rules { + host_rules.shrink_if_needed(); + } + if let Some(ref mut slotted_rules) = self.slotted_rules { + slotted_rules.shrink_if_needed(); + } + self.invalidation_map.shrink_if_needed(); + self.attribute_dependencies.shrink_if_needed(); + self.mapped_ids.shrink_if_needed(); + self.layer_id.shrink_if_needed(); + self.selectors_for_cache_revalidation.shrink_if_needed(); + + } + fn compute_layer_order(&mut self) { debug_assert_ne!( self.layers.len(), From 01e43a8488c63a1c60a35e914f0598f3d5934f12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:28:52 +0200 Subject: [PATCH 54/81] style: Make color-adjust an alias of print-color-adjust as per spec Differential Revision: https://phabricator.services.mozilla.com/D134779 --- components/style/properties/data.py | 1 + .../properties/longhands/inherited_box.mako.rs | 13 ++++++------- components/style/values/computed/color.rs | 2 +- components/style/values/computed/mod.rs | 2 +- components/style/values/specified/color.rs | 10 ++++++++++ components/style/values/specified/mod.rs | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index abcffe68e95..740815bcb38 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -501,6 +501,7 @@ class Longhand(Property): "OverscrollBehavior", "Percentage", "PositiveIntegerOrNone", + "PrintColorAdjust", "Resize", "RubyPosition", "SVGOpacity", diff --git a/components/style/properties/longhands/inherited_box.mako.rs b/components/style/properties/longhands/inherited_box.mako.rs index 294f36bd96f..50f385ee343 100644 --- a/components/style/properties/longhands/inherited_box.mako.rs +++ b/components/style/properties/longhands/inherited_box.mako.rs @@ -56,15 +56,14 @@ ${helpers.single_keyword( spec="https://drafts.csswg.org/css-writing-modes/#propdef-text-orientation", )} -// CSS Color Module Level 4 -// https://drafts.csswg.org/css-color/ -${helpers.single_keyword( - "color-adjust", - "economy exact", +${helpers.predefined_type( + "print-color-adjust", + "PrintColorAdjust", + "computed::PrintColorAdjust::Economy", engines="gecko", - gecko_enum_prefix="StyleColorAdjust", + aliases="color-adjust", + spec="https://drafts.csswg.org/css-color-adjust/#print-color-adjust", animation_value_type="discrete", - spec="https://drafts.csswg.org/css-color/#propdef-color-adjust", )} // According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto` diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 6b970181d85..7610bfbba3b 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -11,7 +11,7 @@ use cssparser::{Color as CSSParserColor, RGBA}; use std::fmt; use style_traits::{CssWriter, ToCss}; -pub use crate::values::specified::color::ColorScheme; +pub use crate::values::specified::color::{ColorScheme, PrintColorAdjust}; /// The computed value of the `color` property. pub type ColorPropertyValue = RGBA; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 054ec1ff410..74e78887480 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -50,7 +50,7 @@ pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; -pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; +pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; pub use self::easing::TimingFunction; diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 550ad207578..4c0683dbce0 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -1006,3 +1006,13 @@ impl ToCss for ColorScheme { Ok(()) } } + +/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust +#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] +#[repr(u8)] +pub enum PrintColorAdjust { + /// Ignore backgrounds and darken text. + Economy, + /// Respect specified colors. + Exact, +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 78da87d4ceb..099b06a48b3 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -42,7 +42,7 @@ pub use self::box_::{Clear, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; -pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme}; +pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet}; pub use self::easing::TimingFunction; From e44bedbcda0883df7869110ee90873591fcd9115 Mon Sep 17 00:00:00 2001 From: Neia Finch Date: Tue, 6 Jun 2023 23:30:40 +0200 Subject: [PATCH 55/81] style: Replace MathML font constants with enum Differential Revision: https://phabricator.services.mozilla.com/D134802 --- components/style/properties/longhands/font.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/longhands/font.mako.rs b/components/style/properties/longhands/font.mako.rs index 12191509622..bd689673bc7 100644 --- a/components/style/properties/longhands/font.mako.rs +++ b/components/style/properties/longhands/font.mako.rs @@ -260,7 +260,7 @@ ${helpers.single_keyword( bold-sans-serif sans-serif-italic sans-serif-bold-italic monospace initial tailed looped stretched""", engines="gecko", - gecko_constant_prefix="NS_MATHML_MATHVARIANT", + gecko_enum_prefix="StyleMathMLMathVariant", gecko_ffi_name="mMathVariant", spec="Internal (not web-exposed)", animation_value_type="none", From f0524611c41547799a52b96329c0640f094eb802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:31:45 +0200 Subject: [PATCH 56/81] style: Rename StyleMathMLMathVariant -> StyleMathVariant MANUAL PUSH: Trivial rename. --- components/style/properties/longhands/font.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/longhands/font.mako.rs b/components/style/properties/longhands/font.mako.rs index bd689673bc7..65ee3cbb6ce 100644 --- a/components/style/properties/longhands/font.mako.rs +++ b/components/style/properties/longhands/font.mako.rs @@ -260,7 +260,7 @@ ${helpers.single_keyword( bold-sans-serif sans-serif-italic sans-serif-bold-italic monospace initial tailed looped stretched""", engines="gecko", - gecko_enum_prefix="StyleMathMLMathVariant", + gecko_enum_prefix="StyleMathVariant", gecko_ffi_name="mMathVariant", spec="Internal (not web-exposed)", animation_value_type="none", From 6cb3b7e254a0b8e3a961c6f91534cf22a6beaa88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:32:53 +0200 Subject: [PATCH 57/81] style: Remove touch-action pref It's been enabled by default since ~forever. Differential Revision: https://phabricator.services.mozilla.com/D134935 --- components/style/properties/longhands/box.mako.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 2bfd4015334..5e67de1565d 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -689,7 +689,6 @@ ${helpers.predefined_type( "TouchAction", "computed::TouchAction::auto()", engines="gecko", - gecko_pref="layout.css.touch_action.enabled", animation_value_type="discrete", spec="https://compat.spec.whatwg.org/#touch-action", )} From 080b3f8d1aa7cc6c352bf0f26878b1cdc1bde5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:34:11 +0200 Subject: [PATCH 58/81] style: Avoid generating InterpolateMatrix operations if there are no size dependencies The issue here is that we end up with a transition between mismatched transform lists that ends up generating an InterpolateMatrix {} operation. So far so good, but we end up interpolating that a lot of times and generating an unboundedly-deep operation list. This implementas an optimization that flattens them to a single matrix when possible (when there's no dependencies on the containing box). This is similar to: https://chromium.googlesource.com/chromium/src.git/+/2b89cc4df436e672ef9cf940d1c0dc73fef82a4a We fix the to_pixel_length() behavior for LenghtPercentage to be correct (and update callers to preserve behavior). Differential Revision: https://phabricator.services.mozilla.com/D134784 --- components/style/values/animated/transform.rs | 108 ++++++++++-------- components/style/values/generics/transform.rs | 40 ++++--- 2 files changed, 83 insertions(+), 65 deletions(-) diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs index 542b4c31023..8600281ce5b 100644 --- a/components/style/values/animated/transform.rs +++ b/components/style/values/animated/transform.rs @@ -891,25 +891,8 @@ impl Animate for ComputedTransform { match (this_remainder, other_remainder) { // If there is a remainder from *both* lists we must have had mismatched functions. // => Add the remainders to a suitable ___Matrix function. - (Some(this_remainder), Some(other_remainder)) => match procedure { - Procedure::Add => { - debug_assert!(false, "Should have already dealt with add by the point"); - return Err(()); - }, - Procedure::Interpolate { progress } => { - result.push(TransformOperation::InterpolateMatrix { - from_list: Transform(this_remainder.to_vec().into()), - to_list: Transform(other_remainder.to_vec().into()), - progress: Percentage(progress as f32), - }); - }, - Procedure::Accumulate { count } => { - result.push(TransformOperation::AccumulateMatrix { - from_list: Transform(this_remainder.to_vec().into()), - to_list: Transform(other_remainder.to_vec().into()), - count: cmp::min(count, i32::max_value() as u64) as i32, - }); - }, + (Some(this_remainder), Some(other_remainder)) => { + result.push(TransformOperation::animate_mismatched_transforms(this_remainder, other_remainder, procedure)?); }, // If there is a remainder from just one list, then one list must be shorter but // completely match the type of the corresponding functions in the longer list. @@ -923,36 +906,19 @@ impl Animate for ComputedTransform { let identity = transform.to_animated_zero().unwrap(); match transform { - // We can't interpolate/accumulate ___Matrix types directly with a - // matrix. Instead we need to wrap it in another ___Matrix type. TransformOperation::AccumulateMatrix { .. } | TransformOperation::InterpolateMatrix { .. } => { - let transform_list = Transform(vec![transform.clone()].into()); - let identity_list = Transform(vec![identity].into()); - let (from_list, to_list) = if fill_right { - (transform_list, identity_list) + let (from, to) = if fill_right { + (transform, &identity) } else { - (identity_list, transform_list) + (&identity, transform) }; - match procedure { - Procedure::Add => Err(()), - Procedure::Interpolate { progress } => { - Ok(TransformOperation::InterpolateMatrix { - from_list, - to_list, - progress: Percentage(progress as f32), - }) - }, - Procedure::Accumulate { count } => { - Ok(TransformOperation::AccumulateMatrix { - from_list, - to_list, - count: cmp::min(count, i32::max_value() as u64) - as i32, - }) - }, - } + TransformOperation::animate_mismatched_transforms( + &[from.clone()], + &[to.clone()], + procedure, + ) }, _ => { let (lhs, rhs) = if fill_right { @@ -981,9 +947,13 @@ impl ComputeSquaredDistance for ComputedTransform { // Roll back to matrix interpolation if there is any Err(()) in the // transform lists, such as mismatched transform functions. + // + // FIXME: Using a zero size here seems a bit sketchy but matches the + // previous behavior. if squared_dist.is_err() { - let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into(); - let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into(); + let rect = euclid::Rect::zero(); + let matrix1: Matrix3D = self.to_transform_3d_matrix(Some(&rect))?.0.into(); + let matrix2: Matrix3D = other.to_transform_3d_matrix(Some(&rect))?.0.into(); return matrix1.compute_squared_distance(&matrix2); } @@ -1141,6 +1111,52 @@ impl Animate for ComputedTransformOperation { } } +impl ComputedTransformOperation { + /// If there are no size dependencies, we try to animate in-place, to avoid + /// creating deeply nested Interpolate* operations. + fn try_animate_mismatched_transforms_in_place( + left: &[Self], + right: &[Self], + procedure: Procedure, + ) -> Result { + let (left, _left_3d) = Transform::components_to_transform_3d_matrix(left, None)?; + let (right, _right_3d) = Transform::components_to_transform_3d_matrix(right, None)?; + ComputedTransformOperation::Matrix3D(left.into()).animate(&ComputedTransformOperation::Matrix3D(right.into()), procedure) + } + + fn animate_mismatched_transforms( + left: &[Self], + right: &[Self], + procedure: Procedure, + ) -> Result { + if let Ok(op) = Self::try_animate_mismatched_transforms_in_place(left, right, procedure) { + return Ok(op); + } + let from_list = Transform(left.to_vec().into()); + let to_list = Transform(right.to_vec().into()); + Ok(match procedure { + Procedure::Add => { + debug_assert!(false, "Addition should've been handled earlier"); + return Err(()) + }, + Procedure::Interpolate { progress } => { + Self::InterpolateMatrix { + from_list, + to_list, + progress: Percentage(progress as f32), + } + } + Procedure::Accumulate { count } => { + Self::AccumulateMatrix { + from_list, + to_list, + count: cmp::min(count, i32::max_value() as u64) as i32, + } + } + }) + } +} + // This might not be the most useful definition of distance. It might be better, for example, // to trace the distance travelled by a point as its transform is interpolated between the two // lists. That, however, proves to be quite complicated so we take a simple approach for now. diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 816dde92e4f..1179001ccf8 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -404,15 +404,7 @@ impl ToAbsoluteLength for ComputedLength { impl ToAbsoluteLength for ComputedLengthPercentage { #[inline] fn to_pixel_length(&self, containing_len: Option) -> Result { - match containing_len { - Some(relative_len) => Ok(self.resolve(relative_len).px()), - // If we don't have reference box, we cannot resolve the used value, - // so only retrieve the length part. This will be used for computing - // distance without any layout info. - // - // FIXME(emilio): This looks wrong. - None => Ok(self.resolve(Zero::zero()).px()), - } + Ok(self.maybe_percentage_relative_to(containing_len).ok_or(())?.px()) } } @@ -572,12 +564,21 @@ impl Transform { impl Transform { /// Return the equivalent 3d matrix of this transform list. + /// /// We return a pair: the first one is the transform matrix, and the second one /// indicates if there is any 3d transform function in this transform list. #[cfg_attr(rustfmt, rustfmt_skip)] pub fn to_transform_3d_matrix( &self, reference_box: Option<&Rect> + ) -> Result<(Transform3D, bool), ()> { + Self::components_to_transform_3d_matrix(&self.0, reference_box) + } + + /// Converts a series of components to a 3d matrix. + pub fn components_to_transform_3d_matrix( + ops: &[T], + reference_box: Option<&Rect> ) -> Result<(Transform3D, bool), ()> { let cast_3d_transform = |m: Transform3D| -> Transform3D { use std::{f32, f64}; @@ -590,26 +591,27 @@ impl Transform { ) }; - let (m, is_3d) = self.to_transform_3d_matrix_f64(reference_box)?; + let (m, is_3d) = Self::components_to_transform_3d_matrix_f64(ops, reference_box)?; Ok((cast_3d_transform(m), is_3d)) } /// Same as Transform::to_transform_3d_matrix but a f64 version. - pub fn to_transform_3d_matrix_f64( - &self, + fn components_to_transform_3d_matrix_f64( + ops: &[T], reference_box: Option<&Rect>, ) -> Result<(Transform3D, bool), ()> { - // We intentionally use Transform3D during computation to avoid error propagation - // because using f32 to compute triangle functions (e.g. in rotation()) is not - // accurate enough. In Gecko, we also use "double" to compute the triangle functions. - // Therefore, let's use Transform3D during matrix computation and cast it into f32 - // in the end. + // We intentionally use Transform3D during computation to avoid + // error propagation because using f32 to compute triangle functions + // (e.g. in rotation()) is not accurate enough. In Gecko, we also use + // "double" to compute the triangle functions. Therefore, let's use + // Transform3D during matrix computation and cast it into f32 in + // the end. let mut transform = Transform3D::::identity(); let mut contain_3d = false; - for operation in &*self.0 { + for operation in ops { let matrix = operation.to_3d_matrix(reference_box)?; - contain_3d |= operation.is_3d(); + contain_3d = contain_3d || operation.is_3d(); transform = matrix.then(&transform); } From 01549f03890a743ce551179c761bf51d6e382e7b Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 00:43:42 +0200 Subject: [PATCH 59/81] Further changes required by Servo --- components/style/values/generics/transform.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 1179001ccf8..21c4b8dd95f 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -596,6 +596,13 @@ impl Transform { } /// Same as Transform::to_transform_3d_matrix but a f64 version. + pub fn to_transform_3d_matrix_f64( + &self, + reference_box: Option<&Rect> + ) -> Result<(Transform3D, bool), ()> { + Self::components_to_transform_3d_matrix_f64(&self.0, reference_box) + } + fn components_to_transform_3d_matrix_f64( ops: &[T], reference_box: Option<&Rect>, From df6b6ba67567b7fa2b20d6db48b1fa30d3c83075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:35:47 +0200 Subject: [PATCH 60/81] style: Apply line-height to ::marker Differential Revision: https://phabricator.services.mozilla.com/D136313 --- components/style/properties/data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 740815bcb38..17d398b0aaa 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -889,6 +889,7 @@ class PropertyRestrictions: "unicode-bidi", "direction", "content", + "line-height", "-moz-osx-font-smoothing", ] + PropertyRestrictions.spec(data, "css-fonts") From 0307500c3d1bd9bf7042a78df31188d45c8c1768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:37:35 +0200 Subject: [PATCH 61/81] style: Choose tabpanel background based on content preferred color-scheme If the theme is dark but user prefers light pages, the background of the tabpanel should arguably be light, since it can be seen across some navigations. Expose a -moz-content-prefers-color-scheme media query to chrome pages so that our UI can correctly query it (and remove the unused -moz-proton atom while at it). Differential Revision: https://phabricator.services.mozilla.com/D136437 --- components/style/gecko/media_features.rs | 32 +++++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index c80ace2015b..66722af6c72 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -396,16 +396,31 @@ fn eval_overflow_inline(device: &Device, query_value: Option) -> } } -/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme -fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { +fn do_eval_prefers_color_scheme( + device: &Device, + use_content: bool, + query_value: Option, +) -> bool { let prefers_color_scheme = - unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) }; match query_value { Some(v) => prefers_color_scheme == v, None => true, } } +/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme +fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { + do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value) +} + +fn eval_content_prefers_color_scheme( + device: &Device, + query_value: Option, +) -> bool { + do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value) +} + bitflags! { /// https://drafts.csswg.org/mediaqueries-4/#mf-interaction struct PointerCapabilities: u8 { @@ -652,7 +667,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; 57] = [ +pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ feature!( atom!("width"), AllowsRanges::Yes, @@ -805,6 +820,15 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 57] = [ keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), ParsingRequirements::empty(), ), + // Evaluates to the preferred color scheme for content. Only useful in + // chrome context, where the chrome color-scheme and the content + // color-scheme might differ. + feature!( + atom!("-moz-content-prefers-color-scheme"), + AllowsRanges::No, + keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), + ParsingRequirements::CHROME_AND_UA_ONLY, + ), feature!( atom!("pointer"), AllowsRanges::No, From e4bb1df87826bc386ecf371cff68507933926739 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Fri, 9 Jun 2023 10:45:45 +0200 Subject: [PATCH 62/81] Upgrade darling to 0.13.1 Based on https://phabricator.services.mozilla.com/D136568 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- components/style_derive/Cargo.toml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8b8b2b5e0c..2db2fe600b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,9 +1204,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.10.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -1214,9 +1214,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.10.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", @@ -1227,9 +1227,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.10.2" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -2759,9 +2759,9 @@ dependencies = [ [[package]] name = "ident_case" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9826188e666f2ed92071d2dadef6edc430b11b158b5b2b3f4babbcc891eaaa" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" diff --git a/Cargo.toml b/Cargo.toml index 190f502fe00..62d66812f3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ cookie = "0.12" content-security-policy = { version = "0.5", features = ["serde"]} crossbeam-channel = "0.4" cssparser = "0.29" -darling = { version = "0.10", default-features = false } +darling = { version = "0.13", default-features = false } data-url = "0.1.0" env_logger = "0.8" fnv = "1.0" diff --git a/components/style_derive/Cargo.toml b/components/style_derive/Cargo.toml index d5b71ace70f..8f8a9ab5add 100644 --- a/components/style_derive/Cargo.toml +++ b/components/style_derive/Cargo.toml @@ -11,7 +11,7 @@ path = "lib.rs" proc-macro = true [dependencies] -darling = { version = "0.10", default-features = false } +darling = { workspace = true, default-features = false } derive_common = { path = "../derive_common" } proc-macro2 = "1" quote = "1" From 01c6eb3556e86402992e0512a714ac7094b2beb6 Mon Sep 17 00:00:00 2001 From: Emily McDonough Date: Tue, 6 Jun 2023 23:39:04 +0200 Subject: [PATCH 63/81] style: Implement basic @page-rule selector parsing This does not support any of the pseudo page types. Differential Revision: https://phabricator.services.mozilla.com/D131532 --- components/style/stylesheets/mod.rs | 2 +- components/style/stylesheets/page_rule.rs | 95 +++++++++++++++++++-- components/style/stylesheets/rule_parser.rs | 17 ++-- components/style/stylist.rs | 73 ++++++++++++++-- 4 files changed, 167 insertions(+), 20 deletions(-) diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index f6348c6190d..d48b7504797 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -56,7 +56,7 @@ pub use self::loader::StylesheetLoader; pub use self::media_rule::MediaRule; pub use self::namespace_rule::NamespaceRule; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; -pub use self::page_rule::PageRule; +pub use self::page_rule::{PageRule, PageSelector, PageSelectors}; pub use self::rule_list::{CssRules, CssRulesHelpers}; pub use self::rule_parser::{InsertRuleContext, State, TopLevelRuleParser}; pub use self::rules_iterator::{AllRules, EffectiveRules}; diff --git a/components/style/stylesheets/page_rule.rs b/components/style/stylesheets/page_rule.rs index 3edf562258f..7bf62f5c9e7 100644 --- a/components/style/stylesheets/page_rule.rs +++ b/components/style/stylesheets/page_rule.rs @@ -6,27 +6,100 @@ //! //! [page]: https://drafts.csswg.org/css2/page.html#page-box +use crate::parser::{Parse, ParserContext}; use crate::properties::PropertyDeclarationBlock; use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; -use cssparser::SourceLocation; +use crate::values::{AtomIdent, CustomIdent}; +use style_traits::{CssWriter, ParseError, ToCss}; +use cssparser::{ToCss as CssParserToCss, Parser, SourceLocation}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use servo_arc::Arc; use std::fmt::{self, Write}; +/// Type of a single [`@page`][page selector] +/// +/// We do not support pseudo selectors yet. +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)] +pub struct PageSelector(pub AtomIdent); + +impl PageSelector { + /// Checks if the ident matches a page-name's ident. + /// + /// This does not currently take pseudo selectors into account. + #[inline] + pub fn ident_matches(&self, other: &CustomIdent) -> bool { + self.0.0 == other.0 + } +} + +impl Parse for PageSelector { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let s = input.expect_ident()?; + Ok(PageSelector(AtomIdent::from(&**s))) + } +} + +impl ToCss for PageSelector { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write + { + (&self.0).to_css(dest) + } +} + +/// A list of [`@page`][page selectors] +/// +/// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors +#[derive(Clone, Debug, Default, MallocSizeOf, ToCss, ToShmem)] +#[css(comma)] +pub struct PageSelectors(#[css(iterable)] pub Box<[PageSelector]>); + +impl PageSelectors { + /// Creates a new PageSelectors from a Vec, as from parse_comma_separated + #[inline] + pub fn new(s: Vec) -> Self { + PageSelectors(s.into()) + } + /// Returns true iff there are any page selectors + #[inline] + pub fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + /// Get the underlying PageSelector data as a slice + #[inline] + pub fn as_slice(&self) -> &[PageSelector] { + &*self.0 + } +} + +impl Parse for PageSelectors { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + Ok(PageSelectors::new(input.parse_comma_separated(|i| PageSelector::parse(context, i))?)) + } +} + /// A [`@page`][page] rule. /// /// This implements only a limited subset of the CSS /// 2.2 syntax. /// -/// In this subset, [page selectors][page-selectors] are not implemented. -/// /// [page]: https://drafts.csswg.org/css2/page.html#page-box /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Debug, ToShmem)] +#[derive(Clone, Debug, ToShmem)] pub struct PageRule { + /// Selectors of the page-rule + pub selectors: PageSelectors, /// The declaration block this page rule contains. pub block: Arc>, /// The source position this rule was found at. @@ -38,7 +111,7 @@ impl PageRule { #[cfg(feature = "gecko")] pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { // Measurement of other fields may be added later. - self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) + self.block.unconditional_shallow_size_of(ops) + self.block.read_with(guard).size_of(ops) + self.selectors.size_of(ops) } } @@ -46,13 +119,18 @@ impl ToCssWithGuard for PageRule { /// Serialization of PageRule is not specced, adapted from steps for /// StyleRule. fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { - dest.write_str("@page { ")?; + dest.write_str("@page ")?; + if !self.selectors.is_empty() { + self.selectors.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + } + dest.write_str("{ ")?; let declaration_block = self.block.read_with(guard); declaration_block.to_css(dest)?; if !declaration_block.declarations().is_empty() { - dest.write_str(" ")?; + dest.write_char(' ')?; } - dest.write_str("}") + dest.write_char('}') } } @@ -64,6 +142,7 @@ impl DeepCloneWithLock for PageRule { _params: &DeepCloneParams, ) -> Self { PageRule { + selectors: self.selectors.clone(), block: Arc::new(lock.wrap(self.block.read_with(&guard).clone())), source_location: self.source_location.clone(), } diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 3c0d8a5c231..868137b52cd 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -23,8 +23,8 @@ use crate::stylesheets::stylesheet::Namespaces; use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::{ viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule, - FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, RulesMutateError, - ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, + FontFeatureValuesRule, KeyframesRule, MediaRule, NamespaceRule, PageRule, PageSelectors, + RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule, }; use crate::values::computed::font::FamilyName; use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName}; @@ -168,8 +168,8 @@ pub enum AtRulePrelude { Viewport, /// A @keyframes rule, with its animation name and vendor prefix if exists. Keyframes(KeyframesName, Option), - /// A @page rule prelude. - Page, + /// A @page rule prelude, with its page name if it exists. + Page(PageSelectors), /// A @document rule, with its conditional. Document(DocumentCondition), /// A @import rule prelude. @@ -469,7 +469,11 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { AtRulePrelude::Keyframes(name, prefix) }, "page" if cfg!(feature = "gecko") => { - AtRulePrelude::Page + AtRulePrelude::Page(if static_prefs::pref!("layout.css.named-pages.enabled") { + input.try_parse(|i| PageSelectors::parse(self.context, i)).unwrap_or_default() + } else { + PageSelectors::default() + }) }, "-moz-document" if cfg!(feature = "gecko") => { let cond = DocumentCondition::parse(self.context, input)?; @@ -583,7 +587,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { }, )))) }, - AtRulePrelude::Page => { + AtRulePrelude::Page(selectors) => { let context = ParserContext::new_with_rule_type( self.context, CssRuleType::Page, @@ -592,6 +596,7 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let declarations = parse_property_declaration_list(&context, input, None); Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule { + selectors, block: Arc::new(self.shared_lock.wrap(declarations)), source_location: start.source_location(), })))) diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 14f88c4fe22..dffc2e849ce 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -1612,6 +1612,52 @@ impl LayerOrderedMap { } } +/// Wrapper to allow better tracking of memory usage by page rule lists. +/// +/// This includes the layer ID for use with the named page table. +#[derive(Clone, Debug, MallocSizeOf)] +pub struct PageRuleData { + /// Layer ID for sorting page rules after matching. + pub layer: LayerId, + /// Page rule + #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] + pub rule: Arc>, +} + +/// Wrapper to allow better tracking of memory usage by page rule lists. +/// +/// This is meant to be used by the global page rule list which are already +/// sorted by layer ID, since all global page rules are less specific than all +/// named page rules that match a certain page. +#[derive(Clone, Debug, Deref, MallocSizeOf)] +pub struct PageRuleDataNoLayer( + #[ignore_malloc_size_of = "Arc, stylesheet measures as primary ref"] + pub Arc>, +); + +/// Stores page rules indexed by page names. +#[derive(Clone, Debug, Default, MallocSizeOf)] +pub struct PageRuleMap { + /// Global, unnamed page rules. + pub global: LayerOrderedVec, + /// Named page rules + pub named: PrecomputedHashMap>, +} + +impl PageRuleMap { + #[inline] + fn clear(&mut self) { + self.global.clear(); + self.named.clear(); + } +} + +impl MallocShallowSizeOf for PageRuleMap { + fn shallow_size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + self.global.size_of(ops) + self.named.shallow_size_of(ops) + } +} + /// This struct holds data which users of Stylist may want to extract /// from stylesheets which can be done at the same time as updating. #[derive(Clone, Debug, Default)] @@ -1631,7 +1677,7 @@ pub struct ExtraStyleData { /// A map of effective page rules. #[cfg(feature = "gecko")] - pub pages: LayerOrderedVec>>, + pub pages: PageRuleMap, /// A map of effective scroll-timeline rules. #[cfg(feature = "gecko")] @@ -1666,8 +1712,25 @@ impl ExtraStyleData { } /// Add the given @page rule. - fn add_page(&mut self, rule: &Arc>, layer: LayerId) { - self.pages.push(rule.clone(), layer); + fn add_page( + &mut self, + guard: &SharedRwLockReadGuard, + rule: &Arc>, + layer: LayerId, + ) -> Result<(), AllocErr> { + let page_rule = rule.read_with(guard); + if page_rule.selectors.0.is_empty() { + self.pages.global.push(PageRuleDataNoLayer(rule.clone()), layer); + } else { + // TODO: Handle pseudo-classes + self.pages.named.try_reserve(page_rule.selectors.0.len())?; + for name in page_rule.selectors.as_slice() { + let vec = self.pages.named.entry(name.0.0.clone()).or_default(); + vec.try_reserve(1)?; + vec.push(PageRuleData{layer, rule: rule.clone()}); + } + } + Ok(()) } /// Add the given @scroll-timeline rule. @@ -1685,7 +1748,7 @@ impl ExtraStyleData { self.font_faces.sort(layers); self.font_feature_values.sort(layers); self.counter_styles.sort(layers); - self.pages.sort(layers); + self.pages.global.sort(layers); self.scroll_timelines.sort(layers); } @@ -2518,7 +2581,7 @@ impl CascadeData { }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(rule, current_layer_id); + self.extra_data.add_page(guard, rule, current_layer_id)?; }, CssRule::Viewport(..) => {}, _ => { From f39ab4ffc1b5354a12efdeac7722bd5372673c5c Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 01:12:56 +0200 Subject: [PATCH 64/81] Further changes required by Servo --- components/style/stylesheets/rule_parser.rs | 3 ++- components/style/stylist.rs | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 868137b52cd..9c0095bc32d 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -468,7 +468,8 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> { let name = KeyframesName::parse(self.context, input)?; AtRulePrelude::Keyframes(name, prefix) }, - "page" if cfg!(feature = "gecko") => { + #[cfg(feature = "gecko")] + "page" => { AtRulePrelude::Page(if static_prefs::pref!("layout.css.named-pages.enabled") { input.try_parse(|i| PageSelectors::parse(self.context, i)).unwrap_or_default() } else { diff --git a/components/style/stylist.rs b/components/style/stylist.rs index dffc2e849ce..a1ad4aab659 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -33,19 +33,19 @@ use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; #[cfg(feature = "gecko")] use crate::stylesheets::{ - CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule, ScrollTimelineRule, + CounterStyleRule, FontFaceRule, FontFeatureValuesRule, ScrollTimelineRule, }; use crate::stylesheets::{ - CssRule, EffectiveRulesIterator, Origin, OriginSet, PerOrigin, PerOriginIter, + CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, }; use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument}; use crate::thread_state::{self, ThreadState}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use fxhash::FxHashMap; -use malloc_size_of::MallocSizeOf; +use malloc_size_of::{MallocSizeOf, MallocShallowSizeOf, MallocSizeOfOps}; #[cfg(feature = "gecko")] -use malloc_size_of::{MallocShallowSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use malloc_size_of::MallocUnconditionalShallowSizeOf; use selectors::attr::{CaseSensitivity, NamespaceConstraint}; use selectors::bloom::BloomFilter; use selectors::matching::VisitedHandlingMode; @@ -1644,6 +1644,7 @@ pub struct PageRuleMap { pub named: PrecomputedHashMap>, } +#[cfg(feature = "gecko")] impl PageRuleMap { #[inline] fn clear(&mut self) { From 199f54342cb60f76ec531bfa8048bb9eab83eeb4 Mon Sep 17 00:00:00 2001 From: Emily McDonough Date: Tue, 6 Jun 2023 23:41:27 +0200 Subject: [PATCH 65/81] style: Add style_traits::ToCss for AtomIdent Differential Revision: https://phabricator.services.mozilla.com/D136290 --- components/style/gecko/selector_parser.rs | 2 +- components/style/stylesheets/layer_rule.rs | 2 +- components/style/stylesheets/namespace_rule.rs | 12 ++++++------ components/style/stylesheets/page_rule.rs | 13 ++----------- .../style/stylesheets/scroll_timeline_rule.rs | 1 - components/style/values/mod.rs | 10 ++++++++++ 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 35c5a5c454d..8fc0900aece 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -69,7 +69,7 @@ impl ToCss for NonTSPseudoClass { $(NonTSPseudoClass::$name => concat!(":", $css),)* NonTSPseudoClass::Lang(ref s) => { dest.write_str(":lang(")?; - s.to_css(dest)?; + cssparser::ToCss::to_css(s, dest)?; return dest.write_char(')'); }, NonTSPseudoClass::MozLocaleDir(ref dir) => { diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index 3607e9f2ebf..ebff5bb9add 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -13,7 +13,7 @@ use crate::values::AtomIdent; use super::CssRules; -use cssparser::{Parser, SourceLocation, ToCss as CssParserToCss, Token}; +use cssparser::{Parser, SourceLocation, Token}; use servo_arc::Arc; use smallvec::SmallVec; use std::fmt::{self, Write}; diff --git a/components/style/stylesheets/namespace_rule.rs b/components/style/stylesheets/namespace_rule.rs index d76703e4f81..c0d017ab78f 100644 --- a/components/style/stylesheets/namespace_rule.rs +++ b/components/style/stylesheets/namespace_rule.rs @@ -7,7 +7,7 @@ use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; use crate::{Namespace, Prefix}; -use cssparser::{self, SourceLocation}; +use cssparser::SourceLocation; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; @@ -25,15 +25,15 @@ pub struct NamespaceRule { impl ToCssWithGuard for NamespaceRule { // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSNamespaceRule - fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest_str: &mut CssStringWriter) -> fmt::Result { + let mut dest = CssWriter::new(dest_str); dest.write_str("@namespace ")?; if let Some(ref prefix) = self.prefix { - let prefix = prefix.to_string(); - cssparser::serialize_identifier(&prefix, dest)?; - dest.write_str(" ")?; + prefix.to_css(&mut dest)?; + dest.write_char(' ')?; } dest.write_str("url(")?; - self.url.to_string().to_css(&mut CssWriter::new(dest))?; + self.url.to_string().to_css(&mut dest)?; dest.write_str(");") } } diff --git a/components/style/stylesheets/page_rule.rs b/components/style/stylesheets/page_rule.rs index 7bf62f5c9e7..ee156cf989e 100644 --- a/components/style/stylesheets/page_rule.rs +++ b/components/style/stylesheets/page_rule.rs @@ -13,7 +13,7 @@ use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::str::CssStringWriter; use crate::values::{AtomIdent, CustomIdent}; use style_traits::{CssWriter, ParseError, ToCss}; -use cssparser::{ToCss as CssParserToCss, Parser, SourceLocation}; +use cssparser::{Parser, SourceLocation}; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use servo_arc::Arc; @@ -23,7 +23,7 @@ use std::fmt::{self, Write}; /// /// We do not support pseudo selectors yet. /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors -#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)] +#[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] pub struct PageSelector(pub AtomIdent); impl PageSelector { @@ -46,15 +46,6 @@ impl Parse for PageSelector { } } -impl ToCss for PageSelector { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write - { - (&self.0).to_css(dest) - } -} - /// A list of [`@page`][page selectors] /// /// [page-selectors]: https://drafts.csswg.org/css2/page.html#page-selectors diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index 589814eff20..12b0d2013a6 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -324,7 +324,6 @@ impl ToCss for ScrollTimelineSelector { where W: Write, { - use crate::cssparser::ToCss as CssparserToCss; dest.write_str("selector(")?; dest.write_char('#')?; self.0.to_css(dest)?; diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index ad09320c42b..050efaa8e3e 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -293,6 +293,16 @@ impl cssparser::ToCss for AtomIdent { } } +#[cfg(feature = "gecko")] +impl style_traits::ToCss for AtomIdent { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + cssparser::ToCss::to_css(self, dest) + } +} + #[cfg(feature = "gecko")] impl PrecomputedHash for AtomIdent { #[inline] From 54878595b30c6e9d2fe49946d882db00c55c8e44 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 01:26:37 +0200 Subject: [PATCH 66/81] Further changes required by Servo --- components/style/values/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 050efaa8e3e..9d7b7d74acc 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -232,6 +232,16 @@ impl cssparser::ToCss for GenericAtomIdent style_traits::ToCss for GenericAtomIdent { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + serialize_atom_identifier(&self.0, dest) + } +} + #[cfg(feature = "servo")] impl PrecomputedHash for GenericAtomIdent { #[inline] From d32c5ed85fadd207757f94e04904e2b33c1f443d Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 6 Jun 2023 23:44:05 +0200 Subject: [PATCH 67/81] style: Remove MOZ_DIST As mentioned in bug 1747354, the location of the dist directory is relied to be $topobjdir/dist, so just use that consistently rather than getting it from a separate variable for rust build scripts. Differential Revision: https://phabricator.services.mozilla.com/D136556 --- components/style/build_gecko.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index ddf4a8b71ef..848ea50f8de 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -58,11 +58,11 @@ lazy_static! { }; static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap(); static ref DISTDIR_PATH: PathBuf = { - let path = PathBuf::from(env::var_os("MOZ_DIST").unwrap()); + let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap()); if !path.is_absolute() || !path.is_dir() { - panic!("MOZ_DIST must be an absolute directory, was: {}", path.display()); + panic!("MOZ_TOPOBJDIR must be an absolute directory, was: {}", path.display()); } - path + path.join("dist") }; static ref SEARCH_PATHS: Vec = vec![ DISTDIR_PATH.join("include"), From d3aeba6bd3d0627fac273b449c2ef650cdfc2372 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Tue, 6 Jun 2023 23:48:37 +0200 Subject: [PATCH 68/81] style: Use the mozbuild crate in servo Differential Revision: https://phabricator.services.mozilla.com/D136562 --- components/style/Cargo.toml | 3 ++- components/style/build_gecko.rs | 14 +++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 17af89e9e34..bbb75880b53 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -17,7 +17,7 @@ path = "lib.rs" doctest = false [features] -gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "num_cpus"] +gecko = ["style_traits/gecko", "bindgen", "regex", "toml", "num_cpus", "mozbuild"] servo = ["serde", "style_traits/servo", "servo_atoms", "servo_config", "html5ever", "cssparser/serde", "encoding_rs", "malloc_size_of/servo", "servo_url", "string_cache", "to_shmem/servo", @@ -79,6 +79,7 @@ void = "1.0.2" bindgen = { version = "0.62", optional = true, default-features = false } lazy_static = "1" log = "0.4" +mozbuild = {version = "0.1", optional = true} regex = { version = "1.1", optional = true } toml = { version = "0.5", optional = true, default-features = false } walkdir = "2.1.4" diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 848ea50f8de..9c411b3ddb2 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -42,14 +42,12 @@ fn read_config(path: &PathBuf) -> Table { lazy_static! { static ref CONFIG: Table = { // Load Gecko's binding generator config from the source tree. - let path = PathBuf::from(env::var_os("MOZ_SRC").unwrap()) - .join("layout/style/ServoBindings.toml"); + let path = mozbuild::TOPSRCDIR.join("layout/style/ServoBindings.toml"); read_config(&path) }; static ref BINDGEN_FLAGS: Vec = { // Load build-specific config overrides. - let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap()) - .join("layout/style/extra-bindgen-flags"); + let path = mozbuild::TOPOBJDIR.join("layout/style/extra-bindgen-flags"); println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); fs::read_to_string(path).expect("Failed to read extra-bindgen-flags file") .split_whitespace() @@ -57,13 +55,7 @@ lazy_static! { .collect() }; static ref INCLUDE_RE: Regex = Regex::new(r#"#include\s*"(.+?)""#).unwrap(); - static ref DISTDIR_PATH: PathBuf = { - let path = PathBuf::from(env::var_os("MOZ_TOPOBJDIR").unwrap()); - if !path.is_absolute() || !path.is_dir() { - panic!("MOZ_TOPOBJDIR must be an absolute directory, was: {}", path.display()); - } - path.join("dist") - }; + static ref DISTDIR_PATH: PathBuf = mozbuild::TOPOBJDIR.join("dist"); static ref SEARCH_PATHS: Vec = vec![ DISTDIR_PATH.join("include"), DISTDIR_PATH.join("include/nspr"), From ebfbd2d9c03c7985b46585b1f3369695b163cfc7 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 01:51:07 +0200 Subject: [PATCH 69/81] Further changes required by Servo --- Cargo.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2db2fe600b8..6a5c42150e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3731,6 +3731,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "mozbuild" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903970ae2f248d7275214cf8f387f8ba0c4ea7e3d87a320e85493db60ce28616" + [[package]] name = "mozjs" version = "0.14.1" @@ -5962,6 +5968,7 @@ dependencies = [ "malloc_size_of", "malloc_size_of_derive", "mime", + "mozbuild", "new_debug_unreachable", "num-derive", "num-integer", From 2a677524182bd946905af6d584021185a397f8a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:50:15 +0200 Subject: [PATCH 70/81] style: Style system and plumbing for mix-blend-mode: plus-lighter Differential Revision: https://phabricator.services.mozilla.com/D137951 --- components/style/properties/longhands/background.mako.rs | 1 + components/style/properties/longhands/effects.mako.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/style/properties/longhands/background.mako.rs b/components/style/properties/longhands/background.mako.rs index 1cbf601ad5d..76b71b12cd0 100644 --- a/components/style/properties/longhands/background.mako.rs +++ b/components/style/properties/longhands/background.mako.rs @@ -111,5 +111,6 @@ ${helpers.single_keyword( vector=True, engines="gecko", animation_value_type="discrete", + gecko_inexhaustive=True, spec="https://drafts.fxtf.org/compositing/#background-blend-mode", )} diff --git a/components/style/properties/longhands/effects.mako.rs b/components/style/properties/longhands/effects.mako.rs index 5470c74d47c..1860f4b1791 100644 --- a/components/style/properties/longhands/effects.mako.rs +++ b/components/style/properties/longhands/effects.mako.rs @@ -78,7 +78,7 @@ ${helpers.single_keyword( "mix-blend-mode", """normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue - saturation color luminosity""", + saturation color luminosity plus-lighter""", engines="gecko servo-2013 servo-2020", gecko_enum_prefix="StyleBlend", animation_value_type="discrete", From e2901169c8c291eb826df02b5722c4dfa58925ef Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 01:43:01 +0200 Subject: [PATCH 71/81] Further changes required by Servo --- components/style/properties/longhands/effects.mako.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/style/properties/longhands/effects.mako.rs b/components/style/properties/longhands/effects.mako.rs index 1860f4b1791..7a33a36babc 100644 --- a/components/style/properties/longhands/effects.mako.rs +++ b/components/style/properties/longhands/effects.mako.rs @@ -78,7 +78,7 @@ ${helpers.single_keyword( "mix-blend-mode", """normal multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue - saturation color luminosity plus-lighter""", + saturation color luminosity""" + ("plus-lighter" if engine == 'gecko' else ""), engines="gecko servo-2013 servo-2020", gecko_enum_prefix="StyleBlend", animation_value_type="discrete", From b798336f81a0158821d64d27bae60f4f8453e253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:51:40 +0200 Subject: [PATCH 72/81] style: Generalize moz-os-version into moz-platform Allow differentiating non-windows platforms on it. Differential Revision: https://phabricator.services.mozilla.com/D138431 --- components/style/gecko/media_features.rs | 41 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index 66722af6c72..af57be431f9 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -13,7 +13,6 @@ use crate::media_queries::{Device, MediaType}; use crate::values::computed::CSSPixelLength; use crate::values::computed::Ratio; use crate::values::computed::Resolution; -use crate::Atom; use app_units::Au; use euclid::default::Size2D; @@ -550,20 +549,36 @@ fn eval_moz_is_resource_document( query_value.map_or(is_resource_doc, |v| v == is_resource_doc) } -fn eval_moz_os_version( - device: &Device, - query_value: Option, - _: Option, -) -> bool { +/// Allows front-end CSS to discern platform via media queries. +#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] +#[repr(u8)] +pub enum Platform { + /// Matches any Android version. + Android, + /// For our purposes here, "linux" is just "gtk" (so unix-but-not-mac). + /// There's no need for our front-end code to differentiate between those + /// platforms and they already use the "linux" string elsewhere (e.g., + /// toolkit/themes/linux). + Linux, + /// Matches any macOS version. + Macos, + /// Matches any Windows version. + Windows, + /// Matches only Windows 7. + WindowsWin7, + /// Matches only Windows 8. + WindowsWin8, + /// Matches windows 10 and actually matches windows 11 too, as of right now. + WindowsWin10, +} + +fn eval_moz_platform(_: &Device, query_value: Option) -> bool { let query_value = match query_value { Some(v) => v, None => return false, }; - let os_version = - unsafe { bindings::Gecko_MediaFeatures_GetOperatingSystemVersion(device.document()) }; - - query_value.as_ptr() == os_version + unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } } fn eval_moz_windows_non_native_menus( @@ -576,7 +591,7 @@ fn eval_moz_windows_non_native_menus( 0 => false, 1 => true, _ => { - eval_moz_os_version(device, Some(atom!("windows-win10")), None) && + eval_moz_platform(device, Some(Platform::WindowsWin10)) && get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32) }, }; @@ -869,9 +884,9 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( - atom!("-moz-os-version"), + atom!("-moz-platform"), AllowsRanges::No, - Evaluator::Ident(eval_moz_os_version), + keyword_evaluator!(eval_moz_platform, Platform), ParsingRequirements::CHROME_AND_UA_ONLY, ), feature!( From da81d71ffa236f43dca46739200bdba2c7ec5e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:52:56 +0200 Subject: [PATCH 73/81] style: Make custom-property substitution only traverse variables which could have references If a name is not in self.seen, it means we've inherited it from our parent. That in turn means that it can't have any variable reference (because we inherit the computed variables) and we can skip the work of traversing it, as we'd hit the early-return in traverse() anyways. This doesn't fix the memory usage issue of the page on the bug, which has a giant list of properties on the root and then a custom property specified on all elements, but should significantly reduce the time we spend iterating over custom properties for all those elements. Differential Revision: https://phabricator.services.mozilla.com/D140825 --- components/style/custom_properties.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 07240879438..0b5bcf8842c 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -735,7 +735,7 @@ impl<'a> CustomPropertiesBuilder<'a> { None => return self.inherited.cloned(), }; if self.may_have_cycles { - substitute_all(&mut map, self.device); + substitute_all(&mut map, &self.seen, self.device); } map.shrink_to_fit(); Some(Arc::new(map)) @@ -746,7 +746,7 @@ impl<'a> CustomPropertiesBuilder<'a> { /// (meaning we should use the inherited value). /// /// It does cycle dependencies removal at the same time as substitution. -fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Device) { +fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, seen: &PrecomputedHashSet<&Name>, device: &Device) { // The cycle dependencies removal in this function is a variant // of Tarjan's algorithm. It is mostly based on the pseudo-code // listed in @@ -804,10 +804,10 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Devi /// doesn't have reference at all in specified value, or it has /// been completely resolved. /// * There is no such variable at all. - fn traverse<'a>(name: Name, context: &mut Context<'a>) -> Option { + fn traverse<'a>(name: &Name, context: &mut Context<'a>) -> Option { // Some shortcut checks. let (name, value) = { - let value = context.map.get(&name)?; + let value = context.map.get(name)?; // Nothing to resolve. if value.references.is_empty() { @@ -820,7 +820,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Devi // Whether this variable has been visited in this traversal. let key; - match context.index_map.entry(name) { + match context.index_map.entry(name.clone()) { Entry::Occupied(entry) => { return Some(*entry.get()); }, @@ -848,7 +848,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Devi let mut self_ref = false; let mut lowlink = index; for next in value.references.iter() { - let next_index = match traverse(next.clone(), context) { + let next_index = match traverse(next, context) { Some(index) => index, // There is nothing to do if the next variable has been // fully resolved at this point. @@ -933,10 +933,10 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, device: &Devi None } - // We have to clone the names so that we can mutably borrow the map - // in the context we create for traversal. - let names: Vec<_> = custom_properties_map.keys().cloned().collect(); - for name in names.into_iter() { + // Note that `seen` doesn't contain names inherited from our parent, but + // those can't have variable references (since we inherit the computed + // variables) so we don't want to spend cycles traversing them anyway. + for name in seen { let mut context = Context { count: 0, index_map: PrecomputedHashMap::default(), From 8016c434b01afde931f8de909c8b9f839f8b6dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:54:09 +0200 Subject: [PATCH 74/81] style: Reuse inherited custom properties if they didn't change after resolution This should be cheap and gives us a lot of memory savings for the page on the bug, by deduplicating the inherited properties between parent and children. WebKit implements a similar optimization. Differential Revision: https://phabricator.services.mozilla.com/D140826 --- components/style/custom_properties.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 0b5bcf8842c..7be700ec350 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -723,6 +723,22 @@ impl<'a> CustomPropertiesBuilder<'a> { true } + fn inherited_properties_match(&self, map: &CustomPropertiesMap) -> bool { + let inherited = match self.inherited { + Some(inherited) => inherited, + None => return false, + }; + if inherited.len() != map.len() { + return false; + } + for name in self.seen.iter() { + if inherited.get(*name) != map.get(*name) { + return false; + } + } + true + } + /// Returns the final map of applicable custom properties. /// /// If there was any specified property, we've created a new map and now we @@ -734,9 +750,19 @@ impl<'a> CustomPropertiesBuilder<'a> { Some(m) => m, None => return self.inherited.cloned(), }; + if self.may_have_cycles { substitute_all(&mut map, &self.seen, self.device); } + + // Some pages apply a lot of redundant custom properties, see e.g. + // bug 1758974 comment 5. Try to detect the case where the values + // haven't really changed, and save some memory by reusing the inherited + // map in that case. + if self.inherited_properties_match(&map) { + return self.inherited.cloned(); + } + map.shrink_to_fit(); Some(Arc::new(map)) } From eb96b29af05e520fc8fe19616d01a784b6d70937 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Tue, 6 Jun 2023 23:56:59 +0200 Subject: [PATCH 75/81] style: Add support for parsing the `content-visibility` property from the CSS Contain specification Add initial parsing support for the CSS `content-visibility` attribute. Currently these parsed values have no effect. Differential Revision: https://phabricator.services.mozilla.com/D140834 --- .../properties/counted_unknown_properties.py | 1 - components/style/properties/data.py | 1 + .../style/properties/longhands/box.mako.rs | 10 +++++++ components/style/values/computed/box.rs | 6 ++-- components/style/values/computed/mod.rs | 2 +- components/style/values/specified/box.rs | 28 +++++++++++++++++++ components/style/values/specified/mod.rs | 2 +- 7 files changed, 44 insertions(+), 6 deletions(-) diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index fffac5ff589..473f2f599d2 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -120,5 +120,4 @@ COUNTED_UNKNOWN_PROPERTIES = [ "-webkit-columns", "-webkit-column-rule-color", "-webkit-shape-margin", - "content-visibility", ] diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 17d398b0aaa..eb61992c44b 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -462,6 +462,7 @@ class Longhand(Property): "Clear", "ColumnCount", "Contain", + "ContentVisibility", "Display", "FillRule", "Float", diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 5e67de1565d..33b28375847 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -613,6 +613,16 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-contain/#contain-property", )} +${helpers.predefined_type( + "content-visibility", + "ContentVisibility", + "computed::ContentVisibility::Visible", + engines="gecko", + spec="https://drafts.csswg.org/css-contain/#content-visibility", + gecko_pref="layout.css.content-visibility.enabled", + animation_value_type="none", +)} + ${helpers.predefined_type( "appearance", "Appearance", diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index 2364ac69045..dd1e4900672 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -13,9 +13,9 @@ use crate::values::specified::box_ as specified; pub use crate::values::specified::box_::{ AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, - Clear as SpecifiedClear, Contain, Display, Float as SpecifiedFloat, Overflow, OverflowAnchor, - OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, - ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, + Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, + OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, + ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, }; /// A computed value for the `vertical-align` property. diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 74e78887480..68194eb733a 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -45,7 +45,7 @@ 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, AnimationTimeline, Contain}; -pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, ContentVisibility, Float}; pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 83fa2c8bd51..97024dd4245 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -1450,6 +1450,34 @@ impl Parse for Contain { } } +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Parse, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(u8)] +pub enum ContentVisibility { + /// `auto` variant, the element turns on layout containment, style containment, and paint + /// containment. In addition, if the element is not relevant to the user (such as by being + /// offscreen) it also skips its content + Auto, + /// `hidden` variant, the element skips its content + Hidden, + /// 'visible' variant, no effect + Visible, +} + /// A specified value for the `perspective` property. pub type Perspective = GenericPerspective; diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 099b06a48b3..ac622961642 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -38,7 +38,7 @@ pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; 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_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; From 32dd0b27e4af530e3591bc8df8286a562bee25ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:58:24 +0200 Subject: [PATCH 76/81] style: Honor background-color: transparent in forced colors mode See comment as for why, and linked bugs, in particular: https://bugzilla.mozilla.org/show_bug.cgi?id=1755713#c16 And the following screenshot for example. Differential Revision: https://phabricator.services.mozilla.com/D141514 --- components/style/properties/cascade.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index dfdb2a2b1c8..37d47e1ce61 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -428,17 +428,18 @@ fn tweak_when_ignoring_colors( // otherwise, this is needed to preserve semi-transparent // backgrounds. // - // FIXME(emilio, bug 1666059): We revert for alpha == 0, but maybe - // should consider not doing that even if it causes some issues like - // bug 1625036, or finding a performant way to preserve the original - // widget background color's rgb channels but not alpha... + // NOTE(emilio): We revert even for alpha == 0. Not doing so would + // be a bit special casey, even though it causes issues like + // bug 1625036. The reasoning is that the conditions that trigger + // that (having mismatched widget and default backgrounds) are both + // uncommon, and broken in other applications as well, and not + // honoring transparent makes stuff uglier or break unconditionally + // (bug 1666059, bug 1755713). let alpha = alpha_channel(color, context); - if alpha != 0 { - let mut color = context.builder.device.default_background_color(); - color.alpha = alpha; - declarations_to_apply_unless_overriden - .push(PropertyDeclaration::BackgroundColor(color.into())) - } + let mut color = context.builder.device.default_background_color(); + color.alpha = alpha; + declarations_to_apply_unless_overriden + .push(PropertyDeclaration::BackgroundColor(color.into())) }, PropertyDeclaration::Color(ref color) => { // We honor color: transparent and system colors. From ece2a747090694166625db2428a1e0a82db9b2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 6 Jun 2023 23:59:59 +0200 Subject: [PATCH 77/81] style: Put overflow: -moz-hidden-unscrollable behind a pref on Nightly Differential Revision: https://phabricator.services.mozilla.com/D141759 --- components/style/values/specified/box.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 97024dd4245..e503e8a2f16 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -2083,7 +2083,6 @@ impl BreakWithin { Eq, Hash, MallocSizeOf, - Parse, PartialEq, SpecifiedValueInfo, ToCss, @@ -2098,10 +2097,28 @@ pub enum Overflow { Scroll, Auto, #[cfg(feature = "gecko")] - #[parse(aliases = "-moz-hidden-unscrollable")] Clip, } +// This can be derived once we remove or keep `-moz-hidden-unscrollable` +// indefinitely. +impl Parse for Overflow { + fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + Ok(try_match_ident_ignore_ascii_case! { input, + "visible" => Self::Visible, + "hidden" => Self::Hidden, + "scroll" => Self::Scroll, + "auto" => Self::Auto, + #[cfg(feature = "gecko")] + "clip" => Self::Clip, + #[cfg(feature = "gecko")] + "-moz-hidden-unscrollable" if static_prefs::pref!("layout.css.overflow-moz-hidden-unscrollable.enabled") => { + Overflow::Clip + }, + }) + } +} + impl Overflow { /// Return true if the value will create a scrollable box. #[inline] From 01fb804320b948e19424c2b2fc84c83f6099dc72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 7 Jun 2023 00:03:23 +0200 Subject: [PATCH 78/81] style: Use atomic ops to read / write node flags from stylo The flags stylo cares about reading and writing potentially at the same time are disjoint, so there's no need for any strong memory ordering. Differential Revision: https://phabricator.services.mozilla.com/D141829 --- components/style/gecko/wrapper.rs | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index d89cac962ee..875700fdf42 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -76,6 +76,7 @@ use selectors::matching::{ElementSelectorFlags, MatchingContext}; use selectors::sink::Push; use selectors::{Element, OpaqueElement}; use servo_arc::{Arc, ArcBorrow, RawOffsetArc}; +use std::sync::atomic::{AtomicU32, Ordering}; use std::fmt; use std::hash::{Hash, Hasher}; use std::mem; @@ -265,9 +266,29 @@ impl<'ln> GeckoNode<'ln> { GeckoNode(&content._base) } + #[inline] + fn flags_atomic(&self) -> &AtomicU32 { + use std::cell::Cell; + let flags: &Cell = &(self.0)._base._base_1.mFlags; + + #[allow(dead_code)] + fn static_assert() { + let _: [u8; std::mem::size_of::>()] = [0u8; std::mem::size_of::()]; + let _: [u8; std::mem::align_of::>()] = [0u8; std::mem::align_of::()]; + } + + // Rust doesn't provide standalone atomic functions like GCC/clang do + // (via the atomic intrinsics) or via std::atomic_ref, but it guarantees + // that the memory representation of u32 and AtomicU32 matches: + // https://doc.rust-lang.org/std/sync/atomic/struct.AtomicU32.html + unsafe { + std::mem::transmute::<&Cell, &AtomicU32>(flags) + } + } + #[inline] fn flags(&self) -> u32 { - (self.0)._base._base_1.mFlags + self.flags_atomic().load(Ordering::Relaxed) } #[inline] @@ -648,18 +669,14 @@ impl<'le> GeckoElement<'le> { self.as_node().flags() } - // FIXME: We can implement this without OOL calls, but we can't easily given - // GeckoNode is a raw reference. - // - // We can use a Cell, but that's a bit of a pain. #[inline] fn set_flags(&self, flags: u32) { - unsafe { Gecko_SetNodeFlags(self.as_node().0, flags) } + self.as_node().flags_atomic().fetch_or(flags, Ordering::Relaxed); } #[inline] unsafe fn unset_flags(&self, flags: u32) { - Gecko_UnsetNodeFlags(self.as_node().0, flags) + self.as_node().flags_atomic().fetch_and(!flags, Ordering::Relaxed); } /// Returns true if this element has descendants for lazy frame construction. From e03261c433a990ce6505d1b1afe77f6cc439e01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 7 Jun 2023 00:04:40 +0200 Subject: [PATCH 79/81] style: Remove some no longer used use statements MANUAL PUSH: Bustage fix CLOSED TREE --- components/style/gecko/wrapper.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 875700fdf42..ca2c3816ec9 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -40,7 +40,6 @@ use crate::gecko_bindings::bindings::Gecko_IsSignificantChild; use crate::gecko_bindings::bindings::Gecko_MatchLang; use crate::gecko_bindings::bindings::Gecko_UnsetDirtyStyleAttr; use crate::gecko_bindings::bindings::Gecko_UpdateAnimations; -use crate::gecko_bindings::bindings::{Gecko_SetNodeFlags, Gecko_UnsetNodeFlags}; use crate::gecko_bindings::structs; use crate::gecko_bindings::structs::nsChangeHint; use crate::gecko_bindings::structs::EffectCompositor_CascadeLevel as CascadeLevel; From c4dbec650a394fc1d0393c9ee8b9887ee3ba19a6 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 02:06:17 +0200 Subject: [PATCH 80/81] Avoid complaints from ./mach test-tidy --- servo-tidy.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/servo-tidy.toml b/servo-tidy.toml index 17b924c0dc0..da0e7e572a8 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -80,16 +80,21 @@ files = [ "./components/net/tests/parsable_mime/text", # These are ignored to avoid diverging from Gecko "./components/style/counter_style/mod.rs", + "./components/style/properties/declaration_block.rs", "./components/style/properties/helpers.mako.rs", "./components/style/rule_collector.rs", "./components/style/selector_map.rs", "./components/style/stylesheets/import_rule.rs", "./components/style/stylesheets/layer_rule.rs", + "./components/style/stylesheets/origin.rs", + "./components/style/stylesheets/page_rule.rs", "./components/style/stylesheets/rule_parser.rs", "./components/style/stylesheets/scroll_timeline_rule.rs", "./components/style/stylist.rs", + "./components/style/values/animated/transform.rs", "./components/style/values/computed/font.rs", "./components/style/values/computed/image.rs", + "./components/style/values/specified/box.rs", "./components/style/values/specified/color.rs", "./components/style/values/specified/transform.rs", # Mako does not lend itself easily to splitting long lines From aefaa3f16cd3d8e2df7399f0d10022cd2cebd5a0 Mon Sep 17 00:00:00 2001 From: Oriol Brufau Date: Wed, 7 Jun 2023 17:35:14 +0200 Subject: [PATCH 81/81] Update test expectations --- .../transform-interpolation-003.html.ini | 55 ----- .../transform-interpolation-004.html.ini | 199 ------------------ ...substitution-variable-declaration.html.ini | 3 - ...les-substitute-guaranteed-invalid.html.ini | 6 - .../transform-interpolation-003.html.ini | 55 ----- .../css/css-values/ic-unit-001.html.ini | 2 - .../css/css-values/ic-unit-008.html.ini | 2 - ...substitution-variable-declaration.html.ini | 3 - ...les-substitute-guaranteed-invalid.html.ini | 6 - 9 files changed, 331 deletions(-) delete mode 100644 tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini delete mode 100644 tests/wpt/metadata/css/css-values/ic-unit-001.html.ini delete mode 100644 tests/wpt/metadata/css/css-values/ic-unit-008.html.ini delete mode 100644 tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini delete mode 100644 tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini diff --git a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini index 46da28061ea..b035eabec54 100644 --- a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-003.html.ini @@ -125,66 +125,11 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%) scaleZ(1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - diff --git a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini index 0050674ab78..03d48da8895 100644 --- a/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-transforms/animation/transform-interpolation-004.html.ini @@ -215,222 +215,63 @@ [Web Animations: property from [skewX(10rad) translateY(70%)\] to [skewX(20rad) translateY(90%)\] at (1) should be [skewX(20rad) translateY(90%)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.25) should be [translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (-1) should be [translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (-1) should be [translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (-1) should be [translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0) should be [matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (2) should be [matrix3d(1, 0, 0, 0, -11.227342763749263, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, -0.0014653608247422677, -8, 4, -12, 0.9861443298969074)\]] - expected: FAIL - [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 400, 600, 1)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.25) should be [translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0) should be [translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 1.1186572632293585, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.002378247422680413, 6, -3, 9, 1.0012989690721648)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (2) should be [translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -0.7525665307288518, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.002128247422680412, 2, -1, 3, 1.001298969072165)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 400, 600, 1)\]] expected: FAIL [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (1) should be [matrix(1, 0, 0, 1, 0, 0) \]] expected: FAIL - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.25) should be [translate3d(7px, -6px, 11px) skewX(1.25rad) matrix3d(1, 0, 0, 0, 0, 1.25, 0, 0, 0, 0, 1, -0.001875, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.0029653608247422686, 16, -8, 24, 0.986144329896907)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.75) should be [translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0.621795827675797, 1, 0, 0, 0, 0, 1, 0, 2, -1, 3, 1)\]] - expected: FAIL - [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 75, 150, 225, 1)\]] expected: FAIL [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0) should be [matrix(1, 0, 1.5574077246549023, 1, 0, 0)\]] expected: FAIL - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (2) should be [matrix3d(1, 0, 0, 0, -11.227342763749263, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, -0.0014653608247422677, -8, 4, -12, 0.9861443298969074)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.0029653608247422686, 16, -8, 24, 0.986144329896907)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (1) should be [translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.75) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 25, 50, 75, 1)\]] expected: FAIL [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0) should be [matrix(1, 0, 1.5574077246549023, 1, 0, 0)\]] expected: FAIL - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 1, 0, 0, 0, 0, 1, 0, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (2) should be [translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0) should be [translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -1.2494279662824135, 1, 0, 0, 0, 0, 1, 0, 6, -3, 9, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (2) should be [matrix3d(1, 0, 0, 0, -5.9274874511779405, 1, 0, 0, 0, 0, 1, 0, 16, -8, 24, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (1) should be [matrix(1, 0, 0, 1, 0, 0) \]] expected: FAIL [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (-1) should be [translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0) should be [matrix(1, 0, 1.5574077246549023, 1, 0, 0)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (2) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -200, -300, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0) should be [translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (1) should be [matrix(1, 0, 0, 1, 0, 0) \]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0) should be [matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (2) should be [translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1)\]] expected: FAIL [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (1) should be [translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)\]] expected: FAIL - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 1.1186572632293585, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.002378247422680413, 6, -3, 9, 1.0012989690721648)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0.621795827675797, 1, 0, 0, 0, 0, 1, 0, 2, -1, 3, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (2) should be [translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (-1) should be [translate3d(12px, 4px, 16px) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -0.7525665307288518, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.002128247422680412, 2, -1, 3, 1.001298969072165)\]] - expected: FAIL - - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 1, 0, 0, 0, 0, 1, 0, 8, -4, 12, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.75) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 25, 50, 75, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 1, 0, 0, 0, 0, 1, 0, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (1) should be [translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.25) should be [translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (-1) should be [matrix3d(1, 0, 0, 0, 5.2998553125713235, 1, 0, 0, 0, 0, 1, 0, -8, 4, -12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.75) should be [translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.25) should be [translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 75, 150, 225, 1)\]] expected: FAIL [CSS Animations: property from [translate3D(100px, 200px, 300px)\] to [none\] at (2) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -200, -300, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.75) should be [translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (2) should be [matrix3d(1, 0, 0, 0, -11.227342763749263, 3, 0, 0, 0.021237113402061854, -0.010618556701030927, 1.03, -0.0014653608247422677, -8, 4, -12, 0.9861443298969074)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0) should be [translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (2) should be [matrix3d(1, 0, 0, 0, -5.9274874511779405, 1, 0, 0, 0, 0, 1, 0, 16, -8, 24, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.75) should be [translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0) should be [translate3d(8px, -4px, 12px) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.75) should be [translate3d(5px, -10px, 9px) matrix3d(1, 0, 0, 0, 0.681366, 1.75, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 1.1186572632293585, 1.25, 0, 0, -0.0151159793814433, 0.00755798969072165, 0.9775, -0.002378247422680413, 6, -3, 9, 1.0012989690721648)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (1) should be [translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0) should be [matrix3d(1, 0, 0, 0, 1.5574077246549023, 1, 0, 0, -0.02, 0.01, 0.97, -0.0025, 8, -4, 12, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0) should be [translate3d(8px, -4px, 12px) skewX(1rad) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 200, 400, 600, 1)\]] expected: FAIL - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -1.2494279662824135, 1, 0, 0, 0, 0, 1, 0, 6, -3, 9, 1)\]] - expected: FAIL - - [CSS Animations: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -1.2494279662824135, 1, 0, 0, 0, 0, 1, 0, 6, -3, 9, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (1) should be [translate3d(4px, -12px, 8px) skewX(2rad) matrix(1, 0, 0, 2, 0, 0)\]] expected: FAIL @@ -440,48 +281,8 @@ [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (2) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -100, -200, -300, 1)\]] expected: FAIL - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0.621795827675797, 1, 0, 0, 0, 0, 1, 0, 2, -1, 3, 1)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (0.25) should be [translate3d(7px, -6px, 11px) matrix3d(1, 0, 0, 0, 1.46007, 1.25, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (2) should be [translate3d(0px, -20px, 4px) skewX(3rad) matrix3d(1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1, 0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (2) should be [translate3d(0px, -20px, 4px) matrix3d(1, 0, 0, 0, -4.67222, 3, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (-1) should be [translate3d(12px, 4px, 16px) skewX(0rad) matrix3d(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -0.005, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (2) should be [matrix3d(1, 0, 0, 0, -5.9274874511779405, 1, 0, 0, 0, 0, 1, 0, 16, -8, 24, 1)\]] - expected: FAIL - - [CSS Transitions: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (-1) should be [matrix3d(1, 0, 0, 0, 5.2998553125713235, 1, 0, 0, 0, 0, 1, 0, -8, 4, -12, 1)\]] - expected: FAIL - [CSS Transitions: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0.25) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 75, 150, 225, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (0.75) should be [matrix3d(1, 0, 0, 0, -0.7525665307288518, 1.75, 0, 0, -0.005115979381443298, 0.002557989690721649, 0.9924999999999999, -0.002128247422680412, 2, -1, 3, 1.001298969072165)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (-1) should be [matrix3d(1, 0, 0, 0, 0, 0, 0, 0, -0.03876288659793814, 0.01938144329896907, 0.94, -0.0029653608247422686, 16, -8, 24, 0.986144329896907)\]] - expected: FAIL - - [CSS Animations: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) scaleY(2) perspective(500px)\] at (1) should be [translate3d(4px, -12px, 8px) matrix3d(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [translate3d(4px, -12px, 8px) skewX(2rad) scaleY(2)\] at (0.75) should be [translate3d(5px, -10px, 9px) skewX(1.75rad) matrix3d(1, 0, 0, 0, 0, 1.75, 0, 0, 0, 0, 1, -0.000625, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions with transition: all: property from [translate3D(100px, 200px, 300px)\] to [none\] at (0) should be [matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 100, 200, 300, 1)\]] expected: FAIL - - [CSS Transitions with transition: all: property from [translate3d(8px, -4px, 12px) skewX(1rad) perspective(400px)\] to [scaleY(2) skewX(2rad) perspective(500px)\] at (1) should be [matrix3d(1, 0, 0, 0, -2.185039863261519, 2, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions with transition: all: property from [skewX(1rad)\] to [translate3d(8px, -4px, 12px) skewX(2rad)\] at (-1) should be [matrix3d(1, 0, 0, 0, 5.2998553125713235, 1, 0, 0, 0, 0, 1, 0, -8, 4, -12, 1)\]] - expected: FAIL - diff --git a/tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini b/tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini deleted file mode 100644 index 60c36462bdc..00000000000 --- a/tests/wpt/metadata-layout-2020/css/css-variables/variable-substitution-variable-declaration.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[variable-substitution-variable-declaration.html] - [target10 --varC] - expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini b/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini index 46514359b87..421bd4d7f80 100644 --- a/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-variables/variables-substitute-guaranteed-invalid.html.ini @@ -7,9 +7,3 @@ [A custom property referencing a non-existent variable is treated as unset] expected: FAIL - - [A custom property referencing a cycle becomes guaranteed-invalid] - expected: FAIL - - [A custom property referencing a non-existent variable becomes guaranteed-invalid] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini b/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini index ff51a0717b9..5c0ce17c25c 100644 --- a/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini +++ b/tests/wpt/metadata/css/css-transforms/animation/transform-interpolation-003.html.ini @@ -8,36 +8,24 @@ [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (0) should be [skewX(10rad) scaleZ(1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad) scaleZ(2)\] at (0.75) should be [skewX(17.5rad) scaleZ(1.75)\]] expected: FAIL [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (-1) should be [skewX(0rad) scaleZ(0)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (1) should be [translateY(90%) scaleZ(2)\]] expected: FAIL [Web Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (-1) should be [translateY(50%) scaleZ(0)\]] expected: FAIL - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewY(10rad)\] to [skewY(20rad)\] at (0.25) should be [skewY(12.5rad)\]] expected: FAIL [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.75) should be [scaleZ(3.75) matrix3d(1, 0, 0, 0, 1.16806, 1, 0, 0, 0, 0, 1, -0.002125, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Transitions: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL @@ -50,18 +38,9 @@ [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (0.25) should be [skewX(12.5rad) scaleZ(1.25)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad)\] at (2) should be [skewX(30rad)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] - expected: FAIL - [CSS Animations: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL @@ -71,30 +50,15 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (-1) should be [translateY(50%) scaleZ(0)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] expected: FAIL [CSS Transitions with transition: all: property from [translateY(70%)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad)\] at (1) should be [skewX(20rad)\]] expected: FAIL @@ -104,9 +68,6 @@ [Web Animations: property from [skewX(10rad)\] to [skewX(20rad)\] at (0) should be [skewX(10rad)\]] expected: FAIL - [CSS Transitions with transition: all: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewY(10rad)\] to [skewY(20rad)\] at (0.75) should be [skewY(17.5rad)\]] expected: FAIL @@ -116,9 +77,6 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (0.75) should be [translateY(85%) scaleZ(1.75)\]] expected: FAIL - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad)\] to [skewX(20rad) scaleZ(2)\] at (0.25) should be [skewX(12.5rad) scaleZ(1.25)\]] expected: FAIL @@ -134,9 +92,6 @@ [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (1) should be [scaleZ(4) matrix3d(1, 0, 0, 0, 1.55741, 1, 0, 0, 0, 0, 1, -0.002, 0, 0, 0, 1)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (2) should be [scaleZ(5) matrix3d(1, 0, 0, 0, 3.11482, 1, 0, 0, 0, 0, 1, -0.0015, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] expected: FAIL @@ -161,12 +116,6 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (1) should be [translateY(90%) scaleZ(2)\]] expected: FAIL - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0) should be [scaleZ(3) matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -0.0025, 0, 0, 0, 1)\]] - expected: FAIL - - [CSS Transitions: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (0.25) should be [scaleZ(3.25) matrix3d(1, 0, 0, 0, 0.389352, 1, 0, 0, 0, 0, 1, -0.002375, 0, 0, 0, 1)\]] - expected: FAIL - [Web Animations: property from [skewX(10rad) scaleZ(1)\] to [skewX(20rad) scaleZ(2)\] at (0.75) should be [skewX(17.5rad) scaleZ(1.75)\]] expected: FAIL @@ -187,7 +136,3 @@ [Web Animations: property from [translateY(70%) scaleZ(1)\] to [translateY(90%) scaleZ(2)\] at (0) should be [translateY(70%) scaleZ(1)\]] expected: FAIL - - [CSS Animations: property from [scaleZ(3) perspective(400px)\] to [scaleZ(4) skewX(1rad) perspective(500px)\] at (-1) should be [scaleZ(2) matrix3d(1, 0, 0, 0, -1.55741, 1, 0, 0, 0, 0, 1, -0.003, 0, 0, 0, 1)\]] - expected: FAIL - diff --git a/tests/wpt/metadata/css/css-values/ic-unit-001.html.ini b/tests/wpt/metadata/css/css-values/ic-unit-001.html.ini deleted file mode 100644 index c2cf2787e35..00000000000 --- a/tests/wpt/metadata/css/css-values/ic-unit-001.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[ic-unit-001.html] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-values/ic-unit-008.html.ini b/tests/wpt/metadata/css/css-values/ic-unit-008.html.ini deleted file mode 100644 index 027af6d19c9..00000000000 --- a/tests/wpt/metadata/css/css-values/ic-unit-008.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[ic-unit-008.html] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini b/tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini deleted file mode 100644 index 60c36462bdc..00000000000 --- a/tests/wpt/metadata/css/css-variables/variable-substitution-variable-declaration.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[variable-substitution-variable-declaration.html] - [target10 --varC] - expected: FAIL diff --git a/tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini b/tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini deleted file mode 100644 index aa840ce33fb..00000000000 --- a/tests/wpt/metadata/css/css-variables/variables-substitute-guaranteed-invalid.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[variables-substitute-guaranteed-invalid.html] - [A custom property referencing a cycle becomes guaranteed-invalid] - expected: FAIL - - [A custom property referencing a non-existent variable becomes guaranteed-invalid] - expected: FAIL