diff --git a/dom/webidl/CSSScopeRule.webidl b/dom/webidl/CSSScopeRule.webidl new file mode 100644 index 000000000000..009209beaf5b --- /dev/null +++ b/dom/webidl/CSSScopeRule.webidl @@ -0,0 +1,14 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://drafts.csswg.org/css-cascade-6/#the-cssscoperule-interface + */ + +[Exposed=Window, Pref="layout.css.at-scope.enabled"] +interface CSSScopeRule : CSSGroupingRule { + readonly attribute UTF8String? start; + readonly attribute UTF8String? end; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 3880b727e731..ae65ab1b0060 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -492,6 +492,7 @@ WEBIDL_FILES = [ "CSSPseudoElement.webidl", "CSSRule.webidl", "CSSRuleList.webidl", + "CSSScopeRule.webidl", "CSSStyleDeclaration.webidl", "CSSStyleRule.webidl", "CSSStyleSheet.webidl", diff --git a/layout/inspector/ServoStyleRuleMap.cpp b/layout/inspector/ServoStyleRuleMap.cpp index b67d0c98fc17..e47855ff1a97 100644 --- a/layout/inspector/ServoStyleRuleMap.cpp +++ b/layout/inspector/ServoStyleRuleMap.cpp @@ -86,7 +86,8 @@ void ServoStyleRuleMap::RuleRemoved(StyleSheet& aStyleSheet, case StyleCssRuleType::Supports: case StyleCssRuleType::LayerBlock: case StyleCssRuleType::Container: - case StyleCssRuleType::Document: { + case StyleCssRuleType::Document: + case StyleCssRuleType::Scope: { // See the comment in SheetRemoved. mTable.Clear(); break; @@ -124,7 +125,8 @@ void ServoStyleRuleMap::FillTableFromRule(css::Rule& aRule) { case StyleCssRuleType::Media: case StyleCssRuleType::Supports: case StyleCssRuleType::Container: - case StyleCssRuleType::Document: { + case StyleCssRuleType::Document: + case StyleCssRuleType::Scope: { auto& rule = static_cast(aRule); FillTableFromRuleList(*rule.CssRules()); break; diff --git a/layout/style/CSSScopeRule.cpp b/layout/style/CSSScopeRule.cpp new file mode 100644 index 000000000000..18047a582b67 --- /dev/null +++ b/layout/style/CSSScopeRule.cpp @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/CSSScopeRule.h" +#include "mozilla/dom/CSSScopeRuleBinding.h" +#include "mozilla/ServoBindings.h" + +namespace mozilla::dom { + +CSSScopeRule::CSSScopeRule(RefPtr aRawRule, StyleSheet* aSheet, + css::Rule* aParentRule, uint32_t aLine, + uint32_t aColumn) + : css::GroupRule(aSheet, aParentRule, aLine, aColumn), + mRawRule(std::move(aRawRule)) {} + +NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(CSSScopeRule, css::GroupRule) + +// QueryInterface implementation for SupportsRule + +#ifdef DEBUG +void CSSScopeRule::List(FILE* out, int32_t aIndent) const { + nsAutoCString str; + for (int32_t i = 0; i < aIndent; i++) { + str.AppendLiteral(" "); + } + Servo_ScopeRule_Debug(mRawRule, &str); + fprintf_stderr(out, "%s\n", str.get()); +} +#endif + +StyleCssRuleType CSSScopeRule::Type() const { return StyleCssRuleType::Scope; } + +already_AddRefed CSSScopeRule::GetOrCreateRawRules() { + return Servo_ScopeRule_GetRules(mRawRule).Consume(); +} + +void CSSScopeRule::SetRawAfterClone(RefPtr aRaw) { + mRawRule = std::move(aRaw); + css::GroupRule::DidSetRawAfterClone(); +} + +void CSSScopeRule::GetCssText(nsACString& aCssText) const { + Servo_ScopeRule_GetCssText(mRawRule.get(), &aCssText); +} + +void CSSScopeRule::GetStart(nsACString& aStart) const { + Servo_ScopeRule_GetStart(mRawRule.get(), &aStart); +} + +void CSSScopeRule::GetEnd(nsACString& aEnd) const { + Servo_ScopeRule_GetEnd(mRawRule.get(), &aEnd); +} + +size_t CSSScopeRule::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this); +} + +JSObject* CSSScopeRule::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) { + return CSSScopeRule_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/layout/style/CSSScopeRule.h b/layout/style/CSSScopeRule.h new file mode 100644 index 000000000000..2cb1fa7e4057 --- /dev/null +++ b/layout/style/CSSScopeRule.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef CSSScopeRule_h___ +#define CSSScopeRule_h___ + +#include "mozilla/css/GroupRule.h" +#include "mozilla/ServoBindingTypes.h" + +namespace mozilla::dom { + +class CSSScopeRule final : public css::GroupRule { + public: + CSSScopeRule(RefPtr aRawRule, StyleSheet* aSheet, + css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn); + + NS_DECL_ISUPPORTS_INHERITED + +#ifdef DEBUG + void List(FILE* out = stdout, int32_t aIndent = 0) const final; +#endif + + StyleScopeRule* Raw() const { return mRawRule; } + void SetRawAfterClone(RefPtr); + + already_AddRefed GetOrCreateRawRules() final; + + // WebIDL interface + StyleCssRuleType Type() const final; + void GetCssText(nsACString& aCssText) const final; + + void GetStart(nsACString& aStart) const; + void GetEnd(nsACString& aEnd) const; + + size_t SizeOfIncludingThis(MallocSizeOf) const override; + JSObject* WrapObject(JSContext*, JS::Handle) override; + + private: + ~CSSScopeRule() = default; + + RefPtr mRawRule; +}; + +} // namespace mozilla::dom + +#endif diff --git a/layout/style/Rule.cpp b/layout/style/Rule.cpp index 0a7de4278905..1bba782d6503 100644 --- a/layout/style/Rule.cpp +++ b/layout/style/Rule.cpp @@ -100,13 +100,13 @@ void Rule::AssertParentRuleType() { // this->Type() here since it's virtual. if (mParentRule) { auto type = mParentRule->Type(); - MOZ_ASSERT(type == StyleCssRuleType::Media || - type == StyleCssRuleType::Style || - type == StyleCssRuleType::Document || - type == StyleCssRuleType::Supports || - type == StyleCssRuleType::Keyframes || - type == StyleCssRuleType::LayerBlock || - type == StyleCssRuleType::Container); + MOZ_ASSERT( + type == StyleCssRuleType::Media || type == StyleCssRuleType::Style || + type == StyleCssRuleType::Document || + type == StyleCssRuleType::Supports || + type == StyleCssRuleType::Keyframes || + type == StyleCssRuleType::LayerBlock || + type == StyleCssRuleType::Container || type == StyleCssRuleType::Scope); } } #endif diff --git a/layout/style/ServoBindingTypes.h b/layout/style/ServoBindingTypes.h index fe4f96cf0b5b..196466be1d60 100644 --- a/layout/style/ServoBindingTypes.h +++ b/layout/style/ServoBindingTypes.h @@ -129,6 +129,7 @@ UNLOCKED_RULE_TYPE(Supports) UNLOCKED_RULE_TYPE(Document) UNLOCKED_RULE_TYPE(FontFeatureValues) UNLOCKED_RULE_TYPE(FontPaletteValues) +UNLOCKED_RULE_TYPE(Scope) SERVO_ARC_TYPE(AnimationValue, mozilla::StyleAnimationValue) SERVO_ARC_TYPE(ComputedStyle, mozilla::ComputedStyle) diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h index 6f1900651299..44dfd544236b 100644 --- a/layout/style/ServoBindings.h +++ b/layout/style/ServoBindings.h @@ -87,6 +87,7 @@ BASIC_RULE_FUNCS_UNLOCKED(FontPaletteValues) BASIC_RULE_FUNCS_LOCKED(FontFace) BASIC_RULE_FUNCS_LOCKED(CounterStyle) GROUP_RULE_FUNCS_UNLOCKED(Container) +GROUP_RULE_FUNCS_UNLOCKED(Scope) #undef GROUP_RULE_FUNCS_LOCKED #undef GROUP_RULE_FUNCS_UNLOCKED diff --git a/layout/style/ServoCSSRuleList.cpp b/layout/style/ServoCSSRuleList.cpp index 6fcdfdd4b57a..e26305c9932a 100644 --- a/layout/style/ServoCSSRuleList.cpp +++ b/layout/style/ServoCSSRuleList.cpp @@ -24,6 +24,7 @@ #include "mozilla/dom/CSSPropertyRule.h" #include "mozilla/dom/CSSStyleRule.h" #include "mozilla/dom/CSSSupportsRule.h" +#include "mozilla/dom/CSSScopeRule.h" #include "mozilla/IntegerRange.h" #include "mozilla/ServoBindings.h" #include "mozilla/StyleSheet.h" @@ -98,6 +99,7 @@ css::Rule* ServoCSSRuleList::GetRule(uint32_t aIndex) { CASE_RULE_UNLOCKED(LayerBlock, LayerBlock) CASE_RULE_UNLOCKED(LayerStatement, LayerStatement) CASE_RULE_UNLOCKED(Container, Container) + CASE_RULE_UNLOCKED(Scope, Scope) #undef CASE_RULE_LOCKED #undef CASE_RULE_UNLOCKED #undef CASE_RULE_WITH_PREFIX @@ -276,6 +278,7 @@ void ServoCSSRuleList::SetRawContents(RefPtr aNewRules, RULE_CASE_UNLOCKED(LayerBlock, LayerBlock) RULE_CASE_UNLOCKED(LayerStatement, LayerStatement) RULE_CASE_UNLOCKED(Container, Container) + RULE_CASE_UNLOCKED(Scope, Scope) case StyleCssRuleType::Keyframe: MOZ_ASSERT_UNREACHABLE("keyframe rule cannot be here"); break; diff --git a/layout/style/ServoStyleConstsInlines.h b/layout/style/ServoStyleConstsInlines.h index 1985e8fbeb56..65521767d543 100644 --- a/layout/style/ServoStyleConstsInlines.h +++ b/layout/style/ServoStyleConstsInlines.h @@ -53,6 +53,7 @@ template struct StyleStrong; template struct StyleStrong; template struct StyleStrong; template struct StyleStrong; +template struct StyleStrong; template inline void StyleOwnedSlice::Clear() { diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp index cfc38849b811..87d16ec9346f 100644 --- a/layout/style/ServoStyleSet.cpp +++ b/layout/style/ServoStyleSet.cpp @@ -41,6 +41,7 @@ #include "mozilla/dom/CSSNamespaceRule.h" #include "mozilla/dom/CSSPageRule.h" #include "mozilla/dom/CSSPropertyRule.h" +#include "mozilla/dom/CSSScopeRule.h" #include "mozilla/dom/CSSSupportsRule.h" #include "mozilla/dom/FontFaceSet.h" #include "mozilla/dom/Element.h" @@ -1002,6 +1003,7 @@ void ServoStyleSet::RuleChangedInternal(StyleSheet& aSheet, css::Rule& aRule, CASE_FOR(LayerBlock, LayerBlock) CASE_FOR(LayerStatement, LayerStatement) CASE_FOR(Container, Container) + CASE_FOR(Scope, Scope) // @namespace can only be inserted / removed when there are only other // @namespace and @import rules, and can't be mutated. case StyleCssRuleType::Namespace: diff --git a/layout/style/moz.build b/layout/style/moz.build index a14ab6a7ac8e..36e63fe87e49 100644 --- a/layout/style/moz.build +++ b/layout/style/moz.build @@ -143,6 +143,7 @@ EXPORTS.mozilla.dom += [ "CSSPageRule.h", "CSSPropertyRule.h", "CSSRuleList.h", + "CSSScopeRule.h", "CSSStyleRule.h", "CSSSupportsRule.h", "CSSValue.h", @@ -194,6 +195,7 @@ UNIFIED_SOURCES += [ "CSSPageRule.cpp", "CSSPropertyRule.cpp", "CSSRuleList.cpp", + "CSSScopeRule.cpp", "CSSStyleRule.cpp", "CSSSupportsRule.cpp", "DeclarationBlock.cpp", diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index d4a709f90606..620b5014d6e5 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -8740,6 +8740,13 @@ mirror: always rust: true +# Whether @scope rule is enabled +- name: layout.css.at-scope.enabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + # Dictates whether or not the prefers contrast media query will be # usable. # true: prefers-contrast will toggle based on OS and browser settings. diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs index 9b0acb0671d7..ca3a5c76f4f2 100644 --- a/servo/components/selectors/parser.rs +++ b/servo/components/selectors/parser.rs @@ -469,6 +469,23 @@ impl SelectorList { ) } + pub fn parse_forgiving<'i, 't, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + parse_relative: ParseRelative, + ) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + Self::parse_with_state( + parser, + input, + SelectorParsingState::empty(), + ForgivingParsing::Yes, + parse_relative, + ) + } + #[inline] fn parse_with_state<'i, 't, P>( parser: &P, diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs index 24bf22d69a72..e4300be6e46c 100644 --- a/servo/components/style/gecko/arc_types.rs +++ b/servo/components/style/gecko/arc_types.rs @@ -16,7 +16,7 @@ use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, - MediaRule, NamespaceRule, PageRule, PropertyRule, StyleRule, StylesheetContents, SupportsRule, + MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StyleRule, StylesheetContents, SupportsRule, }; use servo_arc::Arc; @@ -169,3 +169,8 @@ impl_simple_arc_ffi!( Servo_AnimationValue_AddRef, Servo_AnimationValue_Release ); +impl_simple_arc_ffi!( + ScopeRule, + Servo_ScopeRule_AddRef, + Servo_ScopeRule_Release +); diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs index d845897aa44e..82b87dc90438 100644 --- a/servo/components/style/invalidation/stylesheets.rs +++ b/servo/components/style/invalidation/stylesheets.rs @@ -646,6 +646,11 @@ impl StylesheetInvalidationSet { debug!(" > Found unsupported rule, marking the whole subtree invalid."); self.invalidate_fully(); }, + Scope(..) => { + // Addition/removal of @scope requires re-evaluation of scope proximity to properly + // figure out the styling order. + self.invalidate_fully(); + }, } } } diff --git a/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs index 2bf75565dedd..ad2b2fadefb3 100644 --- a/servo/components/style/stylesheets/mod.rs +++ b/servo/components/style/stylesheets/mod.rs @@ -26,6 +26,7 @@ mod rules_iterator; mod style_rule; mod stylesheet; pub mod supports_rule; +mod scope_rule; #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::refptr::RefCounted; @@ -70,6 +71,7 @@ pub use self::rules_iterator::{ EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator, }; pub use self::style_rule::StyleRule; +pub use self::scope_rule::ScopeRule; pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind}; pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet}; pub use self::stylesheet::{StylesheetContents, StylesheetInDocument, UserAgentStylesheets}; @@ -265,6 +267,7 @@ pub enum CssRule { Document(Arc), LayerBlock(Arc), LayerStatement(Arc), + Scope(Arc), } impl CssRule { @@ -311,6 +314,9 @@ impl CssRule { }, // TODO(emilio): Add memory reporting for these rules. CssRule::LayerBlock(_) | CssRule::LayerStatement(_) => 0, + CssRule::Scope(ref rule) => { + rule.unconditional_shallow_size_of(ops) + rule.size_of(guard, ops) + } } } } @@ -349,6 +355,7 @@ pub enum CssRuleType { FontPaletteValues = 19, // 20 is an arbitrary number to use for Property. Property = 20, + Scope = 21, } impl CssRuleType { @@ -436,6 +443,7 @@ impl CssRule { CssRule::LayerBlock(_) => CssRuleType::LayerBlock, CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::Container(_) => CssRuleType::Container, + CssRule::Scope(_) => CssRuleType::Scope, } } @@ -567,6 +575,9 @@ impl DeepCloneWithLock for CssRule { CssRule::LayerBlock(ref arc) => { CssRule::LayerBlock(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) }, + CssRule::Scope(ref arc) => { + CssRule::Scope(Arc::new(arc.deep_clone_with_lock(lock, guard, params))) + } } } } @@ -592,6 +603,7 @@ impl ToCssWithGuard for CssRule { CssRule::LayerBlock(ref rule) => rule.to_css(guard, dest), CssRule::LayerStatement(ref rule) => rule.to_css(guard, dest), CssRule::Container(ref rule) => rule.to_css(guard, dest), + CssRule::Scope(ref rule) => rule.to_css(guard, dest), } } } diff --git a/servo/components/style/stylesheets/rule_parser.rs b/servo/components/style/stylesheets/rule_parser.rs index 742ad5d2501e..edc2262d357e 100644 --- a/servo/components/style/stylesheets/rule_parser.rs +++ b/servo/components/style/stylesheets/rule_parser.rs @@ -24,6 +24,7 @@ use crate::stylesheets::import_rule::{ImportLayer, ImportRule, ImportSupportsCon use crate::stylesheets::keyframes_rule::parse_keyframe_list; use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRule}; use crate::stylesheets::supports_rule::SupportsCondition; +use crate::stylesheets::scope_rule::{ScopeBounds, ScopeRule}; use crate::stylesheets::{ AllowImportRules, CorsMode, CssRule, CssRuleType, CssRuleTypes, CssRules, DocumentRule, FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, MarginRule, MarginRuleType, @@ -231,6 +232,8 @@ pub enum AtRulePrelude { Namespace(Option, Namespace), /// A @layer rule prelude. Layer(Vec), + /// A @scope rule prelude. + Scope(ScopeBounds), } impl AtRulePrelude { @@ -251,6 +254,7 @@ impl AtRulePrelude { Self::Margin(..) => "margin", Self::Namespace(..) => "namespace", Self::Layer(..) => "layer", + Self::Scope(..) => "scope", } } } @@ -507,7 +511,8 @@ impl<'a, 'i> NestedRuleParser<'a, 'i> { AtRulePrelude::Supports(..) | AtRulePrelude::Container(..) | AtRulePrelude::Document(..) | - AtRulePrelude::Layer(..) => true, + AtRulePrelude::Layer(..) | + AtRulePrelude::Scope(..) => true, AtRulePrelude::Namespace(..) | AtRulePrelude::FontFace | @@ -701,6 +706,10 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { let cond = DocumentCondition::parse(&self.context, input)?; AtRulePrelude::Document(cond) }, + "scope" if static_prefs::pref!("layout.css.at-scope.enabled") => { + let bounds = ScopeBounds::parse(&self.context, input, self.in_style_rule()); + AtRulePrelude::Scope(bounds) + }, _ => { if static_prefs::pref!("layout.css.margin-rules.enabled") { if let Some(margin_rule_type) = MarginRuleType::match_name(&name) { @@ -867,6 +876,16 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { // These rules don't have blocks. return Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)); }, + AtRulePrelude::Scope(bounds) => { + let source_location = start.source_location(); + CssRule::Scope(Arc::new(ScopeRule { + bounds, + rules: self + .parse_nested(input, CssRuleType::Scope) + .into_rules(self.shared_lock, source_location), + source_location, + })) + }, }; self.rules.push(rule); Ok(()) diff --git a/servo/components/style/stylesheets/rules_iterator.rs b/servo/components/style/stylesheets/rules_iterator.rs index 76d41c8184f5..1ad95cfdc3f8 100644 --- a/servo/components/style/stylesheets/rules_iterator.rs +++ b/servo/components/style/stylesheets/rules_iterator.rs @@ -116,6 +116,7 @@ where Some(supports_rule.rules.read_with(guard).0.iter()) }, CssRule::LayerBlock(ref layer_rule) => Some(layer_rule.rules.read_with(guard).0.iter()), + CssRule::Scope(ref rule) => Some(rule.rules.read_with(guard).0.iter()) } } } diff --git a/servo/components/style/stylesheets/scope_rule.rs b/servo/components/style/stylesheets/scope_rule.rs new file mode 100644 index 000000000000..2db4919065e2 --- /dev/null +++ b/servo/components/style/stylesheets/scope_rule.rs @@ -0,0 +1,162 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! A [`@scope`][scope] rule. +//! +//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles + +use crate::parser::ParserContext; +use crate::selector_parser::{SelectorImpl, SelectorParser}; +use crate::shared_lock::{ + DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, +}; +use crate::str::CssStringWriter; +use crate::stylesheets::CssRules; +use cssparser::{Parser, SourceLocation, ToCss}; +#[cfg(feature = "gecko")] +use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalSizeOf, MallocUnconditionalShallowSizeOf}; +use selectors::parser::{ParseRelative, SelectorList}; +use servo_arc::Arc; +use std::fmt::{self, Write}; +use style_traits::CssWriter; + +/// A scoped rule. +#[derive(Debug, ToShmem)] +pub struct ScopeRule { + /// Bounds at which this rule applies. + pub bounds: ScopeBounds, + /// The nested rules inside the block. + pub rules: Arc>, + /// The source position where this rule was found. + pub source_location: SourceLocation, +} + +impl DeepCloneWithLock for ScopeRule { + fn deep_clone_with_lock( + &self, + lock: &SharedRwLock, + guard: &SharedRwLockReadGuard, + params: &DeepCloneParams, + ) -> Self { + let rules = self.rules.read_with(guard); + Self { + bounds: self.bounds.clone(), + rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))), + source_location: self.source_location.clone(), + } + } +} + +impl ToCssWithGuard for ScopeRule { + fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@scope")?; + { + let mut writer = CssWriter::new(dest); + if let Some(start) = self.bounds.start.as_ref() { + writer.write_str(" (")?; + start.to_css(&mut writer)?; + writer.write_char(')')?; + } + if let Some(end) = self.bounds.end.as_ref() { + writer.write_str(" to (")?; + end.to_css(&mut writer)?; + writer.write_char(')')?; + } + } + self.rules.read_with(guard).to_css_block(guard, dest) + } +} + +impl ScopeRule { + /// Measure heap usage. + #[cfg(feature = "gecko")] + pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize { + self.rules.unconditional_shallow_size_of(ops) + + self.rules.read_with(guard).size_of(guard, ops) + + self.bounds.size_of(ops) + } +} + +/// Bounds of the scope. +#[derive(Debug, Clone, ToShmem)] +pub struct ScopeBounds { + /// Start of the scope. + pub start: Option>, + /// End of the scope. + pub end: Option>, +} + +impl ScopeBounds { + #[cfg(feature = "gecko")] + fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { + fn bound_size_of(bound: &Option>, ops: &mut MallocSizeOfOps) -> usize { + bound.as_ref().map(|list| list.unconditional_size_of(ops)).unwrap_or(0) + } + bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops) + } +} + +fn parse_scope<'a>( + context: &ParserContext, + input: &mut Parser<'a, '_>, + in_style_rule: bool, + for_end: bool +) -> Option> { + input.try_parse(|input| { + if for_end { + input.expect_ident_matching("to")?; + } + input.expect_parenthesis_block()?; + input.parse_nested_block(|input| { + if input.is_exhausted() { + return Ok(None); + } + let selector_parser = SelectorParser { + stylesheet_origin: context.stylesheet_origin, + namespaces: &context.namespaces, + url_data: context.url_data, + for_supports_rule: false, + }; + let parse_relative = if for_end { + // TODO(dshin): scope-end can be a relative selector, with the anchor being `:scope`. + ParseRelative::No + } else if in_style_rule { + ParseRelative::ForNesting + } else { + ParseRelative::No + }; + Ok(Some(SelectorList::parse_forgiving( + &selector_parser, + input, + parse_relative, + )?)) + }) + }) + .ok() + .flatten() +} + +impl ScopeBounds { + /// Parse a container condition. + pub fn parse<'a>( + context: &ParserContext, + input: &mut Parser<'a, '_>, + in_style_rule: bool, + ) -> Self { + let start = parse_scope( + context, + input, + in_style_rule, + false + ); + + let end = parse_scope( + context, + input, + in_style_rule, + true + ); + Self { start, end } + } +} diff --git a/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs index 1604022871d8..a65b3f748441 100644 --- a/servo/components/style/stylesheets/stylesheet.rs +++ b/servo/components/style/stylesheets/stylesheet.rs @@ -333,7 +333,10 @@ impl SanitizationKind { // TODO(emilio): Perhaps Layer should not be always sanitized? But // we sanitize @media and co, so this seems safer for now. CssRule::LayerStatement(..) | - CssRule::LayerBlock(..) => false, + CssRule::LayerBlock(..) | + // TODO(dshin): Same comment as Layer applies - shouldn't give away + // something like display size - erring on the side of "safe" for now. + CssRule::Scope(..) => false, CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true, diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs index cc1cb7568912..a291c7aab51a 100644 --- a/servo/components/style/stylist.rs +++ b/servo/components/style/stylist.rs @@ -3234,7 +3234,8 @@ impl CascadeData { CssRule::LayerBlock(..) | CssRule::LayerStatement(..) | CssRule::FontPaletteValues(..) | - CssRule::FontFeatureValues(..) => { + CssRule::FontFeatureValues(..) | + CssRule::Scope(..) => { // Not affected by device changes. continue; }, diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs index 83c55ad9e01f..e4d63b659982 100644 --- a/servo/ports/geckolib/glue.rs +++ b/servo/ports/geckolib/glue.rs @@ -139,7 +139,7 @@ use style::stylesheets::{ FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, Origin, OriginSet, PagePseudoClassFlags, PageRule, PropertyRule, SanitizationData, SanitizationKind, StyleRule, StylesheetContents, - StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, + StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, ScopeRule, }; use style::stylist::{add_size_of_ua_cache, AuthorStylesEnabled, RuleInclusion, Stylist}; use style::thread_state; @@ -2478,6 +2478,14 @@ impl_basic_rule_funcs! { (CounterStyle, CounterStyleRule, Locked usize { rule.names.len() diff --git a/startupcache/StartupCache.h b/startupcache/StartupCache.h index 5894a1c9c9eb..93ddc217cfc5 100644 --- a/startupcache/StartupCache.h +++ b/startupcache/StartupCache.h @@ -244,7 +244,7 @@ class StartupCache : public nsIMemoryReporter { nsTArray mOldTables MOZ_GUARDED_BY(mTableLock); size_t mAllowedInvalidationsCount; nsCOMPtr mFile; - loader::AutoMemMap mCacheData MOZ_GUARDED_BY(mTableLock); + mozilla::loader::AutoMemMap mCacheData MOZ_GUARDED_BY(mTableLock); Mutex mTableLock; nsCOMPtr mObserverService; diff --git a/testing/web-platform/meta/css/css-cascade/__dir__.ini b/testing/web-platform/meta/css/css-cascade/__dir__.ini index 221167417d0d..8a09ade22f60 100644 --- a/testing/web-platform/meta/css/css-cascade/__dir__.ini +++ b/testing/web-platform/meta/css/css-cascade/__dir__.ini @@ -1 +1 @@ -prefs: [layout.css.import-supports.enabled:true, layout.css.properties-and-values.enabled:true] +prefs: [layout.css.import-supports.enabled:true, layout.css.properties-and-values.enabled:true, layout.css.at-scope.enabled:true] diff --git a/testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini b/testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini deleted file mode 100644 index dd3a35e95ff8..000000000000 --- a/testing/web-platform/meta/css/css-cascade/at-scope-parsing.html.ini +++ /dev/null @@ -1,80 +0,0 @@ -[at-scope-parsing.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [@scope (.a) is valid] - expected: FAIL - - [@scope (.a + .b) is valid] - expected: FAIL - - [@scope (.a:hover) is valid] - expected: FAIL - - [@scope (.a:hover, #b, div) is valid] - expected: FAIL - - [@scope (:is(div, span)) is valid] - expected: FAIL - - [@scope (.a) to (.b) is valid] - expected: FAIL - - [@scope (.a)to (.b) is valid] - expected: FAIL - - [@scope (.a) to (.b:hover, #c, div) is valid] - expected: FAIL - - [@scope (.c <> .d) is valid] - expected: FAIL - - [@scope (.a, .c <> .d) is valid] - expected: FAIL - - [@scope (.a <> .b, .c) is valid] - expected: FAIL - - [@scope (div::before) is valid] - expected: FAIL - - [@scope (div::after) is valid] - expected: FAIL - - [@scope (slotted(div)) is valid] - expected: FAIL - - [@scope (.a) to (div::before) is valid] - expected: FAIL - - [@scope is valid] - expected: FAIL - - [@scope (.a) to (&) is valid] - expected: FAIL - - [@scope (.a) to (& > &) is valid] - expected: FAIL - - [@scope (.a) to (> .b) is valid] - expected: FAIL - - [@scope (.a) to (+ .b) is valid] - expected: FAIL - - [@scope (.a) to (~ .b) is valid] - expected: FAIL - - [@scope to (.a) is valid] - expected: FAIL - - [@scope (> &) to (>>) is valid] - expected: FAIL - - [@scope () is valid] - expected: FAIL - - [@scope to () is valid] - expected: FAIL - - [@scope () to () is valid] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/idlharness.html.ini b/testing/web-platform/meta/css/css-cascade/idlharness.html.ini deleted file mode 100644 index cd50fa825612..000000000000 --- a/testing/web-platform/meta/css/css-cascade/idlharness.html.ini +++ /dev/null @@ -1,35 +0,0 @@ -[idlharness.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [CSSScopeRule interface: existence and properties of interface object] - expected: FAIL - - [CSSScopeRule interface object length] - expected: FAIL - - [CSSScopeRule interface object name] - expected: FAIL - - [CSSScopeRule interface: existence and properties of interface prototype object] - expected: FAIL - - [CSSScopeRule interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [CSSScopeRule interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [CSSScopeRule interface: attribute start] - expected: FAIL - - [CSSScopeRule interface: attribute end] - expected: FAIL - - [Stringification of scope] - expected: FAIL - - [CSSScopeRule interface: scope must inherit property "start" with the proper type] - expected: FAIL - - [CSSScopeRule interface: scope must inherit property "end" with the proper type] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini b/testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini deleted file mode 100644 index 510a64990d1d..000000000000 --- a/testing/web-platform/meta/css/css-cascade/scope-cssom.html.ini +++ /dev/null @@ -1,39 +0,0 @@ -[scope-cssom.html] - [CSSScopeRule.cssText, implicit scope] - expected: FAIL - - [CSSScopeRule.cssText, root only] - expected: FAIL - - [CSSScopeRule.cssText, root and limit] - expected: FAIL - - [CSSScopeRule.cssText, limit only] - expected: FAIL - - [CSSScopeRule.start, implicit scope] - expected: FAIL - - [CSSScopeRule.start, root only] - expected: FAIL - - [CSSScopeRule.start, root and limit] - expected: FAIL - - [CSSScopeRule.start, limit only] - expected: FAIL - - [CSSScopeRule.end, implicit scope] - expected: FAIL - - [CSSScopeRule.end, root only] - expected: FAIL - - [CSSScopeRule.end, root and limit] - expected: FAIL - - [CSSScopeRule.end, limit only] - expected: FAIL - - [CSSScopeRule is a CSSGroupingRule] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-deep.html.ini b/testing/web-platform/meta/css/css-cascade/scope-deep.html.ini deleted file mode 100644 index 3968ff3a8bce..000000000000 --- a/testing/web-platform/meta/css/css-cascade/scope-deep.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[scope-deep.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [Deep @scope nesting] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini b/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini index 1768b21bb49e..44d48f4b05c6 100644 --- a/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini +++ b/testing/web-platform/meta/css/css-cascade/scope-evaluation.html.ini @@ -17,9 +17,6 @@ [Inner @scope with :scope in from-selector] expected: FAIL - [Multiple scopes from same @scope-rule, only one limited] - expected: FAIL - [Nested scopes] expected: FAIL @@ -43,3 +40,15 @@ [Scope root with :has()] expected: FAIL + + [Scope can not match its own root without :scope] + expected: FAIL + + [Multiple scopes from same @scope-rule, both limited] + expected: FAIL + + [Nested scopes, reverse] + expected: FAIL + + [Scope with no elements] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini b/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini index dc73a3cb3775..a91abe192e14 100644 --- a/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini +++ b/testing/web-platform/meta/css/css-cascade/scope-implicit.html.ini @@ -16,3 +16,9 @@ [Implicit @scope with limit] expected: FAIL + + [@scope with effectively empty :is() must not match anything] + expected: FAIL + + [Implicit @scope has implicitly added :scope descendant combinator] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini b/testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini deleted file mode 100644 index a5ef978d2799..000000000000 --- a/testing/web-platform/meta/css/css-cascade/scope-name-defining-rules.html.ini +++ /dev/null @@ -1,12 +0,0 @@ -[scope-name-defining-rules.html] - [@keyframes is unaffected by @scope] - expected: FAIL - - [@keyframes is unaffected by non-matching @scope] - expected: FAIL - - [@property is unaffected by @scope] - expected: FAIL - - [@property is unaffected by non-matching @scope] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini b/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini index 0d535cd082b2..ff297793265f 100644 --- a/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini +++ b/testing/web-platform/meta/css/css-cascade/scope-nesting.html.ini @@ -46,3 +46,6 @@ [Scoped nested group rule] expected: FAIL + + [Nesting-selector in ] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini b/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini index 2e16da28b1e9..33bb292b8096 100644 --- a/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini +++ b/testing/web-platform/meta/css/css-cascade/scope-proximity.html.ini @@ -6,6 +6,3 @@ [Proximity wins over order of appearance] expected: FAIL - - [Specificity wins over proximity] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini b/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini index bf47d2b4f77b..088f046448b4 100644 --- a/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini +++ b/testing/web-platform/meta/css/css-cascade/scope-shadow.tentative.html.ini @@ -1,7 +1,4 @@ [scope-shadow.tentative.html] - [@scope can match :host] - expected: FAIL - [@scope can match :host(...)] expected: FAIL diff --git a/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini b/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini index beda7272c7c1..c73271a9e84c 100644 --- a/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini +++ b/testing/web-platform/meta/css/css-cascade/scope-visited-cssom.html.ini @@ -1,16 +1,4 @@ [scope-visited-cssom.html] - [:link as scoped selector] - expected: FAIL - - [:not(:visited) as scoped selector] - expected: FAIL - - [:link as scoping root] - expected: FAIL - - [:not(:visited) as scoping root] - expected: FAIL - [:link as scoping root, :scope] expected: FAIL @@ -22,3 +10,9 @@ [:not(:link) as scoping limit] expected: FAIL + + [:visited as scoping root] + expected: FAIL + + [:not(:link) as scoping root] + expected: FAIL diff --git a/testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini b/testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini new file mode 100644 index 000000000000..1275f8492c5f --- /dev/null +++ b/testing/web-platform/meta/css/css-nesting/conditional-rules.html.ini @@ -0,0 +1,3 @@ +prefs: [layout.css.at-scope.enabled:true] +[conditional-rules.html] + expected: FAIL diff --git a/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html index 88e28fe4ff74..e984c1dcc29e 100644 --- a/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html +++ b/testing/web-platform/tests/css/css-cascade/at-scope-parsing.html @@ -75,4 +75,5 @@ test_invalid('@scope (.a'); test_invalid('@scope (.a to (.b)'); test_invalid('@scope ( to (.b)'); + test_invalid('@scope (.a) from (.c)');