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

Backport several style changes from Gecko (3)

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

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

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -71,10 +71,29 @@ static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
make_variable!(atom!("safe-area-inset-right"), get_safearea_inset_right),
];
fn get_titlebar_radius(device: &Device) -> VariableValue {
VariableValue::pixel(device.titlebar_radius())
}
fn get_menu_radius(device: &Device) -> VariableValue {
VariableValue::pixel(device.menu_radius())
}
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 2] = [
make_variable!(atom!("-moz-gtk-csd-titlebar-radius"), get_titlebar_radius),
make_variable!(atom!("-moz-gtk-menu-radius"), get_menu_radius),
];
impl CssEnvironment {
#[inline]
fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> {
let var = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?;
if let Some(var) = ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name) {
return Some((var.evaluator)(device));
}
if !device.is_chrome_document() {
return None;
}
let var = CHROME_ENVIRONMENT_VARIABLES.iter().find(|var| var.name == *name)?;
Some((var.evaluator)(device))
}
}

View file

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

View file

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

View file

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

View file

@ -93,7 +93,9 @@ impl Device {
document,
default_values: ComputedValues::default_values(doc),
root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
body_text_color: AtomicUsize::new(prefs.mColors.mDefault as usize),
// This gets updated when we see the <body>, so it doesn't really
// matter which color-scheme we look at here.
body_text_color: AtomicUsize::new(prefs.mLightColors.mDefault as usize),
used_root_font_size: AtomicBool::new(false),
used_font_metrics: AtomicBool::new(false),
used_viewport_size: AtomicBool::new(false),
@ -386,13 +388,18 @@ impl Device {
}
/// Returns the default background color.
pub fn default_background_color(&self) -> RGBA {
convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefaultBackground)
///
/// This is only for forced-colors/high-contrast, so looking at light colors
/// is ok.
pub fn default_background_color_for_forced_colors(&self) -> RGBA {
convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefaultBackground)
}
/// Returns the default foreground color.
pub fn default_color(&self) -> RGBA {
convert_nscolor_to_rgba(self.pref_sheet_prefs().mColors.mDefault)
///
/// See above for looking at light colors only.
pub fn default_color_for_forced_colors(&self) -> RGBA {
convert_nscolor_to_rgba(self.pref_sheet_prefs().mLightColors.mDefault)
}
/// Returns the current effective text zoom.
@ -439,4 +446,24 @@ impl Device {
bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32)
}
}
/// Returns the gtk titlebar radius in CSS pixels.
pub fn titlebar_radius(&self) -> f32 {
unsafe {
bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::TitlebarRadius as i32) as f32
}
}
/// Returns the gtk menu radius in CSS pixels.
pub fn menu_radius(&self) -> f32 {
unsafe {
bindings::Gecko_GetLookAndFeelInt(bindings::LookAndFeel_IntID::GtkMenuRadius as i32) as f32
}
}
/// Return whether the document is a chrome document.
#[inline]
pub fn is_chrome_document(&self) -> bool {
self.pref_sheet_prefs().mIsChrome
}
}

View file

@ -249,8 +249,7 @@ impl ::selectors::SelectorImpl for SelectorImpl {
type NonTSPseudoClass = NonTSPseudoClass;
fn should_collect_attr_hash(name: &AtomIdent) -> bool {
static_prefs::pref!("layout.css.bloom-filter-attribute-names.enabled") &&
!crate::bloom::is_attr_name_excluded_from_filter(name)
!crate::bloom::is_attr_name_excluded_from_filter(name)
}
}

View file

@ -1016,7 +1016,7 @@ impl<'le> TElement for GeckoElement<'le> {
#[inline]
fn namespace(&self) -> &WeakNamespace {
unsafe {
let namespace_manager = structs::nsContentUtils_sNameSpaceManager;
let namespace_manager = structs::nsNameSpaceManager_sInstance.mRawPtr;
WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr)
}
}

View file

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

View file

@ -430,7 +430,7 @@ fn tweak_when_ignoring_colors(
// widget background color's rgb channels but not alpha...
let alpha = alpha_channel(color, context);
if alpha != 0 {
let mut color = context.builder.device.default_background_color();
let mut color = context.builder.device.default_background_color_for_forced_colors();
color.alpha = alpha;
declarations_to_apply_unless_overriden
.push(PropertyDeclaration::BackgroundColor(color.into()))
@ -448,7 +448,7 @@ fn tweak_when_ignoring_colors(
// override this with a non-transparent color, then override it with
// the default color. Otherwise just let it inherit through.
if context.builder.get_parent_inherited_text().clone_color().alpha == 0 {
let color = context.builder.device.default_color();
let color = context.builder.device.default_color_for_forced_colors();
declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color(
specified::ColorPropertyValue(color.into()),
))
@ -794,12 +794,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
}
if self
.author_specified
.contains_any(LonghandIdSet::padding_properties())
{
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING);
}
if self
.author_specified
@ -859,13 +853,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
// We're using the same reset style as another element, and we'll skip
// applying the relevant properties. So we need to do the relevant
// bookkeeping here to keep these two bits correct.
// bookkeeping here to keep these bits correct.
//
// Note that all the properties involved are non-inherited, so we don't
// need to do anything else other than just copying the bits over.
let reset_props_bits = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND |
ComputedValueFlags::HAS_AUTHOR_SPECIFIED_PADDING;
builder.add_flags(cached_style.flags & reset_props_bits);
// Note that the border/background properties are non-inherited, so we
// don't need to do anything else other than just copying the bits over.
//
// When using this optimization, we also need to copy whether the old
// style specified viewport units / used font-relative lengths, this one
// would as well. It matches the same rules, so it is the right thing
// to do anyways, even if it's only used on inherited properties.
let bits_to_copy = ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND |
ComputedValueFlags::DEPENDS_ON_SELF_FONT_METRICS |
ComputedValueFlags::DEPENDS_ON_INHERITED_FONT_METRICS |
ComputedValueFlags::USES_VIEWPORT_UNITS;
builder.add_flags(cached_style.flags & bits_to_copy);
true
}
@ -916,12 +917,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
// we have a generic family to actually replace it with.
let prioritize_user_fonts = !use_document_fonts &&
default_font_type != GenericFontFamily::None &&
matches!(
generic,
GenericFontFamily::None |
GenericFontFamily::Fantasy |
GenericFontFamily::Cursive
);
!generic.valid_for_user_font_prioritization();
if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback {
// Nothing to do.

View file

@ -84,12 +84,6 @@ bitflags! {
/// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845
const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14;
/// Whether there are author-specified rules for padding-* properties.
///
/// FIXME(emilio): Try to merge this with BORDER_BACKGROUND, see
/// https://github.com/w3c/csswg-drafts/issues/4777
const HAS_AUTHOR_SPECIFIED_PADDING = 1 << 15;
/// Whether there are author-specified rules for `font-family`.
const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16;

View file

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

View file

@ -1049,7 +1049,7 @@ fn static_assert() {
% if member:
ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member};
% else:
ours.m${gecko_ffi_name} = others.m${gecko_ffi_name};
ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone();
% endif
}
}
@ -1183,7 +1183,7 @@ fn static_assert() {
<% skip_box_longhands= """display
animation-name animation-delay animation-duration
animation-direction animation-fill-mode animation-play-state
animation-iteration-count animation-timing-function
animation-iteration-count animation-timeline animation-timing-function
clear transition-duration transition-delay
transition-timing-function transition-property
-webkit-line-clamp""" %>
@ -1445,6 +1445,27 @@ fn static_assert() {
${impl_copy_animation_value('iteration_count', 'IterationCount')}
${impl_animation_or_transition_timing_function('animation')}
pub fn set_animation_timeline<I>(&mut self, v: I)
where
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
let v = v.into_iter();
debug_assert_ne!(v.len(), 0);
let input_len = v.len();
self.gecko.mAnimations.ensure_len(input_len);
self.gecko.mAnimationTimelineCount = input_len as u32;
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
gecko.mTimeline = servo;
}
}
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
self.gecko.mAnimations[index].mTimeline.clone()
}
${impl_animation_count('timeline', 'Timeline')}
${impl_copy_animation_value('timeline', 'Timeline')}
#[allow(non_snake_case)]
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
self.gecko.mLineClamp = match v {

View file

@ -320,6 +320,22 @@ ${helpers.predefined_type(
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-timeline",
"AnimationTimeline",
"computed::AnimationTimeline::auto()",
engines="gecko servo-2013 servo-2020",
servo_2013_pref="layout.2013.unimplemented",
servo_2020_pref="layout.2020.unimplemented",
initial_specified_value="specified::AnimationTimeline::auto()",
vector=True,
need_index=True,
animation_value_type="none",
gecko_pref="layout.css.scroll-linked-animations.enabled",
spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
${helpers.predefined_type(

View file

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

View file

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

View file

@ -945,18 +945,6 @@ impl LonghandIdSet {
&HAS_NO_EFFECT_ON_SCROLLBARS
}
/// Returns the set of padding properties for the purpose of disabling
/// native appearance.
#[inline]
pub fn padding_properties() -> &'static Self {
<% assert "padding" in logical_groups %>
${static_longhand_id_set(
"PADDING_PROPERTIES",
lambda p: p.logical_group == "padding"
)}
&PADDING_PROPERTIES
}
/// Returns the set of border properties for the purpose of disabling native
/// appearance.
#[inline]
@ -2921,7 +2909,7 @@ pub mod style_structs {
}
/// Returns true if animation properties are equal between styles, but without
/// considering keyframe data.
/// considering keyframe data and animation-timeline.
#[cfg(feature = "servo")]
pub fn animations_equals(&self, other: &Self) -> bool {
self.animation_name_iter().eq(other.animation_name_iter()) &&
@ -3098,27 +3086,76 @@ impl ComputedValues {
///
/// Note that the value will usually be the computed value, except for
/// colors, where it's resolved.
pub fn get_longhand_property_value<W>(
///
/// TODO(emilio): We should move all the special resolution from
/// nsComputedDOMStyle to ToResolvedValue instead.
pub fn get_resolved_value(
&self,
property_id: LonghandId,
dest: &mut CssWriter<W>
) -> fmt::Result
where
W: Write,
{
dest: &mut CssStringWriter,
) -> fmt::Result {
use crate::values::resolved::ToResolvedValue;
let mut dest = CssWriter::new(dest);
let context = resolved::Context {
style: self,
};
match property_id {
% for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()):
<% props = list(props) %>
${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => {
let value = match property_id {
% for prop in props:
LonghandId::${prop.camel_case} => self.clone_${prop.ident}(),
% endfor
_ => unsafe { debug_unreachable!() },
};
value.to_resolved_value(&context).to_css(&mut dest)
}
% endfor
}
}
/// Returns the given longhand's resolved value as a property declaration.
pub fn resolved_declaration(&self, property_id: LonghandId) -> PropertyDeclaration {
use crate::values::resolved::ToResolvedValue;
use crate::values::computed::ToComputedValue;
let context = resolved::Context {
style: self,
};
// TODO(emilio): Is it worth to merge branches here just like
// PropertyDeclaration::to_css does?
match property_id {
% for prop in data.longhands:
LonghandId::${prop.camel_case} => {
let value = self.clone_${prop.ident}();
value.to_resolved_value(&context).to_css(dest)
% for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()):
<% props = list(props) %>
${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => {
let value = match property_id {
% for prop in props:
LonghandId::${prop.camel_case} => self.clone_${prop.ident}(),
% endfor
_ => unsafe { debug_unreachable!() },
};
let resolved = value.to_resolved_value(&context);
let computed = ToResolvedValue::from_resolved_value(resolved);
let specified = ToComputedValue::from_computed_value(&computed);
% if props[0].boxed:
let specified = Box::new(specified);
% endif
% if len(props) == 1:
PropertyDeclaration::${props[0].camel_case}(specified)
% else:
unsafe {
let mut out = mem::MaybeUninit::uninit();
ptr::write(
out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified_type}>,
PropertyDeclarationVariantRepr {
tag: property_id as u16,
value: specified,
},
);
out.assume_init()
}
% endif
}
% endfor
}
@ -3195,9 +3232,9 @@ impl ComputedValues {
match property {
PropertyDeclarationId::Longhand(id) => {
let mut s = String::new();
self.get_longhand_property_value(
self.get_resolved_value(
id,
&mut CssWriter::new(&mut s)
&mut s
).unwrap();
s
}

View file

@ -189,11 +189,11 @@ macro_rules! try_parse_one {
sub_properties="animation-name animation-duration
animation-timing-function animation-delay
animation-iteration-count animation-direction
animation-fill-mode animation-play-state"
animation-fill-mode animation-play-state animation-timeline"
rule_types_allowed="Style"
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
<%
props = "name duration timing_function delay iteration_count \
props = "name timeline duration timing_function delay iteration_count \
direction fill_mode play_state".split()
%>
% for prop in props:
@ -210,6 +210,13 @@ macro_rules! try_parse_one {
% endfor
}
fn scroll_linked_animations_enabled() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
#[cfg(feature = "servo")]
return false;
}
fn parse_one_animation<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
@ -234,6 +241,9 @@ macro_rules! try_parse_one {
try_parse_one!(context, input, fill_mode, animation_fill_mode);
try_parse_one!(context, input, play_state, animation_play_state);
try_parse_one!(context, input, name, animation_name);
if scroll_linked_animations_enabled() {
try_parse_one!(context, input, timeline, animation_timeline);
}
parsed -= 1;
break
@ -280,22 +290,46 @@ macro_rules! try_parse_one {
// If any value list length is differs then we don't do a shorthand serialization
// either.
% for name in props[1:]:
% for name in props[2:]:
if len != self.animation_${name}.0.len() {
return Ok(())
}
% endfor
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
// None.
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
return Ok(());
}
for i in 0..len {
if i != 0 {
dest.write_str(", ")?;
}
% for name in props[1:]:
% for name in props[2:]:
self.animation_${name}.0[i].to_css(dest)?;
dest.write_str(" ")?;
% endfor
self.animation_name.0[i].to_css(dest)?;
// Based on the spec, the default values of other properties must be output in at
// least the cases necessary to distinguish an animation-name. The serialization
// order of animation-timeline is always later than animation-name, so it's fine
// to not serialize it if it is the default value. It's still possible to
// distinguish them (because we always serialize animation-name).
// https://drafts.csswg.org/css-animations-1/#animation
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
//
// Note: it's also fine to always serialize this. However, it seems Blink
// doesn't serialize default animation-timeline now, so we follow the same rule.
if let Some(ref timeline) = self.animation_timeline {
if !timeline.0[i].is_auto() {
dest.write_char(' ')?;
timeline.0[i].to_css(dest)?;
}
}
}
Ok(())
}

View file

@ -7,7 +7,6 @@
<%helpers:shorthand name="list-style"
engines="gecko servo-2013 servo-2020"
sub_properties="list-style-position list-style-image list-style-type"
derive_serialize="True"
spec="https://drafts.csswg.org/css-lists/#propdef-list-style">
use crate::properties::longhands::{list_style_image, list_style_position, list_style_type};
use crate::values::specified::Image;
@ -104,4 +103,43 @@
_ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
use longhands::list_style_position::SpecifiedValue as ListStylePosition;
use longhands::list_style_type::SpecifiedValue as ListStyleType;
use longhands::list_style_image::SpecifiedValue as ListStyleImage;
let mut have_one_non_initial_value = false;
#[cfg(any(feature = "gecko", feature = "servo-layout-2013"))]
let position_is_initial = self.list_style_position == &ListStylePosition::Outside;
#[cfg(feature = "servo-layout-2020")]
let position_is_initial = self.list_style_position == Some(&ListStylePosition::Outside);
if !position_is_initial {
self.list_style_position.to_css(dest)?;
have_one_non_initial_value = true;
}
if self.list_style_image != &ListStyleImage::None {
if have_one_non_initial_value {
dest.write_str(" ")?;
}
self.list_style_image.to_css(dest)?;
have_one_non_initial_value = true;
}
#[cfg(feature = "gecko")]
let type_is_initial = self.list_style_type == &ListStyleType::disc();
#[cfg(feature = "servo")]
let type_is_initial = self.list_style_type == &ListStyleType::Disc;
if !type_is_initial {
if have_one_non_initial_value {
dest.write_str(" ")?;
}
self.list_style_type.to_css(dest)?;
have_one_non_initial_value = true;
}
if !have_one_non_initial_value {
self.list_style_position.to_css(dest)?;
}
Ok(())
}
}
</%helpers:shorthand>

View file

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

View file

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

View file

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

View file

@ -190,12 +190,12 @@ impl Device {
}
/// Returns the default background color.
pub fn default_background_color(&self) -> RGBA {
pub fn default_background_color_for_forced_colors(&self) -> RGBA {
RGBA::new(255, 255, 255, 255)
}
/// Returns the default color color.
pub fn default_color(&self) -> RGBA {
/// Returns the default foreground color.
pub fn default_color_for_forced_colors(&self) -> RGBA {
RGBA::new(0, 0, 0, 255)
}
@ -220,6 +220,24 @@ impl Device {
_ => false,
}
}
/// Returns the gtk titlebar radius in CSS pixels.
/// TODO: implement this method.
pub fn titlebar_radius(&self) -> f32 {
0.0
}
/// Returns the gtk menu radius in CSS pixels.
/// TODO: implement this method.
pub fn menu_radius(&self) -> f32 {
0.0
}
/// Return whether the document is a chrome document.
#[inline]
pub fn is_chrome_document(&self) -> bool {
false
}
}
/// https://drafts.csswg.org/mediaqueries-4/#width

View file

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

View file

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

View file

@ -152,63 +152,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
}
}
/// https://html.spec.whatwg.org/multipage/#inert-subtrees
///
/// If -moz-inert is applied then add:
/// -moz-user-focus: none;
/// -moz-user-input: none;
/// -moz-user-modify: read-only;
/// user-select: none;
/// pointer-events: none;
/// cursor: default;
///
/// NOTE: dialog:-moz-topmost-modal-dialog is used to override above
/// rules to remove the inertness for the topmost modal dialog.
///
/// NOTE: If this or the pointer-events tweak is removed, then
/// minimal-xul.css and the scrollbar style caching need to be tweaked.
#[cfg(feature = "gecko")]
fn adjust_for_inert(&mut self) {
use crate::values::specified::ui::CursorKind;
use crate::values::specified::ui::UserSelect;
use properties::longhands::_moz_inert::computed_value::T as Inert;
use properties::longhands::_moz_user_focus::computed_value::T as UserFocus;
use properties::longhands::_moz_user_input::computed_value::T as UserInput;
use properties::longhands::_moz_user_modify::computed_value::T as UserModify;
use properties::longhands::cursor::computed_value::T as Cursor;
use properties::longhands::pointer_events::computed_value::T as PointerEvents;
let needs_update = {
let ui = self.style.get_inherited_ui();
if ui.clone__moz_inert() == Inert::None {
return;
}
ui.clone__moz_user_focus() != UserFocus::None ||
ui.clone__moz_user_input() != UserInput::None ||
ui.clone__moz_user_modify() != UserModify::ReadOnly ||
ui.clone_pointer_events() != PointerEvents::None ||
ui.clone_cursor().keyword != CursorKind::Default ||
ui.clone_cursor().images != Default::default()
};
if needs_update {
let ui = self.style.mutate_inherited_ui();
ui.set__moz_user_focus(UserFocus::None);
ui.set__moz_user_input(UserInput::None);
ui.set__moz_user_modify(UserModify::ReadOnly);
ui.set_pointer_events(PointerEvents::None);
ui.set_cursor(Cursor {
images: Default::default(),
keyword: CursorKind::Default,
});
}
if self.style.get_ui().clone_user_select() != UserSelect::None {
self.style.mutate_ui().set_user_select(UserSelect::None);
}
}
/// Whether we should skip any item-based display property blockification on
/// this element.
fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool
@ -909,7 +852,6 @@ impl<'a, 'b: 'a> StyleAdjuster<'a, 'b> {
#[cfg(feature = "gecko")]
{
self.adjust_for_appearance(element);
self.adjust_for_inert();
self.adjust_for_marker_pseudo();
}
self.set_bits();

View file

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

View file

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

View file

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

View file

@ -0,0 +1,235 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A [`@layer`][layer] urle.
//!
//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{DeepCloneParams, DeepCloneWithLock, Locked};
use crate::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
use crate::values::AtomIdent;
use super::CssRules;
use cssparser::{Parser, SourceLocation, ToCss as CssParserToCss, Token};
use servo_arc::Arc;
use smallvec::SmallVec;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, ToCss};
/// The order of a given layer.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct LayerOrder(u32);
impl LayerOrder {
/// The order of the root layer.
pub const fn root() -> Self {
Self(std::u32::MAX)
}
/// The first cascade layer order.
pub const fn first() -> Self {
Self(0)
}
/// Increment the cascade layer order.
#[inline]
pub fn inc(&mut self) {
self.0 += 1;
}
}
/// The id of a given layer, a sequentially-increasing identifier.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct LayerId(pub u32);
impl LayerId {
/// The id of the root layer.
pub const fn root() -> Self {
Self(0)
}
}
/// A `<layer-name>`: https://drafts.csswg.org/css-cascade-5/#typedef-layer-name
#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
pub struct LayerName(pub SmallVec<[AtomIdent; 1]>);
impl LayerName {
/// Returns an empty layer name (which isn't a valid final state, so caller
/// is responsible to fill up the name before use).
pub fn new_empty() -> Self {
Self(Default::default())
}
/// Returns a synthesized name for an anonymous layer.
pub fn new_anonymous() -> Self {
use std::sync::atomic::{AtomicUsize, Ordering};
static NEXT_ANONYMOUS_LAYER_NAME: AtomicUsize = AtomicUsize::new(0);
let mut name = SmallVec::new();
let next_id = NEXT_ANONYMOUS_LAYER_NAME.fetch_add(1, Ordering::Relaxed);
// The parens don't _technically_ prevent conflicts with authors, as
// authors could write escaped parens as part of the identifier, I
// think, but highly reduces the possibility.
name.push(AtomIdent::from(&*format!("-moz-anon-layer({})", next_id)));
LayerName(name)
}
/// Returns the names of the layers. That is, for a layer like `foo.bar`,
/// it'd return [foo, bar].
pub fn layer_names(&self) -> &[AtomIdent] {
&self.0
}
}
impl Parse for LayerName {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut result = SmallVec::new();
result.push(AtomIdent::from(&**input.expect_ident()?));
loop {
let next_name = input.try_parse(|input| -> Result<AtomIdent, ParseError<'i>> {
match input.next_including_whitespace()? {
Token::Delim('.') => {},
other => {
let t = other.clone();
return Err(input.new_unexpected_token_error(t));
},
}
let name = match input.next_including_whitespace()? {
Token::Ident(ref ident) => ident,
other => {
let t = other.clone();
return Err(input.new_unexpected_token_error(t));
},
};
Ok(AtomIdent::from(&**name))
});
match next_name {
Ok(name) => result.push(name),
Err(..) => break,
}
}
Ok(LayerName(result))
}
}
impl ToCss for LayerName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
let mut first = true;
for name in self.0.iter() {
if !first {
dest.write_char('.')?;
}
first = false;
name.to_css(dest)?;
}
Ok(())
}
}
/// The kind of layer rule this is.
#[derive(Debug, ToShmem)]
pub enum LayerRuleKind {
/// A block `@layer <name>? { ... }`
Block {
/// The layer name, or `None` if anonymous.
name: Option<LayerName>,
/// The nested rules.
rules: Arc<Locked<CssRules>>,
},
/// A statement `@layer <name>, <name>, <name>;`
Statement {
/// The list of layers to sort.
names: Vec<LayerName>,
},
}
/// A [`@layer`][layer] rule.
///
/// [layer]: https://drafts.csswg.org/css-cascade-5/#layering
#[derive(Debug, ToShmem)]
pub struct LayerRule {
/// The kind of layer rule we are.
pub kind: LayerRuleKind,
/// The source position where this media rule was found.
pub source_location: SourceLocation,
}
impl ToCssWithGuard for LayerRule {
fn to_css(
&self,
guard: &SharedRwLockReadGuard,
dest: &mut crate::str::CssStringWriter,
) -> fmt::Result {
dest.write_str("@layer")?;
match self.kind {
LayerRuleKind::Block {
ref name,
ref rules,
} => {
if let Some(ref name) = *name {
dest.write_char(' ')?;
name.to_css(&mut CssWriter::new(dest))?;
}
rules.read_with(guard).to_css_block(guard, dest)
},
LayerRuleKind::Statement { ref names } => {
let mut writer = CssWriter::new(dest);
let mut first = true;
for name in &**names {
if first {
writer.write_char(' ')?;
} else {
writer.write_str(", ")?;
}
first = false;
name.to_css(&mut writer)?;
}
dest.write_char(';')
},
}
}
}
impl DeepCloneWithLock for LayerRule {
fn deep_clone_with_lock(
&self,
lock: &SharedRwLock,
guard: &SharedRwLockReadGuard,
params: &DeepCloneParams,
) -> Self {
Self {
kind: match self.kind {
LayerRuleKind::Block {
ref name,
ref rules,
} => LayerRuleKind::Block {
name: name.clone(),
rules: Arc::new(
lock.wrap(
rules
.read_with(guard)
.deep_clone_with_lock(lock, guard, params),
),
),
},
LayerRuleKind::Statement { ref names } => LayerRuleKind::Statement {
names: names.clone(),
},
},
source_location: self.source_location.clone(),
}
}
}

View file

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

View file

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

View file

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

View file

@ -51,60 +51,68 @@ where
pub fn skip_children(&mut self) {
self.stack.pop();
}
}
fn children_of_rule<'a, C>(
rule: &'a CssRule,
device: &'a Device,
quirks_mode: QuirksMode,
guard: &'a SharedRwLockReadGuard<'_>,
effective: &mut bool,
) -> Option<slice::Iter<'a, CssRule>>
where
C: NestedRuleIterationCondition + 'static,
{
*effective = true;
match *rule {
CssRule::Namespace(_) |
CssRule::Style(_) |
CssRule::FontFace(_) |
CssRule::CounterStyle(_) |
CssRule::Viewport(_) |
CssRule::Keyframes(_) |
CssRule::Page(_) |
CssRule::FontFeatureValues(_) => None,
CssRule::Import(ref import_rule) => {
let import_rule = import_rule.read_with(guard);
if !C::process_import(guard, device, quirks_mode, import_rule) {
*effective = false;
return None;
/// Returns the children of `rule`, and whether `rule` is effective.
pub fn children(
rule: &'a CssRule,
device: &'a Device,
quirks_mode: QuirksMode,
guard: &'a SharedRwLockReadGuard<'_>,
effective: &mut bool,
) -> Option<slice::Iter<'a, CssRule>> {
*effective = true;
match *rule {
CssRule::Namespace(_) |
CssRule::Style(_) |
CssRule::FontFace(_) |
CssRule::CounterStyle(_) |
CssRule::Viewport(_) |
CssRule::Keyframes(_) |
CssRule::ScrollTimeline(_) |
CssRule::Page(_) |
CssRule::FontFeatureValues(_) => None,
CssRule::Import(ref import_rule) => {
let import_rule = import_rule.read_with(guard);
if !C::process_import(guard, device, quirks_mode, import_rule) {
*effective = false;
return None;
}
Some(import_rule.stylesheet.rules(guard).iter())
},
CssRule::Document(ref doc_rule) => {
let doc_rule = doc_rule.read_with(guard);
if !C::process_document(guard, device, quirks_mode, doc_rule) {
*effective = false;
return None;
}
Some(doc_rule.rules.read_with(guard).0.iter())
},
CssRule::Media(ref lock) => {
let media_rule = lock.read_with(guard);
if !C::process_media(guard, device, quirks_mode, media_rule) {
*effective = false;
return None;
}
Some(media_rule.rules.read_with(guard).0.iter())
},
CssRule::Supports(ref lock) => {
let supports_rule = lock.read_with(guard);
if !C::process_supports(guard, device, quirks_mode, supports_rule) {
*effective = false;
return None;
}
Some(supports_rule.rules.read_with(guard).0.iter())
},
CssRule::Layer(ref lock) => {
use crate::stylesheets::layer_rule::LayerRuleKind;
let layer_rule = lock.read_with(guard);
match layer_rule.kind {
LayerRuleKind::Block { ref rules, .. } => Some(rules.read_with(guard).0.iter()),
LayerRuleKind::Statement { .. } => None,
}
}
Some(import_rule.stylesheet.rules(guard).iter())
},
CssRule::Document(ref doc_rule) => {
let doc_rule = doc_rule.read_with(guard);
if !C::process_document(guard, device, quirks_mode, doc_rule) {
*effective = false;
return None;
}
Some(doc_rule.rules.read_with(guard).0.iter())
},
CssRule::Media(ref lock) => {
let media_rule = lock.read_with(guard);
if !C::process_media(guard, device, quirks_mode, media_rule) {
*effective = false;
return None;
}
Some(media_rule.rules.read_with(guard).0.iter())
},
CssRule::Supports(ref lock) => {
let supports_rule = lock.read_with(guard);
if !C::process_supports(guard, device, quirks_mode, supports_rule) {
*effective = false;
return None;
}
Some(supports_rule.rules.read_with(guard).0.iter())
},
}
}
}
@ -129,7 +137,7 @@ where
};
let mut effective = true;
let children = children_of_rule::<C>(
let children = Self::children(
rule,
self.device,
self.quirks_mode,
@ -315,7 +323,7 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> {
guard: &'a SharedRwLockReadGuard<'b>,
rule: &'a CssRule,
) -> Self {
let children = children_of_rule::<AllRules>(rule, device, quirks_mode, guard, &mut false);
let children = RulesIterator::<AllRules>::children(rule, device, quirks_mode, guard, &mut false);
EffectiveRulesIterator::new(device, quirks_mode, guard, children.unwrap_or([].iter()))
}
}

View file

@ -0,0 +1,327 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! scroll-timeline-at-rule: https://drafts.csswg.org/scroll-animations/#scroll-timeline-at-rule
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::specified::{LengthPercentage, Number};
use crate::values::{AtomIdent, TimelineName};
use cssparser::{AtRuleParser, CowRcStr, DeclarationParser, Parser, SourceLocation, Token};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// A [`@scroll-timeline`][descriptors] rule.
///
/// [descriptors] https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors
#[derive(Clone, Debug, ToShmem)]
pub struct ScrollTimelineRule {
/// The name of the current scroll timeline.
pub name: TimelineName,
/// The descriptors.
pub descriptors: ScrollTimelineDescriptors,
/// The line and column of the rule's source code.
pub source_location: SourceLocation,
}
impl ToCssWithGuard for ScrollTimelineRule {
fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
let mut dest = CssWriter::new(dest);
dest.write_str("@scroll-timeline ")?;
self.name.to_css(&mut dest)?;
dest.write_str(" { ")?;
self.descriptors.to_css(&mut dest)?;
dest.write_str("}")
}
}
/// The descriptors of @scroll-timeline.
///
/// https://drafts.csswg.org/scroll-animations/#scroll-timeline-descriptors
#[derive(Clone, Debug, Default, ToShmem)]
pub struct ScrollTimelineDescriptors {
/// The source of the current scroll timeline.
pub source: Option<Source>,
/// The orientation of the current scroll timeline.
pub orientation: Option<Orientation>,
/// The scroll timeline's scrollOffsets.
pub offsets: Option<ScrollOffsets>,
}
impl Parse for ScrollTimelineDescriptors {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
use crate::cssparser::DeclarationListParser;
use crate::error_reporting::ContextualParseError;
let mut descriptors = ScrollTimelineDescriptors::default();
let parser = ScrollTimelineDescriptorsParser {
context,
descriptors: &mut descriptors,
};
let mut iter = DeclarationListParser::new(input, parser);
while let Some(declaration) = iter.next() {
if let Err((error, slice)) = declaration {
let location = error.location;
let error = ContextualParseError::UnsupportedRule(slice, error);
context.log_css_error(location, error)
}
}
Ok(descriptors)
}
}
// Basically, this is used for the serialization of CSSScrollTimelineRule, so we follow the
// instructions in https://drafts.csswg.org/scroll-animations-1/#serialize-a-cssscrolltimelinerule.
impl ToCss for ScrollTimelineDescriptors {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if let Some(ref value) = self.source {
dest.write_str("source: ")?;
value.to_css(dest)?;
dest.write_str("; ")?;
}
if let Some(ref value) = self.orientation {
dest.write_str("orientation: ")?;
value.to_css(dest)?;
dest.write_str("; ")?;
}
// https://github.com/w3c/csswg-drafts/issues/6617
if let Some(ref value) = self.offsets {
dest.write_str("scroll-offsets: ")?;
value.to_css(dest)?;
dest.write_str("; ")?;
}
Ok(())
}
}
struct ScrollTimelineDescriptorsParser<'a, 'b: 'a> {
context: &'a ParserContext<'b>,
descriptors: &'a mut ScrollTimelineDescriptors,
}
impl<'a, 'b, 'i> AtRuleParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> {
type Prelude = ();
type AtRule = ();
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> DeclarationParser<'i> for ScrollTimelineDescriptorsParser<'a, 'b> {
type Declaration = ();
type Error = StyleParseErrorKind<'i>;
fn parse_value<'t>(
&mut self,
name: CowRcStr<'i>,
input: &mut Parser<'i, 't>,
) -> Result<(), ParseError<'i>> {
macro_rules! parse_descriptor {
(
$( $name: tt / $ident: ident, )*
) => {
match_ignore_ascii_case! { &*name,
$(
$name => {
let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
self.descriptors.$ident = Some(value)
},
)*
_ => {
return Err(input.new_custom_error(
SelectorParseErrorKind::UnexpectedIdent(name.clone()),
))
}
}
}
}
parse_descriptor! {
"source" / source,
"orientation" / orientation,
"scroll-offsets" / offsets,
};
Ok(())
}
}
/// The scroll-timeline source.
///
/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-source
#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)]
pub enum Source {
/// The scroll container.
Selector(ScrollTimelineSelector),
/// The initial value. The scrollingElement of the Document associated with the Window that is
/// the current global object.
Auto,
/// Null. However, it's not clear what is the expected behavior of this. See the spec issue:
/// https://drafts.csswg.org/scroll-animations/#issue-0d1e73bd
None,
}
impl Default for Source {
fn default() -> Self {
Source::Auto
}
}
/// The scroll-timeline orientation.
/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-orientation
///
/// Note: the initial orientation is auto, and we will treat it as block, the same as the
/// definition of ScrollTimelineOptions (WebIDL API).
/// https://drafts.csswg.org/scroll-animations/#dom-scrolltimelineoptions-orientation
#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)]
pub enum Orientation {
/// The initial value.
Auto,
/// The direction along the block axis. This is the default value.
Block,
/// The direction along the inline axis
Inline,
/// The physical horizontal direction.
Horizontal,
/// The physical vertical direction.
Vertical,
}
impl Default for Orientation {
fn default() -> Self {
Orientation::Auto
}
}
/// Scroll-timeline offsets. We treat None as an empty vector.
/// value: none | <scroll-timeline-offset>#
///
/// https://drafts.csswg.org/scroll-animations/#descdef-scroll-timeline-scroll-offsets
#[derive(Clone, Default, Debug, ToCss, ToShmem)]
#[css(comma)]
pub struct ScrollOffsets(#[css(if_empty = "none", iterable)] Box<[ScrollTimelineOffset]>);
impl Parse for ScrollOffsets {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(ScrollOffsets(Box::new([])));
}
Ok(ScrollOffsets(
input
.parse_comma_separated(|i| ScrollTimelineOffset::parse(context, i))?
.into_boxed_slice(),
))
}
}
/// A <scroll-timeline-offset>.
/// value: auto | <length-percentage> | <element-offset>
///
/// https://drafts.csswg.org/scroll-animations/#typedef-scroll-timeline-offset
#[derive(Clone, Debug, Parse, PartialEq, ToCss, ToShmem)]
pub enum ScrollTimelineOffset {
/// The initial value. A container-based offset.
Auto,
/// A container-based offset with the distance indicated by the value along source's scroll
/// range in orientation.
LengthPercentage(LengthPercentage),
/// An element-based offset.
ElementOffset(ElementOffset),
}
/// An <element-offset-edge>.
///
/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset-edge
#[derive(Clone, Copy, Debug, MallocSizeOf, Eq, Parse, PartialEq, PartialOrd, ToCss, ToShmem)]
pub enum ElementOffsetEdge {
/// Start edge
Start,
/// End edge.
End,
}
/// An <element-offset>.
/// value: selector( <id-selector> ) [<element-offset-edge> || <number>]?
///
/// https://drafts.csswg.org/scroll-animations-1/#typedef-element-offset
#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)]
pub struct ElementOffset {
/// The target whose intersection with source's scrolling box determines the concrete scroll
/// offset.
target: ScrollTimelineSelector,
/// An optional value of <element-offset-edge>. If not provided, the default value is start.
edge: Option<ElementOffsetEdge>,
/// An optional value of threshold. If not provided, the default value is 0.
threshold: Option<Number>,
}
impl Parse for ElementOffset {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let target = ScrollTimelineSelector::parse(context, input)?;
// Parse `[<element-offset-edge> || <number>]?`
let mut edge = input.try_parse(ElementOffsetEdge::parse).ok();
let threshold = input.try_parse(|i| Number::parse(context, i)).ok();
if edge.is_none() {
edge = input.try_parse(ElementOffsetEdge::parse).ok();
}
Ok(ElementOffset {
target,
edge,
threshold,
})
}
}
/// The type of the selector ID.
#[derive(Clone, Eq, PartialEq, ToShmem)]
pub struct ScrollTimelineSelector(AtomIdent);
impl Parse for ScrollTimelineSelector {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// Parse `selector(<id-selector>)`.
input.expect_function_matching("selector")?;
input.parse_nested_block(|i| match i.next()? {
Token::IDHash(id) => Ok(ScrollTimelineSelector(id.as_ref().into())),
_ => Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
})
}
}
impl ToCss for ScrollTimelineSelector {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
use crate::cssparser::ToCss as CssparserToCss;
dest.write_str("selector(")?;
dest.write_char('#')?;
self.0.to_css(dest)?;
dest.write_char(')')
}
}
impl Debug for ScrollTimelineSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_css(&mut CssWriter::new(f))
}
}

View file

@ -278,15 +278,8 @@ pub trait StylesheetInDocument: ::std::fmt::Debug {
rule_filter! {
effective_style_rules(Style => StyleRule),
effective_media_rules(Media => MediaRule),
effective_font_face_rules(FontFace => FontFaceRule),
effective_font_face_feature_values_rules(FontFeatureValues => FontFeatureValuesRule),
effective_counter_style_rules(CounterStyle => CounterStyleRule),
effective_viewport_rules(Viewport => ViewportRule),
effective_keyframes_rules(Keyframes => KeyframesRule),
effective_supports_rules(Supports => SupportsRule),
effective_page_rules(Page => PageRule),
effective_document_rules(Document => DocumentRule),
}
}
@ -367,7 +360,10 @@ impl SanitizationKind {
CssRule::Document(..) |
CssRule::Media(..) |
CssRule::Supports(..) |
CssRule::Import(..) => false,
CssRule::Import(..) |
// TODO(emilio): Perhaps Layer should not be always sanitized? But
// we sanitize @media and co, so this seems safer for now.
CssRule::Layer(..) => false,
CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true,
@ -375,7 +371,8 @@ impl SanitizationKind {
CssRule::Page(..) |
CssRule::FontFeatureValues(..) |
CssRule::Viewport(..) |
CssRule::CounterStyle(..) => !is_standard,
CssRule::CounterStyle(..) |
CssRule::ScrollTimeline(..) => !is_standard,
}
}
}

View file

@ -25,11 +25,12 @@ use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
use crate::stylesheets::layer_rule::{LayerName, LayerId, LayerOrder};
use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents};
#[cfg(feature = "gecko")]
use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule};
use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter};
use crate::stylesheets::{CssRule, Origin, OriginSet, PerOrigin, PerOriginIter, EffectiveRulesIterator};
use crate::thread_state::{self, ThreadState};
use crate::{Atom, LocalName, Namespace, WeakAtom};
use fallible::FallibleVec;
@ -292,6 +293,8 @@ impl CascadeDataCacheEntry for UserAgentCascadeData {
)?;
}
new_data.cascade_data.compute_layer_order();
Ok(Arc::new(new_data))
}
@ -1866,6 +1869,23 @@ impl PartElementAndPseudoRules {
}
}
#[derive(Clone, Debug, MallocSizeOf)]
struct CascadeLayer {
id: LayerId,
order: LayerOrder,
children: Vec<LayerId>,
}
impl CascadeLayer {
const fn root() -> Self {
Self {
id: LayerId::root(),
order: LayerOrder::root(),
children: vec![],
}
}
}
/// Data resulting from performing the CSS cascade that is specific to a given
/// origin.
///
@ -1931,6 +1951,12 @@ pub struct CascadeData {
/// by name.
animations: PrecomputedHashMap<Atom, KeyframesAnimation>,
/// A map from cascade layer name to layer order.
layer_id: FxHashMap<LayerName, LayerId>,
/// The list of cascade layers, indexed by their layer id.
layers: SmallVec<[CascadeLayer; 1]>,
/// Effective media query results cached from the last rebuild.
effective_media_query_results: EffectiveMediaQueryResults,
@ -1961,8 +1987,18 @@ impl CascadeData {
state_dependencies: ElementState::empty(),
document_state_dependencies: DocumentState::empty(),
mapped_ids: PrecomputedHashSet::default(),
selectors_for_cache_revalidation: SelectorMap::new(),
// NOTE: We disable attribute bucketing for revalidation because we
// rely on the buckets to match, but we don't want to just not share
// style across elements with different attributes.
//
// An alternative to this would be to perform a style sharing check
// like may_match_different_id_rules which would check that the
// attribute buckets match on all scopes. But that seems
// somewhat gnarly.
selectors_for_cache_revalidation: SelectorMap::new_without_attribute_bucketing(),
animations: Default::default(),
layer_id: Default::default(),
layers: smallvec::smallvec![CascadeLayer::root()],
extra_data: ExtraStyleData::default(),
effective_media_query_results: EffectiveMediaQueryResults::new(),
rules_source_order: 0,
@ -2009,6 +2045,8 @@ impl CascadeData {
result.is_ok()
});
self.compute_layer_order();
result
}
@ -2070,6 +2108,43 @@ impl CascadeData {
self.part_rules.is_some()
}
#[inline]
fn layer_order_for(&self, id: LayerId) -> LayerOrder {
self.layers[id.0 as usize].order
}
fn compute_layer_order(&mut self) {
debug_assert_ne!(self.layers.len(), 0, "There should be at least the root layer!");
if self.layers.len() == 1 {
return; // Nothing to do
}
let (first, remaining) = self.layers.split_at_mut(1);
let root = &mut first[0];
let mut order = LayerOrder::first();
compute_layer_order_for_subtree(root, remaining, &mut order);
// NOTE(emilio): This is a bit trickier than it should to avoid having
// to clone() around layer indices.
fn compute_layer_order_for_subtree(
parent: &mut CascadeLayer,
remaining_layers: &mut [CascadeLayer],
order: &mut LayerOrder,
) {
for child in parent.children.iter() {
debug_assert!(parent.id < *child, "Children are always registered after parents");
let child_index = (child.0 - parent.id.0 - 1) as usize;
let (first, remaining) = remaining_layers.split_at_mut(child_index + 1);
let child = &mut first[child_index];
compute_layer_order_for_subtree(child, remaining, order);
}
if parent.id != LayerId::root() {
parent.order = *order;
order.inc();
}
}
}
/// Collects all the applicable media query results into `results`.
///
/// This duplicates part of the logic in `add_stylesheet`, which is
@ -2117,31 +2192,25 @@ impl CascadeData {
}
}
// Returns Err(..) to signify OOM
fn add_stylesheet<S>(
fn add_rule_list<S>(
&mut self,
rules: std::slice::Iter<'_, CssRule>,
device: &Device,
quirks_mode: QuirksMode,
stylesheet: &S,
guard: &SharedRwLockReadGuard,
rebuild_kind: SheetRebuildKind,
mut current_layer: &mut LayerName,
current_layer_id: LayerId,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
) -> Result<(), FailedAllocationError>
where
S: StylesheetInDocument + 'static,
{
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) {
return Ok(());
}
let contents = stylesheet.contents();
let origin = contents.origin;
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results.saw_effective(contents);
}
for rule in stylesheet.effective_rules(device, guard) {
for rule in rules {
// Handle leaf rules first, as those are by far the most common
// ones, and are always effective, so we can skip some checks.
let mut handled = true;
match *rule {
CssRule::Style(ref locked) => {
let style_rule = locked.read_with(&guard);
@ -2154,7 +2223,8 @@ impl CascadeData {
if let Some(pseudo) = pseudo_element {
if pseudo.is_precomputed() {
debug_assert!(selector.is_universal());
debug_assert!(matches!(origin, Origin::UserAgent));
debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent);
debug_assert_eq!(current_layer_id, LayerId::root());
precomputed_pseudo_element_decls
.as_mut()
@ -2165,6 +2235,7 @@ impl CascadeData {
self.rules_source_order,
CascadeLevel::UANormal,
selector.specificity(),
LayerOrder::root(),
));
continue;
}
@ -2180,6 +2251,7 @@ impl CascadeData {
hashes,
locked.clone(),
self.rules_source_order,
current_layer_id,
);
if rebuild_kind.should_rebuild_invalidation() {
@ -2243,43 +2315,52 @@ impl CascadeData {
}
self.rules_source_order += 1;
},
CssRule::Import(ref lock) => {
if rebuild_kind.should_rebuild_invalidation() {
let import_rule = lock.read_with(guard);
self.effective_media_query_results
.saw_effective(import_rule);
}
// NOTE: effective_rules visits the inner stylesheet if
// appropriate.
},
CssRule::Media(ref lock) => {
if rebuild_kind.should_rebuild_invalidation() {
let media_rule = lock.read_with(guard);
self.effective_media_query_results.saw_effective(media_rule);
}
},
CssRule::Keyframes(ref keyframes_rule) => {
#[cfg(feature = "gecko")]
use hashglobe::hash_map::Entry;
#[cfg(feature = "servo")]
use hashglobe::fake::Entry;
let keyframes_rule = keyframes_rule.read_with(guard);
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
// Don't let a prefixed keyframes animation override a non-prefixed one.
let needs_insertion = keyframes_rule.vendor_prefix.is_none() ||
self.animations
.get(keyframes_rule.name.as_atom())
.map_or(true, |rule| rule.vendor_prefix.is_some());
if needs_insertion {
let animation = KeyframesAnimation::from_keyframes(
&keyframes_rule.keyframes,
keyframes_rule.vendor_prefix.clone(),
guard,
);
debug!("Found valid keyframe animation: {:?}", animation);
self.animations
.try_insert(keyframes_rule.name.as_atom().clone(), animation)?;
match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? {
Entry::Vacant(e) => {
e.insert(KeyframesAnimation::from_keyframes(
&keyframes_rule.keyframes,
keyframes_rule.vendor_prefix.clone(),
current_layer_id,
guard,
));
},
Entry::Occupied(mut e) => {
// Don't let a prefixed keyframes animation override
// a non-prefixed one.
//
// TODO(emilio): This will need to be harder for
// layers.
let needs_insert =
keyframes_rule.vendor_prefix.is_none() ||
e.get().vendor_prefix.is_some();
if needs_insert {
e.insert(KeyframesAnimation::from_keyframes(
&keyframes_rule.keyframes,
keyframes_rule.vendor_prefix.clone(),
current_layer_id,
guard,
));
}
},
}
},
#[cfg(feature = "gecko")]
CssRule::ScrollTimeline(..) => {
// TODO: Bug 1676791: set the timeline into animation.
// https://phabricator.services.mozilla.com/D126452
//
// Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue
// https://github.com/w3c/csswg-drafts/issues/6674 gets landed.
},
#[cfg(feature = "gecko")]
CssRule::FontFace(ref rule) => {
self.extra_data.add_font_face(rule);
},
@ -2295,14 +2376,223 @@ impl CascadeData {
CssRule::Page(ref rule) => {
self.extra_data.add_page(rule);
},
CssRule::Viewport(..) => {},
_ => {
handled = false;
},
}
if handled {
// Assert that there are no children, and that the rule is
// effective.
if cfg!(debug_assertions) {
let mut effective = false;
let children = EffectiveRulesIterator::children(
rule,
device,
quirks_mode,
guard,
&mut effective,
);
debug_assert!(children.is_none());
debug_assert!(effective);
}
continue;
}
let mut effective = false;
let children = EffectiveRulesIterator::children(
rule,
device,
quirks_mode,
guard,
&mut effective,
);
if !effective {
continue;
}
fn maybe_register_layer(data: &mut CascadeData, layer: &LayerName) -> LayerId {
// TODO: Measure what's more common / expensive, if
// layer.clone() or the double hash lookup in the insert
// case.
if let Some(id) = data.layer_id.get(layer) {
return *id;
}
let id = LayerId(data.layers.len() as u32);
let parent_layer_id = if layer.layer_names().len() > 1 {
let mut parent = layer.clone();
parent.0.pop();
*data.layer_id
.get_mut(&parent)
.expect("Parent layers should be registered before child layers")
} else {
LayerId::root()
};
data.layers[parent_layer_id.0 as usize].children.push(id);
data.layers.push(CascadeLayer {
id,
// NOTE(emilio): Order is evaluated after rebuild in
// compute_layer_order.
order: LayerOrder::first(),
children: vec![],
});
data.layer_id.insert(layer.clone(), id);
id
}
fn maybe_register_layers(
data: &mut CascadeData,
name: Option<&LayerName>,
current_layer: &mut LayerName,
pushed_layers: &mut usize,
) -> LayerId {
let anon_name;
let name = match name {
Some(name) => name,
None => {
anon_name = LayerName::new_anonymous();
&anon_name
},
};
let mut id = LayerId::root();
for name in name.layer_names() {
current_layer.0.push(name.clone());
id = maybe_register_layer(data, &current_layer);
*pushed_layers += 1;
}
debug_assert_ne!(id, LayerId::root());
id
}
let mut layer_names_to_pop = 0;
let mut children_layer_id = current_layer_id;
match *rule {
CssRule::Import(ref lock) => {
let import_rule = lock.read_with(guard);
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results
.saw_effective(import_rule);
}
if let Some(ref layer) = import_rule.layer {
children_layer_id = maybe_register_layers(
self,
layer.name.as_ref(),
&mut current_layer,
&mut layer_names_to_pop,
);
}
},
CssRule::Media(ref lock) => {
if rebuild_kind.should_rebuild_invalidation() {
let media_rule = lock.read_with(guard);
self.effective_media_query_results.saw_effective(media_rule);
}
},
CssRule::Layer(ref lock) => {
use crate::stylesheets::layer_rule::LayerRuleKind;
let layer_rule = lock.read_with(guard);
match layer_rule.kind {
LayerRuleKind::Block { ref name, .. } => {
children_layer_id = maybe_register_layers(
self,
name.as_ref(),
&mut current_layer,
&mut layer_names_to_pop,
);
}
LayerRuleKind::Statement { ref names } => {
for name in &**names {
let mut pushed = 0;
// There are no children, so we can ignore the
// return value.
maybe_register_layers(
self,
Some(name),
&mut current_layer,
&mut pushed,
);
for _ in 0..pushed {
current_layer.0.pop();
}
}
}
}
},
// We don't care about any other rule.
_ => {},
}
if let Some(children) = children {
self.add_rule_list(
children,
device,
quirks_mode,
stylesheet,
guard,
rebuild_kind,
current_layer,
children_layer_id,
precomputed_pseudo_element_decls.as_deref_mut(),
)?;
}
for _ in 0..layer_names_to_pop {
current_layer.0.pop();
}
}
Ok(())
}
// Returns Err(..) to signify OOM
fn add_stylesheet<S>(
&mut self,
device: &Device,
quirks_mode: QuirksMode,
stylesheet: &S,
guard: &SharedRwLockReadGuard,
rebuild_kind: SheetRebuildKind,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
) -> Result<(), FailedAllocationError>
where
S: StylesheetInDocument + 'static,
{
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) {
return Ok(());
}
let contents = stylesheet.contents();
if rebuild_kind.should_rebuild_invalidation() {
self.effective_media_query_results.saw_effective(contents);
}
let mut current_layer = LayerName::new_empty();
self.add_rule_list(
contents.rules(guard).iter(),
device,
quirks_mode,
stylesheet,
guard,
rebuild_kind,
&mut current_layer,
LayerId::root(),
precomputed_pseudo_element_decls.as_deref_mut(),
)?;
Ok(())
}
/// Returns whether all the media-feature affected values matched before and
/// match now in the given stylesheet.
pub fn media_feature_affected_matches<S>(
@ -2345,9 +2635,11 @@ impl CascadeData {
CssRule::CounterStyle(..) |
CssRule::Supports(..) |
CssRule::Keyframes(..) |
CssRule::ScrollTimeline(..) |
CssRule::Page(..) |
CssRule::Viewport(..) |
CssRule::Document(..) |
CssRule::Layer(..) |
CssRule::FontFeatureValues(..) => {
// Not affected by device changes.
continue;
@ -2413,6 +2705,9 @@ impl CascadeData {
host_rules.clear();
}
self.animations.clear();
self.layer_id.clear();
self.layers.clear();
self.layers.push(CascadeLayer::root());
self.extra_data.clear();
self.rules_source_order = 0;
self.num_selectors = 0;
@ -2501,6 +2796,9 @@ pub struct Rule {
/// we could repurpose that storage here if we needed to.
pub source_order: u32,
/// The current layer id of this style rule.
pub layer_id: LayerId,
/// The actual style rule.
#[cfg_attr(
feature = "gecko",
@ -2527,9 +2825,16 @@ impl Rule {
pub fn to_applicable_declaration_block(
&self,
level: CascadeLevel,
cascade_data: &CascadeData,
) -> ApplicableDeclarationBlock {
let source = StyleSource::from_rule(self.style_rule.clone());
ApplicableDeclarationBlock::new(source, self.source_order, level, self.specificity())
ApplicableDeclarationBlock::new(
source,
self.source_order,
level,
self.specificity(),
cascade_data.layer_order_for(self.layer_id),
)
}
/// Creates a new Rule.
@ -2538,12 +2843,14 @@ impl Rule {
hashes: AncestorHashes,
style_rule: Arc<Locked<StyleRule>>,
source_order: u32,
layer_id: LayerId,
) -> Self {
Rule {
selector: selector,
hashes: hashes,
style_rule: style_rule,
source_order: source_order,
selector,
hashes,
style_rule,
source_order,
layer_id,
}
}
}

View file

@ -11,14 +11,12 @@ use crate::values::generics::box_::Perspective as GenericPerspective;
use crate::values::generics::box_::VerticalAlign as GenericVerticalAlign;
use crate::values::specified::box_ as specified;
pub use crate::values::specified::box_::Clear as SpecifiedClear;
pub use crate::values::specified::box_::{AnimationName, Appearance, BreakBetween, BreakWithin};
pub use crate::values::specified::box_::{Contain, Display, Float as SpecifiedFloat, Overflow};
pub use crate::values::specified::box_::{OverflowAnchor, OverflowClipBox, OverscrollBehavior};
pub use crate::values::specified::box_::{
ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType,
AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin,
Clear as SpecifiedClear, Contain, Display, Float as SpecifiedFloat, Overflow, OverflowAnchor,
OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness,
ScrollSnapType, TouchAction, TransitionProperty, WillChange,
};
pub use crate::values::specified::box_::{TouchAction, TransitionProperty, WillChange};
/// A computed value for the `vertical-align` property.
pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>;

View file

@ -439,6 +439,26 @@ pub enum GenericFontFamily {
MozEmoji,
}
impl GenericFontFamily {
/// When we disallow websites to override fonts, we ignore some generic
/// families that the website might specify, since they're not configured by
/// the user. See bug 789788 and bug 1730098.
#[cfg(feature = "gecko")]
pub (crate) fn valid_for_user_font_prioritization(self) -> bool {
match self {
Self::None |
Self::Fantasy |
Self::Cursive |
Self::SystemUi |
Self::MozEmoji => false,
Self::Serif |
Self::SansSerif |
Self::Monospace => true,
}
}
}
impl Parse for SingleFontFamily {
/// Parse a font-family value.
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
@ -579,14 +599,14 @@ impl FontFamilyList {
self.list = crate::ArcSlice::from_iter(new_list.into_iter());
}
/// If there's a generic font family on the list (which isn't cursive or
/// fantasy), then move it to the front of the list. Otherwise, prepend the
/// default generic.
/// If there's a generic font family on the list which is suitable for user
/// font prioritization, then move it to the front of the list. Otherwise,
/// prepend the default generic.
#[cfg(feature = "gecko")]
pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) {
let index_of_first_generic = self.iter().position(|f| {
match *f {
SingleFontFamily::Generic(f) => f != GenericFontFamily::Cursive && f != GenericFontFamily::Fantasy,
SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(),
_ => false,
}
});

View file

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

View file

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

View file

@ -44,7 +44,7 @@ pub use self::basic_shape::FillRule;
pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderImageSlice, BorderImageWidth};
pub use self::box_::{AnimationIterationCount, AnimationName, Contain};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain};
pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float};
pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
@ -62,7 +62,7 @@ pub use self::font::{FontSize, FontSizeAdjust, FontStretch, FontSynthesis};
pub use self::font::{FontVariantAlternates, FontWeight};
pub use self::font::{FontVariantEastAsian, FontVariationSettings};
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
pub use self::image::{Gradient, Image, LineDirection, MozImageRect};
pub use self::image::{Gradient, Image, LineDirection, MozImageRect, ImageRendering};
pub use self::length::{CSSPixelLength, NonNegativeLength};
pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber};
pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size};

View file

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

View file

@ -687,7 +687,7 @@ pub trait IsParallelTo {
impl<Number, Angle> ToCss for Rotate<Number, Angle>
where
Number: Copy + ToCss,
Number: Copy + ToCss + Zero,
Angle: ToCss,
(Number, Number, Number): IsParallelTo,
{
@ -700,25 +700,41 @@ where
Rotate::None => dest.write_str("none"),
Rotate::Rotate(ref angle) => angle.to_css(dest),
Rotate::Rotate3D(x, y, z, ref angle) => {
// If a 3d rotation is specified, the property must serialize with an axis
// specified. If the axis is parallel with the x, y, or z axises, it must
// serialize as the appropriate keyword.
// If the axis is parallel with the x or y axes, it must serialize as the
// appropriate keyword. If a rotation about the z axis (that is, in 2D) is
// specified, the property must serialize as just an <angle>
//
// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
let v = (x, y, z);
if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
dest.write_char('x')?;
let axis = if x.is_zero() && y.is_zero() && z.is_zero() {
// The zero length vector is parallel to every other vector, so
// is_parallel_to() returns true for it. However, it is definitely different
// from x axis, y axis, or z axis, and it's meaningless to perform a rotation
// using that direction vector. So we *have* to serialize it using that same
// vector - we can't simplify to some theoretically parallel axis-aligned
// vector.
None
} else if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
Some("x ")
} else if v.is_parallel_to(&DirectionVector::new(0., 1., 0.)) {
dest.write_char('y')?;
Some("y ")
} else if v.is_parallel_to(&DirectionVector::new(0., 0., 1.)) {
dest.write_char('z')?;
// When we're parallel to the z-axis, we can just serialize the angle.
return angle.to_css(dest);
} else {
x.to_css(dest)?;
dest.write_char(' ')?;
y.to_css(dest)?;
dest.write_char(' ')?;
z.to_css(dest)?;
None
};
match axis {
Some(a) => dest.write_str(a)?,
None => {
x.to_css(dest)?;
dest.write_char(' ')?;
y.to_css(dest)?;
dest.write_char(' ')?;
z.to_css(dest)?;
dest.write_char(' ')?;
}
}
dest.write_char(' ')?;
angle.to_css(dest)
},
}

View file

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

View file

@ -130,6 +130,15 @@ impl Angle {
}
}
/// Creates an angle with the given value in radians.
#[inline]
pub fn from_radians(value: CSSFloat) -> Self {
Angle {
value: AngleDimension::Rad(value),
was_calc: false,
}
}
/// Return `0deg`.
pub fn zero() -> Self {
Self::from_degrees(0.0, false)
@ -141,6 +150,13 @@ impl Angle {
self.value.degrees()
}
/// Returns the value of the angle in radians.
#[inline]
pub fn radians(&self) -> CSSFloat {
const RAD_PER_DEG: f32 = PI / 180.0;
self.value.degrees() * RAD_PER_DEG
}
/// Whether this specified angle came from a `calc()` expression.
#[inline]
pub fn was_calc(&self) -> bool {

View file

@ -13,7 +13,7 @@ use crate::values::generics::box_::Perspective as GenericPerspective;
use crate::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
use crate::values::specified::{AllowQuirks, Number};
use crate::values::{CustomIdent, KeyframesName};
use crate::values::{CustomIdent, KeyframesName, TimelineName};
use crate::Atom;
use cssparser::Parser;
use num_traits::FromPrimitive;
@ -110,8 +110,6 @@ pub enum DisplayInside {
#[cfg(feature = "gecko")]
MozBox,
#[cfg(feature = "gecko")]
MozStack,
#[cfg(feature = "gecko")]
MozDeck,
#[cfg(feature = "gecko")]
MozPopup,
@ -226,8 +224,6 @@ impl Display {
#[cfg(feature = "gecko")]
pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox);
#[cfg(feature = "gecko")]
pub const MozStack: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozStack);
#[cfg(feature = "gecko")]
pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck);
#[cfg(feature = "gecko")]
pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup);
@ -608,8 +604,6 @@ impl Parse for Display {
#[cfg(feature = "gecko")]
"-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox,
#[cfg(feature = "gecko")]
"-moz-stack" if moz_display_values_enabled(context) => Display::MozStack,
#[cfg(feature = "gecko")]
"-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck,
#[cfg(feature = "gecko")]
"-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup,
@ -768,6 +762,67 @@ impl Parse for AnimationName {
}
}
/// A value for the <single-animation-timeline>.
///
/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
/// cbindgen:private-default-tagged-enum-constructor=false
#[derive(
Clone,
Debug,
Eq,
Hash,
MallocSizeOf,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum AnimationTimeline {
/// Use default timeline. The animations timeline is a DocumentTimeline.
Auto,
/// The animation is not associated with a timeline.
None,
/// The scroll-timeline name
Timeline(TimelineName),
}
impl AnimationTimeline {
/// Returns the `auto` value.
pub fn auto() -> Self {
Self::Auto
}
/// Returns true if it is auto (i.e. the default value).
pub fn is_auto(&self) -> bool {
matches!(self, Self::Auto)
}
}
impl Parse for AnimationTimeline {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
// We are using the same parser for TimelineName and KeyframesName, but animation-timeline
// accepts "auto", so need to manually parse this. (We can not derive Parse because
// TimelineName excludes only "none" keyword.)
// FIXME: Bug 1733260: we may drop None based on the spec issue:
// Note: https://github.com/w3c/csswg-drafts/issues/6674.
if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
return Ok(Self::Auto);
}
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
return Ok(Self::None);
}
TimelineName::parse(context, input).map(AnimationTimeline::Timeline)
}
}
/// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]

View file

@ -21,7 +21,7 @@ use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
/// The name of the mathematical function that we're parsing.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Parse)]
pub enum MathFunction {
/// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
Calc,
@ -31,6 +31,18 @@ pub enum MathFunction {
Max,
/// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
Clamp,
/// `sin()`: https://drafts.csswg.org/css-values-4/#funcdef-sin
Sin,
/// `cos()`: https://drafts.csswg.org/css-values-4/#funcdef-cos
Cos,
/// `tan()`: https://drafts.csswg.org/css-values-4/#funcdef-tan
Tan,
/// `asin()`: https://drafts.csswg.org/css-values-4/#funcdef-asin
Asin,
/// `acos()`: https://drafts.csswg.org/css-values-4/#funcdef-acos
Acos,
/// `atan()`: https://drafts.csswg.org/css-values-4/#funcdef-atan
Atan,
}
/// A leaf node inside a `Calc` expression's AST.
@ -231,6 +243,13 @@ impl generic::CalcNodeLeaf for Leaf {
}
}
fn trig_enabled() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.trig.enabled");
#[cfg(feature = "servo")]
return false;
}
/// A calc node representation for specified values.
pub type CalcNode = generic::GenericCalcNode<Leaf>;
@ -301,6 +320,17 @@ impl CalcNode {
let function = CalcNode::math_function(name, location)?;
CalcNode::parse(context, input, function, expected_unit)
},
(&Token::Ident(ref ident), _) => {
if !trig_enabled() {
return Err(location.new_unexpected_token_error(Token::Ident(ident.clone())));
}
let number = match_ignore_ascii_case! { &**ident,
"e" => std::f32::consts::E,
"pi" => std::f32::consts::PI,
_ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))),
};
Ok(CalcNode::Leaf(Leaf::Number(number)))
},
(t, _) => Err(location.new_unexpected_token_error(t.clone())),
}
}
@ -350,6 +380,47 @@ impl CalcNode {
Ok(Self::MinMax(arguments.into(), op))
},
MathFunction::Sin |
MathFunction::Cos |
MathFunction::Tan => {
let argument = Self::parse_argument(context, input, CalcUnit::Angle)?;
let radians = match argument.to_number() {
Ok(v) => v,
Err(()) => match argument.to_angle() {
Ok(angle) => angle.radians(),
Err(()) => return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
),
},
};
let number = match function {
MathFunction::Sin => radians.sin(),
MathFunction::Cos => radians.cos(),
MathFunction::Tan => radians.tan(),
_ => unsafe { debug_unreachable!("We just checked!"); },
};
Ok(Self::Leaf(Leaf::Number(number)))
},
MathFunction::Asin |
MathFunction::Acos |
MathFunction::Atan => {
let argument = Self::parse_argument(context, input, CalcUnit::Number)?;
let number = match argument.to_number() {
Ok(v) => v,
Err(()) => return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
),
};
let radians = match function {
MathFunction::Asin => number.asin(),
MathFunction::Acos => number.acos(),
MathFunction::Atan => number.atan(),
_ => unsafe { debug_unreachable!("We just checked!"); },
};
Ok(Self::Leaf(Leaf::Angle(Angle::from_radians(radians))))
},
}
})
}
@ -522,13 +593,18 @@ impl CalcNode {
name: &CowRcStr<'i>,
location: cssparser::SourceLocation,
) -> Result<MathFunction, ParseError<'i>> {
Ok(match_ignore_ascii_case! { &*name,
"calc" => MathFunction::Calc,
"min" => MathFunction::Min,
"max" => MathFunction::Max,
"clamp" => MathFunction::Clamp,
_ => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
})
use self::MathFunction::*;
let function = match MathFunction::from_ident(&*name) {
Ok(f) => f,
Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
};
if matches!(function, Sin | Cos | Tan | Asin | Acos | Atan) && !trig_enabled() {
return Err(location.new_unexpected_token_error(Token::Function(name.clone())));
}
Ok(function)
}
/// Convenience parsing function for integers.

View file

@ -194,20 +194,6 @@ impl ToCss for ColorMix {
}
}
/// The color scheme for a specific system color.
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum SystemColorScheme {
/// The default color-scheme for the document.
#[css(skip)]
Default,
/// A light color scheme.
Light,
/// A dark color scheme.
Dark,
}
/// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color {
@ -222,10 +208,9 @@ pub enum Color {
},
/// A complex color value from computed value
Complex(ComputedColor),
/// Either a system color, or a `-moz-system-color(<system-color>, light|dark)`
/// function which allows chrome code to choose between color schemes.
/// A system color.
#[cfg(feature = "gecko")]
System(SystemColor, SystemColorScheme),
System(SystemColor),
/// A color mix.
ColorMix(Box<ColorMix>),
/// Quirksmode-only rule for inheriting color from the body
@ -233,36 +218,19 @@ pub enum Color {
InheritFromBodyQuirk,
}
/// System colors.
/// System colors. A bunch of these are ad-hoc, others come from Windows:
///
/// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor
///
/// Others are HTML/CSS specific. Spec is:
///
/// https://drafts.csswg.org/css-color/#css-system-colors
/// https://drafts.csswg.org/css-color/#deprecated-system-colors
#[allow(missing_docs)]
#[cfg(feature = "gecko")]
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
#[repr(u8)]
pub enum SystemColor {
#[css(skip)]
WindowBackground,
#[css(skip)]
WindowForeground,
#[css(skip)]
WidgetBackground,
#[css(skip)]
WidgetForeground,
#[css(skip)]
WidgetSelectBackground,
#[css(skip)]
WidgetSelectForeground,
#[css(skip)]
Widget3DHighlight,
#[css(skip)]
Widget3DShadow,
#[css(skip)]
TextBackground,
#[css(skip)]
TextForeground,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectBackground,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectForeground,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
TextSelectBackgroundDisabled,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
@ -310,6 +278,7 @@ pub enum SystemColor {
#[css(skip)]
ThemedScrollbarThumbInactive,
Activeborder,
/// Background in the (active) titlebar.
Activecaption,
Appworkspace,
Background,
@ -317,16 +286,23 @@ pub enum SystemColor {
Buttonhighlight,
Buttonshadow,
Buttontext,
/// Text color in the (active) titlebar.
Captiontext,
#[parse(aliases = "-moz-field")]
Field,
/// Used for disabled field backgrounds.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozDisabledfield,
#[parse(aliases = "-moz-fieldtext")]
Fieldtext,
Graytext,
Highlight,
Highlighttext,
Inactiveborder,
/// Background in the (inactive) titlebar.
Inactivecaption,
/// Text color in the (inactive) titlebar.
Inactivecaptiontext,
Infobackground,
Infotext,
@ -351,13 +327,15 @@ pub enum SystemColor {
/// Used to highlight valid regions to drop something onto.
MozDragtargetzone,
/// Used for selected but not focused cell backgrounds.
#[parse(aliases = "-moz-html-cellhighlight")]
MozCellhighlight,
/// Used for selected but not focused cell text.
#[parse(aliases = "-moz-html-cellhighlighttext")]
MozCellhighlighttext,
/// Used for selected but not focused html cell backgrounds.
MozHtmlCellhighlight,
/// Used for selected but not focused html cell text.
MozHtmlCellhighlighttext,
/// Used for selected and focused html cell backgrounds.
Selecteditem,
/// Used for selected and focused html cell text.
Selecteditemtext,
/// Used to button text background when hovered.
MozButtonhoverface,
/// Used to button text color when hovered.
@ -378,10 +356,16 @@ pub enum SystemColor {
/// Used for button text when pressed.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozGtkButtonactivetext,
MozButtonactivetext,
/// Used for button background when pressed.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozButtonactiveface,
/// Used for button background when disabled.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozButtondisabledface,
/// Used for button text when pressed.
MozMacButtonactivetext,
/// Background color of chrome toolbars in active windows.
MozMacChromeActive,
/// Background color of chrome toolbars in inactive windows.
@ -423,6 +407,10 @@ pub enum SystemColor {
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozAccentColorForeground,
/// The background-color for :autofill-ed inputs.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozAutofillBackground,
/// Media rebar text.
MozWinMediatext,
/// Communications rebar text.
@ -457,14 +445,6 @@ pub enum SystemColor {
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozColheaderhovertext,
/// Color of text in the (active) titlebar.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozGtkTitlebarText,
/// Color of text in the (inactive) titlebar.
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozGtkTitlebarInactiveText,
#[css(skip)]
End, // Just for array-indexing purposes.
}
@ -472,31 +452,20 @@ pub enum SystemColor {
#[cfg(feature = "gecko")]
impl SystemColor {
#[inline]
fn compute(&self, cx: &Context, scheme: SystemColorScheme) -> ComputedColor {
fn compute(&self, cx: &Context) -> ComputedColor {
use crate::gecko_bindings::bindings;
let colors = &cx.device().pref_sheet_prefs().mColors;
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
// TODO: At least Canvas / CanvasText should be color-scheme aware
// (probably the link colors too).
convert_nscolor_to_computedcolor(match *self {
SystemColor::Canvastext => colors.mDefault,
SystemColor::Canvas => colors.mDefaultBackground,
SystemColor::Linktext => colors.mLink,
SystemColor::Activetext => colors.mActiveLink,
SystemColor::Visitedtext => colors.mVisitedLink,
_ => {
let color = unsafe {
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme)
};
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor();
}
color
},
})
// TODO: We should avoid cloning here most likely, though it's
// cheap-ish.
let style_color_scheme =
cx.style().get_inherited_ui().clone_color_scheme();
let color = unsafe {
bindings::Gecko_ComputeSystemColor(*self, cx.device().document(), &style_color_scheme)
};
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
return ComputedColor::currentcolor();
}
convert_nscolor_to_computedcolor(color)
}
}
@ -570,21 +539,6 @@ impl<'a, 'b: 'a, 'i: 'a> ::cssparser::ColorComponentParser<'i> for ColorComponen
}
}
#[cfg(feature = "gecko")]
fn parse_moz_system_color<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<(SystemColor, SystemColorScheme), ParseError<'i>> {
debug_assert!(context.chrome_rules_enabled());
input.expect_function_matching("-moz-system-color")?;
input.parse_nested_block(|input| {
let color = SystemColor::parse(context, input)?;
input.expect_comma()?;
let scheme = SystemColorScheme::parse(input)?;
Ok((color, scheme))
})
}
impl Parse for Color {
fn parse<'i, 't>(
context: &ParserContext,
@ -610,15 +564,7 @@ impl Parse for Color {
#[cfg(feature = "gecko")]
{
if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
return Ok(Color::System(system, SystemColorScheme::Default));
}
if context.chrome_rules_enabled() {
if let Ok((color, scheme)) =
input.try_parse(|i| parse_moz_system_color(context, i))
{
return Ok(Color::System(color, scheme));
}
return Ok(Color::System(system));
}
}
@ -657,17 +603,7 @@ impl ToCss for Color {
Color::Complex(_) => Ok(()),
Color::ColorMix(ref mix) => mix.to_css(dest),
#[cfg(feature = "gecko")]
Color::System(system, scheme) => {
if scheme == SystemColorScheme::Default {
system.to_css(dest)
} else {
dest.write_str("-moz-system-color(")?;
system.to_css(dest)?;
dest.write_str(", ")?;
scheme.to_css(dest)?;
dest.write_char(')')
}
},
Color::System(system) => system.to_css(dest),
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => Ok(()),
}
@ -835,7 +771,7 @@ impl Color {
))
},
#[cfg(feature = "gecko")]
Color::System(system, scheme) => system.compute(context?, scheme),
Color::System(system) => system.compute(context?),
#[cfg(feature = "gecko")]
Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
})
@ -995,6 +931,11 @@ impl ColorScheme {
bits: ColorSchemeFlags::empty(),
}
}
/// Returns the raw bitfield.
pub fn raw_bits(&self) -> u8 {
self.bits.bits
}
}
impl Parse for ColorScheme {

View file

@ -49,6 +49,11 @@ impl Parse for IntersectionObserverRootMargin {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
use crate::Zero;
if input.is_exhausted() {
// If there are zero elements in tokens, set tokens to ["0px"].
return Ok(IntersectionObserverRootMargin(Rect::all(LengthPercentage::zero())));
}
let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?;
Ok(IntersectionObserverRootMargin(rect))
}

View file

@ -1226,3 +1226,42 @@ impl MozImageRect {
})
}
}
/// https://drafts.csswg.org/css-images/#propdef-image-rendering
#[allow(missing_docs)]
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ImageRendering {
Auto,
#[cfg(feature = "gecko")]
Smooth,
#[parse(aliases = "-moz-crisp-edges")]
CrispEdges,
Pixelated,
// From the spec:
//
// This property previously accepted the values optimizeSpeed and
// optimizeQuality. These are now deprecated; a user agent must accept
// them as valid values but must treat them as having the same behavior
// as crisp-edges and smooth respectively, and authors must not use
// them.
//
#[cfg(feature = "gecko")]
Optimizespeed,
#[cfg(feature = "gecko")]
Optimizequality,
}

View file

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

View file

@ -36,7 +36,7 @@ pub use self::basic_shape::FillRule;
pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle};
pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display};
pub use self::box_::{Appearance, BreakBetween, BreakWithin};
pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
@ -55,7 +55,7 @@ pub use self::font::{FontVariantAlternates, FontWeight};
pub use self::font::{FontVariantEastAsian, FontVariationSettings};
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
pub use self::image::{EndingShape as GradientEndingShape, Gradient};
pub use self::image::{Image, MozImageRect};
pub use self::image::{Image, MozImageRect, ImageRendering};
pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};

View file

@ -82,7 +82,12 @@ files = [
# These are ignored to avoid diverging from Gecko
"./components/style/counter_style/mod.rs",
"./components/style/properties/helpers.mako.rs",
"./components/style/rule_collector.rs",
"./components/style/selector_map.rs",
"./components/style/stylesheets/import_rule.rs",
"./components/style/stylesheets/layer_rule.rs",
"./components/style/stylesheets/rule_parser.rs",
"./components/style/stylesheets/scroll_timeline_rule.rs",
"./components/style/stylist.rs",
"./components/style/values/computed/font.rs",
"./components/style/values/computed/image.rs",

View file

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

View file

@ -344,14 +344,5 @@
[Web Animations: property <rotate> from [1 -2.5 3.64 100deg\] to [1 -2.5 3.64 -100deg\] at (1) should be [0.22 -0.55 0.8 -100deg\]]
expected: FAIL
[CSS Transitions: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL
[CSS Transitions with transition: all: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL
[CSS Animations: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL
[Web Animations: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
expected: FAIL

View file

@ -1,12 +0,0 @@
[rotate-parsing-valid.html]
[e.style['rotate'\] = "0 0 0 400grad" should set the property value]
expected: FAIL
[e.style['rotate'\] = "400grad z" should set the property value]
expected: FAIL
[e.style['rotate'\] = "0 0 0.5 400grad" should set the property value]
expected: FAIL
[e.style['rotate'\] = "0 0 1 400grad" should set the property value]
expected: FAIL

View file

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