mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
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:
commit
7399a3a686
63 changed files with 2128 additions and 723 deletions
|
@ -1,3 +1,5 @@
|
|||
-moz-gtk-csd-titlebar-radius
|
||||
-moz-gtk-menu-radius
|
||||
DOMContentLoaded
|
||||
abort
|
||||
activate
|
||||
|
|
|
@ -105,6 +105,8 @@ impl CSSRule {
|
|||
},
|
||||
StyleCssRule::Page(_) => unreachable!(),
|
||||
StyleCssRule::Document(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::Layer(_) => unimplemented!(), // TODO
|
||||
StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -476,6 +476,7 @@ class Longhand(Property):
|
|||
"FontWeight",
|
||||
"GreaterThanOrEqualToOneNumber",
|
||||
"GridAutoFlow",
|
||||
"ImageRendering",
|
||||
"InitialLetter",
|
||||
"Integer",
|
||||
"JustifyContent",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(¤t_bucket) {
|
||||
current_bucket = new_bucket;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))]
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
|
|
|
@ -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(';')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
235
components/style/stylesheets/layer_rule.rs
Normal file
235
components/style/stylesheets/layer_rule.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>>;
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 it’s 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)]
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
|
327
components/style/stylesheets/scroll_timeline_rule.rs
Normal file
327
components/style/stylesheets/scroll_timeline_rule.rs
Normal 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))
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, ¤t_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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -189,7 +189,7 @@ impl Size {
|
|||
#[cfg(feature = "gecko")]
|
||||
GenericSize::MinContent |
|
||||
GenericSize::MaxContent |
|
||||
GenericSize::MozFitContent |
|
||||
GenericSize::FitContent |
|
||||
GenericSize::MozAvailable |
|
||||
GenericSize::FitContentFunction(_) => false
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 animation’s timeline is a DocumentTimeline.
|
||||
Auto,
|
||||
/// The animation is not associated with a timeline.
|
||||
None,
|
||||
/// The scroll-timeline name
|
||||
Timeline(TimelineName),
|
||||
}
|
||||
|
||||
impl AnimationTimeline {
|
||||
/// Returns the `auto` value.
|
||||
pub fn auto() -> Self {
|
||||
Self::Auto
|
||||
}
|
||||
|
||||
/// 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))]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue