Revert "Backport several style changes from Gecko (5) (#30099)" (#30104)

This reverts commit 8e15389cae.
This commit is contained in:
Oriol Brufau 2023-08-16 08:24:42 +02:00 committed by GitHub
parent 8e15389cae
commit d6ae8dc112
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
152 changed files with 4622 additions and 5862 deletions

View file

@ -62,14 +62,13 @@ servo_config = { path = "../config", optional = true }
servo_url = { path = "../url", optional = true }
smallbitvec = "2.3.0"
smallvec = "1.0"
static_assertions = "1.1"
string_cache = { version = "0.8", optional = true }
style_derive = { path = "../style_derive" }
style_traits = { path = "../style_traits" }
time = "0.1"
to_shmem = { path = "../to_shmem" }
to_shmem_derive = { path = "../to_shmem_derive" }
uluru = "3.0"
uluru = "2"
unicode-bidi = "0.3"
unicode-segmentation = "1.0"
void = "1.0.2"

View file

@ -455,8 +455,8 @@ pub struct Animation {
impl Animation {
/// Whether or not this animation is cancelled by changes from a new style.
fn is_cancelled_in_new_style(&self, new_style: &Arc<ComputedValues>) -> bool {
let new_ui = new_style.get_ui();
let index = new_ui
let index = new_style
.get_box()
.animation_name_iter()
.position(|animation_name| Some(&self.name) == animation_name.as_atom());
let index = match index {
@ -464,7 +464,7 @@ impl Animation {
None => return true,
};
new_ui.animation_duration_mod(index).seconds() == 0.
new_style.get_box().animation_duration_mod(index).seconds() == 0.
}
/// Given the current time, advances this animation to the next iteration,
@ -1073,10 +1073,10 @@ impl ElementAnimationSet {
old_style: &ComputedValues,
new_style: &Arc<ComputedValues>,
) {
let style = new_style.get_ui();
let timing_function = style.transition_timing_function_mod(index);
let duration = style.transition_duration_mod(index);
let delay = style.transition_delay_mod(index).seconds() as f64;
let box_style = new_style.get_box();
let timing_function = box_style.transition_timing_function_mod(index);
let duration = box_style.transition_duration_mod(index);
let delay = box_style.transition_delay_mod(index).seconds() as f64;
let now = context.current_time_for_animations;
// Only start a new transition if the style actually changes between
@ -1344,15 +1344,15 @@ pub fn maybe_start_animations<E>(
) where
E: TElement,
{
let style = new_style.get_ui();
for (i, name) in style.animation_name_iter().enumerate() {
let box_style = new_style.get_box();
for (i, name) in box_style.animation_name_iter().enumerate() {
let name = match name.as_atom() {
Some(atom) => atom,
None => continue,
};
debug!("maybe_start_animations: name={}", name);
let duration = style.animation_duration_mod(i).seconds() as f64;
let duration = box_style.animation_duration_mod(i).seconds() as f64;
if duration == 0. {
continue;
}
@ -1375,14 +1375,14 @@ pub fn maybe_start_animations<E>(
// NB: This delay may be negative, meaning that the animation may be created
// in a state where we have advanced one or more iterations or even that the
// animation begins in a finished state.
let delay = style.animation_delay_mod(i).seconds();
let delay = box_style.animation_delay_mod(i).seconds();
let iteration_state = match style.animation_iteration_count_mod(i) {
let iteration_state = match box_style.animation_iteration_count_mod(i) {
AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0),
AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()),
};
let animation_direction = style.animation_direction_mod(i);
let animation_direction = box_style.animation_direction_mod(i);
let initial_direction = match animation_direction {
AnimationDirection::Normal | AnimationDirection::Alternate => {
@ -1396,7 +1396,7 @@ pub fn maybe_start_animations<E>(
let now = context.current_time_for_animations;
let started_at = now + delay as f64;
let mut starting_progress = (now - started_at) / duration;
let state = match style.animation_play_state_mod(i) {
let state = match box_style.animation_play_state_mod(i) {
AnimationPlayState::Paused => AnimationState::Paused(starting_progress),
AnimationPlayState::Running => AnimationState::Pending,
};
@ -1406,7 +1406,7 @@ pub fn maybe_start_animations<E>(
&keyframe_animation,
context,
new_style,
style.animation_timing_function_mod(i),
new_style.get_box().animation_timing_function_mod(i),
resolver,
);
@ -1416,7 +1416,7 @@ pub fn maybe_start_animations<E>(
computed_steps,
started_at,
duration,
fill_mode: style.animation_fill_mode_mod(i),
fill_mode: box_style.animation_fill_mode_mod(i),
delay: delay as f64,
iteration_state,
state,

View file

@ -42,7 +42,13 @@ pub struct CascadePriority {
layer_order: LayerOrder,
}
const_assert_eq!(std::mem::size_of::<CascadePriority>(), std::mem::size_of::<u32>());
#[allow(dead_code)]
fn size_assert() {
#[allow(unsafe_code)]
unsafe {
std::mem::transmute::<u32, CascadePriority>(0u32)
};
}
impl PartialOrd for CascadePriority {
#[inline]
@ -202,6 +208,3 @@ impl ApplicableDeclarationBlock {
(self.source, self.cascade_priority)
}
}
// Size of this struct determines sorting and selector-matching performance.
size_of_test!(ApplicableDeclarationBlock, 24);

View file

@ -285,11 +285,11 @@ fn generate_structs() {
let mut fixups = vec![];
let builder = BuilderWithConfig::new(builder, CONFIG["structs"].as_table().unwrap())
.handle_common(&mut fixups)
.handle_str_items("allowlist-functions", |b, item| b.allowlist_function(item))
.handle_str_items("whitelist-functions", |b, item| b.allowlist_function(item))
.handle_str_items("bitfield-enums", |b, item| b.bitfield_enum(item))
.handle_str_items("rusty-enums", |b, item| b.rustified_enum(item))
.handle_str_items("allowlist-vars", |b, item| b.allowlist_var(item))
.handle_str_items("allowlist-types", |b, item| b.allowlist_type(item))
.handle_str_items("whitelist-vars", |b, item| b.allowlist_var(item))
.handle_str_items("whitelist-types", |b, item| b.allowlist_type(item))
.handle_str_items("opaque-types", |b, item| b.opaque_type(item))
.handle_table_items("cbindgen-types", |b, item| {
let gecko = item["gecko"].as_str().unwrap();

View file

@ -27,8 +27,8 @@ use crate::traversal_flags::TraversalFlags;
use app_units::Au;
use euclid::default::Size2D;
use euclid::Scale;
#[cfg(feature = "servo")]
use fxhash::FxHashMap;
use selectors::matching::ElementSelectorFlags;
use selectors::NthIndexCache;
#[cfg(feature = "gecko")]
use servo_arc::Arc;
@ -41,6 +41,7 @@ use style_traits::DevicePixel;
#[cfg(feature = "servo")]
use style_traits::SpeculativePainter;
use time;
use uluru::LRUCache;
pub use selectors::matching::QuirksMode;
@ -516,6 +517,65 @@ impl<E: TElement> SequentialTask<E> {
}
}
type CacheItem<E> = (SendElement<E>, ElementSelectorFlags);
/// Map from Elements to ElementSelectorFlags. Used to defer applying selector
/// flags until after the traversal.
pub struct SelectorFlagsMap<E: TElement> {
/// The hashmap storing the flags to apply.
map: FxHashMap<SendElement<E>, ElementSelectorFlags>,
/// An LRU cache to avoid hashmap lookups, which can be slow if the map
/// gets big.
cache: LRUCache<CacheItem<E>, { 4 + 1 }>,
}
#[cfg(debug_assertions)]
impl<E: TElement> Drop for SelectorFlagsMap<E> {
fn drop(&mut self) {
debug_assert!(self.map.is_empty());
}
}
impl<E: TElement> SelectorFlagsMap<E> {
/// Creates a new empty SelectorFlagsMap.
pub fn new() -> Self {
SelectorFlagsMap {
map: FxHashMap::default(),
cache: LRUCache::default(),
}
}
/// Inserts some flags into the map for a given element.
pub fn insert_flags(&mut self, element: E, flags: ElementSelectorFlags) {
let el = unsafe { SendElement::new(element) };
// Check the cache. If the flags have already been noted, we're done.
if let Some(item) = self.cache.find(|x| x.0 == el) {
if !item.1.contains(flags) {
item.1.insert(flags);
self.map.get_mut(&el).unwrap().insert(flags);
}
return;
}
let f = self.map.entry(el).or_insert(ElementSelectorFlags::empty());
*f |= flags;
self.cache
.insert((unsafe { SendElement::new(element) }, *f))
}
/// Applies the flags. Must be called on the main thread.
fn apply_flags(&mut self) {
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
self.cache.clear();
for (el, flags) in self.map.drain() {
unsafe {
el.set_selector_flags(flags);
}
}
}
}
/// A list of SequentialTasks that get executed on Drop.
pub struct SequentialTaskList<E>(Vec<SequentialTask<E>>)
where
@ -637,6 +697,11 @@ pub struct ThreadLocalStyleContext<E: TElement> {
/// filter, to ensure they're dropped before we execute the tasks, which
/// could create another ThreadLocalStyleContext for style computation.
pub tasks: SequentialTaskList<E>,
/// ElementSelectorFlags that need to be applied after the traversal is
/// complete. This map is used in cases where the matching algorithm needs
/// to set flags on elements it doesn't have exclusive access to (i.e. other
/// than the current element).
pub selector_flags: SelectorFlagsMap<E>,
/// Statistics about the traversal.
pub statistics: PerThreadTraversalStatistics,
/// A checker used to ensure that parallel.rs does not recurse indefinitely
@ -654,6 +719,7 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
rule_cache: RuleCache::new(),
bloom_filter: StyleBloom::new(),
tasks: SequentialTaskList(Vec::new()),
selector_flags: SelectorFlagsMap::new(),
statistics: PerThreadTraversalStatistics::default(),
stack_limit_checker: StackLimitChecker::new(
(STYLE_THREAD_STACK_SIZE_KB - STACK_SAFETY_MARGIN_KB) * 1024,
@ -663,6 +729,15 @@ impl<E: TElement> ThreadLocalStyleContext<E> {
}
}
impl<E: TElement> Drop for ThreadLocalStyleContext<E> {
fn drop(&mut self) {
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
// Apply any slow selector flags that need to be set on parents.
self.selector_flags.apply_flags();
}
}
/// A `StyleContext` is just a simple container for a immutable reference to a
/// shared style context, and a mutable reference to a local one.
pub struct StyleContext<'a, E: TElement + 'a> {

View file

@ -64,27 +64,6 @@ fn get_safearea_inset_right(device: &Device) -> VariableValue {
VariableValue::pixels(device.safe_area_insets().right)
}
#[cfg(feature = "gecko")]
fn get_content_preferred_color_scheme(device: &Device) -> VariableValue {
use crate::gecko::media_features::PrefersColorScheme;
let prefers_color_scheme = unsafe {
crate::gecko_bindings::bindings::Gecko_MediaFeatures_PrefersColorScheme(
device.document(),
/* use_content = */ true,
)
};
VariableValue::ident(match prefers_color_scheme {
PrefersColorScheme::Light => "light",
PrefersColorScheme::Dark => "dark",
})
}
#[cfg(feature = "servo")]
fn get_content_preferred_color_scheme(_device: &Device) -> VariableValue {
// TODO: implement this.
VariableValue::ident("light")
}
static ENVIRONMENT_VARIABLES: [EnvironmentVariable; 4] = [
make_variable!(atom!("safe-area-inset-top"), get_safearea_inset_top),
make_variable!(atom!("safe-area-inset-bottom"), get_safearea_inset_bottom),
@ -120,7 +99,7 @@ macro_rules! lnf_int_variable {
}};
}
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [
static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [
lnf_int_variable!(
atom!("-moz-gtk-csd-titlebar-radius"),
TitlebarRadius,
@ -142,7 +121,6 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [
GTKCSDMaximizeButtonPosition,
integer
),
make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme),
];
impl CssEnvironment {
@ -350,11 +328,6 @@ impl VariableValue {
})
}
/// Create VariableValue from an int.
fn ident(ident: &'static str) -> Self {
Self::from_token(Token::Ident(ident.into()))
}
/// Create VariableValue from a float amount of CSS pixels.
fn pixels(number: f32) -> Self {
// FIXME (https://github.com/servo/rust-cssparser/issues/266):

View file

@ -159,9 +159,6 @@ pub struct ElementStyles {
pub pseudos: EagerPseudoStyles,
}
// There's one of these per rendered elements so it better be small.
size_of_test!(ElementStyles, 16);
impl ElementStyles {
/// Returns the primary style.
pub fn get_primary(&self) -> Option<&Arc<ComputedValues>> {
@ -252,9 +249,6 @@ pub struct ElementData {
pub flags: ElementDataFlags,
}
// There's one of these per rendered elements so it better be small.
size_of_test!(ElementData, 24);
/// The kind of restyle that a single element should do.
#[derive(Debug)]
pub enum RestyleKind {

View file

@ -22,7 +22,7 @@ use crate::traversal_flags::TraversalFlags;
use crate::values::AtomIdent;
use crate::{LocalName, Namespace, WeakAtom};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use selectors::matching::{QuirksMode, VisitedHandlingMode};
use selectors::matching::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode};
use selectors::sink::Push;
use selectors::Element as SelectorsElement;
use servo_arc::{Arc, ArcBorrow};
@ -734,6 +734,19 @@ pub trait TElement:
/// native anonymous content can opt out of this style fixup.)
fn skip_item_display_fixup(&self) -> bool;
/// Sets selector flags, which indicate what kinds of selectors may have
/// matched on this element and therefore what kind of work may need to
/// be performed when DOM state changes.
///
/// This is unsafe, like all the flag-setting methods, because it's only safe
/// to call with exclusive access to the element. When setting flags on the
/// parent during parallel traversal, we use SequentialTask to queue up the
/// set to run after the threads join.
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags);
/// Returns true if the element has all the specified selector flags.
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool;
/// In Gecko, element has a flag that represents the element may have
/// any type of animations or not to bail out animation stuff early.
/// Whereas Servo doesn't have such flag.
@ -928,9 +941,6 @@ pub trait TElement:
/// Returns element's namespace.
fn namespace(&self)
-> &<SelectorImpl as selectors::parser::SelectorImpl>::BorrowedNamespaceUrl;
/// Returns the size of the primary box of the element.
fn primary_box_size(&self) -> euclid::default::Size2D<app_units::Au>;
}
/// TNode and TElement aren't Send because we want to be careful and explicit

View file

@ -12,7 +12,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
use crate::invalidation::element::invalidator::{InvalidationProcessor, InvalidationVector};
use crate::values::AtomIdent;
use selectors::attr::CaseSensitivity;
use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags};
use selectors::matching::{self, MatchingContext, MatchingMode};
use selectors::parser::{Combinator, Component, LocalName, SelectorImpl};
use selectors::{Element, NthIndexCache, SelectorList};
use smallvec::SmallVec;
@ -26,13 +26,7 @@ pub fn element_matches<E>(
where
E: Element,
{
let mut context = MatchingContext::new(
MatchingMode::Normal,
None,
None,
quirks_mode,
NeedsSelectorFlags::No,
);
let mut context = MatchingContext::new(MatchingMode::Normal, None, None, quirks_mode);
context.scope_element = Some(element.opaque());
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
matching::matches_selector_list(selector_list, element, &mut context)
@ -54,7 +48,6 @@ where
None,
Some(&mut nth_index_cache),
quirks_mode,
NeedsSelectorFlags::No,
);
context.scope_element = Some(element.opaque());
context.current_host = element.containing_shadow_host().map(|e| e.opaque());
@ -625,8 +618,8 @@ pub fn query_selector<E, Q>(
None,
Some(&mut nth_index_cache),
quirks_mode,
NeedsSelectorFlags::No,
);
let root_element = root.as_element();
matching_context.scope_element = root_element.map(|e| e.opaque());
matching_context.current_host = match root_element {

View file

@ -118,8 +118,8 @@ bitflags! {
const IN_MODAL_DIALOG_STATE = 1 << 42;
/// <https://html.spec.whatwg.org/multipage/#inert-subtrees>
const IN_MOZINERT_STATE = 1 << 43;
/// State for the topmost modal element in top layer
const IN_TOPMOST_MODAL_TOP_LAYER_STATE = 1 << 44;
/// State for the topmost dialog element in top layer
const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44;
/// Initially used for the devtools highlighter, but now somehow only
/// used for the devtools accessibility inspector.
const IN_DEVTOOLS_HIGHLIGHTED_STATE = 1 << 45;
@ -148,5 +148,9 @@ bitflags! {
const LTR_LOCALE = 1 << 2;
/// LWTheme status
const LWTHEME = 1 << 3;
/// LWTheme status
const LWTHEME_BRIGHTTEXT = 1 << 4;
/// LWTheme status
const LWTHEME_DARKTEXT = 1 << 5;
}
}

View file

@ -15,7 +15,7 @@ use crate::gecko_bindings::structs::{
RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList,
RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule,
RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents,
RawServoSupportsRule, RawServoContainerRule, ServoCssRules,
RawServoSupportsRule, ServoCssRules,
};
use crate::gecko_bindings::sugar::ownership::{HasArcFFI, HasFFI, Strong};
use crate::media_queries::MediaList;
@ -26,7 +26,7 @@ use crate::stylesheets::keyframes_rule::Keyframe;
use crate::stylesheets::{
CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule, ImportRule,
KeyframesRule, LayerBlockRule, LayerStatementRule, MediaRule, NamespaceRule, PageRule,
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule,
ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule,
};
use servo_arc::{Arc, ArcBorrow};
use std::{mem, ptr};
@ -98,9 +98,6 @@ impl_arc_ffi!(Locked<ScrollTimelineRule> => RawServoScrollTimelineRule
impl_arc_ffi!(Locked<SupportsRule> => RawServoSupportsRule
[Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]);
impl_arc_ffi!(Locked<ContainerRule> => RawServoContainerRule
[Servo_ContainerRule_AddRef, Servo_ContainerRule_Release]);
impl_arc_ffi!(Locked<DocumentRule> => RawServoMozDocumentRule
[Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]);

View file

@ -129,12 +129,6 @@ impl StylesheetInDocument for GeckoStyleSheet {
pub struct PerDocumentStyleDataImpl {
/// Rule processor.
pub stylist: Stylist,
/// A cache from element to resolved style.
pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache,
/// The generation for which our cache is valid.
pub undisplayed_style_cache_generation: u64,
}
/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
@ -149,8 +143,6 @@ impl PerDocumentStyleData {
PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
stylist: Stylist::new(device, quirks_mode.into()),
undisplayed_style_cache: Default::default(),
undisplayed_style_cache_generation: 0,
}))
}
@ -185,6 +177,12 @@ impl PerDocumentStyleDataImpl {
self.stylist.device().default_computed_values_arc()
}
/// Returns whether visited styles are enabled.
#[inline]
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.stylist.device().document()) }
}
/// Measure heap usage.
pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
self.stylist.add_size_of(ops, sizes);

View file

@ -6,10 +6,13 @@
use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs;
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
use crate::queries::values::Orientation;
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
use crate::media_queries::media_feature_expression::RangeOrOperator;
use crate::media_queries::{Device, MediaType};
use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution};
use crate::values::computed::CSSPixelLength;
use crate::values::computed::Ratio;
use crate::values::computed::Resolution;
use app_units::Au;
use euclid::default::Size2D;
@ -23,56 +26,150 @@ fn device_size(device: &Device) -> Size2D<Au> {
}
/// https://drafts.csswg.org/mediaqueries-4/#width
fn eval_width(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
fn eval_width(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device.au_viewport_size().width,
)
}
/// https://drafts.csswg.org/mediaqueries-4/#device-width
fn eval_device_width(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(device_size(context.device()).width.to_f32_px())
fn eval_device_width(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device_size(device).width,
)
}
/// https://drafts.csswg.org/mediaqueries-4/#height
fn eval_height(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px())
fn eval_height(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device.au_viewport_size().height,
)
}
/// https://drafts.csswg.org/mediaqueries-4/#device-height
fn eval_device_height(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(device_size(context.device()).height.to_f32_px())
fn eval_device_height(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device_size(device).height,
)
}
fn eval_aspect_ratio_for<F>(context: &Context, get_size: F) -> Ratio
fn eval_aspect_ratio_for<F>(
device: &Device,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
get_size: F,
) -> bool
where
F: FnOnce(&Device) -> Size2D<Au>,
{
let size = get_size(context.device());
Ratio::new(size.width.0 as f32, size.height.0 as f32)
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
// to convert it if necessary.
// FIXME: we may need to update here once
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
let query_value = match query_value {
Some(v) => v.used_value(),
None => return true,
};
let size = get_size(device);
let value = Ratio::new(size.width.0 as f32, size.height.0 as f32);
RangeOrOperator::evaluate_with_query_value(range_or_operator, query_value, value)
}
/// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio
fn eval_aspect_ratio(context: &Context) -> Ratio {
eval_aspect_ratio_for(context, Device::au_viewport_size)
fn eval_aspect_ratio(
device: &Device,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
eval_aspect_ratio_for(
device,
query_value,
range_or_operator,
Device::au_viewport_size,
)
}
/// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio
fn eval_device_aspect_ratio(context: &Context) -> Ratio {
eval_aspect_ratio_for(context, device_size)
fn eval_device_aspect_ratio(
device: &Device,
query_value: Option<Ratio>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
eval_aspect_ratio_for(device, query_value, range_or_operator, device_size)
}
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio
fn eval_device_pixel_ratio(context: &Context) -> f32 {
eval_resolution(context).dppx()
fn eval_device_pixel_ratio(
device: &Device,
query_value: Option<f32>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
eval_resolution(
device,
query_value.map(Resolution::from_dppx),
range_or_operator,
)
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
#[repr(u8)]
enum Orientation {
Landscape,
Portrait,
}
fn eval_orientation_for<F>(device: &Device, value: Option<Orientation>, get_size: F) -> bool
where
F: FnOnce(&Device) -> Size2D<Au>,
{
let query_orientation = match value {
Some(v) => v,
None => return true,
};
let size = get_size(device);
// Per spec, square viewports should be 'portrait'
let is_landscape = size.width > size.height;
match query_orientation {
Orientation::Landscape => is_landscape,
Orientation::Portrait => !is_landscape,
}
}
/// https://drafts.csswg.org/mediaqueries-4/#orientation
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
Orientation::eval(context.device().au_viewport_size(), value)
fn eval_orientation(device: &Device, value: Option<Orientation>) -> bool {
eval_orientation_for(device, value, Device::au_viewport_size)
}
/// FIXME: There's no spec for `-moz-device-orientation`.
fn eval_device_orientation(context: &Context, value: Option<Orientation>) -> bool {
Orientation::eval(device_size(context.device()), value)
fn eval_device_orientation(device: &Device, value: Option<Orientation>) -> bool {
eval_orientation_for(device, value, device_size)
}
/// Values for the display-mode media feature.
@ -87,23 +184,25 @@ pub enum DisplayMode {
}
/// https://w3c.github.io/manifest/#the-display-mode-media-feature
fn eval_display_mode(context: &Context, query_value: Option<DisplayMode>) -> bool {
fn eval_display_mode(device: &Device, query_value: Option<DisplayMode>) -> bool {
match query_value {
Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) },
Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) },
None => true,
}
}
/// https://drafts.csswg.org/mediaqueries-4/#grid
fn eval_grid(_: &Context) -> bool {
fn eval_grid(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
// Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature
// is always 0.
false
let supports_grid = false;
query_value.map_or(supports_grid, |v| v == supports_grid)
}
/// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d
fn eval_transform_3d(_: &Context) -> bool {
true
fn eval_transform_3d(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
let supports_transforms = true;
query_value.map_or(supports_transforms, |v| v == supports_transforms)
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -114,33 +213,58 @@ enum Scan {
}
/// https://drafts.csswg.org/mediaqueries-4/#scan
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
// Since Gecko doesn't support the 'tv' media type, the 'scan' feature never
// matches.
false
}
/// https://drafts.csswg.org/mediaqueries-4/#color
fn eval_color(context: &Context) -> u32 {
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) }
fn eval_color(
device: &Device,
query_value: Option<u32>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
let color_bits_per_channel =
unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) };
RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel)
}
/// https://drafts.csswg.org/mediaqueries-4/#color-index
fn eval_color_index(_: &Context) -> u32 {
fn eval_color_index(
_: &Device,
query_value: Option<u32>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
// We should return zero if the device does not use a color lookup table.
0
let index = 0;
RangeOrOperator::evaluate(range_or_operator, query_value, index)
}
/// https://drafts.csswg.org/mediaqueries-4/#monochrome
fn eval_monochrome(context: &Context) -> u32 {
fn eval_monochrome(
device: &Device,
query_value: Option<u32>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
// For color devices we should return 0.
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) }
let depth =
unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) };
RangeOrOperator::evaluate(range_or_operator, query_value, depth)
}
/// https://drafts.csswg.org/mediaqueries-4/#resolution
fn eval_resolution(context: &Context) -> Resolution {
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) };
Resolution::from_dppx(resolution_dppx)
fn eval_resolution(
device: &Device,
query_value: Option<Resolution>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) };
RangeOrOperator::evaluate(
range_or_operator,
query_value.map(|r| r.dppx()),
resolution_dppx,
)
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -159,22 +283,10 @@ pub enum PrefersColorScheme {
Dark,
}
/// Values for the dynamic-range and video-dynamic-range media features.
/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range
/// This implements PartialOrd so that lower values will correctly match
/// higher capabilities.
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, PartialOrd, ToCss)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum DynamicRange {
Standard,
High,
}
/// https://drafts.csswg.org/mediaqueries-5/#prefers-reduced-motion
fn eval_prefers_reduced_motion(context: &Context, query_value: Option<PrefersReducedMotion>) -> bool {
fn eval_prefers_reduced_motion(device: &Device, query_value: Option<PrefersReducedMotion>) -> bool {
let prefers_reduced =
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) };
unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) };
let query_value = match query_value {
Some(v) => v,
None => return prefers_reduced,
@ -191,20 +303,19 @@ fn eval_prefers_reduced_motion(context: &Context, query_value: Option<PrefersRed
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, PartialEq, ToCss)]
#[repr(u8)]
pub enum PrefersContrast {
/// More contrast is preferred.
/// More contrast is preferred. Corresponds to an accessibility theme
/// being enabled or Firefox forcing high contrast colors.
More,
/// Low contrast is preferred.
Less,
/// Custom (not more, not less).
Custom,
/// The default value if neither high or low contrast is enabled.
NoPreference,
}
/// https://drafts.csswg.org/mediaqueries-5/#prefers-contrast
fn eval_prefers_contrast(context: &Context, query_value: Option<PrefersContrast>) -> bool {
fn eval_prefers_contrast(device: &Device, query_value: Option<PrefersContrast>) -> bool {
let prefers_contrast =
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) };
unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) };
match query_value {
Some(v) => v == prefers_contrast,
None => prefers_contrast != PrefersContrast::NoPreference,
@ -223,8 +334,8 @@ pub enum ForcedColors {
}
/// https://drafts.csswg.org/mediaqueries-5/#forced-colors
fn eval_forced_colors(context: &Context, query_value: Option<ForcedColors>) -> bool {
let forced = !context.device().use_document_colors();
fn eval_forced_colors(device: &Device, query_value: Option<ForcedColors>) -> bool {
let forced = !device.use_document_colors();
match query_value {
Some(query_value) => forced == (query_value == ForcedColors::Active),
None => forced,
@ -241,7 +352,7 @@ enum OverflowBlock {
}
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block
fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) -> bool {
fn eval_overflow_block(device: &Device, query_value: Option<OverflowBlock>) -> bool {
// For the time being, assume that printing (including previews)
// is the only time when we paginate, and we are otherwise always
// scrolling. This is true at the moment in Firefox, but may need
@ -249,7 +360,7 @@ fn eval_overflow_block(context: &Context, query_value: Option<OverflowBlock>) ->
// billboard mode that doesn't support overflow at all).
//
// If this ever changes, don't forget to change eval_overflow_inline too.
let scrolling = context.device().media_type() != MediaType::print();
let scrolling = device.media_type() != MediaType::print();
let query_value = match query_value {
Some(v) => v,
None => return true,
@ -270,9 +381,9 @@ enum OverflowInline {
}
/// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline
fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>) -> bool {
fn eval_overflow_inline(device: &Device, query_value: Option<OverflowInline>) -> bool {
// See the note in eval_overflow_block.
let scrolling = context.device().media_type() != MediaType::print();
let scrolling = device.media_type() != MediaType::print();
let query_value = match query_value {
Some(v) => v,
None => return scrolling,
@ -284,41 +395,13 @@ fn eval_overflow_inline(context: &Context, query_value: Option<OverflowInline>)
}
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
#[repr(u8)]
enum Update {
None,
Slow,
Fast,
}
/// https://drafts.csswg.org/mediaqueries-4/#update
fn eval_update(context: &Context, query_value: Option<Update>) -> bool {
// This has similar caveats to those described in eval_overflow_block.
// For now, we report that print (incl. print media simulation,
// which can in fact update but is limited to the developer tools)
// is `update: none` and that all other contexts are `update: fast`,
// which may not be true for future platforms, like e-ink devices.
let can_update = context.device().media_type() != MediaType::print();
let query_value = match query_value {
Some(v) => v,
None => return can_update,
};
match query_value {
Update::None => !can_update,
Update::Slow => false,
Update::Fast => can_update,
}
}
fn do_eval_prefers_color_scheme(
context: &Context,
device: &Device,
use_content: bool,
query_value: Option<PrefersColorScheme>,
) -> bool {
let prefers_color_scheme =
unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) };
unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) };
match query_value {
Some(v) => prefers_color_scheme == v,
None => true,
@ -326,34 +409,15 @@ fn do_eval_prefers_color_scheme(
}
/// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
fn eval_prefers_color_scheme(context: &Context, query_value: Option<PrefersColorScheme>) -> bool {
do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value)
fn eval_prefers_color_scheme(device: &Device, query_value: Option<PrefersColorScheme>) -> bool {
do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value)
}
fn eval_content_prefers_color_scheme(
context: &Context,
device: &Device,
query_value: Option<PrefersColorScheme>,
) -> bool {
do_eval_prefers_color_scheme(context, /* use_content = */ true, query_value)
}
/// https://drafts.csswg.org/mediaqueries-5/#dynamic-range
fn eval_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool {
let dynamic_range =
unsafe { bindings::Gecko_MediaFeatures_DynamicRange(context.device().document()) };
match query_value {
Some(v) => dynamic_range >= v,
None => false,
}
}
/// https://drafts.csswg.org/mediaqueries-5/#video-dynamic-range
fn eval_video_dynamic_range(context: &Context, query_value: Option<DynamicRange>) -> bool {
let dynamic_range =
unsafe { bindings::Gecko_MediaFeatures_VideoDynamicRange(context.device().document()) };
match query_value {
Some(v) => dynamic_range >= v,
None => false,
}
do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value)
}
bitflags! {
@ -365,15 +429,15 @@ bitflags! {
}
}
fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities {
fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities {
PointerCapabilities::from_bits_truncate(unsafe {
bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document())
bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document())
})
}
fn all_pointer_capabilities(context: &Context) -> PointerCapabilities {
fn all_pointer_capabilities(device: &Device) -> PointerCapabilities {
PointerCapabilities::from_bits_truncate(unsafe {
bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document())
bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document())
})
}
@ -402,13 +466,13 @@ fn eval_pointer_capabilities(
}
/// https://drafts.csswg.org/mediaqueries-4/#pointer
fn eval_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, primary_pointer_capabilities(context))
fn eval_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, primary_pointer_capabilities(device))
}
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer
fn eval_any_pointer(context: &Context, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, all_pointer_capabilities(context))
fn eval_any_pointer(device: &Device, query_value: Option<Pointer>) -> bool {
eval_pointer_capabilities(query_value, all_pointer_capabilities(device))
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -435,33 +499,54 @@ fn eval_hover_capabilities(
}
/// https://drafts.csswg.org/mediaqueries-4/#hover
fn eval_hover(context: &Context, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, primary_pointer_capabilities(context))
fn eval_hover(device: &Device, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, primary_pointer_capabilities(device))
}
/// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover
fn eval_any_hover(context: &Context, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, all_pointer_capabilities(context))
fn eval_any_hover(device: &Device, query_value: Option<Hover>) -> bool {
eval_hover_capabilities(query_value, all_pointer_capabilities(device))
}
fn eval_moz_is_glyph(context: &Context) -> bool {
context.device().document().mIsSVGGlyphsDocument()
fn eval_moz_is_glyph(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let is_glyph = device.document().mIsSVGGlyphsDocument();
query_value.map_or(is_glyph, |v| v == is_glyph)
}
fn eval_moz_print_preview(context: &Context) -> bool {
let is_print_preview = context.device().is_print_preview();
fn eval_moz_print_preview(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let is_print_preview = device.is_print_preview();
if is_print_preview {
debug_assert_eq!(context.device().media_type(), MediaType::print());
debug_assert_eq!(device.media_type(), MediaType::print());
}
is_print_preview
query_value.map_or(is_print_preview, |v| v == is_print_preview)
}
fn eval_moz_non_native_content_theme(context: &Context) -> bool {
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) }
fn eval_moz_non_native_content_theme(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let non_native_theme =
unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(device.document()) };
query_value.map_or(non_native_theme, |v| v == non_native_theme)
}
fn eval_moz_is_resource_document(context: &Context) -> bool {
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) }
fn eval_moz_is_resource_document(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let is_resource_doc =
unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) };
query_value.map_or(is_resource_doc, |v| v == is_resource_doc)
}
/// Allows front-end CSS to discern platform via media queries.
@ -487,7 +572,7 @@ pub enum Platform {
WindowsWin10,
}
fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
fn eval_moz_platform(_: &Device, query_value: Option<Platform>) -> bool {
let query_value = match query_value {
Some(v) => v,
None => return false,
@ -496,22 +581,32 @@ fn eval_moz_platform(_: &Context, query_value: Option<Platform>) -> bool {
unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) }
}
fn eval_moz_windows_non_native_menus(context: &Context) -> bool {
fn eval_moz_windows_non_native_menus(
device: &Device,
query_value: Option<bool>,
_: Option<RangeOrOperator>,
) -> bool {
let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus")
{
0 => false,
1 => true,
_ => {
eval_moz_platform(context, Some(Platform::WindowsWin10)) &&
eval_moz_platform(device, Some(Platform::WindowsWin10)) &&
get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32)
},
};
use_non_native_menus
query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus)
}
fn eval_moz_overlay_scrollbars(context: &Context) -> bool {
unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) }
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 {
@ -540,15 +635,16 @@ fn get_scrollbar_end_forward(int_id: i32) -> bool {
macro_rules! lnf_int_feature {
($feature_name:expr, $int_id:ident, $get_value:ident) => {{
fn __eval(_: &Context) -> bool {
$get_value(bindings::LookAndFeel_IntID::$int_id as i32)
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32);
query_value.map_or(value, |v| v == value)
}
feature!(
$feature_name,
AllowsRanges::No,
Evaluator::BoolInteger(__eval),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
)
}};
($feature_name:expr, $int_id:ident) => {{
@ -565,18 +661,18 @@ macro_rules! lnf_int_feature {
/// pref, with `rust: true`. The feature name needs to be defined in
/// `StaticAtoms.py` just like the others. In order to support dynamic changes,
/// you also need to add them to kMediaQueryPrefs in nsXPLookAndFeel.cpp
#[allow(unused)]
macro_rules! bool_pref_feature {
($feature_name:expr, $pref:tt) => {{
fn __eval(_: &Context) -> bool {
static_prefs::pref!($pref)
fn __eval(_: &Device, query_value: Option<bool>, _: Option<RangeOrOperator>) -> bool {
let value = static_prefs::pref!($pref);
query_value.map_or(value, |v| v == value)
}
feature!(
$feature_name,
AllowsRanges::No,
Evaluator::BoolInteger(__eval),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
)
}};
}
@ -586,54 +682,54 @@ macro_rules! bool_pref_feature {
/// to support new types in these entries and (2) ensuring that either
/// nsPresContext::MediaFeatureValuesChanged is called when the value that
/// would be returned by the evaluator function could change.
pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("height"),
AllowsRanges::Yes,
Evaluator::Length(eval_height),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_aspect_ratio),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_orientation, Orientation),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("device-width"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_width),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("device-height"),
AllowsRanges::Yes,
Evaluator::Length(eval_device_height),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("device-aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_device_aspect_ratio),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("-moz-device-orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_device_orientation, Orientation),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
// Webkit extensions that we support for de-facto web compatibility.
// -webkit-{min|max}-device-pixel-ratio (controlled with its own pref):
@ -641,68 +737,68 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
atom!("device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
FeatureFlags::WEBKIT_PREFIX,
ParsingRequirements::WEBKIT_PREFIX,
),
// -webkit-transform-3d.
feature!(
atom!("transform-3d"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_transform_3d),
FeatureFlags::WEBKIT_PREFIX,
ParsingRequirements::WEBKIT_PREFIX,
),
feature!(
atom!("-moz-device-pixel-ratio"),
AllowsRanges::Yes,
Evaluator::Float(eval_device_pixel_ratio),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("resolution"),
AllowsRanges::Yes,
Evaluator::Resolution(eval_resolution),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("display-mode"),
AllowsRanges::No,
keyword_evaluator!(eval_display_mode, DisplayMode),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("grid"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_grid),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("scan"),
AllowsRanges::No,
keyword_evaluator!(eval_scan, Scan),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("color"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("color-index"),
AllowsRanges::Yes,
Evaluator::Integer(eval_color_index),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("monochrome"),
AllowsRanges::Yes,
Evaluator::Integer(eval_monochrome),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-reduced-motion"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-contrast"),
@ -713,49 +809,31 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
// layout.css.prefers-contrast.enabled preference. See
// disabed_by_pref in media_feature_expression.rs for how that
// is done.
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("forced-colors"),
AllowsRanges::No,
keyword_evaluator!(eval_forced_colors, ForcedColors),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("overflow-block"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_block, OverflowBlock),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("overflow-inline"),
AllowsRanges::No,
keyword_evaluator!(eval_overflow_inline, OverflowInline),
FeatureFlags::empty(),
),
feature!(
atom!("update"),
AllowsRanges::No,
keyword_evaluator!(eval_update, Update),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("prefers-color-scheme"),
AllowsRanges::No,
keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme),
FeatureFlags::empty(),
),
feature!(
atom!("dynamic-range"),
AllowsRanges::No,
keyword_evaluator!(eval_dynamic_range, DynamicRange),
FeatureFlags::empty(),
),
feature!(
atom!("video-dynamic-range"),
AllowsRanges::No,
keyword_evaluator!(eval_video_dynamic_range, DynamicRange),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
// Evaluates to the preferred color scheme for content. Only useful in
// chrome context, where the chrome color-scheme and the content
@ -764,31 +842,31 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
atom!("-moz-content-prefers-color-scheme"),
AllowsRanges::No,
keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_pointer, Pointer),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("any-pointer"),
AllowsRanges::No,
keyword_evaluator!(eval_any_pointer, Pointer),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("hover"),
AllowsRanges::No,
keyword_evaluator!(eval_hover, Hover),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("any-hover"),
AllowsRanges::No,
keyword_evaluator!(eval_any_hover, Hover),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
// Internal -moz-is-glyph media feature: applies only inside SVG glyphs.
// Internal because it is really only useful in the user agent anyway
@ -797,43 +875,43 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
atom!("-moz-is-glyph"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_glyph),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-is-resource-document"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_is_resource_document),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-platform"),
AllowsRanges::No,
keyword_evaluator!(eval_moz_platform, Platform),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-print-preview"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_print_preview),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-non-native-content-theme"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_non_native_content_theme),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-windows-non-native-menus"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_windows_non_native_menus),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
feature!(
atom!("-moz-overlay-scrollbars"),
AllowsRanges::No,
Evaluator::BoolInteger(eval_moz_overlay_scrollbars),
FeatureFlags::CHROME_AND_UA_ONLY,
ParsingRequirements::CHROME_AND_UA_ONLY,
),
lnf_int_feature!(
atom!("-moz-scrollbar-start-backward"),
@ -855,6 +933,10 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
ScrollArrowStyle,
get_scrollbar_end_forward
),
lnf_int_feature!(
atom!("-moz-scrollbar-thumb-proportional"),
ScrollSliderStyle
),
lnf_int_feature!(atom!("-moz-menubar-drag"), MenuBarDrag),
lnf_int_feature!(atom!("-moz-windows-default-theme"), WindowsDefaultTheme),
lnf_int_feature!(atom!("-moz-mac-graphite-theme"), MacGraphiteTheme),
@ -877,4 +959,8 @@ pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [
GTKCSDReversedPlacement
),
lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme),
bool_pref_feature!(
atom!("-moz-proton-places-tooltip"),
"browser.proton.places-tooltip.enabled"
),
];

View file

@ -17,7 +17,6 @@ use crate::values::computed::font::GenericFontFamily;
use crate::values::computed::{ColorScheme, Length};
use crate::values::specified::color::SystemColor;
use crate::values::specified::font::FONT_MEDIUM_PX;
use crate::values::specified::ViewportVariant;
use crate::values::{CustomIdent, KeyframesName};
use app_units::{Au, AU_PER_PX};
use cssparser::RGBA;
@ -59,9 +58,6 @@ pub struct Device {
/// Whether any styles computed in the document relied on the viewport size
/// by using vw/vh/vmin/vmax units.
used_viewport_size: AtomicBool,
/// Whether any styles computed in the document relied on the viewport size
/// by using dvw/dvh/dvmin/dvmax units.
used_dynamic_viewport_size: AtomicBool,
/// The CssEnvironment object responsible of getting CSS environment
/// variables.
environment: CssEnvironment,
@ -104,7 +100,6 @@ impl Device {
used_root_font_size: AtomicBool::new(false),
used_font_metrics: AtomicBool::new(false),
used_viewport_size: AtomicBool::new(false),
used_dynamic_viewport_size: AtomicBool::new(false),
environment: CssEnvironment,
}
}
@ -272,8 +267,6 @@ impl Device {
self.used_root_font_size.store(false, Ordering::Relaxed);
self.used_font_metrics.store(false, Ordering::Relaxed);
self.used_viewport_size.store(false, Ordering::Relaxed);
self.used_dynamic_viewport_size
.store(false, Ordering::Relaxed);
}
/// Returns whether we ever looked up the root font size of the Device.
@ -344,10 +337,7 @@ impl Device {
/// Returns the current viewport size in app units, recording that it's been
/// used for viewport unit resolution.
pub fn au_viewport_size_for_viewport_unit_resolution(
&self,
variant: ViewportVariant,
) -> Size2D<Au> {
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
self.used_viewport_size.store(true, Ordering::Relaxed);
let pc = match self.pres_context() {
Some(pc) => pc,
@ -358,42 +348,8 @@ impl Device {
return self.page_size_minus_default_margin(pc);
}
match variant {
ViewportVariant::UADefault => {
let size = &pc.mSizeForViewportUnits;
Size2D::new(Au(size.width), Au(size.height))
},
ViewportVariant::Small => {
let size = &pc.mVisibleArea;
Size2D::new(Au(size.width), Au(size.height))
},
ViewportVariant::Large => {
let size = &pc.mVisibleArea;
// Looks like IntCoordTyped is treated as if it's u32 in Rust.
debug_assert!(
/* pc.mDynamicToolbarMaxHeight >=0 && */
pc.mDynamicToolbarMaxHeight < i32::MAX as u32
);
Size2D::new(
Au(size.width),
Au(size.height
+ pc.mDynamicToolbarMaxHeight as i32 * pc.mCurAppUnitsPerDevPixel),
)
},
ViewportVariant::Dynamic => {
self.used_dynamic_viewport_size.store(true, Ordering::Relaxed);
let size = &pc.mVisibleArea;
// Looks like IntCoordTyped is treated as if it's u32 in Rust.
debug_assert!(
/* pc.mDynamicToolbarHeight >=0 && */
pc.mDynamicToolbarHeight < i32::MAX as u32
);
Size2D::new(
Au(size.width),
Au(size.height + pc.mDynamicToolbarHeight as i32 * pc.mCurAppUnitsPerDevPixel),
)
},
}
let size = &pc.mSizeForViewportUnits;
Size2D::new(Au(size.width), Au(size.height))
}
/// Returns whether we ever looked up the viewport size of the Device.
@ -401,21 +357,11 @@ impl Device {
self.used_viewport_size.load(Ordering::Relaxed)
}
/// Returns whether we ever looked up the dynamic viewport size of the Device.
pub fn used_dynamic_viewport_size(&self) -> bool {
self.used_dynamic_viewport_size.load(Ordering::Relaxed)
}
/// Returns whether font metrics have been queried.
pub fn used_font_metrics(&self) -> bool {
self.used_font_metrics.load(Ordering::Relaxed)
}
/// Returns whether visited styles are enabled.
pub fn visited_styles_enabled(&self) -> bool {
unsafe { bindings::Gecko_VisitedStylesEnabled(self.document()) }
}
/// Returns the device pixel ratio.
pub fn device_pixel_ratio(&self) -> Scale<f32, CSSPixel, DevicePixel> {
let pc = match self.pres_context() {

View file

@ -54,7 +54,7 @@ macro_rules! apply_non_ts_list {
("-moz-styleeditor-transitioning", MozStyleeditorTransitioning, IN_STYLEEDITOR_TRANSITIONING_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("fullscreen", Fullscreen, IN_FULLSCREEN_STATE, _),
("-moz-modal-dialog", MozModalDialog, IN_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-topmost-modal", MozTopmostModal, IN_TOPMOST_MODAL_TOP_LAYER_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-topmost-modal-dialog", MozTopmostModalDialog, IN_TOPMOST_MODAL_DIALOG_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
("-moz-broken", MozBroken, IN_BROKEN_STATE, _),
("-moz-loading", MozLoading, IN_LOADING_STATE, _),
("-moz-has-dir-attr", MozHasDirAttr, IN_HAS_DIR_ATTR_STATE, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS),
@ -92,6 +92,8 @@ macro_rules! apply_non_ts_list {
("-moz-is-html", MozIsHTML, _, _),
("-moz-placeholder", MozPlaceholder, _, _),
("-moz-lwtheme", MozLWTheme, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("-moz-lwtheme-brighttext", MozLWThemeBrightText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("-moz-lwtheme-darktext", MozLWThemeDarkText, _, PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME),
("-moz-window-inactive", MozWindowInactive, _, _),
]
}

View file

@ -139,6 +139,15 @@ impl NonTSPseudoClass {
/// Returns whether the pseudo-class is enabled in content sheets.
#[inline]
fn is_enabled_in_content(&self) -> bool {
if matches!(
*self,
Self::MozLWTheme | Self::MozLWThemeBrightText | Self::MozLWThemeDarkText
) {
return static_prefs::pref!("layout.css.moz-lwtheme.content.enabled");
}
if let NonTSPseudoClass::MozLocaleDir(..) = *self {
return static_prefs::pref!("layout.css.moz-locale-dir.content.enabled");
}
!self.has_any_flag(NonTSPseudoClassFlag::PSEUDO_CLASS_ENABLED_IN_UA_SHEETS_AND_CHROME)
}
@ -175,6 +184,8 @@ impl NonTSPseudoClass {
},
NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE,
NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME,
NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT,
NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT,
_ => DocumentState::empty(),
}
}
@ -197,13 +208,15 @@ impl NonTSPseudoClass {
NonTSPseudoClass::MozNativeAnonymous |
// :-moz-placeholder is parsed but never matches.
NonTSPseudoClass::MozPlaceholder |
// :-moz-lwtheme, :-moz-locale-dir and
// :-moz-window-inactive depend only on the state of the
// document, which is invariant across all the elements
// involved in a given style cache.
NonTSPseudoClass::MozLWTheme |
// :-moz-locale-dir and :-moz-window-inactive depend only on
// the state of the document, which is invariant across all
// the elements involved in a given style cache.
NonTSPseudoClass::MozLocaleDir(_) |
NonTSPseudoClass::MozWindowInactive
NonTSPseudoClass::MozWindowInactive |
// Similar for the document themes.
NonTSPseudoClass::MozLWTheme |
NonTSPseudoClass::MozLWThemeBrightText |
NonTSPseudoClass::MozLWThemeDarkText
)
}
}
@ -445,9 +458,3 @@ unsafe impl HasFFI for SelectorList<SelectorImpl> {
}
unsafe impl HasSimpleFFI for SelectorList<SelectorImpl> {}
unsafe impl HasBoxFFI for SelectorList<SelectorImpl> {}
// Selector and component sizes are important for matching performance.
size_of_test!(selectors::parser::Selector<SelectorImpl>, 8);
size_of_test!(selectors::parser::Component<SelectorImpl>, 24);
size_of_test!(PseudoElement, 16);
size_of_test!(NonTSPseudoClass, 16);

View file

@ -44,14 +44,14 @@ unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
structs::nsAttrValue_ValueType_eAtomArray
);
// NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
let attr_array: *mut _ = *(*container)
let array: *mut u8 = *(*container)
.__bindgen_anon_1
.mValue
.as_ref()
.__bindgen_anon_1
.mAtomArray
.as_ref();
let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
let array = array as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
return Class::More(&**array);
}
debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);

View file

@ -66,8 +66,6 @@ use crate::stylist::CascadeData;
use crate::values::{AtomIdent, AtomString};
use crate::CaseSensitivityExt;
use crate::LocalName;
use app_units::Au;
use euclid::default::Size2D;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use fxhash::FxHashMap;
use selectors::attr::{AttrSelectorOperation, AttrSelectorOperator};
@ -323,11 +321,6 @@ impl<'ln> GeckoNode<'ln> {
self.flags() & (NODE_IS_IN_SHADOW_TREE as u32) != 0
}
#[inline]
fn is_connected(&self) -> bool {
self.get_bool_flag(nsINode_BooleanFlag::IsConnected)
}
/// WARNING: This logic is duplicated in Gecko's FlattenedTreeParentIsParent.
/// Make sure to mirror any modifications in both places.
#[inline]
@ -1037,21 +1030,6 @@ impl<'le> TElement for GeckoElement<'le> {
}
}
#[inline]
fn primary_box_size(&self) -> Size2D<Au> {
if !self.as_node().is_connected() {
return Size2D::zero();
}
unsafe {
let frame = self.0._base._base._base.__bindgen_anon_1.mPrimaryFrame.as_ref();
if frame.is_null() {
return Size2D::zero();
}
Size2D::new(Au((**frame).mRect.width), Au((**frame).mRect.height))
}
}
/// Return the list of slotted nodes of this node.
#[inline]
fn slotted_nodes(&self) -> &[Self::ConcreteNode] {
@ -1415,6 +1393,16 @@ impl<'le> TElement for GeckoElement<'le> {
self.is_root_of_native_anonymous_subtree()
}
unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) {
debug_assert!(!flags.is_empty());
self.set_flags(selector_flags_to_node_flags(flags));
}
fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool {
let node_flags = selector_flags_to_node_flags(flags);
(self.flags() & node_flags) == node_flags
}
#[inline]
fn may_have_animations(&self) -> bool {
if let Some(pseudo) = self.implemented_pseudo_element() {
@ -1513,7 +1501,7 @@ impl<'le> TElement for GeckoElement<'le> {
) -> bool {
use crate::properties::LonghandIdSet;
let after_change_ui_style = after_change_style.get_ui();
let after_change_box_style = after_change_style.get_box();
let existing_transitions = self.css_transitions_info();
let mut transitions_to_keep = LonghandIdSet::new();
for transition_property in after_change_style.transition_properties() {
@ -1523,7 +1511,7 @@ impl<'le> TElement for GeckoElement<'le> {
transitions_to_keep.insert(physical_longhand);
if self.needs_transitions_update_per_property(
physical_longhand,
after_change_ui_style.transition_combined_duration_at(transition_property.index),
after_change_box_style.transition_combined_duration_at(transition_property.index),
before_change_style,
after_change_style,
&existing_transitions,
@ -1605,9 +1593,23 @@ impl<'le> TElement for GeckoElement<'le> {
use crate::properties::longhands::_x_lang::SpecifiedValue as SpecifiedLang;
use crate::properties::longhands::_x_text_zoom::SpecifiedValue as SpecifiedZoom;
use crate::properties::longhands::color::SpecifiedValue as SpecifiedColor;
use crate::properties::longhands::text_align::SpecifiedValue as SpecifiedTextAlign;
use crate::stylesheets::layer_rule::LayerOrder;
use crate::values::specified::color::Color;
lazy_static! {
static ref TH_RULE: ApplicableDeclarationBlock = {
let global_style_data = &*GLOBAL_STYLE_DATA;
let pdb = PropertyDeclarationBlock::with_one(
PropertyDeclaration::TextAlign(SpecifiedTextAlign::MozCenterOrInherit),
Importance::Normal,
);
let arc = Arc::new_leaked(global_style_data.shared_lock.wrap(pdb));
ApplicableDeclarationBlock::from_declarations(
arc,
ServoCascadeLevel::PresHints,
LayerOrder::root(),
)
};
static ref TABLE_COLOR_RULE: ApplicableDeclarationBlock = {
let global_style_data = &*GLOBAL_STYLE_DATA;
let pdb = PropertyDeclarationBlock::with_one(
@ -1652,7 +1654,9 @@ impl<'le> TElement for GeckoElement<'le> {
let ns = self.namespace_id();
// <th> elements get a default MozCenterOrInherit which may get overridden
if ns == structs::kNameSpaceID_XHTML as i32 {
if self.local_name().as_ptr() == atom!("table").as_ptr() &&
if self.local_name().as_ptr() == atom!("th").as_ptr() {
hints.push(TH_RULE.clone());
} else if self.local_name().as_ptr() == atom!("table").as_ptr() &&
self.as_node().owner_doc().quirks_mode() == QuirksMode::Quirks
{
hints.push(TABLE_COLOR_RULE.clone());
@ -1844,11 +1848,6 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
None
}
fn set_selector_flags(&self, flags: ElementSelectorFlags) {
debug_assert!(!flags.is_empty());
self.set_flags(selector_flags_to_node_flags(flags));
}
fn attr_matches(
&self,
ns: &NamespaceConstraint<&Namespace>,
@ -1961,11 +1960,15 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
self.local_name() == other.local_name() && self.namespace() == other.namespace()
}
fn match_non_ts_pseudo_class(
fn match_non_ts_pseudo_class<F>(
&self,
pseudo_class: &NonTSPseudoClass,
context: &mut MatchingContext<Self::Impl>,
) -> bool {
flags_setter: &mut F,
) -> bool
where
F: FnMut(&Self, ElementSelectorFlags),
{
use selectors::matching::*;
match *pseudo_class {
NonTSPseudoClass::Autofill |
@ -2006,7 +2009,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozDirAttrRTL |
NonTSPseudoClass::MozDirAttrLikeAuto |
NonTSPseudoClass::MozModalDialog |
NonTSPseudoClass::MozTopmostModal |
NonTSPseudoClass::MozTopmostModalDialog |
NonTSPseudoClass::Active |
NonTSPseudoClass::Hover |
NonTSPseudoClass::MozAutofillPreview |
@ -2021,9 +2024,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
self.is_link() && context.visited_handling().matches_visited()
},
NonTSPseudoClass::MozFirstNode => {
if context.needs_selector_flags() {
self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
}
flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
let mut elem = self.as_node();
while let Some(prev) = elem.prev_sibling() {
if prev.contains_non_whitespace_content() {
@ -2034,9 +2035,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
true
},
NonTSPseudoClass::MozLastNode => {
if context.needs_selector_flags() {
self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
}
flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR);
let mut elem = self.as_node();
while let Some(next) = elem.next_sibling() {
if next.contains_non_whitespace_content() {
@ -2047,9 +2046,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
true
},
NonTSPseudoClass::MozOnlyWhitespace => {
if context.needs_selector_flags() {
self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR);
}
flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
if self
.as_node()
.dom_children()
@ -2071,6 +2068,8 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
NonTSPseudoClass::MozIsHTML => self.is_html_element_in_html_document(),
NonTSPseudoClass::MozLWTheme |
NonTSPseudoClass::MozLWThemeBrightText |
NonTSPseudoClass::MozLWThemeDarkText |
NonTSPseudoClass::MozLocaleDir(..) |
NonTSPseudoClass::MozWindowInactive => {
let state_bit = pseudo_class.document_state_flag();

View file

@ -12,11 +12,11 @@ use crate::shared_lock::SharedRwLock;
use crate::thread_state;
#[cfg(feature = "gecko")]
use gecko_profiler;
use parking_lot::{Mutex, RwLock, RwLockReadGuard};
use parking_lot::{RwLock, RwLockReadGuard};
use rayon;
use std::env;
use std::io;
use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
/// Global style data
pub struct GlobalStyleData {
@ -43,32 +43,12 @@ fn thread_name(index: usize) -> String {
format!("Style#{}", index)
}
lazy_static! {
/// JoinHandles for spawned style threads. These will be joined during
/// StyleThreadPool::shutdown() after exiting the thread pool.
///
/// This would be quite inefficient if rayon destroyed and re-created
/// threads regularly during threadpool operation in response to demand,
/// however rayon actually never destroys its threads until the entire
/// thread pool is shut-down, so the size of this list is bounded.
static ref STYLE_THREAD_JOIN_HANDLES: Mutex<Vec<thread::JoinHandle<()>>> =
Mutex::new(Vec::new());
}
fn thread_spawn(options: rayon::ThreadBuilder) -> io::Result<()> {
let mut b = thread::Builder::new();
if let Some(name) = options.name() {
b = b.name(name.to_owned());
}
if let Some(stack_size) = options.stack_size() {
b = b.stack_size(stack_size);
}
let join_handle = b.spawn(|| options.run())?;
STYLE_THREAD_JOIN_HANDLES.lock().push(join_handle);
Ok(())
}
// A counter so that we can wait for shutdown of all threads. See
// StyleThreadPool::shutdown.
static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0);
fn thread_startup(_index: usize) {
ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed);
thread_state::initialize_layout_worker_thread();
#[cfg(feature = "gecko")]
unsafe {
@ -84,24 +64,33 @@ fn thread_shutdown(_: usize) {
gecko_profiler::unregister_thread();
bindings::Gecko_SetJemallocThreadLocalArena(false);
}
ALIVE_WORKER_THREADS.fetch_sub(1, Ordering::Relaxed);
}
impl StyleThreadPool {
/// Shuts down the thread pool, waiting for all work to complete.
pub fn shutdown() {
if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() {
if ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 {
return;
}
{
// Drop the pool.
let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take();
}
// Join spawned threads until all of the threads have been joined. This
// will usually be pretty fast, as on shutdown there should be basically
// no threads left running.
while let Some(join_handle) = STYLE_THREAD_JOIN_HANDLES.lock().pop() {
let _ = join_handle.join();
// Spin until all our threads are done. This will usually be pretty
// fast, as on shutdown there should be basically no threads left
// running.
//
// This still _technically_ doesn't give us the guarantee of TLS
// destructors running on the worker threads. For that we'd need help
// from rayon to properly join the threads.
//
// See https://github.com/rayon-rs/rayon/issues/688
//
// So we instead intentionally leak TLS stuff (see BLOOM_KEY and co) for
// now until that's fixed.
while ALIVE_WORKER_THREADS.load(Ordering::Relaxed) != 0 {
std::thread::yield_now();
}
}
@ -116,7 +105,7 @@ impl StyleThreadPool {
lazy_static! {
/// Global thread pool
pub static ref STYLE_THREAD_POOL: std::sync::Mutex<StyleThreadPool> = {
pub static ref STYLE_THREAD_POOL: Mutex<StyleThreadPool> = {
let stylo_threads = env::var("STYLO_THREADS")
.map(|s| s.parse::<usize>().expect("invalid STYLO_THREADS value"));
let mut num_threads = match stylo_threads {
@ -160,7 +149,6 @@ lazy_static! {
None
} else {
let workers = rayon::ThreadPoolBuilder::new()
.spawn_handler(thread_spawn)
.num_threads(num_threads)
.thread_name(thread_name)
.start_handler(thread_startup)
@ -170,7 +158,7 @@ lazy_static! {
workers.ok()
};
std::sync::Mutex::new(StyleThreadPool {
Mutex::new(StyleThreadPool {
num_threads: if num_threads > 0 {
Some(num_threads)
} else {

View file

@ -11,7 +11,7 @@ use crate::invalidation::element::invalidator::{DescendantInvalidationLists, Inv
use crate::invalidation::element::invalidator::{Invalidation, InvalidationProcessor};
use crate::invalidation::element::state_and_attributes;
use crate::stylist::CascadeData;
use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode, NeedsSelectorFlags};
use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode};
/// A struct holding the members necessary to invalidate document state
/// selectors.
@ -47,7 +47,6 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> {
None,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
quirks_mode,
NeedsSelectorFlags::No,
);
matching_context.extra_data = InvalidationMatchingData {

View file

@ -166,11 +166,15 @@ where
{
type Impl = SelectorImpl;
fn match_non_ts_pseudo_class(
fn match_non_ts_pseudo_class<F>(
&self,
pseudo_class: &NonTSPseudoClass,
context: &mut MatchingContext<Self::Impl>,
) -> bool {
_setter: &mut F,
) -> bool
where
F: FnMut(&Self, ElementSelectorFlags),
{
// Some pseudo-classes need special handling to evaluate them against
// the snapshot.
match *pseudo_class {
@ -228,20 +232,16 @@ where
if flag.is_empty() {
return self
.element
.match_non_ts_pseudo_class(pseudo_class, context);
.match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {});
}
match self.snapshot().and_then(|s| s.state()) {
Some(snapshot_state) => snapshot_state.intersects(flag),
None => self
.element
.match_non_ts_pseudo_class(pseudo_class, context),
.match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}),
}
}
fn set_selector_flags(&self, _flags: ElementSelectorFlags) {
debug_assert!(false, "Shouldn't need selector flags for invalidation");
}
fn match_pseudo_element(
&self,
pseudo_element: &PseudoElement,

View file

@ -67,8 +67,6 @@ pub struct Dependency {
pub parent: Option<Box<Dependency>>,
}
size_of_test!(Dependency, 24);
/// The kind of elements down the tree this dependency may affect.
#[derive(Debug, Eq, PartialEq)]
pub enum DependencyInvalidationKind {

View file

@ -19,7 +19,8 @@ use crate::selector_parser::Snapshot;
use crate::stylesheets::origin::OriginSet;
use crate::{Atom, WeakAtom};
use selectors::attr::CaseSensitivity;
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags};
use selectors::matching::matches_selector;
use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode};
use selectors::NthIndexCache;
use smallvec::SmallVec;
@ -66,7 +67,6 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
Some(nth_index_cache),
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
shared_context.quirks_mode(),
NeedsSelectorFlags::No,
);
Self {
@ -84,7 +84,7 @@ pub fn check_dependency<E, W>(
dependency: &Dependency,
element: &E,
wrapper: &W,
context: &mut MatchingContext<'_, E::Impl>,
mut context: &mut MatchingContext<'_, E::Impl>,
) -> bool
where
E: TElement,
@ -95,7 +95,8 @@ where
dependency.selector_offset,
None,
element,
context,
&mut context,
&mut |_, _| {},
);
let matched_then = matches_selector(
@ -103,7 +104,8 @@ where
dependency.selector_offset,
None,
wrapper,
context,
&mut context,
&mut |_, _| {},
);
matched_then != matches_now

View file

@ -556,7 +556,6 @@ impl StylesheetInvalidationSet {
FontFace(..) |
Keyframes(..) |
ScrollTimeline(..) |
Container(..) |
Style(..) => {
if is_generic_change {
// TODO(emilio): We need to do this for selector / keyframe
@ -611,7 +610,7 @@ impl StylesheetInvalidationSet {
}
},
Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) |
Container(..) | LayerStatement(..) | LayerBlock(..) => {
LayerStatement(..) | LayerBlock(..) => {
// Do nothing, relevant nested rules are visited as part of the
// iteration.
},

View file

@ -63,8 +63,6 @@ pub use servo_arc;
#[macro_use]
extern crate servo_atoms;
#[macro_use]
extern crate static_assertions;
#[macro_use]
extern crate style_derive;
#[macro_use]
extern crate to_shmem_derive;
@ -103,12 +101,10 @@ pub mod invalidation;
#[allow(missing_docs)] // TODO.
pub mod logical_geometry;
pub mod matching;
#[macro_use]
pub mod media_queries;
pub mod parallel;
pub mod parser;
pub mod piecewise_linear;
#[macro_use]
pub mod queries;
pub mod rule_cache;
pub mod rule_collector;
pub mod rule_tree;
@ -188,7 +184,7 @@ pub mod gecko_properties {
}
macro_rules! reexport_computed_values {
( $( { $name: ident } )+ ) => {
( $( { $name: ident, $boxed: expr } )+ ) => {
/// Types for [computed values][computed].
///
/// [computed]: https://drafts.csswg.org/css-cascade/#computed
@ -202,6 +198,7 @@ macro_rules! reexport_computed_values {
}
}
longhand_properties_idents!(reexport_computed_values);
#[cfg(feature = "gecko")]
use crate::gecko_string_cache::WeakAtom;
#[cfg(feature = "servo")]

View file

@ -168,11 +168,6 @@ impl WritingMode {
flags
}
/// Returns the `horizontal-tb` value.
pub fn horizontal_tb() -> Self {
Self::from_bits_truncate(0)
}
#[inline]
pub fn is_vertical(&self) -> bool {
self.intersects(WritingMode::VERTICAL)
@ -877,10 +872,10 @@ impl<T> LogicalMargin<T> {
inline_start: T,
) -> LogicalMargin<T> {
LogicalMargin {
block_start,
inline_end,
block_end,
inline_start,
block_start: block_start,
inline_end: inline_end,
block_end: block_end,
inline_start: inline_start,
debug_writing_mode: DebugWritingMode::new(mode),
}
}
@ -1055,18 +1050,6 @@ impl<T: Copy> LogicalMargin<T> {
}
}
#[inline]
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
if mode_from == mode_to {
self.debug_writing_mode.check(mode_from);
*self
} else {
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
}
}
}
impl<T: Clone> LogicalMargin<T> {
#[inline]
pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
self.debug_writing_mode.check(mode);
@ -1076,32 +1059,42 @@ impl<T: Clone> LogicalMargin<T> {
let left;
if mode.is_vertical() {
if mode.is_vertical_lr() {
left = self.block_start.clone();
right = self.block_end.clone();
left = self.block_start;
right = self.block_end;
} else {
right = self.block_start.clone();
left = self.block_end.clone();
right = self.block_start;
left = self.block_end;
}
if mode.is_inline_tb() {
top = self.inline_start.clone();
bottom = self.inline_end.clone();
top = self.inline_start;
bottom = self.inline_end;
} else {
bottom = self.inline_start.clone();
top = self.inline_end.clone();
bottom = self.inline_start;
top = self.inline_end;
}
} else {
top = self.block_start.clone();
bottom = self.block_end.clone();
top = self.block_start;
bottom = self.block_end;
if mode.is_bidi_ltr() {
left = self.inline_start.clone();
right = self.inline_end.clone();
left = self.inline_start;
right = self.inline_end;
} else {
right = self.inline_start.clone();
left = self.inline_end.clone();
right = self.inline_start;
left = self.inline_end;
}
}
SideOffsets2D::new(top, right, bottom, left)
}
#[inline]
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
if mode_from == mode_to {
self.debug_writing_mode.check(mode_from);
*self
} else {
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
}
}
}
impl<T: PartialEq + Zero> LogicalMargin<T> {

View file

@ -128,11 +128,3 @@ macro_rules! local_name {
$crate::values::AtomIdent(atom!($s))
};
}
/// Asserts the size of a type at compile time.
macro_rules! size_of_test {
($t: ty, $expected_size: expr) => {
#[cfg(target_pointer_width = "64")]
const_assert_eq!(std::mem::size_of::<$t>(), $expected_size);
};
}

View file

@ -8,7 +8,7 @@
#![deny(missing_docs)]
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode};
use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap};
use crate::context::{SharedStyleContext, StyleContext};
use crate::data::{ElementData, ElementStyles};
use crate::dom::TElement;
@ -26,6 +26,7 @@ use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
use crate::stylesheets::layer_rule::LayerOrder;
use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags;
use selectors::matching::ElementSelectorFlags;
use servo_arc::{Arc, ArcBorrow};
/// Represents the result of comparing an element's old and new style.
@ -254,8 +255,8 @@ trait PrivateMatchMethods: TElement {
new_style: &ComputedValues,
pseudo_element: Option<PseudoElement>,
) -> bool {
let new_ui_style = new_style.get_ui();
let new_style_specifies_animations = new_ui_style.specifies_animations();
let new_box_style = new_style.get_box();
let new_style_specifies_animations = new_box_style.specifies_animations();
let has_animations = self.has_css_animations(&context.shared, pseudo_element);
if !new_style_specifies_animations && !has_animations {
@ -282,7 +283,7 @@ trait PrivateMatchMethods: TElement {
},
};
let old_ui_style = old_style.get_ui();
let old_box_style = old_style.get_box();
let keyframes_or_timeline_could_have_changed = context
.shared
@ -301,12 +302,12 @@ trait PrivateMatchMethods: TElement {
}
// If the animations changed, well...
if !old_ui_style.animations_equals(new_ui_style) {
if !old_box_style.animations_equals(new_box_style) {
return true;
}
let old_display = old_style.clone_display();
let new_display = new_style.clone_display();
let old_display = old_box_style.clone_display();
let new_display = new_box_style.clone_display();
// If we were display: none, we may need to trigger animations.
if old_display == Display::None && new_display != Display::None {
@ -341,13 +342,14 @@ trait PrivateMatchMethods: TElement {
None => return false,
};
let new_box_style = new_style.get_box();
if !self.has_css_transitions(context.shared, pseudo_element) &&
!new_style.get_ui().specifies_transitions()
!new_box_style.specifies_transitions()
{
return false;
}
if new_style.clone_display().is_none() || old_style.clone_display().is_none() {
if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() {
return false;
}
@ -764,8 +766,8 @@ trait PrivateMatchMethods: TElement {
},
}
let old_display = old_values.clone_display();
let new_display = new_values.clone_display();
let old_display = old_values.get_box().clone_display();
let new_display = new_values.get_box().clone_display();
if old_display != new_display {
// If we used to be a display: none element, and no longer are, our
@ -1005,6 +1007,51 @@ pub trait MatchMethods: TElement {
cascade_requirement
}
/// Applies selector flags to an element, deferring mutations of the parent
/// until after the traversal.
///
/// TODO(emilio): This is somewhat inefficient, because it doesn't take
/// advantage of us knowing that the traversal is sequential.
fn apply_selector_flags(
&self,
map: &mut SelectorFlagsMap<Self>,
element: &Self,
flags: ElementSelectorFlags,
) {
// Handle flags that apply to the element.
let self_flags = flags.for_self();
if !self_flags.is_empty() {
if element == self {
// If this is the element we're styling, we have exclusive
// access to the element, and thus it's fine inserting them,
// even from the worker.
unsafe {
element.set_selector_flags(self_flags);
}
} else {
// Otherwise, this element is an ancestor of the current element
// we're styling, and thus multiple children could write to it
// if we did from here.
//
// Instead, we can read them, and post them if necessary as a
// sequential task in order for them to be processed later.
if !element.has_selector_flags(self_flags) {
map.insert_flags(*element, self_flags);
}
}
}
// Handle flags that apply to the parent.
let parent_flags = flags.for_parent();
if !parent_flags.is_empty() {
if let Some(p) = element.parent_element() {
if !p.has_selector_flags(parent_flags) {
map.insert_flags(p, parent_flags);
}
}
}
}
/// Updates the rule nodes without re-running selector matching, using just
/// the rule tree.
///

View file

@ -2,14 +2,13 @@
* 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 query condition:
//! A media query condition:
//!
//! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition
//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition
use super::{QueryFeatureExpression, FeatureType, FeatureFlags};
use super::{Device, MediaFeatureExpression};
use crate::context::QuirksMode;
use crate::parser::ParserContext;
use crate::values::computed;
use cssparser::{Parser, Token};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
@ -29,38 +28,38 @@ enum AllowOr {
No,
}
/// Represents a condition.
/// Represents a media condition.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum QueryCondition {
/// A simple feature expression, implicitly parenthesized.
Feature(QueryFeatureExpression),
pub enum MediaCondition {
/// A simple media feature expression, implicitly parenthesized.
Feature(MediaFeatureExpression),
/// A negation of a condition.
Not(Box<QueryCondition>),
Not(Box<MediaCondition>),
/// A set of joint operations.
Operation(Box<[QueryCondition]>, Operator),
Operation(Box<[MediaCondition]>, Operator),
/// A condition wrapped in parenthesis.
InParens(Box<QueryCondition>),
InParens(Box<MediaCondition>),
}
impl ToCss for QueryCondition {
impl ToCss for MediaCondition {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
match *self {
// NOTE(emilio): QueryFeatureExpression already includes the
// NOTE(emilio): MediaFeatureExpression already includes the
// parenthesis.
QueryCondition::Feature(ref f) => f.to_css(dest),
QueryCondition::Not(ref c) => {
MediaCondition::Feature(ref f) => f.to_css(dest),
MediaCondition::Not(ref c) => {
dest.write_str("not ")?;
c.to_css(dest)
},
QueryCondition::InParens(ref c) => {
MediaCondition::InParens(ref c) => {
dest.write_char('(')?;
c.to_css(dest)?;
dest.write_char(')')
},
QueryCondition::Operation(ref list, op) => {
MediaCondition::Operation(ref list, op) => {
let mut iter = list.iter();
iter.next().unwrap().to_css(dest)?;
for item in iter {
@ -75,60 +74,28 @@ impl ToCss for QueryCondition {
}
}
impl QueryCondition {
/// Parse a single condition.
impl MediaCondition {
/// Parse a single media condition.
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, feature_type, AllowOr::Yes)
Self::parse_internal(context, input, AllowOr::Yes)
}
fn visit<F>(&self, visitor: &mut F)
where
F: FnMut(&Self),
{
visitor(self);
match *self {
Self::Feature(..) => {},
Self::Not(ref cond) => cond.visit(visitor),
Self::Operation(ref conds, _op) => {
for cond in conds.iter() {
cond.visit(visitor);
}
},
Self::InParens(ref cond) => cond.visit(visitor),
}
}
/// Returns the union of all flags in the expression. This is useful for
/// container queries.
pub fn cumulative_flags(&self) -> FeatureFlags {
let mut result = FeatureFlags::empty();
self.visit(&mut |condition| {
if let Self::Feature(ref f) = condition {
result.insert(f.feature_flags())
}
});
result
}
/// Parse a single condition, disallowing `or` expressions.
/// Parse a single media condition, disallowing `or` expressions.
///
/// To be used from the legacy query syntax.
/// To be used from the legacy media query syntax.
pub fn parse_disallow_or<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
Self::parse_internal(context, input, feature_type, AllowOr::No)
Self::parse_internal(context, input, AllowOr::No)
}
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
allow_or: AllowOr,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
@ -141,12 +108,12 @@ impl QueryCondition {
};
if is_negation {
let inner_condition = Self::parse_in_parens(context, input, feature_type)?;
return Ok(QueryCondition::Not(Box::new(inner_condition)));
let inner_condition = Self::parse_in_parens(context, input)?;
return Ok(MediaCondition::Not(Box::new(inner_condition)));
}
// ParenthesisBlock.
let first_condition = Self::parse_paren_block(context, input, feature_type)?;
let first_condition = Self::parse_paren_block(context, input)?;
let operator = match input.try_parse(Operator::parse) {
Ok(op) => op,
Err(..) => return Ok(first_condition),
@ -158,7 +125,7 @@ impl QueryCondition {
let mut conditions = vec![];
conditions.push(first_condition);
conditions.push(Self::parse_in_parens(context, input, feature_type)?);
conditions.push(Self::parse_in_parens(context, input)?);
let delim = match operator {
Operator::And => "and",
@ -167,52 +134,50 @@ impl QueryCondition {
loop {
if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() {
return Ok(QueryCondition::Operation(
return Ok(MediaCondition::Operation(
conditions.into_boxed_slice(),
operator,
));
}
conditions.push(Self::parse_in_parens(context, input, feature_type)?);
conditions.push(Self::parse_in_parens(context, input)?);
}
}
/// Parse a condition in parentheses.
/// Parse a media condition in parentheses.
pub fn parse_in_parens<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
input.expect_parenthesis_block()?;
Self::parse_paren_block(context, input, feature_type)
Self::parse_paren_block(context, input)
}
fn parse_paren_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
input.parse_nested_block(|input| {
// Base case.
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) {
return Ok(QueryCondition::InParens(Box::new(inner)));
if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) {
return Ok(MediaCondition::InParens(Box::new(inner)));
}
let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?;
Ok(QueryCondition::Feature(expr))
let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?;
Ok(MediaCondition::Feature(expr))
})
}
/// Whether this condition matches the device and quirks mode.
pub fn matches(&self, context: &computed::Context) -> bool {
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
match *self {
QueryCondition::Feature(ref f) => f.matches(context),
QueryCondition::InParens(ref c) => c.matches(context),
QueryCondition::Not(ref c) => !c.matches(context),
QueryCondition::Operation(ref conditions, op) => {
MediaCondition::Feature(ref f) => f.matches(device, quirks_mode),
MediaCondition::InParens(ref c) => c.matches(device, quirks_mode),
MediaCondition::Not(ref c) => !c.matches(device, quirks_mode),
MediaCondition::Operation(ref conditions, op) => {
let mut iter = conditions.iter();
match op {
Operator::And => iter.all(|c| c.matches(context)),
Operator::Or => iter.any(|c| c.matches(context)),
Operator::And => iter.all(|c| c.matches(device, quirks_mode)),
Operator::Or => iter.any(|c| c.matches(device, quirks_mode)),
}
},
}

View file

@ -2,10 +2,13 @@
* 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/. */
//! Query features.
//! Media features.
use super::media_feature_expression::RangeOrOperator;
use super::Device;
use crate::parser::ParserContext;
use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio};
use crate::values::computed::Ratio;
use crate::values::computed::{CSSPixelLength, Resolution};
use crate::Atom;
use cssparser::Parser;
use std::fmt;
@ -14,7 +17,12 @@ use style_traits::ParseError;
/// A generic discriminant for an enum value.
pub type KeywordDiscriminant = u8;
type QueryFeatureGetter<T> = fn(device: &computed::Context) -> T;
type MediaFeatureEvaluator<T> = fn(
device: &Device,
// null == no value was given in the query.
value: Option<T>,
range_or_operator: Option<RangeOrOperator>,
) -> bool;
/// Serializes a given discriminant.
///
@ -28,19 +36,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn(
input: &'a mut Parser<'i, 't>,
) -> Result<KeywordDiscriminant, ParseError<'i>>;
/// An evaluator for a given feature.
/// An evaluator for a given media feature.
///
/// This determines the kind of values that get parsed, too.
#[allow(missing_docs)]
pub enum Evaluator {
Length(QueryFeatureGetter<CSSPixelLength>),
Integer(QueryFeatureGetter<u32>),
Float(QueryFeatureGetter<f32>),
BoolInteger(QueryFeatureGetter<bool>),
Length(MediaFeatureEvaluator<CSSPixelLength>),
Integer(MediaFeatureEvaluator<u32>),
Float(MediaFeatureEvaluator<f32>),
BoolInteger(MediaFeatureEvaluator<bool>),
/// A non-negative number ratio, such as the one from device-pixel-ratio.
NumberRatio(QueryFeatureGetter<Ratio>),
NumberRatio(MediaFeatureEvaluator<Ratio>),
/// A resolution.
Resolution(QueryFeatureGetter<Resolution>),
Resolution(MediaFeatureEvaluator<Resolution>),
/// A keyword value.
Enumerated {
/// The parser to get a discriminant given a string.
@ -52,8 +60,9 @@ pub enum Evaluator {
serializer: KeywordSerializer,
/// The evaluator itself. This is guaranteed to be called with a
/// keyword that `parser` has produced.
evaluator: fn(&computed::Context, Option<KeywordDiscriminant>) -> bool,
evaluator: MediaFeatureEvaluator<KeywordDiscriminant>,
},
Ident(MediaFeatureEvaluator<Atom>),
}
/// A simple helper macro to create a keyword evaluator.
@ -67,14 +76,14 @@ macro_rules! keyword_evaluator {
context: &$crate::parser::ParserContext,
input: &mut $crate::cssparser::Parser<'i, 't>,
) -> Result<
$crate::queries::feature::KeywordDiscriminant,
$crate::media_queries::media_feature::KeywordDiscriminant,
::style_traits::ParseError<'i>,
> {
let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?;
Ok(kw as $crate::queries::feature::KeywordDiscriminant)
Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant)
}
fn __serialize(kw: $crate::queries::feature::KeywordDiscriminant) -> String {
fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String {
// This unwrap is ok because the only discriminants that get
// back to us is the ones that `parse` produces.
let value: $keyword_type = ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap();
@ -82,17 +91,24 @@ macro_rules! keyword_evaluator {
}
fn __evaluate(
context: &$crate::values::computed::Context,
value: Option<$crate::queries::feature::KeywordDiscriminant>,
device: &$crate::media_queries::Device,
value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>,
range_or_operator: Option<
$crate::media_queries::media_feature_expression::RangeOrOperator,
>,
) -> bool {
debug_assert!(
range_or_operator.is_none(),
"Since when do keywords accept ranges?"
);
// This unwrap is ok because the only discriminants that get
// back to us is the ones that `parse` produces.
let value: Option<$keyword_type> =
value.map(|kw| ::num_traits::cast::FromPrimitive::from_u8(kw).unwrap());
$actual_evaluator(context, value)
$actual_evaluator(device, value)
}
$crate::queries::feature::Evaluator::Enumerated {
$crate::media_queries::media_feature::Evaluator::Enumerated {
parser: __parse,
serializer: __serialize,
evaluator: __evaluate,
@ -101,46 +117,17 @@ macro_rules! keyword_evaluator {
}
bitflags! {
/// Different flags or toggles that change how a expression is parsed or
/// evaluated.
#[derive(ToShmem)]
pub struct FeatureFlags : u8 {
/// Different requirements or toggles that change how a expression is
/// parsed.
pub struct ParsingRequirements: u8 {
/// The feature should only be parsed in chrome and ua sheets.
const CHROME_AND_UA_ONLY = 1 << 0;
/// The feature requires a -webkit- prefix.
const WEBKIT_PREFIX = 1 << 1;
/// The feature requires the inline-axis containment.
const CONTAINER_REQUIRES_INLINE_AXIS = 1 << 2;
/// The feature requires the block-axis containment.
const CONTAINER_REQUIRES_BLOCK_AXIS = 1 << 3;
/// The feature requires containment in the physical width axis.
const CONTAINER_REQUIRES_WIDTH_AXIS = 1 << 4;
/// The feature requires containment in the physical height axis.
const CONTAINER_REQUIRES_HEIGHT_AXIS = 1 << 5;
}
}
impl FeatureFlags {
/// Returns parsing requirement flags.
pub fn parsing_requirements(self) -> Self {
self.intersection(Self::CHROME_AND_UA_ONLY | Self::WEBKIT_PREFIX)
}
/// Returns all the container axis flags.
pub fn all_container_axes() -> Self {
Self::CONTAINER_REQUIRES_INLINE_AXIS |
Self::CONTAINER_REQUIRES_BLOCK_AXIS |
Self::CONTAINER_REQUIRES_WIDTH_AXIS |
Self::CONTAINER_REQUIRES_HEIGHT_AXIS
}
/// Returns our subset of container axis flags.
pub fn container_axes(self) -> Self {
self.intersection(Self::all_container_axes())
}
}
/// Whether a feature allows ranges or not.
/// Whether a media feature allows ranges or not.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum AllowsRanges {
@ -148,45 +135,46 @@ pub enum AllowsRanges {
No,
}
/// A description of a feature.
pub struct QueryFeatureDescription {
/// The feature name, in ascii lowercase.
/// A description of a media feature.
pub struct MediaFeatureDescription {
/// The media feature name, in ascii lowercase.
pub name: Atom,
/// Whether min- / max- prefixes are allowed or not.
pub allows_ranges: AllowsRanges,
/// The evaluator, which we also use to determine which kind of value to
/// parse.
pub evaluator: Evaluator,
/// Different feature-specific flags.
pub flags: FeatureFlags,
/// Different requirements that need to hold for the feature to be
/// successfully parsed.
pub requirements: ParsingRequirements,
}
impl QueryFeatureDescription {
/// Whether this feature allows ranges.
impl MediaFeatureDescription {
/// Whether this media feature allows ranges.
#[inline]
pub fn allows_ranges(&self) -> bool {
self.allows_ranges == AllowsRanges::Yes
}
}
/// A simple helper to construct a `QueryFeatureDescription`.
/// A simple helper to construct a `MediaFeatureDescription`.
macro_rules! feature {
($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => {
$crate::queries::feature::QueryFeatureDescription {
($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => {
$crate::media_queries::media_feature::MediaFeatureDescription {
name: $name,
allows_ranges: $allows_ranges,
evaluator: $evaluator,
flags: $flags,
requirements: $reqs,
}
};
}
impl fmt::Debug for QueryFeatureDescription {
impl fmt::Debug for MediaFeatureDescription {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("QueryFeatureDescription")
f.debug_struct("MediaFeatureExpression")
.field("name", &self.name)
.field("allows_ranges", &self.allows_ranges)
.field("flags", &self.flags)
.field("requirements", &self.requirements)
.finish()
}
}

View file

@ -0,0 +1,535 @@
/* 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/. */
//! Parsing for media feature expressions, like `(foo: bar)` or
//! `(width >= 400px)`.
use super::media_feature::{Evaluator, MediaFeatureDescription};
use super::media_feature::{KeywordDiscriminant, ParsingRequirements};
use super::Device;
use crate::context::QuirksMode;
#[cfg(feature = "gecko")]
use crate::gecko::media_features::MEDIA_FEATURES;
use crate::parser::{Parse, ParserContext};
#[cfg(feature = "servo")]
use crate::servo::media_queries::MEDIA_FEATURES;
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::values::computed::{self, Ratio, ToComputedValue};
use crate::values::specified::{Integer, Length, Number, Resolution};
use crate::values::{serialize_atom_identifier, CSSFloat};
use crate::{Atom, Zero};
use cssparser::{Parser, Token};
use std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// The kind of matching that should be performed on a media feature value.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum Range {
/// At least the specified value.
Min,
/// At most the specified value.
Max,
}
/// The operator that was specified in this media feature.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum Operator {
/// =
Equal,
/// >
GreaterThan,
/// >=
GreaterThanEqual,
/// <
LessThan,
/// <=
LessThanEqual,
}
impl ToCss for Operator {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str(match *self {
Operator::Equal => "=",
Operator::LessThan => "<",
Operator::LessThanEqual => "<=",
Operator::GreaterThan => ">",
Operator::GreaterThanEqual => ">=",
})
}
}
/// Either a `Range` or an `Operator`.
///
/// Ranged media features are not allowed with operations (that'd make no
/// sense).
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum RangeOrOperator {
/// A `Range`.
Range(Range),
/// An `Operator`.
Operator(Operator),
}
impl RangeOrOperator {
/// Evaluate a given range given an optional query value and a value from
/// the browser.
pub fn evaluate<T>(range_or_op: Option<Self>, query_value: Option<T>, value: T) -> bool
where
T: PartialOrd + Zero,
{
match query_value {
Some(v) => Self::evaluate_with_query_value(range_or_op, v, value),
None => !value.is_zero(),
}
}
/// Evaluate a given range given a non-optional query value and a value from
/// the browser.
pub fn evaluate_with_query_value<T>(range_or_op: Option<Self>, query_value: T, value: T) -> bool
where
T: PartialOrd,
{
let cmp = match value.partial_cmp(&query_value) {
Some(c) => c,
None => return false,
};
let range_or_op = match range_or_op {
Some(r) => r,
None => return cmp == Ordering::Equal,
};
match range_or_op {
RangeOrOperator::Range(range) => {
cmp == Ordering::Equal ||
match range {
Range::Min => cmp == Ordering::Greater,
Range::Max => cmp == Ordering::Less,
}
},
RangeOrOperator::Operator(op) => match op {
Operator::Equal => cmp == Ordering::Equal,
Operator::GreaterThan => cmp == Ordering::Greater,
Operator::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
Operator::LessThan => cmp == Ordering::Less,
Operator::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
},
}
}
}
/// A feature expression contains a reference to the media feature, the value
/// the media query contained, and the range to evaluate.
#[derive(Clone, Debug, MallocSizeOf, ToShmem)]
pub struct MediaFeatureExpression {
feature_index: usize,
value: Option<MediaExpressionValue>,
range_or_operator: Option<RangeOrOperator>,
}
impl PartialEq for MediaFeatureExpression {
fn eq(&self, other: &Self) -> bool {
self.feature_index == other.feature_index &&
self.value == other.value &&
self.range_or_operator == other.range_or_operator
}
}
impl ToCss for MediaFeatureExpression {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str("(")?;
let feature = self.feature();
if feature
.requirements
.contains(ParsingRequirements::WEBKIT_PREFIX)
{
dest.write_str("-webkit-")?;
}
if let Some(RangeOrOperator::Range(range)) = self.range_or_operator {
match range {
Range::Min => dest.write_str("min-")?,
Range::Max => dest.write_str("max-")?,
}
}
// NB: CssStringWriter not needed, feature names are under control.
write!(dest, "{}", feature.name)?;
if let Some(RangeOrOperator::Operator(op)) = self.range_or_operator {
dest.write_char(' ')?;
op.to_css(dest)?;
dest.write_char(' ')?;
} else if self.value.is_some() {
dest.write_str(": ")?;
}
if let Some(ref val) = self.value {
val.to_css(dest, self)?;
}
dest.write_str(")")
}
}
/// Consumes an operation or a colon, or returns an error.
fn consume_operation_or_colon(input: &mut Parser) -> Result<Option<Operator>, ()> {
let first_delim = {
let next_token = match input.next() {
Ok(t) => t,
Err(..) => return Err(()),
};
match *next_token {
Token::Colon => return Ok(None),
Token::Delim(oper) => oper,
_ => return Err(()),
}
};
let operator = match first_delim {
'=' => return Ok(Some(Operator::Equal)),
'>' => Operator::GreaterThan,
'<' => Operator::LessThan,
_ => return Err(()),
};
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
//
// No whitespace is allowed between the “<” or “>”
// <delim-token>s and the following “=” <delim-token>, if its
// present.
//
// TODO(emilio): Maybe we should ignore comments as well?
// https://github.com/w3c/csswg-drafts/issues/6248
let parsed_equal = input
.try_parse(|i| {
let t = i.next_including_whitespace().map_err(|_| ())?;
if !matches!(t, Token::Delim('=')) {
return Err(());
}
Ok(())
})
.is_ok();
if !parsed_equal {
return Ok(Some(operator));
}
Ok(Some(match operator {
Operator::GreaterThan => Operator::GreaterThanEqual,
Operator::LessThan => Operator::LessThanEqual,
_ => unreachable!(),
}))
}
#[allow(unused_variables)]
fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
#[cfg(feature = "gecko")]
{
if *feature == atom!("forced-colors") {
// forced-colors is always enabled in the ua and chrome. On
// the web it is hidden behind a preference, which is defaulted
// to 'true' as of bug 1659511.
return !context.in_ua_or_chrome_sheet() &&
!static_prefs::pref!("layout.css.forced-colors.enabled");
}
// prefers-contrast is always enabled in the ua and chrome. On
// the web it is hidden behind a preference.
if *feature == atom!("prefers-contrast") {
return !context.in_ua_or_chrome_sheet() &&
!static_prefs::pref!("layout.css.prefers-contrast.enabled");
}
}
false
}
impl MediaFeatureExpression {
fn new(
feature_index: usize,
value: Option<MediaExpressionValue>,
range_or_operator: Option<RangeOrOperator>,
) -> Self {
debug_assert!(feature_index < MEDIA_FEATURES.len());
Self {
feature_index,
value,
range_or_operator,
}
}
fn feature(&self) -> &'static MediaFeatureDescription {
&MEDIA_FEATURES[self.feature_index]
}
/// Parse a media expression of the form:
///
/// ```
/// (media-feature: media-value)
/// ```
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.expect_parenthesis_block()?;
input.parse_nested_block(|input| Self::parse_in_parenthesis_block(context, input))
}
/// Parse a media feature expression where we've already consumed the
/// parenthesis.
pub fn parse_in_parenthesis_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut requirements = ParsingRequirements::empty();
let location = input.current_source_location();
let ident = input.expect_ident()?;
if context.in_ua_or_chrome_sheet() {
requirements.insert(ParsingRequirements::CHROME_AND_UA_ONLY);
}
let mut feature_name = &**ident;
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
feature_name = &feature_name[8..];
requirements.insert(ParsingRequirements::WEBKIT_PREFIX);
}
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
feature_name = &feature_name[4..];
Some(Range::Min)
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
feature_name = &feature_name[4..];
Some(Range::Max)
} else {
None
};
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
let (feature_index, feature) = match MEDIA_FEATURES
.iter()
.enumerate()
.find(|(_, f)| f.name == atom)
{
Some((i, f)) => (i, f),
None => {
return Err(location.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
))
},
};
if disabled_by_pref(&feature.name, context) ||
!requirements.contains(feature.requirements) ||
(range.is_some() && !feature.allows_ranges())
{
return Err(location.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
));
}
let operator = input.try_parse(consume_operation_or_colon);
let operator = match operator {
Err(..) => {
// If there's no colon, this is a media query of the
// form '(<feature>)', that is, there's no value
// specified.
//
// Gecko doesn't allow ranged expressions without a
// value, so just reject them here too.
if range.is_some() {
return Err(
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
);
}
return Ok(Self::new(feature_index, None, None));
},
Ok(operator) => operator,
};
let range_or_operator = match range {
Some(range) => {
if operator.is_some() {
return Err(
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
);
}
Some(RangeOrOperator::Range(range))
},
None => match operator {
Some(operator) => {
if !feature.allows_ranges() {
return Err(input
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
}
Some(RangeOrOperator::Operator(operator))
},
None => None,
},
};
let value = MediaExpressionValue::parse(feature, context, input).map_err(|err| {
err.location
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
})?;
Ok(Self::new(feature_index, Some(value), range_or_operator))
}
/// Returns whether this media query evaluates to true for the given device.
pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
let value = self.value.as_ref();
macro_rules! expect {
($variant:ident) => {
value.map(|value| match *value {
MediaExpressionValue::$variant(ref v) => v,
_ => unreachable!("Unexpected MediaExpressionValue"),
})
};
}
match self.feature().evaluator {
Evaluator::Length(eval) => {
let computed = expect!(Length).map(|specified| {
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
specified.to_computed_value(context)
})
});
eval(device, computed, self.range_or_operator)
},
Evaluator::Integer(eval) => {
eval(device, expect!(Integer).cloned(), self.range_or_operator)
},
Evaluator::Float(eval) => eval(device, expect!(Float).cloned(), self.range_or_operator),
Evaluator::NumberRatio(eval) => eval(
device,
expect!(NumberRatio).cloned(),
self.range_or_operator,
),
Evaluator::Resolution(eval) => {
let computed = expect!(Resolution).map(|specified| {
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
specified.to_computed_value(context)
})
});
eval(device, computed, self.range_or_operator)
},
Evaluator::Enumerated { evaluator, .. } => {
evaluator(device, expect!(Enumerated).cloned(), self.range_or_operator)
},
Evaluator::Ident(eval) => eval(device, expect!(Ident).cloned(), self.range_or_operator),
Evaluator::BoolInteger(eval) => eval(
device,
expect!(BoolInteger).cloned(),
self.range_or_operator,
),
}
}
}
/// A value found or expected in a media expression.
///
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
/// BoolInteger / NumberRatio case, as computed or as specified value?
///
/// If the first, this would need to store the relevant values.
///
/// See: https://github.com/w3c/csswg-drafts/issues/1968
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum MediaExpressionValue {
/// A length.
Length(Length),
/// A (non-negative) integer.
Integer(u32),
/// A floating point value.
Float(CSSFloat),
/// A boolean value, specified as an integer (i.e., either 0 or 1).
BoolInteger(bool),
/// A single non-negative number or two non-negative numbers separated by '/',
/// with optional whitespace on either side of the '/'.
NumberRatio(Ratio),
/// A resolution.
Resolution(Resolution),
/// An enumerated value, defined by the variant keyword table in the
/// feature's `mData` member.
Enumerated(KeywordDiscriminant),
/// An identifier.
Ident(Atom),
}
impl MediaExpressionValue {
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &MediaFeatureExpression) -> fmt::Result
where
W: fmt::Write,
{
match *self {
MediaExpressionValue::Length(ref l) => l.to_css(dest),
MediaExpressionValue::Integer(v) => v.to_css(dest),
MediaExpressionValue::Float(v) => v.to_css(dest),
MediaExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
MediaExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
MediaExpressionValue::Resolution(ref r) => r.to_css(dest),
MediaExpressionValue::Ident(ref ident) => serialize_atom_identifier(ident, dest),
MediaExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
_ => unreachable!(),
},
}
}
fn parse<'i, 't>(
for_feature: &MediaFeatureDescription,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<MediaExpressionValue, ParseError<'i>> {
Ok(match for_feature.evaluator {
Evaluator::Length(..) => {
let length = Length::parse_non_negative(context, input)?;
MediaExpressionValue::Length(length)
},
Evaluator::Integer(..) => {
let integer = Integer::parse_non_negative(context, input)?;
MediaExpressionValue::Integer(integer.value() as u32)
},
Evaluator::BoolInteger(..) => {
let integer = Integer::parse_non_negative(context, input)?;
let value = integer.value();
if value > 1 {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
MediaExpressionValue::BoolInteger(value == 1)
},
Evaluator::Float(..) => {
let number = Number::parse(context, input)?;
MediaExpressionValue::Float(number.get())
},
Evaluator::NumberRatio(..) => {
use crate::values::specified::Ratio as SpecifiedRatio;
let ratio = SpecifiedRatio::parse(context, input)?;
MediaExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
},
Evaluator::Resolution(..) => {
MediaExpressionValue::Resolution(Resolution::parse(context, input)?)
},
Evaluator::Enumerated { parser, .. } => {
MediaExpressionValue::Enumerated(parser(context, input)?)
},
Evaluator::Ident(..) => {
MediaExpressionValue::Ident(Atom::from(input.expect_ident()?.as_ref()))
},
})
}
}

View file

@ -10,7 +10,6 @@ use super::{Device, MediaQuery, Qualifier};
use crate::context::QuirksMode;
use crate::error_reporting::ContextualParseError;
use crate::parser::ParserContext;
use crate::values::computed;
use cssparser::{Delimiter, Parser};
use cssparser::{ParserInput, Token};
@ -75,17 +74,15 @@ impl MediaList {
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
// Check if it is an empty media query list or any queries match.
// https://drafts.csswg.org/mediaqueries-4/#mq-list
if self.media_queries.is_empty() {
return true;
}
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
self.media_queries.is_empty() ||
self.media_queries.iter().any(|mq| {
let media_match = mq.media_type.matches(device.media_type());
// Check if the media condition match.
let query_match = media_match &&
mq.condition.as_ref().map_or(true, |c| c.matches(context));
mq.condition
.as_ref()
.map_or(true, |c| c.matches(device, quirks_mode));
// Apply the logical NOT qualifier to the result
match mq.qualifier {
@ -93,7 +90,6 @@ impl MediaList {
_ => query_match,
}
})
})
}
/// Whether this `MediaList` contains no media queries.

View file

@ -6,7 +6,7 @@
//!
//! https://drafts.csswg.org/mediaqueries/#typedef-media-query
use crate::queries::{QueryCondition, FeatureType};
use super::media_condition::MediaCondition;
use crate::parser::ParserContext;
use crate::str::string_as_ascii_lowercase;
use crate::values::CustomIdent;
@ -66,7 +66,7 @@ pub struct MediaQuery {
pub media_type: MediaQueryType,
/// The condition that this media query contains. This cannot have `or`
/// in the first level.
pub condition: Option<QueryCondition>,
pub condition: Option<MediaCondition>,
}
impl ToCss for MediaQuery {
@ -134,9 +134,9 @@ impl MediaQuery {
.unwrap_or_default();
let condition = if explicit_media_type.is_none() {
Some(QueryCondition::parse(context, input, FeatureType::Media)?)
Some(MediaCondition::parse(context, input)?)
} else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() {
Some(QueryCondition::parse_disallow_or(context, input, FeatureType::Media)?)
Some(MediaCondition::parse_disallow_or(context, input)?)
} else {
None
};

View file

@ -6,9 +6,15 @@
//!
//! [mq]: https://drafts.csswg.org/mediaqueries/
mod media_condition;
mod media_list;
mod media_query;
#[macro_use]
pub mod media_feature;
pub mod media_feature_expression;
pub use self::media_condition::MediaCondition;
pub use self::media_feature_expression::MediaFeatureExpression;
pub use self::media_list::MediaList;
pub use self::media_query::{MediaQuery, MediaQueryType, MediaType, Qualifier};

View file

@ -1,223 +0,0 @@
/* 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 piecewise linear function, following CSS linear easing
/// draft as in https://github.com/w3c/csswg-drafts/pull/6533.
use euclid::approxeq::ApproxEq;
use itertools::Itertools;
use crate::values::CSSFloat;
type ValueType = CSSFloat;
/// a single entry in a piecewise linear function.
#[derive(Clone, Copy)]
#[repr(C)]
struct Entry {
x: ValueType,
y: ValueType,
}
/// Representation of a piecewise linear function, a series of linear functions.
#[derive(Default)]
#[repr(C)]
pub struct PiecewiseLinearFunction {
entries: crate::OwnedSlice<Entry>,
}
impl PiecewiseLinearFunction {
/// Interpolate y value given x and two points. The linear function will be rooted at the asymptote.
fn interpolate(x: ValueType, prev: Entry, next: Entry, asymptote: &Entry) -> ValueType {
// Line is vertical, or the two points are identical. Avoid infinite slope by pretending
// the line is flat.
if prev.x.approx_eq(&next.x) {
return asymptote.y;
}
let slope = (next.y - prev.y) / (next.x - prev.x);
return slope * (x - asymptote.x) + asymptote.y;
}
/// Get the y value of the piecewise linear function given the x value.
pub fn at(&self, x: ValueType) -> ValueType {
if !x.is_finite() {
return if x > 0.0 { 1.0 } else { 0.0 };
}
if self.entries.is_empty() {
// Implied y = x, as per spec.
return x;
}
if self.entries.len() == 1 {
// Implied y = <constant>, as per spec.
return self.entries[0].y;
}
// Spec dictates the valid input domain is [0, 1]. Outside of this range, the output
// should be calculated as if the slopes at start and end extend to infinity. However, if the
// start/end have two points of the same position, the line should extend along the x-axis.
// The function doesn't have to cover the input domain, in which case the extension logic
// applies even if the input falls in the input domain.
// Also, we're guaranteed to have at least two elements at this point.
if x < self.entries[0].x {
return Self::interpolate(x, self.entries[0], self.entries[1], &self.entries[0]);
}
let mut rev_iter = self.entries.iter().rev();
let last = rev_iter.next().unwrap();
if x > last.x {
let second_last = rev_iter.next().unwrap();
return Self::interpolate(x, *second_last, *last, last);
}
// Now we know the input sits within the domain explicitly defined by our function.
for (prev, next) in self.entries.iter().tuple_windows() {
if x > next.x {
continue;
}
// Prefer left hand side value
if x.approx_eq(&prev.x) {
return prev.y;
}
if x.approx_eq(&next.x) {
return next.y;
}
return Self::interpolate(x, *prev, *next, prev);
}
unreachable!("Input is supposed to be within the entries' min & max!");
}
}
/// Entry of a piecewise linear function while building, where the calculation of x value can be deferred.
#[derive(Clone, Copy)]
struct BuildEntry {
x: Option<ValueType>,
y: ValueType,
}
/// Builder object to generate a linear function.
#[derive(Default)]
pub struct PiecewiseLinearFunctionBuilder {
largest_x: Option<ValueType>,
smallest_x: Option<ValueType>,
entries: Vec<BuildEntry>,
}
impl PiecewiseLinearFunctionBuilder {
#[allow(missing_docs)]
pub fn new() -> Self {
PiecewiseLinearFunctionBuilder::default()
}
fn create_entry(&mut self, y: ValueType, x: Option<ValueType>) {
let x = match x {
Some(x) if x.is_finite() => x,
_ => {
self.entries.push(BuildEntry { x: None, y });
return;
},
};
// Specified x value cannot regress, as per spec.
let x = match self.largest_x {
Some(largest_x) => x.max(largest_x),
None => x,
};
self.largest_x = Some(x);
// Whatever we see the earliest is the smallest value.
if self.smallest_x.is_none() {
self.smallest_x = Some(x);
}
self.entries.push(BuildEntry { x: Some(x), y });
}
/// Add a new entry into the piecewise linear function with specified y value.
/// If the start x value is given, that is where the x value will be. Otherwise,
/// the x value is calculated later. If the end x value is specified, a flat segment
/// is generated. If start x value is not specified but end x is, it is treated as
/// start x.
pub fn push(mut self, y: CSSFloat, x_start: Option<CSSFloat>, x_end: Option<CSSFloat>) -> Self {
self.create_entry(y, x_start);
if x_end.is_some() {
self.create_entry(y, x_end.map(|x| x));
}
self
}
/// Finish building the piecewise linear function by resolving all undefined x values,
/// then return the result.
pub fn build(mut self) -> PiecewiseLinearFunction {
if self.entries.is_empty() {
return PiecewiseLinearFunction::default();
}
if self.entries.len() == 1 {
// Don't bother resolving anything.
return PiecewiseLinearFunction {
entries: crate::OwnedSlice::from_slice(&[Entry {
x: 0.,
y: self.entries[0].y,
}]),
};
}
// Guaranteed at least two elements.
// Start and end elements guaranteed to have defined x value.
// Note(dshin): Spec asserts that start/end elements are supposed to have 0/1 assigned
// respectively if their x values are undefined at this time; however, the spec does
// not disallow negative/100%+ inputs, and inputs like `linear(0, 0.1 -10%, 0.9 110%, 1.0)`
// would break the assumption that the x values in the list increase monotonically.
// Otherwise, we still want 0/1 assigned to the start/end values regardless of
// adjacent x values (i.e. `linear(0, 0.1 10%, 0.9 90%, 1.0)` ==
// `linear(0 0%, 0.1 10%, 0.9 90%, 1.0)` != `linear(0 10%, 0.1 10%, 0.9 90%, 1.0 90%)`)
self.entries[0]
.x
.get_or_insert(self.smallest_x.filter(|x| x < &0.0).unwrap_or(0.0));
self.entries
.last_mut()
.unwrap()
.x
.get_or_insert(self.largest_x.filter(|x| x > &1.0).unwrap_or(1.0));
let mut result = Vec::with_capacity(self.entries.len());
result.push(Entry {
x: self.entries[0].x.unwrap(),
y: self.entries[0].y,
});
for (i, e) in self.entries.iter().enumerate().skip(1) {
if e.x.is_none() {
// Need to calculate x values by first finding an entry with the first
// defined x value (Guaranteed to exist as the list end has it defined).
continue;
}
// x is defined for this element.
let divisor = i - result.len() + 1;
// Any element(s) with undefined x to assign?
if divisor != 1 {
// Have at least one element in result at all times.
let start_x = result.last().unwrap().x;
let increment = (e.x.unwrap() - start_x) / divisor as ValueType;
// Grab every element with undefined x to this point, which starts at the end of the result
// array, and ending right before the current index. Then, assigned the evenly divided
// x values.
result.extend(
self.entries[result.len()..i]
.iter()
.enumerate()
.map(|(j, e)| {
debug_assert!(e.x.is_none(), "Expected an entry with x undefined!");
Entry {
x: increment * (j + 1) as ValueType + start_x,
y: e.y,
}
}),
);
}
result.push(Entry {
x: e.x.unwrap(),
y: e.y,
});
}
debug_assert_eq!(
result.len(),
self.entries.len(),
"Should've mapped one-to-one!"
);
PiecewiseLinearFunction {
entries: result.into(),
}
}
}

View file

@ -28,8 +28,34 @@ use smallvec::SmallVec;
use std::borrow::Cow;
use std::cell::RefCell;
/// We split the cascade in two phases: 'early' properties, and 'late'
/// properties.
///
/// Early properties are the ones that don't have dependencies _and_ other
/// properties depend on, for example, writing-mode related properties, color
/// (for currentColor), or font-size (for em, etc).
///
/// Late properties are all the others.
trait CascadePhase {
fn is_early() -> bool;
}
struct EarlyProperties;
impl CascadePhase for EarlyProperties {
fn is_early() -> bool {
true
}
}
struct LateProperties;
impl CascadePhase for LateProperties {
fn is_early() -> bool {
false
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CanHaveLogicalProperties {
enum ApplyResetProperties {
No,
Yes,
}
@ -257,7 +283,6 @@ where
let inherited_style = parent_style.unwrap_or(device.default_computed_values());
let mut declarations = SmallVec::<[(&_, CascadePriority); 32]>::new();
let mut referenced_properties = LonghandIdSet::default();
let custom_properties = {
let mut builder = CustomPropertiesBuilder::new(inherited_style.custom_properties(), device);
@ -265,8 +290,6 @@ where
declarations.push((declaration, priority));
if let PropertyDeclaration::Custom(ref declaration) = *declaration {
builder.cascade(declaration, priority);
} else {
referenced_properties.insert(declaration.id().as_longhand().unwrap());
}
}
@ -292,74 +315,46 @@ where
in_media_query: false,
for_smil_animation: false,
for_non_inherited_property: None,
container_info: None,
quirks_mode,
rule_cache_conditions: RefCell::new(rule_cache_conditions),
};
let using_cached_reset_properties;
let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties);
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
let using_cached_reset_properties = {
let mut cascade = Cascade::new(&mut context, cascade_mode);
let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default();
let properties_to_apply = match cascade.cascade_mode {
CascadeMode::Visited { writing_mode } => {
cascade.context.builder.writing_mode = writing_mode;
// We never insert visited styles into the cache so we don't need to
// try looking it up. It also wouldn't be super-profitable, only a
// handful reset properties are non-inherited.
using_cached_reset_properties = false;
LonghandIdSet::visited_dependent()
},
CascadeMode::Unvisited { visited_rules } => {
if cascade.apply_properties(
CanHaveLogicalProperties::No,
LonghandIdSet::writing_mode_group(),
declarations.iter().cloned(),
&mut shorthand_cache,
) {
cascade.compute_writing_mode();
}
cascade.apply_properties::<EarlyProperties, _>(
ApplyResetProperties::Yes,
declarations.iter().cloned(),
&mut shorthand_cache,
);
if cascade.apply_properties(
CanHaveLogicalProperties::No,
LonghandIdSet::fonts_and_color_group(),
declarations.iter().cloned(),
&mut shorthand_cache,
) {
cascade.fixup_font_stuff();
}
cascade.compute_visited_style_if_needed(
element,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
guards,
);
if let Some(visited_rules) = visited_rules {
cascade.compute_visited_style_if_needed(
element,
parent_style,
parent_style_ignoring_first_line,
layout_parent_style,
visited_rules,
guards,
);
}
let using_cached_reset_properties =
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
using_cached_reset_properties =
cascade.try_to_use_cached_reset_properties(rule_cache, guards);
let apply_reset = if using_cached_reset_properties {
ApplyResetProperties::No
} else {
ApplyResetProperties::Yes
};
if using_cached_reset_properties {
LonghandIdSet::late_group_only_inherited()
} else {
LonghandIdSet::late_group()
}
}
cascade.apply_properties::<LateProperties, _>(
apply_reset,
declarations.iter().cloned(),
&mut shorthand_cache,
);
using_cached_reset_properties
};
cascade.apply_properties(
CanHaveLogicalProperties::Yes,
properties_to_apply,
declarations.iter().cloned(),
&mut shorthand_cache,
);
cascade.finished_applying_properties();
context.builder.clear_modified_reset();
if matches!(cascade_mode, CascadeMode::Unvisited { .. }) {
@ -418,7 +413,7 @@ fn tweak_when_ignoring_colors(
fn alpha_channel(color: &Color, context: &computed::Context) -> u8 {
// We assume here currentColor is opaque.
let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255));
let color = color.to_computed_value(context).to_rgba(RGBA::new(0, 0, 0, 255));
color.alpha
}
@ -433,17 +428,14 @@ fn tweak_when_ignoring_colors(
// otherwise, this is needed to preserve semi-transparent
// backgrounds.
//
// NOTE(emilio): We honor transparent unconditionally, like we do
// for color, even though it causes issues like bug 1625036. The
// reasoning is that the conditions that trigger that (having
// mismatched widget and default backgrounds) are both uncommon, and
// broken in other applications as well, and not honoring
// transparent makes stuff uglier or break unconditionally
// NOTE(emilio): We revert even for alpha == 0. Not doing so would
// be a bit special casey, even though it causes issues like
// bug 1625036. The reasoning is that the conditions that trigger
// that (having mismatched widget and default backgrounds) are both
// uncommon, and broken in other applications as well, and not
// honoring transparent makes stuff uglier or break unconditionally
// (bug 1666059, bug 1755713).
let alpha = alpha_channel(color, context);
if alpha == 0 {
return;
}
let mut color = context.builder.device.default_background_color();
color.alpha = alpha;
declarations_to_apply_unless_overriden
@ -505,8 +497,6 @@ fn tweak_when_ignoring_colors(
struct Cascade<'a, 'b: 'a> {
context: &'a mut computed::Context<'b>,
cascade_mode: CascadeMode<'a>,
/// All the properties that have a declaration in the cascade.
referenced: &'a LonghandIdSet,
seen: LonghandIdSet,
author_specified: LonghandIdSet,
reverted_set: LonghandIdSet,
@ -514,15 +504,10 @@ struct Cascade<'a, 'b: 'a> {
}
impl<'a, 'b: 'a> Cascade<'a, 'b> {
fn new(
context: &'a mut computed::Context<'b>,
cascade_mode: CascadeMode<'a>,
referenced: &'a LonghandIdSet,
) -> Self {
fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self {
Self {
context,
cascade_mode,
referenced,
seen: LonghandIdSet::default(),
author_specified: LonghandIdSet::default(),
reverted_set: Default::default(),
@ -590,21 +575,23 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
(CASCADE_PROPERTY[discriminant])(declaration, &mut self.context);
}
fn apply_properties<'decls, I>(
fn apply_properties<'decls, Phase, I>(
&mut self,
can_have_logical_properties: CanHaveLogicalProperties,
properties_to_apply: &'a LonghandIdSet,
apply_reset: ApplyResetProperties,
declarations: I,
mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache,
) -> bool
where
) where
Phase: CascadePhase,
I: Iterator<Item = (&'decls PropertyDeclaration, CascadePriority)>,
{
if !self.referenced.contains_any(properties_to_apply) {
return false;
}
let apply_reset = apply_reset == ApplyResetProperties::Yes;
let can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes;
debug_assert!(
!Phase::is_early() || apply_reset,
"Should always apply reset properties in the early phase, since we \
need to know font-size / writing-mode to decide whether to use the \
cached reset properties"
);
let ignore_colors = !self.context.builder.device.use_document_colors();
let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new();
@ -618,15 +605,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
PropertyDeclarationId::Custom(..) => continue,
};
if !properties_to_apply.contains(longhand_id) {
let inherited = longhand_id.inherited();
if !apply_reset && !inherited {
continue;
}
debug_assert!(can_have_logical_properties || !longhand_id.is_logical());
let physical_longhand_id = if can_have_logical_properties {
longhand_id.to_physical(self.context.builder.writing_mode)
} else {
if Phase::is_early() != longhand_id.is_early_property() {
continue;
}
debug_assert!(!Phase::is_early() || !longhand_id.is_logical());
let physical_longhand_id = if Phase::is_early() {
longhand_id
} else {
longhand_id.to_physical(self.context.builder.writing_mode)
};
if self.seen.contains(physical_longhand_id) {
@ -641,6 +633,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
}
}
// Only a few properties are allowed to depend on the visited state
// of links. When cascading visited styles, we can save time by
// only processing these properties.
if matches!(self.cascade_mode, CascadeMode::Visited { .. }) &&
!physical_longhand_id.is_visited_dependent()
{
continue;
}
let mut declaration =
self.substitute_variables_if_needed(declaration, &mut shorthand_cache);
@ -674,8 +675,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
continue;
},
CSSWideKeyword::Unset => true,
CSSWideKeyword::Inherit => longhand_id.inherited(),
CSSWideKeyword::Initial => !longhand_id.inherited(),
CSSWideKeyword::Inherit => inherited,
CSSWideKeyword::Initial => !inherited,
},
None => false,
};
@ -709,13 +710,22 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
}
}
true
if Phase::is_early() {
self.fixup_font_stuff();
self.compute_writing_mode();
} else {
self.finished_applying_properties();
}
}
fn compute_writing_mode(&mut self) {
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
self.context.builder.writing_mode =
WritingMode::new(self.context.builder.get_inherited_box())
let writing_mode = match self.cascade_mode {
CascadeMode::Unvisited { .. } => {
WritingMode::new(self.context.builder.get_inherited_box())
},
CascadeMode::Visited { writing_mode } => writing_mode,
};
self.context.builder.writing_mode = writing_mode;
}
fn compute_visited_style_if_needed<E>(
@ -724,12 +734,20 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> {
parent_style: Option<&ComputedValues>,
parent_style_ignoring_first_line: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
visited_rules: &StrongRuleNode,
guards: &StylesheetGuards,
) where
E: TElement,
{
debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. }));
let visited_rules = match self.cascade_mode {
CascadeMode::Unvisited { visited_rules } => visited_rules,
CascadeMode::Visited { .. } => return,
};
let visited_rules = match visited_rules {
Some(rules) => rules,
None => return,
};
let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link();
macro_rules! visited_parent {

View file

@ -73,6 +73,7 @@ COUNTED_UNKNOWN_PROPERTIES = [
"-webkit-perspective-origin-y",
"-webkit-margin-before-collapse",
"-webkit-border-before-style",
"scroll-snap-stop",
"-webkit-margin-bottom-collapse",
"-webkit-ruby-position",
"-webkit-column-break-after",

View file

@ -444,7 +444,6 @@ class Longhand(Property):
"ColumnCount",
"Contain",
"ContentVisibility",
"ContainerType",
"Display",
"FillRule",
"Float",
@ -492,7 +491,6 @@ class Longhand(Property):
"ScrollbarGutter",
"ScrollSnapAlign",
"ScrollSnapAxis",
"ScrollSnapStop",
"ScrollSnapStrictness",
"ScrollSnapType",
"TextAlign",

View file

@ -755,6 +755,7 @@ fn static_assert() {
<%self:impl_trait style_struct_name="Margin"
skip_longhands="${skip_margin_longhands}
${skip_scroll_margin_longhands}">
% for side in SIDES:
<% impl_split_style_coord("margin_%s" % side.ident,
"mMargin",
@ -1180,7 +1181,11 @@ fn static_assert() {
</%def>
<% skip_box_longhands= """display
clear
animation-name animation-delay animation-duration
animation-direction animation-fill-mode animation-play-state
animation-iteration-count animation-timeline animation-timing-function
clear transition-duration transition-delay
transition-timing-function transition-property
-webkit-line-clamp""" %>
<%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}">
#[inline]
@ -1222,6 +1227,245 @@ fn static_assert() {
) %>
${impl_keyword('clear', 'mBreakType', clear_keyword)}
${impl_transition_time_value('delay', 'Delay')}
${impl_transition_time_value('duration', 'Duration')}
${impl_animation_or_transition_timing_function('transition')}
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
+ self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
}
pub fn set_transition_property<I>(&mut self, v: I)
where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
let v = v.into_iter();
if v.len() != 0 {
self.gecko.mTransitions.ensure_len(v.len());
self.gecko.mTransitionPropertyCount = v.len() as u32;
for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
unsafe { gecko.mUnknownProperty.clear() };
match servo {
TransitionProperty::Unsupported(ident) => {
gecko.mProperty = eCSSProperty_UNKNOWN;
gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
},
TransitionProperty::Custom(name) => {
gecko.mProperty = eCSSPropertyExtra_variable;
gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
}
_ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
}
}
} else {
// In gecko |none| is represented by eCSSPropertyExtra_no_properties.
self.gecko.mTransitionPropertyCount = 1;
self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
}
}
/// Returns whether there are any transitions specified.
pub fn specifies_transitions(&self) -> bool {
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
if self.gecko.mTransitionPropertyCount == 1 &&
self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
self.transition_combined_duration_at(0) <= 0.0f32 {
return false;
}
self.gecko.mTransitionPropertyCount > 0
}
pub fn transition_property_at(&self, index: usize)
-> longhands::transition_property::computed_value::SingleComputedValue {
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
let property = self.gecko.mTransitions[index].mProperty;
if property == eCSSProperty_UNKNOWN {
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
debug_assert!(!atom.is_null());
TransitionProperty::Unsupported(CustomIdent(unsafe{
Atom::from_raw(atom)
}))
} else if property == eCSSPropertyExtra_variable {
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
debug_assert!(!atom.is_null());
TransitionProperty::Custom(unsafe{
Atom::from_raw(atom)
})
} else if property == eCSSPropertyExtra_no_properties {
// Actually, we don't expect TransitionProperty::Unsupported also
// represents "none", but if the caller wants to convert it, it is
// fine. Please use it carefully.
//
// FIXME(emilio): This is a hack, is this reachable?
TransitionProperty::Unsupported(CustomIdent(atom!("none")))
} else {
property.into()
}
}
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
self.gecko.mTransitions[index].mProperty
}
pub fn copy_transition_property_from(&mut self, other: &Self) {
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());
let count = other.gecko.mTransitionPropertyCount;
self.gecko.mTransitionPropertyCount = count;
for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
transition.mProperty = other.gecko.mTransitions[index].mProperty;
unsafe { transition.mUnknownProperty.clear() };
if transition.mProperty == eCSSProperty_UNKNOWN ||
transition.mProperty == eCSSPropertyExtra_variable {
let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
debug_assert!(!atom.is_null());
transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
}
}
}
pub fn reset_transition_property(&mut self, other: &Self) {
self.copy_transition_property_from(other)
}
${impl_transition_count('property', 'Property')}
pub fn animations_equals(&self, other: &Self) -> bool {
return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
&& self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
&& self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
&& self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
&& self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
&& self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
&& self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
&& self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
&& unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
}
pub fn set_animation_name<I>(&mut self, v: I)
where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
let v = v.into_iter();
debug_assert_ne!(v.len(), 0);
self.gecko.mAnimations.ensure_len(v.len());
self.gecko.mAnimationNameCount = v.len() as u32;
for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
let atom = match servo.0 {
None => atom!(""),
Some(ref name) => name.as_atom().clone(),
};
unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
}
}
pub fn animation_name_at(&self, index: usize)
-> longhands::animation_name::computed_value::SingleComputedValue {
use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
let atom = self.gecko.mAnimations[index].mName.mRawPtr;
if atom == atom!("").as_ptr() {
return AnimationName(None)
}
AnimationName(Some(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) })))
}
pub fn copy_animation_name_from(&mut self, other: &Self) {
self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
}
pub fn reset_animation_name(&mut self, other: &Self) {
self.copy_animation_name_from(other)
}
${impl_animation_count('name', 'Name')}
${impl_animation_time_value('delay', 'Delay')}
${impl_animation_time_value('duration', 'Duration')}
${impl_animation_keyword('direction', 'Direction',
data.longhands_by_name["animation-direction"].keyword)}
${impl_animation_keyword('fill_mode', 'FillMode',
data.longhands_by_name["animation-fill-mode"].keyword)}
${impl_animation_keyword('play_state', 'PlayState',
data.longhands_by_name["animation-play-state"].keyword)}
pub fn set_animation_iteration_count<I>(&mut self, v: I)
where
I: IntoIterator<Item = values::computed::AnimationIterationCount>,
I::IntoIter: ExactSizeIterator + Clone
{
use std::f32;
use crate::values::generics::box_::AnimationIterationCount;
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.mAnimationIterationCountCount = input_len as u32;
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
match servo {
AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
}
}
}
pub fn animation_iteration_count_at(
&self,
index: usize,
) -> values::computed::AnimationIterationCount {
use crate::values::generics::box_::AnimationIterationCount;
if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
AnimationIterationCount::Infinite
} else {
AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
}
}
${impl_animation_count('iteration_count', 'IterationCount')}
${impl_copy_animation_value('iteration_count', 'IterationCount')}
${impl_animation_or_transition_timing_function('animation')}
pub fn set_animation_timeline<I>(&mut self, v: I)
where
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
let v = v.into_iter();
debug_assert_ne!(v.len(), 0);
let input_len = v.len();
self.gecko.mAnimations.ensure_len(input_len);
self.gecko.mAnimationTimelineCount = input_len as u32;
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
gecko.mTimeline = servo;
}
}
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
self.gecko.mAnimations[index].mTimeline.clone()
}
${impl_animation_count('timeline', 'Timeline')}
${impl_copy_animation_value('timeline', 'Timeline')}
#[allow(non_snake_case)]
pub fn set__webkit_line_clamp(&mut self, v: longhands::_webkit_line_clamp::computed_value::T) {
self.gecko.mLineClamp = match v {
@ -1776,247 +2020,7 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask-
}
</%self:impl_trait>
<% skip_ui_longhands = """animation-name animation-delay animation-duration
animation-direction animation-fill-mode
animation-play-state animation-iteration-count
animation-timeline animation-timing-function
transition-duration transition-delay
transition-timing-function transition-property""" %>
<%self:impl_trait style_struct_name="UI" skip_longhands="${skip_ui_longhands}">
${impl_transition_time_value('delay', 'Delay')}
${impl_transition_time_value('duration', 'Duration')}
${impl_animation_or_transition_timing_function('transition')}
pub fn transition_combined_duration_at(&self, index: usize) -> f32 {
// https://drafts.csswg.org/css-transitions/#transition-combined-duration
self.gecko.mTransitions[index % self.gecko.mTransitionDurationCount as usize].mDuration.max(0.0)
+ self.gecko.mTransitions[index % self.gecko.mTransitionDelayCount as usize].mDelay
}
pub fn set_transition_property<I>(&mut self, v: I)
where I: IntoIterator<Item = longhands::transition_property::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
let v = v.into_iter();
if v.len() != 0 {
self.gecko.mTransitions.ensure_len(v.len());
self.gecko.mTransitionPropertyCount = v.len() as u32;
for (servo, gecko) in v.zip(self.gecko.mTransitions.iter_mut()) {
unsafe { gecko.mUnknownProperty.clear() };
match servo {
TransitionProperty::Unsupported(ident) => {
gecko.mProperty = eCSSProperty_UNKNOWN;
gecko.mUnknownProperty.mRawPtr = ident.0.into_addrefed();
},
TransitionProperty::Custom(name) => {
gecko.mProperty = eCSSPropertyExtra_variable;
gecko.mUnknownProperty.mRawPtr = name.into_addrefed();
}
_ => gecko.mProperty = servo.to_nscsspropertyid().unwrap(),
}
}
} else {
// In gecko |none| is represented by eCSSPropertyExtra_no_properties.
self.gecko.mTransitionPropertyCount = 1;
self.gecko.mTransitions[0].mProperty = eCSSPropertyExtra_no_properties;
}
}
/// Returns whether there are any transitions specified.
pub fn specifies_transitions(&self) -> bool {
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_all_properties;
if self.gecko.mTransitionPropertyCount == 1 &&
self.gecko.mTransitions[0].mProperty == eCSSPropertyExtra_all_properties &&
self.transition_combined_duration_at(0) <= 0.0f32 {
return false;
}
self.gecko.mTransitionPropertyCount > 0
}
pub fn transition_property_at(&self, index: usize)
-> longhands::transition_property::computed_value::SingleComputedValue {
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_no_properties;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
let property = self.gecko.mTransitions[index].mProperty;
if property == eCSSProperty_UNKNOWN {
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
debug_assert!(!atom.is_null());
TransitionProperty::Unsupported(CustomIdent(unsafe{
Atom::from_raw(atom)
}))
} else if property == eCSSPropertyExtra_variable {
let atom = self.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
debug_assert!(!atom.is_null());
TransitionProperty::Custom(unsafe{
Atom::from_raw(atom)
})
} else if property == eCSSPropertyExtra_no_properties {
// Actually, we don't expect TransitionProperty::Unsupported also
// represents "none", but if the caller wants to convert it, it is
// fine. Please use it carefully.
//
// FIXME(emilio): This is a hack, is this reachable?
TransitionProperty::Unsupported(CustomIdent(atom!("none")))
} else {
property.into()
}
}
pub fn transition_nscsspropertyid_at(&self, index: usize) -> nsCSSPropertyID {
self.gecko.mTransitions[index].mProperty
}
pub fn copy_transition_property_from(&mut self, other: &Self) {
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSPropertyExtra_variable;
use crate::gecko_bindings::structs::nsCSSPropertyID::eCSSProperty_UNKNOWN;
self.gecko.mTransitions.ensure_len(other.gecko.mTransitions.len());
let count = other.gecko.mTransitionPropertyCount;
self.gecko.mTransitionPropertyCount = count;
for (index, transition) in self.gecko.mTransitions.iter_mut().enumerate().take(count as usize) {
transition.mProperty = other.gecko.mTransitions[index].mProperty;
unsafe { transition.mUnknownProperty.clear() };
if transition.mProperty == eCSSProperty_UNKNOWN ||
transition.mProperty == eCSSPropertyExtra_variable {
let atom = other.gecko.mTransitions[index].mUnknownProperty.mRawPtr;
debug_assert!(!atom.is_null());
transition.mUnknownProperty.mRawPtr = unsafe { Atom::from_raw(atom) }.into_addrefed();
}
}
}
pub fn reset_transition_property(&mut self, other: &Self) {
self.copy_transition_property_from(other)
}
${impl_transition_count('property', 'Property')}
pub fn animations_equals(&self, other: &Self) -> bool {
return self.gecko.mAnimationNameCount == other.gecko.mAnimationNameCount
&& self.gecko.mAnimationDelayCount == other.gecko.mAnimationDelayCount
&& self.gecko.mAnimationDirectionCount == other.gecko.mAnimationDirectionCount
&& self.gecko.mAnimationDurationCount == other.gecko.mAnimationDurationCount
&& self.gecko.mAnimationFillModeCount == other.gecko.mAnimationFillModeCount
&& self.gecko.mAnimationIterationCountCount == other.gecko.mAnimationIterationCountCount
&& self.gecko.mAnimationPlayStateCount == other.gecko.mAnimationPlayStateCount
&& self.gecko.mAnimationTimingFunctionCount == other.gecko.mAnimationTimingFunctionCount
&& unsafe { bindings::Gecko_StyleAnimationsEquals(&self.gecko.mAnimations, &other.gecko.mAnimations) }
}
pub fn set_animation_name<I>(&mut self, v: I)
where I: IntoIterator<Item = longhands::animation_name::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
let v = v.into_iter();
debug_assert_ne!(v.len(), 0);
self.gecko.mAnimations.ensure_len(v.len());
self.gecko.mAnimationNameCount = v.len() as u32;
for (servo, gecko) in v.zip(self.gecko.mAnimations.iter_mut()) {
let atom = servo.0.as_atom().clone();
unsafe { bindings::Gecko_SetAnimationName(gecko, atom.into_addrefed()); }
}
}
pub fn animation_name_at(&self, index: usize)
-> longhands::animation_name::computed_value::SingleComputedValue {
use crate::properties::longhands::animation_name::single_value::SpecifiedValue as AnimationName;
let atom = self.gecko.mAnimations[index].mName.mRawPtr;
AnimationName(KeyframesName::from_atom(unsafe { Atom::from_raw(atom) }))
}
pub fn copy_animation_name_from(&mut self, other: &Self) {
self.gecko.mAnimationNameCount = other.gecko.mAnimationNameCount;
unsafe { bindings::Gecko_CopyAnimationNames(&mut self.gecko.mAnimations, &other.gecko.mAnimations); }
}
pub fn reset_animation_name(&mut self, other: &Self) {
self.copy_animation_name_from(other)
}
${impl_animation_count('name', 'Name')}
${impl_animation_time_value('delay', 'Delay')}
${impl_animation_time_value('duration', 'Duration')}
${impl_animation_keyword('direction', 'Direction',
data.longhands_by_name["animation-direction"].keyword)}
${impl_animation_keyword('fill_mode', 'FillMode',
data.longhands_by_name["animation-fill-mode"].keyword)}
${impl_animation_keyword('play_state', 'PlayState',
data.longhands_by_name["animation-play-state"].keyword)}
pub fn set_animation_iteration_count<I>(&mut self, v: I)
where
I: IntoIterator<Item = values::computed::AnimationIterationCount>,
I::IntoIter: ExactSizeIterator + Clone
{
use std::f32;
use crate::values::generics::box_::AnimationIterationCount;
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.mAnimationIterationCountCount = input_len as u32;
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
match servo {
AnimationIterationCount::Number(n) => gecko.mIterationCount = n,
AnimationIterationCount::Infinite => gecko.mIterationCount = f32::INFINITY,
}
}
}
pub fn animation_iteration_count_at(
&self,
index: usize,
) -> values::computed::AnimationIterationCount {
use crate::values::generics::box_::AnimationIterationCount;
if self.gecko.mAnimations[index].mIterationCount.is_infinite() {
AnimationIterationCount::Infinite
} else {
AnimationIterationCount::Number(self.gecko.mAnimations[index].mIterationCount)
}
}
${impl_animation_count('iteration_count', 'IterationCount')}
${impl_copy_animation_value('iteration_count', 'IterationCount')}
${impl_animation_or_transition_timing_function('animation')}
pub fn set_animation_timeline<I>(&mut self, v: I)
where
I: IntoIterator<Item = longhands::animation_timeline::computed_value::single_value::T>,
I::IntoIter: ExactSizeIterator
{
let v = v.into_iter();
debug_assert_ne!(v.len(), 0);
let input_len = v.len();
self.gecko.mAnimations.ensure_len(input_len);
self.gecko.mAnimationTimelineCount = input_len as u32;
for (gecko, servo) in self.gecko.mAnimations.iter_mut().take(input_len as usize).zip(v) {
gecko.mTimeline = servo;
}
}
pub fn animation_timeline_at(&self, index: usize) -> values::specified::box_::AnimationTimeline {
self.gecko.mAnimations[index].mTimeline.clone()
}
${impl_animation_count('timeline', 'Timeline')}
${impl_copy_animation_value('timeline', 'Timeline')}
<%self:impl_trait style_struct_name="UI">
</%self:impl_trait>
<%self:impl_trait style_struct_name="XUL">

View file

@ -799,7 +799,7 @@ impl<'a> TransitionPropertyIterator<'a> {
pub fn from_style(style: &'a ComputedValues) -> Self {
Self {
style,
index_range: 0..style.get_ui().transition_property_count(),
index_range: 0..style.get_box().transition_property_count(),
longhand_iterator: None,
}
}
@ -832,7 +832,7 @@ impl<'a> Iterator for TransitionPropertyIterator<'a> {
}
let index = self.index_range.next()?;
match self.style.get_ui().transition_property_at(index) {
match self.style.get_box().transition_property_at(index) {
TransitionProperty::Longhand(longhand_id) => {
return Some(TransitionPropertyIteration {
longhand_id,

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%>
<% from data import ALL_AXES, DEFAULT_RULES_EXCEPT_KEYFRAME, Keyword, Method, to_rust_ident, to_camel_case%>
<% data.new_style_struct("Box",
inherited=False,
@ -150,6 +150,193 @@ ${helpers.predefined_type(
animation_value_type="discrete",
)}
<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %>
${helpers.predefined_type(
"transition-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
${helpers.predefined_type(
"transition-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function",
)}
${helpers.predefined_type(
"transition-property",
"TransitionProperty",
"computed::TransitionProperty::all()",
engines="gecko servo",
initial_specified_value="specified::TransitionProperty::all()",
vector=True,
allow_empty="NotInitial",
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property",
)}
${helpers.predefined_type(
"transition-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay",
)}
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
${helpers.predefined_type(
"animation-name",
"AnimationName",
"computed::AnimationName::none()",
engines="gecko servo",
initial_specified_value="specified::AnimationName::none()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-name",
)}
${helpers.predefined_type(
"animation-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
// animation-timing-function is the exception to the rule for allowed_in_keyframe_block:
// https://drafts.csswg.org/css-animations/#keyframes
${helpers.predefined_type(
"animation-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function",
)}
${helpers.predefined_type(
"animation-iteration-count",
"AnimationIterationCount",
"computed::AnimationIterationCount::one()",
engines="gecko servo",
initial_specified_value="specified::AnimationIterationCount::one()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
)}
<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
${helpers.single_keyword(
"animation-direction",
"normal reverse alternate alternate-reverse",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="PlaybackDirection",
custom_consts=animation_direction_custom_consts,
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-play-state",
"running paused",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
extra_prefixes=animation_extra_prefixes,
gecko_enum_prefix="StyleAnimationPlayState",
spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-fill-mode",
"none forwards backwards both",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="FillMode",
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-timeline",
"AnimationTimeline",
"computed::AnimationTimeline::auto()",
engines="gecko servo",
servo_pref="layout.unimplemented",
initial_specified_value="specified::AnimationTimeline::auto()",
vector=True,
need_index=True,
animation_value_type="none",
gecko_pref="layout.css.scroll-linked-animations.enabled",
spec="https://drafts.csswg.org/css-animations-2/#propdef-animation-timeline",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
<% transform_extra_prefixes = "moz:layout.css.prefixes.transforms webkit" %>
${helpers.predefined_type(
@ -285,15 +472,6 @@ ${helpers.predefined_type(
animation_value_type="discrete",
)}
${helpers.predefined_type(
"scroll-snap-stop",
"ScrollSnapStop",
"computed::ScrollSnapStop::Normal",
engines="gecko",
spec="https://drafts.csswg.org/css-scroll-snap-1/#scroll-snap-stop",
animation_value_type="discrete",
)}
% for (axis, logical) in ALL_AXES:
${helpers.predefined_type(
"overscroll-behavior-" + axis,
@ -445,28 +623,6 @@ ${helpers.predefined_type(
animation_value_type="none",
)}
${helpers.predefined_type(
"container-type",
"ContainerType",
"computed::ContainerType::NONE",
engines="gecko servo",
animation_value_type="none",
gecko_pref="layout.css.container-queries.enabled",
servo_pref="layout.container-queries.enabled",
spec="https://drafts.csswg.org/css-contain-3/#container-type",
)}
${helpers.predefined_type(
"container-name",
"ContainerName",
"computed::ContainerName::none()",
engines="gecko servo",
animation_value_type="none",
gecko_pref="layout.css.container-queries.enabled",
servo_pref="layout.container-queries.enabled",
spec="https://drafts.csswg.org/css-contain-3/#container-name",
)}
${helpers.predefined_type(
"appearance",
"Appearance",

View file

@ -28,16 +28,6 @@
)}
% endfor
${helpers.predefined_type(
"overflow-clip-margin",
"Length",
"computed::Length::zero()",
parse_method="parse_non_negative",
engines="gecko",
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin",
animation_value_type="ComputedValue",
)}
% for side in ALL_SIDES:
${helpers.predefined_type(
"scroll-margin-%s" % side[0],

View file

@ -20,7 +20,7 @@ ${helpers.single_keyword(
${helpers.predefined_type(
"stop-color",
"Color",
"computed::Color::black()",
"RGBA::new(0, 0, 0, 255).into()",
engines="gecko",
animation_value_type="AnimatedRGBA",
spec="https://www.w3.org/TR/SVGTiny12/painting.html#StopColorProperty",
@ -40,7 +40,7 @@ ${helpers.predefined_type(
${helpers.predefined_type(
"flood-color",
"Color",
"computed::Color::black()",
"RGBA::new(0, 0, 0, 255).into()",
engines="gecko",
animation_value_type="AnimatedColor",
spec="https://www.w3.org/TR/SVG/filters.html#FloodColorProperty",
@ -58,7 +58,7 @@ ${helpers.predefined_type(
${helpers.predefined_type(
"lighting-color",
"Color",
"computed::Color::white()",
"RGBA::new(255, 255, 255, 255).into()",
engines="gecko",
animation_value_type="AnimatedColor",
spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty",

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
<%namespace name="helpers" file="/helpers.mako.rs" />
<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %>
<% from data import Method %>
// CSS Basic User Interface Module Level 1
// https://drafts.csswg.org/css-ui-3/
@ -51,15 +51,12 @@ ${helpers.single_keyword(
spec="None (Nonstandard Firefox-only property)",
)}
// TODO(emilio): Maybe make shadow behavior on macOS match Linux / Windows, and remove this
// property.
${helpers.single_keyword(
"-moz-window-shadow",
"default none",
"default none menu tooltip sheet cliprounded",
engines="gecko",
gecko_ffi_name="mWindowShadow",
gecko_enum_prefix="StyleWindowShadow",
gecko_inexhaustive=True,
animation_value_type="discrete",
enabled_in="chrome",
spec="None (Nonstandard internal property)",
@ -98,16 +95,6 @@ ${helpers.predefined_type(
enabled_in="chrome",
)}
${helpers.predefined_type(
"-moz-window-input-region-margin",
"Length",
"computed::Length::zero()",
engines="gecko",
animation_value_type="ComputedValue",
spec="None (Nonstandard internal property)",
enabled_in="chrome",
)}
// TODO(emilio): Probably also should be hidden from content.
${helpers.predefined_type(
"-moz-force-broken-image-icon",
@ -117,190 +104,3 @@ ${helpers.predefined_type(
animation_value_type="discrete",
spec="None (Nonstandard Firefox-only property)",
)}
<% transition_extra_prefixes = "moz:layout.css.prefixes.transitions webkit" %>
${helpers.predefined_type(
"transition-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
${helpers.predefined_type(
"transition-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-timing-function",
)}
${helpers.predefined_type(
"transition-property",
"TransitionProperty",
"computed::TransitionProperty::all()",
engines="gecko servo",
initial_specified_value="specified::TransitionProperty::all()",
vector=True,
allow_empty="NotInitial",
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-property",
)}
${helpers.predefined_type(
"transition-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=transition_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-delay",
)}
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
${helpers.predefined_type(
"animation-name",
"AnimationName",
"computed::AnimationName::none()",
engines="gecko servo",
initial_specified_value="specified::AnimationName::none()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-name",
)}
${helpers.predefined_type(
"animation-duration",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
parse_method="parse_non_negative",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-transition-duration",
)}
// animation-timing-function is the exception to the rule for allowed_in_keyframe_block:
// https://drafts.csswg.org/css-animations/#keyframes
${helpers.predefined_type(
"animation-timing-function",
"TimingFunction",
"computed::TimingFunction::ease()",
engines="gecko servo",
initial_specified_value="specified::TimingFunction::ease()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-transitions/#propdef-animation-timing-function",
)}
${helpers.predefined_type(
"animation-iteration-count",
"AnimationIterationCount",
"computed::AnimationIterationCount::one()",
engines="gecko servo",
initial_specified_value="specified::AnimationIterationCount::one()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-iteration-count",
)}
<% animation_direction_custom_consts = { "alternate-reverse": "Alternate_reverse" } %>
${helpers.single_keyword(
"animation-direction",
"normal reverse alternate alternate-reverse",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="PlaybackDirection",
custom_consts=animation_direction_custom_consts,
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-direction",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-play-state",
"running paused",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
extra_prefixes=animation_extra_prefixes,
gecko_enum_prefix="StyleAnimationPlayState",
spec="https://drafts.csswg.org/css-animations/#propdef-animation-play-state",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.single_keyword(
"animation-fill-mode",
"none forwards backwards both",
engines="gecko servo",
need_index=True,
animation_value_type="none",
vector=True,
gecko_enum_prefix="FillMode",
extra_prefixes=animation_extra_prefixes,
gecko_inexhaustive=True,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-fill-mode",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-delay",
"Time",
"computed::Time::zero()",
engines="gecko servo",
initial_specified_value="specified::Time::zero()",
vector=True,
need_index=True,
animation_value_type="none",
extra_prefixes=animation_extra_prefixes,
spec="https://drafts.csswg.org/css-animations/#propdef-animation-delay",
rule_types_allowed=DEFAULT_RULES_EXCEPT_KEYFRAME,
)}
${helpers.predefined_type(
"animation-timeline",
"AnimationTimeline",
"computed::AnimationTimeline::auto()",
engines="gecko servo",
servo_pref="layout.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,
)}

View file

@ -267,9 +267,6 @@ pub enum PropertyDeclaration {
% endfor
}
// There's one of these for each parsed declaration so it better be small.
size_of_test!(PropertyDeclaration, 32);
#[repr(C)]
struct PropertyDeclarationVariantRepr<T> {
tag: u16,
@ -477,10 +474,9 @@ impl NonCustomPropertyId {
self.0
}
/// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`.
#[cfg(feature = "gecko")]
#[inline]
pub fn to_nscsspropertyid(self) -> nsCSSPropertyID {
fn to_nscsspropertyid(self) -> nsCSSPropertyID {
// unsafe: guaranteed by static_assert_nscsspropertyid above.
unsafe { std::mem::transmute(self.0 as i32) }
}
@ -896,72 +892,6 @@ impl<'a> Iterator for LonghandIdSetIterator<'a> {
}
}
<%
CASCADE_GROUPS = {
# The writing-mode group has the most priority of all property groups, as
# sizes like font-size can depend on it.
"writing_mode": [
"writing-mode",
"direction",
"text-orientation",
],
# The fonts and colors group has the second priority, as all other lengths
# and colors depend on them.
#
# There are some interdependencies between these, but we fix them up in
# Cascade::fixup_font_stuff.
"fonts_and_color": [
# Needed to properly compute the zoomed font-size.
# FIXME(emilio): This could probably just be a cascade flag
# like IN_SVG_SUBTREE or such, and we could nuke this property.
"-x-text-zoom",
# Needed to do font-size computation in a language-dependent way.
"-x-lang",
# Needed for ruby to respect language-dependent min-font-size
# preferences properly, see bug 1165538.
"-moz-min-font-size-ratio",
# font-size depends on math-depth's computed value.
"math-depth",
# Needed to compute the first available font, in order to
# compute font-relative units correctly.
"font-size",
"font-weight",
"font-stretch",
"font-style",
"font-family",
# color-scheme affects how system colors resolve.
"color-scheme",
],
}
def in_late_group(p):
return p.name not in CASCADE_GROUPS["writing_mode"] and p.name not in CASCADE_GROUPS["fonts_and_color"]
def is_visited_dependent(p):
return p.name in [
"column-rule-color",
"text-emphasis-color",
"-webkit-text-fill-color",
"-webkit-text-stroke-color",
"text-decoration-color",
"fill",
"stroke",
"caret-color",
"background-color",
"border-top-color",
"border-right-color",
"border-bottom-color",
"border-left-color",
"border-block-start-color",
"border-inline-end-color",
"border-block-end-color",
"border-inline-start-color",
"outline-color",
"color",
]
%>
impl LonghandIdSet {
#[inline]
fn reset() -> &'static Self {
@ -990,7 +920,7 @@ impl LonghandIdSet {
/// Returns the set of longhands that are ignored when document colors are
/// disabled.
#[inline]
fn ignored_when_colors_disabled() -> &'static Self {
pub fn ignored_when_colors_disabled() -> &'static Self {
${static_longhand_id_set(
"IGNORED_WHEN_COLORS_DISABLED",
lambda p: p.ignored_when_colors_disabled
@ -998,48 +928,6 @@ impl LonghandIdSet {
&IGNORED_WHEN_COLORS_DISABLED
}
/// Only a few properties are allowed to depend on the visited state of
/// links. When cascading visited styles, we can save time by only
/// processing these properties.
fn visited_dependent() -> &'static Self {
${static_longhand_id_set(
"VISITED_DEPENDENT",
lambda p: is_visited_dependent(p)
)}
debug_assert!(Self::late_group().contains_all(&VISITED_DEPENDENT));
&VISITED_DEPENDENT
}
#[inline]
fn writing_mode_group() -> &'static Self {
${static_longhand_id_set(
"WRITING_MODE_GROUP",
lambda p: p.name in CASCADE_GROUPS["writing_mode"]
)}
&WRITING_MODE_GROUP
}
#[inline]
fn fonts_and_color_group() -> &'static Self {
${static_longhand_id_set(
"FONTS_AND_COLOR_GROUP",
lambda p: p.name in CASCADE_GROUPS["fonts_and_color"]
)}
&FONTS_AND_COLOR_GROUP
}
#[inline]
fn late_group_only_inherited() -> &'static Self {
${static_longhand_id_set("LATE_GROUP_ONLY_INHERITED", lambda p: p.style_struct.inherited and in_late_group(p))}
&LATE_GROUP_ONLY_INHERITED
}
#[inline]
fn late_group() -> &'static Self {
${static_longhand_id_set("LATE_GROUP", lambda p: in_late_group(p))}
&LATE_GROUP
}
/// Returns the set of properties that are declared as having no effect on
/// Gecko <scrollbar> elements or their descendant scrollbar parts.
#[cfg(debug_assertions)]
@ -1442,12 +1330,91 @@ impl LonghandId {
PropertyFlags::from_bits_truncate(FLAGS[self as usize])
}
/// Only a few properties are allowed to depend on the visited state of
/// links. When cascading visited styles, we can save time by only
/// processing these properties.
fn is_visited_dependent(&self) -> bool {
matches!(*self,
% if engine == "gecko":
LonghandId::ColumnRuleColor |
LonghandId::TextEmphasisColor |
LonghandId::WebkitTextFillColor |
LonghandId::WebkitTextStrokeColor |
LonghandId::TextDecorationColor |
LonghandId::Fill |
LonghandId::Stroke |
LonghandId::CaretColor |
% endif
LonghandId::BackgroundColor |
LonghandId::BorderTopColor |
LonghandId::BorderRightColor |
LonghandId::BorderBottomColor |
LonghandId::BorderLeftColor |
LonghandId::OutlineColor |
LonghandId::Color
)
}
/// Returns true if the property is one that is ignored when document
/// colors are disabled.
#[inline]
fn ignored_when_document_colors_disabled(self) -> bool {
LonghandIdSet::ignored_when_colors_disabled().contains(self)
}
/// The computed value of some properties depends on the (sometimes
/// computed) value of *other* properties.
///
/// So we classify properties into "early" and "other", such that the only
/// dependencies can be from "other" to "early".
///
/// Unfortunately, its not easy to check that this classification is
/// correct.
fn is_early_property(&self) -> bool {
matches!(*self,
% if engine == "gecko":
// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff. In this block instead of along
// `WritingMode` and `Direction` just for convenience, since it's
// Gecko-only (for now at least).
//
// see WritingMode::new.
LonghandId::TextOrientation |
// Needed to properly compute the zoomed font-size.
//
// FIXME(emilio): This could probably just be a cascade flag like
// IN_SVG_SUBTREE or such, and we could nuke this property.
LonghandId::XTextZoom |
// Needed to do font-size computation in a language-dependent way.
LonghandId::XLang |
// Needed for ruby to respect language-dependent min-font-size
// preferences properly, see bug 1165538.
LonghandId::MozMinFontSizeRatio |
// font-size depends on math-depth's computed value.
LonghandId::MathDepth |
// color-scheme affects how system colors resolve.
LonghandId::ColorScheme |
% endif
// Needed to compute the first available font, in order to
// compute font-relative units correctly.
LonghandId::FontSize |
LonghandId::FontWeight |
LonghandId::FontStretch |
LonghandId::FontStyle |
LonghandId::FontFamily |
// Needed to properly compute the writing mode, to resolve logical
// properties, and similar stuff.
LonghandId::WritingMode |
LonghandId::Direction
)
}
}
/// An iterator over all the property ids that are enabled for a given
@ -2593,11 +2560,9 @@ impl PropertyDeclaration {
}
}
const SUB_PROPERTIES_ARRAY_CAP: usize =
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
if data.shorthands_except_all() else 0};
type SubpropertiesVec<T> = ArrayVec<T, SUB_PROPERTIES_ARRAY_CAP>;
type SubpropertiesVec<T> = ArrayVec<T, ${max(len(s.sub_properties) \
for s in data.shorthands_except_all()) \
if data.shorthands_except_all() else 0}>;
/// A stack-allocated vector of `PropertyDeclaration`
/// large enough to parse one CSS `key: value` declaration.
@ -2609,10 +2574,6 @@ pub struct SourcePropertyDeclaration {
all_shorthand: AllShorthand,
}
// This is huge, but we allocate it on the stack and then never move it,
// we only pass `&mut SourcePropertyDeclaration` references around.
size_of_test!(SourcePropertyDeclaration, 568);
impl SourcePropertyDeclaration {
/// Create one. Its big, try not to move it around.
#[inline]
@ -2657,7 +2618,11 @@ impl SourcePropertyDeclaration {
/// Return type of SourcePropertyDeclaration::drain
pub struct SourcePropertyDeclarationDrain<'a> {
declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>,
declarations: ArrayVecDrain<
'a, PropertyDeclaration,
${max(len(s.sub_properties) for s in data.shorthands_except_all()) \
if data.shorthands_except_all() else 0}
>,
all_shorthand: AllShorthand,
}
@ -2938,11 +2903,11 @@ pub mod style_structs {
% endif
% endfor
% if style_struct.name == "UI":
% if style_struct.name == "Box":
/// Returns whether there is any animation specified with
/// animation-name other than `none`.
pub fn specifies_animations(&self) -> bool {
self.animation_name_iter().any(|name| !name.is_none())
self.animation_name_iter().any(|name| name.0.is_some())
}
/// Returns whether there are any transitions specified.
@ -3070,7 +3035,7 @@ impl ComputedValues {
/// Returns whether this style's display value is equal to contents.
pub fn is_display_contents(&self) -> bool {
self.clone_display().is_contents()
self.get_box().clone_display().is_contents()
}
/// Gets a reference to the rule node. Panic if no rule node exists.
@ -3219,7 +3184,7 @@ impl ComputedValues {
/// style.resolve_color(style.get_border().clone_border_top_color());
#[inline]
pub fn resolve_color(&self, color: computed::Color) -> RGBA {
color.into_rgba(self.get_inherited_text().clone_color())
color.to_rgba(self.get_inherited_text().clone_color())
}
/// Returns which longhand properties have different values in the two
@ -4223,7 +4188,7 @@ macro_rules! css_properties_accessors {
/// Call the given macro with tokens like this for each longhand properties:
///
/// ```
/// { snake_case_ident }
/// { snake_case_ident, true }
/// ```
///
/// … where the boolean indicates whether the property value type
@ -4233,39 +4198,12 @@ macro_rules! longhand_properties_idents {
($macro_name: ident) => {
$macro_name! {
% for property in data.longhands:
{ ${property.ident} }
{ ${property.ident}, ${"true" if property.boxed else "false"} }
% endfor
}
}
}
// Large pages generate tens of thousands of ComputedValues.
size_of_test!(ComputedValues, 192);
// FFI relies on this.
size_of_test!(Option<Arc<ComputedValues>>, 8);
// There are two reasons for this test to fail:
//
// * Your changes made a specified value type for a given property go
// over the threshold. In that case, you should try to shrink it again
// or, if not possible, mark the property as boxed in the property
// definition.
//
// * Your changes made a specified value type smaller, so that it no
// longer needs to be boxed. In this case you just need to remove
// boxed=True from the property definition. Nice job!
#[cfg(target_pointer_width = "64")]
#[allow(dead_code)] // https://github.com/rust-lang/rust/issues/96952
const BOX_THRESHOLD: usize = 24;
% for longhand in data.longhands:
#[cfg(target_pointer_width = "64")]
% if longhand.boxed:
const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() > BOX_THRESHOLD);
% else:
const_assert!(std::mem::size_of::<longhands::${longhand.ident}::SpecifiedValue>() <= BOX_THRESHOLD);
% endif
% endfor
% if engine == "servo":
% for effect_name in ["repaint", "reflow_out_of_flow", "reflow", "rebuild_and_reflow_inline", "rebuild_and_reflow"]:
macro_rules! restyle_damage_${effect_name} {

View file

@ -24,6 +24,318 @@ ${helpers.two_properties_shorthand(
"(https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box)",
)}
macro_rules! try_parse_one {
($context: expr, $input: expr, $var: ident, $prop_module: ident) => {
if $var.is_none() {
if let Ok(value) = $input.try_parse(|i| {
$prop_module::single_value::parse($context, i)
}) {
$var = Some(value);
continue;
}
}
};
}
<%helpers:shorthand name="transition"
engines="gecko servo"
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
sub_properties="transition-property transition-duration
transition-timing-function
transition-delay"
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
use crate::parser::Parse;
% for prop in "delay duration property timing_function".split():
use crate::properties::longhands::transition_${prop};
% endfor
use crate::values::specified::TransitionProperty;
pub fn parse_value<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Longhands, ParseError<'i>> {
struct SingleTransition {
% for prop in "duration timing_function delay".split():
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
% endfor
// Unlike other properties, transition-property uses an Option<> to
// represent 'none' as `None`.
transition_property: Option<TransitionProperty>,
}
fn parse_one_transition<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<SingleTransition,ParseError<'i>> {
% for prop in "property duration timing_function delay".split():
let mut ${prop} = None;
% endfor
let mut parsed = 0;
loop {
parsed += 1;
try_parse_one!(context, input, duration, transition_duration);
try_parse_one!(context, input, timing_function, transition_timing_function);
try_parse_one!(context, input, delay, transition_delay);
// Must check 'transition-property' after 'transition-timing-function' since
// 'transition-property' accepts any keyword.
if property.is_none() {
if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
property = Some(Some(value));
continue;
}
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
// 'none' is not a valid value for <single-transition-property>,
// so it's not acceptable in the function above.
property = Some(None);
continue;
}
}
parsed -= 1;
break
}
if parsed != 0 {
Ok(SingleTransition {
% for prop in "duration timing_function delay".split():
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
::get_initial_specified_value),
% endfor
transition_property: property.unwrap_or(
Some(transition_property::single_value::get_initial_specified_value())),
})
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
% for prop in "property duration timing_function delay".split():
let mut ${prop}s = Vec::new();
% endfor
let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
let multiple_items = results.len() >= 2;
for result in results {
if let Some(value) = result.transition_property {
propertys.push(value);
} else if multiple_items {
// If there is more than one item, and any of transitions has 'none',
// then it's invalid. Othersize, leave propertys to be empty (which
// means "transition-property: none");
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
% for prop in "duration timing_function delay".split():
${prop}s.push(result.transition_${prop});
% endfor
}
Ok(expanded! {
% for prop in "property duration timing_function delay".split():
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
% endfor
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
let property_len = self.transition_property.0.len();
// There are two cases that we can do shorthand serialization:
// * when all value lists have the same length, or
// * when transition-property is none, and other value lists have exactly one item.
if property_len == 0 {
% for name in "duration delay timing_function".split():
if self.transition_${name}.0.len() != 1 {
return Ok(());
}
% endfor
} else {
% for name in "duration delay timing_function".split():
if self.transition_${name}.0.len() != property_len {
return Ok(());
}
% endfor
}
// Representative length.
let len = self.transition_duration.0.len();
for i in 0..len {
if i != 0 {
dest.write_str(", ")?;
}
if property_len == 0 {
dest.write_str("none")?;
} else {
self.transition_property.0[i].to_css(dest)?;
}
% for name in "duration timing_function delay".split():
dest.write_str(" ")?;
self.transition_${name}.0[i].to_css(dest)?;
% endfor
}
Ok(())
}
}
</%helpers:shorthand>
<%helpers:shorthand name="animation"
engines="gecko servo"
extra_prefixes="moz:layout.css.prefixes.animations webkit"
sub_properties="animation-name animation-duration
animation-timing-function animation-delay
animation-iteration-count animation-direction
animation-fill-mode animation-play-state animation-timeline"
rule_types_allowed="Style"
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
<%
props = "name timeline duration timing_function delay iteration_count \
direction fill_mode play_state".split()
%>
% for prop in props:
use crate::properties::longhands::animation_${prop};
% endfor
pub fn parse_value<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Longhands, ParseError<'i>> {
struct SingleAnimation {
% for prop in props:
animation_${prop}: animation_${prop}::SingleSpecifiedValue,
% endfor
}
fn scroll_linked_animations_enabled() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
#[cfg(feature = "servo")]
return false;
}
fn parse_one_animation<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<SingleAnimation, ParseError<'i>> {
% for prop in props:
let mut ${prop} = None;
% endfor
let mut parsed = 0;
// NB: Name must be the last one here so that keywords valid for other
// longhands are not interpreted as names.
//
// Also, duration must be before delay, see
// https://drafts.csswg.org/css-animations/#typedef-single-animation
loop {
parsed += 1;
try_parse_one!(context, input, duration, animation_duration);
try_parse_one!(context, input, timing_function, animation_timing_function);
try_parse_one!(context, input, delay, animation_delay);
try_parse_one!(context, input, iteration_count, animation_iteration_count);
try_parse_one!(context, input, direction, animation_direction);
try_parse_one!(context, input, fill_mode, animation_fill_mode);
try_parse_one!(context, input, play_state, animation_play_state);
try_parse_one!(context, input, name, animation_name);
if scroll_linked_animations_enabled() {
try_parse_one!(context, input, timeline, animation_timeline);
}
parsed -= 1;
break
}
// If nothing is parsed, this is an invalid entry.
if parsed == 0 {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(SingleAnimation {
% for prop in props:
animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
::get_initial_specified_value),
% endfor
})
}
}
% for prop in props:
let mut ${prop}s = vec![];
% endfor
let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
for result in results.into_iter() {
% for prop in props:
${prop}s.push(result.animation_${prop});
% endfor
}
Ok(expanded! {
% for prop in props:
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()),
% endfor
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
let len = self.animation_name.0.len();
// There should be at least one declared value
if len == 0 {
return Ok(());
}
// If any value list length is differs then we don't do a shorthand serialization
// either.
% for name in props[2:]:
if len != self.animation_${name}.0.len() {
return Ok(())
}
% endfor
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
// None.
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
return Ok(());
}
for i in 0..len {
if i != 0 {
dest.write_str(", ")?;
}
% for name in props[2:]:
self.animation_${name}.0[i].to_css(dest)?;
dest.write_str(" ")?;
% endfor
self.animation_name.0[i].to_css(dest)?;
// Based on the spec, the default values of other properties must be output in at
// least the cases necessary to distinguish an animation-name. The serialization
// order of animation-timeline is always later than animation-name, so it's fine
// to not serialize it if it is the default value. It's still possible to
// distinguish them (because we always serialize animation-name).
// https://drafts.csswg.org/css-animations-1/#animation
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
//
// Note: it's also fine to always serialize this. However, it seems Blink
// doesn't serialize default animation-timeline now, so we follow the same rule.
if let Some(ref timeline) = self.animation_timeline {
if !timeline.0[i].is_auto() {
dest.write_char(' ')?;
timeline.0[i].to_css(dest)?;
}
}
}
Ok(())
}
}
</%helpers:shorthand>
${helpers.two_properties_shorthand(
"overscroll-behavior",
"overscroll-behavior-x",
@ -33,45 +345,6 @@ ${helpers.two_properties_shorthand(
spec="https://wicg.github.io/overscroll-behavior/#overscroll-behavior-properties",
)}
<%helpers:shorthand
engines="gecko"
name="container"
sub_properties="container-name container-type"
gecko_pref="layout.css.container-queries.enabled",
spec="https://drafts.csswg.org/css-contain-3/#container-shorthand"
>
pub fn parse_value<'i>(
context: &ParserContext,
input: &mut Parser<'i, '_>,
) -> Result<Longhands, ParseError<'i>> {
use crate::parser::Parse;
use crate::values::specified::box_::{ContainerName, ContainerType};
// See https://github.com/w3c/csswg-drafts/issues/7180 for why we don't
// match the spec.
let container_name = ContainerName::parse(context, input)?;
let container_type = if input.try_parse(|input| input.expect_delim('/')).is_ok() {
ContainerType::parse(context, input)?
} else {
ContainerType::NONE
};
Ok(expanded! {
container_name: container_name,
container_type: container_type,
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
self.container_name.to_css(dest)?;
if !self.container_type.is_empty() {
dest.write_str(" / ")?;
self.container_type.to_css(dest)?;
}
Ok(())
}
}
</%helpers:shorthand>
<%helpers:shorthand
engines="gecko"
name="page-break-before"

View file

@ -1,317 +0,0 @@
/* 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/. */
<%namespace name="helpers" file="/helpers.mako.rs" />
macro_rules! try_parse_one {
($context: expr, $input: expr, $var: ident, $prop_module: ident) => {
if $var.is_none() {
if let Ok(value) = $input.try_parse(|i| {
$prop_module::single_value::parse($context, i)
}) {
$var = Some(value);
continue;
}
}
};
}
<%helpers:shorthand name="transition"
engines="gecko servo"
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
sub_properties="transition-property transition-duration
transition-timing-function
transition-delay"
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
use crate::parser::Parse;
% for prop in "delay duration property timing_function".split():
use crate::properties::longhands::transition_${prop};
% endfor
use crate::values::specified::TransitionProperty;
pub fn parse_value<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Longhands, ParseError<'i>> {
struct SingleTransition {
% for prop in "duration timing_function delay".split():
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
% endfor
// Unlike other properties, transition-property uses an Option<> to
// represent 'none' as `None`.
transition_property: Option<TransitionProperty>,
}
fn parse_one_transition<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<SingleTransition,ParseError<'i>> {
% for prop in "property duration timing_function delay".split():
let mut ${prop} = None;
% endfor
let mut parsed = 0;
loop {
parsed += 1;
try_parse_one!(context, input, duration, transition_duration);
try_parse_one!(context, input, timing_function, transition_timing_function);
try_parse_one!(context, input, delay, transition_delay);
// Must check 'transition-property' after 'transition-timing-function' since
// 'transition-property' accepts any keyword.
if property.is_none() {
if let Ok(value) = input.try_parse(|i| TransitionProperty::parse(context, i)) {
property = Some(Some(value));
continue;
}
if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() {
// 'none' is not a valid value for <single-transition-property>,
// so it's not acceptable in the function above.
property = Some(None);
continue;
}
}
parsed -= 1;
break
}
if parsed != 0 {
Ok(SingleTransition {
% for prop in "duration timing_function delay".split():
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
::get_initial_specified_value),
% endfor
transition_property: property.unwrap_or(
Some(transition_property::single_value::get_initial_specified_value())),
})
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
% for prop in "property duration timing_function delay".split():
let mut ${prop}s = Vec::new();
% endfor
let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
let multiple_items = results.len() >= 2;
for result in results {
if let Some(value) = result.transition_property {
propertys.push(value);
} else if multiple_items {
// If there is more than one item, and any of transitions has 'none',
// then it's invalid. Othersize, leave propertys to be empty (which
// means "transition-property: none");
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
% for prop in "duration timing_function delay".split():
${prop}s.push(result.transition_${prop});
% endfor
}
Ok(expanded! {
% for prop in "property duration timing_function delay".split():
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
% endfor
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
let property_len = self.transition_property.0.len();
// There are two cases that we can do shorthand serialization:
// * when all value lists have the same length, or
// * when transition-property is none, and other value lists have exactly one item.
if property_len == 0 {
% for name in "duration delay timing_function".split():
if self.transition_${name}.0.len() != 1 {
return Ok(());
}
% endfor
} else {
% for name in "duration delay timing_function".split():
if self.transition_${name}.0.len() != property_len {
return Ok(());
}
% endfor
}
// Representative length.
let len = self.transition_duration.0.len();
for i in 0..len {
if i != 0 {
dest.write_str(", ")?;
}
if property_len == 0 {
dest.write_str("none")?;
} else {
self.transition_property.0[i].to_css(dest)?;
}
% for name in "duration timing_function delay".split():
dest.write_str(" ")?;
self.transition_${name}.0[i].to_css(dest)?;
% endfor
}
Ok(())
}
}
</%helpers:shorthand>
<%helpers:shorthand name="animation"
engines="gecko servo"
extra_prefixes="moz:layout.css.prefixes.animations webkit"
sub_properties="animation-name animation-duration
animation-timing-function animation-delay
animation-iteration-count animation-direction
animation-fill-mode animation-play-state animation-timeline"
rule_types_allowed="Style"
spec="https://drafts.csswg.org/css-animations/#propdef-animation">
<%
props = "name timeline duration timing_function delay iteration_count \
direction fill_mode play_state".split()
%>
% for prop in props:
use crate::properties::longhands::animation_${prop};
% endfor
pub fn parse_value<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Longhands, ParseError<'i>> {
struct SingleAnimation {
% for prop in props:
animation_${prop}: animation_${prop}::SingleSpecifiedValue,
% endfor
}
fn scroll_linked_animations_enabled() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.scroll-linked-animations.enabled");
#[cfg(feature = "servo")]
return false;
}
fn parse_one_animation<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<SingleAnimation, ParseError<'i>> {
% for prop in props:
let mut ${prop} = None;
% endfor
let mut parsed = 0;
// NB: Name must be the last one here so that keywords valid for other
// longhands are not interpreted as names.
//
// Also, duration must be before delay, see
// https://drafts.csswg.org/css-animations/#typedef-single-animation
loop {
parsed += 1;
try_parse_one!(context, input, duration, animation_duration);
try_parse_one!(context, input, timing_function, animation_timing_function);
try_parse_one!(context, input, delay, animation_delay);
try_parse_one!(context, input, iteration_count, animation_iteration_count);
try_parse_one!(context, input, direction, animation_direction);
try_parse_one!(context, input, fill_mode, animation_fill_mode);
try_parse_one!(context, input, play_state, animation_play_state);
try_parse_one!(context, input, name, animation_name);
if scroll_linked_animations_enabled() {
try_parse_one!(context, input, timeline, animation_timeline);
}
parsed -= 1;
break
}
// If nothing is parsed, this is an invalid entry.
if parsed == 0 {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(SingleAnimation {
% for prop in props:
animation_${prop}: ${prop}.unwrap_or_else(animation_${prop}::single_value
::get_initial_specified_value),
% endfor
})
}
}
% for prop in props:
let mut ${prop}s = vec![];
% endfor
let results = input.parse_comma_separated(|i| parse_one_animation(context, i))?;
for result in results.into_iter() {
% for prop in props:
${prop}s.push(result.animation_${prop});
% endfor
}
Ok(expanded! {
% for prop in props:
animation_${prop}: animation_${prop}::SpecifiedValue(${prop}s.into()),
% endfor
})
}
impl<'a> ToCss for LonghandsToSerialize<'a> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write {
let len = self.animation_name.0.len();
// There should be at least one declared value
if len == 0 {
return Ok(());
}
// If any value list length is differs then we don't do a shorthand serialization
// either.
% for name in props[2:]:
if len != self.animation_${name}.0.len() {
return Ok(())
}
% endfor
// If the preference of animation-timeline is disabled, `self.animation_timeline` is
// None.
if self.animation_timeline.map_or(false, |v| len != v.0.len()) {
return Ok(());
}
for i in 0..len {
if i != 0 {
dest.write_str(", ")?;
}
% for name in props[2:]:
self.animation_${name}.0[i].to_css(dest)?;
dest.write_str(" ")?;
% endfor
self.animation_name.0[i].to_css(dest)?;
// Based on the spec, the default values of other properties must be output in at
// least the cases necessary to distinguish an animation-name. The serialization
// order of animation-timeline is always later than animation-name, so it's fine
// to not serialize it if it is the default value. It's still possible to
// distinguish them (because we always serialize animation-name).
// https://drafts.csswg.org/css-animations-1/#animation
// https://drafts.csswg.org/css-animations-2/#typedef-single-animation
//
// Note: it's also fine to always serialize this. However, it seems Blink
// doesn't serialize default animation-timeline now, so we follow the same rule.
if let Some(ref timeline) = self.animation_timeline {
if !timeline.0[i].is_auto() {
dest.write_char(' ')?;
timeline.0[i].to_css(dest)?;
}
}
}
Ok(())
}
}
</%helpers:shorthand>

View file

@ -1,711 +0,0 @@
/* 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/. */
//! Parsing for query feature expressions, like `(foo: bar)` or
//! `(width >= 400px)`.
use super::feature::{Evaluator, QueryFeatureDescription};
use super::feature::{KeywordDiscriminant, FeatureFlags};
use crate::parser::{Parse, ParserContext};
use crate::str::{starts_with_ignore_ascii_case, string_as_ascii_lowercase};
use crate::values::computed::{self, Ratio, ToComputedValue};
use crate::values::specified::{Integer, Length, Number, Resolution};
use crate::values::CSSFloat;
use crate::{Atom, Zero};
use cssparser::{Parser, Token};
use std::cmp::{Ordering, PartialOrd};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// Whether we're parsing a media or container query feature.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
pub enum FeatureType {
/// We're parsing a media feature.
Media,
/// We're parsing a container feature.
Container,
}
impl FeatureType {
fn features(&self) -> &'static [QueryFeatureDescription] {
#[cfg(feature = "gecko")]
let media_features = &crate::gecko::media_features::MEDIA_FEATURES;
#[cfg(feature = "servo")]
let media_features = &*crate::servo::media_queries::MEDIA_FEATURES;
use crate::stylesheets::container_rule::CONTAINER_FEATURES;
match *self {
FeatureType::Media => media_features,
FeatureType::Container => &CONTAINER_FEATURES,
}
}
fn find_feature(&self, name: &Atom) -> Option<(usize, &'static QueryFeatureDescription)> {
self.features().iter().enumerate().find(|(_, f)| f.name == *name)
}
}
/// The kind of matching that should be performed on a feature value.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
enum LegacyRange {
/// At least the specified value.
Min,
/// At most the specified value.
Max,
}
/// The operator that was specified in this feature.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
enum Operator {
/// =
Equal,
/// >
GreaterThan,
/// >=
GreaterThanEqual,
/// <
LessThan,
/// <=
LessThanEqual,
}
impl ToCss for Operator {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str(match *self {
Self::Equal => "=",
Self::LessThan => "<",
Self::LessThanEqual => "<=",
Self::GreaterThan => ">",
Self::GreaterThanEqual => ">=",
})
}
}
impl Operator {
fn is_compatible_with(self, right_op: Self) -> bool {
// Some operators are not compatible with each other in multi-range
// context.
match self {
Self::Equal => false,
Self::GreaterThan | Self::GreaterThanEqual => matches!(right_op, Self::GreaterThan | Self::GreaterThanEqual),
Self::LessThan | Self::LessThanEqual => matches!(right_op, Self::LessThan | Self::LessThanEqual),
}
}
fn evaluate(&self, cmp: Ordering) -> bool {
match *self {
Self::Equal => cmp == Ordering::Equal,
Self::GreaterThan => cmp == Ordering::Greater,
Self::GreaterThanEqual => cmp == Ordering::Equal || cmp == Ordering::Greater,
Self::LessThan => cmp == Ordering::Less,
Self::LessThanEqual => cmp == Ordering::Equal || cmp == Ordering::Less,
}
}
fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
let operator = match *input.next()? {
Token::Delim('=') => return Ok(Operator::Equal),
Token::Delim('>') => Operator::GreaterThan,
Token::Delim('<') => Operator::LessThan,
ref t => return Err(location.new_unexpected_token_error(t.clone())),
};
// https://drafts.csswg.org/mediaqueries-4/#mq-syntax:
//
// No whitespace is allowed between the “<” or “>”
// <delim-token>s and the following “=” <delim-token>, if its
// present.
//
// TODO(emilio): Maybe we should ignore comments as well?
// https://github.com/w3c/csswg-drafts/issues/6248
let parsed_equal = input
.try_parse(|i| {
let t = i.next_including_whitespace().map_err(|_| ())?;
if !matches!(t, Token::Delim('=')) {
return Err(());
}
Ok(())
})
.is_ok();
if !parsed_equal {
return Ok(operator);
}
Ok(match operator {
Operator::GreaterThan => Operator::GreaterThanEqual,
Operator::LessThan => Operator::LessThanEqual,
_ => unreachable!(),
})
}
}
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
enum QueryFeatureExpressionKind {
/// Just the media feature name.
Empty,
/// A single value.
Single(QueryExpressionValue),
/// Legacy range syntax (min-*: value) or so.
LegacyRange(LegacyRange, QueryExpressionValue),
/// Modern range context syntax:
/// https://drafts.csswg.org/mediaqueries-5/#mq-range-context
Range {
left: Option<(Operator, QueryExpressionValue)>,
right: Option<(Operator, QueryExpressionValue)>,
},
}
impl QueryFeatureExpressionKind {
/// Evaluate a given range given an optional query value and a value from
/// the browser.
fn evaluate<T>(
&self,
context_value: T,
mut compute: impl FnMut(&QueryExpressionValue) -> T,
) -> bool
where
T: PartialOrd + Zero,
{
match *self {
Self::Empty => return !context_value.is_zero(),
Self::Single(ref value) => {
let value = compute(value);
let cmp = match context_value.partial_cmp(&value) {
Some(c) => c,
None => return false,
};
cmp == Ordering::Equal
},
Self::LegacyRange(ref range, ref value) => {
let value = compute(value);
let cmp = match context_value.partial_cmp(&value) {
Some(c) => c,
None => return false,
};
cmp == Ordering::Equal ||
match range {
LegacyRange::Min => cmp == Ordering::Greater,
LegacyRange::Max => cmp == Ordering::Less,
}
},
Self::Range {
ref left,
ref right,
} => {
debug_assert!(left.is_some() || right.is_some());
if let Some((ref op, ref value)) = left {
let value = compute(value);
let cmp = match value.partial_cmp(&context_value) {
Some(c) => c,
None => return false,
};
if !op.evaluate(cmp) {
return false;
}
}
if let Some((ref op, ref value)) = right {
let value = compute(value);
let cmp = match context_value.partial_cmp(&value) {
Some(c) => c,
None => return false,
};
if !op.evaluate(cmp) {
return false;
}
}
true
},
}
}
/// Non-ranged features only need to compare to one value at most.
fn non_ranged_value(&self) -> Option<&QueryExpressionValue> {
match *self {
Self::Empty => None,
Self::Single(ref v) => Some(v),
Self::LegacyRange(..) | Self::Range { .. } => {
debug_assert!(false, "Unexpected ranged value in non-ranged feature!");
None
},
}
}
}
/// A feature expression contains a reference to the feature, the value the
/// query contained, and the range to evaluate.
#[derive(Clone, Debug, MallocSizeOf, ToShmem, PartialEq)]
pub struct QueryFeatureExpression {
feature_type: FeatureType,
feature_index: usize,
kind: QueryFeatureExpressionKind,
}
impl ToCss for QueryFeatureExpression {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
dest.write_str("(")?;
match self.kind {
QueryFeatureExpressionKind::Empty => self.write_name(dest)?,
QueryFeatureExpressionKind::Single(ref v) |
QueryFeatureExpressionKind::LegacyRange(_, ref v) => {
self.write_name(dest)?;
dest.write_str(": ")?;
v.to_css(dest, self)?;
},
QueryFeatureExpressionKind::Range {
ref left,
ref right,
} => {
if let Some((ref op, ref val)) = left {
val.to_css(dest, self)?;
dest.write_char(' ')?;
op.to_css(dest)?;
dest.write_char(' ')?;
}
self.write_name(dest)?;
if let Some((ref op, ref val)) = right {
dest.write_char(' ')?;
op.to_css(dest)?;
dest.write_char(' ')?;
val.to_css(dest, self)?;
}
},
}
dest.write_char(')')
}
}
fn consume_operation_or_colon<'i>(
input: &mut Parser<'i, '_>,
) -> Result<Option<Operator>, ParseError<'i>> {
if input.try_parse(|input| input.expect_colon()).is_ok() {
return Ok(None);
}
Operator::parse(input).map(|op| Some(op))
}
#[allow(unused_variables)]
fn disabled_by_pref(feature: &Atom, context: &ParserContext) -> bool {
#[cfg(feature = "gecko")]
{
if *feature == atom!("forced-colors") {
// forced-colors is always enabled in the ua and chrome. On
// the web it is hidden behind a preference, which is defaulted
// to 'true' as of bug 1659511.
return !context.in_ua_or_chrome_sheet() &&
!static_prefs::pref!("layout.css.forced-colors.enabled");
}
// prefers-contrast is always enabled in the ua and chrome. On
// the web it is hidden behind a preference.
if *feature == atom!("prefers-contrast") {
return !context.in_ua_or_chrome_sheet() &&
!static_prefs::pref!("layout.css.prefers-contrast.enabled");
}
}
false
}
impl QueryFeatureExpression {
fn new(
feature_type: FeatureType,
feature_index: usize,
kind: QueryFeatureExpressionKind,
) -> Self {
debug_assert!(feature_index < feature_type.features().len());
Self {
feature_type,
feature_index,
kind,
}
}
fn write_name<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
let feature = self.feature();
if feature.flags.contains(FeatureFlags::WEBKIT_PREFIX) {
dest.write_str("-webkit-")?;
}
if let QueryFeatureExpressionKind::LegacyRange(range, _) = self.kind {
match range {
LegacyRange::Min => dest.write_str("min-")?,
LegacyRange::Max => dest.write_str("max-")?,
}
}
// NB: CssStringWriter not needed, feature names are under control.
write!(dest, "{}", feature.name)?;
Ok(())
}
fn feature(&self) -> &'static QueryFeatureDescription {
&self.feature_type.features()[self.feature_index]
}
/// Returns the feature flags for our feature.
pub fn feature_flags(&self) -> FeatureFlags {
self.feature().flags
}
/// Parse a feature expression of the form:
///
/// ```
/// (media-feature: media-value)
/// ```
pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
input.expect_parenthesis_block()?;
input.parse_nested_block(|input| {
Self::parse_in_parenthesis_block(context, input, feature_type)
})
}
fn parse_feature_name<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<(usize, Option<LegacyRange>), ParseError<'i>> {
let mut flags = FeatureFlags::empty();
let location = input.current_source_location();
let ident = input.expect_ident()?;
if context.in_ua_or_chrome_sheet() {
flags.insert(FeatureFlags::CHROME_AND_UA_ONLY);
}
let mut feature_name = &**ident;
if starts_with_ignore_ascii_case(feature_name, "-webkit-") {
feature_name = &feature_name[8..];
flags.insert(FeatureFlags::WEBKIT_PREFIX);
}
let range = if starts_with_ignore_ascii_case(feature_name, "min-") {
feature_name = &feature_name[4..];
Some(LegacyRange::Min)
} else if starts_with_ignore_ascii_case(feature_name, "max-") {
feature_name = &feature_name[4..];
Some(LegacyRange::Max)
} else {
None
};
let atom = Atom::from(string_as_ascii_lowercase(feature_name));
let (feature_index, feature) = match feature_type.find_feature(&atom) {
Some((i, f)) => (i, f),
None => {
return Err(location.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
))
},
};
if disabled_by_pref(&feature.name, context) ||
!flags.contains(feature.flags.parsing_requirements()) ||
(range.is_some() && !feature.allows_ranges())
{
return Err(location.new_custom_error(
StyleParseErrorKind::MediaQueryExpectedFeatureName(ident.clone()),
));
}
Ok((feature_index, range))
}
/// Parses the following range syntax:
///
/// (feature-value <operator> feature-name)
/// (feature-value <operator> feature-name <operator> feature-value)
fn parse_multi_range_syntax<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
let start = input.state();
// To parse the values, we first need to find the feature name. We rely
// on feature values for ranged features not being able to be top-level
// <ident>s, which holds.
let feature_index = loop {
// NOTE: parse_feature_name advances the input.
if let Ok((index, range)) = Self::parse_feature_name(context, input, feature_type) {
if range.is_some() {
// Ranged names are not allowed here.
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
break index;
}
if input.is_exhausted() {
return Err(start
.source_location()
.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
};
input.reset(&start);
let feature = &feature_type.features()[feature_index];
let left_val = QueryExpressionValue::parse(feature, context, input)?;
let left_op = Operator::parse(input)?;
{
let (parsed_index, _) = Self::parse_feature_name(context, input, feature_type)?;
debug_assert_eq!(
parsed_index, feature_index,
"How did we find a different feature?"
);
}
let right_op = input.try_parse(Operator::parse).ok();
let right = match right_op {
Some(op) => {
if !left_op.is_compatible_with(op) {
return Err(
input.new_custom_error(StyleParseErrorKind::UnspecifiedError)
);
}
Some((op, QueryExpressionValue::parse(feature, context, input)?))
},
None => None,
};
Ok(Self::new(
feature_type,
feature_index,
QueryFeatureExpressionKind::Range {
left: Some((left_op, left_val)),
right,
},
))
}
/// Parse a feature expression where we've already consumed the parenthesis.
pub fn parse_in_parenthesis_block<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
feature_type: FeatureType,
) -> Result<Self, ParseError<'i>> {
let (feature_index, range) =
match input.try_parse(|input| Self::parse_feature_name(context, input, feature_type)) {
Ok(v) => v,
Err(e) => {
if let Ok(expr) = Self::parse_multi_range_syntax(context, input, feature_type) {
return Ok(expr);
}
return Err(e);
},
};
let operator = input.try_parse(consume_operation_or_colon);
let operator = match operator {
Err(..) => {
// If there's no colon, this is a query of the form
// '(<feature>)', that is, there's no value specified.
//
// Gecko doesn't allow ranged expressions without a
// value, so just reject them here too.
if range.is_some() {
return Err(
input.new_custom_error(StyleParseErrorKind::RangedExpressionWithNoValue)
);
}
return Ok(Self::new(
feature_type,
feature_index,
QueryFeatureExpressionKind::Empty,
));
},
Ok(operator) => operator,
};
let feature = &feature_type.features()[feature_index];
let value = QueryExpressionValue::parse(feature, context, input).map_err(|err| {
err.location
.new_custom_error(StyleParseErrorKind::MediaQueryExpectedFeatureValue)
})?;
let kind = match range {
Some(range) => {
if operator.is_some() {
return Err(
input.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator)
);
}
QueryFeatureExpressionKind::LegacyRange(range, value)
},
None => match operator {
Some(operator) => {
if !feature.allows_ranges() {
return Err(input
.new_custom_error(StyleParseErrorKind::MediaQueryUnexpectedOperator));
}
QueryFeatureExpressionKind::Range {
left: None,
right: Some((operator, value)),
}
},
None => QueryFeatureExpressionKind::Single(value),
},
};
Ok(Self::new(feature_type, feature_index, kind))
}
/// Returns whether this query evaluates to true for the given device.
pub fn matches(&self, context: &computed::Context) -> bool {
macro_rules! expect {
($variant:ident, $v:expr) => {
match *$v {
QueryExpressionValue::$variant(ref v) => v,
_ => unreachable!("Unexpected QueryExpressionValue"),
}
};
}
match self.feature().evaluator {
Evaluator::Length(eval) => {
let v = eval(context);
self.kind.evaluate(v, |v| {
expect!(Length, v).to_computed_value(context)
})
},
Evaluator::Integer(eval) => {
let v = eval(context);
self.kind.evaluate(v, |v| *expect!(Integer, v))
},
Evaluator::Float(eval) => {
let v = eval(context);
self.kind.evaluate(v, |v| *expect!(Float, v))
}
Evaluator::NumberRatio(eval) => {
let ratio = eval(context);
// A ratio of 0/0 behaves as the ratio 1/0, so we need to call used_value()
// to convert it if necessary.
// FIXME: we may need to update here once
// https://github.com/w3c/csswg-drafts/issues/4954 got resolved.
self.kind.evaluate(ratio, |v| expect!(NumberRatio, v).used_value())
},
Evaluator::Resolution(eval) => {
let v = eval(context).dppx();
self.kind.evaluate(v, |v| {
expect!(Resolution, v).to_computed_value(context).dppx()
})
},
Evaluator::Enumerated { evaluator, .. } => {
let computed = self.kind.non_ranged_value().map(|v| *expect!(Enumerated, v));
evaluator(context, computed)
},
Evaluator::BoolInteger(eval) => {
let computed = self.kind.non_ranged_value().map(|v| *expect!(BoolInteger, v));
let boolean = eval(context);
computed.map_or(boolean, |v| v == boolean)
},
}
}
}
/// A value found or expected in a expression.
///
/// FIXME(emilio): How should calc() serialize in the Number / Integer /
/// BoolInteger / NumberRatio case, as computed or as specified value?
///
/// If the first, this would need to store the relevant values.
///
/// See: https://github.com/w3c/csswg-drafts/issues/1968
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum QueryExpressionValue {
/// A length.
Length(Length),
/// A (non-negative) integer.
Integer(u32),
/// A floating point value.
Float(CSSFloat),
/// A boolean value, specified as an integer (i.e., either 0 or 1).
BoolInteger(bool),
/// A single non-negative number or two non-negative numbers separated by '/',
/// with optional whitespace on either side of the '/'.
NumberRatio(Ratio),
/// A resolution.
Resolution(Resolution),
/// An enumerated value, defined by the variant keyword table in the
/// feature's `mData` member.
Enumerated(KeywordDiscriminant),
}
impl QueryExpressionValue {
fn to_css<W>(&self, dest: &mut CssWriter<W>, for_expr: &QueryFeatureExpression) -> fmt::Result
where
W: fmt::Write,
{
match *self {
QueryExpressionValue::Length(ref l) => l.to_css(dest),
QueryExpressionValue::Integer(v) => v.to_css(dest),
QueryExpressionValue::Float(v) => v.to_css(dest),
QueryExpressionValue::BoolInteger(v) => dest.write_str(if v { "1" } else { "0" }),
QueryExpressionValue::NumberRatio(ratio) => ratio.to_css(dest),
QueryExpressionValue::Resolution(ref r) => r.to_css(dest),
QueryExpressionValue::Enumerated(value) => match for_expr.feature().evaluator {
Evaluator::Enumerated { serializer, .. } => dest.write_str(&*serializer(value)),
_ => unreachable!(),
},
}
}
fn parse<'i, 't>(
for_feature: &QueryFeatureDescription,
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<QueryExpressionValue, ParseError<'i>> {
Ok(match for_feature.evaluator {
Evaluator::Length(..) => {
let length = Length::parse_non_negative(context, input)?;
QueryExpressionValue::Length(length)
},
Evaluator::Integer(..) => {
let integer = Integer::parse_non_negative(context, input)?;
QueryExpressionValue::Integer(integer.value() as u32)
},
Evaluator::BoolInteger(..) => {
let integer = Integer::parse_non_negative(context, input)?;
let value = integer.value();
if value > 1 {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
QueryExpressionValue::BoolInteger(value == 1)
},
Evaluator::Float(..) => {
let number = Number::parse(context, input)?;
QueryExpressionValue::Float(number.get())
},
Evaluator::NumberRatio(..) => {
use crate::values::specified::Ratio as SpecifiedRatio;
let ratio = SpecifiedRatio::parse(context, input)?;
QueryExpressionValue::NumberRatio(Ratio::new(ratio.0.get(), ratio.1.get()))
},
Evaluator::Resolution(..) => {
QueryExpressionValue::Resolution(Resolution::parse(context, input)?)
},
Evaluator::Enumerated { parser, .. } => {
QueryExpressionValue::Enumerated(parser(context, input)?)
},
})
}
}

View file

@ -1,19 +0,0 @@
/* 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/. */
//! Code shared between [media queries][mq] and [container queries][cq].
//!
//! [mq]: https://drafts.csswg.org/mediaqueries/
//! [cq]: https://drafts.csswg.org/css-contain-3/#container-rule
mod condition;
#[macro_use]
pub mod feature;
pub mod feature_expression;
pub mod values;
pub use self::condition::QueryCondition;
pub use self::feature::FeatureFlags;
pub use self::feature_expression::{QueryFeatureExpression, FeatureType};

View file

@ -1,36 +0,0 @@
/* 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/. */
//! Common feature values between media and container features.
/// The orientation media / container feature.
/// https://drafts.csswg.org/mediaqueries-5/#orientation
/// https://drafts.csswg.org/css-contain-3/#orientation
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum Orientation {
Portrait,
Landscape,
}
impl Orientation {
/// A helper to evaluate a orientation query given a generic size getter.
pub fn eval<T>(size: euclid::default::Size2D<T>, value: Option<Self>) -> bool
where
T: PartialOrd,
{
let query_orientation = match value {
Some(v) => v,
None => return true,
};
// Per spec, square viewports should be 'portrait'
let is_landscape = size.width > size.height;
match query_orientation {
Self::Landscape => is_landscape,
Self::Portrait => !is_landscape,
}
}
}

View file

@ -13,7 +13,7 @@ use crate::selector_parser::PseudoElement;
use crate::shared_lock::Locked;
use crate::stylesheets::{layer_rule::LayerOrder, Origin};
use crate::stylist::{AuthorStylesEnabled, CascadeData, Rule, RuleInclusion, Stylist};
use selectors::matching::{MatchingContext, MatchingMode};
use selectors::matching::{ElementSelectorFlags, MatchingContext, MatchingMode};
use servo_arc::ArcBorrow;
use smallvec::SmallVec;
@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use<E: TElement>(
///
/// This is done basically to be able to organize the cascade in smaller
/// functions, and be able to reason about it easily.
pub struct RuleCollector<'a, 'b: 'a, E>
pub struct RuleCollector<'a, 'b: 'a, E, F: 'a>
where
E: TElement,
{
@ -73,14 +73,16 @@ where
rule_inclusion: RuleInclusion,
rules: &'a mut ApplicableDeclarationList,
context: &'a mut MatchingContext<'b, E::Impl>,
flags_setter: &'a mut F,
matches_user_and_author_rules: bool,
matches_document_author_rules: bool,
in_sort_scope: bool,
}
impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E>
impl<'a, 'b: 'a, E, F: 'a> RuleCollector<'a, 'b, E, F>
where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
/// Trivially construct a new collector.
pub fn new(
@ -93,6 +95,7 @@ where
rule_inclusion: RuleInclusion,
rules: &'a mut ApplicableDeclarationList,
context: &'a mut MatchingContext<'b, E::Impl>,
flags_setter: &'a mut F,
) -> Self {
// When we're matching with matching_mode =
// `ForStatelessPseudoeElement`, the "target" for the rule hash is the
@ -122,6 +125,7 @@ where
animation_declarations,
rule_inclusion,
context,
flags_setter,
rules,
matches_user_and_author_rules,
matches_document_author_rules: matches_user_and_author_rules,
@ -223,9 +227,9 @@ where
part_rules,
&mut self.rules,
&mut self.context,
&mut self.flags_setter,
cascade_level,
cascade_data,
&self.stylist,
);
}
@ -242,9 +246,9 @@ where
self.rule_hash_target,
&mut self.rules,
&mut self.context,
&mut self.flags_setter,
cascade_level,
cascade_data,
&self.stylist,
);
}

View file

@ -203,6 +203,9 @@ impl RuleTree {
/// where it likely did not result from a rigorous performance analysis.)
const RULE_TREE_GC_INTERVAL: usize = 300;
/// Used for some size assertions.
pub const RULE_NODE_SIZE: usize = std::mem::size_of::<RuleNode>();
/// A node in the rule tree.
struct RuleNode {
/// The root node. Only the root has no root pointer, for obvious reasons.
@ -765,8 +768,3 @@ impl hash::Hash for StrongRuleNode {
(&*self.p as *const RuleNode).hash(state)
}
}
// Large pages generate thousands of RuleNode objects.
size_of_test!(RuleNode, 80);
// StrongRuleNode should be pointer-sized even inside an option.
size_of_test!(Option<StrongRuleNode>, 8);

View file

@ -20,7 +20,7 @@ mod map;
mod source;
mod unsafe_box;
pub use self::core::{RuleTree, StrongRuleNode};
pub use self::core::{RuleTree, StrongRuleNode, RULE_NODE_SIZE};
pub use self::level::{CascadeLevel, ShadowCascadeOrder};
pub use self::source::StyleSource;

View file

@ -10,11 +10,11 @@ use crate::context::QuirksMode;
use crate::dom::TElement;
use crate::rule_tree::CascadeLevel;
use crate::selector_parser::SelectorImpl;
use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId};
use crate::stylist::{CascadeData, Rule};
use crate::AllocErr;
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
use precomputed_hash::PrecomputedHash;
use selectors::matching::{matches_selector, MatchingContext};
use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext};
use selectors::parser::{Combinator, Component, SelectorIter};
use smallvec::SmallVec;
use std::collections::hash_map;
@ -186,33 +186,34 @@ impl SelectorMap<Rule> {
///
/// Extract matching rules as per element's ID, classes, tag name, etc..
/// Sort the Rules at the end to maintain cascading order.
pub fn get_all_matching_rules<E>(
pub fn get_all_matching_rules<E, F>(
&self,
element: E,
rule_hash_target: E,
matching_rules_list: &mut ApplicableDeclarationList,
matching_context: &mut MatchingContext<E::Impl>,
context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F,
cascade_level: CascadeLevel,
cascade_data: &CascadeData,
stylist: &Stylist,
) where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
if self.is_empty() {
return;
}
let quirks_mode = matching_context.quirks_mode();
let quirks_mode = context.quirks_mode();
if rule_hash_target.is_root() {
SelectorMap::get_matching_rules(
element,
&self.root,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
);
}
@ -222,10 +223,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
)
}
}
@ -236,10 +237,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
)
}
});
@ -251,10 +252,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
)
}
});
@ -265,10 +266,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
)
}
@ -277,10 +278,10 @@ impl SelectorMap<Rule> {
element,
rules,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
)
}
@ -288,44 +289,38 @@ impl SelectorMap<Rule> {
element,
&self.other,
matching_rules_list,
matching_context,
context,
flags_setter,
cascade_level,
cascade_data,
stylist,
);
}
/// Adds rules in `rules` that match `element` to the `matching_rules` list.
pub(crate) fn get_matching_rules<E>(
pub(crate) fn get_matching_rules<E, F>(
element: E,
rules: &[Rule],
matching_rules: &mut ApplicableDeclarationList,
matching_context: &mut MatchingContext<E::Impl>,
context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F,
cascade_level: CascadeLevel,
cascade_data: &CascadeData,
stylist: &Stylist,
) where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
for rule in rules {
if !matches_selector(
if matches_selector(
&rule.selector,
0,
Some(&rule.hashes),
&element,
matching_context,
context,
flags_setter,
) {
continue;
matching_rules
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
}
if rule.container_condition_id != ContainerConditionId::none() {
if !cascade_data.container_condition_matches(rule.container_condition_id, stylist, element) {
continue;
}
}
matching_rules
.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
}
}
}

View file

@ -7,13 +7,13 @@
use crate::context::QuirksMode;
use crate::custom_properties::CssEnvironment;
use crate::font_metrics::FontMetrics;
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
use crate::media_queries::media_feature::{AllowsRanges, ParsingRequirements};
use crate::media_queries::media_feature::{Evaluator, MediaFeatureDescription};
use crate::media_queries::media_feature_expression::RangeOrOperator;
use crate::media_queries::MediaType;
use crate::properties::ComputedValues;
use crate::values::computed::CSSPixelLength;
use crate::values::computed::Context;
use crate::values::specified::font::FONT_MEDIUM_PX;
use crate::values::specified::ViewportVariant;
use crate::values::KeyframesName;
use app_units::Au;
use cssparser::RGBA;
@ -143,13 +143,8 @@ impl Device {
}
/// Like the above, but records that we've used viewport units.
pub fn au_viewport_size_for_viewport_unit_resolution(
&self,
_: ViewportVariant,
) -> UntypedSize2D<Au> {
pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D<Au> {
self.used_viewport_units.store(true, Ordering::Relaxed);
// Servo doesn't have dynamic UA interfaces that affect the viewport,
// so we can just ignore the ViewportVariant.
self.au_viewport_size()
}
@ -234,8 +229,16 @@ impl Device {
}
/// https://drafts.csswg.org/mediaqueries-4/#width
fn eval_width(context: &Context) -> CSSPixelLength {
CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px())
fn eval_width(
device: &Device,
value: Option<CSSPixelLength>,
range_or_operator: Option<RangeOrOperator>,
) -> bool {
RangeOrOperator::evaluate(
range_or_operator,
value.map(Au::from),
device.au_viewport_size().width,
)
}
#[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)]
@ -246,7 +249,7 @@ enum Scan {
}
/// https://drafts.csswg.org/mediaqueries-4/#scan
fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
fn eval_scan(_: &Device, _: Option<Scan>) -> bool {
// Since we doesn't support the 'tv' media type, the 'scan' feature never
// matches.
false
@ -254,18 +257,18 @@ fn eval_scan(_: &Context, _: Option<Scan>) -> bool {
lazy_static! {
/// A list with all the media features that Servo supports.
pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 2] = [
pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
feature!(
atom!("scan"),
AllowsRanges::No,
keyword_evaluator!(eval_scan, Scan),
FeatureFlags::empty(),
ParsingRequirements::empty(),
),
];
}

View file

@ -7,7 +7,7 @@
//! elements can indeed share the same style.
use crate::bloom::StyleBloom;
use crate::context::SharedStyleContext;
use crate::context::{SelectorFlagsMap, SharedStyleContext};
use crate::dom::TElement;
use crate::sharing::{StyleSharingCandidate, StyleSharingTarget};
use selectors::NthIndexCache;
@ -120,6 +120,7 @@ pub fn revalidate<E>(
shared_context: &SharedStyleContext,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>,
) -> bool
where
E: TElement,
@ -127,7 +128,7 @@ where
let stylist = &shared_context.stylist;
let for_element =
target.revalidation_match_results(stylist, bloom, nth_index_cache);
target.revalidation_match_results(stylist, bloom, nth_index_cache, selector_flags_map);
let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache);

View file

@ -66,8 +66,9 @@
use crate::applicable_declarations::ApplicableDeclarationBlock;
use crate::bloom::StyleBloom;
use crate::context::{SharedStyleContext, StyleContext};
use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext};
use crate::dom::{SendElement, TElement};
use crate::matching::MatchMethods;
use crate::properties::ComputedValues;
use crate::rule_tree::StrongRuleNode;
use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
@ -75,7 +76,7 @@ use crate::stylist::Stylist;
use crate::values::AtomIdent;
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use owning_ref::OwningHandle;
use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags};
use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode};
use selectors::NthIndexCache;
use servo_arc::Arc;
use smallbitvec::SmallBitVec;
@ -222,17 +223,18 @@ impl ValidationData {
/// Computes the revalidation results if needed, and returns it.
/// Inline so we know at compile time what bloom_known_valid is.
#[inline]
fn revalidation_match_results<E>(
fn revalidation_match_results<E, F>(
&mut self,
element: E,
stylist: &Stylist,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
bloom_known_valid: bool,
needs_selector_flags: NeedsSelectorFlags,
flags_setter: &mut F,
) -> &SmallBitVec
where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
self.revalidation_match_results.get_or_insert_with(|| {
// The bloom filter may already be set up for our element.
@ -255,7 +257,7 @@ impl ValidationData {
element,
bloom_to_use,
nth_index_cache,
needs_selector_flags,
flags_setter,
)
})
}
@ -325,9 +327,7 @@ impl<E: TElement> StyleSharingCandidate<E> {
bloom,
nth_index_cache,
/* bloom_known_valid = */ false,
// The candidate must already have the right bits already, if
// needed.
NeedsSelectorFlags::No,
&mut |_, _| {},
)
}
}
@ -384,6 +384,7 @@ impl<E: TElement> StyleSharingTarget<E> {
stylist: &Stylist,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>,
) -> &SmallBitVec {
// It's important to set the selector flags. Otherwise, if we succeed in
// sharing the style, we may not set the slow selector flags for the
@ -400,13 +401,18 @@ impl<E: TElement> StyleSharingTarget<E> {
// The style sharing cache will get a hit for the second span. When the
// child span is subsequently removed from the DOM, missing selector
// flags would cause us to miss the restyle on the second span.
let element = self.element;
let mut set_selector_flags = |el: &E, flags: ElementSelectorFlags| {
element.apply_selector_flags(selector_flags_map, el, flags);
};
self.validation_data.revalidation_match_results(
self.element,
stylist,
bloom,
nth_index_cache,
/* bloom_known_valid = */ true,
NeedsSelectorFlags::Yes,
&mut set_selector_flags,
)
}
@ -417,6 +423,7 @@ impl<E: TElement> StyleSharingTarget<E> {
) -> Option<ResolvedElementStyles> {
let cache = &mut context.thread_local.sharing_cache;
let shared_context = &context.shared;
let selector_flags_map = &mut context.thread_local.selector_flags;
let bloom_filter = &context.thread_local.bloom_filter;
let nth_index_cache = &mut context.thread_local.nth_index_cache;
@ -436,6 +443,7 @@ impl<E: TElement> StyleSharingTarget<E> {
cache.share_style_if_possible(
shared_context,
selector_flags_map,
bloom_filter,
nth_index_cache,
self,
@ -623,13 +631,13 @@ impl<E: TElement> StyleSharingCache<E> {
//
// These are things we don't check in the candidate match because they
// are either uncommon or expensive.
let ui_style = style.style().get_ui();
if ui_style.specifies_transitions() {
let box_style = style.style().get_box();
if box_style.specifies_transitions() {
debug!("Failing to insert to the cache: transitions");
return;
}
if ui_style.specifies_animations() {
if box_style.specifies_animations() {
debug!("Failing to insert to the cache: animations");
return;
}
@ -659,6 +667,7 @@ impl<E: TElement> StyleSharingCache<E> {
fn share_style_if_possible(
&mut self,
shared_context: &SharedStyleContext,
selector_flags_map: &mut SelectorFlagsMap<E>,
bloom_filter: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
target: &mut StyleSharingTarget<E>,
@ -691,6 +700,7 @@ impl<E: TElement> StyleSharingCache<E> {
&shared_context,
bloom_filter,
nth_index_cache,
selector_flags_map,
shared_context,
)
})
@ -702,6 +712,7 @@ impl<E: TElement> StyleSharingCache<E> {
shared: &SharedStyleContext,
bloom: &StyleBloom<E>,
nth_index_cache: &mut NthIndexCache,
selector_flags_map: &mut SelectorFlagsMap<E>,
shared_context: &SharedStyleContext,
) -> Option<ResolvedElementStyles> {
debug_assert!(!target.is_in_native_anonymous_subtree());
@ -806,6 +817,7 @@ impl<E: TElement> StyleSharingCache<E> {
shared,
bloom,
nth_index_cache,
selector_flags_map,
) {
trace!("Miss: Revalidation");
return None;

View file

@ -15,7 +15,7 @@ use crate::rule_tree::StrongRuleNode;
use crate::selector_parser::{PseudoElement, SelectorImpl};
use crate::stylist::RuleInclusion;
use log::Level::Trace;
use selectors::matching::{NeedsSelectorFlags, MatchingContext};
use selectors::matching::{ElementSelectorFlags, MatchingContext};
use selectors::matching::{MatchingMode, VisitedHandlingMode};
use servo_arc::Arc;
@ -451,6 +451,7 @@ where
);
let mut applicable_declarations = ApplicableDeclarationList::new();
let map = &mut self.context.thread_local.selector_flags;
let bloom_filter = self.context.thread_local.bloom_filter.filter();
let nth_index_cache = &mut self.context.thread_local.nth_index_cache;
let mut matching_context = MatchingContext::new_for_visited(
@ -459,22 +460,29 @@ where
Some(nth_index_cache),
visited_handling,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
);
let stylist = &self.context.shared.stylist;
let implemented_pseudo = self.element.implemented_pseudo_element();
// Compute the primary rule node.
stylist.push_applicable_declarations(
self.element,
implemented_pseudo.as_ref(),
self.element.style_attribute(),
self.element.smil_override(),
self.element.animation_declarations(self.context.shared),
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
);
{
let resolving_element = self.element;
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
resolving_element.apply_selector_flags(map, element, flags);
};
// Compute the primary rule node.
stylist.push_applicable_declarations(
self.element,
implemented_pseudo.as_ref(),
self.element.style_attribute(),
self.element.smil_override(),
self.element.animation_declarations(self.context.shared),
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
&mut set_selector_flags,
);
}
// FIXME(emilio): This is a hack for animations, and should go away.
self.element.unset_dirty_style_attribute();
@ -532,9 +540,14 @@ where
Some(nth_index_cache),
visited_handling,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
);
let map = &mut self.context.thread_local.selector_flags;
let resolving_element = self.element;
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
resolving_element.apply_selector_flags(map, element, flags);
};
// NB: We handle animation rules for ::before and ::after when
// traversing them.
stylist.push_applicable_declarations(
@ -546,6 +559,7 @@ where
self.rule_inclusion,
&mut applicable_declarations,
&mut matching_context,
&mut set_selector_flags,
);
if applicable_declarations.is_empty() {

View file

@ -1,281 +0,0 @@
/* 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 [`@container`][container] rule.
//!
//! [container]: https://drafts.csswg.org/css-contain-3/#container-rule
use crate::logical_geometry::{WritingMode, LogicalSize};
use crate::dom::TElement;
use crate::media_queries::Device;
use crate::parser::ParserContext;
use crate::queries::{QueryCondition, FeatureType};
use crate::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription};
use crate::queries::values::Orientation;
use crate::str::CssStringWriter;
use crate::shared_lock::{
DeepCloneParams, DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
};
use crate::values::specified::ContainerName;
use crate::values::computed::{Context, CSSPixelLength, Ratio};
use crate::properties::ComputedValues;
use crate::stylesheets::CssRules;
use app_units::Au;
use cssparser::{SourceLocation, Parser};
use euclid::default::Size2D;
#[cfg(feature = "gecko")]
use malloc_size_of::{MallocSizeOfOps, MallocUnconditionalShallowSizeOf};
use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss, ParseError};
/// A container rule.
#[derive(Debug, ToShmem)]
pub struct ContainerRule {
/// The container query and name.
pub condition: Arc<ContainerCondition>,
/// The nested rules inside the block.
pub rules: Arc<Locked<CssRules>>,
/// The source position where this rule was found.
pub source_location: SourceLocation,
}
impl ContainerRule {
/// Returns the query condition.
pub fn query_condition(&self) -> &QueryCondition {
&self.condition.condition
}
/// Measure heap usage.
#[cfg(feature = "gecko")]
pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
// Measurement of other fields may be added later.
self.rules.unconditional_shallow_size_of(ops)
+ self.rules.read_with(guard).size_of(guard, ops)
}
}
impl DeepCloneWithLock for ContainerRule {
fn deep_clone_with_lock(
&self,
lock: &SharedRwLock,
guard: &SharedRwLockReadGuard,
params: &DeepCloneParams,
) -> Self {
let rules = self.rules.read_with(guard);
Self {
condition: self.condition.clone(),
rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard, params))),
source_location: self.source_location.clone(),
}
}
}
impl ToCssWithGuard for ContainerRule {
fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
dest.write_str("@container ")?;
{
let mut writer = CssWriter::new(dest);
if !self.condition.name.is_none() {
self.condition.name.to_css(&mut writer)?;
writer.write_char(' ')?;
}
self.condition.condition.to_css(&mut writer)?;
}
self.rules.read_with(guard).to_css_block(guard, dest)
}
}
/// A container condition and filter, combined.
#[derive(Debug, ToShmem)]
pub struct ContainerCondition {
name: ContainerName,
condition: QueryCondition,
flags: FeatureFlags,
}
impl ContainerCondition {
/// Parse a container condition.
pub fn parse<'a>(
context: &ParserContext,
input: &mut Parser<'a, '_>,
) -> Result<Self, ParseError<'a>> {
use crate::parser::Parse;
// FIXME: This is a bit ambiguous:
// https://github.com/w3c/csswg-drafts/issues/7203
let name = input.try_parse(|input| {
ContainerName::parse(context, input)
}).ok().unwrap_or_else(ContainerName::none);
let condition = QueryCondition::parse(context, input, FeatureType::Container)?;
let flags = condition.cumulative_flags();
Ok(Self { name, condition, flags })
}
fn valid_container_info<E>(&self, potential_container: E) -> Option<(ContainerInfo, Arc<ComputedValues>)>
where
E: TElement,
{
use crate::values::computed::ContainerType;
fn container_type_axes(ty_: ContainerType, wm: WritingMode) -> FeatureFlags {
if ty_.intersects(ContainerType::SIZE) {
return FeatureFlags::all_container_axes()
}
if ty_.intersects(ContainerType::INLINE_SIZE) {
let physical_axis = if wm.is_vertical() {
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS
} else {
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS
};
return FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS | physical_axis
}
FeatureFlags::empty()
}
let data = match potential_container.borrow_data() {
Some(data) => data,
None => return None,
};
let style = data.styles.primary();
let wm = style.writing_mode;
let box_style = style.get_box();
// Filter by container-type.
let container_type = box_style.clone_container_type();
let available_axes = container_type_axes(container_type, wm);
if !available_axes.contains(self.flags.container_axes()) {
return None;
}
// Filter by container-name.
let container_name = box_style.clone_container_name();
for filter_name in self.name.0.iter() {
if !container_name.0.contains(filter_name) {
return None;
}
}
let size = potential_container.primary_box_size();
let style = style.clone();
Some((ContainerInfo { size, wm }, style))
}
fn find_container<E>(&self, mut e: E) -> Option<(ContainerInfo, Arc<ComputedValues>)>
where
E: TElement,
{
while let Some(element) = e.traversal_parent() {
if let Some(info) = self.valid_container_info(element) {
return Some(info);
}
e = element;
}
None
}
/// Tries to match a container query condition for a given element.
pub(crate) fn matches<E>(&self, device: &Device, element: E) -> bool
where
E: TElement,
{
let info = self.find_container(element);
Context::for_container_query_evaluation(device, info, |context| {
self.condition.matches(context)
})
}
}
/// Information needed to evaluate an individual container query.
#[derive(Copy, Clone)]
pub struct ContainerInfo {
size: Size2D<Au>,
wm: WritingMode,
}
fn get_container(context: &Context) -> ContainerInfo {
if let Some(ref info) = context.container_info {
return info.clone()
}
ContainerInfo {
size: context.device().au_viewport_size(),
wm: WritingMode::horizontal_tb(),
}
}
fn eval_width(context: &Context) -> CSSPixelLength {
let info = get_container(context);
CSSPixelLength::new(info.size.width.to_f32_px())
}
fn eval_height(context: &Context) -> CSSPixelLength {
let info = get_container(context);
CSSPixelLength::new(info.size.height.to_f32_px())
}
fn eval_inline_size(context: &Context) -> CSSPixelLength {
let info = get_container(context);
CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).inline.to_f32_px())
}
fn eval_block_size(context: &Context) -> CSSPixelLength {
let info = get_container(context);
CSSPixelLength::new(LogicalSize::from_physical(info.wm, info.size).block.to_f32_px())
}
fn eval_aspect_ratio(context: &Context) -> Ratio {
let info = get_container(context);
Ratio::new(info.size.width.0 as f32, info.size.height.0 as f32)
}
fn eval_orientation(context: &Context, value: Option<Orientation>) -> bool {
let info = get_container(context);
Orientation::eval(info.size, value)
}
/// https://drafts.csswg.org/css-contain-3/#container-features
///
/// TODO: Support style queries, perhaps.
pub static CONTAINER_FEATURES: [QueryFeatureDescription; 6] = [
feature!(
atom!("width"),
AllowsRanges::Yes,
Evaluator::Length(eval_width),
FeatureFlags::CONTAINER_REQUIRES_WIDTH_AXIS,
),
feature!(
atom!("height"),
AllowsRanges::Yes,
Evaluator::Length(eval_height),
FeatureFlags::CONTAINER_REQUIRES_HEIGHT_AXIS,
),
feature!(
atom!("inline-size"),
AllowsRanges::Yes,
Evaluator::Length(eval_inline_size),
FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS,
),
feature!(
atom!("block-size"),
AllowsRanges::Yes,
Evaluator::Length(eval_block_size),
FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS,
),
feature!(
atom!("aspect-ratio"),
AllowsRanges::Yes,
Evaluator::NumberRatio(eval_aspect_ratio),
// XXX from_bits_truncate is const, but the pipe operator isn't, so this
// works around it.
FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()),
),
feature!(
atom!("orientation"),
AllowsRanges::No,
keyword_evaluator!(eval_orientation, Orientation),
FeatureFlags::from_bits_truncate(FeatureFlags::CONTAINER_REQUIRES_BLOCK_AXIS.bits() | FeatureFlags::CONTAINER_REQUIRES_INLINE_AXIS.bits()),
),
];

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! A [`@layer`][layer] rule.
//! A [`@layer`][layer] urle.
//!
//! [layer]: https://drafts.csswg.org/css-cascade-5/#layering
@ -61,6 +61,17 @@ impl LayerOrder {
}
}
/// 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]>);

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! An [`@media`][media] rule.
//! An [`@media`][media] urle.
//!
//! [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
@ -18,7 +18,7 @@ use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
/// An [`@media`][media] rule.
/// An [`@media`][media] urle.
///
/// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media
#[derive(Debug, ToShmem)]

View file

@ -12,7 +12,6 @@ pub mod font_feature_values_rule;
pub mod import_rule;
pub mod keyframes_rule;
pub mod layer_rule;
pub mod container_rule;
mod loader;
mod media_rule;
mod namespace_rule;
@ -54,7 +53,6 @@ pub use self::import_rule::ImportRule;
pub use self::keyframes_rule::KeyframesRule;
pub use self::layer_rule::{LayerBlockRule, LayerStatementRule};
pub use self::loader::StylesheetLoader;
pub use self::container_rule::ContainerRule;
pub use self::media_rule::MediaRule;
pub use self::namespace_rule::NamespaceRule;
pub use self::origin::{Origin, OriginSet, OriginSetIterator, PerOrigin, PerOriginIter};
@ -255,7 +253,6 @@ pub enum CssRule {
Import(Arc<Locked<ImportRule>>),
Style(Arc<Locked<StyleRule>>),
Media(Arc<Locked<MediaRule>>),
Container(Arc<Locked<ContainerRule>>),
FontFace(Arc<Locked<FontFaceRule>>),
FontFeatureValues(Arc<Locked<FontFeatureValuesRule>>),
CounterStyle(Arc<Locked<CounterStyleRule>>),
@ -290,10 +287,6 @@ impl CssRule {
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
},
CssRule::Container(ref lock) => {
lock.unconditional_shallow_size_of(ops) + lock.read_with(guard).size_of(guard, ops)
},
CssRule::FontFace(_) => 0,
CssRule::FontFeatureValues(_) => 0,
CssRule::CounterStyle(_) => 0,
@ -351,7 +344,6 @@ pub enum CssRuleType {
LayerBlock = 16,
LayerStatement = 17,
ScrollTimeline = 18,
Container = 19,
}
#[allow(missing_docs)]
@ -381,7 +373,16 @@ impl CssRule {
CssRule::LayerBlock(_) => CssRuleType::LayerBlock,
CssRule::LayerStatement(_) => CssRuleType::LayerStatement,
CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline,
CssRule::Container(_) => CssRuleType::Container,
}
}
fn rule_state(&self) -> State {
match *self {
// CssRule::Charset(..) => State::Start,
CssRule::Import(..) => State::Imports,
CssRule::Namespace(..) => State::Namespaces,
// TODO(emilio): Do we need something for EarlyLayers?
_ => State::Body,
}
}
@ -459,12 +460,6 @@ impl DeepCloneWithLock for CssRule {
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
))
},
CssRule::Container(ref arc) => {
let rule = arc.read_with(guard);
CssRule::Container(Arc::new(
lock.wrap(rule.deep_clone_with_lock(lock, guard, params)),
))
},
CssRule::Media(ref arc) => {
let rule = arc.read_with(guard);
CssRule::Media(Arc::new(
@ -550,7 +545,6 @@ impl ToCssWithGuard for CssRule {
CssRule::LayerBlock(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::LayerStatement(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::ScrollTimeline(ref lock) => lock.read_with(guard).to_css(guard, dest),
CssRule::Container(ref lock) => lock.read_with(guard).to_css(guard, dest),
}
}
}

View file

@ -153,17 +153,21 @@ impl CssRulesHelpers for RawOffsetArc<Locked<CssRules>> {
}
// Computes the parser state at the given index
let insert_rule_context = InsertRuleContext {
rule_list: &rules.0,
index,
};
let state = if nested {
State::Body
} else if index == 0 {
State::Start
} else {
insert_rule_context.max_rule_state_at_index(index - 1)
rules
.0
.get(index - 1)
.map(CssRule::rule_state)
.unwrap_or(State::Body)
};
let insert_rule_context = InsertRuleContext {
rule_list: &rules.0,
index,
};
// Steps 3, 4, 5, 6

View file

@ -13,7 +13,6 @@ use crate::properties::parse_property_declaration_list;
use crate::selector_parser::{SelectorImpl, SelectorParser};
use crate::shared_lock::{Locked, SharedRwLock};
use crate::str::starts_with_ignore_ascii_case;
use crate::stylesheets::container_rule::{ContainerRule, ContainerCondition};
use crate::stylesheets::document_rule::DocumentCondition;
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
use crate::stylesheets::import_rule::ImportLayer;
@ -46,36 +45,6 @@ pub struct InsertRuleContext<'a> {
pub index: usize,
}
impl<'a> InsertRuleContext<'a> {
/// Returns the max rule state allowable for insertion at a given index in
/// the rule list.
pub fn max_rule_state_at_index(&self, index: usize) -> State {
let rule = match self.rule_list.get(index) {
Some(rule) => rule,
None => return State::Body,
};
match rule {
CssRule::Import(..) => State::Imports,
CssRule::Namespace(..) => State::Namespaces,
CssRule::LayerStatement(..) => {
// If there are @import / @namespace after this layer, then
// we're in the early-layers phase, otherwise we're in the body
// and everything is fair game.
let next_non_layer_statement_rule = self.rule_list[index + 1..]
.iter()
.find(|r| !matches!(*r, CssRule::LayerStatement(..)));
if let Some(non_layer) = next_non_layer_statement_rule {
if matches!(*non_layer, CssRule::Import(..) | CssRule::Namespace(..)) {
return State::EarlyLayers;
}
}
State::Body
}
_ => State::Body,
}
}
}
/// The parser for the top-level rules in a stylesheet.
pub struct TopLevelRuleParser<'a> {
/// A reference to the lock we need to use to create rules.
@ -133,8 +102,12 @@ impl<'b> TopLevelRuleParser<'b> {
None => return true,
};
let max_rule_state = ctx.max_rule_state_at_index(ctx.index);
if new_state > max_rule_state {
let next_rule_state = match ctx.rule_list.get(ctx.index) {
None => return true,
Some(rule) => rule.rule_state(),
};
if new_state > next_rule_state {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return false;
}
@ -189,8 +162,6 @@ pub enum AtRulePrelude {
CounterStyle(CustomIdent),
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>),
/// A @container rule prelude.
Container(Arc<ContainerCondition>),
/// An @supports rule, with its conditional
Supports(SupportsCondition),
/// A @viewport rule prelude.
@ -291,23 +262,11 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
self.dom_error = Some(RulesMutateError::HierarchyRequest);
return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule))
},
"layer" => {
let state_to_check = if self.state <= State::EarlyLayers {
// The real state depends on whether there's a block or not.
// We don't know that yet, but the parse_block check deals
// with that.
State::EarlyLayers
} else {
State::Body
};
if !self.check_state(state_to_check) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
},
_ => {
// All other rules have blocks, so we do this check early in
// parse_block instead.
}
_ => {}
}
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
AtRuleParser::parse_prelude(&mut self.nested(), name, input)
@ -320,9 +279,6 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> {
start: &ParserState,
input: &mut Parser<'i, 't>,
) -> Result<Self::AtRule, ParseError<'i>> {
if !self.check_state(State::Body) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let rule = AtRuleParser::parse_block(&mut self.nested(), prelude, start, input)?;
self.state = State::Body;
Ok((start.position(), rule))
@ -454,16 +410,6 @@ impl<'a, 'b> NestedRuleParser<'a, 'b> {
}
}
fn container_queries_enabled() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.container-queries.enabled");
#[cfg(feature = "servo")]
return servo_config::prefs::pref_map()
.get("layout.container-queries.enabled")
.as_bool()
.unwrap_or(false);
}
impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
type Prelude = AtRulePrelude;
type AtRule = CssRule;
@ -487,10 +433,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
"font-face" => {
AtRulePrelude::FontFace
},
"container" if container_queries_enabled() => {
let condition = Arc::new(ContainerCondition::parse(self.context, input)?);
AtRulePrelude::Container(condition)
},
"layer" => {
let names = input.try_parse(|input| {
input.parse_comma_separated(|input| {
@ -672,15 +614,6 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for NestedRuleParser<'a, 'b> {
},
))))
},
AtRulePrelude::Container(condition) => {
Ok(CssRule::Container(Arc::new(self.shared_lock.wrap(
ContainerRule {
condition,
rules: self.parse_nested_rules(input, CssRuleType::Container),
source_location: start.source_location(),
},
))))
},
AtRulePrelude::Layer(names) => {
let name = match names.len() {
0 | 1 => names.into_iter().next(),

View file

@ -88,10 +88,6 @@ where
}
Some(doc_rule.rules.read_with(guard).0.iter())
},
CssRule::Container(ref lock) => {
let container_rule = lock.read_with(guard);
Some(container_rule.rules.read_with(guard).0.iter())
},
CssRule::Media(ref lock) => {
let media_rule = lock.read_with(guard);
if !C::process_media(guard, device, quirks_mode, media_rule) {

View file

@ -206,6 +206,7 @@ impl Default for ScrollDirection {
}
}
// Avoid name collision in cbindgen with StyleOrientation.
pub use self::ScrollDirection as Orientation;
/// Scroll-timeline offsets. We treat None as an empty vector.

View file

@ -370,7 +370,6 @@ impl SanitizationKind {
CssRule::Media(..) |
CssRule::Supports(..) |
CssRule::Import(..) |
CssRule::Container(..) |
// TODO(emilio): Perhaps Layer should not be always sanitized? But
// we sanitize @media and co, so this seems safer for now.
CssRule::LayerStatement(..) |

View file

@ -671,8 +671,7 @@ impl MaybeNew for ViewportConstraints {
builder: StyleBuilder::for_inheritance(device, None, None),
cached_system_font: None,
in_media_query: false,
quirks_mode,
container_info: None,
quirks_mode: quirks_mode,
for_smil_animation: false,
for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions),

View file

@ -28,9 +28,8 @@ use crate::selector_parser::{PerPseudoElementMap, PseudoElement, SelectorImpl, S
use crate::shared_lock::{Locked, SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheet_set::{DataValidity, DocumentStylesheetSet, SheetRebuildKind};
use crate::stylesheet_set::{DocumentStylesheetFlusher, SheetCollectionFlusher};
use crate::stylesheets::container_rule::ContainerCondition;
use crate::stylesheets::keyframes_rule::KeyframesAnimation;
use crate::stylesheets::layer_rule::{LayerName, LayerOrder};
use crate::stylesheets::layer_rule::{LayerId, LayerName, LayerOrder};
use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule};
#[cfg(feature = "gecko")]
use crate::stylesheets::{
@ -40,6 +39,7 @@ use crate::stylesheets::{
CssRule, EffectiveRulesIterator, Origin, OriginSet, PageRule, PerOrigin, PerOriginIter,
};
use crate::stylesheets::{StyleRule, StylesheetContents, StylesheetInDocument};
use crate::thread_state::{self, ThreadState};
use crate::AllocErr;
use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom};
use fxhash::FxHashMap;
@ -49,7 +49,7 @@ use malloc_size_of::MallocUnconditionalShallowSizeOf;
use selectors::attr::{CaseSensitivity, NamespaceConstraint};
use selectors::bloom::BloomFilter;
use selectors::matching::VisitedHandlingMode;
use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags};
use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext, MatchingMode};
use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorIter};
use selectors::visitor::SelectorVisitor;
use selectors::NthIndexCache;
@ -549,47 +549,6 @@ impl From<StyleRuleInclusion> for RuleInclusion {
}
}
/// A struct containing state from ancestor rules like @layer / @import /
/// @container.
struct ContainingRuleState {
layer_name: LayerName,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
}
impl Default for ContainingRuleState {
fn default() -> Self {
Self {
layer_name: LayerName::new_empty(),
layer_id: LayerId::root(),
container_condition_id: ContainerConditionId::none(),
}
}
}
struct SavedContainingRuleState {
layer_name_len: usize,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
}
impl ContainingRuleState {
fn save(&self) -> SavedContainingRuleState {
SavedContainingRuleState {
layer_name_len: self.layer_name.0.len(),
layer_id: self.layer_id,
container_condition_id: self.container_condition_id,
}
}
fn restore(&mut self, saved: &SavedContainingRuleState) {
debug_assert!(self.layer_name.0.len() >= saved.layer_name_len);
self.layer_name.0.truncate(saved.layer_name_len);
self.layer_id = saved.layer_id;
self.container_condition_id = saved.container_condition_id;
}
}
impl Stylist {
/// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
/// If more members are added here, think about whether they should
@ -1114,12 +1073,38 @@ impl Stylist {
{
debug_assert!(pseudo.is_lazy());
// No need to bother setting the selector flags when we're computing
// default styles.
let needs_selector_flags = if rule_inclusion == RuleInclusion::DefaultOnly {
NeedsSelectorFlags::No
} else {
NeedsSelectorFlags::Yes
// Apply the selector flags. We should be in sequential mode
// already, so we can directly apply the parent flags.
let mut set_selector_flags = |element: &E, flags: ElementSelectorFlags| {
if cfg!(feature = "servo") {
// Servo calls this function from the worker, but only for internal
// pseudos, so we should never generate selector flags here.
unreachable!("internal pseudo generated slow selector flags?");
}
// No need to bother setting the selector flags when we're computing
// default styles.
if rule_inclusion == RuleInclusion::DefaultOnly {
return;
}
// Gecko calls this from sequential mode, so we can directly apply
// the flags.
debug_assert_eq!(thread_state::get(), ThreadState::LAYOUT);
let self_flags = flags.for_self();
if !self_flags.is_empty() {
unsafe {
element.set_selector_flags(self_flags);
}
}
let parent_flags = flags.for_parent();
if !parent_flags.is_empty() {
if let Some(p) = element.parent_element() {
unsafe {
p.set_selector_flags(parent_flags);
}
}
}
};
let mut declarations = ApplicableDeclarationList::new();
@ -1128,7 +1113,6 @@ impl Stylist {
None,
None,
self.quirks_mode,
needs_selector_flags,
);
matching_context.pseudo_element_matching_fn = matching_fn;
@ -1142,6 +1126,7 @@ impl Stylist {
rule_inclusion,
&mut declarations,
&mut matching_context,
&mut set_selector_flags,
);
if declarations.is_empty() && is_probe {
@ -1159,7 +1144,6 @@ impl Stylist {
None,
VisitedHandlingMode::RelevantLinkVisited,
self.quirks_mode,
needs_selector_flags,
);
matching_context.pseudo_element_matching_fn = matching_fn;
@ -1172,6 +1156,7 @@ impl Stylist {
rule_inclusion,
&mut declarations,
&mut matching_context,
&mut set_selector_flags,
);
if !declarations.is_empty() {
let rule_node = self.rule_tree.insert_ordered_rules_with_important(
@ -1284,7 +1269,7 @@ impl Stylist {
}
/// Returns the applicable CSS declarations for the given element.
pub fn push_applicable_declarations<E>(
pub fn push_applicable_declarations<E, F>(
&self,
element: E,
pseudo_element: Option<&PseudoElement>,
@ -1294,8 +1279,10 @@ impl Stylist {
rule_inclusion: RuleInclusion,
applicable_declarations: &mut ApplicableDeclarationList,
context: &mut MatchingContext<E::Impl>,
flags_setter: &mut F,
) where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
RuleCollector::new(
self,
@ -1307,6 +1294,7 @@ impl Stylist {
rule_inclusion,
applicable_declarations,
context,
flags_setter,
)
.collect_all();
}
@ -1386,15 +1374,16 @@ impl Stylist {
/// Computes the match results of a given element against the set of
/// revalidation selectors.
pub fn match_revalidation_selectors<E>(
pub fn match_revalidation_selectors<E, F>(
&self,
element: E,
bloom: Option<&BloomFilter>,
nth_index_cache: &mut NthIndexCache,
needs_selector_flags: NeedsSelectorFlags,
flags_setter: &mut F,
) -> SmallBitVec
where
E: TElement,
F: FnMut(&E, ElementSelectorFlags),
{
// NB: `MatchingMode` doesn't really matter, given we don't share style
// between pseudos.
@ -1403,7 +1392,6 @@ impl Stylist {
bloom,
Some(nth_index_cache),
self.quirks_mode,
needs_selector_flags,
);
// Note that, by the time we're revalidating, we're guaranteed that the
@ -1426,6 +1414,7 @@ impl Stylist {
Some(&selector_and_hashes.hashes),
&element,
matching_context,
flags_setter,
));
true
},
@ -1448,6 +1437,7 @@ impl Stylist {
Some(&selector_and_hashes.hashes),
&element,
&mut matching_context,
flags_setter,
));
true
},
@ -2071,17 +2061,6 @@ impl PartElementAndPseudoRules {
}
}
/// The id of a given layer, a sequentially-increasing identifier.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct LayerId(u16);
impl LayerId {
/// The id of the root layer.
pub const fn root() -> Self {
Self(0)
}
}
#[derive(Clone, Debug, MallocSizeOf)]
struct CascadeLayer {
id: LayerId,
@ -2099,35 +2078,6 @@ impl CascadeLayer {
}
}
/// The id of a given container condition, a sequentially-increasing identifier
/// for a given style set.
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, PartialOrd, Ord)]
pub struct ContainerConditionId(u16);
impl ContainerConditionId {
/// A special id that represents no container rule all.
pub const fn none() -> Self {
Self(0)
}
}
#[derive(Clone, Debug, MallocSizeOf)]
struct ContainerConditionReference {
parent: ContainerConditionId,
#[ignore_malloc_size_of = "Arc"]
condition: Option<Arc<ContainerCondition>>,
}
impl ContainerConditionReference {
const fn none() -> Self {
Self {
parent: ContainerConditionId::none(),
condition: None,
}
}
}
/// Data resulting from performing the CSS cascade that is specific to a given
/// origin.
///
@ -2199,9 +2149,6 @@ pub struct CascadeData {
/// The list of cascade layers, indexed by their layer id.
layers: SmallVec<[CascadeLayer; 1]>,
/// The list of container conditions, indexed by their id.
container_conditions: SmallVec<[ContainerConditionReference; 1]>,
/// Effective media query results cached from the last rebuild.
effective_media_query_results: EffectiveMediaQueryResults,
@ -2244,7 +2191,6 @@ impl CascadeData {
animations: Default::default(),
layer_id: Default::default(),
layers: smallvec::smallvec![CascadeLayer::root()],
container_conditions: smallvec::smallvec![ContainerConditionReference::none()],
extra_data: ExtraStyleData::default(),
effective_media_query_results: EffectiveMediaQueryResults::new(),
rules_source_order: 0,
@ -2359,23 +2305,6 @@ impl CascadeData {
self.layers[id.0 as usize].order
}
pub(crate) fn container_condition_matches<E>(&self, mut id: ContainerConditionId, stylist: &Stylist, element: E) -> bool
where
E: TElement,
{
loop {
let condition_ref = &self.container_conditions[id.0 as usize];
let condition = match condition_ref.condition {
None => return true,
Some(ref c) => c,
};
if !condition.matches(stylist.device(), element) {
return false;
}
id = condition_ref.parent;
}
}
fn did_finish_rebuild(&mut self) {
self.shrink_maps_if_needed();
self.compute_layer_order();
@ -2497,7 +2426,8 @@ impl CascadeData {
stylesheet: &S,
guard: &SharedRwLockReadGuard,
rebuild_kind: SheetRebuildKind,
containing_rule_state: &mut ContainingRuleState,
mut current_layer: &mut LayerName,
current_layer_id: LayerId,
mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>,
) -> Result<(), AllocErr>
where
@ -2520,7 +2450,7 @@ impl CascadeData {
if pseudo.is_precomputed() {
debug_assert!(selector.is_universal());
debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent);
debug_assert_eq!(containing_rule_state.layer_id, LayerId::root());
debug_assert_eq!(current_layer_id, LayerId::root());
precomputed_pseudo_element_decls
.as_mut()
@ -2547,8 +2477,7 @@ impl CascadeData {
hashes,
locked.clone(),
self.rules_source_order,
containing_rule_state.layer_id,
containing_rule_state.container_condition_id,
current_layer_id,
);
if rebuild_kind.should_rebuild_invalidation() {
@ -2626,7 +2555,7 @@ impl CascadeData {
self.animations.try_insert_with(
name,
animation,
containing_rule_state.layer_id,
current_layer_id,
compare_keyframes_in_same_layer,
)?;
},
@ -2635,35 +2564,25 @@ impl CascadeData {
// Note: Bug 1733260: we may drop @scroll-timeline rule once this spec issue
// https://github.com/w3c/csswg-drafts/issues/6674 gets landed.
self.extra_data
.add_scroll_timeline(guard, rule, containing_rule_state.layer_id)?;
.add_scroll_timeline(guard, rule, current_layer_id)?;
},
#[cfg(feature = "gecko")]
CssRule::FontFace(ref rule) => {
// NOTE(emilio): We don't care about container_condition_id
// because:
//
// Global, name-defining at-rules such as @keyframes or
// @font-face or @layer that are defined inside container
// queries are not constrained by the container query
// conditions.
//
// https://drafts.csswg.org/css-contain-3/#container-rule
// (Same elsewhere)
self.extra_data.add_font_face(rule, containing_rule_state.layer_id);
self.extra_data.add_font_face(rule, current_layer_id);
},
#[cfg(feature = "gecko")]
CssRule::FontFeatureValues(ref rule) => {
self.extra_data
.add_font_feature_values(rule, containing_rule_state.layer_id);
.add_font_feature_values(rule, current_layer_id);
},
#[cfg(feature = "gecko")]
CssRule::CounterStyle(ref rule) => {
self.extra_data
.add_counter_style(guard, rule, containing_rule_state.layer_id)?;
.add_counter_style(guard, rule, current_layer_id)?;
},
#[cfg(feature = "gecko")]
CssRule::Page(ref rule) => {
self.extra_data.add_page(guard, rule, containing_rule_state.layer_id)?;
self.extra_data.add_page(guard, rule, current_layer_id)?;
},
CssRule::Viewport(..) => {},
_ => {
@ -2704,7 +2623,7 @@ impl CascadeData {
if let Some(id) = data.layer_id.get(layer) {
return *id;
}
let id = LayerId(data.layers.len() as u16);
let id = LayerId(data.layers.len() as u32);
let parent_layer_id = if layer.layer_names().len() > 1 {
let mut parent = layer.clone();
@ -2735,8 +2654,9 @@ impl CascadeData {
fn maybe_register_layers(
data: &mut CascadeData,
name: Option<&LayerName>,
containing_rule_state: &mut ContainingRuleState,
) {
current_layer: &mut LayerName,
pushed_layers: &mut usize,
) -> LayerId {
let anon_name;
let name = match name {
Some(name) => name,
@ -2745,14 +2665,19 @@ impl CascadeData {
&anon_name
},
};
let mut id = LayerId::root();
for name in name.layer_names() {
containing_rule_state.layer_name.0.push(name.clone());
containing_rule_state.layer_id = maybe_register_layer(data, &containing_rule_state.layer_name);
current_layer.0.push(name.clone());
id = maybe_register_layer(data, &current_layer);
*pushed_layers += 1;
}
debug_assert_ne!(containing_rule_state.layer_id, LayerId::root());
debug_assert_ne!(id, LayerId::root());
id
}
let saved_containing_rule_state = containing_rule_state.save();
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);
@ -2761,10 +2686,11 @@ impl CascadeData {
.saw_effective(import_rule);
}
if let Some(ref layer) = import_rule.layer {
maybe_register_layers(
children_layer_id = maybe_register_layers(
self,
layer.name.as_ref(),
containing_rule_state
&mut current_layer,
&mut layer_names_to_pop,
);
}
},
@ -2776,29 +2702,25 @@ impl CascadeData {
},
CssRule::LayerBlock(ref lock) => {
let layer_rule = lock.read_with(guard);
maybe_register_layers(
children_layer_id = maybe_register_layers(
self,
layer_rule.name.as_ref(),
containing_rule_state,
&mut current_layer,
&mut layer_names_to_pop,
);
},
CssRule::LayerStatement(ref lock) => {
let layer_rule = lock.read_with(guard);
for name in &*layer_rule.names {
maybe_register_layers(self, Some(name), containing_rule_state);
// Register each layer individually.
containing_rule_state.restore(&saved_containing_rule_state);
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();
}
}
},
CssRule::Container(ref lock) => {
let container_rule = lock.read_with(guard);
let id = ContainerConditionId(self.container_conditions.len() as u16);
self.container_conditions.push(ContainerConditionReference {
parent: containing_rule_state.container_condition_id,
condition: Some(container_rule.condition.clone()),
});
containing_rule_state.container_condition_id = id;
},
// We don't care about any other rule.
_ => {},
}
@ -2811,12 +2733,15 @@ impl CascadeData {
stylesheet,
guard,
rebuild_kind,
containing_rule_state,
current_layer,
children_layer_id,
precomputed_pseudo_element_decls.as_deref_mut(),
)?;
}
containing_rule_state.restore(&saved_containing_rule_state);
for _ in 0..layer_names_to_pop {
current_layer.0.pop();
}
}
Ok(())
@ -2845,7 +2770,7 @@ impl CascadeData {
self.effective_media_query_results.saw_effective(contents);
}
let mut state = ContainingRuleState::default();
let mut current_layer = LayerName::new_empty();
self.add_rule_list(
contents.rules(guard).iter(),
device,
@ -2853,7 +2778,8 @@ impl CascadeData {
stylesheet,
guard,
rebuild_kind,
&mut state,
&mut current_layer,
LayerId::root(),
precomputed_pseudo_element_decls.as_deref_mut(),
)?;
@ -2901,7 +2827,6 @@ impl CascadeData {
CssRule::Style(..) |
CssRule::Namespace(..) |
CssRule::FontFace(..) |
CssRule::Container(..) |
CssRule::CounterStyle(..) |
CssRule::Supports(..) |
CssRule::Keyframes(..) |
@ -2979,8 +2904,6 @@ impl CascadeData {
self.layer_id.clear();
self.layers.clear();
self.layers.push(CascadeLayer::root());
self.container_conditions.clear();
self.container_conditions.push(ContainerConditionReference::none());
#[cfg(feature = "gecko")]
{
self.extra_data.clear();
@ -3075,9 +2998,6 @@ pub struct Rule {
/// The current layer id of this style rule.
pub layer_id: LayerId,
/// The current @container rule id.
pub container_condition_id: ContainerConditionId,
/// The actual style rule.
#[cfg_attr(
feature = "gecko",
@ -3123,7 +3043,6 @@ impl Rule {
style_rule: Arc<Locked<StyleRule>>,
source_order: u32,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
) -> Self {
Rule {
selector,
@ -3131,17 +3050,10 @@ impl Rule {
style_rule,
source_order,
layer_id,
container_condition_id,
}
}
}
// The size of this is critical to performance on the bloom-basic
// microbenchmark.
// When iterating over a large Rule array, we want to be able to fast-reject
// selectors (with the inline hashes) with as few cache misses as possible.
size_of_test!(Rule, 40);
/// A function to be able to test the revalidation stuff.
pub fn needs_revalidation_for_testing(s: &Selector<SelectorImpl>) -> bool {
let mut attribute_dependencies = Default::default();

View file

@ -16,13 +16,6 @@ use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags;
use selectors::NthIndexCache;
use smallvec::SmallVec;
use std::collections::HashMap;
/// A cache from element reference to known-valid computed style.
pub type UndisplayedStyleCache = HashMap<
selectors::OpaqueElement,
servo_arc::Arc<crate::properties::ComputedValues>,
>;
/// A per-traversal-level chunk of data. This is sent down by the traversal, and
/// currently only holds the dom depth for the bloom filter.
@ -301,7 +294,6 @@ pub fn resolve_style<E>(
element: E,
rule_inclusion: RuleInclusion,
pseudo: Option<&PseudoElement>,
mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>,
) -> ElementStyles
where
E: TElement,
@ -312,11 +304,6 @@ where
element.borrow_data().map_or(true, |d| !d.has_styles()),
"Why are we here?"
);
debug_assert!(
rule_inclusion == RuleInclusion::All || undisplayed_style_cache.is_none(),
"can't use the cache for default styles only"
);
let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();
// Clear the bloom filter, just in case the caller is reusing TLS.
@ -333,12 +320,6 @@ where
}
}
}
if let Some(ref mut cache) = undisplayed_style_cache {
if let Some(s) = cache.get(&current.opaque()) {
style = Some(s.clone());
break;
}
}
ancestors_requiring_style_resolution.push(current);
ancestor = current.traversal_parent();
}
@ -356,9 +337,7 @@ where
}
ancestor = ancestor.unwrap().traversal_parent();
layout_parent_style = ancestor.and_then(|a| {
a.borrow_data().map(|data| data.styles.primary().clone())
});
layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone());
}
for ancestor in ancestors_requiring_style_resolution.iter().rev() {
@ -381,27 +360,18 @@ where
layout_parent_style = style.clone();
}
if let Some(ref mut cache) = undisplayed_style_cache {
cache.insert(ancestor.opaque(), style.clone().unwrap());
}
context.thread_local.bloom_filter.push(*ancestor);
}
context.thread_local.bloom_filter.assert_complete(element);
let styles: ElementStyles = StyleResolverForElement::new(
StyleResolverForElement::new(
element,
context,
rule_inclusion,
PseudoElementResolution::Force,
)
.resolve_style(style.as_deref(), layout_parent_style.as_deref())
.into();
if let Some(ref mut cache) = undisplayed_style_cache {
cache.insert(element.opaque(), styles.primary().clone());
}
styles
.into()
}
/// Calculates the style for a single node.

File diff suppressed because it is too large Load diff

View file

@ -13,11 +13,9 @@ use crate::values::specified::box_ as specified;
pub use crate::values::specified::box_::{
AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin,
Clear as SpecifiedClear, Contain, ContainerName, ContainerType, ContentVisibility, Display,
Float as SpecifiedFloat, Overflow, OverflowAnchor, OverflowClipBox,
OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop,
ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction,
TransitionProperty, WillChange,
Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow,
OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis,
ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange,
};
/// A computed value for the `vertical-align` property.

View file

@ -7,7 +7,6 @@
use crate::values::animated::color::RGBA as AnimatedRGBA;
use crate::values::animated::ToAnimatedValue;
use crate::values::generics::color::{GenericCaretColor, GenericColor, GenericColorOrAuto};
use crate::values::computed::percentage::Percentage;
use cssparser::{Color as CSSParserColor, RGBA};
use std::fmt;
use style_traits::{CssWriter, ToCss};
@ -21,20 +20,7 @@ pub type ColorPropertyValue = RGBA;
pub type MozFontSmoothingBackgroundColor = RGBA;
/// A computed value for `<color>`.
pub type Color = GenericColor<RGBA, Percentage>;
impl ToCss for Color {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
match *self {
Self::Numeric(ref c) => c.to_css(dest),
Self::CurrentColor => CSSParserColor::CurrentColor.to_css(dest),
Self::ColorMix(ref m) => m.to_css(dest),
}
}
}
pub type Color = GenericColor<RGBA>;
impl Color {
/// Returns a complex color value representing transparent.
@ -42,21 +28,67 @@ impl Color {
Color::rgba(RGBA::transparent())
}
/// Returns opaque black.
pub fn black() -> Color {
Color::rgba(RGBA::new(0, 0, 0, 255))
}
/// Returns opaque white.
pub fn white() -> Color {
Color::rgba(RGBA::new(255, 255, 255, 255))
}
/// Combine this complex color with the given foreground color into
/// a numeric RGBA color.
pub fn into_rgba(mut self, current_color: RGBA) -> RGBA {
self.simplify(Some(&current_color));
*self.as_numeric().unwrap()
/// a numeric RGBA color. It currently uses linear blending.
pub fn to_rgba(&self, fg_color: RGBA) -> RGBA {
// Common cases that the complex color is either pure numeric color or
// pure currentcolor.
if self.is_numeric() {
return self.color;
}
if self.is_currentcolor() {
return fg_color;
}
let ratios = &self.ratios;
let color = &self.color;
// For the more complicated case that the alpha value differs,
// we use the following formula to compute the components:
// alpha = self_alpha * bg_ratio + fg_alpha * fg_ratio
// color = (self_color * self_alpha * bg_ratio +
// fg_color * fg_alpha * fg_ratio) / alpha
let p1 = ratios.bg;
let a1 = color.alpha_f32();
let r1 = a1 * color.red_f32();
let g1 = a1 * color.green_f32();
let b1 = a1 * color.blue_f32();
let p2 = ratios.fg;
let a2 = fg_color.alpha_f32();
let r2 = a2 * fg_color.red_f32();
let g2 = a2 * fg_color.green_f32();
let b2 = a2 * fg_color.blue_f32();
let a = p1 * a1 + p2 * a2;
if a <= 0. {
return RGBA::transparent();
}
let a = a.min(1.);
let inv = 1. / a;
let r = (p1 * r1 + p2 * r2) * inv;
let g = (p1 * g1 + p2 * g2) * inv;
let b = (p1 * b1 + p2 * b2) * inv;
RGBA::from_floats(r, g, b, a)
}
}
impl ToCss for Color {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
if self.is_currentcolor() {
return CSSParserColor::CurrentColor.to_css(dest);
}
if self.is_numeric() {
return self.color.to_css(dest);
}
Ok(())
}
}

View file

@ -31,9 +31,6 @@ pub use specified::ImageRendering;
pub type Image =
generic::GenericImage<Gradient, MozImageRect, ComputedImageUrl, Color, Percentage, Resolution>;
// Images should remain small, see https://github.com/servo/servo/pull/18430
size_of_test!(Image, 40);
/// Computed values for a CSS gradient.
/// <https://drafts.csswg.org/css-images/#gradients>
pub type Gradient = generic::GenericGradient<
@ -50,6 +47,8 @@ pub type Gradient = generic::GenericGradient<
/// Computed values for CSS cross-fade
/// <https://drafts.csswg.org/css-images-4/#cross-fade-function>
pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
/// A computed percentage or nothing.
pub type PercentOrNone = generic::PercentOrNone<Percentage>;
/// A computed radial gradient ending shape.
pub type EndingShape = generic::GenericEndingShape<NonNegativeLength, NonNegativeLengthPercentage>;

View file

@ -40,7 +40,7 @@ impl ToComputedValue for specified::NoCalcLength {
context
.builder
.add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS);
length.to_computed_value(context)
length.to_computed_value(context.viewport_size_for_viewport_unit_resolution())
},
specified::NoCalcLength::ServoCharacterWidth(length) => {
length.to_computed_value(context.style().get_font().clone_font_size().size())

View file

@ -16,7 +16,6 @@ use super::specified;
use super::{CSSFloat, CSSInteger};
use crate::computed_value_flags::ComputedValueFlags;
use crate::context::QuirksMode;
use crate::stylesheets::container_rule::ContainerInfo;
use crate::font_metrics::{FontMetrics, FontMetricsOrientation};
use crate::media_queries::Device;
#[cfg(feature = "gecko")]
@ -45,12 +44,11 @@ pub use self::basic_shape::FillRule;
pub use self::border::{BorderCornerRadius, BorderRadius, BorderSpacing};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderImageSlice, BorderImageWidth};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, ContainerName, ContainerType};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain};
pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, ContentVisibility, Float};
pub use self::box_::{Display, Overflow, OverflowAnchor, TransitionProperty};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop};
pub use self::box_::{ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust};
pub use self::column::ColumnCount;
@ -74,7 +72,7 @@ pub use self::list::ListStyleType;
pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle;
pub use self::page::{PageOrientation, PageName, PageSize, PaperSize};
pub use self::page::{Orientation, PageName, PageSize, PaperSize};
pub use self::percentage::{NonNegativePercentage, Percentage};
pub use self::position::AspectRatio;
pub use self::position::{
@ -99,7 +97,6 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate};
pub use self::ui::CursorImage;
pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect};
pub use super::specified::TextTransform;
pub use super::specified::ViewportVariant;
pub use super::specified::{BorderStyle, TextDecorationLine};
pub use super::{Auto, Either, None_};
pub use app_units::Au;
@ -172,9 +169,6 @@ pub struct Context<'a> {
/// values, which SMIL allows.
pub for_smil_animation: bool,
/// Returns the container information to evaluate a given container query.
pub container_info: Option<ContainerInfo>,
/// The property we are computing a value for, if it is a non-inherited
/// property. None if we are computed a value for an inherited property
/// or not computing for a property at all (e.g. in a media query
@ -203,40 +197,6 @@ impl<'a> Context<'a> {
in_media_query: true,
quirks_mode,
for_smil_animation: false,
container_info: None,
for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions),
};
f(&context)
}
/// Creates a suitable context for container query evaluation for the style
/// specified.
pub fn for_container_query_evaluation<F, R>(
device: &Device,
container_info_and_style: Option<(ContainerInfo, Arc<ComputedValues>)>,
f: F,
) -> R
where
F: FnOnce(&Context) -> R,
{
let mut conditions = RuleCacheConditions::default();
let (container_info, style) = match container_info_and_style {
Some((ci, s)) => (Some(ci), Some(s)),
None => (None, None),
};
let style = style.as_ref().map(|s| &**s);
let quirks_mode = device.quirks_mode();
let context = Context {
builder: StyleBuilder::for_inheritance(device, style, None),
cached_system_font: None,
in_media_query: true,
quirks_mode,
for_smil_animation: false,
container_info,
for_non_inherited_property: None,
rule_cache_conditions: RefCell::new(&mut conditions),
};
@ -293,13 +253,10 @@ impl<'a> Context<'a> {
}
/// The current viewport size, used to resolve viewport units.
pub fn viewport_size_for_viewport_unit_resolution(
&self,
variant: ViewportVariant,
) -> default::Size2D<Au> {
pub fn viewport_size_for_viewport_unit_resolution(&self) -> default::Size2D<Au> {
self.builder
.device
.au_viewport_size_for_viewport_unit_resolution(variant)
.au_viewport_size_for_viewport_unit_resolution()
}
/// The default computed style we're getting our reset style from.
@ -343,8 +300,8 @@ impl<'a, 'cx, 'cx_a: 'cx, S: ToComputedValue + 'a> ComputedVecIter<'a, 'cx, 'cx_
/// Construct an iterator from a slice of specified values and a context
pub fn new(cx: &'cx Context<'cx_a>, values: &'a [S]) -> Self {
ComputedVecIter {
cx,
values,
cx: cx,
values: values,
}
}
}

View file

@ -11,7 +11,7 @@ use crate::values::generics::size::Size2D;
use crate::values::specified::page as specified;
pub use generics::page::GenericPageSize;
pub use generics::page::PageOrientation;
pub use generics::page::Orientation;
pub use generics::page::PaperSize;
pub use specified::PageName;
@ -26,7 +26,7 @@ pub enum PageSize {
/// Specified size, paper size, or paper size and orientation.
Size(Size2D<NonNegativeLength>),
/// `landscape` or `portrait` value, no specified size.
Orientation(PageOrientation),
Orientation(Orientation),
/// `auto` value
Auto,
}
@ -37,11 +37,11 @@ impl ToComputedValue for specified::PageSize {
fn to_computed_value(&self, ctx: &Context) -> Self::ComputedValue {
match &*self {
Self::Size(s) => PageSize::Size(s.to_computed_value(ctx)),
Self::PaperSize(p, PageOrientation::Landscape) => PageSize::Size(Size2D {
Self::PaperSize(p, Orientation::Landscape) => PageSize::Size(Size2D {
width: p.long_edge().to_computed_value(ctx),
height: p.short_edge().to_computed_value(ctx),
}),
Self::PaperSize(p, PageOrientation::Portrait) => PageSize::Size(Size2D {
Self::PaperSize(p, Orientation::Portrait) => PageSize::Size(Size2D {
width: p.short_edge().to_computed_value(ctx),
height: p.long_edge().to_computed_value(ctx),
}),

View file

@ -6,7 +6,6 @@
use crate::values::animated::ToAnimatedValue;
use crate::values::generics::NonNegative;
use crate::values::specified::percentage::ToPercentage;
use crate::values::{serialize_percentage, CSSFloat};
use crate::Zero;
use std::fmt;
@ -65,12 +64,6 @@ impl Zero for Percentage {
}
}
impl ToPercentage for Percentage {
fn to_percentage(&self) -> CSSFloat {
self.0
}
}
impl std::ops::AddAssign for Percentage {
fn add_assign(&mut self, other: Self) {
self.0 += other.0

View file

@ -72,16 +72,6 @@ impl ComputeSquaredDistance for Ratio {
}
}
impl Zero for Ratio {
fn zero() -> Self {
Self::new(Zero::zero(), One::one())
}
fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
impl Ratio {
/// Returns a new Ratio.
#[inline]

View file

@ -8,6 +8,7 @@ use crate::values::computed::color::Color;
use crate::values::computed::url::ComputedUrl;
use crate::values::computed::{LengthPercentage, NonNegativeLengthPercentage, Opacity};
use crate::values::generics::svg as generic;
use crate::values::RGBA;
use crate::Zero;
pub use crate::values::specified::{DProperty, MozContextProperties, SVGPaintOrder};
@ -21,8 +22,9 @@ pub type SVGPaintKind = generic::GenericSVGPaintKind<Color, ComputedUrl>;
impl SVGPaint {
/// Opaque black color
pub fn black() -> Self {
let rgba = RGBA::from_floats(0., 0., 0., 1.).into();
SVGPaint {
kind: generic::SVGPaintKind::Color(Color::black()),
kind: generic::SVGPaintKind::Color(rgba),
fallback: generic::SVGPaintFallback::Unset,
}
}

View file

@ -45,33 +45,13 @@ pub enum SortKey {
Cap,
Ch,
Deg,
Dvb,
Dvh,
Dvi,
Dvmax,
Dvmin,
Dvw,
Em,
Ex,
Ic,
Lvb,
Lvh,
Lvi,
Lvmax,
Lvmin,
Lvw,
Px,
Rem,
Sec,
Svb,
Svh,
Svi,
Svmax,
Svmin,
Svw,
Vb,
Vh,
Vi,
Vmax,
Vmin,
Vw,

View file

@ -4,268 +4,81 @@
//! Generic types for color properties.
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, ToCss};
use crate::values::{Parse, ParserContext, Parser};
use crate::values::specified::percentage::ToPercentage;
use crate::values::animated::ToAnimatedValue;
use crate::values::animated::color::RGBA as AnimatedRGBA;
/// Ratios representing the contribution of color and currentcolor to
/// the final color value.
///
/// NOTE(emilio): For animated colors, the sum of these two might be more than
/// one (because the background color would've been scaled down already). So
/// beware that it is not generally safe to assume that if bg is 1 then fg is 0,
/// for example.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[repr(C)]
pub struct ComplexColorRatios {
/// Numeric color contribution.
pub bg: f32,
/// currentcolor contribution.
pub fg: f32,
}
impl ComplexColorRatios {
/// Ratios representing a `Numeric` color.
pub const NUMERIC: ComplexColorRatios = ComplexColorRatios { bg: 1., fg: 0. };
/// Ratios representing the `CurrentColor` color.
pub const CURRENT_COLOR: ComplexColorRatios = ComplexColorRatios { bg: 0., fg: 1. };
}
/// This struct represents a combined color from a numeric color and
/// the current foreground color (currentcolor keyword).
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
#[repr(C)]
pub enum GenericColor<RGBA, Percentage> {
pub struct GenericColor<RGBA> {
/// The actual numeric color.
Numeric(RGBA),
/// The `CurrentColor` keyword.
CurrentColor,
/// The color-mix() function.
ColorMix(Box<GenericColorMix<Self, Percentage>>),
}
/// A color space as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-color-space
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
#[repr(u8)]
pub enum ColorSpace {
/// The sRGB color space.
Srgb,
/// The linear-sRGB color space.
LinearSrgb,
/// The CIEXYZ color space.
#[parse(aliases = "xyz-d65")]
Xyz,
/// https://drafts.csswg.org/css-color-4/#valdef-color-xyz
XyzD50,
/// The CIELAB color space.
Lab,
/// https://drafts.csswg.org/css-color-4/#valdef-hsl-hsl
Hsl,
/// https://drafts.csswg.org/css-color-4/#valdef-hwb-hwb
Hwb,
/// The CIELAB color space, expressed in cylindrical coordinates.
Lch,
// TODO: Oklab, Lch
}
impl ColorSpace {
/// Returns whether this is a `<polar-color-space>`.
pub fn is_polar(self) -> bool {
match self {
Self::Srgb | Self::LinearSrgb | Self::Xyz | Self::XyzD50 | Self::Lab => false,
Self::Hsl | Self::Hwb | Self::Lch => true,
}
}
}
/// A hue-interpolation-method as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-4/#typedef-hue-interpolation-method
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
#[repr(u8)]
pub enum HueInterpolationMethod {
/// https://drafts.csswg.org/css-color-4/#shorter
Shorter,
/// https://drafts.csswg.org/css-color-4/#longer
Longer,
/// https://drafts.csswg.org/css-color-4/#increasing
Increasing,
/// https://drafts.csswg.org/css-color-4/#decreasing
Decreasing,
/// https://drafts.csswg.org/css-color-4/#specified
Specified,
}
/// https://drafts.csswg.org/css-color-4/#color-interpolation-method
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem, ToAnimatedValue, ToComputedValue, ToResolvedValue)]
#[repr(C)]
pub struct ColorInterpolationMethod {
/// The color-space the interpolation should be done in.
pub space: ColorSpace,
/// The hue interpolation method.
pub hue: HueInterpolationMethod,
}
impl ColorInterpolationMethod {
/// Returns the srgb interpolation method.
pub fn srgb() -> Self {
Self {
space: ColorSpace::Srgb,
hue: HueInterpolationMethod::Shorter,
}
}
}
impl Parse for ColorInterpolationMethod {
fn parse<'i, 't>(
_: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
input.expect_ident_matching("in")?;
let space = ColorSpace::parse(input)?;
// https://drafts.csswg.org/css-color-4/#hue-interpolation
// Unless otherwise specified, if no specific hue interpolation
// algorithm is selected by the host syntax, the default is shorter.
let hue = if space.is_polar() {
input.try_parse(|input| -> Result<_, ParseError<'i>> {
let hue = HueInterpolationMethod::parse(input)?;
input.expect_ident_matching("hue")?;
Ok(hue)
}).unwrap_or(HueInterpolationMethod::Shorter)
} else {
HueInterpolationMethod::Shorter
};
Ok(Self { space, hue })
}
}
impl ToCss for ColorInterpolationMethod {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
dest.write_str("in ")?;
self.space.to_css(dest)?;
if self.hue != HueInterpolationMethod::Shorter {
dest.write_char(' ')?;
self.hue.to_css(dest)?;
dest.write_str(" hue")?;
}
Ok(())
}
}
/// A restricted version of the css `color-mix()` function, which only supports
/// percentages.
///
/// https://drafts.csswg.org/css-color-5/#color-mix
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToComputedValue, ToResolvedValue, ToShmem)]
#[allow(missing_docs)]
#[repr(C)]
pub struct GenericColorMix<Color, Percentage> {
pub interpolation: ColorInterpolationMethod,
pub left: Color,
pub left_percentage: Percentage,
pub right: Color,
pub right_percentage: Percentage,
pub normalize_weights: bool,
}
pub use self::GenericColorMix as ColorMix;
impl<Color: ToCss, Percentage: ToCss + ToPercentage> ToCss for ColorMix<Color, Percentage> {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
fn can_omit<Percentage: ToPercentage>(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
if percent.is_calc() {
return false;
}
if percent.to_percentage() == 0.5 {
return other.to_percentage() == 0.5;
}
if is_left {
return false;
}
(1.0 - percent.to_percentage() - other.to_percentage()).abs() <= f32::EPSILON
}
dest.write_str("color-mix(")?;
self.interpolation.to_css(dest)?;
dest.write_str(", ")?;
self.left.to_css(dest)?;
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
dest.write_str(" ")?;
self.left_percentage.to_css(dest)?;
}
dest.write_str(", ")?;
self.right.to_css(dest)?;
if !can_omit(&self.right_percentage, &self.left_percentage, false) {
dest.write_str(" ")?;
self.right_percentage.to_css(dest)?;
}
dest.write_str(")")
}
}
impl<RGBA, Percentage> ColorMix<GenericColor<RGBA, Percentage>, Percentage> {
fn to_rgba(&self) -> Option<RGBA>
where
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
Percentage: ToPercentage,
{
use crate::values::animated::color::Color as AnimatedColor;
let left = self.left.as_numeric()?.clone().to_animated_value();
let right = self.right.as_numeric()?.clone().to_animated_value();
Some(ToAnimatedValue::from_animated_value(AnimatedColor::mix(
&self.interpolation,
&left,
self.left_percentage.to_percentage(),
&right,
self.right_percentage.to_percentage(),
self.normalize_weights,
)))
}
pub color: RGBA,
/// The ratios of mixing between numeric and currentcolor.
/// The formula is: `color * ratios.bg + currentcolor * ratios.fg`.
pub ratios: ComplexColorRatios,
}
pub use self::GenericColor as Color;
impl<RGBA, Percentage> Color<RGBA, Percentage> {
/// Returns the numeric rgba value if this color is numeric, or None
/// otherwise.
pub fn as_numeric(&self) -> Option<&RGBA> {
match *self {
Self::Numeric(ref rgba) => Some(rgba),
_ => None,
}
}
/// Simplifies the color-mix()es to the extent possible given a current
/// color (or not).
pub fn simplify(&mut self, current_color: Option<&RGBA>)
where
RGBA: Clone + ToAnimatedValue<AnimatedValue = AnimatedRGBA>,
Percentage: ToPercentage,
{
match *self {
Self::Numeric(..) => {},
Self::CurrentColor => {
if let Some(c) = current_color {
*self = Self::Numeric(c.clone());
}
},
Self::ColorMix(ref mut mix) => {
mix.left.simplify(current_color);
mix.right.simplify(current_color);
if let Some(mix) = mix.to_rgba() {
*self = Self::Numeric(mix);
}
},
}
}
impl Color<cssparser::RGBA> {
/// Returns a color value representing currentcolor.
pub fn currentcolor() -> Self {
Self::CurrentColor
Color {
color: cssparser::RGBA::transparent(),
ratios: ComplexColorRatios::CURRENT_COLOR,
}
}
}
impl<RGBA> Color<RGBA> {
/// Create a color based upon the specified ratios.
pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self {
Self { color, ratios }
}
/// Returns a numeric color representing the given RGBA value.
pub fn rgba(color: RGBA) -> Self {
Self::Numeric(color)
}
/// Whether it is a currentcolor value (no numeric color component).
pub fn is_currentcolor(&self) -> bool {
matches!(*self, Self::CurrentColor)
Self {
color,
ratios: ComplexColorRatios::NUMERIC,
}
}
/// Whether it is a numeric color (no currentcolor component).
pub fn is_numeric(&self) -> bool {
matches!(*self, Self::Numeric(..))
self.ratios == ComplexColorRatios::NUMERIC
}
/// Whether it is a currentcolor value (no numeric color component).
pub fn is_currentcolor(&self) -> bool {
self.ratios == ComplexColorRatios::CURRENT_COLOR
}
}
impl<RGBA> From<RGBA> for Color<RGBA> {
fn from(color: RGBA) -> Self {
Self::rgba(color)
}
}

View file

@ -8,7 +8,6 @@
use crate::custom_properties;
use crate::values::generics::position::PositionComponent;
use crate::values::generics::Optional;
use crate::values::serialize_atom_identifier;
use crate::Atom;
use crate::Zero;
@ -72,6 +71,20 @@ pub struct GenericCrossFade<Image, Color, Percentage> {
pub elements: crate::OwnedSlice<GenericCrossFadeElement<Image, Color, Percentage>>,
}
/// A `<percent> | none` value. Represents optional percentage values
/// assosicated with cross-fade images.
#[derive(
Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
)]
#[repr(C, u8)]
pub enum PercentOrNone<Percentage> {
/// `none` variant.
#[css(skip)]
None,
/// A percentage variant.
Percent(Percentage),
}
/// An optional percent and a cross fade image.
#[derive(
Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, ToCss,
@ -79,7 +92,7 @@ pub struct GenericCrossFade<Image, Color, Percentage> {
#[repr(C)]
pub struct GenericCrossFadeElement<Image, Color, Percentage> {
/// The percent of the final image that `image` will be.
pub percent: Optional<Percentage>,
pub percent: PercentOrNone<Percentage>,
/// A color or image that will be blended when cross-fade is
/// evaluated.
pub image: GenericCrossFadeImage<Image, Color>,

View file

@ -310,74 +310,3 @@ impl<L> ClipRectOrAuto<L> {
}
pub use page::PageSize;
/// An optional value, much like `Option<T>`, but with a defined struct layout
/// to be able to use it from C++ as well.
///
/// Note that this is relatively inefficient, struct-layout-wise, as you have
/// one byte for the tag, but padding to the alignment of T. If you have
/// multiple optional values and care about struct compactness, you might be
/// better off "coalescing" the combinations into a parent enum. But that
/// shouldn't matter for most use cases.
#[allow(missing_docs)]
#[derive(
Animate,
Clone,
ComputeSquaredDistance,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToAnimatedValue,
ToAnimatedZero,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(C, u8)]
pub enum Optional<T> {
#[css(skip)]
None,
Some(T),
}
impl<T> Optional<T> {
/// Returns whether this value is present.
pub fn is_some(&self) -> bool {
matches!(*self, Self::Some(..))
}
/// Returns whether this value is not present.
pub fn is_none(&self) -> bool {
matches!(*self, Self::None)
}
/// Turns this Optional<> into a regular rust Option<>.
pub fn into_rust(self) -> Option<T> {
match self {
Self::Some(v) => Some(v),
Self::None => None,
}
}
/// Return a reference to the containing value, if any, as a plain rust
/// Option<>.
pub fn as_ref(&self) -> Option<&T> {
match *self {
Self::Some(ref v) => Some(v),
Self::None => None,
}
}
}
impl<T> From<Option<T>> for Optional<T> {
fn from(rust: Option<T>) -> Self {
match rust {
Some(t) => Self::Some(t),
None => Self::None,
}
}
}

View file

@ -87,7 +87,7 @@ impl PaperSize {
ToShmem,
)]
#[repr(u8)]
pub enum PageOrientation {
pub enum Orientation {
/// Portrait orientation
Portrait,
/// Landscape orientation
@ -95,8 +95,8 @@ pub enum PageOrientation {
}
#[inline]
fn is_portrait(orientation: &PageOrientation) -> bool {
*orientation == PageOrientation::Portrait
fn is_portrait(orientation: &Orientation) -> bool {
*orientation == Orientation::Portrait
}
/// Page size property
@ -110,9 +110,9 @@ pub enum GenericPageSize<S> {
/// Page dimensions.
Size(S),
/// An orientation with no size.
Orientation(PageOrientation),
Orientation(Orientation),
/// Paper size by name
PaperSize(PaperSize, #[css(skip_if = "is_portrait")] PageOrientation),
PaperSize(PaperSize, #[css(skip_if = "is_portrait")] Orientation),
}
pub use self::GenericPageSize as PageSize;

View file

@ -16,6 +16,7 @@ pub use cssparser::{SourceLocation, Token, RGBA};
use precomputed_hash::PrecomputedHash;
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Write};
use std::hash;
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
use to_shmem::impl_trivial_to_shmem;
@ -457,32 +458,27 @@ impl CustomIdent {
ident: &CowRcStr<'i>,
excluding: &[&str],
) -> Result<Self, ParseError<'i>> {
if !Self::is_valid(ident, excluding) {
return Err(
location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
);
}
if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(CustomIdent(Atom::from(ident.as_ref())))
}
}
fn is_valid(ident: &str, excluding: &[&str]) -> bool {
use crate::properties::CSSWideKeyword;
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// The CSS-wide keywords are not valid <custom-ident>s. The default
// keyword is reserved and is also not a valid <custom-ident>.
//
if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") {
return false;
return Err(
location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))
);
}
// https://drafts.csswg.org/css-values-4/#custom-idents:
//
// Excluded keywords are excluded in all ASCII case permutations.
!excluding.iter().any(|s| ident.eq_ignore_ascii_case(s))
//
if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} else {
Ok(CustomIdent(Atom::from(ident.as_ref())))
}
}
}
@ -500,72 +496,45 @@ impl ToCss for CustomIdent {
///
/// <https://drafts.csswg.org/css-animations-2/#typedef-timeline-name>
/// <https://drafts.csswg.org/css-animations/#typedef-keyframes-name>
///
/// We use a single atom for these. Empty atom represents `none` animation.
#[repr(transparent)]
#[derive(
Clone, Debug, Hash, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
pub struct TimelineOrKeyframesName(Atom);
#[repr(C, u8)]
pub enum TimelineOrKeyframesName {
/// <custom-ident>
Ident(CustomIdent),
/// <string>
QuotedString(Atom),
}
impl TimelineOrKeyframesName {
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
pub fn from_ident(value: &str) -> Self {
Self(Atom::from(value))
}
/// Returns the `none` value.
pub fn none() -> Self {
Self(atom!(""))
}
/// Returns whether this is the special `none` value.
pub fn is_none(&self) -> bool {
self.0 == atom!("")
let location = SourceLocation { line: 0, column: 0 };
let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok();
match custom_ident {
Some(ident) => Self::Ident(ident),
None => Self::QuotedString(value.into()),
}
}
/// Create a new TimelineOrKeyframesName from Atom.
#[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self {
Self(atom)
debug_assert_ne!(atom, atom!(""));
// FIXME: We might want to preserve <string>, but currently Gecko
// stores both of <custom-ident> and <string> into nsAtom, so
// we can't tell it.
Self::Ident(CustomIdent(atom))
}
/// The name as an Atom
pub fn as_atom(&self) -> &Atom {
&self.0
}
fn parse<'i, 't>(input: &mut Parser<'i, 't>, invalid: &[&str]) -> Result<Self, ParseError<'i>> {
debug_assert!(invalid.contains(&"none"));
let location = input.current_source_location();
Ok(match *input.next()? {
Token::Ident(ref s) => Self(CustomIdent::from_ident(location, s, invalid)?.0),
Token::QuotedString(ref s) => Self(Atom::from(s.as_ref())),
ref t => return Err(location.new_unexpected_token_error(t.clone())),
})
}
fn to_css<W>(&self, dest: &mut CssWriter<W>, invalid: &[&str]) -> fmt::Result
where
W: Write,
{
debug_assert!(invalid.contains(&"none"));
if self.0 == atom!("") {
return dest.write_str("none")
match *self {
Self::Ident(ref ident) => &ident.0,
Self::QuotedString(ref atom) => atom,
}
let mut serialize = |s: &_| {
if CustomIdent::is_valid(s, invalid) {
serialize_identifier(s, dest)
} else {
s.to_css(dest)
}
};
#[cfg(feature = "gecko")]
return self.0.with_str(|s| serialize(s));
#[cfg(feature = "servo")]
return serialize(self.0.as_ref());
}
}
@ -578,70 +547,53 @@ pub trait IsAuto {
fn is_auto(&self) -> bool;
}
/// The typedef of <timeline-name>.
#[repr(transparent)]
#[derive(
Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
pub struct TimelineName(TimelineOrKeyframesName);
impl TimelineName {
/// Returns the `none` value.
pub fn none() -> Self {
Self(TimelineOrKeyframesName::none())
impl PartialEq for TimelineOrKeyframesName {
fn eq(&self, other: &Self) -> bool {
self.as_atom() == other.as_atom()
}
}
impl Parse for TimelineName {
fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Ok(Self(TimelineOrKeyframesName::parse(input, &["none", "auto"])?))
impl hash::Hash for TimelineOrKeyframesName {
fn hash<H>(&self, state: &mut H)
where
H: hash::Hasher,
{
self.as_atom().hash(state)
}
}
impl ToCss for TimelineName {
impl Parse for TimelineOrKeyframesName {
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Ident(ref s) => Ok(Self::Ident(CustomIdent::from_ident(
location,
s,
&["none"],
)?)),
Token::QuotedString(ref s) => Ok(Self::QuotedString(Atom::from(s.as_ref()))),
ref t => Err(location.new_unexpected_token_error(t.clone())),
}
}
}
impl ToCss for TimelineOrKeyframesName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.0.to_css(dest, &["none", "auto"])
match *self {
Self::Ident(ref ident) => ident.to_css(dest),
Self::QuotedString(ref atom) => atom.to_string().to_css(dest),
}
}
}
/// The typedef of <timeline-name>.
pub type TimelineName = TimelineOrKeyframesName;
/// The typedef of <keyframes-name>.
#[derive(
Clone, Debug, Deref, Hash, Eq, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem,
)]
pub struct KeyframesName(TimelineOrKeyframesName);
impl KeyframesName {
/// Create a new KeyframesName from Atom.
#[cfg(feature = "gecko")]
pub fn from_atom(atom: Atom) -> Self {
Self(TimelineOrKeyframesName::from_atom(atom))
}
/// <https://drafts.csswg.org/css-animations/#dom-csskeyframesrule-name>
pub fn from_ident(value: &str) -> Self {
Self(TimelineOrKeyframesName::from_ident(value))
}
/// Returns the `none` value.
pub fn none() -> Self {
Self(TimelineOrKeyframesName::none())
}
}
impl Parse for KeyframesName {
fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
Ok(Self(TimelineOrKeyframesName::parse(input, &["none"])?))
}
}
impl ToCss for KeyframesName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
self.0.to_css(dest, &["none"])
}
}
pub type KeyframesName = TimelineOrKeyframesName;

View file

@ -17,6 +17,7 @@ use crate::values::{CustomIdent, KeyframesName, TimelineName};
use crate::Atom;
use cssparser::Parser;
use num_traits::FromPrimitive;
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Debug, Formatter, Write};
use style_traits::{CssWriter, KeywordsCollectFn, ParseError};
use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
@ -679,30 +680,33 @@ impl AnimationIterationCount {
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[value_info(other_values = "none")]
pub struct AnimationName(pub KeyframesName);
pub struct AnimationName(pub Option<KeyframesName>);
impl AnimationName {
/// Get the name of the animation as an `Atom`.
pub fn as_atom(&self) -> Option<&Atom> {
if self.is_none() {
return None;
}
Some(self.0.as_atom())
self.0.as_ref().map(|n| n.as_atom())
}
/// Returns the `none` value.
pub fn none() -> Self {
AnimationName(KeyframesName::none())
AnimationName(None)
}
}
/// Returns whether this is the none value.
pub fn is_none(&self) -> bool {
self.0.is_none()
impl ToCss for AnimationName {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match self.0 {
Some(ref name) => name.to_css(dest),
None => dest.write_str("none"),
}
}
}
@ -712,88 +716,14 @@ impl Parse for AnimationName {
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) {
return Ok(AnimationName(name));
return Ok(AnimationName(Some(name)));
}
input.expect_ident_matching("none")?;
Ok(AnimationName(KeyframesName::none()))
Ok(AnimationName(None))
}
}
/// A value for the <Scroller> used in scroll().
///
/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-scroller
#[derive(
Clone,
Debug,
Eq,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum Scroller {
/// The nearest ancestor scroll container. (Default.)
Nearest,
/// The document viewport as the scroll container.
Root,
// FIXME: Bug 1764450: Once we support container-name CSS property (Bug 1744224), we may add
// <custom-ident> here, based on the result of the spec issue:
// https://github.com/w3c/csswg-drafts/issues/7046
}
impl Default for Scroller {
fn default() -> Self {
Self::Nearest
}
}
/// A value for the <Axis> used in scroll().
///
/// https://drafts.csswg.org/scroll-animations-1/rewrite#typedef-axis
#[derive(
Clone,
Debug,
Eq,
Hash,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ScrollAxis {
/// The block axis of the scroll container. (Default.)
Block,
/// The inline axis of the scroll container.
Inline,
/// The vertical block axis of the scroll container.
Vertical,
/// The horizontal axis of the scroll container.
Horizontal,
}
impl Default for ScrollAxis {
fn default() -> Self {
Self::Block
}
}
#[inline]
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
*value == Default::default()
}
/// A value for the <single-animation-timeline>.
///
/// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline
@ -815,15 +745,10 @@ fn is_default<T: Default + PartialEq>(value: &T) -> bool {
pub enum AnimationTimeline {
/// Use default timeline. The animations timeline is a DocumentTimeline.
Auto,
/// The animation is not associated with a timeline.
None,
/// The scroll-timeline name
Timeline(TimelineName),
/// The scroll() notation
/// https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation
#[css(function)]
Scroll(
#[css(skip_if = "is_default")] ScrollAxis,
#[css(skip_if = "is_default")] Scroller,
),
}
impl AnimationTimeline {
@ -844,30 +769,16 @@ impl Parse for AnimationTimeline {
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 the "none" keyword).
//
// 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:
// https://github.com/w3c/csswg-drafts/issues/6674
//
// If `none` is removed, then we could potentially shrink this the same
// way we deal with animation-name.
// 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(AnimationTimeline::Timeline(TimelineName::none()));
}
// https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation
if input.try_parse(|i| i.expect_function_matching("scroll")).is_ok() {
return input.parse_nested_block(|i| {
Ok(Self::Scroll(
i.try_parse(ScrollAxis::parse).unwrap_or(ScrollAxis::Block),
i.try_parse(Scroller::parse).unwrap_or(Scroller::Nearest),
))
});
return Ok(Self::None);
}
TimelineName::parse(context, input).map(AnimationTimeline::Timeline)
@ -1080,28 +991,6 @@ impl ToCss for ScrollSnapAlign {
}
}
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone,
Copy,
Debug,
Eq,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToComputedValue,
ToCss,
ToResolvedValue,
ToShmem,
)]
#[repr(u8)]
pub enum ScrollSnapStop {
Normal,
Always,
}
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
@ -1307,9 +1196,7 @@ impl Parse for WillChange {
&["will-change", "none", "all", "auto"],
)?;
if context.in_ua_sheet() && ident.0 == atom!("-moz-fixed-pos-containing-block") {
bits |= WillChangeBits::FIXPOS_CB_NON_SVG;
} else if ident.0 == atom!("scroll-position") {
if ident.0 == atom!("scroll-position") {
bits |= WillChangeBits::SCROLL;
} else {
bits |= change_bits_for_maybe_property(&parser_ident, context);
@ -1326,8 +1213,9 @@ impl Parse for WillChange {
bitflags! {
/// Values for the `touch-action` property.
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)]
#[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))]
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
/// These constants match Gecko's `NS_STYLE_TOUCH_ACTION_*` constants.
#[value_info(other_values = "auto,none,manipulation,pan-x,pan-y,pinch-zoom")]
#[repr(C)]
pub struct TouchAction: u8 {
/// `none` variant
@ -1353,32 +1241,178 @@ impl TouchAction {
}
}
impl ToCss for TouchAction {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.contains(TouchAction::AUTO) {
return dest.write_str("auto");
}
if self.contains(TouchAction::NONE) {
return dest.write_str("none");
}
if self.contains(TouchAction::MANIPULATION) {
return dest.write_str("manipulation");
}
let mut has_any = false;
macro_rules! maybe_write_value {
($ident:path => $str:expr) => {
if self.contains($ident) {
if has_any {
dest.write_str(" ")?;
}
has_any = true;
dest.write_str($str)?;
}
};
}
maybe_write_value!(TouchAction::PAN_X => "pan-x");
maybe_write_value!(TouchAction::PAN_Y => "pan-y");
maybe_write_value!(TouchAction::PINCH_ZOOM => "pinch-zoom");
debug_assert!(has_any);
Ok(())
}
}
impl Parse for TouchAction {
/// auto | none | [ pan-x || pan-y || pinch-zoom ] | manipulation
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<TouchAction, ParseError<'i>> {
let mut result = TouchAction::empty();
while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) {
let flag = match_ignore_ascii_case! { &name,
"pan-x" => Some(TouchAction::PAN_X),
"pan-y" => Some(TouchAction::PAN_Y),
"pinch-zoom" => Some(TouchAction::PINCH_ZOOM),
"none" if result.is_empty() => return Ok(TouchAction::NONE),
"manipulation" if result.is_empty() => return Ok(TouchAction::MANIPULATION),
"auto" if result.is_empty() => return Ok(TouchAction::AUTO),
_ => None
};
let flag = match flag {
Some(flag) if !result.contains(flag) => flag,
_ => {
return Err(
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name))
);
},
};
result.insert(flag);
}
if !result.is_empty() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
bitflags! {
#[derive(MallocSizeOf, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
#[css(bitflags(single = "none,strict,content", mixed="size,layout,paint,inline-size", overlapping_bits))]
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "none,strict,content,size,layout,paint")]
#[repr(C)]
/// Constants for contain: https://drafts.csswg.org/css-contain/#contain-property
pub struct Contain: u8 {
/// `none` variant, just for convenience.
const NONE = 0;
/// `inline-size` variant, turns on single-axis inline size containment
const INLINE_SIZE = 1 << 0;
/// `block-size` variant, turns on single-axis block size containment, internal only
const BLOCK_SIZE = 1 << 1;
/// `layout` variant, turns on layout containment
const LAYOUT = 1 << 2;
/// `paint` variant, turns on paint containment
const PAINT = 1 << 3;
/// 'size' variant, turns on size containment
const SIZE = 1 << 4 | Contain::INLINE_SIZE.bits | Contain::BLOCK_SIZE.bits;
/// `content` variant, turns on layout and paint containment
const CONTENT = 1 << 5 | Contain::LAYOUT.bits | Contain::PAINT.bits;
const SIZE = 1 << 0;
/// `layout` variant, turns on layout containment
const LAYOUT = 1 << 1;
/// `paint` variant, turns on paint containment
const PAINT = 1 << 2;
/// `strict` variant, turns on all types of containment
const STRICT = 1 << 6 | Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits;
const STRICT = 1 << 3;
/// 'content' variant, turns on layout and paint containment
const CONTENT = 1 << 4;
/// variant with all the bits that contain: strict turns on
const STRICT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits;
/// variant with all the bits that contain: content turns on
const CONTENT_BITS = Contain::LAYOUT.bits | Contain::PAINT.bits;
}
}
/// https://drafts.csswg.org/css-contain-2/#content-visibility
impl ToCss for Contain {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("none");
}
if self.contains(Contain::STRICT) {
return dest.write_str("strict");
}
if self.contains(Contain::CONTENT) {
return dest.write_str("content");
}
let mut has_any = false;
macro_rules! maybe_write_value {
($ident:path => $str:expr) => {
if self.contains($ident) {
if has_any {
dest.write_str(" ")?;
}
has_any = true;
dest.write_str($str)?;
}
};
}
maybe_write_value!(Contain::SIZE => "size");
maybe_write_value!(Contain::LAYOUT => "layout");
maybe_write_value!(Contain::PAINT => "paint");
debug_assert!(has_any);
Ok(())
}
}
impl Parse for Contain {
/// none | strict | content | [ size || layout || paint ]
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Contain, ParseError<'i>> {
let mut result = Contain::empty();
while let Ok(name) = input.try_parse(|i| i.expect_ident_cloned()) {
let flag = match_ignore_ascii_case! { &name,
"size" => Some(Contain::SIZE),
"layout" => Some(Contain::LAYOUT),
"paint" => Some(Contain::PAINT),
"strict" if result.is_empty() => return Ok(Contain::STRICT | Contain::STRICT_BITS),
"content" if result.is_empty() => return Ok(Contain::CONTENT | Contain::CONTENT_BITS),
"none" if result.is_empty() => return Ok(result),
_ => None
};
let flag = match flag {
Some(flag) if !result.contains(flag) => flag,
_ => {
return Err(
input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name))
);
},
};
result.insert(flag);
}
if !result.is_empty() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
Clone,
@ -1406,61 +1440,6 @@ pub enum ContentVisibility {
Visible,
}
bitflags! {
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, Parse, ToResolvedValue, ToShmem)]
#[repr(C)]
#[allow(missing_docs)]
#[css(bitflags(single="none", mixed="style,size,inline-size", overlapping_bits))]
/// https://drafts.csswg.org/css-contain-3/#container-type
///
/// TODO: block-size is on the spec but it seems it was removed? WPTs don't
/// support it, see https://github.com/w3c/csswg-drafts/issues/7179.
pub struct ContainerType: u8 {
/// The `none` variant.
const NONE = 0;
/// The `style` variant.
const STYLE = 1 << 0;
/// The `inline-size` variant.
const INLINE_SIZE = 1 << 1;
/// The `size` variant, exclusive with `inline-size` (they sharing bits
/// guarantees this).
const SIZE = 1 << 2 | Self::INLINE_SIZE.bits;
}
}
/// https://drafts.csswg.org/css-contain-3/#container-name
#[repr(transparent)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)]
pub struct ContainerName(#[css(iterable, if_empty = "none")] pub crate::OwnedSlice<CustomIdent>);
impl ContainerName {
/// Return the `none` value.
pub fn none() -> Self {
Self(Default::default())
}
/// Returns whether this is the `none` value.
pub fn is_none(&self) -> bool {
self.0.is_empty()
}
}
impl Parse for ContainerName {
fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
let mut idents = vec![];
let location = input.current_source_location();
let first = input.expect_ident()?;
if first.eq_ignore_ascii_case("none") {
return Ok(Self::none())
}
idents.push(CustomIdent::from_ident(location, first, &["none"])?);
while let Ok(ident) = input.try_parse(|input| input.expect_ident_cloned()) {
idents.push(CustomIdent::from_ident(location, &ident, &["none"])?);
}
Ok(ContainerName(idents.into()))
}
}
/// A specified value for the `perspective` property.
pub type Perspective = GenericPerspective<NonNegativeLength>;
@ -1911,6 +1890,10 @@ pub enum Appearance {
MozMacSourceList,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacSourceListSelection,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacVibrantTitlebarDark,
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
MozMacVibrantTitlebarLight,
/// A themed focus outline (for outline:auto).
///
@ -2118,9 +2101,9 @@ impl Overflow {
}
bitflags! {
#[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)]
#[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "auto,stable,both-edges")]
#[repr(C)]
#[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))]
/// Values for scrollbar-gutter:
/// <https://drafts.csswg.org/css-overflow-3/#scrollbar-gutter-property>
pub struct ScrollbarGutter: u8 {
@ -2133,9 +2116,56 @@ bitflags! {
}
}
impl ScrollbarGutter {
#[inline]
fn has_stable(self) -> bool {
self.intersects(Self::STABLE)
impl ToCss for ScrollbarGutter {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("auto");
}
debug_assert!(
self.contains(ScrollbarGutter::STABLE),
"We failed to parse the syntax!"
);
dest.write_str("stable")?;
if self.contains(ScrollbarGutter::BOTH_EDGES) {
dest.write_str(" both-edges")?;
}
Ok(())
}
}
impl Parse for ScrollbarGutter {
/// auto | stable && both-edges?
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<ScrollbarGutter, ParseError<'i>> {
if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
return Ok(ScrollbarGutter::AUTO);
}
let mut result = ScrollbarGutter::empty();
while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) {
let flag = match_ignore_ascii_case! { &ident,
"stable" => Some(ScrollbarGutter::STABLE),
"both-edges" => Some(ScrollbarGutter::BOTH_EDGES),
_ => None
};
match flag {
Some(flag) if !result.contains(flag) => result.insert(flag),
_ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
if result.contains(ScrollbarGutter::STABLE) {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}

View file

@ -190,29 +190,9 @@ impl generic::CalcNodeLeaf for Leaf {
},
NoCalcLength::ViewportPercentage(ref vp) => match *vp {
ViewportPercentageLength::Vh(..) => SortKey::Vh,
ViewportPercentageLength::Svh(..) => SortKey::Svh,
ViewportPercentageLength::Lvh(..) => SortKey::Lvh,
ViewportPercentageLength::Dvh(..) => SortKey::Dvh,
ViewportPercentageLength::Vw(..) => SortKey::Vw,
ViewportPercentageLength::Svw(..) => SortKey::Svw,
ViewportPercentageLength::Lvw(..) => SortKey::Lvw,
ViewportPercentageLength::Dvw(..) => SortKey::Dvw,
ViewportPercentageLength::Vmax(..) => SortKey::Vmax,
ViewportPercentageLength::Svmax(..) => SortKey::Svmax,
ViewportPercentageLength::Lvmax(..) => SortKey::Lvmax,
ViewportPercentageLength::Dvmax(..) => SortKey::Dvmax,
ViewportPercentageLength::Vmin(..) => SortKey::Vmin,
ViewportPercentageLength::Svmin(..) => SortKey::Svmin,
ViewportPercentageLength::Lvmin(..) => SortKey::Lvmin,
ViewportPercentageLength::Dvmin(..) => SortKey::Dvmin,
ViewportPercentageLength::Vb(..) => SortKey::Vb,
ViewportPercentageLength::Svb(..) => SortKey::Svb,
ViewportPercentageLength::Lvb(..) => SortKey::Lvb,
ViewportPercentageLength::Dvb(..) => SortKey::Dvb,
ViewportPercentageLength::Vi(..) => SortKey::Vi,
ViewportPercentageLength::Svi(..) => SortKey::Svi,
ViewportPercentageLength::Lvi(..) => SortKey::Lvi,
ViewportPercentageLength::Dvi(..) => SortKey::Dvi,
},
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
},

View file

@ -7,7 +7,7 @@
use super::AllowQuirks;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
use crate::values::generics::color::{ColorInterpolationMethod, GenericColorMix, GenericCaretColor, GenericColorOrAuto};
use crate::values::generics::color::{GenericCaretColor, GenericColorOrAuto};
use crate::values::specified::calc::CalcNode;
use crate::values::specified::Percentage;
use crate::values::CustomIdent;
@ -19,8 +19,52 @@ use std::io::Write as IoWrite;
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind};
/// A specified color-mix().
pub type ColorMix = GenericColorMix<Color, Percentage>;
/// A color space as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-colorspace
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum ColorSpaceKind {
/// The sRGB color space.
Srgb,
/// The CIEXYZ color space.
Xyz,
/// The CIELAB color space.
Lab,
/// The CIELAB color space, expressed in cylindrical coordinates.
Lch,
}
/// A hue adjuster as defined in [1].
///
/// [1]: https://drafts.csswg.org/css-color-5/#typedef-hue-adjuster
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum HueAdjuster {
/// The "shorter" angle adjustment.
Shorter,
/// The "longer" angle adjustment.
Longer,
/// The "increasing" angle adjustment.
Increasing,
/// The "decreasing" angle adjustment.
Decreasing,
/// The "specified" angle adjustment.
Specified,
}
/// A restricted version of the css `color-mix()` function, which only supports
/// percentages.
///
/// https://drafts.csswg.org/css-color-5/#color-mix
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub struct ColorMix {
pub color_space: ColorSpaceKind,
pub left: Color,
pub left_percentage: Percentage,
pub right: Color,
pub right_percentage: Percentage,
pub hue_adjuster: HueAdjuster,
}
#[inline]
fn allow_color_mix() -> bool {
@ -30,6 +74,18 @@ fn allow_color_mix() -> bool {
return false;
}
#[inline]
fn allow_color_mix_color_spaces() -> bool {
#[cfg(feature = "gecko")]
return static_prefs::pref!("layout.css.color-mix.color-spaces.enabled");
#[cfg(feature = "servo")]
return false;
}
// NOTE(emilio): Syntax is still a bit in-flux, since [1] doesn't seem
// particularly complete, and disagrees with the examples.
//
// [1]: https://github.com/w3c/csswg-drafts/commit/a4316446112f9e814668c2caff7f826f512f8fed
impl Parse for ColorMix {
fn parse<'i, 't>(
context: &ParserContext,
@ -42,58 +98,100 @@ impl Parse for ColorMix {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let color_spaces_enabled = context.chrome_rules_enabled() ||
allow_color_mix_color_spaces();
input.expect_function_matching("color-mix")?;
// NOTE(emilio): This implements the syntax described here for now,
// might need to get updated in the future.
//
// https://github.com/w3c/csswg-drafts/issues/6066#issuecomment-789836765
input.parse_nested_block(|input| {
let interpolation = ColorInterpolationMethod::parse(context, input)?;
input.expect_comma()?;
let try_parse_percentage = |input: &mut Parser| -> Option<Percentage> {
input
.try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input))
.ok()
input.expect_ident_matching("in")?;
let color_space = if color_spaces_enabled {
ColorSpaceKind::parse(input)?
} else {
input.expect_ident_matching("srgb")?;
ColorSpaceKind::Srgb
};
let mut left_percentage = try_parse_percentage(input);
input.expect_comma()?;
let left = Color::parse(context, input)?;
if left_percentage.is_none() {
left_percentage = try_parse_percentage(input);
}
let left_percentage = input
.try_parse(|input| Percentage::parse(context, input))
.ok();
input.expect_comma()?;
let mut right_percentage = try_parse_percentage(input);
let right = Color::parse(context, input)?;
if right_percentage.is_none() {
right_percentage = try_parse_percentage(input);
}
let right_percentage = right_percentage
.unwrap_or_else(|| Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get())));
let right_percentage = input
.try_parse(|input| Percentage::parse(context, input))
.unwrap_or_else(|_| {
Percentage::new(1.0 - left_percentage.map_or(0.5, |p| p.get()))
});
let left_percentage =
left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get()));
if left_percentage.get() + right_percentage.get() <= 0.0 {
// If the percentages sum to zero, the function is invalid.
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
let hue_adjuster = input
.try_parse(|input| HueAdjuster::parse(input))
.unwrap_or(HueAdjuster::Shorter);
Ok(ColorMix {
interpolation,
color_space,
left,
left_percentage,
right,
right_percentage,
normalize_weights: true,
hue_adjuster,
})
})
}
}
impl ToCss for ColorMix {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
fn can_omit(percent: &Percentage, other: &Percentage, is_left: bool) -> bool {
if percent.is_calc() {
return false;
}
if percent.get() == 0.5 {
return other.get() == 0.5;
}
if is_left {
return false;
}
(1.0 - percent.get() - other.get()).abs() <= f32::EPSILON
}
dest.write_str("color-mix(in ")?;
self.color_space.to_css(dest)?;
dest.write_str(", ")?;
self.left.to_css(dest)?;
if !can_omit(&self.left_percentage, &self.right_percentage, true) {
dest.write_str(" ")?;
self.left_percentage.to_css(dest)?;
}
dest.write_str(", ")?;
self.right.to_css(dest)?;
if !can_omit(&self.right_percentage, &self.left_percentage, false) {
dest.write_str(" ")?;
self.right_percentage.to_css(dest)?;
}
if self.hue_adjuster != HueAdjuster::Shorter {
dest.write_str(" ")?;
self.hue_adjuster.to_css(dest)?;
}
dest.write_str(")")
}
}
/// Specified color value
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum Color {
@ -106,6 +204,8 @@ pub enum Color {
/// Authored representation
authored: Option<Box<str>>,
},
/// A complex color value from computed value
Complex(ComputedColor),
/// A system color.
#[cfg(feature = "gecko")]
System(SystemColor),
@ -287,6 +387,10 @@ pub enum SystemColor {
/// Inactive light hightlight
MozMacSecondaryhighlight,
/// Font smoothing background colors needed by the Mac OS X theme, based on
/// -moz-appearance names.
MozMacVibrantTitlebarLight,
MozMacVibrantTitlebarDark,
MozMacMenupopup,
MozMacMenuitem,
MozMacActiveMenuitem,
@ -493,6 +597,8 @@ impl ToCss for Color {
Color::Numeric {
parsed: ref rgba, ..
} => rgba.to_css(dest),
// TODO: Could represent this as a color-mix() instead.
Color::Complex(_) => Ok(()),
Color::ColorMix(ref mix) => mix.to_css(dest),
#[cfg(feature = "gecko")]
Color::System(system) => system.to_css(dest),
@ -638,23 +744,23 @@ impl Color {
/// the context to resolve, then `None` is returned.
pub fn to_computed_color(&self, context: Option<&Context>) -> Option<ComputedColor> {
Some(match *self {
Color::CurrentColor => ComputedColor::CurrentColor,
Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed),
Color::CurrentColor => ComputedColor::currentcolor(),
Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed),
Color::Complex(ref complex) => *complex,
Color::ColorMix(ref mix) => {
use crate::values::computed::percentage::Percentage;
use crate::values::animated::color::Color as AnimatedColor;
use crate::values::animated::ToAnimatedValue;
let left = mix.left.to_computed_color(context)?;
let right = mix.right.to_computed_color(context)?;
let mut color = ComputedColor::ColorMix(Box::new(GenericColorMix {
interpolation: mix.interpolation,
left,
left_percentage: Percentage(mix.left_percentage.get()),
right,
right_percentage: Percentage(mix.right_percentage.get()),
normalize_weights: mix.normalize_weights,
}));
color.simplify(None);
color
let left = mix.left.to_computed_color(context)?.to_animated_value();
let right = mix.right.to_computed_color(context)?.to_animated_value();
ToAnimatedValue::from_animated_value(AnimatedColor::mix(
mix.color_space,
&left,
mix.left_percentage.get(),
&right,
mix.right_percentage.get(),
mix.hue_adjuster,
))
},
#[cfg(feature = "gecko")]
Color::System(system) => system.compute(context?),
@ -672,13 +778,13 @@ impl ToComputedValue for Color {
}
fn from_computed_value(computed: &ComputedColor) -> Self {
match *computed {
ComputedColor::Numeric(ref color) => Color::rgba(*color),
ComputedColor::CurrentColor => Color::CurrentColor,
ComputedColor::ColorMix(ref mix) => {
Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))
},
if computed.is_numeric() {
return Color::rgba(computed.color);
}
if computed.is_currentcolor() {
return Color::currentcolor();
}
Color::Complex(*computed)
}
}
@ -706,7 +812,7 @@ impl ToComputedValue for MozFontSmoothingBackgroundColor {
fn to_computed_value(&self, context: &Context) -> RGBA {
self.0
.to_computed_value(context)
.into_rgba(RGBA::transparent())
.to_rgba(RGBA::transparent())
}
fn from_computed_value(computed: &RGBA) -> Self {
@ -723,15 +829,7 @@ impl SpecifiedValueInfo for Color {
// should probably be handled that way as well.
// XXX `currentColor` should really be `currentcolor`. But let's
// keep it consistent with the old system for now.
f(&[
"rgb",
"rgba",
"hsl",
"hsla",
"hwb",
"currentColor",
"transparent",
]);
f(&["rgb", "rgba", "hsl", "hsla", "hwb", "currentColor", "transparent"]);
}
}
@ -748,7 +846,7 @@ impl ToComputedValue for ColorPropertyValue {
fn to_computed_value(&self, context: &Context) -> RGBA {
self.0
.to_computed_value(context)
.into_rgba(context.builder.get_parent_inherited_text().clone_color())
.to_rgba(context.builder.get_parent_inherited_text().clone_color())
}
#[inline]
@ -910,19 +1008,7 @@ impl ToCss for ColorScheme {
}
/// https://drafts.csswg.org/css-color-adjust/#print-color-adjust
#[derive(
Clone,
Copy,
Debug,
MallocSizeOf,
Parse,
PartialEq,
SpecifiedValueInfo,
ToCss,
ToComputedValue,
ToResolvedValue,
ToShmem,
)]
#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
#[repr(u8)]
pub enum PrintColorAdjust {
/// Ignore backgrounds and darken text.

View file

@ -39,9 +39,6 @@ use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss};
pub type Image =
generic::Image<Gradient, MozImageRect, SpecifiedImageUrl, Color, Percentage, Resolution>;
// Images should remain small, see https://github.com/servo/servo/pull/18430
size_of_test!(Image, 40);
/// Specified values for a CSS gradient.
/// <https://drafts.csswg.org/css-images/#gradients>
pub type Gradient = generic::Gradient<
@ -63,6 +60,8 @@ pub type CrossFade = generic::CrossFade<Image, Color, Percentage>;
pub type CrossFadeElement = generic::CrossFadeElement<Image, Color, Percentage>;
/// CrossFadeImage = image | color
pub type CrossFadeImage = generic::CrossFadeImage<Image, Color>;
/// A specified percentage or nothing.
pub type PercentOrNone = generic::PercentOrNone<Percentage>;
/// `image-set()`
pub type ImageSet = generic::ImageSet<Image, Resolution>;
@ -316,16 +315,6 @@ impl CrossFade {
}
impl CrossFadeElement {
fn parse_percentage<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Option<Percentage> {
// We clamp our values here as this is the way that Safari and Chrome's
// implementation handle out-of-bounds percentages but whether or not
// this behavior follows the specification is still being discussed.
// See: <https://github.com/w3c/csswg-drafts/issues/5333>
input.try_parse(|input| Percentage::parse_non_negative(context, input))
.ok()
.map(|p| p.clamp_to_hundred())
}
/// <cf-image> = <percentage>? && [ <image> | <color> ]
fn parse<'i, 't>(
context: &ParserContext,
@ -333,17 +322,14 @@ impl CrossFadeElement {
cors_mode: CorsMode,
) -> Result<Self, ParseError<'i>> {
// Try and parse a leading percent sign.
let mut percent = Self::parse_percentage(context, input);
let mut percent = PercentOrNone::parse_or_none(context, input);
// Parse the image
let image = CrossFadeImage::parse(context, input, cors_mode)?;
// Try and parse a trailing percent sign.
if percent.is_none() {
percent = Self::parse_percentage(context, input);
if percent == PercentOrNone::None {
percent = PercentOrNone::parse_or_none(context, input);
}
Ok(Self {
percent: percent.into(),
image,
})
Ok(Self { percent, image })
}
}
@ -365,6 +351,22 @@ impl CrossFadeImage {
}
}
impl PercentOrNone {
fn parse_or_none<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Self {
// We clamp our values here as this is the way that Safari and
// Chrome's implementation handle out-of-bounds percentages
// but whether or not this behavior follows the specification
// is still being discussed. See:
// <https://github.com/w3c/csswg-drafts/issues/5333>
if let Ok(percent) = input.try_parse(|input| Percentage::parse_non_negative(context, input))
{
Self::Percent(percent.clamp_to_hundred())
} else {
Self::None
}
}
}
impl ImageSet {
fn parse<'i, 't>(
context: &ParserContext,
@ -733,12 +735,12 @@ impl Gradient {
if items.is_empty() {
items = vec![
generic::GradientItem::ComplexColorStop {
color: Color::transparent(),
position: LengthPercentage::zero_percent(),
color: Color::transparent().into(),
position: Percentage::zero().into(),
},
generic::GradientItem::ComplexColorStop {
color: Color::transparent(),
position: LengthPercentage::hundred_percent(),
color: Color::transparent().into(),
position: Percentage::hundred().into(),
},
];
} else if items.len() == 1 {

View file

@ -21,6 +21,7 @@ use crate::values::CSSFloat;
use crate::Zero;
use app_units::Au;
use cssparser::{Parser, Token};
use euclid::default::Size2D;
use std::cmp;
use std::ops::{Add, Mul};
use style_traits::values::specified::AllowedNumericType;
@ -277,249 +278,45 @@ impl FontRelativeLength {
}
}
/// https://drafts.csswg.org/css-values/#viewport-variants
pub enum ViewportVariant {
/// https://drafts.csswg.org/css-values/#ua-default-viewport-size
UADefault,
/// https://drafts.csswg.org/css-values/#small-viewport-percentage-units
Small,
/// https://drafts.csswg.org/css-values/#large-viewport-percentage-units
Large,
/// https://drafts.csswg.org/css-values/#dynamic-viewport-percentage-units
Dynamic,
}
/// https://drafts.csswg.org/css-values/#viewport-relative-units
#[derive(PartialEq)]
enum ViewportUnit {
/// *vw units.
Vw,
/// *vh units.
Vh,
/// *vmin units.
Vmin,
/// *vmax units.
Vmax,
/// *vb units.
Vb,
/// *vi units.
Vi,
}
/// A viewport-relative length.
///
/// <https://drafts.csswg.org/css-values/#viewport-relative-lengths>
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)]
pub enum ViewportPercentageLength {
/// <https://drafts.csswg.org/css-values/#valdef-length-vw>
/// A vw unit: https://drafts.csswg.org/css-values/#vw
#[css(dimension)]
Vw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svw>
#[css(dimension)]
Svw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvw>
#[css(dimension)]
Lvw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvw>
#[css(dimension)]
Dvw(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vh>
/// A vh unit: https://drafts.csswg.org/css-values/#vh
#[css(dimension)]
Vh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svh>
#[css(dimension)]
Svh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvh>
#[css(dimension)]
Lvh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvh>
#[css(dimension)]
Dvh(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vmin>
/// <https://drafts.csswg.org/css-values/#vmin>
#[css(dimension)]
Vmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svmin>
#[css(dimension)]
Svmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvmin>
#[css(dimension)]
Lvmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvmin>
#[css(dimension)]
Dvmin(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vmax>
/// <https://drafts.csswg.org/css-values/#vmax>
#[css(dimension)]
Vmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svmax>
#[css(dimension)]
Svmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvmax>
#[css(dimension)]
Lvmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvmax>
#[css(dimension)]
Dvmax(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vb>
#[css(dimension)]
Vb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svb>
#[css(dimension)]
Svb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvb>
#[css(dimension)]
Lvb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvb>
#[css(dimension)]
Dvb(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-vi>
#[css(dimension)]
Vi(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-svi>
#[css(dimension)]
Svi(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-lvi>
#[css(dimension)]
Lvi(CSSFloat),
/// <https://drafts.csswg.org/css-values/#valdef-length-dvi>
#[css(dimension)]
Dvi(CSSFloat),
}
impl ViewportPercentageLength {
/// Return true if this is a zero value.
fn is_zero(&self) -> bool {
let (_, _, v) = self.unpack();
v == 0.
match *self {
ViewportPercentageLength::Vw(v) |
ViewportPercentageLength::Vh(v) |
ViewportPercentageLength::Vmin(v) |
ViewportPercentageLength::Vmax(v) => v == 0.,
}
}
fn is_negative(&self) -> bool {
let (_, _, v) = self.unpack();
v < 0.
}
fn unpack(&self) -> (ViewportVariant, ViewportUnit, CSSFloat) {
match *self {
ViewportPercentageLength::Vw(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Svw(v) => (
ViewportVariant::Small,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Lvw(v) => (
ViewportVariant::Large,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Dvw(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vw,
v,
),
ViewportPercentageLength::Vh(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vh,
v,
),
ViewportPercentageLength::Svh(v) => (
ViewportVariant::Small,
ViewportUnit::Vh,
v,
),
ViewportPercentageLength::Lvh(v) => (
ViewportVariant::Large,
ViewportUnit::Vh,
v),
ViewportPercentageLength::Dvh(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vh,
v,
),
ViewportPercentageLength::Vmin(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Svmin(v) => (
ViewportVariant::Small,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Lvmin(v) => (
ViewportVariant::Large,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Dvmin(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vmin,
v,
),
ViewportPercentageLength::Vmax(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Svmax(v) => (
ViewportVariant::Small,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Lvmax(v) => (
ViewportVariant::Large,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Dvmax(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vmax,
v,
),
ViewportPercentageLength::Vb(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Svb(v) => (
ViewportVariant::Small,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Lvb(v) => (
ViewportVariant::Large,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Dvb(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vb,
v,
),
ViewportPercentageLength::Vi(v) => (
ViewportVariant::UADefault,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Svi(v) => (
ViewportVariant::Small,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Lvi(v) => (
ViewportVariant::Large,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Dvi(v) => (
ViewportVariant::Dynamic,
ViewportUnit::Vi,
v,
),
ViewportPercentageLength::Vw(v) |
ViewportPercentageLength::Vh(v) |
ViewportPercentageLength::Vmin(v) |
ViewportPercentageLength::Vmax(v) => v < 0.,
}
}
fn try_sum(&self, other: &Self) -> Result<Self, ()> {
use self::ViewportPercentageLength::*;
@ -529,39 +326,14 @@ impl ViewportPercentageLength {
Ok(match (self, other) {
(&Vw(one), &Vw(other)) => Vw(one + other),
(&Svw(one), &Svw(other)) => Svw(one + other),
(&Lvw(one), &Lvw(other)) => Lvw(one + other),
(&Dvw(one), &Dvw(other)) => Dvw(one + other),
(&Vh(one), &Vh(other)) => Vh(one + other),
(&Svh(one), &Svh(other)) => Svh(one + other),
(&Lvh(one), &Lvh(other)) => Lvh(one + other),
(&Dvh(one), &Dvh(other)) => Dvh(one + other),
(&Vmin(one), &Vmin(other)) => Vmin(one + other),
(&Svmin(one), &Svmin(other)) => Svmin(one + other),
(&Lvmin(one), &Lvmin(other)) => Lvmin(one + other),
(&Dvmin(one), &Dvmin(other)) => Dvmin(one + other),
(&Vmax(one), &Vmax(other)) => Vmax(one + other),
(&Svmax(one), &Svmax(other)) => Svmax(one + other),
(&Lvmax(one), &Lvmax(other)) => Lvmax(one + other),
(&Dvmax(one), &Dvmax(other)) => Dvmax(one + other),
(&Vb(one), &Vb(other)) => Vb(one + other),
(&Svb(one), &Svb(other)) => Svb(one + other),
(&Lvb(one), &Lvb(other)) => Lvb(one + other),
(&Dvb(one), &Dvb(other)) => Dvb(one + other),
(&Vi(one), &Vi(other)) => Vi(one + other),
(&Svi(one), &Svi(other)) => Svi(one + other),
(&Lvi(one), &Lvi(other)) => Lvi(one + other),
(&Dvi(one), &Dvi(other)) => Dvi(one + other),
// See https://github.com/rust-lang/rust/issues/68867. rustc isn't
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Vw(..) | Svw(..) | Lvw(..) | Dvw(..) |
Vh(..) | Svh(..) | Lvh(..) | Dvh(..) |
Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) |
Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) |
Vb(..) | Svb(..) | Lvb(..) | Dvb(..) |
Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {},
Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_sum()")
},
@ -569,24 +341,15 @@ impl ViewportPercentageLength {
}
/// Computes the given viewport-relative length for the given viewport size.
pub fn to_computed_value(&self, context: &Context) -> CSSPixelLength {
let (variant, unit, factor) = self.unpack();
let size = context.viewport_size_for_viewport_unit_resolution(variant);
let length = match unit {
ViewportUnit::Vw => size.width,
ViewportUnit::Vh => size.height,
ViewportUnit::Vmin => cmp::min(size.width, size.height),
ViewportUnit::Vmax => cmp::max(size.width, size.height),
ViewportUnit::Vi | ViewportUnit::Vb => {
context
.rule_cache_conditions
.borrow_mut()
.set_writing_mode_dependency(context.builder.writing_mode);
if (unit == ViewportUnit::Vb) == context.style().writing_mode.is_vertical() {
size.width
} else {
size.height
}
pub fn to_computed_value(&self, viewport_size: Size2D<Au>) -> CSSPixelLength {
let (factor, length) = match *self {
ViewportPercentageLength::Vw(length) => (length, viewport_size.width),
ViewportPercentageLength::Vh(length) => (length, viewport_size.height),
ViewportPercentageLength::Vmin(length) => {
(length, cmp::min(viewport_size.width, viewport_size.height))
},
ViewportPercentageLength::Vmax(length) => {
(length, cmp::max(viewport_size.width, viewport_size.height))
},
};
@ -815,75 +578,15 @@ impl NoCalcLength {
"vw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(value))
},
"svw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svw(value))
},
"lvw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvw(value))
},
"dvw" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvw(value))
},
"vh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(value))
},
"svh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svh(value))
},
"lvh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvh(value))
},
"dvh" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvh(value))
},
"vmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmin(value))
},
"svmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmin(value))
},
"lvmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmin(value))
},
"dvmin" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmin(value))
},
"vmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vmax(value))
},
"svmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svmax(value))
},
"lvmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvmax(value))
},
"dvmax" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvmax(value))
},
"vb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vb(value))
},
"svb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svb(value))
},
"lvb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvb(value))
},
"dvb" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvb(value))
},
"vi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vi(value))
},
"svi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Svi(value))
},
"lvi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Lvi(value))
},
"dvi" if !context.in_page_rule() => {
NoCalcLength::ViewportPercentage(ViewportPercentageLength::Dvi(value))
},
_ => return Err(()),
})
}
@ -1067,29 +770,9 @@ impl Mul<CSSFloat> for ViewportPercentageLength {
fn mul(self, scalar: CSSFloat) -> ViewportPercentageLength {
match self {
ViewportPercentageLength::Vw(v) => ViewportPercentageLength::Vw(v * scalar),
ViewportPercentageLength::Svw(v) => ViewportPercentageLength::Svw(v * scalar),
ViewportPercentageLength::Lvw(v) => ViewportPercentageLength::Lvw(v * scalar),
ViewportPercentageLength::Dvw(v) => ViewportPercentageLength::Dvw(v * scalar),
ViewportPercentageLength::Vh(v) => ViewportPercentageLength::Vh(v * scalar),
ViewportPercentageLength::Svh(v) => ViewportPercentageLength::Svh(v * scalar),
ViewportPercentageLength::Lvh(v) => ViewportPercentageLength::Lvh(v * scalar),
ViewportPercentageLength::Dvh(v) => ViewportPercentageLength::Dvh(v * scalar),
ViewportPercentageLength::Vmin(v) => ViewportPercentageLength::Vmin(v * scalar),
ViewportPercentageLength::Svmin(v) => ViewportPercentageLength::Svmin(v * scalar),
ViewportPercentageLength::Lvmin(v) => ViewportPercentageLength::Lvmin(v * scalar),
ViewportPercentageLength::Dvmin(v) => ViewportPercentageLength::Dvmin(v * scalar),
ViewportPercentageLength::Vmax(v) => ViewportPercentageLength::Vmax(v * scalar),
ViewportPercentageLength::Svmax(v) => ViewportPercentageLength::Svmax(v * scalar),
ViewportPercentageLength::Lvmax(v) => ViewportPercentageLength::Lvmax(v * scalar),
ViewportPercentageLength::Dvmax(v) => ViewportPercentageLength::Dvmax(v * scalar),
ViewportPercentageLength::Vb(v) => ViewportPercentageLength::Vb(v * scalar),
ViewportPercentageLength::Svb(v) => ViewportPercentageLength::Svb(v * scalar),
ViewportPercentageLength::Lvb(v) => ViewportPercentageLength::Lvb(v * scalar),
ViewportPercentageLength::Dvb(v) => ViewportPercentageLength::Dvb(v * scalar),
ViewportPercentageLength::Vi(v) => ViewportPercentageLength::Vi(v * scalar),
ViewportPercentageLength::Svi(v) => ViewportPercentageLength::Svi(v * scalar),
ViewportPercentageLength::Lvi(v) => ViewportPercentageLength::Lvi(v * scalar),
ViewportPercentageLength::Dvi(v) => ViewportPercentageLength::Dvi(v * scalar),
}
}
}
@ -1104,39 +787,14 @@ impl PartialOrd for ViewportPercentageLength {
match (self, other) {
(&Vw(ref one), &Vw(ref other)) => one.partial_cmp(other),
(&Svw(ref one), &Svw(ref other)) => one.partial_cmp(other),
(&Lvw(ref one), &Lvw(ref other)) => one.partial_cmp(other),
(&Dvw(ref one), &Dvw(ref other)) => one.partial_cmp(other),
(&Vh(ref one), &Vh(ref other)) => one.partial_cmp(other),
(&Svh(ref one), &Svh(ref other)) => one.partial_cmp(other),
(&Lvh(ref one), &Lvh(ref other)) => one.partial_cmp(other),
(&Dvh(ref one), &Dvh(ref other)) => one.partial_cmp(other),
(&Vmin(ref one), &Vmin(ref other)) => one.partial_cmp(other),
(&Svmin(ref one), &Svmin(ref other)) => one.partial_cmp(other),
(&Lvmin(ref one), &Lvmin(ref other)) => one.partial_cmp(other),
(&Dvmin(ref one), &Dvmin(ref other)) => one.partial_cmp(other),
(&Vmax(ref one), &Vmax(ref other)) => one.partial_cmp(other),
(&Svmax(ref one), &Svmax(ref other)) => one.partial_cmp(other),
(&Lvmax(ref one), &Lvmax(ref other)) => one.partial_cmp(other),
(&Dvmax(ref one), &Dvmax(ref other)) => one.partial_cmp(other),
(&Vb(ref one), &Vb(ref other)) => one.partial_cmp(other),
(&Svb(ref one), &Svb(ref other)) => one.partial_cmp(other),
(&Lvb(ref one), &Lvb(ref other)) => one.partial_cmp(other),
(&Dvb(ref one), &Dvb(ref other)) => one.partial_cmp(other),
(&Vi(ref one), &Vi(ref other)) => one.partial_cmp(other),
(&Svi(ref one), &Svi(ref other)) => one.partial_cmp(other),
(&Lvi(ref one), &Lvi(ref other)) => one.partial_cmp(other),
(&Dvi(ref one), &Dvi(ref other)) => one.partial_cmp(other),
// See https://github.com/rust-lang/rust/issues/68867. rustc isn't
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Vw(..) | Svw(..) | Lvw(..) | Dvw(..) |
Vh(..) | Svh(..) | Lvh(..) | Dvh(..) |
Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) |
Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) |
Vb(..) | Svb(..) | Lvb(..) | Dvb(..) |
Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {},
Vw(..) | Vh(..) | Vmin(..) | Vmax(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
@ -1328,9 +986,10 @@ impl From<NoCalcLength> for LengthPercentage {
impl From<Percentage> for LengthPercentage {
#[inline]
fn from(pc: Percentage) -> Self {
if let Some(clamping_mode) = pc.calc_clamping_mode() {
if pc.is_calc() {
// FIXME(emilio): Hard-coding the clamping mode is suspect.
LengthPercentage::Calc(Box::new(CalcLengthPercentage {
clamping_mode,
clamping_mode: AllowedNumericType::All,
node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())),
}))
} else {
@ -1363,12 +1022,6 @@ impl LengthPercentage {
LengthPercentage::Percentage(computed::Percentage::zero())
}
#[inline]
/// Returns a `100%` value.
pub fn hundred_percent() -> LengthPercentage {
LengthPercentage::Percentage(computed::Percentage::hundred())
}
fn parse_internal<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,

View file

@ -37,11 +37,10 @@ pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth};
pub use self::border::{BorderImageRepeat, BorderImageSideWidth};
pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle};
pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, Display};
pub use self::box_::{Appearance, BreakBetween, BreakWithin, ContainerName, ContainerType};
pub use self::box_::{Appearance, BreakBetween, BreakWithin};
pub use self::box_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor};
pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop};
pub use self::box_::{ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType};
pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange};
pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust};
pub use self::column::ColumnCount;
@ -61,7 +60,7 @@ pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
pub use self::length::{MaxSize, Size};
pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant};
pub use self::length::{NoCalcLength, ViewportPercentageLength};
pub use self::length::{
NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
};
@ -70,7 +69,7 @@ pub use self::list::ListStyleType;
pub use self::list::Quotes;
pub use self::motion::{OffsetPath, OffsetRotate};
pub use self::outline::OutlineStyle;
pub use self::page::{PageOrientation, PageName, PageSize, PaperSize};
pub use self::page::{Orientation, PageName, PageSize, PaperSize};
pub use self::percentage::{NonNegativePercentage, Percentage};
pub use self::position::AspectRatio;
pub use self::position::{

View file

@ -11,7 +11,7 @@ use crate::values::{generics, CustomIdent};
use cssparser::Parser;
use style_traits::ParseError;
pub use generics::page::PageOrientation;
pub use generics::page::Orientation;
pub use generics::page::PaperSize;
/// Specified value of the @page size descriptor
pub type PageSize = generics::page::PageSize<Size2D<NonNegativeLength>>;
@ -24,12 +24,12 @@ impl Parse for PageSize {
// Try to parse as <page-size> [ <orientation> ]
if let Ok(paper_size) = input.try_parse(PaperSize::parse) {
let orientation = input
.try_parse(PageOrientation::parse)
.unwrap_or(PageOrientation::Portrait);
.try_parse(Orientation::parse)
.unwrap_or(Orientation::Portrait);
return Ok(PageSize::PaperSize(paper_size, orientation));
}
// Try to parse as <orientation> [ <page-size> ]
if let Ok(orientation) = input.try_parse(PageOrientation::parse) {
if let Ok(orientation) = input.try_parse(Orientation::parse) {
if let Ok(paper_size) = input.try_parse(PaperSize::parse) {
return Ok(PageSize::PaperSize(paper_size, orientation));
}

View file

@ -92,9 +92,9 @@ impl Percentage {
Number::new_with_clamping_mode(self.value, self.calc_clamping_mode)
}
/// Returns the calc() clamping mode for this percentage.
pub fn calc_clamping_mode(&self) -> Option<AllowedNumericType> {
self.calc_clamping_mode
/// Returns whether this percentage is a `calc()` value.
pub fn is_calc(&self) -> bool {
self.calc_clamping_mode.is_some()
}
/// Reverses this percentage, preserving calc-ness.
@ -138,15 +138,6 @@ impl Percentage {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
}
/// Parses a percentage token, but rejects it if it's negative or more than
/// 100%.
pub fn parse_zero_to_a_hundred<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::ZeroToOne)
}
/// Clamp to 100% if the value is over 100%.
#[inline]
pub fn clamp_to_hundred(self) -> Self {
@ -183,24 +174,6 @@ impl ToComputedValue for Percentage {
impl SpecifiedValueInfo for Percentage {}
/// Turns the percentage into a plain float.
pub trait ToPercentage {
/// Returns whether this percentage used to be a calc().
fn is_calc(&self) -> bool { false }
/// Turns the percentage into a plain float.
fn to_percentage(&self) -> CSSFloat;
}
impl ToPercentage for Percentage {
fn is_calc(&self) -> bool {
self.calc_clamping_mode.is_some()
}
fn to_percentage(&self) -> CSSFloat {
self.get()
}
}
/// A wrapper of Percentage, whose value must be >= 0.
pub type NonNegativePercentage = NonNegative<Percentage>;

View file

@ -556,7 +556,6 @@ impl From<MasonryAutoFlow> for u8 {
}
}
// TODO: Can be derived with some care.
impl Parse for GridAutoFlow {
/// [ row | column ] || dense
fn parse<'i, 't>(

View file

@ -6,9 +6,8 @@
#[cfg(feature = "gecko")]
use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
use crate::media_queries::Device;
use crate::media_queries::{Device, MediaCondition};
use crate::parser::{Parse, ParserContext};
use crate::queries::{QueryCondition, FeatureType};
use crate::values::computed::{self, ToComputedValue};
use crate::values::specified::{Length, NoCalcLength, ViewportPercentageLength};
use app_units::Au;
@ -21,7 +20,7 @@ use style_traits::ParseError;
/// https://html.spec.whatwg.org/multipage/#source-size
#[derive(Debug)]
pub struct SourceSize {
condition: QueryCondition,
condition: MediaCondition,
value: Length,
}
@ -30,8 +29,9 @@ impl Parse for SourceSize {
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let condition = QueryCondition::parse(context, input, FeatureType::Media)?;
let condition = MediaCondition::parse(context, input)?;
let value = Length::parse_non_negative(context, input)?;
Ok(Self { condition, value })
}
}
@ -56,12 +56,12 @@ impl SourceSizeList {
/// Evaluate this <source-size-list> to get the final viewport length.
pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au {
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
let matching_source_size = self
.source_sizes
.iter()
.find(|source_size| source_size.condition.matches(context));
let matching_source_size = self
.source_sizes
.iter()
.find(|source_size| source_size.condition.matches(device, quirks_mode));
computed::Context::for_media_query_evaluation(device, quirks_mode, |context| {
match matching_source_size {
Some(source_size) => source_size.value.to_computed_value(context),
None => match self.value {

View file

@ -232,8 +232,8 @@ impl ToComputedValue for TextOverflow {
}
bitflags! {
#[derive(MallocSizeOf, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)]
#[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))]
#[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)]
#[value_info(other_values = "none,underline,overline,line-through,blink")]
#[repr(C)]
/// Specified keyword values for the text-decoration-line property.
pub struct TextDecorationLine: u8 {
@ -265,6 +265,94 @@ impl Default for TextDecorationLine {
}
}
impl Parse for TextDecorationLine {
/// none | [ underline || overline || line-through || blink ]
fn parse<'i, 't>(
_context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let mut result = TextDecorationLine::empty();
// NOTE(emilio): this loop has this weird structure because we run this
// code to parse the text-decoration shorthand as well, so we need to
// ensure we don't return an error if we don't consume the whole thing
// because we find an invalid identifier or other kind of token.
loop {
let flag: Result<_, ParseError<'i>> = input.try_parse(|input| {
let flag = try_match_ident_ignore_ascii_case! { input,
"none" if result.is_empty() => TextDecorationLine::NONE,
"underline" => TextDecorationLine::UNDERLINE,
"overline" => TextDecorationLine::OVERLINE,
"line-through" => TextDecorationLine::LINE_THROUGH,
"blink" => TextDecorationLine::BLINK,
};
Ok(flag)
});
let flag = match flag {
Ok(flag) => flag,
Err(..) => break,
};
if flag.is_empty() {
return Ok(TextDecorationLine::NONE);
}
if result.contains(flag) {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
result.insert(flag)
}
if !result.is_empty() {
Ok(result)
} else {
Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
}
}
impl ToCss for TextDecorationLine {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
if self.is_empty() {
return dest.write_str("none");
}
#[cfg(feature = "gecko")]
{
if *self == TextDecorationLine::COLOR_OVERRIDE {
return Ok(());
}
}
let mut writer = SequenceWriter::new(dest, " ");
let mut any = false;
macro_rules! maybe_write {
($ident:ident => $str:expr) => {
if self.contains(TextDecorationLine::$ident) {
any = true;
writer.raw_item($str)?;
}
};
}
maybe_write!(UNDERLINE => "underline");
maybe_write!(OVERLINE => "overline");
maybe_write!(LINE_THROUGH => "line-through");
maybe_write!(BLINK => "blink");
debug_assert!(any);
Ok(())
}
}
impl TextDecorationLine {
#[inline]
/// Returns the initial value of text-decoration-line
@ -311,7 +399,6 @@ impl TextTransform {
}
}
// TODO: This can be simplified by deriving it.
impl Parse for TextTransform {
fn parse<'i, 't>(
_context: &ParserContext,
@ -528,20 +615,11 @@ pub enum TextAlign {
/// unlike other keywords.
#[cfg(feature = "gecko")]
MatchParent,
/// This is how we implement the following HTML behavior from
/// https://html.spec.whatwg.org/#tables-2:
///
/// User agents are expected to have a rule in their user agent style sheet
/// that matches th elements that have a parent node whose computed value
/// for the 'text-align' property is its initial value, whose declaration
/// block consists of just a single declaration that sets the 'text-align'
/// property to the value 'center'.
///
/// Since selectors can't depend on the ancestor styles, we implement it with a
/// magic value that computes to the right thing. Since this is an
/// implementation detail, it shouldn't be exposed to web content.
/// `MozCenterOrInherit` value of text-align property. It cannot be parsed,
/// only set directly on the elements and it has a different handling
/// unlike other values.
#[cfg(feature = "gecko")]
#[parse(condition = "ParserContext::in_ua_or_chrome_sheet")]
#[css(skip)]
MozCenterOrInherit,
}
@ -1116,7 +1194,6 @@ bitflags! {
}
}
// TODO: This can be derived with some care.
impl Parse for TextUnderlinePosition {
fn parse<'i, 't>(
_context: &ParserContext,