mirror of
https://github.com/servo/servo.git
synced 2025-08-04 13:10:20 +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
|
DOMContentLoaded
|
||||||
abort
|
abort
|
||||||
activate
|
activate
|
||||||
|
|
|
@ -105,6 +105,8 @@ impl CSSRule {
|
||||||
},
|
},
|
||||||
StyleCssRule::Page(_) => unreachable!(),
|
StyleCssRule::Page(_) => unreachable!(),
|
||||||
StyleCssRule::Document(_) => unimplemented!(), // TODO
|
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
|
* 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/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use crate::dom::attr::AttrHelpersForLayout;
|
||||||
use crate::dom::bindings::inheritance::{
|
use crate::dom::bindings::inheritance::{
|
||||||
CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId,
|
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 {
|
fn has_dirty_descendants(&self) -> bool {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.as_node()
|
self.as_node()
|
||||||
|
|
|
@ -38,7 +38,7 @@ use std::sync::Mutex;
|
||||||
use style::media_queries::MediaList;
|
use style::media_queries::MediaList;
|
||||||
use style::parser::ParserContext;
|
use style::parser::ParserContext;
|
||||||
use style::shared_lock::{Locked, SharedRwLock};
|
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::StylesheetLoader as StyleStylesheetLoader;
|
||||||
use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents};
|
use style::stylesheets::{CssRules, ImportRule, Origin, Stylesheet, StylesheetContents};
|
||||||
use style::values::CssUrl;
|
use style::values::CssUrl;
|
||||||
|
@ -358,6 +358,7 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
lock: &SharedRwLock,
|
lock: &SharedRwLock,
|
||||||
media: Arc<Locked<MediaList>>,
|
media: Arc<Locked<MediaList>>,
|
||||||
|
layer: Option<ImportLayer>,
|
||||||
) -> Arc<Locked<ImportRule>> {
|
) -> Arc<Locked<ImportRule>> {
|
||||||
let sheet = Arc::new(Stylesheet {
|
let sheet = Arc::new(Stylesheet {
|
||||||
contents: StylesheetContents::from_shared_data(
|
contents: StylesheetContents::from_shared_data(
|
||||||
|
@ -374,8 +375,9 @@ impl<'a> StyleStylesheetLoader for StylesheetLoader<'a> {
|
||||||
let stylesheet = ImportSheet(sheet.clone());
|
let stylesheet = ImportSheet(sheet.clone());
|
||||||
let import = ImportRule {
|
let import = ImportRule {
|
||||||
url,
|
url,
|
||||||
source_location,
|
|
||||||
stylesheet,
|
stylesheet,
|
||||||
|
layer,
|
||||||
|
source_location,
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = match import.url.url().cloned() {
|
let url = match import.url.url().cloned() {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
use crate::properties::PropertyDeclarationBlock;
|
use crate::properties::PropertyDeclarationBlock;
|
||||||
use crate::rule_tree::{CascadeLevel, StyleSource};
|
use crate::rule_tree::{CascadeLevel, StyleSource};
|
||||||
|
use crate::stylesheets::layer_rule::LayerOrder;
|
||||||
use crate::shared_lock::Locked;
|
use crate::shared_lock::Locked;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -69,8 +70,10 @@ pub struct ApplicableDeclarationBlock {
|
||||||
/// The bits containing the source order, cascade level, and shadow cascade
|
/// The bits containing the source order, cascade level, and shadow cascade
|
||||||
/// order.
|
/// order.
|
||||||
bits: ApplicableDeclarationBits,
|
bits: ApplicableDeclarationBits,
|
||||||
/// The specificity of the selector this block is represented by.
|
/// The specificity of the selector.
|
||||||
pub specificity: u32,
|
pub specificity: u32,
|
||||||
|
/// The layer order of the selector.
|
||||||
|
pub layer_order: LayerOrder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicableDeclarationBlock {
|
impl ApplicableDeclarationBlock {
|
||||||
|
@ -85,16 +88,24 @@ impl ApplicableDeclarationBlock {
|
||||||
source: StyleSource::from_declarations(declarations),
|
source: StyleSource::from_declarations(declarations),
|
||||||
bits: ApplicableDeclarationBits::new(0, level),
|
bits: ApplicableDeclarationBits::new(0, level),
|
||||||
specificity: 0,
|
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]
|
#[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 {
|
ApplicableDeclarationBlock {
|
||||||
source,
|
source,
|
||||||
bits: ApplicableDeclarationBits::new(order, level),
|
bits: ApplicableDeclarationBits::new(source_order, level),
|
||||||
specificity,
|
specificity,
|
||||||
|
layer_order,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use crate::dom::{SendElement, TElement};
|
use crate::dom::{SendElement, TElement};
|
||||||
|
use crate::LocalName;
|
||||||
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
|
||||||
use owning_ref::OwningHandle;
|
use owning_ref::OwningHandle;
|
||||||
use selectors::bloom::BloomFilter;
|
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
|
/// We do this for attributes that are very common but not commonly used in
|
||||||
/// selectors.
|
/// selectors.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(feature = "gecko")]
|
pub fn is_attr_name_excluded_from_filter(name: &LocalName) -> bool {
|
||||||
pub fn is_attr_name_excluded_from_filter(atom: &crate::Atom) -> bool {
|
return *name == local_name!("class") || *name == local_name!("id") || *name == local_name!("style")
|
||||||
*atom == atom!("class") || *atom == atom!("id") || *atom == atom!("style")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn each_relevant_element_hash<E, F>(element: E, mut f: F)
|
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()));
|
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| {
|
element.each_attr_name(|name| {
|
||||||
if !is_attr_name_excluded_from_filter(name) {
|
if !is_attr_name_excluded_from_filter(name) {
|
||||||
f(name.get_hash())
|
f(name.get_hash())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: TElement> Drop for StyleBloom<E> {
|
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),
|
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 {
|
impl CssEnvironment {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get(&self, name: &Atom, device: &Device) -> Option<VariableValue> {
|
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))
|
Some((var.evaluator)(device))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -520,10 +520,9 @@ pub trait TElement:
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal iterator for the attribute names of this element.
|
/// Internal iterator for the attribute names of this element.
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
fn each_attr_name<F>(&self, callback: F)
|
fn each_attr_name<F>(&self, callback: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&AtomIdent);
|
F: FnMut(&LocalName);
|
||||||
|
|
||||||
/// Internal iterator for the part names that this element exports for a
|
/// Internal iterator for the part names that this element exports for a
|
||||||
/// given part name.
|
/// given part name.
|
||||||
|
|
|
@ -9,34 +9,24 @@
|
||||||
#![allow(non_snake_case, missing_docs)]
|
#![allow(non_snake_case, missing_docs)]
|
||||||
|
|
||||||
use crate::gecko::url::CssUrlData;
|
use crate::gecko::url::CssUrlData;
|
||||||
use crate::gecko_bindings::structs::RawServoAnimationValue;
|
use crate::gecko_bindings::structs::{
|
||||||
use crate::gecko_bindings::structs::RawServoCounterStyleRule;
|
RawServoAnimationValue, RawServoCounterStyleRule, RawServoCssUrlData, RawServoDeclarationBlock,
|
||||||
use crate::gecko_bindings::structs::RawServoCssUrlData;
|
RawServoFontFaceRule, RawServoFontFeatureValuesRule, RawServoImportRule, RawServoKeyframe,
|
||||||
use crate::gecko_bindings::structs::RawServoDeclarationBlock;
|
RawServoKeyframesRule, RawServoLayerRule, RawServoMediaList, RawServoMediaRule,
|
||||||
use crate::gecko_bindings::structs::RawServoFontFaceRule;
|
RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule,
|
||||||
use crate::gecko_bindings::structs::RawServoFontFeatureValuesRule;
|
RawServoStyleRule, RawServoStyleSheetContents, RawServoSupportsRule, ServoCssRules,
|
||||||
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::sugar::ownership::{HasArcFFI, HasFFI, Strong};
|
use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
|
||||||
use crate::media_queries::MediaList;
|
use crate::media_queries::MediaList;
|
||||||
use crate::properties::animated_properties::AnimationValue;
|
use crate::properties::animated_properties::AnimationValue;
|
||||||
use crate::properties::{ComputedValues, PropertyDeclarationBlock};
|
use crate::properties::{ComputedValues, PropertyDeclarationBlock};
|
||||||
use crate::shared_lock::Locked;
|
use crate::shared_lock::Locked;
|
||||||
use crate::stylesheets::keyframes_rule::Keyframe;
|
use crate::stylesheets::keyframes_rule::Keyframe;
|
||||||
use crate::stylesheets::{CounterStyleRule, CssRules, FontFaceRule, FontFeatureValuesRule};
|
use crate::stylesheets::{
|
||||||
use crate::stylesheets::{DocumentRule, ImportRule, KeyframesRule, MediaRule};
|
CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
|
||||||
use crate::stylesheets::{NamespaceRule, PageRule};
|
KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule, ScrollTimelineRule, StyleRule,
|
||||||
use crate::stylesheets::{StyleRule, StylesheetContents, SupportsRule};
|
StylesheetContents, SupportsRule,
|
||||||
|
};
|
||||||
use servo_arc::{Arc, ArcBorrow};
|
use servo_arc::{Arc, ArcBorrow};
|
||||||
use std::{mem, ptr};
|
use std::{mem, ptr};
|
||||||
|
|
||||||
|
@ -83,6 +73,9 @@ impl_arc_ffi!(Locked<Keyframe> => RawServoKeyframe
|
||||||
impl_arc_ffi!(Locked<KeyframesRule> => RawServoKeyframesRule
|
impl_arc_ffi!(Locked<KeyframesRule> => RawServoKeyframesRule
|
||||||
[Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]);
|
[Servo_KeyframesRule_AddRef, Servo_KeyframesRule_Release]);
|
||||||
|
|
||||||
|
impl_arc_ffi!(Locked<LayerRule> => RawServoLayerRule
|
||||||
|
[Servo_LayerRule_AddRef, Servo_LayerRule_Release]);
|
||||||
|
|
||||||
impl_arc_ffi!(Locked<MediaList> => RawServoMediaList
|
impl_arc_ffi!(Locked<MediaList> => RawServoMediaList
|
||||||
[Servo_MediaList_AddRef, Servo_MediaList_Release]);
|
[Servo_MediaList_AddRef, Servo_MediaList_Release]);
|
||||||
|
|
||||||
|
@ -95,6 +88,9 @@ impl_arc_ffi!(Locked<NamespaceRule> => RawServoNamespaceRule
|
||||||
impl_arc_ffi!(Locked<PageRule> => RawServoPageRule
|
impl_arc_ffi!(Locked<PageRule> => RawServoPageRule
|
||||||
[Servo_PageRule_AddRef, Servo_PageRule_Release]);
|
[Servo_PageRule_AddRef, Servo_PageRule_Release]);
|
||||||
|
|
||||||
|
impl_arc_ffi!(Locked<ScrollTimelineRule> => RawServoScrollTimelineRule
|
||||||
|
[Servo_ScrollTimelineRule_AddRef, Servo_ScrollTimelineRule_Release]);
|
||||||
|
|
||||||
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
|
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
|
||||||
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
|
[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! {
|
bitflags! {
|
||||||
/// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
|
/// https://drafts.csswg.org/mediaqueries-4/#mf-interaction
|
||||||
struct PointerCapabilities: u8 {
|
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)
|
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 {
|
fn get_lnf_int(int_id: i32) -> i32 {
|
||||||
unsafe { bindings::Gecko_GetLookAndFeelInt(int_id) }
|
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
|
/// to support new types in these entries and (2) ensuring that either
|
||||||
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
|
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
|
||||||
/// would be returned by the evaluator function could change.
|
/// would be returned by the evaluator function could change.
|
||||||
pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [
|
pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
|
||||||
feature!(
|
feature!(
|
||||||
atom!("width"),
|
atom!("width"),
|
||||||
AllowsRanges::Yes,
|
AllowsRanges::Yes,
|
||||||
|
@ -890,37 +861,35 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 60] = [
|
||||||
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
|
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
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!(
|
feature!(
|
||||||
atom!("-moz-windows-non-native-menus"),
|
atom!("-moz-windows-non-native-menus"),
|
||||||
AllowsRanges::No,
|
AllowsRanges::No,
|
||||||
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
|
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
|
||||||
ParsingRequirements::CHROME_AND_UA_ONLY,
|
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-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-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-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-end-forward"), ScrollArrowStyle, get_scrollbar_end_forward),
|
||||||
lnf_int_feature!(atom!("-moz-scrollbar-thumb-proportional"), ScrollSliderStyle),
|
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-menubar-drag"), MenuBarDrag),
|
||||||
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
|
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
|
||||||
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
|
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-big-sur-theme"), MacBigSurTheme),
|
||||||
|
lnf_int_feature!(atom!("-moz-mac-rtl"), MacRTL),
|
||||||
lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar),
|
lnf_int_feature!(atom!("-moz-windows-accent-color-in-titlebar"), WindowsAccentColorInTitlebar),
|
||||||
lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor),
|
lnf_int_feature!(atom!("-moz-windows-compositor"), DWMCompositor),
|
||||||
lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic),
|
lnf_int_feature!(atom!("-moz-windows-classic"), WindowsClassic),
|
||||||
lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass),
|
lnf_int_feature!(atom!("-moz-windows-glass"), WindowsGlass),
|
||||||
lnf_int_feature!(atom!("-moz-swipe-animation-enabled"), SwipeAnimationEnabled),
|
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-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-minimize-button"), GTKCSDMinimizeButton),
|
||||||
lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton),
|
lnf_int_feature!(atom!("-moz-gtk-csd-maximize-button"), GTKCSDMaximizeButton),
|
||||||
lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton),
|
lnf_int_feature!(atom!("-moz-gtk-csd-close-button"), GTKCSDCloseButton),
|
||||||
|
|
|
@ -93,7 +93,9 @@ impl Device {
|
||||||
document,
|
document,
|
||||||
default_values: ComputedValues::default_values(doc),
|
default_values: ComputedValues::default_values(doc),
|
||||||
root_font_size: AtomicU32::new(FONT_MEDIUM_PX.to_bits()),
|
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_root_font_size: AtomicBool::new(false),
|
||||||
used_font_metrics: AtomicBool::new(false),
|
used_font_metrics: AtomicBool::new(false),
|
||||||
used_viewport_size: AtomicBool::new(false),
|
used_viewport_size: AtomicBool::new(false),
|
||||||
|
@ -386,13 +388,18 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default background color.
|
/// 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.
|
/// 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.
|
/// Returns the current effective text zoom.
|
||||||
|
@ -439,4 +446,24 @@ impl Device {
|
||||||
bindings::Gecko_IsSupportedImageMimeType(mime_type.as_ptr(), mime_type.len() as u32)
|
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,7 +249,6 @@ impl ::selectors::SelectorImpl for SelectorImpl {
|
||||||
type NonTSPseudoClass = NonTSPseudoClass;
|
type NonTSPseudoClass = NonTSPseudoClass;
|
||||||
|
|
||||||
fn should_collect_attr_hash(name: &AtomIdent) -> bool {
|
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]
|
#[inline]
|
||||||
fn namespace(&self) -> &WeakNamespace {
|
fn namespace(&self) -> &WeakNamespace {
|
||||||
unsafe {
|
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)
|
WeakNamespace::new((*namespace_manager).mURIArray[self.namespace_id() as usize].mRawPtr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -543,6 +543,7 @@ impl StylesheetInvalidationSet {
|
||||||
FontFeatureValues(..) |
|
FontFeatureValues(..) |
|
||||||
FontFace(..) |
|
FontFace(..) |
|
||||||
Keyframes(..) |
|
Keyframes(..) |
|
||||||
|
ScrollTimeline(..) |
|
||||||
Style(..) => {
|
Style(..) => {
|
||||||
if is_generic_change {
|
if is_generic_change {
|
||||||
// TODO(emilio): We need to do this for selector / keyframe
|
// 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)
|
self.collect_invalidations_for_rule(rule, guard, device, quirks_mode)
|
||||||
},
|
},
|
||||||
Document(..) | Import(..) | Media(..) | Supports(..) => {
|
Document(..) | Import(..) | Media(..) | Supports(..) | Layer(..) => {
|
||||||
if !is_generic_change &&
|
if !is_generic_change &&
|
||||||
!EffectiveRules::is_effective(guard, device, quirks_mode, rule)
|
!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
|
// Do nothing, relevant nested rules are visited as part of the
|
||||||
// iteration.
|
// iteration.
|
||||||
},
|
},
|
||||||
|
@ -618,6 +619,10 @@ impl StylesheetInvalidationSet {
|
||||||
// existing elements.
|
// existing elements.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ScrollTimeline(..) => {
|
||||||
|
// TODO: Bug 1676784: check if animation-timeline name is referenced.
|
||||||
|
// Now we do nothing.
|
||||||
|
},
|
||||||
CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => {
|
CounterStyle(..) | Page(..) | Viewport(..) | FontFeatureValues(..) => {
|
||||||
debug!(
|
debug!(
|
||||||
" > Found unsupported rule, marking the whole subtree \
|
" > 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...
|
// widget background color's rgb channels but not alpha...
|
||||||
let alpha = alpha_channel(color, context);
|
let alpha = alpha_channel(color, context);
|
||||||
if alpha != 0 {
|
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;
|
color.alpha = alpha;
|
||||||
declarations_to_apply_unless_overriden
|
declarations_to_apply_unless_overriden
|
||||||
.push(PropertyDeclaration::BackgroundColor(color.into()))
|
.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
|
// override this with a non-transparent color, then override it with
|
||||||
// the default color. Otherwise just let it inherit through.
|
// the default color. Otherwise just let it inherit through.
|
||||||
if context.builder.get_parent_inherited_text().clone_color().alpha == 0 {
|
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(
|
declarations_to_apply_unless_overriden.push(PropertyDeclaration::Color(
|
||||||
specified::ColorPropertyValue(color.into()),
|
specified::ColorPropertyValue(color.into()),
|
||||||
))
|
))
|
||||||
|
@ -794,12 +794,6 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
{
|
{
|
||||||
builder.add_flags(ComputedValueFlags::HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND);
|
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
|
if self
|
||||||
.author_specified
|
.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
|
// 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
|
// 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
|
// Note that the border/background properties are non-inherited, so we
|
||||||
// need to do anything else other than just copying the bits over.
|
// 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;
|
// When using this optimization, we also need to copy whether the old
|
||||||
builder.add_flags(cached_style.flags & reset_props_bits);
|
// 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
|
true
|
||||||
}
|
}
|
||||||
|
@ -916,12 +917,7 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
|
||||||
// we have a generic family to actually replace it with.
|
// we have a generic family to actually replace it with.
|
||||||
let prioritize_user_fonts = !use_document_fonts &&
|
let prioritize_user_fonts = !use_document_fonts &&
|
||||||
default_font_type != GenericFontFamily::None &&
|
default_font_type != GenericFontFamily::None &&
|
||||||
matches!(
|
!generic.valid_for_user_font_prioritization();
|
||||||
generic,
|
|
||||||
GenericFontFamily::None |
|
|
||||||
GenericFontFamily::Fantasy |
|
|
||||||
GenericFontFamily::Cursive
|
|
||||||
);
|
|
||||||
|
|
||||||
if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback {
|
if !prioritize_user_fonts && default_font_type == font.mFont.family.families.fallback {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
|
|
|
@ -84,12 +84,6 @@ bitflags! {
|
||||||
/// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845
|
/// https://github.com/w3c/csswg-drafts/issues/4777#issuecomment-604424845
|
||||||
const HAS_AUTHOR_SPECIFIED_BORDER_BACKGROUND = 1 << 14;
|
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`.
|
/// Whether there are author-specified rules for `font-family`.
|
||||||
const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16;
|
const HAS_AUTHOR_SPECIFIED_FONT_FAMILY = 1 << 16;
|
||||||
|
|
||||||
|
|
|
@ -476,6 +476,7 @@ class Longhand(Property):
|
||||||
"FontWeight",
|
"FontWeight",
|
||||||
"GreaterThanOrEqualToOneNumber",
|
"GreaterThanOrEqualToOneNumber",
|
||||||
"GridAutoFlow",
|
"GridAutoFlow",
|
||||||
|
"ImageRendering",
|
||||||
"InitialLetter",
|
"InitialLetter",
|
||||||
"Integer",
|
"Integer",
|
||||||
"JustifyContent",
|
"JustifyContent",
|
||||||
|
|
|
@ -1049,7 +1049,7 @@ fn static_assert() {
|
||||||
% if member:
|
% if member:
|
||||||
ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member};
|
ours.m${gecko_ffi_name}.${member} = others.m${gecko_ffi_name}.${member};
|
||||||
% else:
|
% else:
|
||||||
ours.m${gecko_ffi_name} = others.m${gecko_ffi_name};
|
ours.m${gecko_ffi_name} = others.m${gecko_ffi_name}.clone();
|
||||||
% endif
|
% endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1183,7 +1183,7 @@ fn static_assert() {
|
||||||
<% skip_box_longhands= """display
|
<% skip_box_longhands= """display
|
||||||
animation-name animation-delay animation-duration
|
animation-name animation-delay animation-duration
|
||||||
animation-direction animation-fill-mode animation-play-state
|
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
|
clear transition-duration transition-delay
|
||||||
transition-timing-function transition-property
|
transition-timing-function transition-property
|
||||||
-webkit-line-clamp""" %>
|
-webkit-line-clamp""" %>
|
||||||
|
@ -1445,6 +1445,27 @@ fn static_assert() {
|
||||||
${impl_copy_animation_value('iteration_count', 'IterationCount')}
|
${impl_copy_animation_value('iteration_count', 'IterationCount')}
|
||||||
${impl_animation_or_transition_timing_function('animation')}
|
${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)]
|
#[allow(non_snake_case)]
|
||||||
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
|
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
|
||||||
self.gecko.mLineClamp = match v {
|
self.gecko.mLineClamp = match v {
|
||||||
|
|
|
@ -320,6 +320,22 @@ ${helpers.predefined_type(
|
||||||
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
|
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" %>
|
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
|
||||||
|
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
|
|
|
@ -69,17 +69,13 @@ ${helpers.single_keyword(
|
||||||
|
|
||||||
// According to to CSS-IMAGES-3, `optimizespeed` and `optimizequality` are synonyms for `auto`
|
// 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)
|
// And, firefox doesn't support `pixelated` yet (https://bugzilla.mozilla.org/show_bug.cgi?id=856337)
|
||||||
${helpers.single_keyword(
|
${helpers.predefined_type(
|
||||||
"image-rendering",
|
"image-rendering",
|
||||||
"auto crisp-edges",
|
"ImageRendering",
|
||||||
|
"computed::ImageRendering::Auto",
|
||||||
engines="gecko servo-2013 servo-2020",
|
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",
|
spec="https://drafts.csswg.org/css-images/#propdef-image-rendering",
|
||||||
|
animation_value_type="discrete",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${helpers.single_keyword(
|
${helpers.single_keyword(
|
||||||
|
|
|
@ -104,6 +104,8 @@ ${helpers.predefined_type(
|
||||||
gecko_pref="layout.css.color-scheme.enabled",
|
gecko_pref="layout.css.color-scheme.enabled",
|
||||||
animation_value_type="discrete",
|
animation_value_type="discrete",
|
||||||
has_effect_on_gecko_scrollbars=False,
|
has_effect_on_gecko_scrollbars=False,
|
||||||
|
ignored_when_colors_disabled=True,
|
||||||
|
enabled_in="chrome",
|
||||||
)}
|
)}
|
||||||
|
|
||||||
${helpers.predefined_type(
|
${helpers.predefined_type(
|
||||||
|
|
|
@ -945,18 +945,6 @@ impl LonghandIdSet {
|
||||||
&HAS_NO_EFFECT_ON_SCROLLBARS
|
&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
|
/// Returns the set of border properties for the purpose of disabling native
|
||||||
/// appearance.
|
/// appearance.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -2921,7 +2909,7 @@ pub mod style_structs {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if animation properties are equal between styles, but without
|
/// Returns true if animation properties are equal between styles, but without
|
||||||
/// considering keyframe data.
|
/// considering keyframe data and animation-timeline.
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
pub fn animations_equals(&self, other: &Self) -> bool {
|
pub fn animations_equals(&self, other: &Self) -> bool {
|
||||||
self.animation_name_iter().eq(other.animation_name_iter()) &&
|
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
|
/// Note that the value will usually be the computed value, except for
|
||||||
/// colors, where it's resolved.
|
/// 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,
|
&self,
|
||||||
property_id: LonghandId,
|
property_id: LonghandId,
|
||||||
dest: &mut CssWriter<W>
|
dest: &mut CssStringWriter,
|
||||||
) -> fmt::Result
|
) -> fmt::Result {
|
||||||
where
|
|
||||||
W: Write,
|
|
||||||
{
|
|
||||||
use crate::values::resolved::ToResolvedValue;
|
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 {
|
let context = resolved::Context {
|
||||||
style: self,
|
style: self,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(emilio): Is it worth to merge branches here just like
|
|
||||||
// PropertyDeclaration::to_css does?
|
|
||||||
match property_id {
|
match property_id {
|
||||||
% for prop in data.longhands:
|
% for specified_type, props in groupby(data.longhands, key=lambda x: x.specified_type()):
|
||||||
LonghandId::${prop.camel_case} => {
|
<% props = list(props) %>
|
||||||
let value = self.clone_${prop.ident}();
|
${" |\n".join("LonghandId::{}".format(p.camel_case) for p in props)} => {
|
||||||
value.to_resolved_value(&context).to_css(dest)
|
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
|
% endfor
|
||||||
}
|
}
|
||||||
|
@ -3195,9 +3232,9 @@ impl ComputedValues {
|
||||||
match property {
|
match property {
|
||||||
PropertyDeclarationId::Longhand(id) => {
|
PropertyDeclarationId::Longhand(id) => {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
self.get_longhand_property_value(
|
self.get_resolved_value(
|
||||||
id,
|
id,
|
||||||
&mut CssWriter::new(&mut s)
|
&mut s
|
||||||
).unwrap();
|
).unwrap();
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,11 +189,11 @@ macro_rules! try_parse_one {
|
||||||
sub_properties="animation-name animation-duration
|
sub_properties="animation-name animation-duration
|
||||||
animation-timing-function animation-delay
|
animation-timing-function animation-delay
|
||||||
animation-iteration-count animation-direction
|
animation-iteration-count animation-direction
|
||||||
animation-fill-mode animation-play-state"
|
animation-fill-mode animation-play-state animation-timeline"
|
||||||
rule_types_allowed="Style"
|
rule_types_allowed="Style"
|
||||||
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
|
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()
|
direction fill_mode play_state".split()
|
||||||
%>
|
%>
|
||||||
% for prop in props:
|
% for prop in props:
|
||||||
|
@ -210,6 +210,13 @@ macro_rules! try_parse_one {
|
||||||
% endfor
|
% 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>(
|
fn parse_one_animation<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
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, fill_mode, animation_fill_mode);
|
||||||
try_parse_one!(context, input, play_state, animation_play_state);
|
try_parse_one!(context, input, play_state, animation_play_state);
|
||||||
try_parse_one!(context, input, name, animation_name);
|
try_parse_one!(context, input, name, animation_name);
|
||||||
|
if scroll_linked_animations_enabled() {
|
||||||
|
try_parse_one!(context, input, timeline, animation_timeline);
|
||||||
|
}
|
||||||
|
|
||||||
parsed -= 1;
|
parsed -= 1;
|
||||||
break
|
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
|
// If any value list length is differs then we don't do a shorthand serialization
|
||||||
// either.
|
// either.
|
||||||
% for name in props[1:]:
|
% for name in props[2:]:
|
||||||
if len != self.animation_${name}.0.len() {
|
if len != self.animation_${name}.0.len() {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
% endfor
|
% 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 {
|
for i in 0..len {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
dest.write_str(", ")?;
|
dest.write_str(", ")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
% for name in props[1:]:
|
% for name in props[2:]:
|
||||||
self.animation_${name}.0[i].to_css(dest)?;
|
self.animation_${name}.0[i].to_css(dest)?;
|
||||||
dest.write_str(" ")?;
|
dest.write_str(" ")?;
|
||||||
% endfor
|
% endfor
|
||||||
|
|
||||||
self.animation_name.0[i].to_css(dest)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
<%helpers:shorthand name="list-style"
|
<%helpers:shorthand name="list-style"
|
||||||
engines="gecko servo-2013 servo-2020"
|
engines="gecko servo-2013 servo-2020"
|
||||||
sub_properties="list-style-position list-style-image list-style-type"
|
sub_properties="list-style-position list-style-image list-style-type"
|
||||||
derive_serialize="True"
|
|
||||||
spec="https://drafts.csswg.org/css-lists/#propdef-list-style">
|
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::properties::longhands::{list_style_image, list_style_position, list_style_type};
|
||||||
use crate::values::specified::Image;
|
use crate::values::specified::Image;
|
||||||
|
@ -104,4 +103,43 @@
|
||||||
_ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
|
_ => 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>
|
</%helpers:shorthand>
|
||||||
|
|
|
@ -89,6 +89,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
% if engine == "gecko":
|
% 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 !is_solid_style {
|
||||||
if has_value {
|
if has_value {
|
||||||
dest.write_str(" ")?;
|
dest.write_str(" ")?;
|
||||||
|
@ -104,13 +112,6 @@
|
||||||
self.text_decoration_color.to_css(dest)?;
|
self.text_decoration_color.to_css(dest)?;
|
||||||
has_value = true;
|
has_value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_auto_thickness {
|
|
||||||
if has_value {
|
|
||||||
dest.write_str(" ")?;
|
|
||||||
}
|
|
||||||
self.text_decoration_thickness.to_css(dest)?;
|
|
||||||
}
|
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::selector_map::SelectorMap;
|
||||||
use crate::selector_parser::PseudoElement;
|
use crate::selector_parser::PseudoElement;
|
||||||
use crate::shared_lock::Locked;
|
use crate::shared_lock::Locked;
|
||||||
use crate::stylesheets::Origin;
|
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 selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
|
||||||
use servo_arc::ArcBorrow;
|
use servo_arc::ArcBorrow;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -148,7 +148,7 @@ where
|
||||||
f(self);
|
f(self);
|
||||||
if start != self.rules.len() {
|
if start != self.rules.len() {
|
||||||
self.rules[start..]
|
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.context.current_host = old_host;
|
||||||
self.in_sort_scope = false;
|
self.in_sort_scope = false;
|
||||||
|
@ -173,7 +173,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
self.in_tree(None, |collector| {
|
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]
|
#[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");
|
debug_assert!(self.in_sort_scope, "Rules gotta be sorted");
|
||||||
SelectorMap::get_matching_rules(
|
SelectorMap::get_matching_rules(
|
||||||
self.element,
|
self.element,
|
||||||
|
@ -223,11 +223,12 @@ where
|
||||||
&mut self.context,
|
&mut self.context,
|
||||||
&mut self.flags_setter,
|
&mut self.flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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");
|
debug_assert!(self.in_sort_scope, "Rules gotta be sorted");
|
||||||
map.get_all_matching_rules(
|
map.get_all_matching_rules(
|
||||||
self.element,
|
self.element,
|
||||||
|
@ -236,6 +237,7 @@ where
|
||||||
&mut self.context,
|
&mut self.context,
|
||||||
&mut self.flags_setter,
|
&mut self.flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +279,7 @@ where
|
||||||
let cascade_level = CascadeLevel::AuthorNormal {
|
let cascade_level = CascadeLevel::AuthorNormal {
|
||||||
shadow_cascade_order,
|
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();
|
let cascade_level = CascadeLevel::same_tree_author_normal();
|
||||||
self.in_shadow_tree(containing_shadow.host(), |collector| {
|
self.in_shadow_tree(containing_shadow.host(), |collector| {
|
||||||
if let Some(map) = cascade_data.normal_rules(collector.pseudo_element) {
|
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
|
// Collect rules from :host::part() and such
|
||||||
|
@ -319,7 +321,7 @@ where
|
||||||
|
|
||||||
hash_target.each_part(|part| {
|
hash_target.each_part(|part| {
|
||||||
if let Some(part_rules) = part_rules.get(&part.0) {
|
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 {
|
let cascade_level = CascadeLevel::AuthorNormal {
|
||||||
shadow_cascade_order,
|
shadow_cascade_order,
|
||||||
};
|
};
|
||||||
collector.collect_rules_in_map(host_rules, cascade_level);
|
collector.collect_rules_in_map(host_rules, cascade_level, style_data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,18 +388,17 @@ where
|
||||||
|
|
||||||
let inner_shadow_host = inner_shadow.host();
|
let inner_shadow_host = inner_shadow.host();
|
||||||
let outer_shadow = inner_shadow_host.containing_shadow();
|
let outer_shadow = inner_shadow_host.containing_shadow();
|
||||||
let part_rules = match outer_shadow {
|
let cascade_data = match outer_shadow {
|
||||||
Some(shadow) => shadow
|
Some(shadow) => shadow.style_data(),
|
||||||
.style_data()
|
None => Some(self
|
||||||
.and_then(|data| data.part_rules(self.pseudo_element)),
|
|
||||||
None => self
|
|
||||||
.stylist
|
.stylist
|
||||||
.cascade_data()
|
.cascade_data()
|
||||||
.borrow_for_origin(Origin::Author)
|
.borrow_for_origin(Origin::Author)
|
||||||
.part_rules(self.pseudo_element),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(part_rules) = part_rules {
|
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 containing_host = outer_shadow.map(|s| s.host());
|
||||||
let cascade_level = CascadeLevel::AuthorNormal {
|
let cascade_level = CascadeLevel::AuthorNormal {
|
||||||
shadow_cascade_order,
|
shadow_cascade_order,
|
||||||
|
@ -405,12 +406,13 @@ where
|
||||||
self.in_tree(containing_host, |collector| {
|
self.in_tree(containing_host, |collector| {
|
||||||
for p in &parts {
|
for p in &parts {
|
||||||
if let Some(part_rules) = part_rules.get(&p.0) {
|
if let Some(part_rules) = part_rules.get(&p.0) {
|
||||||
collector.collect_rules_in_list(part_rules, cascade_level);
|
collector.collect_rules_in_list(part_rules, cascade_level, cascade_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
shadow_cascade_order.inc();
|
shadow_cascade_order.inc();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inner_shadow = match outer_shadow {
|
inner_shadow = match outer_shadow {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::hash::map as hash_map;
|
||||||
use crate::hash::{HashMap, HashSet};
|
use crate::hash::{HashMap, HashSet};
|
||||||
use crate::rule_tree::CascadeLevel;
|
use crate::rule_tree::CascadeLevel;
|
||||||
use crate::selector_parser::SelectorImpl;
|
use crate::selector_parser::SelectorImpl;
|
||||||
use crate::stylist::Rule;
|
use crate::stylist::{Rule, CascadeData};
|
||||||
use crate::{Atom, LocalName, Namespace, WeakAtom};
|
use crate::{Atom, LocalName, Namespace, WeakAtom};
|
||||||
use fallible::FallibleVec;
|
use fallible::FallibleVec;
|
||||||
use hashglobe::FailedAllocationError;
|
use hashglobe::FailedAllocationError;
|
||||||
|
@ -104,10 +104,14 @@ pub struct SelectorMap<T: 'static> {
|
||||||
pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
|
pub class_hash: MaybeCaseInsensitiveHashMap<Atom, SmallVec<[T; 1]>>,
|
||||||
/// A hash from local name to rules which contain that local name selector.
|
/// A hash from local name to rules which contain that local name selector.
|
||||||
pub local_name_hash: PrecomputedHashMap<LocalName, SmallVec<[T; 1]>>,
|
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.
|
/// A hash from namespace to rules which contain that namespace selector.
|
||||||
pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>,
|
pub namespace_hash: PrecomputedHashMap<Namespace, SmallVec<[T; 1]>>,
|
||||||
/// All other rules.
|
/// All other rules.
|
||||||
pub other: SmallVec<[T; 1]>,
|
pub other: SmallVec<[T; 1]>,
|
||||||
|
/// Whether we should bucket by attribute names.
|
||||||
|
bucket_attributes: bool,
|
||||||
/// The number of entries in this map.
|
/// The number of entries in this map.
|
||||||
pub count: usize,
|
pub count: usize,
|
||||||
}
|
}
|
||||||
|
@ -129,18 +133,32 @@ impl<T: 'static> SelectorMap<T> {
|
||||||
root: SmallVec::new(),
|
root: SmallVec::new(),
|
||||||
id_hash: MaybeCaseInsensitiveHashMap::new(),
|
id_hash: MaybeCaseInsensitiveHashMap::new(),
|
||||||
class_hash: MaybeCaseInsensitiveHashMap::new(),
|
class_hash: MaybeCaseInsensitiveHashMap::new(),
|
||||||
|
attribute_hash: HashMap::default(),
|
||||||
local_name_hash: HashMap::default(),
|
local_name_hash: HashMap::default(),
|
||||||
namespace_hash: HashMap::default(),
|
namespace_hash: HashMap::default(),
|
||||||
other: SmallVec::new(),
|
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,
|
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.
|
/// Clears the hashmap retaining storage.
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.root.clear();
|
self.root.clear();
|
||||||
self.id_hash.clear();
|
self.id_hash.clear();
|
||||||
self.class_hash.clear();
|
self.class_hash.clear();
|
||||||
|
self.attribute_hash.clear();
|
||||||
self.local_name_hash.clear();
|
self.local_name_hash.clear();
|
||||||
self.namespace_hash.clear();
|
self.namespace_hash.clear();
|
||||||
self.other.clear();
|
self.other.clear();
|
||||||
|
@ -171,6 +189,7 @@ impl SelectorMap<Rule> {
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
flags_setter: &mut F,
|
||||||
cascade_level: CascadeLevel,
|
cascade_level: CascadeLevel,
|
||||||
|
cascade_data: &CascadeData,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
F: FnMut(&E, ElementSelectorFlags),
|
||||||
|
@ -189,6 +208,7 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +221,7 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,10 +235,27 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
flags_setter,
|
||||||
cascade_level,
|
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()) {
|
if let Some(rules) = self.local_name_hash.get(rule_hash_target.local_name()) {
|
||||||
SelectorMap::get_matching_rules(
|
SelectorMap::get_matching_rules(
|
||||||
element,
|
element,
|
||||||
|
@ -226,6 +264,7 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +276,7 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +287,7 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
flags_setter,
|
||||||
cascade_level,
|
cascade_level,
|
||||||
|
cascade_data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,6 +299,7 @@ impl SelectorMap<Rule> {
|
||||||
context: &mut MatchingContext<E::Impl>,
|
context: &mut MatchingContext<E::Impl>,
|
||||||
flags_setter: &mut F,
|
flags_setter: &mut F,
|
||||||
cascade_level: CascadeLevel,
|
cascade_level: CascadeLevel,
|
||||||
|
cascade_data: &CascadeData,
|
||||||
) where
|
) where
|
||||||
E: TElement,
|
E: TElement,
|
||||||
F: FnMut(&E, ElementSelectorFlags),
|
F: FnMut(&E, ElementSelectorFlags),
|
||||||
|
@ -271,7 +313,7 @@ impl SelectorMap<Rule> {
|
||||||
context,
|
context,
|
||||||
flags_setter,
|
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
|
.class_hash
|
||||||
.try_entry(class.clone(), quirks_mode)?
|
.try_entry(class.clone(), quirks_mode)?
|
||||||
.or_insert_with(SmallVec::new),
|
.or_insert_with(SmallVec::new),
|
||||||
|
Bucket::Attribute { name, lower_name } |
|
||||||
Bucket::LocalName { name, lower_name } => {
|
Bucket::LocalName { name, lower_name } => {
|
||||||
// If the local name in the selector isn't lowercase,
|
// If the local name in the selector isn't lowercase,
|
||||||
// insert it into the rule hash twice. This means that,
|
// 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
|
// selector, the rulehash lookup may produce superfluous
|
||||||
// selectors, but the subsequent selector matching work
|
// selectors, but the subsequent selector matching work
|
||||||
// will filter them out.
|
// 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 {
|
if name != lower_name {
|
||||||
self.local_name_hash
|
hash
|
||||||
.try_entry(lower_name.clone())?
|
.try_entry(lower_name.clone())?
|
||||||
.or_insert_with(SmallVec::new)
|
.or_insert_with(SmallVec::new)
|
||||||
.try_push($entry.clone())?;
|
.try_push($entry.clone())?;
|
||||||
}
|
}
|
||||||
self.local_name_hash
|
hash
|
||||||
.try_entry(name.clone())?
|
.try_entry(name.clone())?
|
||||||
.or_insert_with(SmallVec::new)
|
.or_insert_with(SmallVec::new)
|
||||||
},
|
},
|
||||||
|
@ -338,7 +387,7 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
|
||||||
|
|
||||||
let bucket = {
|
let bucket = {
|
||||||
let mut disjoint_buckets = SmallVec::new();
|
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
|
// See if inserting this selector in multiple entries in the
|
||||||
// selector map would be worth it. Consider a case like:
|
// selector map would be worth it. Consider a case like:
|
||||||
|
@ -409,7 +458,9 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
|
||||||
|
|
||||||
let mut done = false;
|
let mut done = false;
|
||||||
element.each_class(|class| {
|
element.each_class(|class| {
|
||||||
if !done {
|
if done {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(v) = self.class_hash.get(class, quirks_mode) {
|
if let Some(v) = self.class_hash.get(class, quirks_mode) {
|
||||||
for entry in v.iter() {
|
for entry in v.iter() {
|
||||||
if !f(&entry) {
|
if !f(&entry) {
|
||||||
|
@ -418,12 +469,32 @@ impl<T: SelectorMapEntry> SelectorMap<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if done {
|
if done {
|
||||||
return false;
|
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;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if done {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(v) = self.local_name_hash.get(element.local_name()) {
|
if let Some(v) = self.local_name_hash.get(element.local_name()) {
|
||||||
for entry in v.iter() {
|
for entry in v.iter() {
|
||||||
if !f(&entry) {
|
if !f(&entry) {
|
||||||
|
@ -507,6 +578,10 @@ enum Bucket<'a> {
|
||||||
name: &'a LocalName,
|
name: &'a LocalName,
|
||||||
lower_name: &'a LocalName,
|
lower_name: &'a LocalName,
|
||||||
},
|
},
|
||||||
|
Attribute {
|
||||||
|
name: &'a LocalName,
|
||||||
|
lower_name: &'a LocalName,
|
||||||
|
},
|
||||||
Class(&'a Atom),
|
Class(&'a Atom),
|
||||||
ID(&'a Atom),
|
ID(&'a Atom),
|
||||||
Root,
|
Root,
|
||||||
|
@ -520,9 +595,10 @@ impl<'a> Bucket<'a> {
|
||||||
Bucket::Universal => 0,
|
Bucket::Universal => 0,
|
||||||
Bucket::Namespace(..) => 1,
|
Bucket::Namespace(..) => 1,
|
||||||
Bucket::LocalName { .. } => 2,
|
Bucket::LocalName { .. } => 2,
|
||||||
Bucket::Class(..) => 3,
|
Bucket::Attribute { .. } => 3,
|
||||||
Bucket::ID(..) => 4,
|
Bucket::Class(..) => 4,
|
||||||
Bucket::Root => 5,
|
Bucket::ID(..) => 5,
|
||||||
|
Bucket::Root => 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,11 +613,24 @@ type DisjointBuckets<'a> = SmallVec<[Bucket<'a>; 5]>;
|
||||||
fn specific_bucket_for<'a>(
|
fn specific_bucket_for<'a>(
|
||||||
component: &'a Component<SelectorImpl>,
|
component: &'a Component<SelectorImpl>,
|
||||||
disjoint_buckets: &mut DisjointBuckets<'a>,
|
disjoint_buckets: &mut DisjointBuckets<'a>,
|
||||||
|
bucket_attributes: bool,
|
||||||
) -> Bucket<'a> {
|
) -> Bucket<'a> {
|
||||||
match *component {
|
match *component {
|
||||||
Component::Root => Bucket::Root,
|
Component::Root => Bucket::Root,
|
||||||
Component::ID(ref id) => Bucket::ID(id),
|
Component::ID(ref id) => Bucket::ID(id),
|
||||||
Component::Class(ref class) => Bucket::Class(class),
|
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 {
|
Component::LocalName(ref selector) => Bucket::LocalName {
|
||||||
name: &selector.name,
|
name: &selector.name,
|
||||||
lower_name: &selector.lower_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
|
// So inserting `span` in the rule hash makes sense since we want to
|
||||||
// match the slotted <span>.
|
// match the slotted <span>.
|
||||||
Component::Slotted(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),
|
Component::Host(Some(ref selector)) => find_bucket(selector.iter(), disjoint_buckets, bucket_attributes),
|
||||||
Component::Is(ref list) | Component::Where(ref list) => {
|
Component::Is(ref list) | Component::Where(ref list) => {
|
||||||
if list.len() == 1 {
|
if list.len() == 1 {
|
||||||
find_bucket(list[0].iter(), disjoint_buckets)
|
find_bucket(list[0].iter(), disjoint_buckets, bucket_attributes)
|
||||||
} else {
|
} else {
|
||||||
for selector in &**list {
|
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);
|
disjoint_buckets.push(bucket);
|
||||||
}
|
}
|
||||||
Bucket::Universal
|
Bucket::Universal
|
||||||
|
@ -593,12 +682,13 @@ fn specific_bucket_for<'a>(
|
||||||
fn find_bucket<'a>(
|
fn find_bucket<'a>(
|
||||||
mut iter: SelectorIter<'a, SelectorImpl>,
|
mut iter: SelectorIter<'a, SelectorImpl>,
|
||||||
disjoint_buckets: &mut DisjointBuckets<'a>,
|
disjoint_buckets: &mut DisjointBuckets<'a>,
|
||||||
|
bucket_attributes: bool,
|
||||||
) -> Bucket<'a> {
|
) -> Bucket<'a> {
|
||||||
let mut current_bucket = Bucket::Universal;
|
let mut current_bucket = Bucket::Universal;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
for ss in &mut iter {
|
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) {
|
if new_bucket.more_specific_than(¤t_bucket) {
|
||||||
current_bucket = new_bucket;
|
current_bucket = new_bucket;
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,12 +190,12 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default background color.
|
/// 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)
|
RGBA::new(255, 255, 255, 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the default color color.
|
/// Returns the default foreground color.
|
||||||
pub fn default_color(&self) -> RGBA {
|
pub fn default_color_for_forced_colors(&self) -> RGBA {
|
||||||
RGBA::new(0, 0, 0, 255)
|
RGBA::new(0, 0, 0, 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +220,24 @@ impl Device {
|
||||||
_ => false,
|
_ => 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
|
/// https://drafts.csswg.org/mediaqueries-4/#width
|
||||||
|
|
|
@ -94,6 +94,12 @@ impl SharedRwLock {
|
||||||
SharedRwLock { cell: None }
|
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.
|
/// Wrap the given data to make its access protected by this lock.
|
||||||
pub fn wrap<T>(&self, data: T) -> Locked<T> {
|
pub fn wrap<T>(&self, data: T) -> Locked<T> {
|
||||||
Locked {
|
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).
|
/// Proof that a shared lock was obtained for writing (servo).
|
||||||
#[cfg(feature = "servo")]
|
#[cfg(feature = "servo")]
|
||||||
pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock);
|
pub struct SharedRwLockWriteGuard<'a>(&'a SharedRwLock);
|
||||||
|
@ -190,25 +204,18 @@ impl<T> Locked<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
fn same_lock_as(&self, derefed_guard: Option<&SomethingZeroSizedButTyped>) -> bool {
|
fn same_lock_as(&self, ptr: *const SomethingZeroSizedButTyped) -> bool {
|
||||||
ptr::eq(
|
ptr::eq(self.shared_lock.ptr(), ptr)
|
||||||
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()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the data for reading.
|
/// Access the data for reading.
|
||||||
pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T {
|
pub fn read_with<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a T {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
assert!(
|
assert!(
|
||||||
self.is_read_only_lock() || self.same_lock_as(guard.0.as_ref().map(|r| &**r)),
|
self.is_read_only_lock() || self.same_lock_as(guard.ptr()),
|
||||||
"Locked::read_with called with a guard from an unrelated SharedRwLock"
|
"Locked::read_with called with a guard from an unrelated SharedRwLock: {:?} vs. {:?}",
|
||||||
|
self.shared_lock.ptr(),
|
||||||
|
guard.ptr(),
|
||||||
);
|
);
|
||||||
#[cfg(not(feature = "gecko"))]
|
#[cfg(not(feature = "gecko"))]
|
||||||
assert!(self.same_lock_as(&guard.0));
|
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 {
|
pub fn write_with<'a>(&'a self, guard: &'a mut SharedRwLockWriteGuard) -> &'a mut T {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
assert!(
|
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"
|
"Locked::write_with called with a guard from a read only or unrelated SharedRwLock"
|
||||||
);
|
);
|
||||||
#[cfg(not(feature = "gecko"))]
|
#[cfg(not(feature = "gecko"))]
|
||||||
|
|
|
@ -786,10 +786,7 @@ impl<E: TElement> StyleSharingCache<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's possible that there are no styles for either id.
|
// It's possible that there are no styles for either id.
|
||||||
let may_match_different_id_rules =
|
if checks::may_match_different_id_rules(shared, target.element, candidate.element) {
|
||||||
checks::may_match_different_id_rules(shared, target.element, candidate.element);
|
|
||||||
|
|
||||||
if may_match_different_id_rules {
|
|
||||||
trace!("Miss: ID Attr");
|
trace!("Miss: ID Attr");
|
||||||
return None;
|
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
|
/// Whether we should skip any item-based display property blockification on
|
||||||
/// this element.
|
/// this element.
|
||||||
fn skip_item_display_fixup<E>(&self, element: Option<E>) -> bool
|
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")]
|
#[cfg(feature = "gecko")]
|
||||||
{
|
{
|
||||||
self.adjust_for_appearance(element);
|
self.adjust_for_appearance(element);
|
||||||
self.adjust_for_inert();
|
|
||||||
self.adjust_for_marker_pseudo();
|
self.adjust_for_marker_pseudo();
|
||||||
}
|
}
|
||||||
self.set_bits();
|
self.set_bits();
|
||||||
|
|
|
@ -396,13 +396,14 @@ macro_rules! font_feature_values_blocks {
|
||||||
type AtRule = ();
|
type AtRule = ();
|
||||||
type Error = StyleParseErrorKind<'i>;
|
type Error = StyleParseErrorKind<'i>;
|
||||||
|
|
||||||
fn parse_prelude<'t>(&mut self,
|
fn parse_prelude<'t>(
|
||||||
|
&mut self,
|
||||||
name: CowRcStr<'i>,
|
name: CowRcStr<'i>,
|
||||||
input: &mut Parser<'i, 't>)
|
input: &mut Parser<'i, 't>,
|
||||||
-> Result<Self::Prelude, ParseError<'i>> {
|
) -> Result<BlockType, ParseError<'i>> {
|
||||||
match_ignore_ascii_case! { &*name,
|
match_ignore_ascii_case! { &*name,
|
||||||
$(
|
$(
|
||||||
$name => Ok(Self::Prelude::$ident_camel),
|
$name => Ok(BlockType::$ident_camel),
|
||||||
)*
|
)*
|
||||||
_ => Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
|
_ => 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::shared_lock::{SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard};
|
||||||
use crate::str::CssStringWriter;
|
use crate::str::CssStringWriter;
|
||||||
use crate::stylesheets::{CssRule, StylesheetInDocument};
|
use crate::stylesheets::{CssRule, StylesheetInDocument};
|
||||||
|
use crate::stylesheets::layer_rule::LayerName;
|
||||||
use crate::values::CssUrl;
|
use crate::values::CssUrl;
|
||||||
use cssparser::SourceLocation;
|
use cssparser::SourceLocation;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
use to_shmem::{self, SharedMemoryBuilder, ToShmem};
|
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.
|
/// A sheet that is held from an import rule.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -34,7 +26,7 @@ pub enum ImportSheet {
|
||||||
Sheet(crate::gecko::data::GeckoStyleSheet),
|
Sheet(crate::gecko::data::GeckoStyleSheet),
|
||||||
/// An @import created while parsing off-main-thread, whose Gecko sheet has
|
/// An @import created while parsing off-main-thread, whose Gecko sheet has
|
||||||
/// yet to be created and attached.
|
/// yet to be created and attached.
|
||||||
Pending(PendingSheet),
|
Pending,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
|
@ -45,11 +37,8 @@ impl ImportSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a pending ImportSheet for a load that has not started yet.
|
/// Creates a pending ImportSheet for a load that has not started yet.
|
||||||
pub fn new_pending(origin: Origin, quirks_mode: QuirksMode) -> Self {
|
pub fn new_pending() -> Self {
|
||||||
ImportSheet::Pending(PendingSheet {
|
ImportSheet::Pending
|
||||||
origin,
|
|
||||||
quirks_mode,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it
|
/// Returns a reference to the GeckoStyleSheet in this ImportSheet, if it
|
||||||
|
@ -63,7 +52,7 @@ impl ImportSheet {
|
||||||
}
|
}
|
||||||
Some(s)
|
Some(s)
|
||||||
},
|
},
|
||||||
ImportSheet::Pending(_) => None,
|
ImportSheet::Pending => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +87,7 @@ impl DeepCloneWithLock for ImportSheet {
|
||||||
};
|
};
|
||||||
ImportSheet::Sheet(unsafe { GeckoStyleSheet::from_addrefed(clone) })
|
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.
|
/// The [`@import`][import] at-rule.
|
||||||
///
|
///
|
||||||
/// [import]: https://drafts.csswg.org/css-cascade-3/#at-import
|
/// [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.
|
/// ImportSheet just has stub behavior until it appears.
|
||||||
pub stylesheet: ImportSheet,
|
pub stylesheet: ImportSheet,
|
||||||
|
|
||||||
|
/// A `layer()` function name.
|
||||||
|
pub layer: Option<ImportLayer>,
|
||||||
|
|
||||||
/// The line and column of the rule's source code.
|
/// The line and column of the rule's source code.
|
||||||
pub source_location: SourceLocation,
|
pub source_location: SourceLocation,
|
||||||
}
|
}
|
||||||
|
@ -170,6 +186,7 @@ impl DeepCloneWithLock for ImportRule {
|
||||||
ImportRule {
|
ImportRule {
|
||||||
url: self.url.clone(),
|
url: self.url.clone(),
|
||||||
stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
|
stylesheet: self.stylesheet.deep_clone_with_lock(lock, guard, params),
|
||||||
|
layer: self.layer.clone(),
|
||||||
source_location: self.source_location.clone(),
|
source_location: self.source_location.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,14 +197,18 @@ impl ToCssWithGuard for ImportRule {
|
||||||
dest.write_str("@import ")?;
|
dest.write_str("@import ")?;
|
||||||
self.url.to_css(&mut CssWriter::new(dest))?;
|
self.url.to_css(&mut CssWriter::new(dest))?;
|
||||||
|
|
||||||
match self.stylesheet.media(guard) {
|
if let Some(media) = self.stylesheet.media(guard) {
|
||||||
Some(media) if !media.is_empty() => {
|
if !media.is_empty() {
|
||||||
dest.write_str(" ")?;
|
dest.write_char(' ')?;
|
||||||
media.to_css(&mut CssWriter::new(dest))?;
|
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::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard};
|
||||||
use crate::shared_lock::{Locked, ToCssWithGuard};
|
use crate::shared_lock::{Locked, ToCssWithGuard};
|
||||||
use crate::str::CssStringWriter;
|
use crate::str::CssStringWriter;
|
||||||
|
use crate::stylesheets::layer_rule::LayerId;
|
||||||
use crate::stylesheets::rule_parser::VendorPrefix;
|
use crate::stylesheets::rule_parser::VendorPrefix;
|
||||||
use crate::stylesheets::{CssRuleType, StylesheetContents};
|
use crate::stylesheets::{CssRuleType, StylesheetContents};
|
||||||
use crate::values::{serialize_percentage, KeyframesName};
|
use crate::values::{serialize_percentage, KeyframesName};
|
||||||
|
@ -357,6 +358,8 @@ pub struct KeyframesAnimation {
|
||||||
pub properties_changed: LonghandIdSet,
|
pub properties_changed: LonghandIdSet,
|
||||||
/// Vendor prefix type the @keyframes has.
|
/// Vendor prefix type the @keyframes has.
|
||||||
pub vendor_prefix: Option<VendorPrefix>,
|
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.
|
/// Get all the animated properties in a keyframes animation.
|
||||||
|
@ -409,12 +412,14 @@ impl KeyframesAnimation {
|
||||||
pub fn from_keyframes(
|
pub fn from_keyframes(
|
||||||
keyframes: &[Arc<Locked<Keyframe>>],
|
keyframes: &[Arc<Locked<Keyframe>>],
|
||||||
vendor_prefix: Option<VendorPrefix>,
|
vendor_prefix: Option<VendorPrefix>,
|
||||||
|
layer_id: LayerId,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut result = KeyframesAnimation {
|
let mut result = KeyframesAnimation {
|
||||||
steps: vec![],
|
steps: vec![],
|
||||||
properties_changed: LonghandIdSet::new(),
|
properties_changed: LonghandIdSet::new(),
|
||||||
vendor_prefix,
|
vendor_prefix,
|
||||||
|
layer_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
if keyframes.is_empty() {
|
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::media_queries::MediaList;
|
||||||
use crate::parser::ParserContext;
|
use crate::parser::ParserContext;
|
||||||
use crate::shared_lock::{Locked, SharedRwLock};
|
use crate::shared_lock::{Locked, SharedRwLock};
|
||||||
use crate::stylesheets::import_rule::ImportRule;
|
use crate::stylesheets::import_rule::{ImportRule, ImportLayer};
|
||||||
use crate::values::CssUrl;
|
use crate::values::CssUrl;
|
||||||
use cssparser::SourceLocation;
|
use cssparser::SourceLocation;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
|
@ -25,5 +25,6 @@ pub trait StylesheetLoader {
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
lock: &SharedRwLock,
|
lock: &SharedRwLock,
|
||||||
media: Arc<Locked<MediaList>>,
|
media: Arc<Locked<MediaList>>,
|
||||||
|
layer: Option<ImportLayer>,
|
||||||
) -> Arc<Locked<ImportRule>>;
|
) -> Arc<Locked<ImportRule>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ mod font_face_rule;
|
||||||
pub mod font_feature_values_rule;
|
pub mod font_feature_values_rule;
|
||||||
pub mod import_rule;
|
pub mod import_rule;
|
||||||
pub mod keyframes_rule;
|
pub mod keyframes_rule;
|
||||||
|
pub mod layer_rule;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod media_rule;
|
mod media_rule;
|
||||||
mod namespace_rule;
|
mod namespace_rule;
|
||||||
|
@ -19,6 +20,7 @@ mod page_rule;
|
||||||
mod rule_list;
|
mod rule_list;
|
||||||
mod rule_parser;
|
mod rule_parser;
|
||||||
mod rules_iterator;
|
mod rules_iterator;
|
||||||
|
pub mod scroll_timeline_rule;
|
||||||
mod style_rule;
|
mod style_rule;
|
||||||
mod stylesheet;
|
mod stylesheet;
|
||||||
pub mod supports_rule;
|
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::font_feature_values_rule::FontFeatureValuesRule;
|
||||||
pub use self::import_rule::ImportRule;
|
pub use self::import_rule::ImportRule;
|
||||||
pub use self::keyframes_rule::KeyframesRule;
|
pub use self::keyframes_rule::KeyframesRule;
|
||||||
|
pub use self::layer_rule::LayerRule;
|
||||||
pub use self::loader::StylesheetLoader;
|
pub use self::loader::StylesheetLoader;
|
||||||
pub use self::media_rule::MediaRule;
|
pub use self::media_rule::MediaRule;
|
||||||
pub use self::namespace_rule::NamespaceRule;
|
pub use self::namespace_rule::NamespaceRule;
|
||||||
|
@ -60,6 +63,7 @@ pub use self::rules_iterator::{AllRules, EffectiveRules};
|
||||||
pub use self::rules_iterator::{
|
pub use self::rules_iterator::{
|
||||||
EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator,
|
EffectiveRulesIterator, NestedRuleIterationCondition, RulesIterator,
|
||||||
};
|
};
|
||||||
|
pub use self::scroll_timeline_rule::ScrollTimelineRule;
|
||||||
pub use self::style_rule::StyleRule;
|
pub use self::style_rule::StyleRule;
|
||||||
pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind};
|
pub use self::stylesheet::{AllowImportRules, SanitizationData, SanitizationKind};
|
||||||
pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet};
|
pub use self::stylesheet::{DocumentStyleSheet, Namespaces, Stylesheet};
|
||||||
|
@ -257,6 +261,8 @@ pub enum CssRule {
|
||||||
Supports(Arc<Locked<SupportsRule>>),
|
Supports(Arc<Locked<SupportsRule>>),
|
||||||
Page(Arc<Locked<PageRule>>),
|
Page(Arc<Locked<PageRule>>),
|
||||||
Document(Arc<Locked<DocumentRule>>),
|
Document(Arc<Locked<DocumentRule>>),
|
||||||
|
Layer(Arc<Locked<LayerRule>>),
|
||||||
|
ScrollTimeline(Arc<Locked<ScrollTimelineRule>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CssRule {
|
impl CssRule {
|
||||||
|
@ -297,16 +303,22 @@ impl CssRule {
|
||||||
CssRule::Document(ref lock) => {
|
CssRule::Document(ref lock) => {
|
||||||
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
|
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)]
|
#[allow(missing_docs)]
|
||||||
#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, FromPrimitive, PartialEq)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum CssRuleType {
|
pub enum CssRuleType {
|
||||||
// https://drafts.csswg.org/cssom/#the-cssrule-interface
|
// https://drafts.csswg.org/cssom/#the-cssrule-interface
|
||||||
Style = 1,
|
Style = 1,
|
||||||
Charset = 2,
|
// Charset = 2, // Historical
|
||||||
Import = 3,
|
Import = 3,
|
||||||
Media = 4,
|
Media = 4,
|
||||||
FontFace = 5,
|
FontFace = 5,
|
||||||
|
@ -315,7 +327,7 @@ pub enum CssRuleType {
|
||||||
Keyframes = 7,
|
Keyframes = 7,
|
||||||
Keyframe = 8,
|
Keyframe = 8,
|
||||||
// https://drafts.csswg.org/cssom/#the-cssrule-interface
|
// https://drafts.csswg.org/cssom/#the-cssrule-interface
|
||||||
Margin = 9,
|
// Margin = 9, // Not implemented yet.
|
||||||
Namespace = 10,
|
Namespace = 10,
|
||||||
// https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface
|
// https://drafts.csswg.org/css-counter-styles-3/#extentions-to-cssrule-interface
|
||||||
CounterStyle = 11,
|
CounterStyle = 11,
|
||||||
|
@ -323,10 +335,14 @@ pub enum CssRuleType {
|
||||||
Supports = 12,
|
Supports = 12,
|
||||||
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface
|
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#extentions-to-cssrule-interface
|
||||||
Document = 13,
|
Document = 13,
|
||||||
// https://drafts.csswg.org/css-fonts-3/#om-fontfeaturevalues
|
// https://drafts.csswg.org/css-fonts/#om-fontfeaturevalues
|
||||||
FontFeatureValues = 14,
|
FontFeatureValues = 14,
|
||||||
// https://drafts.csswg.org/css-device-adapt/#css-rule-interface
|
// https://drafts.csswg.org/css-device-adapt/#css-rule-interface
|
||||||
Viewport = 15,
|
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)]
|
#[allow(missing_docs)]
|
||||||
|
@ -353,6 +369,8 @@ impl CssRule {
|
||||||
CssRule::Supports(_) => CssRuleType::Supports,
|
CssRule::Supports(_) => CssRuleType::Supports,
|
||||||
CssRule::Page(_) => CssRuleType::Page,
|
CssRule::Page(_) => CssRuleType::Page,
|
||||||
CssRule::Document(_) => CssRuleType::Document,
|
CssRule::Document(_) => CssRuleType::Document,
|
||||||
|
CssRule::Layer(_) => CssRuleType::Layer,
|
||||||
|
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,6 +379,7 @@ impl CssRule {
|
||||||
// CssRule::Charset(..) => State::Start,
|
// CssRule::Charset(..) => State::Start,
|
||||||
CssRule::Import(..) => State::Imports,
|
CssRule::Import(..) => State::Imports,
|
||||||
CssRule::Namespace(..) => State::Namespaces,
|
CssRule::Namespace(..) => State::Namespaces,
|
||||||
|
// TODO(emilio): Do we need something for EarlyLayers?
|
||||||
_ => State::Body,
|
_ => State::Body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,6 +504,16 @@ impl DeepCloneWithLock for CssRule {
|
||||||
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
|
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::Supports(ref lock) => lock.read_with(guard).to_css(guard, dest),
|
||||||
CssRule::Page(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::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::str::starts_with_ignore_ascii_case;
|
||||||
use crate::stylesheets::document_rule::DocumentCondition;
|
use crate::stylesheets::document_rule::DocumentCondition;
|
||||||
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
|
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::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::stylesheet::Namespaces;
|
||||||
use crate::stylesheets::supports_rule::SupportsCondition;
|
use crate::stylesheets::supports_rule::SupportsCondition;
|
||||||
use crate::stylesheets::viewport_rule;
|
use crate::stylesheets::{
|
||||||
use crate::stylesheets::AllowImportRules;
|
viewport_rule, AllowImportRules, CorsMode, CssRule, CssRuleType, CssRules, DocumentRule,
|
||||||
use crate::stylesheets::{CorsMode, DocumentRule, FontFeatureValuesRule, KeyframesRule, MediaRule};
|
FontFeatureValuesRule, KeyframesRule, LayerRule, MediaRule, NamespaceRule, PageRule,
|
||||||
use crate::stylesheets::{CssRule, CssRuleType, CssRules, RulesMutateError, StylesheetLoader};
|
RulesMutateError, ScrollTimelineRule, StyleRule, StylesheetLoader, SupportsRule, ViewportRule,
|
||||||
use crate::stylesheets::{NamespaceRule, PageRule, StyleRule, SupportsRule, ViewportRule};
|
};
|
||||||
use crate::values::computed::font::FamilyName;
|
use crate::values::computed::font::FamilyName;
|
||||||
use crate::values::{CssUrl, CustomIdent, KeyframesName};
|
use crate::values::{CssUrl, CustomIdent, KeyframesName, TimelineName};
|
||||||
use crate::{Namespace, Prefix};
|
use crate::{Namespace, Prefix};
|
||||||
use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, RuleListParser};
|
|
||||||
use cssparser::{
|
use cssparser::{
|
||||||
BasicParseError, BasicParseErrorKind, CowRcStr, ParseErrorKind, ParserState, SourcePosition,
|
AtRuleParser, BasicParseError, BasicParseErrorKind, CowRcStr, Parser, ParserState,
|
||||||
|
QualifiedRuleParser, RuleListParser, SourcePosition,
|
||||||
};
|
};
|
||||||
use selectors::SelectorList;
|
use selectors::SelectorList;
|
||||||
use servo_arc::Arc;
|
use servo_arc::Arc;
|
||||||
|
@ -130,12 +133,14 @@ impl<'b> TopLevelRuleParser<'b> {
|
||||||
pub enum State {
|
pub enum State {
|
||||||
/// We haven't started parsing rules.
|
/// We haven't started parsing rules.
|
||||||
Start = 1,
|
Start = 1,
|
||||||
/// We're parsing `@import` rules.
|
/// We're parsing early `@layer` statement rules.
|
||||||
Imports = 2,
|
EarlyLayers = 2,
|
||||||
|
/// We're parsing `@import` and early `@layer` statement rules.
|
||||||
|
Imports = 3,
|
||||||
/// We're parsing `@namespace` rules.
|
/// We're parsing `@namespace` rules.
|
||||||
Namespaces = 3,
|
Namespaces = 4,
|
||||||
/// We're parsing the main body of the stylesheet.
|
/// We're parsing the main body of the stylesheet.
|
||||||
Body = 4,
|
Body = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
|
#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
|
||||||
|
@ -168,9 +173,13 @@ pub enum AtRulePrelude {
|
||||||
/// A @document rule, with its conditional.
|
/// A @document rule, with its conditional.
|
||||||
Document(DocumentCondition),
|
Document(DocumentCondition),
|
||||||
/// A @import rule prelude.
|
/// A @import rule prelude.
|
||||||
Import(CssUrl, Arc<Locked<MediaList>>),
|
Import(CssUrl, Arc<Locked<MediaList>>, Option<ImportLayer>),
|
||||||
/// A @namespace rule prelude.
|
/// A @namespace rule prelude.
|
||||||
Namespace(Option<Prefix>, Namespace),
|
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> {
|
impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
|
@ -182,7 +191,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
name: CowRcStr<'i>,
|
name: CowRcStr<'i>,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<Self::Prelude, ParseError<'i>> {
|
) -> Result<AtRulePrelude, ParseError<'i>> {
|
||||||
match_ignore_ascii_case! { &*name,
|
match_ignore_ascii_case! { &*name,
|
||||||
"import" => {
|
"import" => {
|
||||||
if !self.check_state(State::Imports) {
|
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_string = input.expect_url_or_string()?.as_ref().to_owned();
|
||||||
let url = CssUrl::parse_from_string(url_string, &self.context, CorsMode::None);
|
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 = MediaList::parse(&self.context, input);
|
||||||
let media = Arc::new(self.shared_lock.wrap(media));
|
let media = Arc::new(self.shared_lock.wrap(media));
|
||||||
|
|
||||||
let prelude = AtRulePrelude::Import(url, media);
|
return Ok(AtRulePrelude::Import(url, media, layer));
|
||||||
|
|
||||||
return Ok(prelude);
|
|
||||||
},
|
},
|
||||||
"namespace" => {
|
"namespace" => {
|
||||||
if !self.check_state(State::Namespaces) {
|
if !self.check_state(State::Namespaces) {
|
||||||
|
@ -225,8 +254,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
let url = Namespace::from(maybe_namespace.as_ref());
|
let url = Namespace::from(maybe_namespace.as_ref());
|
||||||
let prelude = AtRulePrelude::Namespace(prefix, url);
|
return Ok(AtRulePrelude::Namespace(prefix, url));
|
||||||
return Ok(prelude);
|
|
||||||
},
|
},
|
||||||
// @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
|
// @charset is removed by rust-cssparser if it’s the first rule in the stylesheet
|
||||||
// anything left is invalid.
|
// anything left is invalid.
|
||||||
|
@ -263,7 +291,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
start: &ParserState,
|
start: &ParserState,
|
||||||
) -> Result<Self::AtRule, ()> {
|
) -> Result<Self::AtRule, ()> {
|
||||||
let rule = match prelude {
|
let rule = match prelude {
|
||||||
AtRulePrelude::Import(url, media) => {
|
AtRulePrelude::Import(url, media, layer) => {
|
||||||
let loader = self
|
let loader = self
|
||||||
.loader
|
.loader
|
||||||
.expect("Expected a stylesheet loader for @import");
|
.expect("Expected a stylesheet loader for @import");
|
||||||
|
@ -274,6 +302,7 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
&self.context,
|
&self.context,
|
||||||
&self.shared_lock,
|
&self.shared_lock,
|
||||||
media,
|
media,
|
||||||
|
layer,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.state = State::Imports;
|
self.state = State::Imports;
|
||||||
|
@ -295,7 +324,19 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
|
||||||
source_location: start.source_location(),
|
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))
|
Ok((start.position(), rule))
|
||||||
|
@ -379,41 +420,37 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
||||||
name: CowRcStr<'i>,
|
name: CowRcStr<'i>,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<Self::Prelude, ParseError<'i>> {
|
) -> Result<Self::Prelude, ParseError<'i>> {
|
||||||
match_ignore_ascii_case! { &*name,
|
Ok(match_ignore_ascii_case! { &*name,
|
||||||
"media" => {
|
"media" => {
|
||||||
let media_queries = MediaList::parse(self.context, input);
|
let media_queries = MediaList::parse(self.context, input);
|
||||||
let arc = Arc::new(self.shared_lock.wrap(media_queries));
|
let arc = Arc::new(self.shared_lock.wrap(media_queries));
|
||||||
Ok(Self::Prelude::Media(arc))
|
AtRulePrelude::Media(arc)
|
||||||
},
|
},
|
||||||
"supports" => {
|
"supports" => {
|
||||||
let cond = SupportsCondition::parse(input)?;
|
let cond = SupportsCondition::parse(input)?;
|
||||||
Ok(Self::Prelude::Supports(cond))
|
AtRulePrelude::Supports(cond)
|
||||||
},
|
},
|
||||||
"font-face" => {
|
"font-face" => {
|
||||||
Ok(Self::Prelude::FontFace)
|
AtRulePrelude::FontFace
|
||||||
},
|
},
|
||||||
"font-feature-values" => {
|
"layer" => {
|
||||||
if !cfg!(feature = "gecko") {
|
let names = input.try_parse(|input| {
|
||||||
// Support for this rule is not fully implemented in Servo yet.
|
input.parse_comma_separated(|input| {
|
||||||
return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
|
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)?;
|
let family_names = parse_family_name_list(self.context, input)?;
|
||||||
Ok(Self::Prelude::FontFeatureValues(family_names))
|
AtRulePrelude::FontFeatureValues(family_names)
|
||||||
},
|
},
|
||||||
"counter-style" => {
|
"counter-style" if cfg!(feature = "gecko") => {
|
||||||
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())))
|
|
||||||
}
|
|
||||||
let name = parse_counter_style_name_definition(input)?;
|
let name = parse_counter_style_name_definition(input)?;
|
||||||
Ok(Self::Prelude::CounterStyle(name))
|
AtRulePrelude::CounterStyle(name)
|
||||||
},
|
},
|
||||||
"viewport" => {
|
"viewport" if viewport_rule::enabled() => {
|
||||||
if viewport_rule::enabled() {
|
AtRulePrelude::Viewport
|
||||||
Ok(Self::Prelude::Viewport)
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
|
"keyframes" | "-webkit-keyframes" | "-moz-keyframes" => {
|
||||||
let prefix = if starts_with_ignore_ascii_case(&*name, "-webkit-") {
|
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())))
|
return Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
|
||||||
}
|
}
|
||||||
let name = KeyframesName::parse(self.context, input)?;
|
let name = KeyframesName::parse(self.context, input)?;
|
||||||
|
AtRulePrelude::Keyframes(name, prefix)
|
||||||
Ok(Self::Prelude::Keyframes(name, prefix))
|
|
||||||
},
|
},
|
||||||
"page" => {
|
"page" if cfg!(feature = "gecko") => {
|
||||||
if cfg!(feature = "gecko") {
|
AtRulePrelude::Page
|
||||||
Ok(Self::Prelude::Page)
|
|
||||||
} else {
|
|
||||||
Err(input.new_custom_error(StyleParseErrorKind::UnsupportedAtRule(name.clone())))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"-moz-document" => {
|
"-moz-document" if cfg!(feature = "gecko") => {
|
||||||
if !cfg!(feature = "gecko") {
|
|
||||||
return Err(input.new_custom_error(
|
|
||||||
StyleParseErrorKind::UnsupportedAtRule(name.clone())
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
let cond = DocumentCondition::parse(self.context, input)?;
|
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>(
|
fn parse_block<'t>(
|
||||||
|
@ -577,14 +608,62 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
|
||||||
},
|
},
|
||||||
))))
|
))))
|
||||||
},
|
},
|
||||||
_ => Err(ParseError {
|
AtRulePrelude::Layer(names) => {
|
||||||
kind: ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(CowRcStr::from(
|
let name = match names.len() {
|
||||||
"Unsupported AtRule Prelude.",
|
0 | 1 => names.into_iter().next(),
|
||||||
))),
|
_ => return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid)),
|
||||||
location: start.source_location(),
|
};
|
||||||
}),
|
Ok(CssRule::Layer(Arc::new(self.shared_lock.wrap(
|
||||||
|
LayerRule {
|
||||||
|
kind: LayerRuleKind::Block {
|
||||||
|
name,
|
||||||
|
rules: self.parse_nested_rules(input, CssRuleType::Layer),
|
||||||
|
},
|
||||||
|
source_location: start.source_location(),
|
||||||
|
},
|
||||||
|
))))
|
||||||
|
},
|
||||||
|
AtRulePrelude::Import(..) | AtRulePrelude::Namespace(..) => {
|
||||||
|
// These rules don't have blocks.
|
||||||
|
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)]
|
#[inline(never)]
|
||||||
|
|
|
@ -51,18 +51,15 @@ where
|
||||||
pub fn skip_children(&mut self) {
|
pub fn skip_children(&mut self) {
|
||||||
self.stack.pop();
|
self.stack.pop();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn children_of_rule<'a, C>(
|
/// Returns the children of `rule`, and whether `rule` is effective.
|
||||||
|
pub fn children(
|
||||||
rule: &'a CssRule,
|
rule: &'a CssRule,
|
||||||
device: &'a Device,
|
device: &'a Device,
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
guard: &'a SharedRwLockReadGuard<'_>,
|
guard: &'a SharedRwLockReadGuard<'_>,
|
||||||
effective: &mut bool,
|
effective: &mut bool,
|
||||||
) -> Option<slice::Iter<'a, CssRule>>
|
) -> Option<slice::Iter<'a, CssRule>> {
|
||||||
where
|
|
||||||
C: NestedRuleIterationCondition + 'static,
|
|
||||||
{
|
|
||||||
*effective = true;
|
*effective = true;
|
||||||
match *rule {
|
match *rule {
|
||||||
CssRule::Namespace(_) |
|
CssRule::Namespace(_) |
|
||||||
|
@ -71,6 +68,7 @@ where
|
||||||
CssRule::CounterStyle(_) |
|
CssRule::CounterStyle(_) |
|
||||||
CssRule::Viewport(_) |
|
CssRule::Viewport(_) |
|
||||||
CssRule::Keyframes(_) |
|
CssRule::Keyframes(_) |
|
||||||
|
CssRule::ScrollTimeline(_) |
|
||||||
CssRule::Page(_) |
|
CssRule::Page(_) |
|
||||||
CssRule::FontFeatureValues(_) => None,
|
CssRule::FontFeatureValues(_) => None,
|
||||||
CssRule::Import(ref import_rule) => {
|
CssRule::Import(ref import_rule) => {
|
||||||
|
@ -105,6 +103,16 @@ where
|
||||||
}
|
}
|
||||||
Some(supports_rule.rules.read_with(guard).0.iter())
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +137,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut effective = true;
|
let mut effective = true;
|
||||||
let children = children_of_rule::<C>(
|
let children = Self::children(
|
||||||
rule,
|
rule,
|
||||||
self.device,
|
self.device,
|
||||||
self.quirks_mode,
|
self.quirks_mode,
|
||||||
|
@ -315,7 +323,7 @@ impl<'a, 'b> EffectiveRulesIterator<'a, 'b> {
|
||||||
guard: &'a SharedRwLockReadGuard<'b>,
|
guard: &'a SharedRwLockReadGuard<'b>,
|
||||||
rule: &'a CssRule,
|
rule: &'a CssRule,
|
||||||
) -> Self {
|
) -> 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()))
|
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! {
|
rule_filter! {
|
||||||
effective_style_rules(Style => StyleRule),
|
effective_style_rules(Style => StyleRule),
|
||||||
effective_media_rules(Media => MediaRule),
|
|
||||||
effective_font_face_rules(FontFace => FontFaceRule),
|
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_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::Document(..) |
|
||||||
CssRule::Media(..) |
|
CssRule::Media(..) |
|
||||||
CssRule::Supports(..) |
|
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,
|
CssRule::FontFace(..) | CssRule::Namespace(..) | CssRule::Style(..) => true,
|
||||||
|
|
||||||
|
@ -375,7 +371,8 @@ impl SanitizationKind {
|
||||||
CssRule::Page(..) |
|
CssRule::Page(..) |
|
||||||
CssRule::FontFeatureValues(..) |
|
CssRule::FontFeatureValues(..) |
|
||||||
CssRule::Viewport(..) |
|
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::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
|
||||||
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
|
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
|
||||||
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
|
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::viewport_rule::{self, MaybeNew, ViewportRule};
|
||||||
use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents};
|
use crate::stylesheets::{StyleRule, StylesheetInDocument, StylesheetContents};
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
use crate::stylesheets::{CounterStyleRule, FontFaceRule, FontFeatureValuesRule, PageRule};
|
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::thread_state::{self, ThreadState};
|
||||||
use crate::{Atom, LocalName, Namespace, WeakAtom};
|
use crate::{Atom, LocalName, Namespace, WeakAtom};
|
||||||
use fallible::FallibleVec;
|
use fallible::FallibleVec;
|
||||||
|
@ -292,6 +293,8 @@ impl CascadeDataCacheEntry for UserAgentCascadeData {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new_data.cascade_data.compute_layer_order();
|
||||||
|
|
||||||
Ok(Arc::new(new_data))
|
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
|
/// Data resulting from performing the CSS cascade that is specific to a given
|
||||||
/// origin.
|
/// origin.
|
||||||
///
|
///
|
||||||
|
@ -1931,6 +1951,12 @@ pub struct CascadeData {
|
||||||
/// by name.
|
/// by name.
|
||||||
animations: PrecomputedHashMap<Atom, KeyframesAnimation>,
|
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 cached from the last rebuild.
|
||||||
effective_media_query_results: EffectiveMediaQueryResults,
|
effective_media_query_results: EffectiveMediaQueryResults,
|
||||||
|
|
||||||
|
@ -1961,8 +1987,18 @@ impl CascadeData {
|
||||||
state_dependencies: ElementState::empty(),
|
state_dependencies: ElementState::empty(),
|
||||||
document_state_dependencies: DocumentState::empty(),
|
document_state_dependencies: DocumentState::empty(),
|
||||||
mapped_ids: PrecomputedHashSet::default(),
|
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(),
|
animations: Default::default(),
|
||||||
|
layer_id: Default::default(),
|
||||||
|
layers: smallvec::smallvec![CascadeLayer::root()],
|
||||||
extra_data: ExtraStyleData::default(),
|
extra_data: ExtraStyleData::default(),
|
||||||
effective_media_query_results: EffectiveMediaQueryResults::new(),
|
effective_media_query_results: EffectiveMediaQueryResults::new(),
|
||||||
rules_source_order: 0,
|
rules_source_order: 0,
|
||||||
|
@ -2009,6 +2045,8 @@ impl CascadeData {
|
||||||
result.is_ok()
|
result.is_ok()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.compute_layer_order();
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2070,6 +2108,43 @@ impl CascadeData {
|
||||||
self.part_rules.is_some()
|
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`.
|
/// Collects all the applicable media query results into `results`.
|
||||||
///
|
///
|
||||||
/// This duplicates part of the logic in `add_stylesheet`, which is
|
/// This duplicates part of the logic in `add_stylesheet`, which is
|
||||||
|
@ -2117,31 +2192,25 @@ impl CascadeData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns Err(..) to signify OOM
|
fn add_rule_list<S>(
|
||||||
fn add_stylesheet<S>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
|
rules: std::slice::Iter<'_, CssRule>,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
quirks_mode: QuirksMode,
|
quirks_mode: QuirksMode,
|
||||||
stylesheet: &S,
|
stylesheet: &S,
|
||||||
guard: &SharedRwLockReadGuard,
|
guard: &SharedRwLockReadGuard,
|
||||||
rebuild_kind: SheetRebuildKind,
|
rebuild_kind: SheetRebuildKind,
|
||||||
|
mut current_layer: &mut LayerName,
|
||||||
|
current_layer_id: LayerId,
|
||||||
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
|
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
|
||||||
) -> Result<(), FailedAllocationError>
|
) -> Result<(), FailedAllocationError>
|
||||||
where
|
where
|
||||||
S: StylesheetInDocument + 'static,
|
S: StylesheetInDocument + 'static,
|
||||||
{
|
{
|
||||||
if !stylesheet.enabled() || !stylesheet.is_effective_for_device(device, guard) {
|
for rule in rules {
|
||||||
return Ok(());
|
// 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;
|
||||||
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) {
|
|
||||||
match *rule {
|
match *rule {
|
||||||
CssRule::Style(ref locked) => {
|
CssRule::Style(ref locked) => {
|
||||||
let style_rule = locked.read_with(&guard);
|
let style_rule = locked.read_with(&guard);
|
||||||
|
@ -2154,7 +2223,8 @@ impl CascadeData {
|
||||||
if let Some(pseudo) = pseudo_element {
|
if let Some(pseudo) = pseudo_element {
|
||||||
if pseudo.is_precomputed() {
|
if pseudo.is_precomputed() {
|
||||||
debug_assert!(selector.is_universal());
|
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
|
precomputed_pseudo_element_decls
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
@ -2165,6 +2235,7 @@ impl CascadeData {
|
||||||
self.rules_source_order,
|
self.rules_source_order,
|
||||||
CascadeLevel::UANormal,
|
CascadeLevel::UANormal,
|
||||||
selector.specificity(),
|
selector.specificity(),
|
||||||
|
LayerOrder::root(),
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -2180,6 +2251,7 @@ impl CascadeData {
|
||||||
hashes,
|
hashes,
|
||||||
locked.clone(),
|
locked.clone(),
|
||||||
self.rules_source_order,
|
self.rules_source_order,
|
||||||
|
current_layer_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if rebuild_kind.should_rebuild_invalidation() {
|
if rebuild_kind.should_rebuild_invalidation() {
|
||||||
|
@ -2243,42 +2315,51 @@ impl CascadeData {
|
||||||
}
|
}
|
||||||
self.rules_source_order += 1;
|
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) => {
|
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);
|
let keyframes_rule = keyframes_rule.read_with(guard);
|
||||||
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
|
debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
|
||||||
|
match self.animations.try_entry(keyframes_rule.name.as_atom().clone())? {
|
||||||
// Don't let a prefixed keyframes animation override a non-prefixed one.
|
Entry::Vacant(e) => {
|
||||||
let needs_insertion = keyframes_rule.vendor_prefix.is_none() ||
|
e.insert(KeyframesAnimation::from_keyframes(
|
||||||
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.keyframes,
|
||||||
keyframes_rule.vendor_prefix.clone(),
|
keyframes_rule.vendor_prefix.clone(),
|
||||||
|
current_layer_id,
|
||||||
guard,
|
guard,
|
||||||
);
|
));
|
||||||
debug!("Found valid keyframe animation: {:?}", animation);
|
},
|
||||||
self.animations
|
Entry::Occupied(mut e) => {
|
||||||
.try_insert(keyframes_rule.name.as_atom().clone(), animation)?;
|
// 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")]
|
#[cfg(feature = "gecko")]
|
||||||
CssRule::FontFace(ref rule) => {
|
CssRule::FontFace(ref rule) => {
|
||||||
self.extra_data.add_font_face(rule);
|
self.extra_data.add_font_face(rule);
|
||||||
|
@ -2295,11 +2376,220 @@ impl CascadeData {
|
||||||
CssRule::Page(ref rule) => {
|
CssRule::Page(ref rule) => {
|
||||||
self.extra_data.add_page(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.
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2345,9 +2635,11 @@ impl CascadeData {
|
||||||
CssRule::CounterStyle(..) |
|
CssRule::CounterStyle(..) |
|
||||||
CssRule::Supports(..) |
|
CssRule::Supports(..) |
|
||||||
CssRule::Keyframes(..) |
|
CssRule::Keyframes(..) |
|
||||||
|
CssRule::ScrollTimeline(..) |
|
||||||
CssRule::Page(..) |
|
CssRule::Page(..) |
|
||||||
CssRule::Viewport(..) |
|
CssRule::Viewport(..) |
|
||||||
CssRule::Document(..) |
|
CssRule::Document(..) |
|
||||||
|
CssRule::Layer(..) |
|
||||||
CssRule::FontFeatureValues(..) => {
|
CssRule::FontFeatureValues(..) => {
|
||||||
// Not affected by device changes.
|
// Not affected by device changes.
|
||||||
continue;
|
continue;
|
||||||
|
@ -2413,6 +2705,9 @@ impl CascadeData {
|
||||||
host_rules.clear();
|
host_rules.clear();
|
||||||
}
|
}
|
||||||
self.animations.clear();
|
self.animations.clear();
|
||||||
|
self.layer_id.clear();
|
||||||
|
self.layers.clear();
|
||||||
|
self.layers.push(CascadeLayer::root());
|
||||||
self.extra_data.clear();
|
self.extra_data.clear();
|
||||||
self.rules_source_order = 0;
|
self.rules_source_order = 0;
|
||||||
self.num_selectors = 0;
|
self.num_selectors = 0;
|
||||||
|
@ -2501,6 +2796,9 @@ pub struct Rule {
|
||||||
/// we could repurpose that storage here if we needed to.
|
/// we could repurpose that storage here if we needed to.
|
||||||
pub source_order: u32,
|
pub source_order: u32,
|
||||||
|
|
||||||
|
/// The current layer id of this style rule.
|
||||||
|
pub layer_id: LayerId,
|
||||||
|
|
||||||
/// The actual style rule.
|
/// The actual style rule.
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "gecko",
|
feature = "gecko",
|
||||||
|
@ -2527,9 +2825,16 @@ impl Rule {
|
||||||
pub fn to_applicable_declaration_block(
|
pub fn to_applicable_declaration_block(
|
||||||
&self,
|
&self,
|
||||||
level: CascadeLevel,
|
level: CascadeLevel,
|
||||||
|
cascade_data: &CascadeData,
|
||||||
) -> ApplicableDeclarationBlock {
|
) -> ApplicableDeclarationBlock {
|
||||||
let source = StyleSource::from_rule(self.style_rule.clone());
|
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.
|
/// Creates a new Rule.
|
||||||
|
@ -2538,12 +2843,14 @@ impl Rule {
|
||||||
hashes: AncestorHashes,
|
hashes: AncestorHashes,
|
||||||
style_rule: Arc<Locked<StyleRule>>,
|
style_rule: Arc<Locked<StyleRule>>,
|
||||||
source_order: u32,
|
source_order: u32,
|
||||||
|
layer_id: LayerId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Rule {
|
Rule {
|
||||||
selector: selector,
|
selector,
|
||||||
hashes: hashes,
|
hashes,
|
||||||
style_rule: style_rule,
|
style_rule,
|
||||||
source_order: source_order,
|
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::generics::box_::VerticalAlign as GenericVerticalAlign;
|
||||||
use crate::values::specified::box_ as specified;
|
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_::{
|
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.
|
/// A computed value for the `vertical-align` property.
|
||||||
pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>;
|
pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>;
|
||||||
|
|
|
@ -439,6 +439,26 @@ pub enum GenericFontFamily {
|
||||||
MozEmoji,
|
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 {
|
impl Parse for SingleFontFamily {
|
||||||
/// Parse a font-family value.
|
/// Parse a font-family value.
|
||||||
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
|
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());
|
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
|
/// If there's a generic font family on the list which is suitable for user
|
||||||
/// fantasy), then move it to the front of the list. Otherwise, prepend the
|
/// font prioritization, then move it to the front of the list. Otherwise,
|
||||||
/// default generic.
|
/// prepend the default generic.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) {
|
pub (crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) {
|
||||||
let index_of_first_generic = self.iter().position(|f| {
|
let index_of_first_generic = self.iter().position(|f| {
|
||||||
match *f {
|
match *f {
|
||||||
SingleFontFamily::Generic(f) => f != GenericFontFamily::Cursive && f != GenericFontFamily::Fantasy,
|
SingleFontFamily::Generic(f) => f.valid_for_user_font_prioritization(),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,8 @@ use std::f32::consts::PI;
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use style_traits::{CssWriter, ToCss};
|
use style_traits::{CssWriter, ToCss};
|
||||||
|
|
||||||
|
pub use specified::ImageRendering;
|
||||||
|
|
||||||
/// Computed values for an image according to CSS-IMAGES.
|
/// Computed values for an image according to CSS-IMAGES.
|
||||||
/// <https://drafts.csswg.org/css-images/#image-values>
|
/// <https://drafts.csswg.org/css-images/#image-values>
|
||||||
pub type Image =
|
pub type Image =
|
||||||
|
|
|
@ -189,7 +189,7 @@ impl Size {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
GenericSize::MinContent |
|
GenericSize::MinContent |
|
||||||
GenericSize::MaxContent |
|
GenericSize::MaxContent |
|
||||||
GenericSize::MozFitContent |
|
GenericSize::FitContent |
|
||||||
GenericSize::MozAvailable |
|
GenericSize::MozAvailable |
|
||||||
GenericSize::FitContentFunction(_) => false
|
GenericSize::FitContentFunction(_) => false
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub use self::basic_shape::FillRule;
|
||||||
pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
|
pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
|
||||||
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
|
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
|
||||||
pub use self::border::{BorderImageSlice, BorderImageWidth};
|
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_::{Appearance, BreakBetween, BreakWithin, Clear, Float};
|
||||||
pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
|
pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
|
||||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
|
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::{FontVariantAlternates, FontWeight};
|
||||||
pub use self::font::{FontVariantEastAsian, FontVariationSettings};
|
pub use self::font::{FontVariantEastAsian, FontVariationSettings};
|
||||||
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
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::{CSSPixelLength, NonNegativeLength};
|
||||||
pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber};
|
pub use self::length::{Length, LengthOrNumber, LengthPercentage, NonNegativeLengthOrNumber};
|
||||||
pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size};
|
pub use self::length::{LengthOrAuto, LengthPercentageOrAuto, MaxSize, Size};
|
||||||
|
|
|
@ -157,7 +157,7 @@ pub enum GenericSize<LengthPercent> {
|
||||||
MinContent,
|
MinContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[animation(error)]
|
#[animation(error)]
|
||||||
MozFitContent,
|
FitContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[animation(error)]
|
#[animation(error)]
|
||||||
MozAvailable,
|
MozAvailable,
|
||||||
|
@ -207,15 +207,13 @@ pub enum GenericMaxSize<LengthPercent> {
|
||||||
None,
|
None,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[animation(error)]
|
#[animation(error)]
|
||||||
#[parse(aliases = "-moz-max-content")]
|
|
||||||
MaxContent,
|
MaxContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[animation(error)]
|
#[animation(error)]
|
||||||
#[parse(aliases = "-moz-min-content")]
|
|
||||||
MinContent,
|
MinContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[animation(error)]
|
#[animation(error)]
|
||||||
MozFitContent,
|
FitContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[animation(error)]
|
#[animation(error)]
|
||||||
MozAvailable,
|
MozAvailable,
|
||||||
|
|
|
@ -687,7 +687,7 @@ pub trait IsParallelTo {
|
||||||
|
|
||||||
impl<Number, Angle> ToCss for Rotate<Number, Angle>
|
impl<Number, Angle> ToCss for Rotate<Number, Angle>
|
||||||
where
|
where
|
||||||
Number: Copy + ToCss,
|
Number: Copy + ToCss + Zero,
|
||||||
Angle: ToCss,
|
Angle: ToCss,
|
||||||
(Number, Number, Number): IsParallelTo,
|
(Number, Number, Number): IsParallelTo,
|
||||||
{
|
{
|
||||||
|
@ -700,25 +700,41 @@ where
|
||||||
Rotate::None => dest.write_str("none"),
|
Rotate::None => dest.write_str("none"),
|
||||||
Rotate::Rotate(ref angle) => angle.to_css(dest),
|
Rotate::Rotate(ref angle) => angle.to_css(dest),
|
||||||
Rotate::Rotate3D(x, y, z, ref angle) => {
|
Rotate::Rotate3D(x, y, z, ref angle) => {
|
||||||
// If a 3d rotation is specified, the property must serialize with an axis
|
// If the axis is parallel with the x or y axes, it must serialize as the
|
||||||
// specified. If the axis is parallel with the x, y, or z axises, it must
|
// appropriate keyword. If a rotation about the z axis (that is, in 2D) is
|
||||||
// serialize as the appropriate keyword.
|
// specified, the property must serialize as just an <angle>
|
||||||
|
//
|
||||||
// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
|
// https://drafts.csswg.org/css-transforms-2/#individual-transform-serialization
|
||||||
let v = (x, y, z);
|
let v = (x, y, z);
|
||||||
if v.is_parallel_to(&DirectionVector::new(1., 0., 0.)) {
|
let axis = if x.is_zero() && y.is_zero() && z.is_zero() {
|
||||||
dest.write_char('x')?;
|
// 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.)) {
|
} 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.)) {
|
} 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 {
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
match axis {
|
||||||
|
Some(a) => dest.write_str(a)?,
|
||||||
|
None => {
|
||||||
x.to_css(dest)?;
|
x.to_css(dest)?;
|
||||||
dest.write_char(' ')?;
|
dest.write_char(' ')?;
|
||||||
y.to_css(dest)?;
|
y.to_css(dest)?;
|
||||||
dest.write_char(' ')?;
|
dest.write_char(' ')?;
|
||||||
z.to_css(dest)?;
|
z.to_css(dest)?;
|
||||||
}
|
|
||||||
dest.write_char(' ')?;
|
dest.write_char(' ')?;
|
||||||
|
}
|
||||||
|
}
|
||||||
angle.to_css(dest)
|
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>
|
/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name>
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
|
Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
|
||||||
)]
|
)]
|
||||||
pub enum KeyframesName {
|
#[repr(C, u8)]
|
||||||
|
pub enum TimelineOrKeyframesName {
|
||||||
/// <custom-ident>
|
/// <custom-ident>
|
||||||
Ident(CustomIdent),
|
Ident(CustomIdent),
|
||||||
/// <string>
|
/// <string>
|
||||||
QuotedString(Atom),
|
QuotedString(Atom),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyframesName {
|
impl TimelineOrKeyframesName {
|
||||||
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
|
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
|
||||||
pub fn from_ident(value: &str) -> Self {
|
pub fn from_ident(value: &str) -> Self {
|
||||||
let location = SourceLocation { line: 0, column: 0 };
|
let location = SourceLocation { line: 0, column: 0 };
|
||||||
let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok();
|
let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok();
|
||||||
match custom_ident {
|
match custom_ident {
|
||||||
Some(ident) => KeyframesName::Ident(ident),
|
Some(ident) => Self::Ident(ident),
|
||||||
None => KeyframesName::QuotedString(value.into()),
|
None => Self::QuotedString(value.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new KeyframesName from Atom.
|
/// Create a new TimelineOrKeyframesName from Atom.
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub fn from_atom(atom: Atom) -> Self {
|
pub fn from_atom(atom: Atom) -> Self {
|
||||||
debug_assert_ne!(atom, atom!(""));
|
debug_assert_ne!(atom, atom!(""));
|
||||||
|
@ -494,19 +499,19 @@ impl KeyframesName {
|
||||||
// FIXME: We might want to preserve <string>, but currently Gecko
|
// FIXME: We might want to preserve <string>, but currently Gecko
|
||||||
// stores both of <custom-ident> and <string> into nsAtom, so
|
// stores both of <custom-ident> and <string> into nsAtom, so
|
||||||
// we can't tell it.
|
// we can't tell it.
|
||||||
KeyframesName::Ident(CustomIdent(atom))
|
Self::Ident(CustomIdent(atom))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The name as an Atom
|
/// The name as an Atom
|
||||||
pub fn as_atom(&self) -> &Atom {
|
pub fn as_atom(&self) -> &Atom {
|
||||||
match *self {
|
match *self {
|
||||||
KeyframesName::Ident(ref ident) => &ident.0,
|
Self::Ident(ref ident) => &ident.0,
|
||||||
KeyframesName::QuotedString(ref atom) => atom,
|
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
|
/// 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`.
|
/// only needed for background-size serialization, which special-cases `auto`.
|
||||||
|
@ -515,13 +520,13 @@ pub trait IsAuto {
|
||||||
fn is_auto(&self) -> bool;
|
fn is_auto(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for KeyframesName {
|
impl PartialEq for TimelineOrKeyframesName {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.as_atom() == other.as_atom()
|
self.as_atom() == other.as_atom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl hash::Hash for KeyframesName {
|
impl hash::Hash for TimelineOrKeyframesName {
|
||||||
fn hash<H>(&self, state: &mut H)
|
fn hash<H>(&self, state: &mut H)
|
||||||
where
|
where
|
||||||
H: hash::Hasher,
|
H: hash::Hasher,
|
||||||
|
@ -530,32 +535,40 @@ impl hash::Hash for KeyframesName {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for KeyframesName {
|
impl Parse for TimelineOrKeyframesName {
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
_context: &ParserContext,
|
_context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> Result<Self, ParseError<'i>> {
|
||||||
let location = input.current_source_location();
|
let location = input.current_source_location();
|
||||||
match *input.next()? {
|
match *input.next()? {
|
||||||
Token::Ident(ref s) => Ok(KeyframesName::Ident(CustomIdent::from_ident(
|
Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident(
|
||||||
location,
|
location,
|
||||||
s,
|
s,
|
||||||
&["none"],
|
&["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())),
|
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
|
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
|
||||||
where
|
where
|
||||||
W: Write,
|
W: Write,
|
||||||
{
|
{
|
||||||
match *self {
|
match *self {
|
||||||
KeyframesName::Ident(ref ident) => ident.to_css(dest),
|
Self::Ident(ref ident) => ident.to_css(dest),
|
||||||
KeyframesName::QuotedString(ref atom) => atom.to_string().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`.
|
/// Return `0deg`.
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
Self::from_degrees(0.0, false)
|
Self::from_degrees(0.0, false)
|
||||||
|
@ -141,6 +150,13 @@ impl Angle {
|
||||||
self.value.degrees()
|
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.
|
/// Whether this specified angle came from a `calc()` expression.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn was_calc(&self) -> bool {
|
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::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
|
||||||
use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
|
use crate::values::specified::length::{LengthPercentage, NonNegativeLength};
|
||||||
use crate::values::specified::{AllowQuirks, Number};
|
use crate::values::specified::{AllowQuirks, Number};
|
||||||
use crate::values::{CustomIdent, KeyframesName};
|
use crate::values::{CustomIdent, KeyframesName, TimelineName};
|
||||||
use crate::Atom;
|
use crate::Atom;
|
||||||
use cssparser::Parser;
|
use cssparser::Parser;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
|
@ -110,8 +110,6 @@ pub enum DisplayInside {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
MozBox,
|
MozBox,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
MozStack,
|
|
||||||
#[cfg(feature = "gecko")]
|
|
||||||
MozDeck,
|
MozDeck,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
MozPopup,
|
MozPopup,
|
||||||
|
@ -226,8 +224,6 @@ impl Display {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox);
|
pub const MozInlineBox: Self = Self::new(DisplayOutside::Inline, DisplayInside::MozBox);
|
||||||
#[cfg(feature = "gecko")]
|
#[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);
|
pub const MozDeck: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozDeck);
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup);
|
pub const MozPopup: Self = Self::new(DisplayOutside::XUL, DisplayInside::MozPopup);
|
||||||
|
@ -608,8 +604,6 @@ impl Parse for Display {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
"-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox,
|
"-moz-inline-box" if moz_box_display_values_enabled(context) => Display::MozInlineBox,
|
||||||
#[cfg(feature = "gecko")]
|
#[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,
|
"-moz-deck" if moz_display_values_enabled(context) => Display::MozDeck,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
"-moz-popup" if moz_display_values_enabled(context) => Display::MozPopup,
|
"-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
|
/// https://drafts.csswg.org/css-scroll-snap-1/#snap-axis
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
|
#[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};
|
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
|
||||||
|
|
||||||
/// The name of the mathematical function that we're parsing.
|
/// The name of the mathematical function that we're parsing.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Parse)]
|
||||||
pub enum MathFunction {
|
pub enum MathFunction {
|
||||||
/// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
|
/// `calc()`: https://drafts.csswg.org/css-values-4/#funcdef-calc
|
||||||
Calc,
|
Calc,
|
||||||
|
@ -31,6 +31,18 @@ pub enum MathFunction {
|
||||||
Max,
|
Max,
|
||||||
/// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
|
/// `clamp()`: https://drafts.csswg.org/css-values-4/#funcdef-clamp
|
||||||
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.
|
/// 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.
|
/// A calc node representation for specified values.
|
||||||
pub type CalcNode = generic::GenericCalcNode<Leaf>;
|
pub type CalcNode = generic::GenericCalcNode<Leaf>;
|
||||||
|
|
||||||
|
@ -301,6 +320,17 @@ impl CalcNode {
|
||||||
let function = CalcNode::math_function(name, location)?;
|
let function = CalcNode::math_function(name, location)?;
|
||||||
CalcNode::parse(context, input, function, expected_unit)
|
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())),
|
(t, _) => Err(location.new_unexpected_token_error(t.clone())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,6 +380,47 @@ impl CalcNode {
|
||||||
|
|
||||||
Ok(Self::MinMax(arguments.into(), op))
|
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>,
|
name: &CowRcStr<'i>,
|
||||||
location: cssparser::SourceLocation,
|
location: cssparser::SourceLocation,
|
||||||
) -> Result<MathFunction, ParseError<'i>> {
|
) -> Result<MathFunction, ParseError<'i>> {
|
||||||
Ok(match_ignore_ascii_case! { &*name,
|
use self::MathFunction::*;
|
||||||
"calc" => MathFunction::Calc,
|
|
||||||
"min" => MathFunction::Min,
|
let function = match MathFunction::from_ident(&*name) {
|
||||||
"max" => MathFunction::Max,
|
Ok(f) => f,
|
||||||
"clamp" => MathFunction::Clamp,
|
Err(()) => return Err(location.new_unexpected_token_error(Token::Function(name.clone()))),
|
||||||
_ => 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.
|
/// 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
|
/// Specified color value
|
||||||
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
@ -222,10 +208,9 @@ pub enum Color {
|
||||||
},
|
},
|
||||||
/// A complex color value from computed value
|
/// A complex color value from computed value
|
||||||
Complex(ComputedColor),
|
Complex(ComputedColor),
|
||||||
/// Either a system color, or a `-moz-system-color(<system-color>, light|dark)`
|
/// A system color.
|
||||||
/// function which allows chrome code to choose between color schemes.
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
System(SystemColor, SystemColorScheme),
|
System(SystemColor),
|
||||||
/// A color mix.
|
/// A color mix.
|
||||||
ColorMix(Box<ColorMix>),
|
ColorMix(Box<ColorMix>),
|
||||||
/// Quirksmode-only rule for inheriting color from the body
|
/// Quirksmode-only rule for inheriting color from the body
|
||||||
|
@ -233,36 +218,19 @@ pub enum Color {
|
||||||
InheritFromBodyQuirk,
|
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)]
|
#[allow(missing_docs)]
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
|
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum SystemColor {
|
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")]
|
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
||||||
TextSelectBackgroundDisabled,
|
TextSelectBackgroundDisabled,
|
||||||
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
||||||
|
@ -310,6 +278,7 @@ pub enum SystemColor {
|
||||||
#[css(skip)]
|
#[css(skip)]
|
||||||
ThemedScrollbarThumbInactive,
|
ThemedScrollbarThumbInactive,
|
||||||
Activeborder,
|
Activeborder,
|
||||||
|
/// Background in the (active) titlebar.
|
||||||
Activecaption,
|
Activecaption,
|
||||||
Appworkspace,
|
Appworkspace,
|
||||||
Background,
|
Background,
|
||||||
|
@ -317,16 +286,23 @@ pub enum SystemColor {
|
||||||
Buttonhighlight,
|
Buttonhighlight,
|
||||||
Buttonshadow,
|
Buttonshadow,
|
||||||
Buttontext,
|
Buttontext,
|
||||||
|
/// Text color in the (active) titlebar.
|
||||||
Captiontext,
|
Captiontext,
|
||||||
#[parse(aliases = "-moz-field")]
|
#[parse(aliases = "-moz-field")]
|
||||||
Field,
|
Field,
|
||||||
|
/// Used for disabled field backgrounds.
|
||||||
|
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
||||||
|
MozDisabledfield,
|
||||||
#[parse(aliases = "-moz-fieldtext")]
|
#[parse(aliases = "-moz-fieldtext")]
|
||||||
Fieldtext,
|
Fieldtext,
|
||||||
|
|
||||||
Graytext,
|
Graytext,
|
||||||
Highlight,
|
Highlight,
|
||||||
Highlighttext,
|
Highlighttext,
|
||||||
Inactiveborder,
|
Inactiveborder,
|
||||||
|
/// Background in the (inactive) titlebar.
|
||||||
Inactivecaption,
|
Inactivecaption,
|
||||||
|
/// Text color in the (inactive) titlebar.
|
||||||
Inactivecaptiontext,
|
Inactivecaptiontext,
|
||||||
Infobackground,
|
Infobackground,
|
||||||
Infotext,
|
Infotext,
|
||||||
|
@ -351,13 +327,15 @@ pub enum SystemColor {
|
||||||
/// Used to highlight valid regions to drop something onto.
|
/// Used to highlight valid regions to drop something onto.
|
||||||
MozDragtargetzone,
|
MozDragtargetzone,
|
||||||
/// Used for selected but not focused cell backgrounds.
|
/// Used for selected but not focused cell backgrounds.
|
||||||
|
#[parse(aliases = "-moz-html-cellhighlight")]
|
||||||
MozCellhighlight,
|
MozCellhighlight,
|
||||||
/// Used for selected but not focused cell text.
|
/// Used for selected but not focused cell text.
|
||||||
|
#[parse(aliases = "-moz-html-cellhighlighttext")]
|
||||||
MozCellhighlighttext,
|
MozCellhighlighttext,
|
||||||
/// Used for selected but not focused html cell backgrounds.
|
/// Used for selected and focused html cell backgrounds.
|
||||||
MozHtmlCellhighlight,
|
Selecteditem,
|
||||||
/// Used for selected but not focused html cell text.
|
/// Used for selected and focused html cell text.
|
||||||
MozHtmlCellhighlighttext,
|
Selecteditemtext,
|
||||||
/// Used to button text background when hovered.
|
/// Used to button text background when hovered.
|
||||||
MozButtonhoverface,
|
MozButtonhoverface,
|
||||||
/// Used to button text color when hovered.
|
/// Used to button text color when hovered.
|
||||||
|
@ -378,10 +356,16 @@ pub enum SystemColor {
|
||||||
|
|
||||||
/// Used for button text when pressed.
|
/// Used for button text when pressed.
|
||||||
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
#[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.
|
/// Background color of chrome toolbars in active windows.
|
||||||
MozMacChromeActive,
|
MozMacChromeActive,
|
||||||
/// Background color of chrome toolbars in inactive windows.
|
/// Background color of chrome toolbars in inactive windows.
|
||||||
|
@ -423,6 +407,10 @@ pub enum SystemColor {
|
||||||
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
||||||
MozAccentColorForeground,
|
MozAccentColorForeground,
|
||||||
|
|
||||||
|
/// The background-color for :autofill-ed inputs.
|
||||||
|
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
||||||
|
MozAutofillBackground,
|
||||||
|
|
||||||
/// Media rebar text.
|
/// Media rebar text.
|
||||||
MozWinMediatext,
|
MozWinMediatext,
|
||||||
/// Communications rebar text.
|
/// Communications rebar text.
|
||||||
|
@ -457,14 +445,6 @@ pub enum SystemColor {
|
||||||
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
|
||||||
MozColheaderhovertext,
|
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)]
|
#[css(skip)]
|
||||||
End, // Just for array-indexing purposes.
|
End, // Just for array-indexing purposes.
|
||||||
}
|
}
|
||||||
|
@ -472,31 +452,20 @@ pub enum SystemColor {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
impl SystemColor {
|
impl SystemColor {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn compute(&self, cx: &Context, scheme: SystemColorScheme) -> ComputedColor {
|
fn compute(&self, cx: &Context) -> ComputedColor {
|
||||||
use crate::gecko_bindings::bindings;
|
use crate::gecko_bindings::bindings;
|
||||||
|
|
||||||
let colors = &cx.device().pref_sheet_prefs().mColors;
|
// TODO: We should avoid cloning here most likely, though it's
|
||||||
let style_color_scheme = cx.style().get_inherited_ui().clone_color_scheme();
|
// cheap-ish.
|
||||||
|
let style_color_scheme =
|
||||||
// TODO: At least Canvas / CanvasText should be color-scheme aware
|
cx.style().get_inherited_ui().clone_color_scheme();
|
||||||
// (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 {
|
let color = unsafe {
|
||||||
bindings::Gecko_GetLookAndFeelSystemColor(*self as i32, cx.device().document(), scheme, &style_color_scheme)
|
bindings::Gecko_ComputeSystemColor(*self, cx.device().document(), &style_color_scheme)
|
||||||
};
|
};
|
||||||
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
|
if color == bindings::NS_SAME_AS_FOREGROUND_COLOR {
|
||||||
return ComputedColor::currentcolor();
|
return ComputedColor::currentcolor();
|
||||||
}
|
}
|
||||||
color
|
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 {
|
impl Parse for Color {
|
||||||
fn parse<'i, 't>(
|
fn parse<'i, 't>(
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
|
@ -610,15 +564,7 @@ impl Parse for Color {
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
{
|
{
|
||||||
if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
|
if let Ok(system) = input.try_parse(|i| SystemColor::parse(context, i)) {
|
||||||
return Ok(Color::System(system, SystemColorScheme::Default));
|
return Ok(Color::System(system));
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,17 +603,7 @@ impl ToCss for Color {
|
||||||
Color::Complex(_) => Ok(()),
|
Color::Complex(_) => Ok(()),
|
||||||
Color::ColorMix(ref mix) => mix.to_css(dest),
|
Color::ColorMix(ref mix) => mix.to_css(dest),
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
Color::System(system, scheme) => {
|
Color::System(system) => system.to_css(dest),
|
||||||
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(')')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
Color::InheritFromBodyQuirk => Ok(()),
|
Color::InheritFromBodyQuirk => Ok(()),
|
||||||
}
|
}
|
||||||
|
@ -835,7 +771,7 @@ impl Color {
|
||||||
))
|
))
|
||||||
},
|
},
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
Color::System(system, scheme) => system.compute(context?, scheme),
|
Color::System(system) => system.compute(context?),
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
|
Color::InheritFromBodyQuirk => ComputedColor::rgba(context?.device().body_text_color()),
|
||||||
})
|
})
|
||||||
|
@ -995,6 +931,11 @@ impl ColorScheme {
|
||||||
bits: ColorSchemeFlags::empty(),
|
bits: ColorSchemeFlags::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the raw bitfield.
|
||||||
|
pub fn raw_bits(&self) -> u8 {
|
||||||
|
self.bits.bits
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for ColorScheme {
|
impl Parse for ColorScheme {
|
||||||
|
|
|
@ -49,6 +49,11 @@ impl Parse for IntersectionObserverRootMargin {
|
||||||
context: &ParserContext,
|
context: &ParserContext,
|
||||||
input: &mut Parser<'i, 't>,
|
input: &mut Parser<'i, 't>,
|
||||||
) -> Result<Self, ParseError<'i>> {
|
) -> 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)?;
|
let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?;
|
||||||
Ok(IntersectionObserverRootMargin(rect))
|
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")]
|
#[cfg(feature = "gecko")]
|
||||||
"max-content" | "-moz-max-content" => $size::MaxContent,
|
"max-content" | "-moz-max-content" => $size::MaxContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
"-moz-fit-content" => $size::MozFitContent,
|
"fit-content" | "-moz-fit-content" => $size::FitContent,
|
||||||
#[cfg(feature = "gecko")]
|
#[cfg(feature = "gecko")]
|
||||||
"-moz-available" => $size::MozAvailable,
|
"-moz-available" => $size::MozAvailable,
|
||||||
$auto_or_none => $size::$auto_or_none_ident,
|
$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::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
|
||||||
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
|
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
|
||||||
pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle};
|
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_::{Appearance, BreakBetween, BreakWithin};
|
||||||
pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
|
pub use self::box_::{Clear, Float, Overflow, OverflowAnchor};
|
||||||
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize};
|
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::{FontVariantEastAsian, FontVariationSettings};
|
||||||
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextZoom};
|
||||||
pub use self::image::{EndingShape as GradientEndingShape, Gradient};
|
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::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
|
||||||
pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
|
pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
|
||||||
pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
|
pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
|
||||||
|
|
|
@ -82,7 +82,12 @@ files = [
|
||||||
# These are ignored to avoid diverging from Gecko
|
# These are ignored to avoid diverging from Gecko
|
||||||
"./components/style/counter_style/mod.rs",
|
"./components/style/counter_style/mod.rs",
|
||||||
"./components/style/properties/helpers.mako.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/rule_parser.rs",
|
||||||
|
"./components/style/stylesheets/scroll_timeline_rule.rs",
|
||||||
"./components/style/stylist.rs",
|
"./components/style/stylist.rs",
|
||||||
"./components/style/values/computed/font.rs",
|
"./components/style/values/computed/font.rs",
|
||||||
"./components/style/values/computed/image.rs",
|
"./components/style/values/computed/image.rs",
|
||||||
|
|
|
@ -15,6 +15,7 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock};
|
||||||
use style::selector_map::SelectorMap;
|
use style::selector_map::SelectorMap;
|
||||||
use style::selector_parser::{SelectorImpl, SelectorParser};
|
use style::selector_parser::{SelectorImpl, SelectorParser};
|
||||||
use style::shared_lock::SharedRwLock;
|
use style::shared_lock::SharedRwLock;
|
||||||
|
use style::stylesheets::layer_rule::LayerId;
|
||||||
use style::stylesheets::StyleRule;
|
use style::stylesheets::StyleRule;
|
||||||
use style::stylist::needs_revalidation_for_testing;
|
use style::stylist::needs_revalidation_for_testing;
|
||||||
use style::stylist::{Rule, Stylist};
|
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),
|
AncestorHashes::new(s, QuirksMode::NoQuirks),
|
||||||
locked.clone(),
|
locked.clone(),
|
||||||
i as u32,
|
i as u32,
|
||||||
|
LayerId::root(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.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\]]
|
[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
|
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\]]
|
[Web Animations: property <rotate> from [45deg\] to [-1 1 0 60deg\] at (0) should be [45deg\]]
|
||||||
expected: FAIL
|
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.]
|
[The serialization of list-style-type: circle; list-style-position: inside; list-style-image: initial; should be canonical.]
|
||||||
expected: FAIL
|
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.]
|
[The serialization of border-top: 1px; border-right: 1px; border-bottom: 1px; border-left: 1px; border-image: none; should be canonical.]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue