commit f289eebba9194e9f22fce14b4e97c2da7a6e7c31 parent de5612d526fd3cdb4744a429debe230fdd2648f4 Author: Emilio Cobos Álvarez <emilio@crisal.io> Date: Thu, 30 Oct 2025 22:37:20 +0000 Bug 1744292 - Implement @custom-media queries. r=firefox-style-system-reviewers,layout-reviewers,webidl,smaug,dshin Unfortunately the existing WPT test coverage is rather lacking so we need to fix that before enabling. Differential Revision: https://phabricator.services.mozilla.com/D270675 Diffstat:
33 files changed, 773 insertions(+), 211 deletions(-)
diff --git a/dom/webidl/CSSCustomMediaRule.webidl b/dom/webidl/CSSCustomMediaRule.webidl @@ -0,0 +1,18 @@ +/* -*- 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/cssom/#the-cssmediarule-interface + * https://drafts.csswg.org/css-conditional/#the-cssmediarule-interface + */ + +typedef (MediaList or boolean) CustomMediaQuery; + +// https://drafts.csswg.org/mediaqueries-5/#csscustommediarule +[Exposed=Window, Pref="layout.css.custom-media.enabled"] +interface CSSCustomMediaRule : CSSRule { + readonly attribute UTF8String name; + readonly attribute CustomMediaQuery query; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build @@ -493,6 +493,7 @@ WEBIDL_FILES = [ "CSSConditionRule.webidl", "CSSContainerRule.webidl", "CSSCounterStyleRule.webidl", + "CSSCustomMediaRule.webidl", "CSSFontFaceRule.webidl", "CSSFontFeatureValuesRule.webidl", "CSSFontPaletteValuesRule.webidl", diff --git a/layout/inspector/InspectorUtils.cpp b/layout/inspector/InspectorUtils.cpp @@ -573,6 +573,7 @@ static uint32_t CollectAtRules(ServoCSSRuleList& aRuleList, (void)aResult.AppendElement(OwningNonNull(*rule), fallible); break; } + case StyleCssRuleType::CustomMedia: case StyleCssRuleType::Style: case StyleCssRuleType::Import: case StyleCssRuleType::Document: diff --git a/layout/inspector/ServoStyleRuleMap.cpp b/layout/inspector/ServoStyleRuleMap.cpp @@ -96,6 +96,7 @@ void ServoStyleRuleMap::RuleRemoved(StyleSheet& aStyleSheet, mTable.Clear(); break; } + case StyleCssRuleType::CustomMedia: case StyleCssRuleType::LayerStatement: case StyleCssRuleType::FontFace: case StyleCssRuleType::Page: @@ -162,6 +163,7 @@ void ServoStyleRuleMap::FillTableFromRule(css::Rule& aRule) { } break; } + case StyleCssRuleType::CustomMedia: case StyleCssRuleType::LayerStatement: case StyleCssRuleType::FontFace: case StyleCssRuleType::Page: diff --git a/layout/style/CSSCustomMediaRule.cpp b/layout/style/CSSCustomMediaRule.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/CSSCustomMediaRule.h" + +#include "mozilla/DeclarationBlock.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/dom/CSSCustomMediaRuleBinding.h" +#include "mozilla/dom/CSSCustomMediaRuleBindingFwd.h" +#include "mozilla/dom/MediaList.h" + +namespace mozilla::dom { + +CSSCustomMediaRule::CSSCustomMediaRule(RefPtr<StyleCustomMediaRule> aRawRule, + StyleSheet* aSheet, + css::Rule* aParentRule, uint32_t aLine, + uint32_t aColumn) + : css::Rule(aSheet, aParentRule, aLine, aColumn), + mRawRule(std::move(aRawRule)) {} + +CSSCustomMediaRule::~CSSCustomMediaRule() { + if (mMediaList) { + mMediaList->SetStyleSheet(nullptr); + } +} + +NS_IMPL_ADDREF_INHERITED(CSSCustomMediaRule, Rule) +NS_IMPL_RELEASE_INHERITED(CSSCustomMediaRule, Rule) + +NS_IMPL_CYCLE_COLLECTION_CLASS(CSSCustomMediaRule) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CSSCustomMediaRule) +NS_INTERFACE_MAP_END_INHERITING(Rule) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CSSCustomMediaRule, css::Rule) + if (tmp->mMediaList) { + tmp->mMediaList->SetStyleSheet(nullptr); + tmp->mMediaList = nullptr; + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CSSCustomMediaRule, css::Rule) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaList) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +bool CSSCustomMediaRule::IsCCLeaf() const { + return Rule::IsCCLeaf() && !mMediaList; +} + +/* virtual */ +void CSSCustomMediaRule::DropSheetReference() { + if (mMediaList) { + mMediaList->SetStyleSheet(nullptr); + } + Rule::DropSheetReference(); +} + +void CSSCustomMediaRule::SetRawAfterClone(RefPtr<StyleCustomMediaRule> aRaw) { + mRawRule = std::move(aRaw); + if (mMediaList) { + mMediaList->SetRawAfterClone( + Servo_CustomMediaRule_GetCondition(mRawRule, nullptr).Consume()); + mMediaList->SetStyleSheet(nullptr); + mMediaList->SetStyleSheet(GetStyleSheet()); + } +} + +// WebIDL interfaces +StyleCssRuleType CSSCustomMediaRule::Type() const { + return StyleCssRuleType::CustomMedia; +} + +// CSSRule implementation + +void CSSCustomMediaRule::GetCssText(nsACString& aCssText) const { + Servo_CustomMediaRule_GetCssText(mRawRule, &aCssText); +} + +void CSSCustomMediaRule::GetName(nsACString& aName) const { + auto* name = Servo_CustomMediaRule_GetName(mRawRule); + name->ToUTF8String(aName); +} + +void CSSCustomMediaRule::GetQuery(OwningCustomMediaQuery& aQuery) { + if (mMediaList) { + aQuery.SetAsMediaList() = mMediaList; + return; + } + bool value = false; + RefPtr rawMediaList = + Servo_CustomMediaRule_GetCondition(mRawRule, &value).Consume(); + if (!rawMediaList) { + aQuery.SetAsBoolean() = value; + return; + } + + mMediaList = new MediaList(rawMediaList.forget()); + mMediaList->SetStyleSheet(GetStyleSheet()); + aQuery.SetAsMediaList() = mMediaList; +} + +size_t CSSCustomMediaRule::SizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) const { + // TODO Implement this! + return aMallocSizeOf(this); +} + +#ifdef DEBUG +void CSSCustomMediaRule::List(FILE* out, int32_t aIndent) const { + nsAutoCString str; + for (int32_t i = 0; i < aIndent; i++) { + str.AppendLiteral(" "); + } + Servo_CustomMediaRule_Debug(mRawRule, &str); + fprintf_stderr(out, "%s\n", str.get()); +} +#endif + +JSObject* CSSCustomMediaRule::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return CSSCustomMediaRule_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/layout/style/CSSCustomMediaRule.h b/layout/style/CSSCustomMediaRule.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 mozilla_dom_CSSCustomMediaRule_h +#define mozilla_dom_CSSCustomMediaRule_h + +#include "mozilla/ServoBindingTypes.h" +#include "mozilla/css/Rule.h" +#include "mozilla/dom/CSSCustomMediaRuleBindingFwd.h" + +namespace mozilla::dom { + +class CSSCustomMediaRule final : public css::Rule { + public: + CSSCustomMediaRule(RefPtr<StyleCustomMediaRule> aRawRule, StyleSheet* aSheet, + css::Rule* aParentRule, uint32_t aLine, uint32_t aColumn); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CSSCustomMediaRule, css::Rule) + + void DropSheetReference() final; + bool IsCCLeaf() const final; + + StyleCustomMediaRule* Raw() const { return mRawRule; } + void SetRawAfterClone(RefPtr<StyleCustomMediaRule>); + + // WebIDL interfaces + StyleCssRuleType Type() const final; + void GetCssText(nsACString& aCssText) const final; + + void GetName(nsACString&) const; + void GetQuery(OwningCustomMediaQuery&); + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const final; + +#ifdef DEBUG + void List(FILE* out = stdout, int32_t aIndent = 0) const final; +#endif + + JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final; + + private: + ~CSSCustomMediaRule(); + + RefPtr<StyleCustomMediaRule> mRawRule; + RefPtr<dom::MediaList> mMediaList; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_CSSCustomMediaRule_h diff --git a/layout/style/ServoBindingTypes.h b/layout/style/ServoBindingTypes.h @@ -126,6 +126,7 @@ UNLOCKED_RULE_TYPE(Namespace) UNLOCKED_RULE_TYPE(Margin) UNLOCKED_RULE_TYPE(Container) UNLOCKED_RULE_TYPE(Media) +UNLOCKED_RULE_TYPE(CustomMedia) UNLOCKED_RULE_TYPE(Supports) UNLOCKED_RULE_TYPE(Document) UNLOCKED_RULE_TYPE(FontFeatureValues) diff --git a/layout/style/ServoBindings.h b/layout/style/ServoBindings.h @@ -79,6 +79,7 @@ GROUP_RULE_FUNCS_UNLOCKED(Media) GROUP_RULE_FUNCS_UNLOCKED(Document) BASIC_RULE_FUNCS_UNLOCKED(Namespace) BASIC_RULE_FUNCS_UNLOCKED(Margin) +BASIC_RULE_FUNCS_UNLOCKED(CustomMedia) GROUP_RULE_FUNCS_LOCKED(Page) BASIC_RULE_FUNCS_UNLOCKED(Property) GROUP_RULE_FUNCS_UNLOCKED(Supports) diff --git a/layout/style/ServoCSSRuleList.cpp b/layout/style/ServoCSSRuleList.cpp @@ -12,6 +12,7 @@ #include "mozilla/StyleSheet.h" #include "mozilla/dom/CSSContainerRule.h" #include "mozilla/dom/CSSCounterStyleRule.h" +#include "mozilla/dom/CSSCustomMediaRule.h" #include "mozilla/dom/CSSFontFaceRule.h" #include "mozilla/dom/CSSFontFeatureValuesRule.h" #include "mozilla/dom/CSSFontPaletteValuesRule.h" @@ -107,6 +108,7 @@ css::Rule* ServoCSSRuleList::GetRule(uint32_t aIndex) { CASE_RULE_UNLOCKED(StartingStyle, StartingStyle) CASE_RULE_LOCKED(PositionTry, PositionTry) CASE_RULE_LOCKED(NestedDeclarations, NestedDeclarations) + CASE_RULE_UNLOCKED(CustomMedia, CustomMedia) #undef CASE_RULE_LOCKED #undef CASE_RULE_UNLOCKED #undef CASE_RULE_WITH_PREFIX @@ -286,6 +288,7 @@ void ServoCSSRuleList::SetRawContents(RefPtr<StyleLockedCssRules> aNewRules, RULE_CASE_UNLOCKED(StartingStyle, StartingStyle) RULE_CASE_LOCKED(PositionTry, PositionTry) RULE_CASE_LOCKED(NestedDeclarations, NestedDeclarations) + RULE_CASE_UNLOCKED(CustomMedia, CustomMedia) case StyleCssRuleType::Keyframe: MOZ_ASSERT_UNREACHABLE("keyframe rule cannot be here"); break; diff --git a/layout/style/ServoStyleConstsInlines.h b/layout/style/ServoStyleConstsInlines.h @@ -44,6 +44,7 @@ template struct StyleStrong<StyleLockedStyleRule>; template struct StyleStrong<StyleLockedImportRule>; template struct StyleStrong<StyleLockedKeyframesRule>; template struct StyleStrong<StyleMediaRule>; +template struct StyleStrong<StyleCustomMediaRule>; template struct StyleStrong<StyleDocumentRule>; template struct StyleStrong<StyleNamespaceRule>; template struct StyleStrong<StyleMarginRule>; diff --git a/layout/style/ServoStyleSet.cpp b/layout/style/ServoStyleSet.cpp @@ -29,6 +29,7 @@ #include "mozilla/dom/CSSBinding.h" #include "mozilla/dom/CSSContainerRule.h" #include "mozilla/dom/CSSCounterStyleRule.h" +#include "mozilla/dom/CSSCustomMediaRule.h" #include "mozilla/dom/CSSFontFaceRule.h" #include "mozilla/dom/CSSFontFeatureValuesRule.h" #include "mozilla/dom/CSSFontPaletteValuesRule.h" @@ -1005,6 +1006,7 @@ static Maybe<StyleCssRuleRef> ToRuleRef(css::Rule& aRule) { CASE_FOR(Media, Media) CASE_FOR(Keyframes, Keyframes) CASE_FOR(Margin, Margin) + CASE_FOR(CustomMedia, CustomMedia) CASE_FOR(FontFeatureValues, FontFeatureValues) CASE_FOR(FontPaletteValues, FontPaletteValues) CASE_FOR(FontFace, FontFace) @@ -1051,6 +1053,7 @@ void ServoStyleSet::RuleChangedInternal(StyleSheet& aSheet, css::Rule& aRule, CASE_FOR(CounterStyle, CounterStyle) CASE_FOR(Style, Style) CASE_FOR(Import, Import) + CASE_FOR(CustomMedia, CustomMedia) CASE_FOR(Media, Media) CASE_FOR(Keyframes, Keyframes) CASE_FOR(Margin, Margin) diff --git a/layout/style/moz.build b/layout/style/moz.build @@ -144,6 +144,7 @@ EXPORTS.mozilla.dom += [ "CSS.h", "CSSContainerRule.h", "CSSCounterStyleRule.h", + "CSSCustomMediaRule.h", "CSSFontFaceRule.h", "CSSFontFeatureValuesRule.h", "CSSFontPaletteValuesRule.h", @@ -200,6 +201,7 @@ UNIFIED_SOURCES += [ "CSS.cpp", "CSSContainerRule.cpp", "CSSCounterStyleRule.cpp", + "CSSCustomMediaRule.cpp", "CSSFontFaceRule.cpp", "CSSFontFeatureValuesRule.cpp", "CSSFontPaletteValuesRule.cpp", diff --git a/layout/svg/SVGIntegrationUtils.h b/layout/svg/SVGIntegrationUtils.h @@ -22,6 +22,9 @@ struct nsPoint; struct nsRect; struct nsSize; +// zipstruct.h defines this and causes issues if in the same translation unit. +#undef UNSUPPORTED + enum class WrFiltersStatus { // Image will be rendered unftilered - the filter graph contains invalid refs // (which SVG spec states will be rendered as if there is no filter graph). diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml @@ -9809,6 +9809,15 @@ mirror: always rust: true +# Whether to enable CSS @custom-media +# https://drafts.csswg.org/mediaqueries-5/ +# TODO(emilio): Needs more tests before enabling. +- name: layout.css.custom-media.enabled + type: RelaxedAtomicBool + value: false + mirror: always + rust: true + # Whether to enable CSS Module Scripts support. # https://html.spec.whatwg.org/#creating-a-css-module-script - name: layout.css.module-scripts.enabled diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs @@ -14,9 +14,9 @@ use crate::properties::{ComputedValues, PropertyDeclarationBlock}; use crate::shared_lock::Locked; use crate::stylesheets::keyframes_rule::Keyframe; use crate::stylesheets::{ - ContainerRule, CssRules, DocumentRule, FontFeatureValuesRule, FontPaletteValuesRule, - LayerBlockRule, LayerStatementRule, MarginRule, MediaRule, NamespaceRule, PropertyRule, - ScopeRule, StartingStyleRule, StylesheetContents, SupportsRule, + ContainerRule, CssRules, CustomMediaRule, DocumentRule, FontFeatureValuesRule, + FontPaletteValuesRule, LayerBlockRule, LayerStatementRule, MarginRule, MediaRule, + NamespaceRule, PropertyRule, ScopeRule, StartingStyleRule, StylesheetContents, SupportsRule, }; pub use crate::stylesheets::{ LockedCounterStyleRule, LockedFontFaceRule, LockedImportRule, LockedKeyframesRule, @@ -97,6 +97,11 @@ impl_locked_arc_ffi!( ); impl_simple_arc_ffi!(MediaRule, Servo_MediaRule_AddRef, Servo_MediaRule_Release); impl_simple_arc_ffi!( + CustomMediaRule, + Servo_CustomMediaRule_AddRef, + Servo_CustomMediaRule_Release +); +impl_simple_arc_ffi!( NamespaceRule, Servo_NamespaceRule_AddRef, Servo_NamespaceRule_Release diff --git a/servo/components/style/invalidation/media_queries.rs b/servo/components/style/invalidation/media_queries.rs @@ -7,7 +7,7 @@ use crate::context::QuirksMode; use crate::media_queries::Device; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{DocumentRule, ImportRule, MediaRule}; +use crate::stylesheets::{CustomMediaMap, DocumentRule, ImportRule, MediaRule}; use crate::stylesheets::{NestedRuleIterationCondition, StylesheetContents, SupportsRule}; use rustc_hash::FxHashSet; @@ -97,12 +97,19 @@ impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules { _: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, + _: &CustomMediaMap, _: &ImportRule, ) -> bool { true } - fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool { + fn process_media( + _: &SharedRwLockReadGuard, + _: &Device, + _: QuirksMode, + _: &CustomMediaMap, + _: &MediaRule, + ) -> bool { true } diff --git a/servo/components/style/invalidation/stylesheets.rs b/servo/components/style/invalidation/stylesheets.rs @@ -15,7 +15,7 @@ use crate::media_queries::Device; use crate::selector_parser::{SelectorImpl, Snapshot, SnapshotMap}; use crate::shared_lock::SharedRwLockReadGuard; use crate::simple_buckets_map::SimpleBucketsMap; -use crate::stylesheets::{CssRule, CssRuleRef, StylesheetInDocument}; +use crate::stylesheets::{CssRule, CssRuleRef, CustomMediaMap, StylesheetInDocument}; use crate::stylesheets::{EffectiveRules, EffectiveRulesIterator}; use crate::values::AtomIdent; use crate::LocalName as SelectorLocalName; @@ -129,6 +129,7 @@ impl StylesheetInvalidationSet { pub fn collect_invalidations_for<S>( &mut self, device: &Device, + custom_media: &CustomMediaMap, stylesheet: &S, guard: &SharedRwLockReadGuard, ) where @@ -140,20 +141,25 @@ impl StylesheetInvalidationSet { return; } - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { + if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, custom_media, guard) + { debug!(" > Stylesheet was not effective"); return; // Nothing to do here. } let quirks_mode = device.quirks_mode(); - for rule in stylesheet.contents(guard).effective_rules(device, guard) { + for rule in stylesheet + .contents(guard) + .effective_rules(device, custom_media, guard) + { self.collect_invalidations_for_rule( rule, guard, device, quirks_mode, /* is_generic_change = */ false, - // Note(dshin): Technically, the iterator should provide the ancestor chain as it traverses down, but it shouldn't make a difference. + // Note(dshin): Technically, the iterator should provide the ancestor chain as it + // traverses down, but it shouldn't make a difference. &[], ); if self.fully_invalid { @@ -514,11 +520,7 @@ impl StylesheetInvalidationSet { true } - /// Collects invalidations for a given CSS rule, if not fully invalid - /// already. - /// - /// TODO(emilio): we can't check whether the rule is inside a non-effective - /// subtree, we potentially could do that. + /// Collects invalidations for a given CSS rule, if not fully invalid already. pub fn rule_changed<S>( &mut self, stylesheet: &S, @@ -526,6 +528,7 @@ impl StylesheetInvalidationSet { guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, + custom_media: &CustomMediaMap, change_kind: RuleChangeKind, ancestors: &[CssRuleRef], ) where @@ -536,14 +539,15 @@ impl StylesheetInvalidationSet { return; } - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { + if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, custom_media, guard) + { debug!(" > Stylesheet was not effective"); return; // Nothing to do here. } if ancestors .iter() - .any(|r| !EffectiveRules::is_effective(guard, device, quirks_mode, r)) + .any(|r| !EffectiveRules::is_effective(guard, device, quirks_mode, custom_media, r)) { debug!(" > Ancestor rules not effective"); return; @@ -567,12 +571,18 @@ impl StylesheetInvalidationSet { } if !is_generic_change - && !EffectiveRules::is_effective(guard, device, quirks_mode, &rule.into()) + && !EffectiveRules::is_effective(guard, device, quirks_mode, custom_media, &rule.into()) { return; } - let rules = EffectiveRulesIterator::effective_children(device, quirks_mode, guard, rule); + let rules = EffectiveRulesIterator::effective_children( + device, + quirks_mode, + custom_media, + guard, + rule, + ); for rule in rules { self.collect_invalidations_for_rule( rule, @@ -624,7 +634,7 @@ impl StylesheetInvalidationSet { }, NestedDeclarations(..) => { if ancestors.iter().any(|r| matches!(r, CssRuleRef::Scope(_))) { - self.fully_invalid = true; + self.invalidate_fully(); } }, Namespace(..) => { @@ -677,6 +687,13 @@ impl StylesheetInvalidationSet { // We should probably make an effort to see if this position-try is referenced. self.invalidate_fully(); }, + CustomMedia(..) => { + // @custom-media might be referenced by other rules which we can't get a hand on in + // here, so we don't know which elements are affected. + // + // TODO: Maybe track referenced custom-media rules like we do for @keyframe? + self.invalidate_fully(); + }, } } } diff --git a/servo/components/style/media_queries/media_list.rs b/servo/components/style/media_queries/media_list.rs @@ -10,6 +10,7 @@ use super::{Device, MediaQuery, Qualifier}; use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; +use crate::stylesheets::CustomMediaEvaluator; use crate::values::computed; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; @@ -73,29 +74,41 @@ impl MediaList { } /// Evaluate a whole `MediaList` against `Device`. - pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool { + pub fn evaluate( + &self, + device: &Device, + quirks_mode: QuirksMode, + custom: &mut CustomMediaEvaluator, + ) -> bool { + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { + self.matches(context, custom).to_bool(/* unknown = */ false) + }) + } + + /// Evaluate the current `MediaList` with a pre-existing context and custom-media evaluator. + pub fn matches( + &self, + context: &computed::Context, + custom: &mut CustomMediaEvaluator, + ) -> KleeneValue { // Check if it is an empty media query list or any queries match. // https://drafts.csswg.org/mediaqueries-4/#mq-list if self.media_queries.is_empty() { - return true; + return KleeneValue::True; } - - computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { - self.media_queries.iter().any(|mq| { - let mut query_match = if mq.media_type.matches(device.media_type()) { - mq.condition - .as_ref() - .map_or(KleeneValue::True, |c| c.matches(context)) - } else { - KleeneValue::False - }; - - // Apply the logical NOT qualifier to the result - if matches!(mq.qualifier, Some(Qualifier::Not)) { - query_match = !query_match; - } - query_match.to_bool(/* unknown = */ false) - }) + KleeneValue::any(self.media_queries.iter(), |mq| { + let mut query_match = if mq.media_type.matches(context.device().media_type()) { + mq.condition + .as_ref() + .map_or(KleeneValue::True, |c| c.matches(context, custom)) + } else { + KleeneValue::False + }; + // Apply the logical NOT qualifier to the result + if matches!(mq.qualifier, Some(Qualifier::Not)) { + query_match = !query_match; + } + query_match }) } diff --git a/servo/components/style/queries/condition.rs b/servo/components/style/queries/condition.rs @@ -9,7 +9,8 @@ use super::{FeatureFlags, FeatureType, QueryFeatureExpression}; use crate::custom_properties; -use crate::values::{computed, AtomString}; +use crate::stylesheets::CustomMediaEvaluator; +use crate::values::{computed, AtomString, DashedIdent}; use crate::{error_reporting::ContextualParseError, parser::ParserContext}; use cssparser::{Parser, SourcePosition, Token}; use selectors::kleene_value::KleeneValue; @@ -218,6 +219,8 @@ impl ToCss for MozPrefFeature { pub enum QueryCondition { /// A simple feature expression, implicitly parenthesized. Feature(QueryFeatureExpression), + /// A custom media query reference in a boolean context, implicitly parenthesized. + Custom(DashedIdent), /// A negation of a condition. Not(Box<QueryCondition>), /// A set of joint operations. @@ -241,6 +244,11 @@ impl ToCss for QueryCondition { // NOTE(emilio): QueryFeatureExpression already includes the // parenthesis. QueryCondition::Feature(ref f) => f.to_css(dest), + QueryCondition::Custom(ref name) => { + dest.write_char('(')?; + name.to_css(dest)?; + dest.write_char(')') + }, QueryCondition::Not(ref c) => { dest.write_str("not ")?; c.to_css(dest) @@ -297,8 +305,11 @@ impl QueryCondition { { visitor(self); match *self { - Self::Feature(..) | Self::GeneralEnclosed(..) | Self::Style(..) | Self::MozPref(..) => { - }, + Self::Custom(..) + | Self::Feature(..) + | Self::GeneralEnclosed(..) + | Self::Style(..) + | Self::MozPref(..) => {}, Self::Not(ref cond) => cond.visit(visitor), Self::Operation(ref conds, _op) => { for cond in conds.iter() { @@ -475,36 +486,27 @@ impl QueryCondition { /// https://drafts.csswg.org/mediaqueries/#typedef-general-enclosed /// Kleene 3-valued logic is adopted here due to the introduction of /// <general-enclosed>. - pub fn matches(&self, context: &computed::Context) -> KleeneValue { + pub fn matches( + &self, + context: &computed::Context, + custom: &mut CustomMediaEvaluator, + ) -> KleeneValue { match *self { + QueryCondition::Custom(ref f) => custom.matches(f, context), QueryCondition::Feature(ref f) => f.matches(context), QueryCondition::GeneralEnclosed(_) => KleeneValue::Unknown, - QueryCondition::InParens(ref c) => c.matches(context), - QueryCondition::Not(ref c) => !c.matches(context), + QueryCondition::InParens(ref c) => c.matches(context, custom), + QueryCondition::Not(ref c) => !c.matches(context, custom), QueryCondition::Style(ref c) => c.matches(context), QueryCondition::MozPref(ref c) => c.matches(context), QueryCondition::Operation(ref conditions, op) => { debug_assert!(!conditions.is_empty(), "We never create an empty op"); match op { Operator::And => { - let mut result = KleeneValue::True; - for c in conditions.iter() { - result &= c.matches(context); - if result == KleeneValue::False { - break; - } - } - result + KleeneValue::any_false(conditions.iter(), |c| c.matches(context, custom)) }, Operator::Or => { - let mut result = KleeneValue::False; - for c in conditions.iter() { - result |= c.matches(context); - if result == KleeneValue::True { - break; - } - } - result + KleeneValue::any(conditions.iter(), |c| c.matches(context, custom)) }, } }, diff --git a/servo/components/style/queries/feature_expression.rs b/servo/components/style/queries/feature_expression.rs @@ -371,22 +371,6 @@ impl QueryFeatureExpression { self.feature().flags } - /// Parse a feature expression of the form: - /// - /// ``` - /// (media-feature: media-value) - /// ``` - pub fn parse<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - feature_type: FeatureType, - ) -> Result<Self, ParseError<'i>> { - input.expect_parenthesis_block()?; - input.parse_nested_block(|input| { - Self::parse_in_parenthesis_block(context, input, feature_type) - }) - } - fn parse_feature_name<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, diff --git a/servo/components/style/stylesheet_set.rs b/servo/components/style/stylesheet_set.rs @@ -9,7 +9,9 @@ use crate::invalidation::stylesheets::{RuleChangeKind, StylesheetInvalidationSet use crate::media_queries::Device; use crate::selector_parser::SnapshotMap; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{CssRule, CssRuleRef, Origin, OriginSet, PerOrigin, StylesheetInDocument}; +use crate::stylesheets::{ + CssRule, CssRuleRef, CustomMediaMap, Origin, OriginSet, PerOrigin, StylesheetInDocument, +}; use std::mem; /// Entry for a StylesheetSet. @@ -341,12 +343,13 @@ macro_rules! sheet_set_methods { fn collect_invalidations_for( &mut self, device: Option<&Device>, + custom_media: &CustomMediaMap, sheet: &S, guard: &SharedRwLockReadGuard, ) { if let Some(device) = device { self.invalidations - .collect_invalidations_for(device, sheet, guard); + .collect_invalidations_for(device, custom_media, sheet, guard); } } @@ -356,11 +359,12 @@ macro_rules! sheet_set_methods { pub fn append_stylesheet( &mut self, device: Option<&Device>, + custom_media: &CustomMediaMap, sheet: S, guard: &SharedRwLockReadGuard, ) { debug!(concat!($set_name, "::append_stylesheet")); - self.collect_invalidations_for(device, &sheet, guard); + self.collect_invalidations_for(device, custom_media, &sheet, guard); let collection = self.collection_for(&sheet, guard); collection.append(sheet); } @@ -369,12 +373,13 @@ macro_rules! sheet_set_methods { pub fn insert_stylesheet_before( &mut self, device: Option<&Device>, + custom_media: &CustomMediaMap, sheet: S, before_sheet: S, guard: &SharedRwLockReadGuard, ) { debug!(concat!($set_name, "::insert_stylesheet_before")); - self.collect_invalidations_for(device, &sheet, guard); + self.collect_invalidations_for(device, custom_media, &sheet, guard); let collection = self.collection_for(&sheet, guard); collection.insert_before(sheet, &before_sheet); @@ -384,11 +389,12 @@ macro_rules! sheet_set_methods { pub fn remove_stylesheet( &mut self, device: Option<&Device>, + custom_media: &CustomMediaMap, sheet: S, guard: &SharedRwLockReadGuard, ) { debug!(concat!($set_name, "::remove_stylesheet")); - self.collect_invalidations_for(device, &sheet, guard); + self.collect_invalidations_for(device, custom_media, &sheet, guard); let collection = self.collection_for(&sheet, guard); collection.remove(&sheet) @@ -399,6 +405,7 @@ macro_rules! sheet_set_methods { pub fn rule_changed( &mut self, device: Option<&Device>, + custom_media: &CustomMediaMap, sheet: &S, rule: &CssRule, guard: &SharedRwLockReadGuard, @@ -413,6 +420,7 @@ macro_rules! sheet_set_methods { guard, device, quirks_mode, + custom_media, change_kind, ancestors, ); diff --git a/servo/components/style/stylesheets/container_rule.rs b/servo/components/style/stylesheets/container_rule.rs @@ -17,7 +17,7 @@ use crate::queries::{FeatureType, QueryCondition}; use crate::shared_lock::{ DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard, }; -use crate::stylesheets::CssRules; +use crate::stylesheets::{CssRules, CustomMediaEvaluator}; use crate::stylist::Stylist; use crate::values::computed::{CSSPixelLength, ContainerType, Context, Ratio}; use crate::values::specified::ContainerName; @@ -270,7 +270,9 @@ impl ContainerCondition { info, size_query_container_lookup, |context| { - let matches = self.condition.matches(context); + let matches = self + .condition + .matches(context, &mut CustomMediaEvaluator::none()); if context .style() .flags() diff --git a/servo/components/style/stylesheets/media_rule.rs b/servo/components/style/stylesheets/media_rule.rs @@ -7,15 +7,20 @@ //! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media use crate::media_queries::MediaList; +use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet}; use crate::shared_lock::{DeepCloneWithLock, Locked}; use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard}; use crate::stylesheets::CssRules; +use crate::values::{computed, DashedIdent}; +use crate::Atom; +use cssparser::Parser; use cssparser::SourceLocation; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; +use selectors::kleene_value::KleeneValue; use servo_arc::Arc; use std::fmt::{self, Write}; -use style_traits::{CssStringWriter, CssWriter, ToCss}; +use style_traits::{CssStringWriter, CssWriter, ParseError, ToCss}; /// An [`@media`][media] rule. /// @@ -63,3 +68,125 @@ impl DeepCloneWithLock for MediaRule { } } } + +/// The condition associated to a custom-media query. +#[derive(Debug, ToShmem, Clone, MallocSizeOf)] +pub enum CustomMediaCondition { + /// Unconditionally true. + True, + /// Unconditionally false. + False, + /// A MediaList. + MediaList(#[ignore_malloc_size_of = "Arc"] Arc<Locked<MediaList>>), +} + +impl CustomMediaCondition { + /// Parses the possible keywords for this condition. + pub(crate) fn parse_keyword<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { + Ok(try_match_ident_ignore_ascii_case! { input, + "true" => Self::True, + "false" => Self::False, + }) + } +} + +impl DeepCloneWithLock for CustomMediaCondition { + fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self { + match self { + Self::True => Self::True, + Self::False => Self::False, + Self::MediaList(ref m) => { + Self::MediaList(Arc::new(lock.wrap(m.read_with(guard).clone()))) + }, + } + } +} + +/// A `@custom-media` rule. +/// https://drafts.csswg.org/mediaqueries-5/#custom-mq +#[derive(Debug, ToShmem)] +pub struct CustomMediaRule { + /// The name of the custom media rule. + pub name: DashedIdent, + /// The list of media conditions used by this media rule. + pub condition: CustomMediaCondition, + /// The source position where this media rule was found. + pub source_location: SourceLocation, +} + +impl DeepCloneWithLock for CustomMediaRule { + fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self { + Self { + name: self.name.clone(), + condition: self.condition.deep_clone_with_lock(lock, guard), + source_location: self.source_location.clone(), + } + } +} + +impl ToCssWithGuard for CustomMediaRule { + // Serialization of MediaRule is not specced. + // https://drafts.csswg.org/cssom/#serialize-a-css-rule CSSMediaRule + fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@custom-media ")?; + self.name.to_css(&mut CssWriter::new(dest))?; + dest.write_char(' ')?; + match self.condition { + CustomMediaCondition::True => dest.write_str("true"), + CustomMediaCondition::False => dest.write_str("false"), + CustomMediaCondition::MediaList(ref m) => { + m.read_with(guard).to_css(&mut CssWriter::new(dest)) + }, + } + } +} + +/// The currently effective @custom-media conditions. +pub type CustomMediaMap = PrecomputedHashMap<Atom, CustomMediaCondition>; + +/// A struct that can evaluate custom media conditions. +pub struct CustomMediaEvaluator<'a> { + map: Option<(&'a CustomMediaMap, &'a SharedRwLockReadGuard<'a>)>, + /// Set of media queries we're currently evaluating, needed for cycle detection. + currently_evaluating: PrecomputedHashSet<Atom>, +} + +impl<'a> CustomMediaEvaluator<'a> { + /// Construct a new custom media evaluator with the given map and guard. + pub fn new(map: &'a CustomMediaMap, guard: &'a SharedRwLockReadGuard<'a>) -> Self { + Self { + map: Some((map, guard)), + currently_evaluating: Default::default(), + } + } + + /// Returns an evaluator that can't evaluate custom queries. + pub fn none() -> Self { + Self { + map: None, + currently_evaluating: Default::default(), + } + } + + /// Evaluates a custom media query. + pub fn matches(&mut self, ident: &DashedIdent, context: &computed::Context) -> KleeneValue { + let Some((map, guard)) = self.map else { + return KleeneValue::Unknown; + }; + let Some(condition) = map.get(&ident.0) else { + return KleeneValue::Unknown; + }; + let media = match condition { + CustomMediaCondition::True => return KleeneValue::True, + CustomMediaCondition::False => return KleeneValue::False, + CustomMediaCondition::MediaList(ref m) => m, + }; + if !self.currently_evaluating.insert(ident.0.clone()) { + // Found a cycle while evaluating this rule. + return KleeneValue::False; + } + let result = media.read_with(guard).matches(context, self); + self.currently_evaluating.remove(&ident.0); + result.into() + } +} diff --git a/servo/components/style/stylesheets/mod.rs b/servo/components/style/stylesheets/mod.rs @@ -61,7 +61,9 @@ pub use self::keyframes_rule::KeyframesRule; pub use self::layer_rule::{LayerBlockRule, LayerStatementRule}; pub use self::loader::StylesheetLoader; pub use self::margin_rule::{MarginRule, MarginRuleType}; -pub use self::media_rule::MediaRule; +pub use self::media_rule::{ + CustomMediaCondition, CustomMediaEvaluator, CustomMediaMap, CustomMediaRule, MediaRule, +}; pub use self::namespace_rule::NamespaceRule; pub use self::nested_declarations_rule::NestedDeclarationsRule; pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter}; @@ -331,6 +333,7 @@ pub enum CssRule { Namespace(Arc<NamespaceRule>), Import(Arc<Locked<ImportRule>>), Media(Arc<MediaRule>), + CustomMedia(Arc<CustomMediaRule>), Container(Arc<ContainerRule>), FontFace(Arc<Locked<FontFaceRule>>), FontFeatureValues(Arc<FontFeatureValuesRule>), @@ -369,6 +372,10 @@ impl CssRule { CssRule::Media(ref arc) => { arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) }, + CssRule::CustomMedia(ref arc) => { + // Measurement of other fields might be added later. + arc.unconditional_shallow_size_of(ops) + }, CssRule::Container(ref arc) => { arc.unconditional_shallow_size_of(ops) + arc.size_of(guard, ops) }, @@ -445,6 +452,7 @@ pub enum CssRuleRef<'a> { Namespace(&'a NamespaceRule), Import(&'a LockedImportRule), Media(&'a MediaRule), + CustomMedia(&'a CustomMediaRule), Container(&'a ContainerRule), FontFace(&'a LockedFontFaceRule), FontFeatureValues(&'a FontFeatureValuesRule), @@ -471,6 +479,7 @@ impl<'a> From<&'a CssRule> for CssRuleRef<'a> { CssRule::Namespace(r) => CssRuleRef::Namespace(r.as_ref()), CssRule::Import(r) => CssRuleRef::Import(r.as_ref()), CssRule::Media(r) => CssRuleRef::Media(r.as_ref()), + CssRule::CustomMedia(r) => CssRuleRef::CustomMedia(r.as_ref()), CssRule::Container(r) => CssRuleRef::Container(r.as_ref()), CssRule::FontFace(r) => CssRuleRef::FontFace(r.as_ref()), CssRule::FontFeatureValues(r) => CssRuleRef::FontFeatureValues(r.as_ref()), @@ -533,6 +542,7 @@ pub enum CssRuleType { PositionTry = 23, // https://drafts.csswg.org/css-nesting-1/#nested-declarations-rule NestedDeclarations = 24, + CustomMedia = 25, } impl CssRuleType { @@ -610,6 +620,7 @@ impl CssRule { CssRule::Style(_) => CssRuleType::Style, CssRule::Import(_) => CssRuleType::Import, CssRule::Media(_) => CssRuleType::Media, + CssRule::CustomMedia(_) => CssRuleType::CustomMedia, CssRule::FontFace(_) => CssRuleType::FontFace, CssRule::FontFeatureValues(_) => CssRuleType::FontFeatureValues, CssRule::FontPaletteValues(_) => CssRuleType::FontPaletteValues, @@ -731,6 +742,9 @@ impl DeepCloneWithLock for CssRule { CssRule::Media(ref arc) => { CssRule::Media(Arc::new(arc.deep_clone_with_lock(lock, guard))) }, + CssRule::CustomMedia(ref arc) => { + CssRule::CustomMedia(Arc::new(arc.deep_clone_with_lock(lock, guard))) + }, CssRule::FontFace(ref arc) => { let rule = arc.read_with(guard); CssRule::FontFace(Arc::new(lock.wrap(rule.clone()))) @@ -799,6 +813,7 @@ impl ToCssWithGuard for CssRule { CssRule::Keyframes(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Margin(ref rule) => rule.to_css(guard, dest), CssRule::Media(ref rule) => rule.to_css(guard, dest), + CssRule::CustomMedia(ref rule) => rule.to_css(guard, dest), CssRule::Supports(ref rule) => rule.to_css(guard, dest), CssRule::Page(ref lock) => lock.read_with(guard).to_css(guard, dest), CssRule::Property(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 @@ -26,10 +26,11 @@ use crate::stylesheets::layer_rule::{LayerBlockRule, LayerName, LayerStatementRu use crate::stylesheets::scope_rule::{ScopeBounds, ScopeRule}; use crate::stylesheets::supports_rule::SupportsCondition; use crate::stylesheets::{ - AllowImportRules, CorsMode, CssRule, CssRuleType, CssRuleTypes, CssRules, DocumentRule, - FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, MarginRule, MarginRuleType, - MediaRule, NamespaceRule, NestedDeclarationsRule, PageRule, PageSelectors, PositionTryRule, - RulesMutateError, StartingStyleRule, StyleRule, StylesheetLoader, SupportsRule, + AllowImportRules, CorsMode, CssRule, CssRuleType, CssRuleTypes, CssRules, CustomMediaCondition, + CustomMediaRule, DocumentRule, FontFeatureValuesRule, FontPaletteValuesRule, KeyframesRule, + MarginRule, MarginRuleType, MediaRule, NamespaceRule, NestedDeclarationsRule, PageRule, + PageSelectors, PositionTryRule, RulesMutateError, StartingStyleRule, StyleRule, + StylesheetLoader, SupportsRule, }; use crate::values::computed::font::FamilyName; use crate::values::{CssUrl, CustomIdent, DashedIdent, KeyframesName}; @@ -285,6 +286,8 @@ pub enum AtRulePrelude { StartingStyle, /// A @position-try prelude for Anchor Positioning. PositionTry(DashedIdent), + /// A @custom-media prelude. + CustomMedia(DashedIdent, CustomMediaCondition), } impl AtRulePrelude { @@ -295,6 +298,7 @@ impl AtRulePrelude { Self::FontPaletteValues(..) => "font-palette-values", Self::CounterStyle(..) => "counter-style", Self::Media(..) => "media", + Self::CustomMedia(..) => "custom-media", Self::Container(..) => "container", Self::Supports(..) => "supports", Self::Keyframes(..) => "keyframes", @@ -521,6 +525,7 @@ impl<'a, 'i> NestedRuleParser<'a, 'i> { | AtRulePrelude::Container(..) | AtRulePrelude::Document(..) | AtRulePrelude::Layer(..) + | AtRulePrelude::CustomMedia(..) | AtRulePrelude::Scope(..) | AtRulePrelude::StartingStyle => true, @@ -770,6 +775,15 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { let name = DashedIdent::parse(&self.context, input)?; AtRulePrelude::PositionTry(name) }, + "custom-media" if static_prefs::pref!("layout.css.custom-media.enabled") => { + let name = DashedIdent::parse(&self.context, input)?; + let condition = input.try_parse(CustomMediaCondition::parse_keyword).unwrap_or_else(|_| { + CustomMediaCondition::MediaList(Arc::new(self.shared_lock.wrap( + MediaList::parse(&self.context, input) + ))) + }); + AtRulePrelude::CustomMedia(name, condition) + }, _ => { if static_prefs::pref!("layout.css.margin-rules.enabled") { if let Some(margin_rule_type) = MarginRuleType::match_name(&name) { @@ -917,7 +931,9 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { source_location, })) }, - AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => { + AtRulePrelude::CustomMedia(..) + | AtRulePrelude::Import(..) + | AtRulePrelude::Namespace(..) => { // These rules don't have blocks. return Err(input.new_unexpected_token_error(cssparser::Token::CurlyBracketBlock)); }, @@ -956,6 +972,13 @@ impl<'a, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'i> { } let source_location = start.source_location(); let rule = match prelude { + AtRulePrelude::CustomMedia(name, condition) => { + CssRule::CustomMedia(Arc::new(CustomMediaRule { + name, + condition, + source_location, + })) + }, AtRulePrelude::Layer(names) => { if names.is_empty() { return Err(()); diff --git a/servo/components/style/stylesheets/rules_iterator.rs b/servo/components/style/stylesheets/rules_iterator.rs @@ -7,32 +7,45 @@ use crate::context::QuirksMode; use crate::media_queries::Device; use crate::shared_lock::SharedRwLockReadGuard; -use crate::stylesheets::{CssRule, CssRuleRef, DocumentRule, ImportRule, MediaRule, SupportsRule}; +use crate::stylesheets::{ + CssRule, CssRuleRef, CustomMediaEvaluator, CustomMediaMap, DocumentRule, ImportRule, MediaRule, + SupportsRule, +}; use smallvec::SmallVec; +use std::ops::Deref; use std::slice; /// An iterator over a list of rules. -pub struct RulesIterator<'a, 'b, C> +pub struct RulesIterator<'a, 'b, C, CMM> where 'b: 'a, C: NestedRuleIterationCondition + 'static, + CMM: Deref<Target = CustomMediaMap>, { device: &'a Device, quirks_mode: QuirksMode, + custom_media: CMM, guard: &'a SharedRwLockReadGuard<'b>, stack: SmallVec<[slice::Iter<'a, CssRule>; 3]>, _phantom: ::std::marker::PhantomData<C>, } -impl<'a, 'b, C> RulesIterator<'a, 'b, C> +impl<'a, 'b, C, CMM> RulesIterator<'a, 'b, C, CMM> where 'b: 'a, C: NestedRuleIterationCondition + 'static, + CMM: Deref<Target = CustomMediaMap>, { + /// Returns the custom media map passed at construction. + pub fn custom_media(&mut self) -> &mut CMM { + &mut self.custom_media + } + /// Creates a new `RulesIterator` to iterate over `rules`. pub fn new( device: &'a Device, quirks_mode: QuirksMode, + custom_media: CMM, guard: &'a SharedRwLockReadGuard<'b>, rules: slice::Iter<'a, CssRule>, ) -> Self { @@ -41,6 +54,7 @@ where Self { device, quirks_mode, + custom_media, guard, stack, _phantom: ::std::marker::PhantomData, @@ -57,6 +71,7 @@ where rule: &'a CssRule, device: &'a Device, quirks_mode: QuirksMode, + custom_media_map: &CustomMediaMap, guard: &'a SharedRwLockReadGuard<'_>, effective: &mut bool, ) -> Option<slice::Iter<'a, CssRule>> { @@ -65,6 +80,7 @@ where CssRule::Namespace(_) | CssRule::FontFace(_) | CssRule::CounterStyle(_) + | CssRule::CustomMedia(_) | CssRule::Keyframes(_) | CssRule::Margin(_) | CssRule::Property(_) @@ -87,7 +103,7 @@ where }, CssRule::Import(ref import_rule) => { let import_rule = import_rule.read_with(guard); - if !C::process_import(guard, device, quirks_mode, import_rule) { + if !C::process_import(guard, device, quirks_mode, custom_media_map, import_rule) { *effective = false; return None; } @@ -104,7 +120,7 @@ where Some(container_rule.rules.read_with(guard).0.iter()) }, CssRule::Media(ref media_rule) => { - if !C::process_media(guard, device, quirks_mode, media_rule) { + if !C::process_media(guard, device, quirks_mode, custom_media_map, media_rule) { *effective = false; return None; } @@ -124,10 +140,11 @@ where } } -impl<'a, 'b, C> Iterator for RulesIterator<'a, 'b, C> +impl<'a, 'b, C, CMM> Iterator for RulesIterator<'a, 'b, C, CMM> where 'b: 'a, C: NestedRuleIterationCondition + 'static, + CMM: Deref<Target = CustomMediaMap>, { type Item = &'a CssRule; @@ -149,6 +166,7 @@ where rule, self.device, self.quirks_mode, + &self.custom_media, self.guard, &mut effective, ); @@ -176,6 +194,7 @@ pub trait NestedRuleIterationCondition { guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, + custom_media_map: &CustomMediaMap, rule: &ImportRule, ) -> bool; @@ -184,6 +203,7 @@ pub trait NestedRuleIterationCondition { guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, + custom_media_map: &CustomMediaMap, rule: &MediaRule, ) -> bool; @@ -214,18 +234,19 @@ impl EffectiveRules { guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, + custom_media_map: &CustomMediaMap, rule: &CssRuleRef, ) -> bool { match *rule { CssRuleRef::Import(import_rule) => { let import_rule = import_rule.read_with(guard); - Self::process_import(guard, device, quirks_mode, import_rule) + Self::process_import(guard, device, quirks_mode, custom_media_map, import_rule) }, CssRuleRef::Document(doc_rule) => { Self::process_document(guard, device, quirks_mode, doc_rule) }, CssRuleRef::Media(media_rule) => { - Self::process_media(guard, device, quirks_mode, media_rule) + Self::process_media(guard, device, quirks_mode, custom_media_map, media_rule) }, CssRuleRef::Supports(supports_rule) => { Self::process_supports(guard, device, quirks_mode, supports_rule) @@ -240,10 +261,15 @@ impl NestedRuleIterationCondition for EffectiveRules { guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, + custom_media_map: &CustomMediaMap, rule: &ImportRule, ) -> bool { match rule.stylesheet.media(guard) { - Some(m) => m.evaluate(device, quirks_mode), + Some(m) => m.evaluate( + device, + quirks_mode, + &mut CustomMediaEvaluator::new(custom_media_map, guard), + ), None => true, } } @@ -252,11 +278,14 @@ impl NestedRuleIterationCondition for EffectiveRules { guard: &SharedRwLockReadGuard, device: &Device, quirks_mode: QuirksMode, + custom_media_map: &CustomMediaMap, rule: &MediaRule, ) -> bool { - rule.media_queries - .read_with(guard) - .evaluate(device, quirks_mode) + rule.media_queries.read_with(guard).evaluate( + device, + quirks_mode, + &mut CustomMediaEvaluator::new(custom_media_map, guard), + ) } fn process_document( @@ -286,12 +315,19 @@ impl NestedRuleIterationCondition for AllRules { _: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, + _: &CustomMediaMap, _: &ImportRule, ) -> bool { true } - fn process_media(_: &SharedRwLockReadGuard, _: &Device, _: QuirksMode, _: &MediaRule) -> bool { + fn process_media( + _: &SharedRwLockReadGuard, + _: &Device, + _: QuirksMode, + _: &CustomMediaMap, + _: &MediaRule, + ) -> bool { true } @@ -317,19 +353,35 @@ impl NestedRuleIterationCondition for AllRules { /// An iterator over all the effective rules of a stylesheet. /// /// NOTE: This iterator recurses into `@import` rules. -pub type EffectiveRulesIterator<'a, 'b> = RulesIterator<'a, 'b, EffectiveRules>; +pub type EffectiveRulesIterator<'a, 'b, CMM> = RulesIterator<'a, 'b, EffectiveRules, CMM>; -impl<'a, 'b> EffectiveRulesIterator<'a, 'b> { +impl<'a, 'b, CMM> EffectiveRulesIterator<'a, 'b, CMM> +where + CMM: Deref<Target = CustomMediaMap>, +{ /// Returns an iterator over the effective children of a rule, even if /// `rule` itself is not effective. pub fn effective_children( device: &'a Device, quirks_mode: QuirksMode, + custom_media_map: CMM, guard: &'a SharedRwLockReadGuard<'b>, rule: &'a CssRule, ) -> Self { - let children = - RulesIterator::<AllRules>::children(rule, device, quirks_mode, guard, &mut false); - EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter())) + let children = RulesIterator::<AllRules, CMM>::children( + rule, + device, + quirks_mode, + &custom_media_map, + guard, + &mut false, + ); + EffectiveRulesIterator::new( + device, + quirks_mode, + custom_media_map, + guard, + children.unwrap_or([].iter()), + ) } } diff --git a/servo/components/style/stylesheets/stylesheet.rs b/servo/components/style/stylesheets/stylesheet.rs @@ -12,7 +12,9 @@ use crate::stylesheets::loader::StylesheetLoader; use crate::stylesheets::rule_parser::{State, TopLevelRuleParser}; use crate::stylesheets::rules_iterator::{EffectiveRules, EffectiveRulesIterator}; use crate::stylesheets::rules_iterator::{NestedRuleIterationCondition, RulesIterator}; -use crate::stylesheets::{CssRule, CssRules, Origin, UrlExtraData}; +use crate::stylesheets::{ + CssRule, CssRules, CustomMediaEvaluator, CustomMediaMap, Origin, UrlExtraData, +}; use crate::use_counters::UseCounters; use crate::{Namespace, Prefix}; use cssparser::{Parser, ParserInput, StyleSheetParser}; @@ -20,6 +22,7 @@ use cssparser::{Parser, ParserInput, StyleSheetParser}; use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf}; use rustc_hash::FxHashMap; use servo_arc::Arc; +use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use style_traits::ParsingMode; @@ -163,26 +166,35 @@ impl StylesheetContents { /// Return an iterator using the condition `C`. #[inline] - pub fn iter_rules<'a, 'b, C>( + pub fn iter_rules<'a, 'b, C, CMM>( &'a self, device: &'a Device, + custom_media: CMM, guard: &'a SharedRwLockReadGuard<'b>, - ) -> RulesIterator<'a, 'b, C> + ) -> RulesIterator<'a, 'b, C, CMM> where C: NestedRuleIterationCondition, + CMM: Deref<Target = CustomMediaMap>, { - RulesIterator::new(device, self.quirks_mode, guard, self.rules(guard).iter()) + RulesIterator::new( + device, + self.quirks_mode, + custom_media, + guard, + self.rules(guard).iter(), + ) } /// Return an iterator over the effective rules within the style-sheet, as /// according to the supplied `Device`. #[inline] - pub fn effective_rules<'a, 'b>( + pub fn effective_rules<'a, 'b, CMM: Deref<Target = CustomMediaMap>>( &'a self, device: &'a Device, + custom_media: CMM, guard: &'a SharedRwLockReadGuard<'b>, - ) -> EffectiveRulesIterator<'a, 'b> { - self.iter_rules::<EffectiveRules>(device, guard) + ) -> EffectiveRulesIterator<'a, 'b, CMM> { + self.iter_rules::<EffectiveRules, CMM>(device, custom_media, guard) } /// Perform a deep clone, of this stylesheet, with an explicit URL data if needed. @@ -239,11 +251,21 @@ pub trait StylesheetInDocument: ::std::fmt::Debug { fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents; /// Returns whether the style-sheet applies for the current device. - fn is_effective_for_device(&self, device: &Device, guard: &SharedRwLockReadGuard) -> bool { - match self.media(guard) { - Some(medialist) => medialist.evaluate(device, self.contents(guard).quirks_mode), - None => true, - } + fn is_effective_for_device( + &self, + device: &Device, + custom_media: &CustomMediaMap, + guard: &SharedRwLockReadGuard, + ) -> bool { + let media = match self.media(guard) { + Some(m) => m, + None => return true, + }; + media.evaluate( + device, + self.contents(guard).quirks_mode, + &mut CustomMediaEvaluator::new(custom_media, guard), + ) } /// Return the implicit scope root for this stylesheet, if one exists. @@ -334,6 +356,7 @@ impl SanitizationKind { match *rule { CssRule::Document(..) | CssRule::Media(..) | + CssRule::CustomMedia(..) | CssRule::Supports(..) | CssRule::Import(..) | CssRule::Container(..) | diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs @@ -52,8 +52,10 @@ use crate::stylesheets::{ PagePseudoClassFlags, PositionTryRule, }; use crate::stylesheets::{ - CssRule, CssRuleRef, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter, StylesheetContents, StylesheetInDocument + CssRule, CssRuleRef, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, + PerOriginIter, StylesheetContents, StylesheetInDocument, }; +use crate::stylesheets::{CustomMediaEvaluator, CustomMediaMap}; use crate::values::computed::DashedIdentAndOrTryTactic; use crate::values::specified::position::PositionTryFallbacksTryTactic; use crate::values::{computed, AtomIdent}; @@ -180,6 +182,7 @@ where } let mut key = CascadeDataCacheKey::default(); + let mut custom_media_map = CustomMediaMap::default(); for sheet in collection.sheets() { CascadeData::collect_applicable_media_query_results_into( device, @@ -187,6 +190,7 @@ where guard, &mut key.media_query_results, &mut key.contents, + &mut custom_media_map, ) } @@ -407,6 +411,15 @@ impl DocumentCascadeData { } } + fn custom_media_for_sheet( + &self, + s: &StylistSheet, + guard: &SharedRwLockReadGuard, + ) -> &CustomMediaMap { + let origin = s.contents(guard).origin; + &self.borrow_for_origin(origin).custom_media + } + /// Rebuild the cascade data for the given document stylesheets, and /// optionally with a set of user agent stylesheets. Returns Err(..) /// to signify OOM. @@ -927,17 +940,6 @@ impl Stylist { had_invalidations } - /// Insert a given stylesheet before another stylesheet in the document. - pub fn insert_stylesheet_before( - &mut self, - sheet: StylistSheet, - before_sheet: StylistSheet, - guard: &SharedRwLockReadGuard, - ) { - self.stylesheets - .insert_stylesheet_before(Some(&self.device), sheet, before_sheet, guard) - } - /// Marks a given stylesheet origin as dirty, due to, for example, changes /// in the declarations that affect a given rule. /// @@ -957,16 +959,35 @@ impl Stylist { self.stylesheets.has_changed() } + /// Insert a given stylesheet before another stylesheet in the document. + pub fn insert_stylesheet_before( + &mut self, + sheet: StylistSheet, + before_sheet: StylistSheet, + guard: &SharedRwLockReadGuard, + ) { + let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); + self.stylesheets.insert_stylesheet_before( + Some(&self.device), + custom_media, + sheet, + before_sheet, + guard, + ) + } + /// Appends a new stylesheet to the current set. pub fn append_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { + let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets - .append_stylesheet(Some(&self.device), sheet, guard) + .append_stylesheet(Some(&self.device), custom_media, sheet, guard) } /// Remove a given stylesheet to the current set. pub fn remove_stylesheet(&mut self, sheet: StylistSheet, guard: &SharedRwLockReadGuard) { + let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets - .remove_stylesheet(Some(&self.device), sheet, guard) + .remove_stylesheet(Some(&self.device), custom_media, sheet, guard) } /// Notify of a change of a given rule. @@ -978,8 +999,10 @@ impl Stylist { change_kind: RuleChangeKind, ancestors: &[CssRuleRef], ) { + let custom_media = self.cascade_data.custom_media_for_sheet(&sheet, guard); self.stylesheets.rule_changed( Some(&self.device), + custom_media, sheet, rule, guard, @@ -988,13 +1011,13 @@ impl Stylist { ) } - /// Appends a new stylesheet to the current set. + /// Get the total stylesheet count for a given origin. #[inline] pub fn sheet_count(&self, origin: Origin) -> usize { self.stylesheets.sheet_count(origin) } - /// Appends a new stylesheet to the current set. + /// Get the index-th stylesheet for a given origin. #[inline] pub fn sheet_at(&self, origin: Origin, index: usize) -> Option<&StylistSheet> { self.stylesheets.get(origin, index) @@ -3046,6 +3069,9 @@ pub struct CascadeData { #[ignore_malloc_size_of = "Arc"] custom_property_registrations: LayerOrderedMap<Arc<PropertyRegistration>>, + /// Custom media query registrations. + custom_media: CustomMediaMap, + /// A map from cascade layer name to layer order. layer_id: FxHashMap<LayerName, LayerId>, @@ -3129,6 +3155,7 @@ impl CascadeData { selectors_for_cache_revalidation: SelectorMap::new(), animations: Default::default(), custom_property_registrations: Default::default(), + custom_media: Default::default(), layer_id: Default::default(), layers: smallvec::smallvec![CascadeLayer::root()], container_conditions: smallvec::smallvec![ContainerConditionReference::none()], @@ -3186,6 +3213,11 @@ impl CascadeData { result } + /// Returns the custom media query map. + pub fn custom_media_map(&self) -> &CustomMediaMap { + &self.custom_media + } + /// Returns the invalidation map. pub fn invalidation_map(&self) -> &InvalidationMap { &self.invalidation_map @@ -3456,10 +3488,14 @@ impl CascadeData { guard: &SharedRwLockReadGuard, results: &mut Vec<MediaListKey>, contents_list: &mut StyleSheetContentList, + custom_media_map: &mut CustomMediaMap, ) where S: StylesheetInDocument + 'static, { - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { + if !stylesheet.enabled() { + return; + } + if !stylesheet.is_effective_for_device(device, &custom_media_map, guard) { return; } @@ -3472,8 +3508,15 @@ impl CascadeData { Arc::from_raw_addrefed(&*contents) })); - for rule in stylesheet.contents(guard).effective_rules(device, guard) { + let mut iter = stylesheet + .contents(guard) + .effective_rules(device, custom_media_map, guard); + while let Some(rule) = iter.next() { match *rule { + CssRule::CustomMedia(ref custom_media) => { + iter.custom_media() + .insert(custom_media.name.0.clone(), custom_media.condition.clone()); + }, CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); debug!(" + {:?}", import_rule.stylesheet.media(guard)); @@ -3908,10 +3951,11 @@ impl CascadeData { // effective. if cfg!(debug_assertions) { let mut effective = false; - let children = EffectiveRulesIterator::children( + let children = EffectiveRulesIterator::<&CustomMediaMap>::children( rule, device, quirks_mode, + &self.custom_media, guard, &mut effective, ); @@ -3922,9 +3966,14 @@ impl CascadeData { } let mut effective = false; - let children = - EffectiveRulesIterator::children(rule, device, quirks_mode, guard, &mut effective); - + let children = EffectiveRulesIterator::<&CustomMediaMap>::children( + rule, + device, + quirks_mode, + &self.custom_media, + guard, + &mut effective, + ); if !effective { continue; } @@ -4012,6 +4061,10 @@ impl CascadeData { CssRule::LayerBlock(ref rule) => { maybe_register_layers(self, rule.name.as_ref(), containing_rule_state); }, + CssRule::CustomMedia(ref custom_media) => { + self.custom_media + .insert(custom_media.name.0.clone(), custom_media.condition.clone()); + }, CssRule::LayerStatement(ref rule) => { for name in &*rule.names { maybe_register_layers(self, Some(name), containing_rule_state); @@ -4147,7 +4200,11 @@ impl CascadeData { where S: StylesheetInDocument + 'static, { - if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) { + if !stylesheet.enabled() { + return Ok(()); + } + + if !stylesheet.is_effective_for_device(device, &self.custom_media, guard) { return Ok(()); } @@ -4186,7 +4243,7 @@ impl CascadeData { { use crate::invalidation::media_queries::PotentiallyEffectiveMediaRules; - let effective_now = stylesheet.is_effective_for_device(device, guard); + let effective_now = stylesheet.is_effective_for_device(device, &self.custom_media, guard); let contents = stylesheet.contents(guard); let effective_then = self.effective_media_query_results.was_effective(contents); @@ -4205,7 +4262,10 @@ impl CascadeData { return true; } - let mut iter = contents.iter_rules::<PotentiallyEffectiveMediaRules>(device, guard); + // We don't need a custom media map for PotentiallyEffectiveMediaRules. + let custom_media = CustomMediaMap::default(); + let mut iter = + contents.iter_rules::<PotentiallyEffectiveMediaRules, _>(device, &custom_media, guard); while let Some(rule) = iter.next() { match *rule { CssRule::Style(..) @@ -4226,14 +4286,20 @@ impl CascadeData { | CssRule::FontFeatureValues(..) | CssRule::Scope(..) | CssRule::StartingStyle(..) + | CssRule::CustomMedia(..) | CssRule::PositionTry(..) => { - // Not affected by device changes. + // Not affected by device changes. @custom-media is handled by the potential + // @media rules referencing it being handled. continue; }, CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); let effective_now = match import_rule.stylesheet.media(guard) { - Some(m) => m.evaluate(device, quirks_mode), + Some(m) => m.evaluate( + device, + quirks_mode, + &mut CustomMediaEvaluator::new(&self.custom_media, guard), + ), None => true, }; let effective_then = self @@ -4255,7 +4321,11 @@ impl CascadeData { }, CssRule::Media(ref media_rule) => { let mq = media_rule.media_queries.read_with(guard); - let effective_now = mq.evaluate(device, quirks_mode); + let effective_now = mq.evaluate( + device, + quirks_mode, + &mut CustomMediaEvaluator::new(&self.custom_media, guard), + ); let effective_then = self .effective_media_query_results .was_effective(&**media_rule); @@ -4334,6 +4404,7 @@ impl CascadeData { self.layer_id.clear(); self.layers.clear(); self.layers.push(CascadeLayer::root()); + self.custom_media.clear(); self.container_conditions.clear(); self.container_conditions .push(ContainerConditionReference::none()); diff --git a/servo/components/style/values/specified/source_size_list.rs b/servo/components/style/values/specified/source_size_list.rs @@ -7,6 +7,7 @@ use crate::media_queries::Device; use crate::parser::{Parse, ParserContext}; use crate::queries::{FeatureType, QueryCondition}; +use crate::stylesheets::CustomMediaEvaluator; use crate::values::computed::{self, ToComputedValue}; use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength}; use app_units::Au; @@ -58,7 +59,7 @@ impl SourceSizeList { let matching_source_size = self.source_sizes.iter().find(|source_size| { source_size .condition - .matches(context) + .matches(context, &mut CustomMediaEvaluator::none()) .to_bool(/* unknown = */ false) }); diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs @@ -136,12 +136,13 @@ use style::stylesheets::scope_rule::{ImplicitScopeRoot, ScopeRootCandidate, Scop use style::stylesheets::supports_rule::parse_condition_or_declaration; use style::stylesheets::{ AllowImportRules, ContainerRule, CounterStyleRule, CssRule, CssRuleRef, CssRuleType, - CssRuleTypes, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, - FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule, - MarginRule, MediaRule, NamespaceRule, NestedDeclarationsRule, Origin, OriginSet, - PagePseudoClassFlags, PageRule, PositionTryRule, PropertyRule, SanitizationData, - SanitizationKind, ScopeRule, StartingStyleRule, StyleRule, StylesheetContents, - StylesheetInDocument, StylesheetLoader as StyleStylesheetLoader, SupportsRule, UrlExtraData, + CssRuleTypes, CssRules, CustomMediaCondition, CustomMediaEvaluator, CustomMediaRule, + DocumentRule, FontFaceRule, FontFeatureValuesRule, FontPaletteValuesRule, ImportRule, + KeyframesRule, LayerBlockRule, LayerStatementRule, MarginRule, MediaRule, NamespaceRule, + NestedDeclarationsRule, Origin, OriginSet, PagePseudoClassFlags, PageRule, PositionTryRule, + PropertyRule, SanitizationData, SanitizationKind, ScopeRule, StartingStyleRule, StyleRule, + StylesheetContents, StylesheetInDocument, StylesheetLoader as StyleStylesheetLoader, + SupportsRule, UrlExtraData, }; use style::stylist::{ add_size_of_ua_cache, replace_parent_selector_with_implicit_scope, scope_root_candidates, @@ -1771,7 +1772,9 @@ pub unsafe extern "C" fn Servo_AuthorStyles_AppendStyleSheet( let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); let sheet = GeckoStyleSheet::new(sheet); - styles.stylesheets.append_stylesheet(None, sheet, &guard); + styles + .stylesheets + .append_stylesheet(None, styles.data.custom_media_map(), sheet, &guard); } #[no_mangle] @@ -1784,6 +1787,7 @@ pub unsafe extern "C" fn Servo_AuthorStyles_InsertStyleSheetBefore( let guard = global_style_data.shared_lock.read(); styles.stylesheets.insert_stylesheet_before( None, + styles.data.custom_media_map(), GeckoStyleSheet::new(sheet), GeckoStyleSheet::new(before_sheet), &guard, @@ -1797,9 +1801,12 @@ pub unsafe extern "C" fn Servo_AuthorStyles_RemoveStyleSheet( ) { let global_style_data = &*GLOBAL_STYLE_DATA; let guard = global_style_data.shared_lock.read(); - styles - .stylesheets - .remove_stylesheet(None, GeckoStyleSheet::new(sheet), &guard); + styles.stylesheets.remove_stylesheet( + None, + styles.data.custom_media_map(), + GeckoStyleSheet::new(sheet), + &guard, + ); } #[no_mangle] @@ -2460,6 +2467,13 @@ impl_basic_rule_funcs! { (Margin, MarginRule, MarginRule), changed: Servo_StyleSet_MarginRuleChanged, } +impl_basic_rule_funcs! { (CustomMedia, CustomMediaRule, CustomMediaRule), + getter: Servo_CssRules_GetCustomMediaRuleAt, + debug: Servo_CustomMediaRule_Debug, + to_css: Servo_CustomMediaRule_GetCssText, + changed: Servo_StyleSet_CustomMediaRuleChanged, +} + impl_basic_rule_funcs! { (Namespace, NamespaceRule, NamespaceRule), getter: Servo_CssRules_GetNamespaceRuleAt, debug: Servo_NamespaceRule_Debug, @@ -3281,6 +3295,23 @@ pub extern "C" fn Servo_MediaRule_GetMedia(rule: &MediaRule) -> Strong<LockedMed rule.media_queries.clone().into() } +/// If the condition is null, the true/false gets communicated via the out-param +#[no_mangle] +pub extern "C" fn Servo_CustomMediaRule_GetCondition( + rule: &CustomMediaRule, + value: Option<&mut bool>, +) -> Strong<LockedMediaList> { + let fixed_value = match rule.condition { + CustomMediaCondition::True => true, + CustomMediaCondition::False => false, + CustomMediaCondition::MediaList(ref list) => return list.clone().into(), + }; + if let Some(value) = value { + *value = fixed_value; + } + Strong::null() +} + #[no_mangle] pub extern "C" fn Servo_NamespaceRule_GetPrefix(rule: &NamespaceRule) -> *mut nsAtom { rule.prefix @@ -3304,6 +3335,11 @@ pub extern "C" fn Servo_MarginRule_GetName(rule: &MarginRule, out: &mut nsACStri } #[no_mangle] +pub extern "C" fn Servo_CustomMediaRule_GetName(rule: &CustomMediaRule) -> *mut nsAtom { + rule.name.0.as_ptr() +} + +#[no_mangle] pub extern "C" fn Servo_PageRule_GetStyle(rule: &LockedPageRule) -> Strong<LockedDeclarationBlock> { read_locked_arc(rule, |rule: &PageRule| rule.block.clone().into()) } @@ -5636,6 +5672,7 @@ pub extern "C" fn Servo_MediaList_Matches( list.evaluate( per_doc_data.stylist.device(), per_doc_data.stylist.quirks_mode(), + &mut CustomMediaEvaluator::none(), ) }) } diff --git a/testing/web-platform/meta/css/__dir__.ini b/testing/web-platform/meta/css/__dir__.ini @@ -1 +1 @@ -prefs: [dom.customHighlightAPI.enabled:true,layout.css.basic-shape-shape.enabled:true,layout.css.style-queries.enabled:true,dom.hidden_until_found.enabled:true,dom.viewTransitions.enabled:true,gfx.font_rendering.fallback.async:false] +prefs: [dom.customHighlightAPI.enabled:true,layout.css.basic-shape-shape.enabled:true,layout.css.style-queries.enabled:true,dom.hidden_until_found.enabled:true,dom.viewTransitions.enabled:true,gfx.font_rendering.fallback.async:false,layout.css.custom-media.enabled:true] diff --git a/testing/web-platform/meta/css/mediaqueries/at-custom-media-cssom.html.ini b/testing/web-platform/meta/css/mediaqueries/at-custom-media-cssom.html.ini @@ -1,30 +0,0 @@ -[at-custom-media-cssom.html] - [CSSCustomMediaRule true] - expected: FAIL - - [CSSCustomMediaRule false] - expected: FAIL - - [CSSCustomMediaRule empty] - expected: FAIL - - [CSSCustomMediaRule cycle] - expected: FAIL - - [CSSCustomMediaRule simple] - expected: FAIL - - [CSSCustomMediaRule multiple media queries] - expected: FAIL - - [CSSCustomMediaRule change mediaText] - expected: FAIL - - [CSSCustomMediaRule appendMedium] - expected: FAIL - - [CSSCustomMediaRule deleteMedium] - expected: FAIL - - [CSSCustomMediaRule item] - expected: FAIL diff --git a/testing/web-platform/meta/css/mediaqueries/at-custom-media-parsing.html.ini b/testing/web-platform/meta/css/mediaqueries/at-custom-media-parsing.html.ini @@ -1,21 +0,0 @@ -[at-custom-media-parsing.html] - [@custom-media --query (max-width: 30em) is valid] - expected: FAIL - - [@custom-media --query (color), (hover) is valid] - expected: FAIL - - [@custom-media --query not all and (hover: hover) is valid] - expected: FAIL - - [@custom-media --query true is valid] - expected: FAIL - - [@custom-media --query false is valid] - expected: FAIL - - [@custom-media -- true is valid] - expected: FAIL - - [@custom-media --foo/* */(width > 42px) is valid] - expected: FAIL