diff --git a/Cargo.lock b/Cargo.lock index 910e8e4c197..060e603caf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5372,6 +5372,7 @@ dependencies = [ "phf_codegen", "precomputed-hash", "servo_arc", + "size_of_test", "smallvec", "to_shmem", "to_shmem_derive", @@ -5915,6 +5916,9 @@ checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" [[package]] name = "size_of_test" version = "0.0.1" +dependencies = [ + "static_assertions", +] [[package]] name = "slab" @@ -6042,6 +6046,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std_test_override" version = "0.0.1" @@ -6145,6 +6155,7 @@ dependencies = [ "servo_url", "smallbitvec", "smallvec", + "static_assertions", "string_cache", "style_derive", "style_traits", @@ -6186,7 +6197,6 @@ dependencies = [ "servo_atoms", "servo_config", "servo_url", - "size_of_test", "std_test_override", "style", "style_traits", @@ -6208,6 +6218,7 @@ dependencies = [ "servo_arc", "servo_atoms", "servo_url", + "size_of_test", "to_shmem", "to_shmem_derive", "webrender_api", @@ -6691,9 +6702,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uluru" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308dcc9d947b227796f851adb99936fb060681a89c002c9c1928404a3b6c2d72" +checksum = "794a32261a1f5eb6a4462c81b59cec87b5c27d5deea7dd1ac8fc781c41d226db" dependencies = [ "arrayvec 0.7.1", ] diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt index 7e4f385169e..9ef4e7c5f94 100644 --- a/components/atoms/static_atoms.txt +++ b/components/atoms/static_atoms.txt @@ -1,3 +1,4 @@ +-moz-content-preferred-color-scheme -moz-gtk-csd-close-button-position -moz-gtk-csd-maximize-button-position -moz-gtk-csd-menu-radius @@ -12,7 +13,9 @@ animationcancel animationend animationiteration animationstart +aspect-ratio beforeunload +block-size button canplay canplaythrough @@ -50,11 +53,13 @@ fullscreenchange fullscreenerror gattserverdisconnected hashchange +height hidden icecandidate iceconnectionstatechange icegatheringstatechange image +inline-size input inputsourceschange invalid @@ -82,6 +87,7 @@ none number onchange open +orientation pagehide pageshow password diff --git a/components/derive_common/cg.rs b/components/derive_common/cg.rs index 93b624483c7..022a589eb58 100644 --- a/components/derive_common/cg.rs +++ b/components/derive_common/cg.rs @@ -373,6 +373,11 @@ pub fn to_css_identifier(mut camel_case: &str) -> String { result } +/// Transforms foo-bar to FOO_BAR. +pub fn to_scream_case(css_case: &str) -> String { + css_case.to_uppercase().replace('-', "_") +} + /// Given "FooBar", returns "Foo" and sets `camel_case` to "Bar". fn split_camel_segment<'input>(camel_case: &mut &'input str) -> Option<&'input str> { let index = match camel_case.chars().next() { diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index d11f508c502..fe501517c6e 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -44,6 +44,7 @@ servo_atoms = { path = "../atoms" } servo_config = { path = "../config" } servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } +size_of_test = { path = "../size_of_test" } smallvec = { workspace = true, features = ["union"] } style = { path = "../style", features = ["servo"] } style_traits = { path = "../style_traits" } @@ -52,6 +53,3 @@ unicode-script = { workspace = true } webrender_api = { workspace = true } xi-unicode = { workspace = true } -[dev-dependencies] -size_of_test = { path = "../size_of_test" } - diff --git a/components/layout/display_list/builder.rs b/components/layout/display_list/builder.rs index e6b16710d22..93da81c3532 100644 --- a/components/layout/display_list/builder.rs +++ b/components/layout/display_list/builder.rs @@ -653,7 +653,7 @@ impl Fragment { absolute_bounds: Rect, ) { let background = style.get_background(); - let background_color = style.resolve_color(background.background_color); + let background_color = style.resolve_color(background.background_color.clone()); // XXXManishearth the below method should ideally use an iterator over // backgrounds self.build_display_list_for_background_if_applicable_with_background( @@ -1037,7 +1037,9 @@ impl Fragment { webrender_api::BoxShadowDisplayItem { common: items::empty_common_item_properties(), box_bounds: absolute_bounds.to_layout(), - color: style.resolve_color(box_shadow.base.color).to_layout(), + color: style + .resolve_color(box_shadow.base.color.clone()) + .to_layout(), offset: LayoutVector2D::new( box_shadow.base.horizontal.px(), box_shadow.base.vertical.px(), @@ -1083,10 +1085,10 @@ impl Fragment { let border_style_struct = style.get_border(); let mut colors = SideOffsets2D::new( - border_style_struct.border_top_color, - border_style_struct.border_right_color, - border_style_struct.border_bottom_color, - border_style_struct.border_left_color, + border_style_struct.border_top_color.clone(), + border_style_struct.border_right_color.clone(), + border_style_struct.border_bottom_color.clone(), + border_style_struct.border_left_color.clone(), ); let mut border_style = SideOffsets2D::new( border_style_struct.border_top_style, @@ -1316,7 +1318,7 @@ impl Fragment { // Append the outline to the display list. let color = style - .resolve_color(style.get_outline().outline_color) + .resolve_color(style.get_outline().outline_color.clone()) .to_layout(); let base = state.create_base_display_item( clip, @@ -1451,7 +1453,8 @@ impl Fragment { // TODO: Allow non-text fragments to be selected too. if scanned_text_fragment_info.selected() { let style = self.selected_style(); - let background_color = style.resolve_color(style.get_background().background_color); + let background_color = + style.resolve_color(style.get_background().background_color.clone()); let base = state.create_base_display_item( stacking_relative_border_box, self.node, @@ -2055,7 +2058,7 @@ impl Fragment { base: base.clone(), shadow: webrender_api::Shadow { offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()), - color: self.style.resolve_color(shadow.color).to_layout(), + color: self.style.resolve_color(shadow.color.clone()).to_layout(), blur_radius: shadow.blur.px(), }, }, diff --git a/components/layout/display_list/gradient.rs b/components/layout/display_list/gradient.rs index 0f5394b69c9..4ec54479972 100644 --- a/components/layout/display_list/gradient.rs +++ b/components/layout/display_list/gradient.rs @@ -86,7 +86,7 @@ fn convert_gradient_stops( // Only keep the color stops, discard the color interpolation hints. let mut stop_items = gradient_items .iter() - .filter_map(|item| match *item { + .filter_map(|item| match item { GradientItem::SimpleColorStop(color) => Some(ColorStop { color, position: None, @@ -191,7 +191,7 @@ fn convert_gradient_stops( assert!(offset.is_finite()); stops.push(GradientStop { offset: offset, - color: style.resolve_color(stop.color).to_layout(), + color: style.resolve_color(stop.color.clone()).to_layout(), }) } stops diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 5cf56b6cbdf..7f36e110499 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -158,6 +158,11 @@ pub struct Fragment { pub established_reference_frame: Option, } +#[cfg(debug_assertions)] +size_of_test!(Fragment, 176); +#[cfg(not(debug_assertions))] +size_of_test!(Fragment, 152); + impl Serialize for Fragment { fn serialize(&self, serializer: S) -> Result { let mut serializer = serializer.serialize_struct("fragment", 3)?; @@ -212,6 +217,8 @@ pub enum SpecificFragmentInfo { TruncatedFragment(Box), } +size_of_test!(SpecificFragmentInfo, 24); + impl SpecificFragmentInfo { fn restyle_damage(&self) -> RestyleDamage { let flow = match *self { diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 3f41157c1cd..aab98048a54 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -16,6 +16,8 @@ extern crate log; extern crate range; #[macro_use] extern crate serde; +#[macro_use] +extern crate size_of_test; #[macro_use] pub mod layout_debug; diff --git a/components/layout/query.rs b/components/layout/query.rs index 175ddb2679f..d54003971f5 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -860,7 +860,7 @@ where shared: &context.style_context, thread_local: &mut tlc, }; - let styles = resolve_style(&mut context, element, RuleInclusion::All, None); + let styles = resolve_style(&mut context, element, RuleInclusion::All, None, None); styles.primary().clone() } } else { @@ -916,7 +916,13 @@ pub fn process_resolved_style_request<'dom>( thread_local: &mut tlc, }; - let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); + let styles = resolve_style( + &mut context, + element, + RuleInclusion::All, + pseudo.as_ref(), + None, + ); let style = styles.primary(); let longhand_id = match *property { PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id, diff --git a/components/layout/table.rs b/components/layout/table.rs index ca4097635f9..36c78d9bb81 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -794,7 +794,7 @@ fn perform_border_collapse_for_row( child_table_row .final_collapsed_borders .inline - .push_or_set(i, *this_inline_border); + .push_or_set(i, this_inline_border.clone()); if i == 0 { child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start); } else if i + 1 == number_of_borders_inline_direction { @@ -821,7 +821,7 @@ fn perform_border_collapse_for_row( this_border.combine(&previous_block_borders[i]); } }, - PreviousBlockCollapsedBorders::FromTable(table_border) => { + PreviousBlockCollapsedBorders::FromTable(ref table_border) => { this_border.combine(&table_border); }, } @@ -837,7 +837,7 @@ fn perform_border_collapse_for_row( .iter() .enumerate() { - let next_block = next_block.push_or_set(i, *this_block_border); + let next_block = next_block.push_or_set(i, this_block_border.clone()); match next_block_borders { NextBlockCollapsedBorders::FromNextRow(next_block_borders) => { if next_block_borders.len() > i { @@ -1358,7 +1358,7 @@ impl<'table> TableCellStyleInfo<'table> { if background as *const Background == initial.get_background() as *const _ { return; } - let background_color = sty.resolve_color(background.background_color); + let background_color = sty.resolve_color(background.background_color.clone()); cell_flow.build_display_list_for_background_if_applicable_with_background( state, background, diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index 3a70158be26..6408da05dee 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -375,7 +375,7 @@ impl fmt::Debug for TableCellFlow { } } -#[derive(Clone, Copy, Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct CollapsedBordersForCell { pub inline_start_border: CollapsedBorder, pub inline_end_border: CollapsedBorder, @@ -494,10 +494,10 @@ impl CollapsedBordersForCell { ) { let logical_border_colors = LogicalMargin::new( writing_mode, - self.block_start_border.color, - self.inline_end_border.color, - self.block_end_border.color, - self.inline_start_border.color, + self.block_start_border.color.clone(), + self.inline_end_border.color.clone(), + self.block_end_border.color.clone(), + self.inline_start_border.color.clone(), ); *border_colors = logical_border_colors.to_physical(writing_mode); diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 0d959b7456c..b9090c39c94 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -718,7 +718,7 @@ impl CollapsedBorderSpacingForRow { } /// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub struct CollapsedBorder { /// The style of the border. pub style: BorderStyle, @@ -771,7 +771,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_top_style, width: Au::from(css_style.get_border().border_top_width), - color: css_style.get_border().border_top_color, + color: css_style.get_border().border_top_color.clone(), provenance: provenance, } } @@ -782,7 +782,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_right_style, width: Au::from(css_style.get_border().border_right_width), - color: css_style.get_border().border_right_color, + color: css_style.get_border().border_right_color.clone(), provenance: provenance, } } @@ -796,7 +796,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_bottom_style, width: Au::from(css_style.get_border().border_bottom_width), - color: css_style.get_border().border_bottom_color, + color: css_style.get_border().border_bottom_color.clone(), provenance: provenance, } } @@ -807,7 +807,7 @@ impl CollapsedBorder { CollapsedBorder { style: css_style.get_border().border_left_style, width: Au::from(css_style.get_border().border_left_width), - color: css_style.get_border().border_left_color, + color: css_style.get_border().border_left_color.clone(), provenance: provenance, } } @@ -883,18 +883,18 @@ impl CollapsedBorder { match (self.style, other.style) { // Step 1. (BorderStyle::Hidden, _) => {}, - (_, BorderStyle::Hidden) => *self = *other, + (_, BorderStyle::Hidden) => *self = other.clone(), // Step 2. - (BorderStyle::None, _) => *self = *other, + (BorderStyle::None, _) => *self = other.clone(), (_, BorderStyle::None) => {}, // Step 3. _ if self.width > other.width => {}, - _ if self.width < other.width => *self = *other, + _ if self.width < other.width => *self = other.clone(), (this_style, other_style) if this_style > other_style => {}, - (this_style, other_style) if this_style < other_style => *self = *other, + (this_style, other_style) if this_style < other_style => *self = other.clone(), // Step 4. _ if (self.provenance as i8) >= other.provenance as i8 => {}, - _ => *self = *other, + _ => *self = other.clone(), } } } @@ -1013,22 +1013,22 @@ fn set_inline_position_of_child_flow( .collapsed_borders_for_row .inline .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), inline_end_border: border_collapse_info .collapsed_borders_for_row .inline .get(child_index + 1) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), block_start_border: border_collapse_info .collapsed_borders_for_row .block_start .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), block_end_border: border_collapse_info .collapsed_borders_for_row .block_end .get(child_index) - .map_or(CollapsedBorder::new(), |x| *x), + .map_or(CollapsedBorder::new(), |x| x.clone()), inline_start_width: border_collapse_info .collapsed_border_spacing_for_row .inline diff --git a/components/layout/tests/size_of.rs b/components/layout/tests/size_of.rs deleted file mode 100644 index dec10937177..00000000000 --- a/components/layout/tests/size_of.rs +++ /dev/null @@ -1,21 +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/. */ - -#![cfg(target_pointer_width = "64")] - -#[macro_use] -extern crate size_of_test; - -use layout_2013::Fragment; -use layout_2013::SpecificFragmentInfo; - -#[cfg(debug_assertions)] -size_of_test!(test_size_of_fragment, Fragment, 176); -#[cfg(not(debug_assertions))] -size_of_test!(test_size_of_fragment, Fragment, 152); -size_of_test!( - test_size_of_specific_fragment_info, - SpecificFragmentInfo, - 24 -); diff --git a/components/layout_2020/display_list/gradient.rs b/components/layout_2020/display_list/gradient.rs index c9738da49be..da8ef61eb86 100644 --- a/components/layout_2020/display_list/gradient.rs +++ b/components/layout_2020/display_list/gradient.rs @@ -275,11 +275,11 @@ fn fixup_stops( for item in items { match item { GradientItem::SimpleColorStop(color) => stops.push(ColorStop { - color: super::rgba(style.resolve_color(*color)), + color: super::rgba(style.resolve_color(color.clone())), position: None, }), GradientItem::ComplexColorStop { color, position } => stops.push(ColorStop { - color: super::rgba(style.resolve_color(*color)), + color: super::rgba(style.resolve_color(color.clone())), position: Some(if gradient_line_length.px() == 0. { 0. } else { diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs index 80670108991..c2c7713df13 100644 --- a/components/layout_2020/display_list/mod.rs +++ b/components/layout_2020/display_list/mod.rs @@ -352,7 +352,7 @@ impl Fragment { let text_decoration_color = fragment .parent_style .clone_text_decoration_color() - .to_rgba(color); + .into_rgba(color); let text_decoration_style = fragment.parent_style.clone_text_decoration_style(); if text_decoration_style == ComputedTextDecorationStyle::MozNone { return; @@ -535,7 +535,7 @@ impl<'a> BuilderForBoxFragment<'a> { let source = background::Source::Fragment; let style = &self.fragment.style; let b = style.get_background(); - let background_color = style.resolve_color(b.background_color); + let background_color = style.resolve_color(b.background_color.clone()); if background_color.alpha > 0 { // https://drafts.csswg.org/css-backgrounds/#background-color // “The background color is clipped according to the background-clip @@ -685,10 +685,15 @@ impl<'a> BuilderForBoxFragment<'a> { } let common = builder.common_properties(self.border_rect, &self.fragment.style); let details = wr::BorderDetails::Normal(wr::NormalBorder { - top: self.build_border_side(border.border_top_style, border.border_top_color), - right: self.build_border_side(border.border_right_style, border.border_right_color), - bottom: self.build_border_side(border.border_bottom_style, border.border_bottom_color), - left: self.build_border_side(border.border_left_style, border.border_left_color), + top: self.build_border_side(border.border_top_style, border.border_top_color.clone()), + right: self + .build_border_side(border.border_right_style, border.border_right_color.clone()), + bottom: self.build_border_side( + border.border_bottom_style, + border.border_bottom_color.clone(), + ), + left: self + .build_border_side(border.border_left_style, border.border_left_color.clone()), radius: self.border_radius, do_aa: true, }); @@ -718,7 +723,7 @@ impl<'a> BuilderForBoxFragment<'a> { OutlineStyle::Auto => BorderStyle::Solid, OutlineStyle::BorderStyle(s) => s, }; - let side = self.build_border_side(style, outline.outline_color); + let side = self.build_border_side(style, outline.outline_color.clone()); let details = wr::BorderDetails::Normal(wr::NormalBorder { top: side, right: side, diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs index 666e3225bed..77dc310b471 100644 --- a/components/layout_2020/display_list/stacking_context.rs +++ b/components/layout_2020/display_list/stacking_context.rs @@ -404,7 +404,7 @@ impl StackingContext { .union(&fragment_tree.scrollable_overflow) .to_webrender(); - let background_color = style.resolve_color(style.get_background().background_color); + let background_color = style.resolve_color(style.get_background().background_color.clone()); if background_color.alpha > 0 { let common = builder.common_properties(painting_area, &style); let color = super::rgba(background_color); diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index a2524bb6bcd..e409f5bdd59 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -361,7 +361,13 @@ pub fn process_resolved_style_request_for_unstyled_node<'dom>( }; let element = node.as_element().unwrap(); - let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); + let styles = resolve_style( + &mut context, + element, + RuleInclusion::All, + pseudo.as_ref(), + None, + ); let style = styles.primary(); let longhand_id = match *property { PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id, diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 274f581eb87..8d8740bb961 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -465,7 +465,7 @@ impl ComputedValuesExt for ComputedValues { /// Whether or not this style specifies a non-transparent background. fn background_is_transparent(&self) -> bool { let background = self.get_background(); - let color = self.resolve_color(background.background_color); + let color = self.resolve_color(background.background_color.clone()); color.alpha == 0 && background .background_image diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 3d0bc1a1cc7..2202bb123a1 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1796,7 +1796,8 @@ fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF { .fragment .style .get_background() - .background_color, + .background_color + .clone(), ); ColorF::new( color.red_f32(), diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml index 8bcf5680a2d..49dfb7232bd 100644 --- a/components/msg/Cargo.toml +++ b/components/msg/Cargo.toml @@ -19,7 +19,5 @@ malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = { workspace = true } parking_lot = { workspace = true } serde = { workspace = true } -webrender_api = { workspace = true } - -[dev-dependencies] size_of_test = { path = "../size_of_test" } +webrender_api = { workspace = true } diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 72223cdb387..3dcff217485 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -198,6 +198,9 @@ pub struct PipelineNamespaceId(pub u32); namespace_id! {PipelineId, PipelineIndex} +size_of_test!(PipelineId, 8); +size_of_test!(Option, 8); + impl PipelineId { pub fn new() -> PipelineId { PIPELINE_NAMESPACE.with(|tls| { @@ -240,6 +243,9 @@ impl fmt::Display for PipelineId { namespace_id! {BrowsingContextId, BrowsingContextIndex} +size_of_test!(BrowsingContextId, 8); +size_of_test!(Option, 8); + impl BrowsingContextId { pub fn new() -> BrowsingContextId { PIPELINE_NAMESPACE.with(|tls| { @@ -269,6 +275,9 @@ thread_local!(pub static TOP_LEVEL_BROWSING_CONTEXT_ID: Cell, 8); + impl TopLevelBrowsingContextId { pub fn new() -> TopLevelBrowsingContextId { TopLevelBrowsingContextId(BrowsingContextId::new()) diff --git a/components/msg/lib.rs b/components/msg/lib.rs index 4fe53230c46..03aa40eb8f1 100644 --- a/components/msg/lib.rs +++ b/components/msg/lib.rs @@ -12,5 +12,7 @@ extern crate malloc_size_of; extern crate malloc_size_of_derive; #[macro_use] extern crate serde; +#[macro_use] +extern crate size_of_test; pub mod constellation_msg; diff --git a/components/msg/tests/size_of.rs b/components/msg/tests/size_of.rs deleted file mode 100644 index 4fc1861ea31..00000000000 --- a/components/msg/tests/size_of.rs +++ /dev/null @@ -1,31 +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/. */ - -#![cfg(target_pointer_width = "64")] - -#[macro_use] -extern crate size_of_test; - -use msg::constellation_msg::BrowsingContextId; -use msg::constellation_msg::PipelineId; -use msg::constellation_msg::TopLevelBrowsingContextId; - -size_of_test!(test_size_of_pipeline_id, PipelineId, 8); -size_of_test!(test_size_of_optional_pipeline_id, Option, 8); -size_of_test!(test_size_of_browsing_context_id, BrowsingContextId, 8); -size_of_test!( - test_size_of_optional_browsing_context_id, - Option, - 8 -); -size_of_test!( - test_size_of_top_level_browsing_context_id, - TopLevelBrowsingContextId, - 8 -); -size_of_test!( - test_size_of_top_level_optional_browsing_context_id, - Option, - 8 -); diff --git a/components/script/dom/cssrule.rs b/components/script/dom/cssrule.rs index 560124ef030..b9cc5c19dbd 100644 --- a/components/script/dom/cssrule.rs +++ b/components/script/dom/cssrule.rs @@ -98,8 +98,9 @@ impl CSSRule { DomRoot::upcast(CSSSupportsRule::new(window, parent_stylesheet, s)) }, StyleCssRule::Page(_) => unreachable!(), - StyleCssRule::Document(_) => unimplemented!(), // TODO - StyleCssRule::Viewport(_) => unimplemented!(), // TODO + StyleCssRule::Container(_) => unimplemented!(), // TODO + StyleCssRule::Document(_) => unimplemented!(), // TODO + StyleCssRule::Viewport(_) => unimplemented!(), // TODO StyleCssRule::LayerBlock(_) => unimplemented!(), // TODO StyleCssRule::LayerStatement(_) => unimplemented!(), // TODO StyleCssRule::ScrollTimeline(_) => unimplemented!(), // TODO diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 3304e23cb64..ed04d2e562c 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -111,6 +111,7 @@ use std::cell::Cell; use std::default::Default; use std::fmt; use std::mem; +use std::ops::Deref; use std::rc::Rc; use std::str::FromStr; use style::applicable_declarations::ApplicableDeclarationBlock; @@ -3218,15 +3219,11 @@ impl<'a> SelectorsElement for DomRoot { Element::namespace(self) == Element::namespace(other) } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { match *pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), @@ -3307,6 +3304,15 @@ impl<'a> SelectorsElement for DomRoot { fn is_html_slot_element(&self) -> bool { self.is_html_element() && self.local_name() == &local_name!("slot") } + + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + #[allow(unsafe_code)] + unsafe { + Dom::from_ref(self.deref()) + .to_layout() + .insert_selector_flags(flags); + } + } } impl Element { diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index a4913faa683..7e3367b9b2a 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -79,7 +79,9 @@ use script_layout_interface::{HTMLCanvasData, HTMLMediaData, LayoutElementType, use script_layout_interface::{SVGSVGData, StyleAndOpaqueLayoutData, TrustedNodeAddress}; use script_traits::DocumentActivity; use script_traits::UntrustedNodeAddress; -use selectors::matching::{matches_selector_list, MatchingContext, MatchingMode}; +use selectors::matching::{ + matches_selector_list, MatchingContext, MatchingMode, NeedsSelectorFlags, +}; use selectors::parser::SelectorList; use servo_arc::Arc; use servo_url::ServoUrl; @@ -473,6 +475,7 @@ impl<'a> Iterator for QuerySelectorIterator { None, None, node.owner_doc().quirks_mode(), + NeedsSelectorFlags::No, ); if let Some(element) = DomRoot::downcast(node) { if matches_selector_list(selectors, &element, &mut ctx) { @@ -956,8 +959,13 @@ impl Node { // Step 3. Ok(selectors) => { // FIXME(bholley): Consider an nth-index cache here. - let mut ctx = - MatchingContext::new(MatchingMode::Normal, None, None, doc.quirks_mode()); + let mut ctx = MatchingContext::new( + MatchingMode::Normal, + None, + None, + doc.quirks_mode(), + NeedsSelectorFlags::No, + ); Ok(self .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast) diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index b85e72443c3..91be4baa6ed 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -358,14 +358,6 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement false } - unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) { - self.element.insert_selector_flags(flags); - } - - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { - self.element.has_selector_flags(flags) - } - fn has_animations(&self, context: &SharedStyleContext) -> bool { // This is not used for pseudo elements currently so we can pass None. return self.has_css_animations(context, /* pseudo_element = */ None) || @@ -460,6 +452,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement fn namespace(&self) -> &Namespace { self.element.namespace() } + + fn primary_box_size(&self) -> euclid::default::Size2D { + todo!(); + } } impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element @@ -573,15 +569,11 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element false } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { match *pseudo_class { // https://github.com/servo/servo/issues/8718 NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), @@ -670,6 +662,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element fn is_html_element_in_html_document(&self) -> bool { self.element.is_html_element() && self.as_node().owner_doc().is_html_document() } + + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + self.element.insert_selector_flags(flags); + } } /// A wrapper around elements that ensures layout can only @@ -858,15 +854,11 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element } } - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, _: &NonTSPseudoClass, _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { // NB: This could maybe be implemented warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called"); false @@ -907,6 +899,10 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element warn!("ServoThreadSafeLayoutElement::is_root called"); false } + + fn set_selector_flags(&self, flags: ElementSelectorFlags) { + self.element.element.insert_selector_flags(flags); + } } impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom> diff --git a/components/selectors/Cargo.toml b/components/selectors/Cargo.toml index 2a91f50c636..880c4db0c61 100644 --- a/components/selectors/Cargo.toml +++ b/components/selectors/Cargo.toml @@ -28,6 +28,7 @@ log = "0.4" phf = "0.10" precomputed-hash = "0.1" servo_arc = { version = "0.2", path = "../servo_arc" } +size_of_test = { path = "../size_of_test" } smallvec = "1.0" to_shmem = { version = "0.0.0", path = "../to_shmem", optional = true } to_shmem_derive = { version = "0.0.0", path = "../to_shmem_derive", optional = true } diff --git a/components/selectors/context.rs b/components/selectors/context.rs index 9146b133e38..f595389d2f2 100644 --- a/components/selectors/context.rs +++ b/components/selectors/context.rs @@ -68,6 +68,14 @@ impl VisitedHandlingMode { } } +/// Whether we need to set selector invalidation flags on elements for this +/// match request. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum NeedsSelectorFlags { + No, + Yes, +} + /// Which quirks mode is this document in. /// /// See: https://quirks.spec.whatwg.org/ @@ -140,6 +148,7 @@ where pub extra_data: Impl::ExtraMatchingData, quirks_mode: QuirksMode, + needs_selector_flags: NeedsSelectorFlags, classes_and_ids_case_sensitivity: CaseSensitivity, _impl: ::std::marker::PhantomData, } @@ -154,6 +163,7 @@ where bloom_filter: Option<&'a BloomFilter>, nth_index_cache: Option<&'a mut NthIndexCache>, quirks_mode: QuirksMode, + needs_selector_flags: NeedsSelectorFlags, ) -> Self { Self::new_for_visited( matching_mode, @@ -161,6 +171,7 @@ where nth_index_cache, VisitedHandlingMode::AllLinksUnvisited, quirks_mode, + needs_selector_flags, ) } @@ -171,6 +182,7 @@ where nth_index_cache: Option<&'a mut NthIndexCache>, visited_handling: VisitedHandlingMode, quirks_mode: QuirksMode, + needs_selector_flags: NeedsSelectorFlags, ) -> Self { Self { matching_mode, @@ -179,6 +191,7 @@ where nth_index_cache, quirks_mode, classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), + needs_selector_flags, scope_element: None, current_host: None, nesting_level: 0, @@ -213,6 +226,12 @@ where self.matching_mode } + /// Whether we need to set selector flags. + #[inline] + pub fn needs_selector_flags(&self) -> bool { + self.needs_selector_flags == NeedsSelectorFlags::Yes + } + /// The case-sensitivity for class and ID selectors #[inline] pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { diff --git a/components/selectors/lib.rs b/components/selectors/lib.rs index 17f81c535b6..2b785a8cd65 100644 --- a/components/selectors/lib.rs +++ b/components/selectors/lib.rs @@ -5,6 +5,9 @@ // Make |cargo bench| work. #![cfg_attr(feature = "bench", feature(test))] +#[macro_use] +extern crate size_of_test; + pub mod attr; pub mod bloom; mod builder; diff --git a/components/selectors/matching.rs b/components/selectors/matching.rs index 92c13958985..3ded93e44b9 100644 --- a/components/selectors/matching.rs +++ b/components/selectors/matching.rs @@ -78,8 +78,7 @@ where // This is pretty much any(..) but manually inlined because the compiler // refuses to do so from querySelector / querySelectorAll. for selector in &selector_list.0 { - let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {}); - + let matches = matches_selector(selector, 0, None, element, context); if matches { return true; } @@ -184,17 +183,15 @@ enum MatchesHoverAndActiveQuirk { /// unncessary cache miss for cases when we can fast-reject with AncestorHashes /// (which the caller can store inline with the selector pointer). #[inline(always)] -pub fn matches_selector( +pub fn matches_selector( selector: &Selector, offset: usize, hashes: Option<&AncestorHashes>, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { // Use the bloom filter to fast-reject. if let Some(hashes) = hashes { @@ -205,7 +202,7 @@ where } } - matches_complex_selector(selector.iter_from(offset), element, context, flags_setter) + matches_complex_selector(selector.iter_from(offset), element, context) } /// Whether a compound selector matched, and whether it was the rightmost @@ -277,7 +274,7 @@ where ); for component in iter { - if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) { + if !matches_simple_selector(component, element, &mut local_context) { return CompoundSelectorMatchingResult::NotMatched; } } @@ -293,15 +290,13 @@ where /// Matches a complex selector. #[inline(always)] -pub fn matches_complex_selector( +pub fn matches_complex_selector( mut iter: SelectorIter, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { // If this is the special pseudo-element mode, consume the ::pseudo-element // before proceeding, since the caller has already handled that part. @@ -334,7 +329,7 @@ where } let result = - matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes); + matches_complex_selector_internal(iter, element, context, Rightmost::Yes); matches!(result, SelectorMatchingResult::Matched) } @@ -458,16 +453,14 @@ where } } -fn matches_complex_selector_internal( +fn matches_complex_selector_internal( mut selector_iter: SelectorIter, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, rightmost: Rightmost, ) -> SelectorMatchingResult where E: Element, - F: FnMut(&E, ElementSelectorFlags), { debug!( "Matching complex selector {:?} for {:?}", @@ -478,16 +471,16 @@ where &mut selector_iter, element, context, - flags_setter, rightmost, ); let combinator = selector_iter.next_sequence(); if combinator.map_or(false, |c| c.is_sibling()) { - flags_setter( - element, - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS, - ); + if context.needs_selector_flags() { + element.apply_selector_flags( + ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS + ); + } } if !matches_compound_selector { @@ -532,7 +525,6 @@ where selector_iter.clone(), &element, context, - flags_setter, Rightmost::No, ) }); @@ -595,16 +587,14 @@ where /// Determines whether the given element matches the given compound selector. #[inline] -fn matches_compound_selector( +fn matches_compound_selector( selector_iter: &mut SelectorIter, element: &E, context: &mut MatchingContext, - flags_setter: &mut F, rightmost: Rightmost, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost); @@ -643,19 +633,17 @@ where }; iter::once(selector) .chain(selector_iter) - .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter)) + .all(|simple| matches_simple_selector(simple, element, &mut local_context)) } /// Determines whether the given element matches the given single selector. -fn matches_simple_selector( +fn matches_simple_selector( selector: &Component, element: &E, context: &mut LocalMatchingContext, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); @@ -703,7 +691,7 @@ where // are never flattened tree slottables. !element.is_html_slot_element() && context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context, flags_setter) + matches_complex_selector(selector.iter(), element, context) }) }, Component::PseudoElement(ref pseudo) => { @@ -795,16 +783,18 @@ where return false; } - element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter) + element.match_non_ts_pseudo_class(pc, &mut context.shared) }, - Component::FirstChild => matches_first_child(element, flags_setter), - Component::LastChild => matches_last_child(element, flags_setter), + Component::FirstChild => matches_first_child(element, context.shared), + Component::LastChild => matches_last_child(element, context.shared), Component::OnlyChild => { - matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter) + matches_first_child(element, context.shared) && matches_last_child(element, context.shared) }, Component::Root => element.is_root(), Component::Empty => { - flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); + if context.shared.needs_selector_flags() { + element.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); + } element.is_empty() }, Component::Host(ref selector) => { @@ -814,7 +804,7 @@ where .map_or(false, |host| host == element.opaque()) && selector.as_ref().map_or(true, |selector| { context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context, flags_setter) + matches_complex_selector(selector.iter(), element, context) }) }) }, @@ -823,30 +813,30 @@ where None => element.is_root(), }, Component::NthChild(a, b) => { - matches_generic_nth_child(element, context, a, b, false, false, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, false, false) }, Component::NthLastChild(a, b) => { - matches_generic_nth_child(element, context, a, b, false, true, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, false, true) }, Component::NthOfType(a, b) => { - matches_generic_nth_child(element, context, a, b, true, false, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, true, false) }, Component::NthLastOfType(a, b) => { - matches_generic_nth_child(element, context, a, b, true, true, flags_setter) + matches_generic_nth_child(element, context.shared, a, b, true, true) }, Component::FirstOfType => { - matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) + matches_generic_nth_child(element, context.shared, 0, 1, true, false) }, Component::LastOfType => { - matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) + matches_generic_nth_child(element, context.shared, 0, 1, true, true) }, Component::OnlyOfType => { - matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) && - matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) + matches_generic_nth_child(element, context.shared, 0, 1, true, false) && + matches_generic_nth_child(element, context.shared, 0, 1, true, true) }, Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { + if matches_complex_selector(selector.iter(), element, context) { return true; } } @@ -854,7 +844,7 @@ where }), Component::Negation(ref list) => context.shared.nest_for_negation(|context| { for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { + if matches_complex_selector(selector.iter(), element, context) { return false; } } @@ -873,35 +863,31 @@ fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) } #[inline] -fn matches_generic_nth_child( +fn matches_generic_nth_child( element: &E, - context: &mut LocalMatchingContext, + context: &mut MatchingContext, a: i32, b: i32, is_of_type: bool, is_from_end: bool, - flags_setter: &mut F, ) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { if element.ignores_nth_child_selectors() { return false; } - flags_setter( - element, - if is_from_end { + if context.needs_selector_flags() { + element.apply_selector_flags(if is_from_end { ElementSelectorFlags::HAS_SLOW_SELECTOR } else { ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS - }, - ); + }); + } // Grab a reference to the appropriate cache. let mut cache = context - .shared .nth_index_cache .as_mut() .map(|c| c.get(is_of_type, is_from_end)); @@ -992,21 +978,23 @@ where } #[inline] -fn matches_first_child(element: &E, flags_setter: &mut F) -> bool +fn matches_first_child(element: &E, context: &MatchingContext) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { - flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } element.prev_sibling_element().is_none() } #[inline] -fn matches_last_child(element: &E, flags_setter: &mut F) -> bool +fn matches_last_child(element: &E, context: &MatchingContext) -> bool where E: Element, - F: FnMut(&E, ElementSelectorFlags), { - flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + element.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } element.next_sibling_element().is_none() } diff --git a/components/selectors/parser.rs b/components/selectors/parser.rs index c6650102616..cf342fc8ea8 100644 --- a/components/selectors/parser.rs +++ b/components/selectors/parser.rs @@ -160,6 +160,8 @@ impl SelectorParsingState { pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; +size_of_test!(SelectorParseError, 48); + #[derive(Clone, Debug, PartialEq)] pub enum SelectorParseErrorKind<'i> { NoQualifiedNameInAttributeSelector(Token<'i>), @@ -184,6 +186,8 @@ pub enum SelectorParseErrorKind<'i> { ClassNeedsIdent(Token<'i>), } +size_of_test!(SelectorParseErrorKind, 40); + macro_rules! with_all_bounds { ( [ $( $InSelector: tt )* ] diff --git a/components/selectors/rustfmt.toml b/components/selectors/rustfmt.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/components/selectors/rustfmt.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/components/selectors/tree.rs b/components/selectors/tree.rs index 47cea73a208..c1f0f9341c7 100644 --- a/components/selectors/tree.rs +++ b/components/selectors/tree.rs @@ -77,14 +77,11 @@ pub trait Element: Sized + Clone + Debug { operation: &AttrSelectorOperation<&::AttrValue>, ) -> bool; - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pc: &::NonTSPseudoClass, context: &mut MatchingContext, - flags_setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags); + ) -> bool; fn match_pseudo_element( &self, @@ -92,6 +89,30 @@ pub trait Element: Sized + Clone + Debug { context: &mut MatchingContext, ) -> 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. + /// + /// You probably don't want to use this directly and want to use + /// apply_selector_flags, since that sets flags on the parent as needed. + fn set_selector_flags(&self, flags: ElementSelectorFlags); + + fn apply_selector_flags(&self, flags: ElementSelectorFlags) { + // Handle flags that apply to the element. + let self_flags = flags.for_self(); + if !self_flags.is_empty() { + self.set_selector_flags(self_flags); + } + + // Handle flags that apply to the parent. + let parent_flags = flags.for_parent(); + if !parent_flags.is_empty() { + if let Some(p) = self.parent_element() { + p.set_selector_flags(parent_flags); + } + } + } + /// Whether this element is a `link`. fn is_link(&self) -> bool; diff --git a/components/size_of_test/Cargo.toml b/components/size_of_test/Cargo.toml index 4776581cc52..faea55c5c1c 100644 --- a/components/size_of_test/Cargo.toml +++ b/components/size_of_test/Cargo.toml @@ -8,3 +8,6 @@ publish = false [lib] path = "lib.rs" + +[dependencies] +static_assertions = "1.1" diff --git a/components/size_of_test/lib.rs b/components/size_of_test/lib.rs index 4e2452232f3..18e45175e8c 100644 --- a/components/size_of_test/lib.rs +++ b/components/size_of_test/lib.rs @@ -2,33 +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/. */ +pub use static_assertions::const_assert_eq; + +/// Asserts the size of a type at compile time. #[macro_export] macro_rules! size_of_test { - ($testname: ident, $t: ty, $expected_size: expr) => { - #[test] - fn $testname() { - let new = ::std::mem::size_of::<$t>(); - let old = $expected_size; - if new < old { - panic!( - "Your changes have decreased the stack size of {} from {} to {}. \ - Good work! Please update the expected size in {}.", - stringify!($t), - old, - new, - file!() - ) - } else if new > old { - panic!( - "Your changes have increased the stack size of {} from {} to {}. \ - Please consider choosing a design which avoids this increase. \ - If you feel that the increase is necessary, update the size in {}.", - stringify!($t), - old, - new, - file!() - ) - } - } + ($t: ty, $expected_size: expr) => { + #[cfg(target_pointer_width = "64")] + $crate::const_assert_eq!(std::mem::size_of::<$t>(), $expected_size); }; } diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index 6c5196d93f8..44ac2998e83 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -62,13 +62,14 @@ 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 = "2" +uluru = "3.0" unicode-bidi = "0.3" unicode-segmentation = "1.0" void = "1.0.2" diff --git a/components/style/animation.rs b/components/style/animation.rs index 7f0c7e6a588..1df3dc08cca 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -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) -> bool { - let index = new_style - .get_box() + let new_ui = new_style.get_ui(); + let index = new_ui .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_style.get_box().animation_duration_mod(index).seconds() == 0. + new_ui.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, ) { - 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 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 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( ) where E: TElement, { - let box_style = new_style.get_box(); - for (i, name) in box_style.animation_name_iter().enumerate() { + let style = new_style.get_ui(); + for (i, name) in style.animation_name_iter().enumerate() { let name = match name.as_atom() { Some(atom) => atom, None => continue, }; debug!("maybe_start_animations: name={}", name); - let duration = box_style.animation_duration_mod(i).seconds() as f64; + let duration = style.animation_duration_mod(i).seconds() as f64; if duration == 0. { continue; } @@ -1375,14 +1375,14 @@ pub fn maybe_start_animations( // 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 = box_style.animation_delay_mod(i).seconds(); + let delay = style.animation_delay_mod(i).seconds(); - let iteration_state = match box_style.animation_iteration_count_mod(i) { + let iteration_state = match style.animation_iteration_count_mod(i) { AnimationIterationCount::Infinite => KeyframesIterationState::Infinite(0.0), AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0.0, n.into()), }; - let animation_direction = box_style.animation_direction_mod(i); + let animation_direction = style.animation_direction_mod(i); let initial_direction = match animation_direction { AnimationDirection::Normal | AnimationDirection::Alternate => { @@ -1396,7 +1396,7 @@ pub fn maybe_start_animations( 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 box_style.animation_play_state_mod(i) { + let state = match 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( &keyframe_animation, context, new_style, - new_style.get_box().animation_timing_function_mod(i), + style.animation_timing_function_mod(i), resolver, ); @@ -1416,7 +1416,7 @@ pub fn maybe_start_animations( computed_steps, started_at, duration, - fill_mode: box_style.animation_fill_mode_mod(i), + fill_mode: style.animation_fill_mode_mod(i), delay: delay as f64, iteration_state, state, diff --git a/components/style/applicable_declarations.rs b/components/style/applicable_declarations.rs index 5849e7c2250..387dcdab07d 100644 --- a/components/style/applicable_declarations.rs +++ b/components/style/applicable_declarations.rs @@ -42,13 +42,7 @@ pub struct CascadePriority { layer_order: LayerOrder, } -#[allow(dead_code)] -fn size_assert() { - #[allow(unsafe_code)] - unsafe { - std::mem::transmute::(0u32) - }; -} +const_assert_eq!(std::mem::size_of::(), std::mem::size_of::()); impl PartialOrd for CascadePriority { #[inline] @@ -208,3 +202,6 @@ impl ApplicableDeclarationBlock { (self.source, self.cascade_priority) } } + +// Size of this struct determines sorting and selector-matching performance. +size_of_test!(ApplicableDeclarationBlock, 24); diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 9c411b3ddb2..538b641d706 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -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("whitelist-functions", |b, item| b.allowlist_function(item)) + .handle_str_items("allowlist-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("whitelist-vars", |b, item| b.allowlist_var(item)) - .handle_str_items("whitelist-types", |b, item| b.allowlist_type(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("opaque-types", |b, item| b.opaque_type(item)) .handle_table_items("cbindgen-types", |b, item| { let gecko = item["gecko"].as_str().unwrap(); diff --git a/components/style/context.rs b/components/style/context.rs index a7d5aa4a67b..633479c5a7a 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -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,7 +41,6 @@ use style_traits::DevicePixel; #[cfg(feature = "servo")] use style_traits::SpeculativePainter; use time; -use uluru::LRUCache; pub use selectors::matching::QuirksMode; @@ -517,65 +516,6 @@ impl SequentialTask { } } -type CacheItem = (SendElement, ElementSelectorFlags); - -/// Map from Elements to ElementSelectorFlags. Used to defer applying selector -/// flags until after the traversal. -pub struct SelectorFlagsMap { - /// The hashmap storing the flags to apply. - map: FxHashMap, ElementSelectorFlags>, - /// An LRU cache to avoid hashmap lookups, which can be slow if the map - /// gets big. - cache: LRUCache, { 4 + 1 }>, -} - -#[cfg(debug_assertions)] -impl Drop for SelectorFlagsMap { - fn drop(&mut self) { - debug_assert!(self.map.is_empty()); - } -} - -impl SelectorFlagsMap { - /// 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(Vec>) where @@ -697,11 +637,6 @@ pub struct ThreadLocalStyleContext { /// filter, to ensure they're dropped before we execute the tasks, which /// could create another ThreadLocalStyleContext for style computation. pub tasks: SequentialTaskList, - /// 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, /// Statistics about the traversal. pub statistics: PerThreadTraversalStatistics, /// A checker used to ensure that parallel.rs does not recurse indefinitely @@ -719,7 +654,6 @@ impl ThreadLocalStyleContext { 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, @@ -729,15 +663,6 @@ impl ThreadLocalStyleContext { } } -impl Drop for ThreadLocalStyleContext { - 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> { diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index 7be700ec350..054e5a17c74 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -64,6 +64,27 @@ 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), @@ -99,7 +120,7 @@ macro_rules! lnf_int_variable { }}; } -static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ +static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 6] = [ lnf_int_variable!( atom!("-moz-gtk-csd-titlebar-radius"), TitlebarRadius, @@ -121,6 +142,7 @@ static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 5] = [ GTKCSDMaximizeButtonPosition, integer ), + make_variable!(atom!("-moz-content-preferred-color-scheme"), get_content_preferred_color_scheme), ]; impl CssEnvironment { @@ -328,6 +350,11 @@ 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): diff --git a/components/style/data.rs b/components/style/data.rs index 758adf57a68..2fa0b39c24d 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -159,6 +159,9 @@ 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> { @@ -249,6 +252,9 @@ 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 { diff --git a/components/style/dom.rs b/components/style/dom.rs index 308006efd11..83466eb21e6 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -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::{ElementSelectorFlags, QuirksMode, VisitedHandlingMode}; +use selectors::matching::{QuirksMode, VisitedHandlingMode}; use selectors::sink::Push; use selectors::Element as SelectorsElement; use servo_arc::{Arc, ArcBorrow}; @@ -734,19 +734,6 @@ 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. @@ -941,6 +928,9 @@ pub trait TElement: /// Returns element's namespace. fn namespace(&self) -> &::BorrowedNamespaceUrl; + + /// Returns the size of the primary box of the element. + fn primary_box_size(&self) -> euclid::default::Size2D; } /// TNode and TElement aren't Send because we want to be careful and explicit diff --git a/components/style/dom_apis.rs b/components/style/dom_apis.rs index 6d19801cfc5..e6f1cbf565c 100644 --- a/components/style/dom_apis.rs +++ b/components/style/dom_apis.rs @@ -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}; +use selectors::matching::{self, MatchingContext, MatchingMode, NeedsSelectorFlags}; use selectors::parser::{Combinator, Component, LocalName, SelectorImpl}; use selectors::{Element, NthIndexCache, SelectorList}; use smallvec::SmallVec; @@ -26,7 +26,13 @@ pub fn element_matches( where E: Element, { - let mut context = MatchingContext::new(MatchingMode::Normal, None, None, quirks_mode); + let mut context = MatchingContext::new( + MatchingMode::Normal, + None, + None, + quirks_mode, + NeedsSelectorFlags::No, + ); 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) @@ -48,6 +54,7 @@ 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()); @@ -618,8 +625,8 @@ pub fn query_selector( 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 { diff --git a/components/style/element_state.rs b/components/style/element_state.rs index 30c65dc9feb..f8f4629ef9b 100644 --- a/components/style/element_state.rs +++ b/components/style/element_state.rs @@ -118,8 +118,8 @@ bitflags! { const IN_MODAL_DIALOG_STATE = 1 << 42; /// const IN_MOZINERT_STATE = 1 << 43; - /// State for the topmost dialog element in top layer - const IN_TOPMOST_MODAL_DIALOG_STATE = 1 << 44; + /// State for the topmost modal element in top layer + const IN_TOPMOST_MODAL_TOP_LAYER_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,9 +148,5 @@ 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; } } diff --git a/components/style/gecko/arc_types.rs b/components/style/gecko/arc_types.rs index 4fb08c3a309..893812ea710 100644 --- a/components/style/gecko/arc_types.rs +++ b/components/style/gecko/arc_types.rs @@ -15,7 +15,7 @@ use crate::gecko_bindings::structs::{ RawServoKeyframesRule, RawServoLayerBlockRule, RawServoLayerStatementRule, RawServoMediaList, RawServoMediaRule, RawServoMozDocumentRule, RawServoNamespaceRule, RawServoPageRule, RawServoScrollTimelineRule, RawServoStyleRule, RawServoStyleSheetContents, - RawServoSupportsRule, ServoCssRules, + RawServoSupportsRule, RawServoContainerRule, 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, + ScrollTimelineRule, StyleRule, StylesheetContents, SupportsRule, ContainerRule, }; use servo_arc::{Arc, ArcBorrow}; use std::{mem, ptr}; @@ -98,6 +98,9 @@ impl_arc_ffi!(Locked => RawServoScrollTimelineRule impl_arc_ffi!(Locked => RawServoSupportsRule [Servo_SupportsRule_AddRef, Servo_SupportsRule_Release]); +impl_arc_ffi!(Locked => RawServoContainerRule + [Servo_ContainerRule_AddRef, Servo_ContainerRule_Release]); + impl_arc_ffi!(Locked => RawServoMozDocumentRule [Servo_DocumentRule_AddRef, Servo_DocumentRule_Release]); diff --git a/components/style/gecko/data.rs b/components/style/gecko/data.rs index 39f7d7cc8d8..cabae61d563 100644 --- a/components/style/gecko/data.rs +++ b/components/style/gecko/data.rs @@ -129,6 +129,12 @@ 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 @@ -143,6 +149,8 @@ impl PerDocumentStyleData { PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl { stylist: Stylist::new(device, quirks_mode.into()), + undisplayed_style_cache: Default::default(), + undisplayed_style_cache_generation: 0, })) } @@ -177,12 +185,6 @@ 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); diff --git a/components/style/gecko/media_features.rs b/components/style/gecko/media_features.rs index af57be431f9..8c93dfa24ce 100644 --- a/components/style/gecko/media_features.rs +++ b/components/style/gecko/media_features.rs @@ -6,13 +6,10 @@ use crate::gecko_bindings::bindings; use crate::gecko_bindings::structs; -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::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; +use crate::queries::values::Orientation; use crate::media_queries::{Device, MediaType}; -use crate::values::computed::CSSPixelLength; -use crate::values::computed::Ratio; -use crate::values::computed::Resolution; +use crate::values::computed::{Context, CSSPixelLength, Ratio, Resolution}; use app_units::Au; use euclid::default::Size2D; @@ -26,150 +23,56 @@ fn device_size(device: &Device) -> Size2D { } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().width, - ) +fn eval_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) } /// https://drafts.csswg.org/mediaqueries-4/#device-width -fn eval_device_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device_size(device).width, - ) +fn eval_device_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(device_size(context.device()).width.to_f32_px()) } /// https://drafts.csswg.org/mediaqueries-4/#height -fn eval_height( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().height, - ) +fn eval_height(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().height.to_f32_px()) } /// https://drafts.csswg.org/mediaqueries-4/#device-height -fn eval_device_height( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device_size(device).height, - ) +fn eval_device_height(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(device_size(context.device()).height.to_f32_px()) } -fn eval_aspect_ratio_for( - device: &Device, - query_value: Option, - range_or_operator: Option, - get_size: F, -) -> bool +fn eval_aspect_ratio_for(context: &Context, get_size: F) -> Ratio where F: FnOnce(&Device) -> Size2D, { - // 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) + let size = get_size(context.device()); + Ratio::new(size.width.0 as f32, size.height.0 as f32) } /// https://drafts.csswg.org/mediaqueries-4/#aspect-ratio -fn eval_aspect_ratio( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - eval_aspect_ratio_for( - device, - query_value, - range_or_operator, - Device::au_viewport_size, - ) +fn eval_aspect_ratio(context: &Context) -> Ratio { + eval_aspect_ratio_for(context, Device::au_viewport_size) } /// https://drafts.csswg.org/mediaqueries-4/#device-aspect-ratio -fn eval_device_aspect_ratio( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - eval_aspect_ratio_for(device, query_value, range_or_operator, device_size) +fn eval_device_aspect_ratio(context: &Context) -> Ratio { + eval_aspect_ratio_for(context, device_size) } /// https://compat.spec.whatwg.org/#css-media-queries-webkit-device-pixel-ratio -fn eval_device_pixel_ratio( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> 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(device: &Device, value: Option, get_size: F) -> bool -where - F: FnOnce(&Device) -> Size2D, -{ - 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, - } +fn eval_device_pixel_ratio(context: &Context) -> f32 { + eval_resolution(context).dppx() } /// https://drafts.csswg.org/mediaqueries-4/#orientation -fn eval_orientation(device: &Device, value: Option) -> bool { - eval_orientation_for(device, value, Device::au_viewport_size) +fn eval_orientation(context: &Context, value: Option) -> bool { + Orientation::eval(context.device().au_viewport_size(), value) } /// FIXME: There's no spec for `-moz-device-orientation`. -fn eval_device_orientation(device: &Device, value: Option) -> bool { - eval_orientation_for(device, value, device_size) +fn eval_device_orientation(context: &Context, value: Option) -> bool { + Orientation::eval(device_size(context.device()), value) } /// Values for the display-mode media feature. @@ -184,25 +87,23 @@ pub enum DisplayMode { } /// https://w3c.github.io/manifest/#the-display-mode-media-feature -fn eval_display_mode(device: &Device, query_value: Option) -> bool { +fn eval_display_mode(context: &Context, query_value: Option) -> bool { match query_value { - Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(device.document()) }, + Some(v) => v == unsafe { bindings::Gecko_MediaFeatures_GetDisplayMode(context.device().document()) }, None => true, } } /// https://drafts.csswg.org/mediaqueries-4/#grid -fn eval_grid(_: &Device, query_value: Option, _: Option) -> bool { +fn eval_grid(_: &Context) -> bool { // Gecko doesn't support grid devices (e.g., ttys), so the 'grid' feature // is always 0. - let supports_grid = false; - query_value.map_or(supports_grid, |v| v == supports_grid) + false } /// https://compat.spec.whatwg.org/#css-media-queries-webkit-transform-3d -fn eval_transform_3d(_: &Device, query_value: Option, _: Option) -> bool { - let supports_transforms = true; - query_value.map_or(supports_transforms, |v| v == supports_transforms) +fn eval_transform_3d(_: &Context) -> bool { + true } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -213,58 +114,33 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Device, _: Option) -> bool { +fn eval_scan(_: &Context, _: Option) -> 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( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - let color_bits_per_channel = - unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(device.document()) }; - RangeOrOperator::evaluate(range_or_operator, query_value, color_bits_per_channel) +fn eval_color(context: &Context) -> u32 { + unsafe { bindings::Gecko_MediaFeatures_GetColorDepth(context.device().document()) } } /// https://drafts.csswg.org/mediaqueries-4/#color-index -fn eval_color_index( - _: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { +fn eval_color_index(_: &Context) -> u32 { // We should return zero if the device does not use a color lookup table. - let index = 0; - RangeOrOperator::evaluate(range_or_operator, query_value, index) + 0 } /// https://drafts.csswg.org/mediaqueries-4/#monochrome -fn eval_monochrome( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { +fn eval_monochrome(context: &Context) -> u32 { // For color devices we should return 0. - let depth = - unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(device.document()) }; - RangeOrOperator::evaluate(range_or_operator, query_value, depth) + unsafe { bindings::Gecko_MediaFeatures_GetMonochromeBitsPerPixel(context.device().document()) } } /// https://drafts.csswg.org/mediaqueries-4/#resolution -fn eval_resolution( - device: &Device, - query_value: Option, - range_or_operator: Option, -) -> bool { - let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(device.document()) }; - RangeOrOperator::evaluate( - range_or_operator, - query_value.map(|r| r.dppx()), - resolution_dppx, - ) +fn eval_resolution(context: &Context) -> Resolution { + let resolution_dppx = unsafe { bindings::Gecko_MediaFeatures_GetResolution(context.device().document()) }; + Resolution::from_dppx(resolution_dppx) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -283,10 +159,22 @@ 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(device: &Device, query_value: Option) -> bool { +fn eval_prefers_reduced_motion(context: &Context, query_value: Option) -> bool { let prefers_reduced = - unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(device.document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersReducedMotion(context.device().document()) }; let query_value = match query_value { Some(v) => v, None => return prefers_reduced, @@ -303,19 +191,20 @@ fn eval_prefers_reduced_motion(device: &Device, query_value: Option) -> bool { +fn eval_prefers_contrast(context: &Context, query_value: Option) -> bool { let prefers_contrast = - unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(device.document()) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersContrast(context.device().document()) }; match query_value { Some(v) => v == prefers_contrast, None => prefers_contrast != PrefersContrast::NoPreference, @@ -334,8 +223,8 @@ pub enum ForcedColors { } /// https://drafts.csswg.org/mediaqueries-5/#forced-colors -fn eval_forced_colors(device: &Device, query_value: Option) -> bool { - let forced = !device.use_document_colors(); +fn eval_forced_colors(context: &Context, query_value: Option) -> bool { + let forced = !context.device().use_document_colors(); match query_value { Some(query_value) => forced == (query_value == ForcedColors::Active), None => forced, @@ -352,7 +241,7 @@ enum OverflowBlock { } /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-block -fn eval_overflow_block(device: &Device, query_value: Option) -> bool { +fn eval_overflow_block(context: &Context, query_value: Option) -> 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 @@ -360,7 +249,7 @@ fn eval_overflow_block(device: &Device, query_value: Option) -> b // billboard mode that doesn't support overflow at all). // // If this ever changes, don't forget to change eval_overflow_inline too. - let scrolling = device.media_type() != MediaType::print(); + let scrolling = context.device().media_type() != MediaType::print(); let query_value = match query_value { Some(v) => v, None => return true, @@ -381,9 +270,9 @@ enum OverflowInline { } /// https://drafts.csswg.org/mediaqueries-4/#mf-overflow-inline -fn eval_overflow_inline(device: &Device, query_value: Option) -> bool { +fn eval_overflow_inline(context: &Context, query_value: Option) -> bool { // See the note in eval_overflow_block. - let scrolling = device.media_type() != MediaType::print(); + let scrolling = context.device().media_type() != MediaType::print(); let query_value = match query_value { Some(v) => v, None => return scrolling, @@ -395,13 +284,41 @@ fn eval_overflow_inline(device: &Device, query_value: Option) -> } } +#[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) -> 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( - device: &Device, + context: &Context, use_content: bool, query_value: Option, ) -> bool { let prefers_color_scheme = - unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(device.document(), use_content) }; + unsafe { bindings::Gecko_MediaFeatures_PrefersColorScheme(context.device().document(), use_content) }; match query_value { Some(v) => prefers_color_scheme == v, None => true, @@ -409,15 +326,34 @@ fn do_eval_prefers_color_scheme( } /// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme -fn eval_prefers_color_scheme(device: &Device, query_value: Option) -> bool { - do_eval_prefers_color_scheme(device, /* use_content = */ false, query_value) +fn eval_prefers_color_scheme(context: &Context, query_value: Option) -> bool { + do_eval_prefers_color_scheme(context, /* use_content = */ false, query_value) } fn eval_content_prefers_color_scheme( - device: &Device, + context: &Context, query_value: Option, ) -> bool { - do_eval_prefers_color_scheme(device, /* use_content = */ true, query_value) + 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) -> 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) -> bool { + let dynamic_range = + unsafe { bindings::Gecko_MediaFeatures_VideoDynamicRange(context.device().document()) }; + match query_value { + Some(v) => dynamic_range >= v, + None => false, + } } bitflags! { @@ -429,15 +365,15 @@ bitflags! { } } -fn primary_pointer_capabilities(device: &Device) -> PointerCapabilities { +fn primary_pointer_capabilities(context: &Context) -> PointerCapabilities { PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(device.document()) + bindings::Gecko_MediaFeatures_PrimaryPointerCapabilities(context.device().document()) }) } -fn all_pointer_capabilities(device: &Device) -> PointerCapabilities { +fn all_pointer_capabilities(context: &Context) -> PointerCapabilities { PointerCapabilities::from_bits_truncate(unsafe { - bindings::Gecko_MediaFeatures_AllPointerCapabilities(device.document()) + bindings::Gecko_MediaFeatures_AllPointerCapabilities(context.device().document()) }) } @@ -466,13 +402,13 @@ fn eval_pointer_capabilities( } /// https://drafts.csswg.org/mediaqueries-4/#pointer -fn eval_pointer(device: &Device, query_value: Option) -> bool { - eval_pointer_capabilities(query_value, primary_pointer_capabilities(device)) +fn eval_pointer(context: &Context, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, primary_pointer_capabilities(context)) } /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-pointer -fn eval_any_pointer(device: &Device, query_value: Option) -> bool { - eval_pointer_capabilities(query_value, all_pointer_capabilities(device)) +fn eval_any_pointer(context: &Context, query_value: Option) -> bool { + eval_pointer_capabilities(query_value, all_pointer_capabilities(context)) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -499,54 +435,33 @@ fn eval_hover_capabilities( } /// https://drafts.csswg.org/mediaqueries-4/#hover -fn eval_hover(device: &Device, query_value: Option) -> bool { - eval_hover_capabilities(query_value, primary_pointer_capabilities(device)) +fn eval_hover(context: &Context, query_value: Option) -> bool { + eval_hover_capabilities(query_value, primary_pointer_capabilities(context)) } /// https://drafts.csswg.org/mediaqueries-4/#descdef-media-any-hover -fn eval_any_hover(device: &Device, query_value: Option) -> bool { - eval_hover_capabilities(query_value, all_pointer_capabilities(device)) +fn eval_any_hover(context: &Context, query_value: Option) -> bool { + eval_hover_capabilities(query_value, all_pointer_capabilities(context)) } -fn eval_moz_is_glyph( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let is_glyph = device.document().mIsSVGGlyphsDocument(); - query_value.map_or(is_glyph, |v| v == is_glyph) +fn eval_moz_is_glyph(context: &Context) -> bool { + context.device().document().mIsSVGGlyphsDocument() } -fn eval_moz_print_preview( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let is_print_preview = device.is_print_preview(); +fn eval_moz_print_preview(context: &Context) -> bool { + let is_print_preview = context.device().is_print_preview(); if is_print_preview { - debug_assert_eq!(device.media_type(), MediaType::print()); + debug_assert_eq!(context.device().media_type(), MediaType::print()); } - query_value.map_or(is_print_preview, |v| v == is_print_preview) + is_print_preview } -fn eval_moz_non_native_content_theme( - device: &Device, - query_value: Option, - _: Option, -) -> 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_non_native_content_theme(context: &Context) -> bool { + unsafe { bindings::Gecko_MediaFeatures_ShouldAvoidNativeTheme(context.device().document()) } } -fn eval_moz_is_resource_document( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let is_resource_doc = - unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(device.document()) }; - query_value.map_or(is_resource_doc, |v| v == is_resource_doc) +fn eval_moz_is_resource_document(context: &Context) -> bool { + unsafe { bindings::Gecko_MediaFeatures_IsResourceDocument(context.device().document()) } } /// Allows front-end CSS to discern platform via media queries. @@ -572,7 +487,7 @@ pub enum Platform { WindowsWin10, } -fn eval_moz_platform(_: &Device, query_value: Option) -> bool { +fn eval_moz_platform(_: &Context, query_value: Option) -> bool { let query_value = match query_value { Some(v) => v, None => return false, @@ -581,32 +496,22 @@ fn eval_moz_platform(_: &Device, query_value: Option) -> bool { unsafe { bindings::Gecko_MediaFeatures_MatchesPlatform(query_value) } } -fn eval_moz_windows_non_native_menus( - device: &Device, - query_value: Option, - _: Option, -) -> bool { +fn eval_moz_windows_non_native_menus(context: &Context) -> bool { let use_non_native_menus = match static_prefs::pref!("browser.display.windows.non_native_menus") { 0 => false, 1 => true, _ => { - eval_moz_platform(device, Some(Platform::WindowsWin10)) && + eval_moz_platform(context, Some(Platform::WindowsWin10)) && get_lnf_int_as_bool(bindings::LookAndFeel_IntID::WindowsDefaultTheme as i32) }, }; - query_value.map_or(use_non_native_menus, |v| v == use_non_native_menus) + use_non_native_menus } -fn eval_moz_overlay_scrollbars( - device: &Device, - query_value: Option, - _: Option, -) -> bool { - let use_overlay = - unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(device.document()) }; - query_value.map_or(use_overlay, |v| v == use_overlay) +fn eval_moz_overlay_scrollbars(context: &Context) -> bool { + unsafe { bindings::Gecko_MediaFeatures_UseOverlayScrollbars(context.device().document()) } } fn get_lnf_int(int_id: i32) -> i32 { @@ -635,16 +540,15 @@ 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(_: &Device, query_value: Option, _: Option) -> bool { - let value = $get_value(bindings::LookAndFeel_IntID::$int_id as i32); - query_value.map_or(value, |v| v == value) + fn __eval(_: &Context) -> bool { + $get_value(bindings::LookAndFeel_IntID::$int_id as i32) } feature!( $feature_name, AllowsRanges::No, Evaluator::BoolInteger(__eval), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ) }}; ($feature_name:expr, $int_id:ident) => {{ @@ -661,18 +565,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(_: &Device, query_value: Option, _: Option) -> bool { - let value = static_prefs::pref!($pref); - query_value.map_or(value, |v| v == value) + fn __eval(_: &Context) -> bool { + static_prefs::pref!($pref) } feature!( $feature_name, AllowsRanges::No, Evaluator::BoolInteger(__eval), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ) }}; } @@ -682,54 +586,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: [MediaFeatureDescription; 58] = [ +pub static MEDIA_FEATURES: [QueryFeatureDescription; 59] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("height"), AllowsRanges::Yes, Evaluator::Length(eval_height), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("aspect-ratio"), AllowsRanges::Yes, Evaluator::NumberRatio(eval_aspect_ratio), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("orientation"), AllowsRanges::No, keyword_evaluator!(eval_orientation, Orientation), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("device-width"), AllowsRanges::Yes, Evaluator::Length(eval_device_width), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("device-height"), AllowsRanges::Yes, Evaluator::Length(eval_device_height), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("device-aspect-ratio"), AllowsRanges::Yes, Evaluator::NumberRatio(eval_device_aspect_ratio), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("-moz-device-orientation"), AllowsRanges::No, keyword_evaluator!(eval_device_orientation, Orientation), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), // Webkit extensions that we support for de-facto web compatibility. // -webkit-{min|max}-device-pixel-ratio (controlled with its own pref): @@ -737,68 +641,68 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("device-pixel-ratio"), AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio), - ParsingRequirements::WEBKIT_PREFIX, + FeatureFlags::WEBKIT_PREFIX, ), // -webkit-transform-3d. feature!( atom!("transform-3d"), AllowsRanges::No, Evaluator::BoolInteger(eval_transform_3d), - ParsingRequirements::WEBKIT_PREFIX, + FeatureFlags::WEBKIT_PREFIX, ), feature!( atom!("-moz-device-pixel-ratio"), AllowsRanges::Yes, Evaluator::Float(eval_device_pixel_ratio), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("resolution"), AllowsRanges::Yes, Evaluator::Resolution(eval_resolution), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("display-mode"), AllowsRanges::No, keyword_evaluator!(eval_display_mode, DisplayMode), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("grid"), AllowsRanges::No, Evaluator::BoolInteger(eval_grid), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("scan"), AllowsRanges::No, keyword_evaluator!(eval_scan, Scan), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("color"), AllowsRanges::Yes, Evaluator::Integer(eval_color), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("color-index"), AllowsRanges::Yes, Evaluator::Integer(eval_color_index), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("monochrome"), AllowsRanges::Yes, Evaluator::Integer(eval_monochrome), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("prefers-reduced-motion"), AllowsRanges::No, keyword_evaluator!(eval_prefers_reduced_motion, PrefersReducedMotion), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("prefers-contrast"), @@ -809,31 +713,49 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ // layout.css.prefers-contrast.enabled preference. See // disabed_by_pref in media_feature_expression.rs for how that // is done. - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("forced-colors"), AllowsRanges::No, keyword_evaluator!(eval_forced_colors, ForcedColors), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("overflow-block"), AllowsRanges::No, keyword_evaluator!(eval_overflow_block, OverflowBlock), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("overflow-inline"), AllowsRanges::No, keyword_evaluator!(eval_overflow_inline, OverflowInline), - ParsingRequirements::empty(), + FeatureFlags::empty(), + ), + feature!( + atom!("update"), + AllowsRanges::No, + keyword_evaluator!(eval_update, Update), + FeatureFlags::empty(), ), feature!( atom!("prefers-color-scheme"), AllowsRanges::No, keyword_evaluator!(eval_prefers_color_scheme, PrefersColorScheme), - ParsingRequirements::empty(), + 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(), ), // Evaluates to the preferred color scheme for content. Only useful in // chrome context, where the chrome color-scheme and the content @@ -842,31 +764,31 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("-moz-content-prefers-color-scheme"), AllowsRanges::No, keyword_evaluator!(eval_content_prefers_color_scheme, PrefersColorScheme), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("pointer"), AllowsRanges::No, keyword_evaluator!(eval_pointer, Pointer), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("any-pointer"), AllowsRanges::No, keyword_evaluator!(eval_any_pointer, Pointer), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("hover"), AllowsRanges::No, keyword_evaluator!(eval_hover, Hover), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("any-hover"), AllowsRanges::No, keyword_evaluator!(eval_any_hover, Hover), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), // Internal -moz-is-glyph media feature: applies only inside SVG glyphs. // Internal because it is really only useful in the user agent anyway @@ -875,43 +797,43 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ atom!("-moz-is-glyph"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_is_glyph), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-is-resource-document"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_is_resource_document), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-platform"), AllowsRanges::No, keyword_evaluator!(eval_moz_platform, Platform), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-print-preview"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_print_preview), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-non-native-content-theme"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_non_native_content_theme), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-windows-non-native-menus"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_windows_non_native_menus), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), feature!( atom!("-moz-overlay-scrollbars"), AllowsRanges::No, Evaluator::BoolInteger(eval_moz_overlay_scrollbars), - ParsingRequirements::CHROME_AND_UA_ONLY, + FeatureFlags::CHROME_AND_UA_ONLY, ), lnf_int_feature!( atom!("-moz-scrollbar-start-backward"), @@ -933,10 +855,6 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ 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), @@ -959,8 +877,4 @@ pub static MEDIA_FEATURES: [MediaFeatureDescription; 58] = [ GTKCSDReversedPlacement ), lnf_int_feature!(atom!("-moz-system-dark-theme"), SystemUsesDarkTheme), - bool_pref_feature!( - atom!("-moz-proton-places-tooltip"), - "browser.proton.places-tooltip.enabled" - ), ]; diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index e4634d7eab9..837cc61fffe 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -17,6 +17,7 @@ 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; @@ -58,6 +59,9 @@ 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, @@ -100,6 +104,7 @@ 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, } } @@ -267,6 +272,8 @@ 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. @@ -337,7 +344,10 @@ 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) -> Size2D { + pub fn au_viewport_size_for_viewport_unit_resolution( + &self, + variant: ViewportVariant, + ) -> Size2D { self.used_viewport_size.store(true, Ordering::Relaxed); let pc = match self.pres_context() { Some(pc) => pc, @@ -348,8 +358,42 @@ impl Device { return self.page_size_minus_default_margin(pc); } - let size = &pc.mSizeForViewportUnits; - Size2D::new(Au(size.width), Au(size.height)) + 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), + ) + }, + } } /// Returns whether we ever looked up the viewport size of the Device. @@ -357,11 +401,21 @@ 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 { let pc = match self.pres_context() { diff --git a/components/style/gecko/non_ts_pseudo_class_list.rs b/components/style/gecko/non_ts_pseudo_class_list.rs index feb28feb0df..672e8104fe7 100644 --- a/components/style/gecko/non_ts_pseudo_class_list.rs +++ b/components/style/gecko/non_ts_pseudo_class_list.rs @@ -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-dialog", MozTopmostModalDialog, IN_TOPMOST_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-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,8 +92,6 @@ 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, _, _), ] } diff --git a/components/style/gecko/selector_parser.rs b/components/style/gecko/selector_parser.rs index 8fc0900aece..7cfb01e2794 100644 --- a/components/style/gecko/selector_parser.rs +++ b/components/style/gecko/selector_parser.rs @@ -139,15 +139,6 @@ 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) } @@ -184,8 +175,6 @@ impl NonTSPseudoClass { }, NonTSPseudoClass::MozWindowInactive => DocumentState::WINDOW_INACTIVE, NonTSPseudoClass::MozLWTheme => DocumentState::LWTHEME, - NonTSPseudoClass::MozLWThemeBrightText => DocumentState::LWTHEME_BRIGHTTEXT, - NonTSPseudoClass::MozLWThemeDarkText => DocumentState::LWTHEME_DARKTEXT, _ => DocumentState::empty(), } } @@ -208,15 +197,13 @@ impl NonTSPseudoClass { NonTSPseudoClass::MozNativeAnonymous | // :-moz-placeholder is parsed but never matches. NonTSPseudoClass::MozPlaceholder | - // :-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 | - // Similar for the document themes. + // :-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 | - NonTSPseudoClass::MozLWThemeBrightText | - NonTSPseudoClass::MozLWThemeDarkText + NonTSPseudoClass::MozLocaleDir(_) | + NonTSPseudoClass::MozWindowInactive ) } } @@ -458,3 +445,9 @@ unsafe impl HasFFI for SelectorList { } unsafe impl HasSimpleFFI for SelectorList {} unsafe impl HasBoxFFI for SelectorList {} + +// Selector and component sizes are important for matching performance. +size_of_test!(selectors::parser::Selector, 8); +size_of_test!(selectors::parser::Component, 24); +size_of_test!(PseudoElement, 16); +size_of_test!(NonTSPseudoClass, 16); diff --git a/components/style/gecko/snapshot_helpers.rs b/components/style/gecko/snapshot_helpers.rs index 3cb325bb6b1..d75cd275310 100644 --- a/components/style/gecko/snapshot_helpers.rs +++ b/components/style/gecko/snapshot_helpers.rs @@ -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 array: *mut u8 = *(*container) + let attr_array: *mut _ = *(*container) .__bindgen_anon_1 .mValue .as_ref() .__bindgen_anon_1 .mAtomArray .as_ref(); - let array = array as *const structs::nsTArray>; + let array = (*attr_array).mArray.as_ptr() as *const structs::nsTArray>; return Class::More(&**array); } debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase); diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index ca2c3816ec9..9c4c3c43118 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -66,6 +66,8 @@ 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}; @@ -321,6 +323,11 @@ 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] @@ -1030,6 +1037,21 @@ impl<'le> TElement for GeckoElement<'le> { } } + #[inline] + fn primary_box_size(&self) -> Size2D { + 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] { @@ -1393,16 +1415,6 @@ 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() { @@ -1501,7 +1513,7 @@ impl<'le> TElement for GeckoElement<'le> { ) -> bool { use crate::properties::LonghandIdSet; - let after_change_box_style = after_change_style.get_box(); + let after_change_ui_style = after_change_style.get_ui(); let existing_transitions = self.css_transitions_info(); let mut transitions_to_keep = LonghandIdSet::new(); for transition_property in after_change_style.transition_properties() { @@ -1511,7 +1523,7 @@ impl<'le> TElement for GeckoElement<'le> { transitions_to_keep.insert(physical_longhand); if self.needs_transitions_update_per_property( physical_longhand, - after_change_box_style.transition_combined_duration_at(transition_property.index), + after_change_ui_style.transition_combined_duration_at(transition_property.index), before_change_style, after_change_style, &existing_transitions, @@ -1593,23 +1605,9 @@ 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( @@ -1654,9 +1652,7 @@ impl<'le> TElement for GeckoElement<'le> { let ns = self.namespace_id(); // elements get a default MozCenterOrInherit which may get overridden if ns == structs::kNameSpaceID_XHTML as i32 { - 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() && + 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()); @@ -1848,6 +1844,11 @@ 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>, @@ -1960,15 +1961,11 @@ 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( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, - flags_setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { use selectors::matching::*; match *pseudo_class { NonTSPseudoClass::Autofill | @@ -2009,7 +2006,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { NonTSPseudoClass::MozDirAttrRTL | NonTSPseudoClass::MozDirAttrLikeAuto | NonTSPseudoClass::MozModalDialog | - NonTSPseudoClass::MozTopmostModalDialog | + NonTSPseudoClass::MozTopmostModal | NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::MozAutofillPreview | @@ -2024,7 +2021,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { self.is_link() && context.visited_handling().matches_visited() }, NonTSPseudoClass::MozFirstNode => { - flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } let mut elem = self.as_node(); while let Some(prev) = elem.prev_sibling() { if prev.contains_non_whitespace_content() { @@ -2035,7 +2034,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { true }, NonTSPseudoClass::MozLastNode => { - flags_setter(self, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + if context.needs_selector_flags() { + self.apply_selector_flags(ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + } let mut elem = self.as_node(); while let Some(next) = elem.next_sibling() { if next.contains_non_whitespace_content() { @@ -2046,7 +2047,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { true }, NonTSPseudoClass::MozOnlyWhitespace => { - flags_setter(self, ElementSelectorFlags::HAS_EMPTY_SELECTOR); + if context.needs_selector_flags() { + self.apply_selector_flags(ElementSelectorFlags::HAS_EMPTY_SELECTOR); + } if self .as_node() .dom_children() @@ -2068,8 +2071,6 @@ 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(); diff --git a/components/style/global_style_data.rs b/components/style/global_style_data.rs index 80626267cdd..576ac425b5a 100644 --- a/components/style/global_style_data.rs +++ b/components/style/global_style_data.rs @@ -12,11 +12,11 @@ use crate::shared_lock::SharedRwLock; use crate::thread_state; #[cfg(feature = "gecko")] use gecko_profiler; -use parking_lot::{RwLock, RwLockReadGuard}; +use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rayon; use std::env; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; +use std::io; +use std::thread; /// Global style data pub struct GlobalStyleData { @@ -43,12 +43,32 @@ fn thread_name(index: usize) -> String { format!("Style#{}", index) } -// A counter so that we can wait for shutdown of all threads. See -// StyleThreadPool::shutdown. -static ALIVE_WORKER_THREADS: AtomicUsize = AtomicUsize::new(0); +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>> = + 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(()) +} fn thread_startup(_index: usize) { - ALIVE_WORKER_THREADS.fetch_add(1, Ordering::Relaxed); thread_state::initialize_layout_worker_thread(); #[cfg(feature = "gecko")] unsafe { @@ -64,33 +84,24 @@ 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 ALIVE_WORKER_THREADS.load(Ordering::Relaxed) == 0 { + if STYLE_THREAD_JOIN_HANDLES.lock().is_empty() { return; } { // Drop the pool. let _ = STYLE_THREAD_POOL.lock().unwrap().style_thread_pool.write().take(); } - // 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(); + + // 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(); } } @@ -105,7 +116,7 @@ impl StyleThreadPool { lazy_static! { /// Global thread pool - pub static ref STYLE_THREAD_POOL: Mutex = { + pub static ref STYLE_THREAD_POOL: std::sync::Mutex = { let stylo_threads = env::var("STYLO_THREADS") .map(|s| s.parse::().expect("invalid STYLO_THREADS value")); let mut num_threads = match stylo_threads { @@ -149,6 +160,7 @@ 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) @@ -158,7 +170,7 @@ lazy_static! { workers.ok() }; - Mutex::new(StyleThreadPool { + std::sync::Mutex::new(StyleThreadPool { num_threads: if num_threads > 0 { Some(num_threads) } else { diff --git a/components/style/invalidation/element/document_state.rs b/components/style/invalidation/element/document_state.rs index 9ee97344a4c..358257feac0 100644 --- a/components/style/invalidation/element/document_state.rs +++ b/components/style/invalidation/element/document_state.rs @@ -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}; +use selectors::matching::{MatchingContext, MatchingMode, QuirksMode, VisitedHandlingMode, NeedsSelectorFlags}; /// A struct holding the members necessary to invalidate document state /// selectors. @@ -47,6 +47,7 @@ impl<'a, E: TElement, I> DocumentStateInvalidationProcessor<'a, E, I> { None, VisitedHandlingMode::AllLinksVisitedAndUnvisited, quirks_mode, + NeedsSelectorFlags::No, ); matching_context.extra_data = InvalidationMatchingData { diff --git a/components/style/invalidation/element/element_wrapper.rs b/components/style/invalidation/element/element_wrapper.rs index 2aa5749fdee..84d0e5c351a 100644 --- a/components/style/invalidation/element/element_wrapper.rs +++ b/components/style/invalidation/element/element_wrapper.rs @@ -166,15 +166,11 @@ where { type Impl = SelectorImpl; - fn match_non_ts_pseudo_class( + fn match_non_ts_pseudo_class( &self, pseudo_class: &NonTSPseudoClass, context: &mut MatchingContext, - _setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { + ) -> bool { // Some pseudo-classes need special handling to evaluate them against // the snapshot. match *pseudo_class { @@ -232,16 +228,20 @@ where if flag.is_empty() { return self .element - .match_non_ts_pseudo_class(pseudo_class, context, &mut |_, _| {}); + .match_non_ts_pseudo_class(pseudo_class, context); } 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, &mut |_, _| {}), + .match_non_ts_pseudo_class(pseudo_class, context), } } + 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, diff --git a/components/style/invalidation/element/invalidation_map.rs b/components/style/invalidation/element/invalidation_map.rs index 0bba423a17f..f083225b4b7 100644 --- a/components/style/invalidation/element/invalidation_map.rs +++ b/components/style/invalidation/element/invalidation_map.rs @@ -67,6 +67,8 @@ pub struct Dependency { pub parent: Option>, } +size_of_test!(Dependency, 24); + /// The kind of elements down the tree this dependency may affect. #[derive(Debug, Eq, PartialEq)] pub enum DependencyInvalidationKind { diff --git a/components/style/invalidation/element/state_and_attributes.rs b/components/style/invalidation/element/state_and_attributes.rs index bbb1fb46a80..1d02d52947b 100644 --- a/components/style/invalidation/element/state_and_attributes.rs +++ b/components/style/invalidation/element/state_and_attributes.rs @@ -19,8 +19,7 @@ use crate::selector_parser::Snapshot; use crate::stylesheets::origin::OriginSet; use crate::{Atom, WeakAtom}; use selectors::attr::CaseSensitivity; -use selectors::matching::matches_selector; -use selectors::matching::{MatchingContext, MatchingMode, VisitedHandlingMode}; +use selectors::matching::{matches_selector, MatchingContext, MatchingMode, VisitedHandlingMode, NeedsSelectorFlags}; use selectors::NthIndexCache; use smallvec::SmallVec; @@ -67,6 +66,7 @@ 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( dependency: &Dependency, element: &E, wrapper: &W, - mut context: &mut MatchingContext<'_, E::Impl>, + context: &mut MatchingContext<'_, E::Impl>, ) -> bool where E: TElement, @@ -95,8 +95,7 @@ where dependency.selector_offset, None, element, - &mut context, - &mut |_, _| {}, + context, ); let matched_then = matches_selector( @@ -104,8 +103,7 @@ where dependency.selector_offset, None, wrapper, - &mut context, - &mut |_, _| {}, + context, ); matched_then != matches_now diff --git a/components/style/invalidation/stylesheets.rs b/components/style/invalidation/stylesheets.rs index 53130de7a2e..3c4ee845055 100644 --- a/components/style/invalidation/stylesheets.rs +++ b/components/style/invalidation/stylesheets.rs @@ -556,6 +556,7 @@ impl StylesheetInvalidationSet { FontFace(..) | Keyframes(..) | ScrollTimeline(..) | + Container(..) | Style(..) => { if is_generic_change { // TODO(emilio): We need to do this for selector / keyframe @@ -610,7 +611,7 @@ impl StylesheetInvalidationSet { } }, Document(..) | Namespace(..) | Import(..) | Media(..) | Supports(..) | - LayerStatement(..) | LayerBlock(..) => { + Container(..) | LayerStatement(..) | LayerBlock(..) => { // Do nothing, relevant nested rules are visited as part of the // iteration. }, diff --git a/components/style/lib.rs b/components/style/lib.rs index 2967fbcf7ce..b1cec716fcf 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -63,6 +63,8 @@ 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; @@ -101,10 +103,12 @@ 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; @@ -184,7 +188,7 @@ pub mod gecko_properties { } macro_rules! reexport_computed_values { - ( $( { $name: ident, $boxed: expr } )+ ) => { + ( $( { $name: ident } )+ ) => { /// Types for [computed values][computed]. /// /// [computed]: https://drafts.csswg.org/css-cascade/#computed @@ -198,7 +202,6 @@ macro_rules! reexport_computed_values { } } longhand_properties_idents!(reexport_computed_values); - #[cfg(feature = "gecko")] use crate::gecko_string_cache::WeakAtom; #[cfg(feature = "servo")] diff --git a/components/style/logical_geometry.rs b/components/style/logical_geometry.rs index d4d058adc61..c848462deff 100644 --- a/components/style/logical_geometry.rs +++ b/components/style/logical_geometry.rs @@ -168,6 +168,11 @@ 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) @@ -872,10 +877,10 @@ impl LogicalMargin { inline_start: T, ) -> LogicalMargin { LogicalMargin { - block_start: block_start, - inline_end: inline_end, - block_end: block_end, - inline_start: inline_start, + block_start, + inline_end, + block_end, + inline_start, debug_writing_mode: DebugWritingMode::new(mode), } } @@ -1050,6 +1055,18 @@ impl LogicalMargin { } } + #[inline] + pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin { + 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 LogicalMargin { #[inline] pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D { self.debug_writing_mode.check(mode); @@ -1059,42 +1076,32 @@ impl LogicalMargin { let left; if mode.is_vertical() { if mode.is_vertical_lr() { - left = self.block_start; - right = self.block_end; + left = self.block_start.clone(); + right = self.block_end.clone(); } else { - right = self.block_start; - left = self.block_end; + right = self.block_start.clone(); + left = self.block_end.clone(); } if mode.is_inline_tb() { - top = self.inline_start; - bottom = self.inline_end; + top = self.inline_start.clone(); + bottom = self.inline_end.clone(); } else { - bottom = self.inline_start; - top = self.inline_end; + bottom = self.inline_start.clone(); + top = self.inline_end.clone(); } } else { - top = self.block_start; - bottom = self.block_end; + top = self.block_start.clone(); + bottom = self.block_end.clone(); if mode.is_bidi_ltr() { - left = self.inline_start; - right = self.inline_end; + left = self.inline_start.clone(); + right = self.inline_end.clone(); } else { - right = self.inline_start; - left = self.inline_end; + right = self.inline_start.clone(); + left = self.inline_end.clone(); } } SideOffsets2D::new(top, right, bottom, left) } - - #[inline] - pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin { - 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 LogicalMargin { diff --git a/components/style/macros.rs b/components/style/macros.rs index e27a554acf4..3084d33dd3d 100644 --- a/components/style/macros.rs +++ b/components/style/macros.rs @@ -128,3 +128,11 @@ 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); + }; +} diff --git a/components/style/matching.rs b/components/style/matching.rs index f2d300df4b2..2e18a8de962 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -8,7 +8,7 @@ #![deny(missing_docs)] use crate::computed_value_flags::ComputedValueFlags; -use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode, SelectorFlagsMap}; +use crate::context::{CascadeInputs, ElementCascadeInputs, QuirksMode}; use crate::context::{SharedStyleContext, StyleContext}; use crate::data::{ElementData, ElementStyles}; use crate::dom::TElement; @@ -26,7 +26,6 @@ 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. @@ -255,8 +254,8 @@ trait PrivateMatchMethods: TElement { new_style: &ComputedValues, pseudo_element: Option, ) -> bool { - let new_box_style = new_style.get_box(); - let new_style_specifies_animations = new_box_style.specifies_animations(); + let new_ui_style = new_style.get_ui(); + let new_style_specifies_animations = new_ui_style.specifies_animations(); let has_animations = self.has_css_animations(&context.shared, pseudo_element); if !new_style_specifies_animations && !has_animations { @@ -283,7 +282,7 @@ trait PrivateMatchMethods: TElement { }, }; - let old_box_style = old_style.get_box(); + let old_ui_style = old_style.get_ui(); let keyframes_or_timeline_could_have_changed = context .shared @@ -302,12 +301,12 @@ trait PrivateMatchMethods: TElement { } // If the animations changed, well... - if !old_box_style.animations_equals(new_box_style) { + if !old_ui_style.animations_equals(new_ui_style) { return true; } - let old_display = old_box_style.clone_display(); - let new_display = new_box_style.clone_display(); + let old_display = old_style.clone_display(); + let new_display = new_style.clone_display(); // If we were display: none, we may need to trigger animations. if old_display == Display::None && new_display != Display::None { @@ -342,14 +341,13 @@ trait PrivateMatchMethods: TElement { None => return false, }; - let new_box_style = new_style.get_box(); if !self.has_css_transitions(context.shared, pseudo_element) && - !new_box_style.specifies_transitions() + !new_style.get_ui().specifies_transitions() { return false; } - if new_box_style.clone_display().is_none() || old_style.clone_display().is_none() { + if new_style.clone_display().is_none() || old_style.clone_display().is_none() { return false; } @@ -766,8 +764,8 @@ trait PrivateMatchMethods: TElement { }, } - let old_display = old_values.get_box().clone_display(); - let new_display = new_values.get_box().clone_display(); + let old_display = old_values.clone_display(); + let new_display = new_values.clone_display(); if old_display != new_display { // If we used to be a display: none element, and no longer are, our @@ -1007,51 +1005,6 @@ 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, - 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. /// diff --git a/components/style/media_queries/media_feature_expression.rs b/components/style/media_queries/media_feature_expression.rs deleted file mode 100644 index 80827af401c..00000000000 --- a/components/style/media_queries/media_feature_expression.rs +++ /dev/null @@ -1,535 +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 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(&self, dest: &mut CssWriter) -> 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(range_or_op: Option, query_value: Option, 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(range_or_op: Option, 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, - range_or_operator: Option, -} - -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(&self, dest: &mut CssWriter) -> 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, ()> { - 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 “>” - // s and the following “=” , if it’s - // 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, - range_or_operator: Option, - ) -> 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> { - 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> { - 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 '()', 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(&self, dest: &mut CssWriter, 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> { - 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())) - }, - }) - } -} diff --git a/components/style/media_queries/media_list.rs b/components/style/media_queries/media_list.rs index abc2ae6dbc8..8ce317db534 100644 --- a/components/style/media_queries/media_list.rs +++ b/components/style/media_queries/media_list.rs @@ -10,6 +10,7 @@ use super::{Device, MediaQuery, Qualifier}; use crate::context::QuirksMode; use crate::error_reporting::ContextualParseError; use crate::parser::ParserContext; +use crate::values::computed; use cssparser::{Delimiter, Parser}; use cssparser::{ParserInput, Token}; @@ -74,15 +75,17 @@ 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 - self.media_queries.is_empty() || + if self.media_queries.is_empty() { + return true; + } + + computed::Context::for_media_query_evaluation(device, quirks_mode, |context| { 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(device, quirks_mode)); + mq.condition.as_ref().map_or(true, |c| c.matches(context)); // Apply the logical NOT qualifier to the result match mq.qualifier { @@ -90,6 +93,7 @@ impl MediaList { _ => query_match, } }) + }) } /// Whether this `MediaList` contains no media queries. diff --git a/components/style/media_queries/media_query.rs b/components/style/media_queries/media_query.rs index 83e34c5037f..1e737fa695f 100644 --- a/components/style/media_queries/media_query.rs +++ b/components/style/media_queries/media_query.rs @@ -6,7 +6,7 @@ //! //! https://drafts.csswg.org/mediaqueries/#typedef-media-query -use super::media_condition::MediaCondition; +use crate::queries::{QueryCondition, FeatureType}; 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, + pub condition: Option, } impl ToCss for MediaQuery { @@ -134,9 +134,9 @@ impl MediaQuery { .unwrap_or_default(); let condition = if explicit_media_type.is_none() { - Some(MediaCondition::parse(context, input)?) + Some(QueryCondition::parse(context, input, FeatureType::Media)?) } else if input.try_parse(|i| i.expect_ident_matching("and")).is_ok() { - Some(MediaCondition::parse_disallow_or(context, input)?) + Some(QueryCondition::parse_disallow_or(context, input, FeatureType::Media)?) } else { None }; diff --git a/components/style/media_queries/mod.rs b/components/style/media_queries/mod.rs index ca385857481..833f6f53cb9 100644 --- a/components/style/media_queries/mod.rs +++ b/components/style/media_queries/mod.rs @@ -6,15 +6,9 @@ //! //! [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}; diff --git a/components/style/piecewise_linear.rs b/components/style/piecewise_linear.rs new file mode 100644 index 00000000000..0ac0220e44a --- /dev/null +++ b/components/style/piecewise_linear.rs @@ -0,0 +1,223 @@ +/* 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, +} + +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 = , 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, + y: ValueType, +} + +/// Builder object to generate a linear function. +#[derive(Default)] +pub struct PiecewiseLinearFunctionBuilder { + largest_x: Option, + smallest_x: Option, + entries: Vec, +} + +impl PiecewiseLinearFunctionBuilder { + #[allow(missing_docs)] + pub fn new() -> Self { + PiecewiseLinearFunctionBuilder::default() + } + + fn create_entry(&mut self, y: ValueType, x: Option) { + 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, x_end: Option) -> 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(), + } + } +} diff --git a/components/style/properties/cascade.rs b/components/style/properties/cascade.rs index 37d47e1ce61..3d3c6b833b8 100644 --- a/components/style/properties/cascade.rs +++ b/components/style/properties/cascade.rs @@ -28,34 +28,8 @@ 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 ApplyResetProperties { +enum CanHaveLogicalProperties { No, Yes, } @@ -283,6 +257,7 @@ 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); @@ -290,6 +265,8 @@ 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()); } } @@ -315,46 +292,74 @@ 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); - let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); + let using_cached_reset_properties; + let mut cascade = Cascade::new(&mut context, cascade_mode, &referenced_properties); + let mut shorthand_cache = ShorthandsWithPropertyReferencesCache::default(); - cascade.apply_properties::( - ApplyResetProperties::Yes, - declarations.iter().cloned(), - &mut shorthand_cache, - ); + 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.compute_visited_style_if_needed( - element, - parent_style, - parent_style_ignoring_first_line, - layout_parent_style, - guards, - ); + if cascade.apply_properties( + CanHaveLogicalProperties::No, + LonghandIdSet::fonts_and_color_group(), + declarations.iter().cloned(), + &mut shorthand_cache, + ) { + cascade.fixup_font_stuff(); + } - let using_cached_reset_properties = - cascade.try_to_use_cached_reset_properties(rule_cache, 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 apply_reset = if using_cached_reset_properties { - ApplyResetProperties::No - } else { - ApplyResetProperties::Yes - }; + using_cached_reset_properties = + cascade.try_to_use_cached_reset_properties(rule_cache, guards); - cascade.apply_properties::( - apply_reset, - declarations.iter().cloned(), - &mut shorthand_cache, - ); - - using_cached_reset_properties + if using_cached_reset_properties { + LonghandIdSet::late_group_only_inherited() + } else { + LonghandIdSet::late_group() + } + } }; + 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 { .. }) { @@ -413,7 +418,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).to_rgba(RGBA::new(0, 0, 0, 255)); + let color = color.to_computed_value(context).into_rgba(RGBA::new(0, 0, 0, 255)); color.alpha } @@ -428,14 +433,17 @@ fn tweak_when_ignoring_colors( // otherwise, this is needed to preserve semi-transparent // backgrounds. // - // 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 + // 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 // (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 @@ -497,6 +505,8 @@ 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, @@ -504,10 +514,15 @@ struct Cascade<'a, 'b: 'a> { } impl<'a, 'b: 'a> Cascade<'a, 'b> { - fn new(context: &'a mut computed::Context<'b>, cascade_mode: CascadeMode<'a>) -> Self { + fn new( + context: &'a mut computed::Context<'b>, + cascade_mode: CascadeMode<'a>, + referenced: &'a LonghandIdSet, + ) -> Self { Self { context, cascade_mode, + referenced, seen: LonghandIdSet::default(), author_specified: LonghandIdSet::default(), reverted_set: Default::default(), @@ -575,23 +590,21 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { (CASCADE_PROPERTY[discriminant])(declaration, &mut self.context); } - fn apply_properties<'decls, Phase, I>( + fn apply_properties<'decls, I>( &mut self, - apply_reset: ApplyResetProperties, + can_have_logical_properties: CanHaveLogicalProperties, + properties_to_apply: &'a LonghandIdSet, declarations: I, mut shorthand_cache: &mut ShorthandsWithPropertyReferencesCache, - ) where - Phase: CascadePhase, + ) -> bool + where I: Iterator, { - let apply_reset = apply_reset == ApplyResetProperties::Yes; + if !self.referenced.contains_any(properties_to_apply) { + return false; + } - 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 can_have_logical_properties = can_have_logical_properties == CanHaveLogicalProperties::Yes; let ignore_colors = !self.context.builder.device.use_document_colors(); let mut declarations_to_apply_unless_overriden = DeclarationsToApplyUnlessOverriden::new(); @@ -605,20 +618,15 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { PropertyDeclarationId::Custom(..) => continue, }; - let inherited = longhand_id.inherited(); - if !apply_reset && !inherited { + if !properties_to_apply.contains(longhand_id) { continue; } - 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 { + 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 { + longhand_id }; if self.seen.contains(physical_longhand_id) { @@ -633,15 +641,6 @@ 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); @@ -675,8 +674,8 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { continue; }, CSSWideKeyword::Unset => true, - CSSWideKeyword::Inherit => inherited, - CSSWideKeyword::Initial => !inherited, + CSSWideKeyword::Inherit => longhand_id.inherited(), + CSSWideKeyword::Initial => !longhand_id.inherited(), }, None => false, }; @@ -710,22 +709,13 @@ impl<'a, 'b: 'a> Cascade<'a, 'b> { } } - if Phase::is_early() { - self.fixup_font_stuff(); - self.compute_writing_mode(); - } else { - self.finished_applying_properties(); - } + true } fn compute_writing_mode(&mut self) { - 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; + debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); + self.context.builder.writing_mode = + WritingMode::new(self.context.builder.get_inherited_box()) } fn compute_visited_style_if_needed( @@ -734,20 +724,12 @@ 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, { - 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, - }; - + debug_assert!(matches!(self.cascade_mode, CascadeMode::Unvisited { .. })); let is_link = self.context.builder.pseudo.is_none() && element.unwrap().is_link(); macro_rules! visited_parent { diff --git a/components/style/properties/counted_unknown_properties.py b/components/style/properties/counted_unknown_properties.py index 473f2f599d2..997fd167a07 100644 --- a/components/style/properties/counted_unknown_properties.py +++ b/components/style/properties/counted_unknown_properties.py @@ -73,7 +73,6 @@ 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", diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 84e0b482569..ac1de39f877 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -444,6 +444,7 @@ class Longhand(Property): "ColumnCount", "Contain", "ContentVisibility", + "ContainerType", "Display", "FillRule", "Float", @@ -491,6 +492,7 @@ class Longhand(Property): "ScrollbarGutter", "ScrollSnapAlign", "ScrollSnapAxis", + "ScrollSnapStop", "ScrollSnapStrictness", "ScrollSnapType", "TextAlign", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index bc0a78183a8..0edfbf84451 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -755,7 +755,6 @@ 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", @@ -1181,11 +1180,7 @@ fn static_assert() { <% skip_box_longhands= """display - animation-name animation-delay animation-duration - animation-direction animation-fill-mode animation-play-state - animation-iteration-count animation-timeline animation-timing-function - clear transition-duration transition-delay - transition-timing-function transition-property + clear -webkit-line-clamp""" %> <%self:impl_trait style_struct_name="Box" skip_longhands="${skip_box_longhands}"> #[inline] @@ -1227,245 +1222,6 @@ 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(&mut self, v: I) - where I: IntoIterator, - 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(&mut self, v: I) - where I: IntoIterator, - 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(&mut self, v: I) - where - I: IntoIterator, - 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(&mut self, v: I) - where - I: IntoIterator, - 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 { @@ -2020,7 +1776,247 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask- } -<%self:impl_trait style_struct_name="UI"> +<% 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(&mut self, v: I) + where I: IntoIterator, + 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(&mut self, v: I) + where I: IntoIterator, + 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(&mut self, v: I) + where + I: IntoIterator, + 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(&mut self, v: I) + where + I: IntoIterator, + 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="XUL"> diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 5ffea97ba50..b62ddb25ce4 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -799,7 +799,7 @@ impl<'a> TransitionPropertyIterator<'a> { pub fn from_style(style: &'a ComputedValues) -> Self { Self { style, - index_range: 0..style.get_box().transition_property_count(), + index_range: 0..style.get_ui().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_box().transition_property_at(index) { + match self.style.get_ui().transition_property_at(index) { TransitionProperty::Longhand(longhand_id) => { return Some(TransitionPropertyIteration { longhand_id, diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index 1fccedcc652..6598885c3a1 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -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, DEFAULT_RULES_EXCEPT_KEYFRAME, Keyword, Method, to_rust_ident, to_camel_case%> +<% from data import ALL_AXES, Keyword, Method, to_rust_ident, to_camel_case%> <% data.new_style_struct("Box", inherited=False, @@ -150,193 +150,6 @@ ${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( @@ -472,6 +285,15 @@ ${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, @@ -623,6 +445,28 @@ ${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", diff --git a/components/style/properties/longhands/margin.mako.rs b/components/style/properties/longhands/margin.mako.rs index b2d3ff3949a..251ee85f265 100644 --- a/components/style/properties/longhands/margin.mako.rs +++ b/components/style/properties/longhands/margin.mako.rs @@ -28,6 +28,16 @@ )} % 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], diff --git a/components/style/properties/longhands/svg.mako.rs b/components/style/properties/longhands/svg.mako.rs index 3d711462ff3..13704e5953b 100644 --- a/components/style/properties/longhands/svg.mako.rs +++ b/components/style/properties/longhands/svg.mako.rs @@ -20,7 +20,7 @@ ${helpers.single_keyword( ${helpers.predefined_type( "stop-color", "Color", - "RGBA::new(0, 0, 0, 255).into()", + "computed::Color::black()", 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", - "RGBA::new(0, 0, 0, 255).into()", + "computed::Color::black()", 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", - "RGBA::new(255, 255, 255, 255).into()", + "computed::Color::white()", engines="gecko", animation_value_type="AnimatedColor", spec="https://www.w3.org/TR/SVG/filters.html#LightingColorProperty", diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 400eaedf577..8d4bbab038c 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -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 Method %> +<% from data import DEFAULT_RULES_EXCEPT_KEYFRAME, Method %> // CSS Basic User Interface Module Level 1 // https://drafts.csswg.org/css-ui-3/ @@ -51,12 +51,15 @@ ${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 menu tooltip sheet cliprounded", + "default none", 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)", @@ -95,6 +98,16 @@ ${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", @@ -104,3 +117,190 @@ ${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, +)} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index fd2bc2cb373..4327a739001 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -267,6 +267,9 @@ 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 { tag: u16, @@ -474,9 +477,10 @@ impl NonCustomPropertyId { self.0 } + /// Convert a `NonCustomPropertyId` into a `nsCSSPropertyID`. #[cfg(feature = "gecko")] #[inline] - fn to_nscsspropertyid(self) -> nsCSSPropertyID { + pub fn to_nscsspropertyid(self) -> nsCSSPropertyID { // unsafe: guaranteed by static_assert_nscsspropertyid above. unsafe { std::mem::transmute(self.0 as i32) } } @@ -892,6 +896,72 @@ 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 { @@ -920,7 +990,7 @@ impl LonghandIdSet { /// Returns the set of longhands that are ignored when document colors are /// disabled. #[inline] - pub fn ignored_when_colors_disabled() -> &'static Self { + fn ignored_when_colors_disabled() -> &'static Self { ${static_longhand_id_set( "IGNORED_WHEN_COLORS_DISABLED", lambda p: p.ignored_when_colors_disabled @@ -928,6 +998,48 @@ 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 elements or their descendant scrollbar parts. #[cfg(debug_assertions)] @@ -1330,91 +1442,12 @@ 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, it’s 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 @@ -2560,9 +2593,11 @@ impl PropertyDeclaration { } } -type SubpropertiesVec = ArrayVec; +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 = ArrayVec; /// A stack-allocated vector of `PropertyDeclaration` /// large enough to parse one CSS `key: value` declaration. @@ -2574,6 +2609,10 @@ 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. It’s big, try not to move it around. #[inline] @@ -2618,11 +2657,7 @@ impl SourcePropertyDeclaration { /// Return type of SourcePropertyDeclaration::drain pub struct SourcePropertyDeclarationDrain<'a> { - declarations: ArrayVecDrain< - 'a, PropertyDeclaration, - ${max(len(s.sub_properties) for s in data.shorthands_except_all()) \ - if data.shorthands_except_all() else 0} - >, + declarations: ArrayVecDrain<'a, PropertyDeclaration, SUB_PROPERTIES_ARRAY_CAP>, all_shorthand: AllShorthand, } @@ -2903,11 +2938,11 @@ pub mod style_structs { % endif % endfor - % if style_struct.name == "Box": + % if style_struct.name == "UI": /// 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.0.is_some()) + self.animation_name_iter().any(|name| !name.is_none()) } /// Returns whether there are any transitions specified. @@ -3035,7 +3070,7 @@ impl ComputedValues { /// Returns whether this style's display value is equal to contents. pub fn is_display_contents(&self) -> bool { - self.get_box().clone_display().is_contents() + self.clone_display().is_contents() } /// Gets a reference to the rule node. Panic if no rule node exists. @@ -3184,7 +3219,7 @@ impl ComputedValues { /// style.resolve_color(style.get_border().clone_border_top_color()); #[inline] pub fn resolve_color(&self, color: computed::Color) -> RGBA { - color.to_rgba(self.get_inherited_text().clone_color()) + color.into_rgba(self.get_inherited_text().clone_color()) } /// Returns which longhand properties have different values in the two @@ -4188,7 +4223,7 @@ macro_rules! css_properties_accessors { /// Call the given macro with tokens like this for each longhand properties: /// /// ``` -/// { snake_case_ident, true } +/// { snake_case_ident } /// ``` /// /// … where the boolean indicates whether the property value type @@ -4198,12 +4233,39 @@ macro_rules! longhand_properties_idents { ($macro_name: ident) => { $macro_name! { % for property in data.longhands: - { ${property.ident}, ${"true" if property.boxed else "false"} } + { ${property.ident} } % endfor } } } +// Large pages generate tens of thousands of ComputedValues. +size_of_test!(ComputedValues, 192); +// FFI relies on this. +size_of_test!(Option>, 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::() > BOX_THRESHOLD); +% else: +const_assert!(std::mem::size_of::() <= 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} { diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index a0bc082caeb..a05f7fd461c 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -24,318 +24,6 @@ ${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> { - 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, - } - - fn parse_one_transition<'i, 't>( - context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - % 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 , - // 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(&self, dest: &mut CssWriter) -> 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 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> { - 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> { - % 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(&self, dest: &mut CssWriter) -> 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.two_properties_shorthand( "overscroll-behavior", "overscroll-behavior-x", @@ -345,6 +33,45 @@ ${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> { + 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(&self, dest: &mut CssWriter) -> 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 engines="gecko" name="page-break-before" diff --git a/components/style/properties/shorthands/ui.mako.rs b/components/style/properties/shorthands/ui.mako.rs new file mode 100644 index 00000000000..e2eb8d979a0 --- /dev/null +++ b/components/style/properties/shorthands/ui.mako.rs @@ -0,0 +1,317 @@ +/* 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> { + 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, + } + + fn parse_one_transition<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + % 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 , + // 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(&self, dest: &mut CssWriter) -> 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 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> { + 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> { + % 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(&self, dest: &mut CssWriter) -> 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(()) + } + } + diff --git a/components/style/media_queries/media_condition.rs b/components/style/queries/condition.rs similarity index 57% rename from components/style/media_queries/media_condition.rs rename to components/style/queries/condition.rs index f735704556e..da8ad02d595 100644 --- a/components/style/media_queries/media_condition.rs +++ b/components/style/queries/condition.rs @@ -2,13 +2,14 @@ * 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 media query condition: +//! A query condition: //! //! https://drafts.csswg.org/mediaqueries-4/#typedef-media-condition +//! https://drafts.csswg.org/css-contain-3/#typedef-container-condition -use super::{Device, MediaFeatureExpression}; -use crate::context::QuirksMode; +use super::{QueryFeatureExpression, FeatureType, FeatureFlags}; use crate::parser::ParserContext; +use crate::values::computed; use cssparser::{Parser, Token}; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; @@ -28,38 +29,38 @@ enum AllowOr { No, } -/// Represents a media condition. +/// Represents a condition. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)] -pub enum MediaCondition { - /// A simple media feature expression, implicitly parenthesized. - Feature(MediaFeatureExpression), +pub enum QueryCondition { + /// A simple feature expression, implicitly parenthesized. + Feature(QueryFeatureExpression), /// A negation of a condition. - Not(Box), + Not(Box), /// A set of joint operations. - Operation(Box<[MediaCondition]>, Operator), + Operation(Box<[QueryCondition]>, Operator), /// A condition wrapped in parenthesis. - InParens(Box), + InParens(Box), } -impl ToCss for MediaCondition { +impl ToCss for QueryCondition { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { match *self { - // NOTE(emilio): MediaFeatureExpression already includes the + // NOTE(emilio): QueryFeatureExpression already includes the // parenthesis. - MediaCondition::Feature(ref f) => f.to_css(dest), - MediaCondition::Not(ref c) => { + QueryCondition::Feature(ref f) => f.to_css(dest), + QueryCondition::Not(ref c) => { dest.write_str("not ")?; c.to_css(dest) }, - MediaCondition::InParens(ref c) => { + QueryCondition::InParens(ref c) => { dest.write_char('(')?; c.to_css(dest)?; dest.write_char(')') }, - MediaCondition::Operation(ref list, op) => { + QueryCondition::Operation(ref list, op) => { let mut iter = list.iter(); iter.next().unwrap().to_css(dest)?; for item in iter { @@ -74,28 +75,60 @@ impl ToCss for MediaCondition { } } -impl MediaCondition { - /// Parse a single media condition. +impl QueryCondition { + /// Parse a single condition. pub fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { - Self::parse_internal(context, input, AllowOr::Yes) + Self::parse_internal(context, input, feature_type, AllowOr::Yes) } - /// Parse a single media condition, disallowing `or` expressions. + fn visit(&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. /// - /// To be used from the legacy media query syntax. + /// To be used from the legacy query syntax. pub fn parse_disallow_or<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { - Self::parse_internal(context, input, AllowOr::No) + Self::parse_internal(context, input, feature_type, AllowOr::No) } fn parse_internal<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, allow_or: AllowOr, ) -> Result> { let location = input.current_source_location(); @@ -108,12 +141,12 @@ impl MediaCondition { }; if is_negation { - let inner_condition = Self::parse_in_parens(context, input)?; - return Ok(MediaCondition::Not(Box::new(inner_condition))); + let inner_condition = Self::parse_in_parens(context, input, feature_type)?; + return Ok(QueryCondition::Not(Box::new(inner_condition))); } // ParenthesisBlock. - let first_condition = Self::parse_paren_block(context, input)?; + let first_condition = Self::parse_paren_block(context, input, feature_type)?; let operator = match input.try_parse(Operator::parse) { Ok(op) => op, Err(..) => return Ok(first_condition), @@ -125,7 +158,7 @@ impl MediaCondition { let mut conditions = vec![]; conditions.push(first_condition); - conditions.push(Self::parse_in_parens(context, input)?); + conditions.push(Self::parse_in_parens(context, input, feature_type)?); let delim = match operator { Operator::And => "and", @@ -134,50 +167,52 @@ impl MediaCondition { loop { if input.try_parse(|i| i.expect_ident_matching(delim)).is_err() { - return Ok(MediaCondition::Operation( + return Ok(QueryCondition::Operation( conditions.into_boxed_slice(), operator, )); } - conditions.push(Self::parse_in_parens(context, input)?); + conditions.push(Self::parse_in_parens(context, input, feature_type)?); } } - /// Parse a media condition in parentheses. + /// Parse a condition in parentheses. pub fn parse_in_parens<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { input.expect_parenthesis_block()?; - Self::parse_paren_block(context, input) + Self::parse_paren_block(context, input, feature_type) } fn parse_paren_block<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, + feature_type: FeatureType, ) -> Result> { input.parse_nested_block(|input| { // Base case. - if let Ok(inner) = input.try_parse(|i| Self::parse(context, i)) { - return Ok(MediaCondition::InParens(Box::new(inner))); + if let Ok(inner) = input.try_parse(|i| Self::parse(context, i, feature_type)) { + return Ok(QueryCondition::InParens(Box::new(inner))); } - let expr = MediaFeatureExpression::parse_in_parenthesis_block(context, input)?; - Ok(MediaCondition::Feature(expr)) + let expr = QueryFeatureExpression::parse_in_parenthesis_block(context, input, feature_type)?; + Ok(QueryCondition::Feature(expr)) }) } /// Whether this condition matches the device and quirks mode. - pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool { + pub fn matches(&self, context: &computed::Context) -> bool { match *self { - 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) => { + 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) => { let mut iter = conditions.iter(); match op { - Operator::And => iter.all(|c| c.matches(device, quirks_mode)), - Operator::Or => iter.any(|c| c.matches(device, quirks_mode)), + Operator::And => iter.all(|c| c.matches(context)), + Operator::Or => iter.any(|c| c.matches(context)), } }, } diff --git a/components/style/media_queries/media_feature.rs b/components/style/queries/feature.rs similarity index 57% rename from components/style/media_queries/media_feature.rs rename to components/style/queries/feature.rs index e6edff3f68a..9efd64f8c23 100644 --- a/components/style/media_queries/media_feature.rs +++ b/components/style/queries/feature.rs @@ -2,13 +2,10 @@ * 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/. */ -//! Media features. +//! Query features. -use super::media_feature_expression::RangeOrOperator; -use super::Device; use crate::parser::ParserContext; -use crate::values::computed::Ratio; -use crate::values::computed::{CSSPixelLength, Resolution}; +use crate::values::computed::{self, CSSPixelLength, Resolution, Ratio}; use crate::Atom; use cssparser::Parser; use std::fmt; @@ -17,12 +14,7 @@ use style_traits::ParseError; /// A generic discriminant for an enum value. pub type KeywordDiscriminant = u8; -type MediaFeatureEvaluator = fn( - device: &Device, - // null == no value was given in the query. - value: Option, - range_or_operator: Option, -) -> bool; +type QueryFeatureGetter = fn(device: &computed::Context) -> T; /// Serializes a given discriminant. /// @@ -36,19 +28,19 @@ pub type KeywordParser = for<'a, 'i, 't> fn( input: &'a mut Parser<'i, 't>, ) -> Result>; -/// An evaluator for a given media feature. +/// An evaluator for a given feature. /// /// This determines the kind of values that get parsed, too. #[allow(missing_docs)] pub enum Evaluator { - Length(MediaFeatureEvaluator), - Integer(MediaFeatureEvaluator), - Float(MediaFeatureEvaluator), - BoolInteger(MediaFeatureEvaluator), + Length(QueryFeatureGetter), + Integer(QueryFeatureGetter), + Float(QueryFeatureGetter), + BoolInteger(QueryFeatureGetter), /// A non-negative number ratio, such as the one from device-pixel-ratio. - NumberRatio(MediaFeatureEvaluator), + NumberRatio(QueryFeatureGetter), /// A resolution. - Resolution(MediaFeatureEvaluator), + Resolution(QueryFeatureGetter), /// A keyword value. Enumerated { /// The parser to get a discriminant given a string. @@ -60,9 +52,8 @@ pub enum Evaluator { serializer: KeywordSerializer, /// The evaluator itself. This is guaranteed to be called with a /// keyword that `parser` has produced. - evaluator: MediaFeatureEvaluator, + evaluator: fn(&computed::Context, Option) -> bool, }, - Ident(MediaFeatureEvaluator), } /// A simple helper macro to create a keyword evaluator. @@ -76,14 +67,14 @@ macro_rules! keyword_evaluator { context: &$crate::parser::ParserContext, input: &mut $crate::cssparser::Parser<'i, 't>, ) -> Result< - $crate::media_queries::media_feature::KeywordDiscriminant, + $crate::queries::feature::KeywordDiscriminant, ::style_traits::ParseError<'i>, > { let kw = <$keyword_type as $crate::parser::Parse>::parse(context, input)?; - Ok(kw as $crate::media_queries::media_feature::KeywordDiscriminant) + Ok(kw as $crate::queries::feature::KeywordDiscriminant) } - fn __serialize(kw: $crate::media_queries::media_feature::KeywordDiscriminant) -> String { + fn __serialize(kw: $crate::queries::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(); @@ -91,24 +82,17 @@ macro_rules! keyword_evaluator { } fn __evaluate( - device: &$crate::media_queries::Device, - value: Option<$crate::media_queries::media_feature::KeywordDiscriminant>, - range_or_operator: Option< - $crate::media_queries::media_feature_expression::RangeOrOperator, - >, + context: &$crate::values::computed::Context, + value: Option<$crate::queries::feature::KeywordDiscriminant>, ) -> 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(device, value) + $actual_evaluator(context, value) } - $crate::media_queries::media_feature::Evaluator::Enumerated { + $crate::queries::feature::Evaluator::Enumerated { parser: __parse, serializer: __serialize, evaluator: __evaluate, @@ -117,17 +101,46 @@ macro_rules! keyword_evaluator { } bitflags! { - /// Different requirements or toggles that change how a expression is - /// parsed. - pub struct ParsingRequirements: u8 { + /// Different flags or toggles that change how a expression is parsed or + /// evaluated. + #[derive(ToShmem)] + pub struct FeatureFlags : 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; } } -/// Whether a media feature allows ranges or not. +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. #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum AllowsRanges { @@ -135,46 +148,45 @@ pub enum AllowsRanges { No, } -/// A description of a media feature. -pub struct MediaFeatureDescription { - /// The media feature name, in ascii lowercase. +/// A description of a feature. +pub struct QueryFeatureDescription { + /// The 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 requirements that need to hold for the feature to be - /// successfully parsed. - pub requirements: ParsingRequirements, + /// Different feature-specific flags. + pub flags: FeatureFlags, } -impl MediaFeatureDescription { - /// Whether this media feature allows ranges. +impl QueryFeatureDescription { + /// Whether this feature allows ranges. #[inline] pub fn allows_ranges(&self) -> bool { self.allows_ranges == AllowsRanges::Yes } } -/// A simple helper to construct a `MediaFeatureDescription`. +/// A simple helper to construct a `QueryFeatureDescription`. macro_rules! feature { - ($name:expr, $allows_ranges:expr, $evaluator:expr, $reqs:expr,) => { - $crate::media_queries::media_feature::MediaFeatureDescription { + ($name:expr, $allows_ranges:expr, $evaluator:expr, $flags:expr,) => { + $crate::queries::feature::QueryFeatureDescription { name: $name, allows_ranges: $allows_ranges, evaluator: $evaluator, - requirements: $reqs, + flags: $flags, } }; } -impl fmt::Debug for MediaFeatureDescription { +impl fmt::Debug for QueryFeatureDescription { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("MediaFeatureExpression") + f.debug_struct("QueryFeatureDescription") .field("name", &self.name) .field("allows_ranges", &self.allows_ranges) - .field("requirements", &self.requirements) + .field("flags", &self.flags) .finish() } } diff --git a/components/style/queries/feature_expression.rs b/components/style/queries/feature_expression.rs new file mode 100644 index 00000000000..aa2e4a04582 --- /dev/null +++ b/components/style/queries/feature_expression.rs @@ -0,0 +1,711 @@ +/* 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(&self, dest: &mut CssWriter) -> 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> { + 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 “>” + // s and the following “=” , if it’s + // 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( + &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(&self, dest: &mut CssWriter) -> 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, 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(&self, dest: &mut CssWriter) -> 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> { + 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), 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 feature-name) + /// (feature-value feature-name feature-value) + fn parse_multi_range_syntax<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + feature_type: FeatureType, + ) -> Result> { + 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 + // 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> { + 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 + // '()', 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(&self, dest: &mut CssWriter, 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> { + 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)?) + }, + }) + } +} diff --git a/components/style/queries/mod.rs b/components/style/queries/mod.rs new file mode 100644 index 00000000000..6bbf197c43b --- /dev/null +++ b/components/style/queries/mod.rs @@ -0,0 +1,19 @@ +/* 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}; diff --git a/components/style/queries/values.rs b/components/style/queries/values.rs new file mode 100644 index 00000000000..5bd1cede48b --- /dev/null +++ b/components/style/queries/values.rs @@ -0,0 +1,36 @@ +/* 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(size: euclid::default::Size2D, value: Option) -> 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, + } + } +} diff --git a/components/style/rule_collector.rs b/components/style/rule_collector.rs index dfd4d2bd316..0e18383455c 100644 --- a/components/style/rule_collector.rs +++ b/components/style/rule_collector.rs @@ -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::{ElementSelectorFlags, MatchingContext, MatchingMode}; +use selectors::matching::{MatchingContext, MatchingMode}; use servo_arc::ArcBorrow; use smallvec::SmallVec; @@ -59,7 +59,7 @@ pub fn containing_shadow_ignoring_svg_use( /// /// 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, F: 'a> +pub struct RuleCollector<'a, 'b: 'a, E> where E: TElement, { @@ -73,16 +73,14 @@ 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, F: 'a> RuleCollector<'a, 'b, E, F> +impl<'a, 'b: 'a, E> RuleCollector<'a, 'b, E> where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { /// Trivially construct a new collector. pub fn new( @@ -95,7 +93,6 @@ 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 @@ -125,7 +122,6 @@ where animation_declarations, rule_inclusion, context, - flags_setter, rules, matches_user_and_author_rules, matches_document_author_rules: matches_user_and_author_rules, @@ -227,9 +223,9 @@ where part_rules, &mut self.rules, &mut self.context, - &mut self.flags_setter, cascade_level, cascade_data, + &self.stylist, ); } @@ -246,9 +242,9 @@ where self.rule_hash_target, &mut self.rules, &mut self.context, - &mut self.flags_setter, cascade_level, cascade_data, + &self.stylist, ); } diff --git a/components/style/rule_tree/core.rs b/components/style/rule_tree/core.rs index e4632ffa711..85584a0e224 100644 --- a/components/style/rule_tree/core.rs +++ b/components/style/rule_tree/core.rs @@ -203,9 +203,6 @@ 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::(); - /// A node in the rule tree. struct RuleNode { /// The root node. Only the root has no root pointer, for obvious reasons. @@ -768,3 +765,8 @@ 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, 8); diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index c2339ee9907..01510e62070 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -20,7 +20,7 @@ mod map; mod source; mod unsafe_box; -pub use self::core::{RuleTree, StrongRuleNode, RULE_NODE_SIZE}; +pub use self::core::{RuleTree, StrongRuleNode}; pub use self::level::{CascadeLevel, ShadowCascadeOrder}; pub use self::source::StyleSource; diff --git a/components/style/selector_map.rs b/components/style/selector_map.rs index 8a575ca8386..b2cd8d918ca 100644 --- a/components/style/selector_map.rs +++ b/components/style/selector_map.rs @@ -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::{CascadeData, Rule}; +use crate::stylist::{Stylist, CascadeData, Rule, ContainerConditionId}; use crate::AllocErr; use crate::{Atom, LocalName, Namespace, ShrinkIfNeeded, WeakAtom}; use precomputed_hash::PrecomputedHash; -use selectors::matching::{matches_selector, ElementSelectorFlags, MatchingContext}; +use selectors::matching::{matches_selector, MatchingContext}; use selectors::parser::{Combinator, Component, SelectorIter}; use smallvec::SmallVec; use std::collections::hash_map; @@ -186,34 +186,33 @@ impl SelectorMap { /// /// 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( + pub fn get_all_matching_rules( &self, element: E, rule_hash_target: E, matching_rules_list: &mut ApplicableDeclarationList, - context: &mut MatchingContext, - flags_setter: &mut F, + matching_context: &mut MatchingContext, cascade_level: CascadeLevel, cascade_data: &CascadeData, + stylist: &Stylist, ) where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { if self.is_empty() { return; } - let quirks_mode = context.quirks_mode(); + let quirks_mode = matching_context.quirks_mode(); if rule_hash_target.is_root() { SelectorMap::get_matching_rules( element, &self.root, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ); } @@ -223,10 +222,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } } @@ -237,10 +236,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } }); @@ -252,10 +251,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } }); @@ -266,10 +265,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } @@ -278,10 +277,10 @@ impl SelectorMap { element, rules, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ) } @@ -289,38 +288,44 @@ impl SelectorMap { element, &self.other, matching_rules_list, - context, - flags_setter, + matching_context, cascade_level, cascade_data, + stylist, ); } /// Adds rules in `rules` that match `element` to the `matching_rules` list. - pub(crate) fn get_matching_rules( + pub(crate) fn get_matching_rules( element: E, rules: &[Rule], matching_rules: &mut ApplicableDeclarationList, - context: &mut MatchingContext, - flags_setter: &mut F, + matching_context: &mut MatchingContext, 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, - context, - flags_setter, + matching_context, ) { - matching_rules - .push(rule.to_applicable_declaration_block(cascade_level, cascade_data)); + continue; } + + 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)); } } } diff --git a/components/style/servo/media_queries.rs b/components/style/servo/media_queries.rs index e1822332ff1..d4a4d581b1f 100644 --- a/components/style/servo/media_queries.rs +++ b/components/style/servo/media_queries.rs @@ -7,13 +7,13 @@ use crate::context::QuirksMode; use crate::custom_properties::CssEnvironment; use crate::font_metrics::FontMetrics; -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::queries::feature::{AllowsRanges, Evaluator, FeatureFlags, QueryFeatureDescription}; 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,8 +143,13 @@ impl Device { } /// Like the above, but records that we've used viewport units. - pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> UntypedSize2D { + pub fn au_viewport_size_for_viewport_unit_resolution( + &self, + _: ViewportVariant, + ) -> UntypedSize2D { 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() } @@ -229,16 +234,8 @@ impl Device { } /// https://drafts.csswg.org/mediaqueries-4/#width -fn eval_width( - device: &Device, - value: Option, - range_or_operator: Option, -) -> bool { - RangeOrOperator::evaluate( - range_or_operator, - value.map(Au::from), - device.au_viewport_size().width, - ) +fn eval_width(context: &Context) -> CSSPixelLength { + CSSPixelLength::new(context.device().au_viewport_size().width.to_f32_px()) } #[derive(Clone, Copy, Debug, FromPrimitive, Parse, ToCss)] @@ -249,7 +246,7 @@ enum Scan { } /// https://drafts.csswg.org/mediaqueries-4/#scan -fn eval_scan(_: &Device, _: Option) -> bool { +fn eval_scan(_: &Context, _: Option) -> bool { // Since we doesn't support the 'tv' media type, the 'scan' feature never // matches. false @@ -257,18 +254,18 @@ fn eval_scan(_: &Device, _: Option) -> bool { lazy_static! { /// A list with all the media features that Servo supports. - pub static ref MEDIA_FEATURES: [MediaFeatureDescription; 2] = [ + pub static ref MEDIA_FEATURES: [QueryFeatureDescription; 2] = [ feature!( atom!("width"), AllowsRanges::Yes, Evaluator::Length(eval_width), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), feature!( atom!("scan"), AllowsRanges::No, keyword_evaluator!(eval_scan, Scan), - ParsingRequirements::empty(), + FeatureFlags::empty(), ), ]; } diff --git a/components/style/sharing/checks.rs b/components/style/sharing/checks.rs index 5e8350e78d3..2f8f410190f 100644 --- a/components/style/sharing/checks.rs +++ b/components/style/sharing/checks.rs @@ -7,7 +7,7 @@ //! elements can indeed share the same style. use crate::bloom::StyleBloom; -use crate::context::{SelectorFlagsMap, SharedStyleContext}; +use crate::context::SharedStyleContext; use crate::dom::TElement; use crate::sharing::{StyleSharingCandidate, StyleSharingTarget}; use selectors::NthIndexCache; @@ -120,7 +120,6 @@ pub fn revalidate( shared_context: &SharedStyleContext, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, - selector_flags_map: &mut SelectorFlagsMap, ) -> bool where E: TElement, @@ -128,7 +127,7 @@ where let stylist = &shared_context.stylist; let for_element = - target.revalidation_match_results(stylist, bloom, nth_index_cache, selector_flags_map); + target.revalidation_match_results(stylist, bloom, nth_index_cache); let for_candidate = candidate.revalidation_match_results(stylist, bloom, nth_index_cache); diff --git a/components/style/sharing/mod.rs b/components/style/sharing/mod.rs index 50eb51fba35..91e4f02e084 100644 --- a/components/style/sharing/mod.rs +++ b/components/style/sharing/mod.rs @@ -66,9 +66,8 @@ use crate::applicable_declarations::ApplicableDeclarationBlock; use crate::bloom::StyleBloom; -use crate::context::{SelectorFlagsMap, SharedStyleContext, StyleContext}; +use crate::context::{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}; @@ -76,7 +75,7 @@ use crate::stylist::Stylist; use crate::values::AtomIdent; use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use owning_ref::OwningHandle; -use selectors::matching::{ElementSelectorFlags, VisitedHandlingMode}; +use selectors::matching::{VisitedHandlingMode, NeedsSelectorFlags}; use selectors::NthIndexCache; use servo_arc::Arc; use smallbitvec::SmallBitVec; @@ -223,18 +222,17 @@ 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( + fn revalidation_match_results( &mut self, element: E, stylist: &Stylist, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, bloom_known_valid: bool, - flags_setter: &mut F, + needs_selector_flags: NeedsSelectorFlags, ) -> &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. @@ -257,7 +255,7 @@ impl ValidationData { element, bloom_to_use, nth_index_cache, - flags_setter, + needs_selector_flags, ) }) } @@ -327,7 +325,9 @@ impl StyleSharingCandidate { bloom, nth_index_cache, /* bloom_known_valid = */ false, - &mut |_, _| {}, + // The candidate must already have the right bits already, if + // needed. + NeedsSelectorFlags::No, ) } } @@ -384,7 +384,6 @@ impl StyleSharingTarget { stylist: &Stylist, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, - selector_flags_map: &mut SelectorFlagsMap, ) -> &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 @@ -401,18 +400,13 @@ impl StyleSharingTarget { // 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, - &mut set_selector_flags, + NeedsSelectorFlags::Yes, ) } @@ -423,7 +417,6 @@ impl StyleSharingTarget { ) -> Option { 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; @@ -443,7 +436,6 @@ impl StyleSharingTarget { cache.share_style_if_possible( shared_context, - selector_flags_map, bloom_filter, nth_index_cache, self, @@ -631,13 +623,13 @@ impl StyleSharingCache { // // These are things we don't check in the candidate match because they // are either uncommon or expensive. - let box_style = style.style().get_box(); - if box_style.specifies_transitions() { + let ui_style = style.style().get_ui(); + if ui_style.specifies_transitions() { debug!("Failing to insert to the cache: transitions"); return; } - if box_style.specifies_animations() { + if ui_style.specifies_animations() { debug!("Failing to insert to the cache: animations"); return; } @@ -667,7 +659,6 @@ impl StyleSharingCache { fn share_style_if_possible( &mut self, shared_context: &SharedStyleContext, - selector_flags_map: &mut SelectorFlagsMap, bloom_filter: &StyleBloom, nth_index_cache: &mut NthIndexCache, target: &mut StyleSharingTarget, @@ -700,7 +691,6 @@ impl StyleSharingCache { &shared_context, bloom_filter, nth_index_cache, - selector_flags_map, shared_context, ) }) @@ -712,7 +702,6 @@ impl StyleSharingCache { shared: &SharedStyleContext, bloom: &StyleBloom, nth_index_cache: &mut NthIndexCache, - selector_flags_map: &mut SelectorFlagsMap, shared_context: &SharedStyleContext, ) -> Option { debug_assert!(!target.is_in_native_anonymous_subtree()); @@ -817,7 +806,6 @@ impl StyleSharingCache { shared, bloom, nth_index_cache, - selector_flags_map, ) { trace!("Miss: Revalidation"); return None; diff --git a/components/style/style_resolver.rs b/components/style/style_resolver.rs index 81517f6cd64..7d6135b876f 100644 --- a/components/style/style_resolver.rs +++ b/components/style/style_resolver.rs @@ -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::{ElementSelectorFlags, MatchingContext}; +use selectors::matching::{NeedsSelectorFlags, MatchingContext}; use selectors::matching::{MatchingMode, VisitedHandlingMode}; use servo_arc::Arc; @@ -451,7 +451,6 @@ 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( @@ -460,29 +459,22 @@ 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(); - { - 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, - ); - } + // 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, + ); // FIXME(emilio): This is a hack for animations, and should go away. self.element.unset_dirty_style_attribute(); @@ -540,14 +532,9 @@ 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( @@ -559,7 +546,6 @@ where self.rule_inclusion, &mut applicable_declarations, &mut matching_context, - &mut set_selector_flags, ); if applicable_declarations.is_empty() { diff --git a/components/style/stylesheets/container_rule.rs b/components/style/stylesheets/container_rule.rs new file mode 100644 index 00000000000..fe103d8986d --- /dev/null +++ b/components/style/stylesheets/container_rule.rs @@ -0,0 +1,281 @@ +/* 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, + /// The nested rules inside the block. + pub rules: Arc>, + /// 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> { + 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(&self, potential_container: E) -> Option<(ContainerInfo, Arc)> + 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(&self, mut e: E) -> Option<(ContainerInfo, Arc)> + 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(&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, + 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) -> 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()), + ), +]; diff --git a/components/style/stylesheets/layer_rule.rs b/components/style/stylesheets/layer_rule.rs index ebff5bb9add..c724fd81f1a 100644 --- a/components/style/stylesheets/layer_rule.rs +++ b/components/style/stylesheets/layer_rule.rs @@ -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] urle. +//! A [`@layer`][layer] rule. //! //! [layer]: https://drafts.csswg.org/css-cascade-5/#layering @@ -61,17 +61,6 @@ 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 ``: 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]>); diff --git a/components/style/stylesheets/media_rule.rs b/components/style/stylesheets/media_rule.rs index ea7441a5c09..cde60a16bf7 100644 --- a/components/style/stylesheets/media_rule.rs +++ b/components/style/stylesheets/media_rule.rs @@ -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] urle. +//! An [`@media`][media] rule. //! //! [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] urle. +/// An [`@media`][media] rule. /// /// [media]: https://drafts.csswg.org/css-conditional/#at-ruledef-media #[derive(Debug, ToShmem)] diff --git a/components/style/stylesheets/mod.rs b/components/style/stylesheets/mod.rs index d48b7504797..344fda5ef3c 100644 --- a/components/style/stylesheets/mod.rs +++ b/components/style/stylesheets/mod.rs @@ -12,6 +12,7 @@ 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; @@ -53,6 +54,7 @@ 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}; @@ -253,6 +255,7 @@ pub enum CssRule { Import(Arc>), Style(Arc>), Media(Arc>), + Container(Arc>), FontFace(Arc>), FontFeatureValues(Arc>), CounterStyle(Arc>), @@ -287,6 +290,10 @@ 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, @@ -344,6 +351,7 @@ pub enum CssRuleType { LayerBlock = 16, LayerStatement = 17, ScrollTimeline = 18, + Container = 19, } #[allow(missing_docs)] @@ -373,16 +381,7 @@ impl CssRule { CssRule::LayerBlock(_) => CssRuleType::LayerBlock, CssRule::LayerStatement(_) => CssRuleType::LayerStatement, CssRule::ScrollTimeline(_) => CssRuleType::ScrollTimeline, - } - } - - 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, + CssRule::Container(_) => CssRuleType::Container, } } @@ -460,6 +459,12 @@ 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( @@ -545,6 +550,7 @@ 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), } } } diff --git a/components/style/stylesheets/rule_list.rs b/components/style/stylesheets/rule_list.rs index d84a738bca0..c246d7ae6bb 100644 --- a/components/style/stylesheets/rule_list.rs +++ b/components/style/stylesheets/rule_list.rs @@ -153,21 +153,17 @@ impl CssRulesHelpers for RawOffsetArc> { } // 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 { - rules - .0 - .get(index - 1) - .map(CssRule::rule_state) - .unwrap_or(State::Body) - }; - - let insert_rule_context = InsertRuleContext { - rule_list: &rules.0, - index, + insert_rule_context.max_rule_state_at_index(index - 1) }; // Steps 3, 4, 5, 6 diff --git a/components/style/stylesheets/rule_parser.rs b/components/style/stylesheets/rule_parser.rs index 9c0095bc32d..646a7dbbfd2 100644 --- a/components/style/stylesheets/rule_parser.rs +++ b/components/style/stylesheets/rule_parser.rs @@ -13,6 +13,7 @@ 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; @@ -45,6 +46,36 @@ 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. @@ -102,12 +133,8 @@ impl<'b> TopLevelRuleParser<'b> { None => return true, }; - 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 { + let max_rule_state = ctx.max_rule_state_at_index(ctx.index); + if new_state > max_rule_state { self.dom_error = Some(RulesMutateError::HierarchyRequest); return false; } @@ -162,6 +189,8 @@ pub enum AtRulePrelude { CounterStyle(CustomIdent), /// A @media rule prelude, with its media queries. Media(Arc>), + /// A @container rule prelude. + Container(Arc), /// An @supports rule, with its conditional Supports(SupportsCondition), /// A @viewport rule prelude. @@ -262,11 +291,23 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { self.dom_error = Some(RulesMutateError::HierarchyRequest); return Err(input.new_custom_error(StyleParseErrorKind::UnexpectedCharsetRule)) }, - _ => {} - } - - if !self.check_state(State::Body) { - return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + "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. + } } AtRuleParser::parse_prelude(&mut self.nested(), name, input) @@ -279,6 +320,9 @@ impl<'a, 'i> AtRuleParser<'i> for TopLevelRuleParser<'a> { start: &ParserState, input: &mut Parser<'i, 't>, ) -> Result> { + 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)) @@ -410,6 +454,16 @@ 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; @@ -433,6 +487,10 @@ 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| { @@ -614,6 +672,15 @@ 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(), diff --git a/components/style/stylesheets/rules_iterator.rs b/components/style/stylesheets/rules_iterator.rs index 417185953a0..c86d4dc96c3 100644 --- a/components/style/stylesheets/rules_iterator.rs +++ b/components/style/stylesheets/rules_iterator.rs @@ -88,6 +88,10 @@ 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) { diff --git a/components/style/stylesheets/scroll_timeline_rule.rs b/components/style/stylesheets/scroll_timeline_rule.rs index 12b0d2013a6..f4796ca77aa 100644 --- a/components/style/stylesheets/scroll_timeline_rule.rs +++ b/components/style/stylesheets/scroll_timeline_rule.rs @@ -206,7 +206,6 @@ 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. diff --git a/components/style/stylesheets/stylesheet.rs b/components/style/stylesheets/stylesheet.rs index aaf350b684d..1410634c281 100644 --- a/components/style/stylesheets/stylesheet.rs +++ b/components/style/stylesheets/stylesheet.rs @@ -370,6 +370,7 @@ 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(..) | diff --git a/components/style/stylesheets/viewport_rule.rs b/components/style/stylesheets/viewport_rule.rs index 89f604be01b..10e6af5715b 100644 --- a/components/style/stylesheets/viewport_rule.rs +++ b/components/style/stylesheets/viewport_rule.rs @@ -671,7 +671,8 @@ impl MaybeNew for ViewportConstraints { builder: StyleBuilder::for_inheritance(device, None, None), cached_system_font: None, in_media_query: false, - quirks_mode: quirks_mode, + quirks_mode, + container_info: None, for_smil_animation: false, for_non_inherited_property: None, rule_cache_conditions: RefCell::new(&mut conditions), diff --git a/components/style/stylist.rs b/components/style/stylist.rs index a1ad4aab659..21b975c9e92 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -28,8 +28,9 @@ 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::{LayerId, LayerName, LayerOrder}; +use crate::stylesheets::layer_rule::{LayerName, LayerOrder}; use crate::stylesheets::viewport_rule::{self, MaybeNew, ViewportRule}; #[cfg(feature = "gecko")] use crate::stylesheets::{ @@ -39,7 +40,6 @@ 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, ElementSelectorFlags, MatchingContext, MatchingMode}; +use selectors::matching::{matches_selector, MatchingContext, MatchingMode, NeedsSelectorFlags}; use selectors::parser::{AncestorHashes, Combinator, Component, Selector, SelectorIter}; use selectors::visitor::SelectorVisitor; use selectors::NthIndexCache; @@ -549,6 +549,47 @@ impl From 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 @@ -1073,38 +1114,12 @@ impl Stylist { { debug_assert!(pseudo.is_lazy()); - // 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); - } - } - } + // 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 }; let mut declarations = ApplicableDeclarationList::new(); @@ -1113,6 +1128,7 @@ impl Stylist { None, None, self.quirks_mode, + needs_selector_flags, ); matching_context.pseudo_element_matching_fn = matching_fn; @@ -1126,7 +1142,6 @@ impl Stylist { rule_inclusion, &mut declarations, &mut matching_context, - &mut set_selector_flags, ); if declarations.is_empty() && is_probe { @@ -1144,6 +1159,7 @@ impl Stylist { None, VisitedHandlingMode::RelevantLinkVisited, self.quirks_mode, + needs_selector_flags, ); matching_context.pseudo_element_matching_fn = matching_fn; @@ -1156,7 +1172,6 @@ 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( @@ -1269,7 +1284,7 @@ impl Stylist { } /// Returns the applicable CSS declarations for the given element. - pub fn push_applicable_declarations( + pub fn push_applicable_declarations( &self, element: E, pseudo_element: Option<&PseudoElement>, @@ -1279,10 +1294,8 @@ impl Stylist { rule_inclusion: RuleInclusion, applicable_declarations: &mut ApplicableDeclarationList, context: &mut MatchingContext, - flags_setter: &mut F, ) where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { RuleCollector::new( self, @@ -1294,7 +1307,6 @@ impl Stylist { rule_inclusion, applicable_declarations, context, - flags_setter, ) .collect_all(); } @@ -1374,16 +1386,15 @@ impl Stylist { /// Computes the match results of a given element against the set of /// revalidation selectors. - pub fn match_revalidation_selectors( + pub fn match_revalidation_selectors( &self, element: E, bloom: Option<&BloomFilter>, nth_index_cache: &mut NthIndexCache, - flags_setter: &mut F, + needs_selector_flags: NeedsSelectorFlags, ) -> SmallBitVec where E: TElement, - F: FnMut(&E, ElementSelectorFlags), { // NB: `MatchingMode` doesn't really matter, given we don't share style // between pseudos. @@ -1392,6 +1403,7 @@ 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 @@ -1414,7 +1426,6 @@ impl Stylist { Some(&selector_and_hashes.hashes), &element, matching_context, - flags_setter, )); true }, @@ -1437,7 +1448,6 @@ impl Stylist { Some(&selector_and_hashes.hashes), &element, &mut matching_context, - flags_setter, )); true }, @@ -2061,6 +2071,17 @@ 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, @@ -2078,6 +2099,35 @@ 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>, +} + +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. /// @@ -2149,6 +2199,9 @@ 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, @@ -2191,6 +2244,7 @@ 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, @@ -2305,6 +2359,23 @@ impl CascadeData { self.layers[id.0 as usize].order } + pub(crate) fn container_condition_matches(&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(); @@ -2426,8 +2497,7 @@ impl CascadeData { stylesheet: &S, guard: &SharedRwLockReadGuard, rebuild_kind: SheetRebuildKind, - mut current_layer: &mut LayerName, - current_layer_id: LayerId, + containing_rule_state: &mut ContainingRuleState, mut precomputed_pseudo_element_decls: Option<&mut PrecomputedPseudoElementDeclarations>, ) -> Result<(), AllocErr> where @@ -2450,7 +2520,7 @@ impl CascadeData { if pseudo.is_precomputed() { debug_assert!(selector.is_universal()); debug_assert_eq!(stylesheet.contents().origin, Origin::UserAgent); - debug_assert_eq!(current_layer_id, LayerId::root()); + debug_assert_eq!(containing_rule_state.layer_id, LayerId::root()); precomputed_pseudo_element_decls .as_mut() @@ -2477,7 +2547,8 @@ impl CascadeData { hashes, locked.clone(), self.rules_source_order, - current_layer_id, + containing_rule_state.layer_id, + containing_rule_state.container_condition_id, ); if rebuild_kind.should_rebuild_invalidation() { @@ -2555,7 +2626,7 @@ impl CascadeData { self.animations.try_insert_with( name, animation, - current_layer_id, + containing_rule_state.layer_id, compare_keyframes_in_same_layer, )?; }, @@ -2564,25 +2635,35 @@ 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, current_layer_id)?; + .add_scroll_timeline(guard, rule, containing_rule_state.layer_id)?; }, #[cfg(feature = "gecko")] CssRule::FontFace(ref rule) => { - self.extra_data.add_font_face(rule, current_layer_id); + // 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); }, #[cfg(feature = "gecko")] CssRule::FontFeatureValues(ref rule) => { self.extra_data - .add_font_feature_values(rule, current_layer_id); + .add_font_feature_values(rule, containing_rule_state.layer_id); }, #[cfg(feature = "gecko")] CssRule::CounterStyle(ref rule) => { self.extra_data - .add_counter_style(guard, rule, current_layer_id)?; + .add_counter_style(guard, rule, containing_rule_state.layer_id)?; }, #[cfg(feature = "gecko")] CssRule::Page(ref rule) => { - self.extra_data.add_page(guard, rule, current_layer_id)?; + self.extra_data.add_page(guard, rule, containing_rule_state.layer_id)?; }, CssRule::Viewport(..) => {}, _ => { @@ -2623,7 +2704,7 @@ impl CascadeData { if let Some(id) = data.layer_id.get(layer) { return *id; } - let id = LayerId(data.layers.len() as u32); + let id = LayerId(data.layers.len() as u16); let parent_layer_id = if layer.layer_names().len() > 1 { let mut parent = layer.clone(); @@ -2654,9 +2735,8 @@ impl CascadeData { fn maybe_register_layers( data: &mut CascadeData, name: Option<&LayerName>, - current_layer: &mut LayerName, - pushed_layers: &mut usize, - ) -> LayerId { + containing_rule_state: &mut ContainingRuleState, + ) { let anon_name; let name = match name { Some(name) => name, @@ -2665,19 +2745,14 @@ impl CascadeData { &anon_name }, }; - - let mut id = LayerId::root(); for name in name.layer_names() { - current_layer.0.push(name.clone()); - id = maybe_register_layer(data, ¤t_layer); - *pushed_layers += 1; + containing_rule_state.layer_name.0.push(name.clone()); + containing_rule_state.layer_id = maybe_register_layer(data, &containing_rule_state.layer_name); } - debug_assert_ne!(id, LayerId::root()); - id + debug_assert_ne!(containing_rule_state.layer_id, LayerId::root()); } - let mut layer_names_to_pop = 0; - let mut children_layer_id = current_layer_id; + let saved_containing_rule_state = containing_rule_state.save(); match *rule { CssRule::Import(ref lock) => { let import_rule = lock.read_with(guard); @@ -2686,11 +2761,10 @@ impl CascadeData { .saw_effective(import_rule); } if let Some(ref layer) = import_rule.layer { - children_layer_id = maybe_register_layers( + maybe_register_layers( self, layer.name.as_ref(), - &mut current_layer, - &mut layer_names_to_pop, + containing_rule_state ); } }, @@ -2702,25 +2776,29 @@ impl CascadeData { }, CssRule::LayerBlock(ref lock) => { let layer_rule = lock.read_with(guard); - children_layer_id = maybe_register_layers( + maybe_register_layers( self, layer_rule.name.as_ref(), - &mut current_layer, - &mut layer_names_to_pop, + containing_rule_state, ); }, CssRule::LayerStatement(ref lock) => { let layer_rule = lock.read_with(guard); for name in &*layer_rule.names { - let mut pushed = 0; - // There are no children, so we can ignore the - // return value. - maybe_register_layers(self, Some(name), &mut current_layer, &mut pushed); - for _ in 0..pushed { - current_layer.0.pop(); - } + maybe_register_layers(self, Some(name), containing_rule_state); + // Register each layer individually. + containing_rule_state.restore(&saved_containing_rule_state); } }, + 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. _ => {}, } @@ -2733,15 +2811,12 @@ impl CascadeData { stylesheet, guard, rebuild_kind, - current_layer, - children_layer_id, + containing_rule_state, precomputed_pseudo_element_decls.as_deref_mut(), )?; } - for _ in 0..layer_names_to_pop { - current_layer.0.pop(); - } + containing_rule_state.restore(&saved_containing_rule_state); } Ok(()) @@ -2770,7 +2845,7 @@ impl CascadeData { self.effective_media_query_results.saw_effective(contents); } - let mut current_layer = LayerName::new_empty(); + let mut state = ContainingRuleState::default(); self.add_rule_list( contents.rules(guard).iter(), device, @@ -2778,8 +2853,7 @@ impl CascadeData { stylesheet, guard, rebuild_kind, - &mut current_layer, - LayerId::root(), + &mut state, precomputed_pseudo_element_decls.as_deref_mut(), )?; @@ -2827,6 +2901,7 @@ impl CascadeData { CssRule::Style(..) | CssRule::Namespace(..) | CssRule::FontFace(..) | + CssRule::Container(..) | CssRule::CounterStyle(..) | CssRule::Supports(..) | CssRule::Keyframes(..) | @@ -2904,6 +2979,8 @@ 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(); @@ -2998,6 +3075,9 @@ 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", @@ -3043,6 +3123,7 @@ impl Rule { style_rule: Arc>, source_order: u32, layer_id: LayerId, + container_condition_id: ContainerConditionId, ) -> Self { Rule { selector, @@ -3050,10 +3131,17 @@ 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) -> bool { let mut attribute_dependencies = Default::default(); diff --git a/components/style/traversal.rs b/components/style/traversal.rs index bf0b963c709..fcaeb6e9c57 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -16,6 +16,13 @@ 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, +>; /// 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. @@ -294,6 +301,7 @@ pub fn resolve_style( element: E, rule_inclusion: RuleInclusion, pseudo: Option<&PseudoElement>, + mut undisplayed_style_cache: Option<&mut UndisplayedStyleCache>, ) -> ElementStyles where E: TElement, @@ -304,6 +312,11 @@ 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. @@ -320,6 +333,12 @@ where } } } + if let Some(ref mut cache) = undisplayed_style_cache { + if let Some(s) = cache.get(¤t.opaque()) { + style = Some(s.clone()); + break; + } + } ancestors_requiring_style_resolution.push(current); ancestor = current.traversal_parent(); } @@ -337,7 +356,9 @@ where } ancestor = ancestor.unwrap().traversal_parent(); - layout_parent_style = ancestor.map(|a| a.borrow_data().unwrap().styles.primary().clone()); + layout_parent_style = ancestor.and_then(|a| { + a.borrow_data().map(|data| data.styles.primary().clone()) + }); } for ancestor in ancestors_requiring_style_resolution.iter().rev() { @@ -360,18 +381,27 @@ 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); - StyleResolverForElement::new( + let styles: ElementStyles = StyleResolverForElement::new( element, context, rule_inclusion, PseudoElementResolution::Force, ) .resolve_style(style.as_deref(), layout_parent_style.as_deref()) - .into() + .into(); + + if let Some(ref mut cache) = undisplayed_style_cache { + cache.insert(element.opaque(), styles.primary().clone()); + } + + styles } /// Calculates the style for a single node. diff --git a/components/style/values/animated/color.rs b/components/style/values/animated/color.rs index f99e344a57a..cdfa45dc4e7 100644 --- a/components/style/values/animated/color.rs +++ b/components/style/values/animated/color.rs @@ -5,16 +5,20 @@ //! Animated types for CSS colors. use crate::values::animated::{Animate, Procedure, ToAnimatedZero}; +use crate::values::computed::Percentage; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; -use crate::values::generics::color::{Color as GenericColor, ComplexColorRatios}; -use crate::values::specified::color::{ColorSpaceKind, HueAdjuster}; +use crate::values::generics::color::{ + GenericColor, GenericColorMix, ColorInterpolationMethod, ColorSpace, HueInterpolationMethod, +}; use euclid::default::{Transform3D, Vector3D}; +use std::f32::consts::PI; /// An animated RGBA color. /// /// Unlike in computed values, each component value may exceed the /// range `[0.0, 1.0]`. -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero)] +#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero, ToAnimatedValue)] +#[repr(C)] pub struct RGBA { /// The red component. pub red: f32, @@ -26,6 +30,9 @@ pub struct RGBA { pub alpha: f32, } +const RAD_PER_DEG: f32 = PI / 180.0; +const DEG_PER_RAD: f32 = 180.0 / PI; + impl RGBA { /// Returns a transparent color. #[inline] @@ -43,44 +50,20 @@ impl RGBA { alpha, } } - - /// Returns whether or not the colour is in gamut for sRGB. - pub fn in_gamut(&self) -> bool { - 0. <= self.red && - self.red <= 1. && - 0. <= self.green && - self.green <= 1. && - 0. <= self.blue && - self.blue <= 1. - } - - /// Returns the colour with coordinates clamped to the sRGB range. - pub fn clamp(&self) -> Self { - Self { - red: self.red.max(0.).min(1.), - green: self.green.max(0.).min(1.), - blue: self.blue.max(0.).min(1.), - alpha: self.alpha, - } - } } impl Animate for RGBA { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { - let mut alpha = self.alpha.animate(&other.alpha, procedure)?; - if alpha <= 0. { - // Ideally we should return color value that only alpha component is - // 0, but this is what current gecko does. - return Ok(RGBA::transparent()); - } - - alpha = alpha.min(1.); - let red = (self.red * self.alpha).animate(&(other.red * other.alpha), procedure)?; - let green = (self.green * self.alpha).animate(&(other.green * other.alpha), procedure)?; - let blue = (self.blue * self.alpha).animate(&(other.blue * other.alpha), procedure)?; - let inv = 1. / alpha; - Ok(RGBA::new(red * inv, green * inv, blue * inv, alpha)) + let (left_weight, right_weight) = procedure.weights(); + Ok(Color::mix( + &ColorInterpolationMethod::srgb(), + self, + left_weight as f32, + other, + right_weight as f32, + /* normalize_weights = */ false, + )) } } @@ -108,253 +91,122 @@ impl ComputeSquaredDistance for RGBA { } /// An animated value for ``. -pub type Color = GenericColor; +pub type Color = GenericColor; + +/// An animated value for ``. +pub type ColorMix = GenericColorMix; impl Color { - fn effective_intermediate_rgba(&self) -> RGBA { - if self.ratios.bg == 0. { - return RGBA::transparent(); - } - - if self.ratios.bg == 1. { - return self.color; - } - - RGBA { - alpha: self.color.alpha * self.ratios.bg, - ..self.color - } + fn to_rgba(&self, current_color: RGBA) -> RGBA { + let mut clone = self.clone(); + clone.simplify(Some(¤t_color)); + *clone.as_numeric().unwrap() } /// Mix two colors into one. pub fn mix( - color_space: ColorSpaceKind, - left_color: &Color, - left_weight: f32, - right_color: &Color, - right_weight: f32, - hue_adjuster: HueAdjuster, - ) -> Self { - match color_space { - ColorSpaceKind::Srgb => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), - ColorSpaceKind::Xyz => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), - ColorSpaceKind::Lab => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), - ColorSpaceKind::Lch => Self::mix_in::( - left_color, - left_weight, - right_color, - right_weight, - hue_adjuster, - ), + interpolation: &ColorInterpolationMethod, + left_color: &RGBA, + mut left_weight: f32, + right_color: &RGBA, + mut right_weight: f32, + normalize_weights: bool, + ) -> RGBA { + // https://drafts.csswg.org/css-color-5/#color-mix-percent-norm + let mut alpha_multiplier = 1.0; + if normalize_weights { + let sum = left_weight + right_weight; + if sum != 1.0 { + let scale = 1.0 / sum; + left_weight *= scale; + right_weight *= scale; + if sum < 1.0 { + alpha_multiplier = sum; + } + } } + + let mix_function = match interpolation.space { + ColorSpace::Srgb => Self::mix_in::, + ColorSpace::LinearSrgb => Self::mix_in::, + ColorSpace::Xyz => Self::mix_in::, + ColorSpace::XyzD50 => Self::mix_in::, + ColorSpace::Lab => Self::mix_in::, + ColorSpace::Hwb => Self::mix_in::, + ColorSpace::Hsl => Self::mix_in::, + ColorSpace::Lch => Self::mix_in::, + }; + mix_function( + left_color, + left_weight, + right_color, + right_weight, + interpolation.hue, + alpha_multiplier, + ) } fn mix_in( - left_color: &Color, + left_color: &RGBA, left_weight: f32, - right_color: &Color, + right_color: &RGBA, right_weight: f32, - hue_adjuster: HueAdjuster, - ) -> Self + hue_interpolation: HueInterpolationMethod, + alpha_multiplier: f32, + ) -> RGBA where S: ModelledColor, { - let left_bg = S::from(left_color.scaled_rgba()); - let right_bg = S::from(right_color.scaled_rgba()); + let left = S::from(*left_color); + let right = S::from(*right_color); - let color = S::lerp(left_bg, left_weight, right_bg, right_weight, hue_adjuster); - let rgba: RGBA = color.into(); - let rgba = if !rgba.in_gamut() { - // TODO: Better gamut mapping. - rgba.clamp() - } else { - rgba - }; - - let fg = left_color.ratios.fg * left_weight + right_color.ratios.fg * right_weight; - Self::new(rgba, ComplexColorRatios { bg: 1., fg }) - } - - fn scaled_rgba(&self) -> RGBA { - if self.ratios.bg == 0. { - return RGBA::transparent(); + let color = S::lerp( + &left, + left_weight, + &right, + right_weight, + hue_interpolation, + ); + let mut rgba = RGBA::from(color.into()); + if alpha_multiplier != 1.0 { + rgba.alpha *= alpha_multiplier; } - if self.ratios.bg == 1. { - return self.color; - } - - RGBA { - red: self.color.red * self.ratios.bg, - green: self.color.green * self.ratios.bg, - blue: self.color.blue * self.ratios.bg, - alpha: self.color.alpha * self.ratios.bg, - } + rgba } } impl Animate for Color { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { - let self_numeric = self.is_numeric(); - let other_numeric = other.is_numeric(); - - if self_numeric && other_numeric { - return Ok(Self::rgba(self.color.animate(&other.color, procedure)?)); - } - - let self_currentcolor = self.is_currentcolor(); - let other_currentcolor = other.is_currentcolor(); - - if self_currentcolor && other_currentcolor { - let (self_weight, other_weight) = procedure.weights(); - return Ok(Self::new( - RGBA::transparent(), - ComplexColorRatios { - bg: 0., - fg: (self_weight + other_weight) as f32, - }, - )); - } - - // FIXME(emilio): Without these special cases tests fail, looks fairly - // sketchy! - if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) { - let (self_weight, other_weight) = procedure.weights(); - return Ok(if self_numeric { - Self::new( - self.color, - ComplexColorRatios { - bg: self_weight as f32, - fg: other_weight as f32, - }, - ) - } else { - Self::new( - other.color, - ComplexColorRatios { - bg: other_weight as f32, - fg: self_weight as f32, - }, - ) - }); - } - - // Compute the "scaled" contribution for `color`. - // Each `Color`, represents a complex combination of foreground color and - // background color where fg and bg represent the overall - // contributions. ie: - // - // color = { bg * mColor, fg * foreground } - // = { bg_color , fg_color } - // = bg_color + fg_color - // - // where `foreground` is `currentcolor`, and `bg_color`, - // `fg_color` are the scaled background and foreground - // contributions. - // - // Each operation, lerp, addition, or accumulate, can be - // represented as a scaled-addition each complex color. ie: - // - // p * col1 + q * col2 - // - // where p = (1 - a), q = a for lerp(a), p = 1, q = 1 for - // addition, etc. - // - // Therefore: - // - // col1 op col2 - // = p * col1 + q * col2 - // = p * { bg_color1, fg_color1 } + q * { bg_color2, fg_color2 } - // = p * (bg_color1 + fg_color1) + q * (bg_color2 + fg_color2) - // = p * bg_color1 + p * fg_color1 + q * bg_color2 + p * fg_color2 - // = (p * bg_color1 + q * bg_color2) + (p * fg_color1 + q * fg_color2) - // = (bg_color1 op bg_color2) + (fg_color1 op fg_color2) - // - // fg_color1 op fg_color2 is equivalent to (fg1 op fg2) * foreground, - // so the final color is: - // - // = { bg_color, fg_color } - // = { 1 * (bg_color1 op bg_color2), (fg1 op fg2) * foreground } - // - // To perform the operation on two complex colors, we need to - // generate the scaled contributions of each background color - // component. - let bg_color1 = self.scaled_rgba(); - let bg_color2 = other.scaled_rgba(); - - // Perform bg_color1 op bg_color2 - let bg_color = bg_color1.animate(&bg_color2, procedure)?; - - // Calculate the final foreground color ratios; perform - // animation on effective fg ratios. - let fg = self.ratios.fg.animate(&other.ratios.fg, procedure)?; - - Ok(Self::new(bg_color, ComplexColorRatios { bg: 1., fg })) + let (left_weight, right_weight) = procedure.weights(); + let mut color = Color::ColorMix(Box::new(ColorMix { + interpolation: ColorInterpolationMethod::srgb(), + left: self.clone(), + left_percentage: Percentage(left_weight as f32), + right: other.clone(), + right_percentage: Percentage(right_weight as f32), + // See https://github.com/w3c/csswg-drafts/issues/7324 + normalize_weights: false, + })); + color.simplify(None); + Ok(color) } } impl ComputeSquaredDistance for Color { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { - // All comments from the Animate impl also apply here. - let self_numeric = self.is_numeric(); - let other_numeric = other.is_numeric(); - - if self_numeric && other_numeric { - return self.color.compute_squared_distance(&other.color); - } - - let self_currentcolor = self.is_currentcolor(); - let other_currentcolor = other.is_currentcolor(); - if self_currentcolor && other_currentcolor { - return Ok(SquaredDistance::from_sqrt(0.)); - } - - if (self_currentcolor && other_numeric) || (self_numeric && other_currentcolor) { - let color = if self_numeric { - &self.color - } else { - &other.color - }; - // `computed_squared_distance` is symmetric. - return Ok(color.compute_squared_distance(&RGBA::transparent())? + - SquaredDistance::from_sqrt(1.)); - } - - let self_color = self.effective_intermediate_rgba(); - let other_color = other.effective_intermediate_rgba(); - let self_ratios = self.ratios; - let other_ratios = other.ratios; - - Ok(self_color.compute_squared_distance(&other_color)? + - self_ratios.bg.compute_squared_distance(&other_ratios.bg)? + - self_ratios.fg.compute_squared_distance(&other_ratios.fg)?) + let current_color = RGBA::transparent(); + self.to_rgba(current_color) + .compute_squared_distance(&other.to_rgba(current_color)) } } impl ToAnimatedZero for Color { #[inline] fn to_animated_zero(&self) -> Result { - Ok(RGBA::transparent().into()) + Ok(Color::rgba(RGBA::transparent())) } } @@ -365,327 +217,347 @@ impl ToAnimatedZero for Color { trait ModelledColor: Clone + Copy + From + Into { /// Linearly interpolate between the left and right colors. /// - /// The HueAdjuster parameter is only for color spaces where the hue is + /// The HueInterpolationMethod parameter is only for color spaces where the hue is /// represented as an angle (e.g., CIE LCH). fn lerp( - left_bg: Self, + left_bg: &Self, left_weight: f32, - right_bg: Self, + right_bg: &Self, right_weight: f32, - hue_adjuster: HueAdjuster, + hue_interpolation: HueInterpolationMethod, ) -> Self; } -impl ModelledColor for RGBA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - _: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let mut red = 0.; - let mut green = 0.; - let mut blue = 0.; +fn interpolate_premultiplied_component( + left: f32, + left_weight: f32, + left_alpha: f32, + right: f32, + right_weight: f32, + right_alpha: f32, + inverse_of_result_alpha: f32, +) -> f32 { + (left * left_weight * left_alpha + right * right_weight * right_alpha) * inverse_of_result_alpha +} - // sRGB is a rectangular othogonal color space, so all component values - // are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - red += bg.red * bg.alpha * weight; - green += bg.green * bg.alpha * weight; - blue += bg.blue * bg.alpha * weight; - } +// Normalize hue into [0, 360) +fn normalize_hue(v: f32) -> f32 { + v - 360. * (v / 360.).floor() +} - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - RGBA::transparent() +fn adjust_hue(left: &mut f32, right: &mut f32, hue_interpolation: HueInterpolationMethod) { + // Adjust the hue angle as per + // https://drafts.csswg.org/css-color/#hue-interpolation. + // + // If both hue angles are NAN, they should be set to 0. Otherwise, if a + // single hue angle is NAN, it should use the other hue angle. + if left.is_nan() { + if right.is_nan() { + *left = 0.; + *right = 0.; } else { - let inv = 1. / alpha; - RGBA::new(red * inv, green * inv, blue * inv, alpha) + *left = *right; } + } else if right.is_nan() { + *right = *left; + } + + if hue_interpolation == HueInterpolationMethod::Specified { + // Angles are not adjusted. They are interpolated like any other + // component. + return; + } + + *left = normalize_hue(*left); + *right = normalize_hue(*right); + + match hue_interpolation { + // https://drafts.csswg.org/css-color/#shorter + HueInterpolationMethod::Shorter => { + let delta = *right - *left; + + if delta > 180. { + *left += 360.; + } else if delta < -180. { + *right += 360.; + } + }, + // https://drafts.csswg.org/css-color/#longer + HueInterpolationMethod::Longer => { + let delta = *right - *left; + if 0. < delta && delta < 180. { + *left += 360.; + } else if -180. < delta && delta < 0. { + *right += 360.; + } + }, + // https://drafts.csswg.org/css-color/#increasing + HueInterpolationMethod::Increasing => { + if *right < *left { + *right += 360.; + } + }, + // https://drafts.csswg.org/css-color/#decreasing + HueInterpolationMethod::Decreasing => { + if *left < *right { + *left += 360.; + } + }, + HueInterpolationMethod::Specified => unreachable!("Handled above"), } } -/// An animated XYZA colour. -#[derive(Clone, Copy, Debug)] -pub struct XYZA { - /// The x component. - pub x: f32, - /// The y component. - pub y: f32, - /// The z component. - pub z: f32, - /// The alpha component. - pub alpha: f32, +fn interpolate_hue( + mut left: f32, + left_weight: f32, + mut right: f32, + right_weight: f32, + hue_interpolation: HueInterpolationMethod, +) -> f32 { + adjust_hue(&mut left, &mut right, hue_interpolation); + left * left_weight + right * right_weight } -impl XYZA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self { - x: 0., - y: 0., - z: 0., - alpha: 0., - } +fn interpolate_premultiplied( + left: &[f32; 4], + left_weight: f32, + right: &[f32; 4], + right_weight: f32, + hue_index: Option, + hue_interpolation: HueInterpolationMethod, +) -> [f32; 4] { + let left_alpha = left[3]; + let right_alpha = right[3]; + let result_alpha = (left_alpha * left_weight + right_alpha * right_weight).min(1.); + let mut result = [0.; 4]; + if result_alpha <= 0. { + return result; } + + let inverse_of_result_alpha = 1. / result_alpha; + for i in 0..3 { + let is_hue = hue_index == Some(i); + result[i] = if is_hue { + interpolate_hue( + left[i], + left_weight, + right[i], + right_weight, + hue_interpolation, + ) + } else { + interpolate_premultiplied_component( + left[i], + left_weight, + left_alpha, + right[i], + right_weight, + right_alpha, + inverse_of_result_alpha, + ) + }; + } + result[3] = result_alpha; + + result } -impl ModelledColor for XYZA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - _: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let mut x = 0.; - let mut y = 0.; - let mut z = 0.; - - // CIE XYZ is a rectangular othogonal color space, so all component - // values are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - x += bg.x * bg.alpha * weight; - y += bg.y * bg.alpha * weight; - z += bg.z * bg.alpha * weight; - } - - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - Self::transparent() - } else { - let inv = 1. / alpha; - Self { - x: x * inv, - y: y * inv, - z: z * inv, - alpha, +macro_rules! impl_lerp { + ($ty:ident, $hue_index:expr) => { + // These ensure the transmutes below are sound. + const_assert_eq!(std::mem::size_of::<$ty>(), std::mem::size_of::() * 4); + const_assert_eq!(std::mem::align_of::<$ty>(), std::mem::align_of::()); + impl ModelledColor for $ty { + fn lerp( + left: &Self, + left_weight: f32, + right: &Self, + right_weight: f32, + hue_interpolation: HueInterpolationMethod, + ) -> Self { + use std::mem::transmute; + unsafe { + transmute::<[f32; 4], Self>(interpolate_premultiplied( + transmute::<&Self, &[f32; 4]>(left), + left_weight, + transmute::<&Self, &[f32; 4]>(right), + right_weight, + $hue_index, + hue_interpolation, + )) + } } } - } + }; } -/// An animated LABA colour. +impl_lerp!(RGBA, None); + #[derive(Clone, Copy, Debug)] -pub struct LABA { - /// The lightness component. - pub lightness: f32, - /// The a component. - pub a: f32, - /// The b component. - pub b: f32, - /// The alpha component. - pub alpha: f32, +#[repr(C)] +struct LinearRGBA { + red: f32, + green: f32, + blue: f32, + alpha: f32, } -impl LABA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self { - lightness: 0., - a: 0., - b: 0., - alpha: 0., - } - } +impl_lerp!(LinearRGBA, None); + +/// An animated XYZ D65 colour. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct XYZD65A { + x: f32, + y: f32, + z: f32, + alpha: f32, } -impl ModelledColor for LABA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - _: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let mut lightness = 0.; - let mut a = 0.; - let mut b = 0.; +impl_lerp!(XYZD65A, None); - // CIE LAB is a rectangular othogonal color space, so all component - // values are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - lightness += bg.lightness * bg.alpha * weight; - a += bg.a * bg.alpha * weight; - b += bg.b * bg.alpha * weight; - } - - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - Self::transparent() - } else { - let inv = 1. / alpha; - Self { - lightness: lightness * inv, - a: a * inv, - b: b * inv, - alpha, - } - } - } +/// An animated XYZ D50 colour. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct XYZD50A { + x: f32, + y: f32, + z: f32, + alpha: f32, } +impl_lerp!(XYZD50A, None); + +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct LABA { + lightness: f32, + a: f32, + b: f32, + alpha: f32, +} + +impl_lerp!(LABA, None); + /// An animated LCHA colour. #[derive(Clone, Copy, Debug)] -pub struct LCHA { - /// The lightness component. - pub lightness: f32, - /// The chroma component. - pub chroma: f32, - /// The hua component. - pub hue: f32, - /// The alpha component. - pub alpha: f32, +#[repr(C)] +struct LCHA { + lightness: f32, + chroma: f32, + hue: f32, + alpha: f32, } -impl LCHA { - /// Returns a transparent color. - #[inline] - pub fn transparent() -> Self { - Self { - lightness: 0., - chroma: 0., - hue: 0., - alpha: 0., - } - } +impl_lerp!(LCHA, Some(2)); + +/// An animated hwb() color. +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct HWBA { + hue: f32, + white: f32, + black: f32, + alpha: f32, } -impl LCHA { - fn adjust(left_bg: Self, right_bg: Self, hue_adjuster: HueAdjuster) -> (Self, Self) { - use std::f32::consts::{PI, TAU}; +impl_lerp!(HWBA, Some(0)); - let mut left_bg = left_bg; - let mut right_bg = right_bg; - - // Adjust the hue angle as per - // https://drafts.csswg.org/css-color/#hue-interpolation. - // - // If both hue angles are NAN, they should be set to 0. Otherwise, if a - // single hue angle is NAN, it should use the other hue angle. - if left_bg.hue.is_nan() || right_bg.hue.is_nan() { - if left_bg.hue.is_nan() && right_bg.hue.is_nan() { - left_bg.hue = 0.; - right_bg.hue = 0.; - } else if left_bg.hue.is_nan() { - left_bg.hue = right_bg.hue; - } else if right_bg.hue.is_nan() { - right_bg.hue = left_bg.hue; - } - } - - if hue_adjuster != HueAdjuster::Specified { - // Normalize hue into [0, 2 * PI) - while left_bg.hue < 0. { - left_bg.hue += TAU; - } - while left_bg.hue > TAU { - left_bg.hue -= TAU; - } - - while right_bg.hue < 0. { - right_bg.hue += TAU; - } - while right_bg.hue >= TAU { - right_bg.hue -= TAU; - } - } - - match hue_adjuster { - HueAdjuster::Shorter => { - let delta = right_bg.hue - left_bg.hue; - - if delta > PI { - left_bg.hue += PI; - } else if delta < -1. * PI { - right_bg.hue += PI; - } - }, - - HueAdjuster::Longer => { - let delta = right_bg.hue - left_bg.hue; - if 0. < delta && delta < PI { - left_bg.hue += TAU; - } else if -1. * PI < delta && delta < 0. { - right_bg.hue += TAU; - } - }, - - HueAdjuster::Increasing => { - if right_bg.hue < left_bg.hue { - right_bg.hue += TAU; - } - }, - - HueAdjuster::Decreasing => { - if left_bg.hue < right_bg.hue { - left_bg.hue += TAU; - } - }, - - //Angles are not adjusted. They are interpolated like any other - //component. - HueAdjuster::Specified => {}, - } - - (left_bg, right_bg) - } +#[derive(Clone, Copy, Debug)] +#[repr(C)] +struct HSLA { + hue: f32, + sat: f32, + light: f32, + alpha: f32, } -impl ModelledColor for LCHA { - fn lerp( - left_bg: Self, - left_weight: f32, - right_bg: Self, - right_weight: f32, - hue_adjuster: HueAdjuster, - ) -> Self { - // Interpolation with alpha, as per - // https://drafts.csswg.org/css-color/#interpolation-alpha. - let (left_bg, right_bg) = Self::adjust(left_bg, right_bg, hue_adjuster); +impl_lerp!(HSLA, Some(0)); - let mut lightness = 0.; - let mut chroma = 0.; - let mut hue = 0.; +// https://drafts.csswg.org/css-color/#rgb-to-hsl +// +// We also return min/max for the hwb conversion. +fn rgb_to_hsl(rgba: RGBA) -> (HSLA, f32, f32) { + let RGBA { + red, + green, + blue, + alpha, + } = rgba; + let max = red.max(green).max(blue); + let min = red.min(green).min(blue); + let mut hue = std::f32::NAN; + let mut sat = 0.; + let light = (min + max) / 2.; + let d = max - min; - // CIE LCH is a cylindical polar color space, so all component values - // are multiplied by the alpha value. - for &(bg, weight) in &[(left_bg, left_weight), (right_bg, right_weight)] { - lightness += bg.lightness * bg.alpha * weight; - chroma += bg.chroma * bg.alpha * weight; - // LCHA is a cylindrical color space so the hue coordinate is not - // pre-multipled by the alpha component when interpolating. - hue += bg.hue * weight; - } - - let alpha = (left_bg.alpha * left_weight + right_bg.alpha * right_weight).min(1.); - if alpha <= 0. { - Self::transparent() + if d != 0. { + sat = if light == 0.0 || light == 1.0 { + 0. } else { - let inv = 1. / alpha; - Self { - lightness: lightness * inv, - chroma: chroma * inv, - hue, - alpha, - } + (max - light) / light.min(1. - light) + }; + + if max == red { + hue = (green - blue) / d + if green < blue { 6. } else { 0. } + } else if max == green { + hue = (blue - red) / d + 2.; + } else { + hue = (red - green) / d + 4.; + } + + hue *= 60.; + } + + ( + HSLA { + hue, + sat, + light, + alpha, + }, + min, + max, + ) +} + +impl From for HSLA { + fn from(rgba: RGBA) -> Self { + rgb_to_hsl(rgba).0 + } +} + +impl From for RGBA { + fn from(hsla: HSLA) -> Self { + // cssparser expects hue in the 0..1 range. + let hue_normalized = normalize_hue(hsla.hue) / 360.; + let (r, g, b) = cssparser::hsl_to_rgb(hue_normalized, hsla.sat, hsla.light); + RGBA::new(r, g, b, hsla.alpha) + } +} + +impl From for HWBA { + // https://drafts.csswg.org/css-color/#rgb-to-hwb + fn from(rgba: RGBA) -> Self { + let (hsl, min, max) = rgb_to_hsl(rgba); + Self { + hue: hsl.hue, + white: min, + black: 1. - max, + alpha: rgba.alpha, } } } -impl From for XYZA { - /// Convert an RGBA colour to XYZ as specified in [1]. - /// - /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab +impl From for RGBA { + fn from(hwba: HWBA) -> Self { + let hue_normalized = normalize_hue(hwba.hue) / 360.; + let (r, g, b) = cssparser::hwb_to_rgb(hue_normalized, hwba.white, hwba.black); + RGBA::new(r, g, b, hwba.alpha) + } +} + +impl From for LinearRGBA { fn from(rgba: RGBA) -> Self { fn linearize(value: f32) -> f32 { let sign = if value < 0. { -1. } else { 1. }; @@ -696,15 +568,39 @@ impl From for XYZA { sign * ((abs + 0.055) / 1.055).powf(2.4) } + Self { + red: linearize(rgba.red), + green: linearize(rgba.green), + blue: linearize(rgba.blue), + alpha: rgba.alpha, + } + } +} - #[cfg_attr(rustfmt, rustfmt_skip)] - const SRGB_TO_XYZ: Transform3D = Transform3D::new( - 0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0., - 0.357584339383878, 0.715168678767756, 0.11919477979462598, 0., - 0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0., - 0., 0., 0., 1., - ); +impl From for RGBA { + fn from(lrgba: LinearRGBA) -> Self { + fn delinearize(value: f32) -> f32 { + let sign = if value < 0. { -1. } else { 1. }; + let abs = value.abs(); + if abs > 0.0031308 { + sign * (1.055 * abs.powf(1. / 2.4) - 0.055) + } else { + 12.92 * value + } + } + Self { + red: delinearize(lrgba.red), + green: delinearize(lrgba.green), + blue: delinearize(lrgba.blue), + alpha: lrgba.alpha, + } + } +} + +impl From for XYZD50A { + fn from(d65: XYZD65A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code #[cfg_attr(rustfmt, rustfmt_skip)] const BRADFORD: Transform3D = Transform3D::new( 1.0479298208405488, 0.029627815688159344, -0.009243058152591178, 0., @@ -712,34 +608,100 @@ impl From for XYZA { -0.05019222954313557, -0.01707382502938514, 0.7518742899580008, 0., 0., 0., 0., 1., ); - - // 1. Convert from sRGB to linear-light sRGB (undo gamma encoding). - let rgb = Vector3D::new( - linearize(rgba.red), - linearize(rgba.green), - linearize(rgba.blue), - ); - - // 2. Convert from linear sRGB to CIE XYZ. - // 3. Convert from a D65 whitepoint (used by sRGB) to the D50 whitepoint used in XYZ - // with the Bradford transform. - let xyz = SRGB_TO_XYZ.then(&BRADFORD).transform_vector3d(rgb); - - XYZA { - x: xyz.x, - y: xyz.y, - z: xyz.z, - alpha: rgba.alpha, + let d50 = BRADFORD.transform_vector3d(Vector3D::new(d65.x, d65.y, d65.z)); + Self { + x: d50.x, + y: d50.y, + z: d50.z, + alpha: d65.alpha, } } } -impl From for LABA { +impl From for XYZD65A { + fn from(d50: XYZD50A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const BRADFORD_INVERSE: Transform3D = Transform3D::new( + 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0., + -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0., + 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0., + 0., 0., 0., 1., + ); + let d65 = BRADFORD_INVERSE.transform_vector3d(Vector3D::new(d50.x, d50.y, d50.z)); + Self { + x: d65.x, + y: d65.y, + z: d65.z, + alpha: d50.alpha, + } + } +} + +impl From for XYZD65A { + fn from(lrgba: LinearRGBA) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const LSRGB_TO_XYZ: Transform3D = Transform3D::new( + 0.41239079926595934, 0.21263900587151027, 0.01933081871559182, 0., + 0.357584339383878, 0.715168678767756, 0.11919477979462598, 0., + 0.1804807884018343, 0.07219231536073371, 0.9505321522496607, 0., + 0., 0., 0., 1., + ); + let linear_rgb = Vector3D::new(lrgba.red, lrgba.green, lrgba.blue); + let xyz = LSRGB_TO_XYZ.transform_vector3d(linear_rgb); + Self { + x: xyz.x, + y: xyz.y, + z: xyz.z, + alpha: lrgba.alpha, + } + } +} + +impl From for LinearRGBA { + fn from(d65: XYZD65A) -> Self { + // https://drafts.csswg.org/css-color-4/#color-conversion-code + #[cfg_attr(rustfmt, rustfmt_skip)] + const XYZ_TO_LSRGB: Transform3D = Transform3D::new( + 3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0., + -1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0., + -0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0., + 0., 0., 0., 1., + ); + + let xyz = Vector3D::new(d65.x, d65.y, d65.z); + let rgb = XYZ_TO_LSRGB.transform_vector3d(xyz); + Self { + red: rgb.x, + green: rgb.y, + blue: rgb.z, + alpha: d65.alpha, + } + } +} + +impl From for RGBA { + fn from(d65: XYZD65A) -> Self { + Self::from(LinearRGBA::from(d65)) + } +} + +impl From for XYZD65A { + /// Convert an RGBA colour to XYZ as specified in [1]. + /// + /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab + fn from(rgba: RGBA) -> Self { + Self::from(LinearRGBA::from(rgba)) + } +} + +impl From for LABA { /// Convert an XYZ colour to LAB as specified in [1] and [2]. /// /// [1]: https://drafts.csswg.org/css-color/#rgb-to-lab /// [2]: https://drafts.csswg.org/css-color/#color-conversion-code - fn from(xyza: XYZA) -> Self { + fn from(xyza: XYZD50A) -> Self { const WHITE: [f32; 3] = [0.96422, 1., 0.82521]; fn compute_f(value: f32) -> f32 { @@ -778,7 +740,7 @@ impl From for LCHA { /// /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code fn from(laba: LABA) -> Self { - let hue = laba.b.atan2(laba.a); + let hue = laba.b.atan2(laba.a) * DEG_PER_RAD; let chroma = (laba.a * laba.a + laba.b * laba.b).sqrt(); LCHA { lightness: laba.lightness, @@ -794,8 +756,9 @@ impl From for LABA { /// /// [1]: https://drafts.csswg.org/css-color/#color-conversion-code fn from(lcha: LCHA) -> Self { - let a = lcha.chroma * lcha.hue.cos(); - let b = lcha.chroma * lcha.hue.sin(); + let hue_radians = lcha.hue * RAD_PER_DEG; + let a = lcha.chroma * hue_radians.cos(); + let b = lcha.chroma * hue_radians.sin(); LABA { lightness: lcha.lightness, a, @@ -805,7 +768,7 @@ impl From for LABA { } } -impl From for XYZA { +impl From for XYZD50A { /// Convert a CIELAB color to XYZ as specified in [1] and [2]. /// /// [1]: https://drafts.csswg.org/css-color/#lab-to-predefined @@ -836,7 +799,7 @@ impl From for XYZA { (116. * f2 - 16.) / KAPPA }; - XYZA { + Self { x: x * WHITE[0], y: y * WHITE[1], z: z * WHITE[2], @@ -845,85 +808,38 @@ impl From for XYZA { } } -impl From for RGBA { - /// Convert an XYZ color to sRGB as specified in [1] and [2]. - /// - /// [1]: https://www.w3.org/TR/css-color-4/#lab-to-predefined - /// [2]: https://www.w3.org/TR/css-color-4/#color-conversion-code - fn from(xyza: XYZA) -> Self { - #[cfg_attr(rustfmt, rustfmt_skip)] - const BRADFORD_INVERSE: Transform3D = Transform3D::new( - 0.9554734527042182, -0.028369706963208136, 0.012314001688319899, 0., - -0.023098536874261423, 1.0099954580058226, -0.020507696433477912, 0., - 0.0632593086610217, 0.021041398966943008, 1.3303659366080753, 0., - 0., 0., 0., 1., - ); +impl From for RGBA { + fn from(d50: XYZD50A) -> Self { + Self::from(XYZD65A::from(d50)) + } +} - #[cfg_attr(rustfmt, rustfmt_skip)] - const XYZ_TO_SRGB: Transform3D = Transform3D::new( - 3.2409699419045226, -0.9692436362808796, 0.05563007969699366, 0., - -1.537383177570094, 1.8759675015077202, -0.20397695888897652, 0., - -0.4986107602930034, 0.04155505740717559, 1.0569715142428786, 0., - 0., 0., 0., 1., - ); - - // 2. Convert from a D50 whitepoint (used by Lab) to the D65 whitepoint - // used in sRGB, with the Bradford transform. - // 3. Convert from (D65-adapted) CIE XYZ to linear-light srgb - let xyz = Vector3D::new(xyza.x, xyza.y, xyza.z); - let linear_rgb = BRADFORD_INVERSE.then(&XYZ_TO_SRGB).transform_vector3d(xyz); - - // 4. Convert from linear-light srgb to srgb (do gamma encoding). - fn delinearize(value: f32) -> f32 { - let sign = if value < 0. { -1. } else { 1. }; - let abs = value.abs(); - - if abs > 0.0031308 { - sign * (1.055 * abs.powf(1. / 2.4) - 0.055) - } else { - 12.92 * value - } - } - - let red = delinearize(linear_rgb.x); - let green = delinearize(linear_rgb.y); - let blue = delinearize(linear_rgb.z); - - RGBA { - red, - green, - blue, - alpha: xyza.alpha, - } +impl From for XYZD50A { + fn from(rgba: RGBA) -> Self { + Self::from(XYZD65A::from(rgba)) } } impl From for LABA { fn from(rgba: RGBA) -> Self { - let xyza: XYZA = rgba.into(); - xyza.into() + Self::from(XYZD50A::from(rgba)) } } impl From for RGBA { fn from(laba: LABA) -> Self { - let xyza: XYZA = laba.into(); - xyza.into() + Self::from(XYZD50A::from(laba)) } } impl From for LCHA { fn from(rgba: RGBA) -> Self { - let xyza: XYZA = rgba.into(); - let laba: LABA = xyza.into(); - laba.into() + Self::from(LABA::from(rgba)) } } impl From for RGBA { fn from(lcha: LCHA) -> Self { - let laba: LABA = lcha.into(); - let xyza: XYZA = laba.into(); - xyza.into() + Self::from(LABA::from(lcha)) } } diff --git a/components/style/values/computed/box.rs b/components/style/values/computed/box.rs index dd1e4900672..f05870d6ead 100644 --- a/components/style/values/computed/box.rs +++ b/components/style/values/computed/box.rs @@ -13,9 +13,11 @@ use crate::values::specified::box_ as specified; pub use crate::values::specified::box_::{ AnimationName, AnimationTimeline, Appearance, BreakBetween, BreakWithin, - Clear as SpecifiedClear, Contain, ContentVisibility, Display, Float as SpecifiedFloat, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, - ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, TransitionProperty, WillChange, + Clear as SpecifiedClear, Contain, ContainerName, ContainerType, ContentVisibility, Display, + Float as SpecifiedFloat, Overflow, OverflowAnchor, OverflowClipBox, + OverscrollBehavior, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, + ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, + TransitionProperty, WillChange, }; /// A computed value for the `vertical-align` property. diff --git a/components/style/values/computed/color.rs b/components/style/values/computed/color.rs index 7610bfbba3b..573cb6fe500 100644 --- a/components/style/values/computed/color.rs +++ b/components/style/values/computed/color.rs @@ -7,6 +7,7 @@ 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}; @@ -20,7 +21,20 @@ pub type ColorPropertyValue = RGBA; pub type MozFontSmoothingBackgroundColor = RGBA; /// A computed value for ``. -pub type Color = GenericColor; +pub type Color = GenericColor; + +impl ToCss for Color { + fn to_css(&self, dest: &mut CssWriter) -> 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), + } + } +} impl Color { /// Returns a complex color value representing transparent. @@ -28,67 +42,21 @@ impl Color { Color::rgba(RGBA::transparent()) } - /// Combine this complex color with the given foreground color into - /// 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) + /// Returns opaque black. + pub fn black() -> Color { + Color::rgba(RGBA::new(0, 0, 0, 255)) } -} -impl ToCss for Color { - fn to_css(&self, dest: &mut CssWriter) -> 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(()) + /// 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(¤t_color)); + *self.as_numeric().unwrap() } } diff --git a/components/style/values/computed/image.rs b/components/style/values/computed/image.rs index 980017b2822..5f650631c89 100644 --- a/components/style/values/computed/image.rs +++ b/components/style/values/computed/image.rs @@ -31,6 +31,9 @@ pub use specified::ImageRendering; pub type Image = generic::GenericImage; +// Images should remain small, see https://github.com/servo/servo/pull/18430 +size_of_test!(Image, 40); + /// Computed values for a CSS gradient. /// pub type Gradient = generic::GenericGradient< @@ -47,8 +50,6 @@ pub type Gradient = generic::GenericGradient< /// Computed values for CSS cross-fade /// pub type CrossFade = generic::CrossFade; -/// A computed percentage or nothing. -pub type PercentOrNone = generic::PercentOrNone; /// A computed radial gradient ending shape. pub type EndingShape = generic::GenericEndingShape; diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index b8ff80587e6..896001cbcd8 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -40,7 +40,7 @@ impl ToComputedValue for specified::NoCalcLength { context .builder .add_flags(ComputedValueFlags::USES_VIEWPORT_UNITS); - length.to_computed_value(context.viewport_size_for_viewport_unit_resolution()) + length.to_computed_value(context) }, specified::NoCalcLength::ServoCharacterWidth(length) => { length.to_computed_value(context.style().get_font().clone_font_size().size()) diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 68194eb733a..68256cdd20f 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -16,6 +16,7 @@ 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")] @@ -44,11 +45,12 @@ 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}; +pub use self::box_::{AnimationIterationCount, AnimationName, AnimationTimeline, Contain, ContainerName, ContainerType}; 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, ScrollSnapStrictness, ScrollSnapType}; +pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop}; +pub use self::box_::{ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; @@ -72,7 +74,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::{Orientation, PageName, PageSize, PaperSize}; +pub use self::page::{PageOrientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ @@ -97,6 +99,7 @@ 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; @@ -169,6 +172,9 @@ 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, + /// 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 @@ -197,6 +203,40 @@ 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( + device: &Device, + container_info_and_style: Option<(ContainerInfo, Arc)>, + 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), }; @@ -253,10 +293,13 @@ impl<'a> Context<'a> { } /// The current viewport size, used to resolve viewport units. - pub fn viewport_size_for_viewport_unit_resolution(&self) -> default::Size2D { + pub fn viewport_size_for_viewport_unit_resolution( + &self, + variant: ViewportVariant, + ) -> default::Size2D { self.builder .device - .au_viewport_size_for_viewport_unit_resolution() + .au_viewport_size_for_viewport_unit_resolution(variant) } /// The default computed style we're getting our reset style from. @@ -300,8 +343,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: cx, - values: values, + cx, + values, } } } diff --git a/components/style/values/computed/page.rs b/components/style/values/computed/page.rs index 080681e008f..5daf6bbcde8 100644 --- a/components/style/values/computed/page.rs +++ b/components/style/values/computed/page.rs @@ -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::Orientation; +pub use generics::page::PageOrientation; 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), /// `landscape` or `portrait` value, no specified size. - Orientation(Orientation), + Orientation(PageOrientation), /// `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, Orientation::Landscape) => PageSize::Size(Size2D { + Self::PaperSize(p, PageOrientation::Landscape) => PageSize::Size(Size2D { width: p.long_edge().to_computed_value(ctx), height: p.short_edge().to_computed_value(ctx), }), - Self::PaperSize(p, Orientation::Portrait) => PageSize::Size(Size2D { + Self::PaperSize(p, PageOrientation::Portrait) => PageSize::Size(Size2D { width: p.short_edge().to_computed_value(ctx), height: p.long_edge().to_computed_value(ctx), }), diff --git a/components/style/values/computed/percentage.rs b/components/style/values/computed/percentage.rs index 7430d82d471..4e9732ade2c 100644 --- a/components/style/values/computed/percentage.rs +++ b/components/style/values/computed/percentage.rs @@ -6,6 +6,7 @@ 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; @@ -64,6 +65,12 @@ 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 diff --git a/components/style/values/computed/ratio.rs b/components/style/values/computed/ratio.rs index ba40039eae1..ae8997cfc06 100644 --- a/components/style/values/computed/ratio.rs +++ b/components/style/values/computed/ratio.rs @@ -72,6 +72,16 @@ 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] diff --git a/components/style/values/computed/svg.rs b/components/style/values/computed/svg.rs index 344f1d83518..640c3bfda70 100644 --- a/components/style/values/computed/svg.rs +++ b/components/style/values/computed/svg.rs @@ -8,7 +8,6 @@ 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}; @@ -22,9 +21,8 @@ pub type SVGPaintKind = generic::GenericSVGPaintKind; impl SVGPaint { /// Opaque black color pub fn black() -> Self { - let rgba = RGBA::from_floats(0., 0., 0., 1.).into(); SVGPaint { - kind: generic::SVGPaintKind::Color(rgba), + kind: generic::SVGPaintKind::Color(Color::black()), fallback: generic::SVGPaintFallback::Unset, } } diff --git a/components/style/values/generics/calc.rs b/components/style/values/generics/calc.rs index d9044bbb818..5d52cdf5f5f 100644 --- a/components/style/values/generics/calc.rs +++ b/components/style/values/generics/calc.rs @@ -45,13 +45,33 @@ 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, diff --git a/components/style/values/generics/color.rs b/components/style/values/generics/color.rs index 5b477dee60d..7ab3880bc1a 100644 --- a/components/style/values/generics/color.rs +++ b/components/style/values/generics/color.rs @@ -4,81 +4,268 @@ //! Generic types for color properties. -/// 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. }; -} +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; /// This struct represents a combined color from a numeric color and /// the current foreground color (currentcolor keyword). -#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] +#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)] #[repr(C)] -pub struct GenericColor { +pub enum GenericColor { /// The actual numeric color. - pub color: RGBA, - /// The ratios of mixing between numeric and currentcolor. - /// The formula is: `color * ratios.bg + currentcolor * ratios.fg`. - pub ratios: ComplexColorRatios, + Numeric(RGBA), + /// The `CurrentColor` keyword. + CurrentColor, + /// The color-mix() function. + ColorMix(Box>), +} + +/// 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 ``. + 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> { + 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(&self, dest: &mut CssWriter) -> 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 { + 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 ToCss for ColorMix { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + fn can_omit(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 ColorMix, Percentage> { + fn to_rgba(&self) -> Option + where + RGBA: Clone + ToAnimatedValue, + 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 use self::GenericColor as Color; -impl Color { - /// Returns a color value representing currentcolor. - pub fn currentcolor() -> Self { - Color { - color: cssparser::RGBA::transparent(), - ratios: ComplexColorRatios::CURRENT_COLOR, +impl Color { + /// 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, } } -} -impl Color { - /// Create a color based upon the specified ratios. - pub fn new(color: RGBA, ratios: ComplexColorRatios) -> Self { - Self { color, ratios } + /// 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, + 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); + } + }, + } + } + + /// Returns a color value representing currentcolor. + pub fn currentcolor() -> Self { + Self::CurrentColor } /// Returns a numeric color representing the given RGBA value. pub fn rgba(color: RGBA) -> Self { - Self { - color, - ratios: ComplexColorRatios::NUMERIC, - } - } - - /// Whether it is a numeric color (no currentcolor component). - pub fn is_numeric(&self) -> bool { - self.ratios == ComplexColorRatios::NUMERIC + Self::Numeric(color) } /// Whether it is a currentcolor value (no numeric color component). pub fn is_currentcolor(&self) -> bool { - self.ratios == ComplexColorRatios::CURRENT_COLOR + matches!(*self, Self::CurrentColor) } -} -impl From for Color { - fn from(color: RGBA) -> Self { - Self::rgba(color) + /// Whether it is a numeric color (no currentcolor component). + pub fn is_numeric(&self) -> bool { + matches!(*self, Self::Numeric(..)) } } diff --git a/components/style/values/generics/image.rs b/components/style/values/generics/image.rs index dd504a07d24..d5e830810a8 100644 --- a/components/style/values/generics/image.rs +++ b/components/style/values/generics/image.rs @@ -8,6 +8,7 @@ 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; @@ -71,20 +72,6 @@ pub struct GenericCrossFade { pub elements: crate::OwnedSlice>, } -/// A ` | 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 { - /// `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, @@ -92,7 +79,7 @@ pub enum PercentOrNone { #[repr(C)] pub struct GenericCrossFadeElement { /// The percent of the final image that `image` will be. - pub percent: PercentOrNone, + pub percent: Optional, /// A color or image that will be blended when cross-fade is /// evaluated. pub image: GenericCrossFadeImage, diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index b15d4f01887..8c10085af18 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -310,3 +310,74 @@ impl ClipRectOrAuto { } pub use page::PageSize; + +/// An optional value, much like `Option`, 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 { + #[css(skip)] + None, + Some(T), +} + +impl Optional { + /// 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 { + 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 From> for Optional { + fn from(rust: Option) -> Self { + match rust { + Some(t) => Self::Some(t), + None => Self::None, + } + } +} diff --git a/components/style/values/generics/page.rs b/components/style/values/generics/page.rs index 1de1a8e912c..efb67e0812d 100644 --- a/components/style/values/generics/page.rs +++ b/components/style/values/generics/page.rs @@ -87,7 +87,7 @@ impl PaperSize { ToShmem, )] #[repr(u8)] -pub enum Orientation { +pub enum PageOrientation { /// Portrait orientation Portrait, /// Landscape orientation @@ -95,8 +95,8 @@ pub enum Orientation { } #[inline] -fn is_portrait(orientation: &Orientation) -> bool { - *orientation == Orientation::Portrait +fn is_portrait(orientation: &PageOrientation) -> bool { + *orientation == PageOrientation::Portrait } /// Page size property @@ -110,9 +110,9 @@ pub enum GenericPageSize { /// Page dimensions. Size(S), /// An orientation with no size. - Orientation(Orientation), + Orientation(PageOrientation), /// Paper size by name - PaperSize(PaperSize, #[css(skip_if = "is_portrait")] Orientation), + PaperSize(PaperSize, #[css(skip_if = "is_portrait")] PageOrientation), } pub use self::GenericPageSize as PageSize; diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index 9d7b7d74acc..e221619c843 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -16,7 +16,6 @@ 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; @@ -458,28 +457,33 @@ impl CustomIdent { ident: &CowRcStr<'i>, excluding: &[&str], ) -> Result> { - use crate::properties::CSSWideKeyword; - // https://drafts.csswg.org/css-values-4/#custom-idents: - // - // The CSS-wide keywords are not valid s. The default - // keyword is reserved and is also not a valid . - // - if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { + if !Self::is_valid(ident, excluding) { 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. - // 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 s. The default + // keyword is reserved and is also not a valid . + if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") { + return false; + } + + // 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)) + } } impl ToCss for CustomIdent { @@ -496,45 +500,72 @@ impl ToCss for CustomIdent { /// /// /// +/// +/// We use a single atom for these. Empty atom represents `none` animation. +#[repr(transparent)] #[derive( - Clone, Debug, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, + Clone, Debug, Hash, PartialEq, MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] -#[repr(C, u8)] -pub enum TimelineOrKeyframesName { - /// - Ident(CustomIdent), - /// - QuotedString(Atom), -} +pub struct TimelineOrKeyframesName(Atom); impl TimelineOrKeyframesName { /// pub fn from_ident(value: &str) -> Self { - let location = SourceLocation { line: 0, column: 0 }; - let custom_ident = CustomIdent::from_ident(location, &value.into(), &["none"]).ok(); - match custom_ident { - Some(ident) => Self::Ident(ident), - None => Self::QuotedString(value.into()), - } + 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!("") } /// Create a new TimelineOrKeyframesName from Atom. #[cfg(feature = "gecko")] pub fn from_atom(atom: Atom) -> Self { - debug_assert_ne!(atom, atom!("")); - - // FIXME: We might want to preserve , but currently Gecko - // stores both of and into nsAtom, so - // we can't tell it. - Self::Ident(CustomIdent(atom)) + Self(atom) } /// The name as an Atom pub fn as_atom(&self) -> &Atom { - match *self { - Self::Ident(ref ident) => &ident.0, - Self::QuotedString(ref atom) => atom, + &self.0 + } + + fn parse<'i, 't>(input: &mut Parser<'i, 't>, invalid: &[&str]) -> Result> { + 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(&self, dest: &mut CssWriter, invalid: &[&str]) -> fmt::Result + where + W: Write, + { + debug_assert!(invalid.contains(&"none")); + + if self.0 == atom!("") { + return dest.write_str("none") } + + 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()); } } @@ -547,53 +578,70 @@ pub trait IsAuto { fn is_auto(&self) -> bool; } -impl PartialEq for TimelineOrKeyframesName { - fn eq(&self, other: &Self) -> bool { - self.as_atom() == other.as_atom() +/// The typedef of . +#[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 hash::Hash for TimelineOrKeyframesName { - fn hash(&self, state: &mut H) - where - H: hash::Hasher, - { - self.as_atom().hash(state) +impl Parse for TimelineName { + fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { + Ok(Self(TimelineOrKeyframesName::parse(input, &["none", "auto"])?)) } } -impl Parse for TimelineOrKeyframesName { - fn parse<'i, 't>( - _context: &ParserContext, - input: &mut Parser<'i, 't>, - ) -> Result> { - 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 { +impl ToCss for TimelineName { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { - match *self { - Self::Ident(ref ident) => ident.to_css(dest), - Self::QuotedString(ref atom) => atom.to_string().to_css(dest), - } + self.0.to_css(dest, &["none", "auto"]) } } -/// The typedef of . -pub type TimelineName = TimelineOrKeyframesName; - /// The typedef of . -pub type KeyframesName = TimelineOrKeyframesName; +#[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)) + } + + /// + 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> { + Ok(Self(TimelineOrKeyframesName::parse(input, &["none"])?)) + } +} + +impl ToCss for KeyframesName { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + self.0.to_css(dest, &["none"]) + } +} diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 35b1b3ff648..3a6677de42a 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -17,7 +17,6 @@ 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}; @@ -680,33 +679,30 @@ impl AnimationIterationCount { PartialEq, SpecifiedValueInfo, ToComputedValue, + ToCss, ToResolvedValue, ToShmem, )] #[value_info(other_values = "none")] -pub struct AnimationName(pub Option); +pub struct AnimationName(pub KeyframesName); impl AnimationName { /// Get the name of the animation as an `Atom`. pub fn as_atom(&self) -> Option<&Atom> { - self.0.as_ref().map(|n| n.as_atom()) + if self.is_none() { + return None; + } + Some(self.0.as_atom()) } /// Returns the `none` value. pub fn none() -> Self { - AnimationName(None) + AnimationName(KeyframesName::none()) } -} -impl ToCss for AnimationName { - fn to_css(&self, dest: &mut CssWriter) -> fmt::Result - where - W: Write, - { - match self.0 { - Some(ref name) => name.to_css(dest), - None => dest.write_str("none"), - } + /// Returns whether this is the none value. + pub fn is_none(&self) -> bool { + self.0.is_none() } } @@ -716,14 +712,88 @@ impl Parse for AnimationName { input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(name) = input.try_parse(|input| KeyframesName::parse(context, input)) { - return Ok(AnimationName(Some(name))); + return Ok(AnimationName(name)); } input.expect_ident_matching("none")?; - Ok(AnimationName(None)) + Ok(AnimationName(KeyframesName::none())) } } +/// A value for the 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 + // 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 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(value: &T) -> bool { + *value == Default::default() +} + /// A value for the . /// /// https://drafts.csswg.org/css-animations-2/#typedef-single-animation-timeline @@ -745,10 +815,15 @@ impl Parse for AnimationName { pub enum AnimationTimeline { /// Use default timeline. The animation’s timeline is a DocumentTimeline. Auto, - /// The animation is not associated with a timeline. - None, /// The scroll-timeline name Timeline(TimelineName), + /// 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 { @@ -769,16 +844,30 @@ impl Parse for AnimationTimeline { input: &mut Parser<'i, 't>, ) -> Result> { // We are using the same parser for TimelineName and KeyframesName, but animation-timeline - // accepts "auto", so need to manually parse this. (We can not derive Parse because - // TimelineName excludes only "none" keyword.) + // accepts "auto", so need to manually parse this. (We can not derive + // Parse because TimelineName excludes only the "none" keyword). + // // FIXME: Bug 1733260: we may drop None based on the spec issue: - // Note: https://github.com/w3c/csswg-drafts/issues/6674. + // 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. if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() { return Ok(Self::Auto); } if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { - return Ok(Self::None); + 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), + )) + }); } TimelineName::parse(context, input).map(AnimationTimeline::Timeline) @@ -991,6 +1080,28 @@ 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( @@ -1196,7 +1307,9 @@ impl Parse for WillChange { &["will-change", "none", "all", "auto"], )?; - if ident.0 == atom!("scroll-position") { + 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") { bits |= WillChangeBits::SCROLL; } else { bits |= change_bits_for_maybe_property(&parser_ident, context); @@ -1213,9 +1326,8 @@ impl Parse for WillChange { bitflags! { /// Values for the `touch-action` property. - #[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")] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem, Parse)] + #[css(bitflags(single = "none,auto,manipulation", mixed = "pan-x,pan-y,pinch-zoom"))] #[repr(C)] pub struct TouchAction: u8 { /// `none` variant @@ -1241,178 +1353,32 @@ impl TouchAction { } } -impl ToCss for TouchAction { - fn to_css(&self, dest: &mut CssWriter) -> 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> { - 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, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "none,strict,content,size,layout,paint")] + #[derive(MallocSizeOf, Parse, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + #[css(bitflags(single = "none,strict,content", mixed="size,layout,paint,inline-size", overlapping_bits))] #[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; - /// 'size' variant, turns on size containment - const SIZE = 1 << 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 << 1; + const LAYOUT = 1 << 2; /// `paint` variant, turns on paint containment - const PAINT = 1 << 2; + 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; /// `strict` variant, turns on all types of containment - 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; + const STRICT = 1 << 6 | Contain::LAYOUT.bits | Contain::PAINT.bits | Contain::SIZE.bits; } } -impl ToCss for Contain { - fn to_css(&self, dest: &mut CssWriter) -> 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> { - 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)] +/// https://drafts.csswg.org/css-contain-2/#content-visibility #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[derive( Clone, @@ -1440,6 +1406,61 @@ 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); + +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> { + 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; @@ -1890,10 +1911,6 @@ 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). /// @@ -2101,9 +2118,9 @@ impl Overflow { } bitflags! { - #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "auto,stable,both-edges")] + #[derive(MallocSizeOf, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem, Parse)] #[repr(C)] + #[css(bitflags(single = "auto", mixed = "stable,both-edges", validate_mixed="Self::has_stable"))] /// Values for scrollbar-gutter: /// pub struct ScrollbarGutter: u8 { @@ -2116,56 +2133,9 @@ bitflags! { } } -impl ToCss for ScrollbarGutter { - fn to_css(&self, dest: &mut CssWriter) -> 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> { - 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)) - } +impl ScrollbarGutter { + #[inline] + fn has_stable(self) -> bool { + self.intersects(Self::STABLE) } } diff --git a/components/style/values/specified/calc.rs b/components/style/values/specified/calc.rs index f5448591f21..c3abb9170d5 100644 --- a/components/style/values/specified/calc.rs +++ b/components/style/values/specified/calc.rs @@ -190,9 +190,29 @@ 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!(), }, diff --git a/components/style/values/specified/color.rs b/components/style/values/specified/color.rs index 4c0683dbce0..493c2cf8c85 100644 --- a/components/style/values/specified/color.rs +++ b/components/style/values/specified/color.rs @@ -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::{GenericCaretColor, GenericColorOrAuto}; +use crate::values::generics::color::{ColorInterpolationMethod, GenericColorMix, GenericCaretColor, GenericColorOrAuto}; use crate::values::specified::calc::CalcNode; use crate::values::specified::Percentage; use crate::values::CustomIdent; @@ -19,52 +19,8 @@ use std::io::Write as IoWrite; use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind}; use style_traits::{SpecifiedValueInfo, ToCss, ValueParseErrorKind}; -/// 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, -} +/// A specified color-mix(). +pub type ColorMix = GenericColorMix; #[inline] fn allow_color_mix() -> bool { @@ -74,18 +30,6 @@ 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, @@ -98,100 +42,58 @@ 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| { - input.expect_ident_matching("in")?; - let color_space = if color_spaces_enabled { - ColorSpaceKind::parse(input)? - } else { - input.expect_ident_matching("srgb")?; - ColorSpaceKind::Srgb - }; + let interpolation = ColorInterpolationMethod::parse(context, input)?; input.expect_comma()?; + let try_parse_percentage = |input: &mut Parser| -> Option { + input + .try_parse(|input| Percentage::parse_zero_to_a_hundred(context, input)) + .ok() + }; + + let mut left_percentage = try_parse_percentage(input); + let left = Color::parse(context, input)?; - let left_percentage = input - .try_parse(|input| Percentage::parse(context, input)) - .ok(); + if left_percentage.is_none() { + left_percentage = try_parse_percentage(input); + } input.expect_comma()?; + let mut right_percentage = try_parse_percentage(input); + let right = Color::parse(context, input)?; - 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())) - }); + + 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 left_percentage = left_percentage.unwrap_or_else(|| Percentage::new(1.0 - right_percentage.get())); - let hue_adjuster = input - .try_parse(|input| HueAdjuster::parse(input)) - .unwrap_or(HueAdjuster::Shorter); + 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)); + } Ok(ColorMix { - color_space, + interpolation, left, left_percentage, right, right_percentage, - hue_adjuster, + normalize_weights: true, }) }) } } -impl ToCss for ColorMix { - fn to_css(&self, dest: &mut CssWriter) -> 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 { @@ -204,8 +106,6 @@ pub enum Color { /// Authored representation authored: Option>, }, - /// A complex color value from computed value - Complex(ComputedColor), /// A system color. #[cfg(feature = "gecko")] System(SystemColor), @@ -387,10 +287,6 @@ 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, @@ -597,8 +493,6 @@ 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), @@ -744,23 +638,23 @@ impl Color { /// the context to resolve, then `None` is returned. pub fn to_computed_color(&self, context: Option<&Context>) -> Option { Some(match *self { - Color::CurrentColor => ComputedColor::currentcolor(), - Color::Numeric { ref parsed, .. } => ComputedColor::rgba(*parsed), - Color::Complex(ref complex) => *complex, + Color::CurrentColor => ComputedColor::CurrentColor, + Color::Numeric { ref parsed, .. } => ComputedColor::Numeric(*parsed), Color::ColorMix(ref mix) => { - use crate::values::animated::color::Color as AnimatedColor; - use crate::values::animated::ToAnimatedValue; + use crate::values::computed::percentage::Percentage; - 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, - )) + 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 }, #[cfg(feature = "gecko")] Color::System(system) => system.compute(context?), @@ -778,13 +672,13 @@ impl ToComputedValue for Color { } fn from_computed_value(computed: &ComputedColor) -> Self { - if computed.is_numeric() { - return Color::rgba(computed.color); + 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_currentcolor() { - return Color::currentcolor(); - } - Color::Complex(*computed) } } @@ -812,7 +706,7 @@ impl ToComputedValue for MozFontSmoothingBackgroundColor { fn to_computed_value(&self, context: &Context) -> RGBA { self.0 .to_computed_value(context) - .to_rgba(RGBA::transparent()) + .into_rgba(RGBA::transparent()) } fn from_computed_value(computed: &RGBA) -> Self { @@ -829,7 +723,15 @@ 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", + ]); } } @@ -846,7 +748,7 @@ impl ToComputedValue for ColorPropertyValue { fn to_computed_value(&self, context: &Context) -> RGBA { self.0 .to_computed_value(context) - .to_rgba(context.builder.get_parent_inherited_text().clone_color()) + .into_rgba(context.builder.get_parent_inherited_text().clone_color()) } #[inline] @@ -1008,7 +910,19 @@ 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. diff --git a/components/style/values/specified/image.rs b/components/style/values/specified/image.rs index f4cf417a044..1c1d1c88725 100644 --- a/components/style/values/specified/image.rs +++ b/components/style/values/specified/image.rs @@ -39,6 +39,9 @@ use style_traits::{SpecifiedValueInfo, StyleParseErrorKind, ToCss}; pub type Image = generic::Image; +// Images should remain small, see https://github.com/servo/servo/pull/18430 +size_of_test!(Image, 40); + /// Specified values for a CSS gradient. /// pub type Gradient = generic::Gradient< @@ -60,8 +63,6 @@ pub type CrossFade = generic::CrossFade; pub type CrossFadeElement = generic::CrossFadeElement; /// CrossFadeImage = image | color pub type CrossFadeImage = generic::CrossFadeImage; -/// A specified percentage or nothing. -pub type PercentOrNone = generic::PercentOrNone; /// `image-set()` pub type ImageSet = generic::ImageSet; @@ -315,6 +316,16 @@ impl CrossFade { } impl CrossFadeElement { + fn parse_percentage<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Option { + // 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: + input.try_parse(|input| Percentage::parse_non_negative(context, input)) + .ok() + .map(|p| p.clamp_to_hundred()) + } + /// = ? && [ | ] fn parse<'i, 't>( context: &ParserContext, @@ -322,14 +333,17 @@ impl CrossFadeElement { cors_mode: CorsMode, ) -> Result> { // Try and parse a leading percent sign. - let mut percent = PercentOrNone::parse_or_none(context, input); + let mut percent = Self::parse_percentage(context, input); // Parse the image let image = CrossFadeImage::parse(context, input, cors_mode)?; // Try and parse a trailing percent sign. - if percent == PercentOrNone::None { - percent = PercentOrNone::parse_or_none(context, input); + if percent.is_none() { + percent = Self::parse_percentage(context, input); } - Ok(Self { percent, image }) + Ok(Self { + percent: percent.into(), + image, + }) } } @@ -351,22 +365,6 @@ 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: - // - 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, @@ -735,12 +733,12 @@ impl Gradient { if items.is_empty() { items = vec![ generic::GradientItem::ComplexColorStop { - color: Color::transparent().into(), - position: Percentage::zero().into(), + color: Color::transparent(), + position: LengthPercentage::zero_percent(), }, generic::GradientItem::ComplexColorStop { - color: Color::transparent().into(), - position: Percentage::hundred().into(), + color: Color::transparent(), + position: LengthPercentage::hundred_percent(), }, ]; } else if items.len() == 1 { diff --git a/components/style/values/specified/length.rs b/components/style/values/specified/length.rs index e092f254bd4..159eb996364 100644 --- a/components/style/values/specified/length.rs +++ b/components/style/values/specified/length.rs @@ -21,7 +21,6 @@ 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; @@ -278,45 +277,249 @@ 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. /// /// #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss, ToShmem)] pub enum ViewportPercentageLength { - /// A vw unit: https://drafts.csswg.org/css-values/#vw + /// #[css(dimension)] Vw(CSSFloat), - /// A vh unit: https://drafts.csswg.org/css-values/#vh + /// + #[css(dimension)] + Svw(CSSFloat), + /// + #[css(dimension)] + Lvw(CSSFloat), + /// + #[css(dimension)] + Dvw(CSSFloat), + /// #[css(dimension)] Vh(CSSFloat), - /// + /// + #[css(dimension)] + Svh(CSSFloat), + /// + #[css(dimension)] + Lvh(CSSFloat), + /// + #[css(dimension)] + Dvh(CSSFloat), + /// #[css(dimension)] Vmin(CSSFloat), - /// + /// + #[css(dimension)] + Svmin(CSSFloat), + /// + #[css(dimension)] + Lvmin(CSSFloat), + /// + #[css(dimension)] + Dvmin(CSSFloat), + /// #[css(dimension)] Vmax(CSSFloat), + /// + #[css(dimension)] + Svmax(CSSFloat), + /// + #[css(dimension)] + Lvmax(CSSFloat), + /// + #[css(dimension)] + Dvmax(CSSFloat), + /// + #[css(dimension)] + Vb(CSSFloat), + /// + #[css(dimension)] + Svb(CSSFloat), + /// + #[css(dimension)] + Lvb(CSSFloat), + /// + #[css(dimension)] + Dvb(CSSFloat), + /// + #[css(dimension)] + Vi(CSSFloat), + /// + #[css(dimension)] + Svi(CSSFloat), + /// + #[css(dimension)] + Lvi(CSSFloat), + /// + #[css(dimension)] + Dvi(CSSFloat), } impl ViewportPercentageLength { /// Return true if this is a zero value. fn is_zero(&self) -> bool { - match *self { - ViewportPercentageLength::Vw(v) | - ViewportPercentageLength::Vh(v) | - ViewportPercentageLength::Vmin(v) | - ViewportPercentageLength::Vmax(v) => v == 0., - } + let (_, _, v) = self.unpack(); + v == 0. } fn is_negative(&self) -> bool { - match *self { - ViewportPercentageLength::Vw(v) | - ViewportPercentageLength::Vh(v) | - ViewportPercentageLength::Vmin(v) | - ViewportPercentageLength::Vmax(v) => v < 0., - } + 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, + ), + } + } fn try_sum(&self, other: &Self) -> Result { use self::ViewportPercentageLength::*; @@ -326,14 +529,39 @@ 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(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | + Vh(..) | Svh(..) | Lvh(..) | Dvh(..) | + Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | + Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) | + Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | + Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, } debug_unreachable!("Forgot to handle unit in try_sum()") }, @@ -341,15 +569,24 @@ impl ViewportPercentageLength { } /// Computes the given viewport-relative length for the given viewport size. - pub fn to_computed_value(&self, viewport_size: Size2D) -> 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)) + 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 + } }, }; @@ -578,15 +815,75 @@ 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(()), }) } @@ -770,9 +1067,29 @@ impl Mul 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), } } } @@ -787,14 +1104,39 @@ 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(..) | Vh(..) | Vmin(..) | Vmax(..) => {}, + Vw(..) | Svw(..) | Lvw(..) | Dvw(..) | + Vh(..) | Svh(..) | Lvh(..) | Dvh(..) | + Vmin(..) | Svmin(..) | Lvmin(..) | Dvmin(..) | + Vmax(..) | Svmax(..) | Lvmax(..) | Dvmax(..) | + Vb(..) | Svb(..) | Lvb(..) | Dvb(..) | + Vi(..) | Svi(..) | Lvi(..) | Dvi(..) => {}, } debug_unreachable!("Forgot an arm in partial_cmp?") }, @@ -986,10 +1328,9 @@ impl From for LengthPercentage { impl From for LengthPercentage { #[inline] fn from(pc: Percentage) -> Self { - if pc.is_calc() { - // FIXME(emilio): Hard-coding the clamping mode is suspect. + if let Some(clamping_mode) = pc.calc_clamping_mode() { LengthPercentage::Calc(Box::new(CalcLengthPercentage { - clamping_mode: AllowedNumericType::All, + clamping_mode, node: CalcNode::Leaf(calc::Leaf::Percentage(pc.get())), })) } else { @@ -1022,6 +1363,12 @@ 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>, diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index ac622961642..c06386ef97d 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -37,10 +37,11 @@ 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}; +pub use self::box_::{Appearance, BreakBetween, BreakWithin, ContainerName, ContainerType}; pub use self::box_::{Clear, ContentVisibility, Float, Overflow, OverflowAnchor}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize, ScrollbarGutter}; -pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStrictness, ScrollSnapType}; +pub use self::box_::{ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop}; +pub use self::box_::{ScrollSnapStrictness, ScrollSnapType}; pub use self::box_::{TouchAction, TransitionProperty, VerticalAlign, WillChange}; pub use self::color::{Color, ColorOrAuto, ColorPropertyValue, ColorScheme, PrintColorAdjust}; pub use self::column::ColumnCount; @@ -60,7 +61,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}; +pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant}; pub use self::length::{ NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto, }; @@ -69,7 +70,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::{Orientation, PageName, PageSize, PaperSize}; +pub use self::page::{PageOrientation, PageName, PageSize, PaperSize}; pub use self::percentage::{NonNegativePercentage, Percentage}; pub use self::position::AspectRatio; pub use self::position::{ diff --git a/components/style/values/specified/page.rs b/components/style/values/specified/page.rs index 883f529d867..e660b435327 100644 --- a/components/style/values/specified/page.rs +++ b/components/style/values/specified/page.rs @@ -11,7 +11,7 @@ use crate::values::{generics, CustomIdent}; use cssparser::Parser; use style_traits::ParseError; -pub use generics::page::Orientation; +pub use generics::page::PageOrientation; pub use generics::page::PaperSize; /// Specified value of the @page size descriptor pub type PageSize = generics::page::PageSize>; @@ -24,12 +24,12 @@ impl Parse for PageSize { // Try to parse as [ ] if let Ok(paper_size) = input.try_parse(PaperSize::parse) { let orientation = input - .try_parse(Orientation::parse) - .unwrap_or(Orientation::Portrait); + .try_parse(PageOrientation::parse) + .unwrap_or(PageOrientation::Portrait); return Ok(PageSize::PaperSize(paper_size, orientation)); } // Try to parse as [ ] - if let Ok(orientation) = input.try_parse(Orientation::parse) { + if let Ok(orientation) = input.try_parse(PageOrientation::parse) { if let Ok(paper_size) = input.try_parse(PaperSize::parse) { return Ok(PageSize::PaperSize(paper_size, orientation)); } diff --git a/components/style/values/specified/percentage.rs b/components/style/values/specified/percentage.rs index a152f7d8dc0..1f92c6c3e6a 100644 --- a/components/style/values/specified/percentage.rs +++ b/components/style/values/specified/percentage.rs @@ -92,9 +92,9 @@ impl Percentage { Number::new_with_clamping_mode(self.value, self.calc_clamping_mode) } - /// Returns whether this percentage is a `calc()` value. - pub fn is_calc(&self) -> bool { - self.calc_clamping_mode.is_some() + /// Returns the calc() clamping mode for this percentage. + pub fn calc_clamping_mode(&self) -> Option { + self.calc_clamping_mode } /// Reverses this percentage, preserving calc-ness. @@ -138,6 +138,15 @@ 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::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 { @@ -174,6 +183,24 @@ 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; diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index 26359c155c6..b40460cdcd7 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -556,6 +556,7 @@ impl From for u8 { } } +// TODO: Can be derived with some care. impl Parse for GridAutoFlow { /// [ row | column ] || dense fn parse<'i, 't>( diff --git a/components/style/values/specified/source_size_list.rs b/components/style/values/specified/source_size_list.rs index edb2c09f73e..4023467d1a8 100644 --- a/components/style/values/specified/source_size_list.rs +++ b/components/style/values/specified/source_size_list.rs @@ -6,8 +6,9 @@ #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI}; -use crate::media_queries::{Device, MediaCondition}; +use crate::media_queries::Device; 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; @@ -20,7 +21,7 @@ use style_traits::ParseError; /// https://html.spec.whatwg.org/multipage/#source-size #[derive(Debug)] pub struct SourceSize { - condition: MediaCondition, + condition: QueryCondition, value: Length, } @@ -29,9 +30,8 @@ impl Parse for SourceSize { context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { - let condition = MediaCondition::parse(context, input)?; + let condition = QueryCondition::parse(context, input, FeatureType::Media)?; let value = Length::parse_non_negative(context, input)?; - Ok(Self { condition, value }) } } @@ -56,12 +56,12 @@ impl SourceSizeList { /// Evaluate this to get the final viewport length. pub fn evaluate(&self, device: &Device, quirks_mode: QuirksMode) -> Au { - 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| { + let matching_source_size = self + .source_sizes + .iter() + .find(|source_size| source_size.condition.matches(context)); + match matching_source_size { Some(source_size) => source_size.value.to_computed_value(context), None => match self.value { diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 2d561e04dbd..9c58b6a4572 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -232,8 +232,8 @@ impl ToComputedValue for TextOverflow { } bitflags! { - #[derive(MallocSizeOf, Serialize, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] - #[value_info(other_values = "none,underline,overline,line-through,blink")] + #[derive(MallocSizeOf, Parse, Serialize, SpecifiedValueInfo, ToCss, ToComputedValue, ToResolvedValue, ToShmem)] + #[css(bitflags(single = "none", mixed = "underline,overline,line-through,blink"))] #[repr(C)] /// Specified keyword values for the text-decoration-line property. pub struct TextDecorationLine: u8 { @@ -265,94 +265,6 @@ 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> { - 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(&self, dest: &mut CssWriter) -> 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 @@ -399,6 +311,7 @@ impl TextTransform { } } +// TODO: This can be simplified by deriving it. impl Parse for TextTransform { fn parse<'i, 't>( _context: &ParserContext, @@ -615,11 +528,20 @@ pub enum TextAlign { /// unlike other keywords. #[cfg(feature = "gecko")] MatchParent, - /// `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. + /// 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. #[cfg(feature = "gecko")] - #[css(skip)] + #[parse(condition = "ParserContext::in_ua_or_chrome_sheet")] MozCenterOrInherit, } @@ -1194,6 +1116,7 @@ bitflags! { } } +// TODO: This can be derived with some care. impl Parse for TextUnderlinePosition { fn parse<'i, 't>( _context: &ParserContext, diff --git a/components/style_derive/parse.rs b/components/style_derive/parse.rs index 18ff31c0526..6c1b144066b 100644 --- a/components/style_derive/parse.rs +++ b/components/style_derive/parse.rs @@ -2,14 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::to_css::CssVariantAttrs; +use crate::to_css::{CssBitflagAttrs, CssVariantAttrs}; use darling::FromField; use darling::FromVariant; use derive_common::cg; -use proc_macro2::TokenStream; -use quote::quote; +use proc_macro2::{TokenStream, Span}; +use quote::{quote, TokenStreamExt}; use syn::parse_quote; -use syn::{self, DeriveInput, Path}; +use syn::{self, Ident, DeriveInput, Path}; use synstructure::{Structure, VariantInfo}; #[derive(Default, FromVariant)] @@ -25,6 +25,70 @@ pub struct ParseFieldAttrs { field_bound: bool, } +fn parse_bitflags(bitflags: &CssBitflagAttrs) -> TokenStream { + let mut match_arms = TokenStream::new(); + for (rust_name, css_name) in bitflags.single_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + match_arms.append_all(quote! { + #css_name if result.is_empty() => { + single_flag = true; + Self::#rust_ident + }, + }); + } + + for (rust_name, css_name) in bitflags.mixed_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + match_arms.append_all(quote! { + #css_name => Self::#rust_ident, + }); + } + + let mut validate_condition = quote! { !result.is_empty() }; + if let Some(ref function) = bitflags.validate_mixed { + validate_condition.append_all(quote! { + && #function(result) + }); + } + + // NOTE(emilio): this loop has this weird structure because we run this code + // to parse stuff like text-decoration-line in the text-decoration + // shorthand, so we need to be a bit careful that we don't error if we don't + // consume the whole thing because we find an invalid identifier or other + // kind of token. Instead, we should leave it unconsumed. + quote! { + let mut result = Self::empty(); + loop { + let mut single_flag = false; + let flag: Result<_, style_traits::ParseError<'i>> = input.try_parse(|input| { + Ok(try_match_ident_ignore_ascii_case! { input, + #match_arms + }) + }); + + let flag = match flag { + Ok(flag) => flag, + Err(..) => break, + }; + + if single_flag { + return Ok(flag); + } + + if result.intersects(flag) { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + + result.insert(flag); + } + if #validate_condition { + Ok(result) + } else { + Err(input.new_custom_error(style_traits::StyleParseErrorKind::UnspecifiedError)) + } + } +} + fn parse_non_keyword_variant( where_clause: &mut Option, name: &syn::Ident, @@ -46,6 +110,13 @@ fn parse_non_keyword_variant( let binding_ast = &bindings[0].ast(); let ty = &binding_ast.ty; + if let Some(ref bitflags) = variant_attrs.bitflags { + assert!(skip_try, "Should be the only variant"); + assert!(parse_attrs.condition.is_none(), "Should be the only variant"); + assert!(where_clause.is_none(), "Generic bitflags?"); + return parse_bitflags(bitflags) + } + let field_attrs = cg::parse_field_attrs::(binding_ast); if field_attrs.field_bound { cg::add_predicate(where_clause, parse_quote!(#ty: crate::parser::Parse)); diff --git a/components/style_derive/rustfmt.toml b/components/style_derive/rustfmt.toml new file mode 100644 index 00000000000..c7ad93bafe3 --- /dev/null +++ b/components/style_derive/rustfmt.toml @@ -0,0 +1 @@ +disable_all_formatting = true diff --git a/components/style_derive/specified_value_info.rs b/components/style_derive/specified_value_info.rs index d198990cca4..5d14aaa05ac 100644 --- a/components/style_derive/specified_value_info.rs +++ b/components/style_derive/specified_value_info.rs @@ -71,7 +71,14 @@ pub fn derive(mut input: DeriveInput) -> TokenStream { } }, Data::Struct(ref s) => { - if !derive_struct_fields(&s.fields, &mut types, &mut values) { + if let Some(ref bitflags) = css_attrs.bitflags { + for (_rust_name, css_name) in bitflags.single_flags() { + values.push(css_name) + } + for (_rust_name, css_name) in bitflags.mixed_flags() { + values.push(css_name) + } + } else if !derive_struct_fields(&s.fields, &mut types, &mut values) { values.push(input_name()); } }, diff --git a/components/style_derive/to_css.rs b/components/style_derive/to_css.rs index 40e219c374b..ac0bf05b900 100644 --- a/components/style_derive/to_css.rs +++ b/components/style_derive/to_css.rs @@ -5,15 +5,84 @@ use darling::util::Override; use darling::FromDeriveInput; use darling::FromField; +use darling::FromMeta; use darling::FromVariant; use derive_common::cg; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use quote::{ToTokens, TokenStreamExt}; use syn::parse_quote; -use syn::{self, Data, Path, WhereClause}; +use syn::{self, Data, Ident, Path, WhereClause}; use synstructure::{BindingInfo, Structure, VariantInfo}; +fn derive_bitflags(input: &syn::DeriveInput, bitflags: &CssBitflagAttrs) -> TokenStream { + let name = &input.ident; + let mut body = TokenStream::new(); + for (rust_name, css_name) in bitflags.single_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + body.append_all(quote! { + if *self == Self::#rust_ident { + return dest.write_str(#css_name); + } + }); + } + + body.append_all(quote! { + let mut has_any = false; + }); + + if bitflags.overlapping_bits { + body.append_all(quote! { + let mut serialized = Self::empty(); + }); + } + + for (rust_name, css_name) in bitflags.mixed_flags() { + let rust_ident = Ident::new(&rust_name, Span::call_site()); + let serialize = quote! { + if has_any { + dest.write_char(' ')?; + } + has_any = true; + dest.write_str(#css_name)?; + }; + if bitflags.overlapping_bits { + body.append_all(quote! { + if self.contains(Self::#rust_ident) && !serialized.intersects(Self::#rust_ident) { + #serialize + serialized.insert(Self::#rust_ident); + } + }); + } else { + body.append_all(quote! { + if self.intersects(Self::#rust_ident) { + #serialize + } + }); + } + } + + body.append_all(quote! { + Ok(()) + }); + + quote! { + impl style_traits::ToCss for #name { + #[allow(unused_variables)] + #[inline] + fn to_css( + &self, + dest: &mut style_traits::CssWriter, + ) -> std::fmt::Result + where + W: std::fmt::Write, + { + #body + } + } + } +} + pub fn derive(mut input: syn::DeriveInput) -> TokenStream { let mut where_clause = input.generics.where_clause.take(); for param in input.generics.type_params() { @@ -21,12 +90,21 @@ pub fn derive(mut input: syn::DeriveInput) -> TokenStream { } let input_attrs = cg::parse_input_attrs::(&input); - if let Data::Enum(_) = input.data { + if matches!(input.data, Data::Enum(..)) || input_attrs.bitflags.is_some() { assert!( input_attrs.function.is_none(), - "#[css(function)] is not allowed on enums" + "#[css(function)] is not allowed on enums or bitflags" ); - assert!(!input_attrs.comma, "#[css(comma)] is not allowed on enums"); + assert!( + !input_attrs.comma, + "#[css(comma)] is not allowed on enums or bitflags" + ); + } + + if let Some(ref bitflags) = input_attrs.bitflags { + assert!(!input_attrs.derive_debug, "Bitflags can derive debug on their own"); + assert!(where_clause.is_none(), "Generic bitflags?"); + return derive_bitflags(&input, bitflags); } let match_body = { @@ -249,6 +327,36 @@ fn derive_single_field_expr( expr } +#[derive(Default, FromMeta)] +pub struct CssBitflagAttrs { + /// Flags that can only go on their own, comma-separated. + pub single: String, + /// Flags that can go mixed with each other, comma-separated. + pub mixed: String, + /// Extra validation of the resulting mixed flags. + #[darling(default)] + pub validate_mixed: Option, + /// Whether there are overlapping bits we need to take care of when + /// serializing. + #[darling(default)] + pub overlapping_bits: bool, +} + +impl CssBitflagAttrs { + /// Returns a vector of (rust_name, css_name) of a given flag list. + fn names(s: &str) -> Vec<(String, String)> { + s.split(',').map(|css_name| (cg::to_scream_case(css_name), css_name.to_owned())).collect() + } + + pub fn single_flags(&self) -> Vec<(String, String)> { + Self::names(&self.single) + } + + pub fn mixed_flags(&self) -> Vec<(String, String)> { + Self::names(&self.mixed) + } +} + #[derive(Default, FromDeriveInput)] #[darling(attributes(css), default)] pub struct CssInputAttrs { @@ -257,6 +365,7 @@ pub struct CssInputAttrs { pub function: Option>, // Here because structs variants are also their whole type definition. pub comma: bool, + pub bitflags: Option, } #[derive(Default, FromVariant)] @@ -266,6 +375,7 @@ pub struct CssVariantAttrs { // Here because structs variants are also their whole type definition. pub derive_debug: bool, pub comma: bool, + pub bitflags: Option, pub dimension: bool, pub keyword: Option, pub skip: bool, diff --git a/components/style_traits/Cargo.toml b/components/style_traits/Cargo.toml index 779bb5487b5..8a135354cf8 100644 --- a/components/style_traits/Cargo.toml +++ b/components/style_traits/Cargo.toml @@ -27,6 +27,7 @@ serde = "1.0" servo_arc = { path = "../servo_arc" } servo_atoms = { path = "../atoms", optional = true } servo_url = { path = "../url", optional = true } +size_of_test = { path = "../size_of_test" } to_shmem = { path = "../to_shmem" } to_shmem_derive = { path = "../to_shmem_derive" } webrender_api = { workspace = true, optional = true } diff --git a/components/style_traits/lib.rs b/components/style_traits/lib.rs index 28260272d66..d7c7d8ad33d 100644 --- a/components/style_traits/lib.rs +++ b/components/style_traits/lib.rs @@ -10,6 +10,9 @@ #![crate_type = "rlib"] #![deny(unsafe_code, missing_docs)] +#[macro_use] +extern crate size_of_test; + use bitflags::bitflags; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; @@ -81,9 +84,11 @@ pub use crate::values::{ /// The error type for all CSS parsing routines. pub type ParseError<'i> = cssparser::ParseError<'i, StyleParseErrorKind<'i>>; +size_of_test!(ParseError, 64); /// Error in property value parsing pub type ValueParseError<'i> = cssparser::ParseError<'i, ValueParseErrorKind<'i>>; +size_of_test!(ValueParseError, 48); #[derive(Clone, Debug, PartialEq)] /// Errors that can be encountered while parsing CSS values. @@ -148,6 +153,7 @@ pub enum StyleParseErrorKind<'i> { /// The property is not allowed within a page rule. NotAllowedInPageRule, } +size_of_test!(StyleParseErrorKind, 56); impl<'i> From> for StyleParseErrorKind<'i> { fn from(this: ValueParseErrorKind<'i>) -> Self { @@ -169,6 +175,7 @@ pub enum ValueParseErrorKind<'i> { /// An invalid filter value was encountered. InvalidFilter(Token<'i>), } +size_of_test!(ValueParseErrorKind, 40); impl<'i> StyleParseErrorKind<'i> { /// Create an InvalidValue parse error diff --git a/components/style_traits/values.rs b/components/style_traits/values.rs index 124626bc973..3bae5d2553b 100644 --- a/components/style_traits/values.rs +++ b/components/style_traits/values.rs @@ -44,6 +44,36 @@ use std::fmt::{self, Write}; /// * `#[css(represents_keyword)]` can be used on bool fields in order to /// serialize the field name if the field is true, or nothing otherwise. It /// also collects those keywords for `SpecifiedValueInfo`. +/// * `#[css(bitflags(single="", mixed="", validate="", overlapping_bits)]` can +/// be used to derive parse / serialize / etc on bitflags. The rules for parsing +/// bitflags are the following: +/// +/// * `single` flags can only appear on their own. It's common that bitflags +/// properties at least have one such value like `none` or `auto`. +/// * `mixed` properties can appear mixed together, but not along any other +/// flag that shares a bit with itself. For example, if you have three +/// bitflags like: +/// +/// FOO = 1 << 0; +/// BAR = 1 << 1; +/// BAZ = 1 << 2; +/// BAZZ = BAR | BAZ; +/// +/// Then the following combinations won't be valid: +/// +/// * foo foo: (every flag shares a bit with itself) +/// * bar bazz: (bazz shares a bit with bar) +/// +/// But `bar baz` will be valid, as they don't share bits, and so would +/// `foo` with any other flag, or `bazz` on its own. +/// * `overlapping_bits` enables some tracking during serialization of mixed +/// flags to avoid serializing variants that can subsume other variants. +/// In the example above, you could do: +/// mixed="foo,bazz,bar,baz", overlapping_bits +/// to ensure that if bazz is serialized, bar and baz aren't, even though +/// their bits are set. Note that the serialization order is canonical, +/// and thus depends on the order you specify the flags in. +/// /// * finally, one can put `#[css(derive_debug)]` on the whole type, to /// implement `Debug` by a single call to `ToCss::to_css`. pub trait ToCss { @@ -557,6 +587,8 @@ pub mod specified { NonNegative, /// Allow only numeric values greater or equal to 1.0. AtLeastOne, + /// Allow only numeric values from 0 to 1.0. + ZeroToOne, } impl Default for AllowedNumericType { @@ -577,6 +609,7 @@ pub mod specified { AllowedNumericType::All => true, AllowedNumericType::NonNegative => val >= 0.0, AllowedNumericType::AtLeastOne => val >= 1.0, + AllowedNumericType::ZeroToOne => val >= 0.0 && val <= 1.0, } } @@ -584,9 +617,10 @@ pub mod specified { #[inline] pub fn clamp(&self, val: f32) -> f32 { match *self { - AllowedNumericType::NonNegative if val < 0. => 0., - AllowedNumericType::AtLeastOne if val < 1. => 1., - _ => val, + AllowedNumericType::All => val, + AllowedNumericType::NonNegative => val.max(0.), + AllowedNumericType::AtLeastOne => val.max(1.), + AllowedNumericType::ZeroToOne => val.max(0.).min(1.), } } } diff --git a/servo-tidy.toml b/servo-tidy.toml index a2d4f10c724..0560ae5afd8 100644 --- a/servo-tidy.toml +++ b/servo-tidy.toml @@ -86,32 +86,9 @@ packages = [ # Files that are ignored for all tidy and lint checks. files = [ "./components/net/tests/parsable_mime/text", - # These are ignored to avoid diverging from Gecko - "./components/style/counter_style/mod.rs", - "./components/style/properties/declaration_block.rs", - "./components/style/properties/helpers.mako.rs", - "./components/style/rule_collector.rs", - "./components/style/selector_map.rs", - "./components/style/stylesheets/import_rule.rs", - "./components/style/stylesheets/layer_rule.rs", - "./components/style/stylesheets/origin.rs", - "./components/style/stylesheets/page_rule.rs", - "./components/style/stylesheets/rule_parser.rs", - "./components/style/stylesheets/scroll_timeline_rule.rs", - "./components/style/stylist.rs", - "./components/style/values/animated/transform.rs", - "./components/style/values/computed/font.rs", - "./components/style/values/computed/image.rs", - "./components/style/values/specified/box.rs", - "./components/style/values/specified/color.rs", - "./components/style/values/specified/transform.rs", - # Mako does not lend itself easily to splitting long lines - "./components/style/properties/helpers/animated_properties.mako.rs", - "./components/style/properties/shorthands/text.mako.rs", - # Long regexes are long. - "./components/style/gecko/regen_atoms.py", - # Helper macro where actually a pseudo-element per line makes sense. - "./components/style/gecko/non_ts_pseudo_class_list.rs", + # Ignore style files to avoid diverging too much from upstream Gecko + "./components/style/", + "./components/style_derive/parse.rs", "./resources/hsts_preload.json", "./tests/wpt/meta/MANIFEST.json", "./tests/wpt/meta-legacy-layout/MANIFEST.json", diff --git a/tests/unit/style/Cargo.toml b/tests/unit/style/Cargo.toml index c9f17dcf973..38439c1f280 100644 --- a/tests/unit/style/Cargo.toml +++ b/tests/unit/style/Cargo.toml @@ -21,7 +21,6 @@ servo_arc = {path = "../../../components/servo_arc"} servo_atoms = {path = "../../../components/atoms"} servo_config = {path = "../../../components/config"} servo_url = {path = "../../../components/url"} -size_of_test = {path = "../../../components/size_of_test"} style = {path = "../../../components/style", features = ["servo"]} style_traits = {path = "../../../components/style_traits"} std_test_override = { path = "../../../components/std_test_override" } diff --git a/tests/unit/style/lib.rs b/tests/unit/style/lib.rs index c87c8fbc370..3ccb818dfcb 100644 --- a/tests/unit/style/lib.rs +++ b/tests/unit/style/lib.rs @@ -16,9 +16,6 @@ extern crate serde_json; extern crate servo_arc; extern crate servo_atoms; extern crate servo_url; -#[macro_use] -extern crate size_of_test; -#[macro_use] extern crate style; extern crate style_traits; extern crate test; @@ -30,8 +27,6 @@ mod logical_geometry; mod parsing; mod properties; mod rule_tree; -mod size_of; -mod specified_values; mod str; mod stylesheets; mod stylist; diff --git a/tests/unit/style/size_of.rs b/tests/unit/style/size_of.rs deleted file mode 100644 index cb64495141b..00000000000 --- a/tests/unit/style/size_of.rs +++ /dev/null @@ -1,51 +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/. */ - -use selectors::parser::{SelectorParseError, SelectorParseErrorKind}; -use style::invalidation::element::invalidation_map::Dependency; -use style::properties; - -size_of_test!(test_size_of_dependency, Dependency, 24); - -size_of_test!( - test_size_of_property_declaration, - properties::PropertyDeclaration, - 32 -); - -// 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!( - test_size_of_parsed_declaration, - properties::SourcePropertyDeclaration, - 568 -); - -size_of_test!( - test_size_of_selector_parse_error_kind, - SelectorParseErrorKind, - 40 -); -size_of_test!( - test_size_of_style_parse_error_kind, - ::style_traits::StyleParseErrorKind, - 56 -); -size_of_test!( - test_size_of_value_parse_error_kind, - ::style_traits::ValueParseErrorKind, - 40 -); - -size_of_test!(test_size_of_selector_parse_error, SelectorParseError, 48); -size_of_test!( - test_size_of_style_traits_parse_error, - ::style_traits::ParseError, - 64 -); -size_of_test!( - test_size_of_value_parse_error, - ::style_traits::ValueParseError, - 48 -); diff --git a/tests/unit/style/specified_values.rs b/tests/unit/style/specified_values.rs deleted file mode 100644 index d5c061ab502..00000000000 --- a/tests/unit/style/specified_values.rs +++ /dev/null @@ -1,50 +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/. */ - -use style; - -#[cfg(all(test, target_pointer_width = "64"))] -#[test] -fn size_of_specified_values() { - use std::mem::size_of; - let threshold = 24; - - let mut bad_properties = vec![]; - - macro_rules! check_property { - ( $( { $name: ident, $boxed: expr } )+ ) => { - $( - let size = size_of::(); - let is_boxed = $boxed; - if (!is_boxed && size > threshold) || (is_boxed && size <= threshold) { - bad_properties.push((stringify!($name), size, is_boxed)); - } - )+ - } - } - - longhand_properties_idents!(check_property); - - let mut failing_messages = vec![]; - - for bad_prop in bad_properties { - if !bad_prop.2 { - failing_messages.push( - format!("Your changes have increased the size of {} SpecifiedValue to {}. The threshold is \ - currently {}. SpecifiedValues affect size of PropertyDeclaration enum and \ - increasing the size may negative affect style system performance. Please consider \ - using `boxed=\"True\"` in this longhand.", - bad_prop.0, bad_prop.1, threshold)); - } else if bad_prop.2 { - failing_messages.push( - format!("Your changes have decreased the size of {} SpecifiedValue to {}. Good work! \ - The threshold is currently {}. Please consider removing `boxed=\"True\"` from this longhand.", - bad_prop.0, bad_prop.1, threshold)); - } - } - - if !failing_messages.is_empty() { - panic!("{}", failing_messages.join("\n\n")); - } -} diff --git a/tests/unit/style/stylist.rs b/tests/unit/style/stylist.rs index 46d83fca317..b680d8188a2 100644 --- a/tests/unit/style/stylist.rs +++ b/tests/unit/style/stylist.rs @@ -16,10 +16,9 @@ use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::selector_map::SelectorMap; use style::selector_parser::{SelectorImpl, SelectorParser}; use style::shared_lock::SharedRwLock; -use style::stylesheets::layer_rule::LayerId; use style::stylesheets::StyleRule; use style::stylist::needs_revalidation_for_testing; -use style::stylist::{Rule, Stylist}; +use style::stylist::{ContainerConditionId, LayerId, Rule, Stylist}; use style::thread_state::{self, ThreadState}; /// Helper method to get some Rules from selector strings. @@ -56,6 +55,7 @@ fn get_mock_rules(css_selectors: &[&str]) -> (Vec>, SharedRwLock) { locked.clone(), i as u32, LayerId::root(), + ContainerConditionId::none(), ) }) .collect() diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini deleted file mode 100644 index 06a82c1aaba..00000000000 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-compute.html.ini +++ /dev/null @@ -1,90 +0,0 @@ -[viewport-units-compute.html] - [100vi computes to 200px] - expected: FAIL - - [100svw computes to 200px] - expected: FAIL - - [100svi computes to 200px] - expected: FAIL - - [100svmax computes to 200px] - expected: FAIL - - [100lvw computes to 200px] - expected: FAIL - - [100lvi computes to 200px] - expected: FAIL - - [100lvmax computes to 200px] - expected: FAIL - - [100dvw computes to 200px] - expected: FAIL - - [100dvi computes to 200px] - expected: FAIL - - [100dvmax computes to 200px] - expected: FAIL - - [100vb computes to 100px] - expected: FAIL - - [100svh computes to 100px] - expected: FAIL - - [100svb computes to 100px] - expected: FAIL - - [100svmin computes to 100px] - expected: FAIL - - [100lvh computes to 100px] - expected: FAIL - - [100lvb computes to 100px] - expected: FAIL - - [100lvmin computes to 100px] - expected: FAIL - - [100dvh computes to 100px] - expected: FAIL - - [100dvb computes to 100px] - expected: FAIL - - [100dvmin computes to 100px] - expected: FAIL - - [1dvw computes to 2px] - expected: FAIL - - [10dvw computes to 20px] - expected: FAIL - - [1dvh computes to 1px] - expected: FAIL - - [10dvh computes to 10px] - expected: FAIL - - [calc(1dvw + 1dvw) computes to 4px] - expected: FAIL - - [calc(1dvw + 1dvh) computes to 3px] - expected: FAIL - - [calc(1dvw + 100px) computes to 102px] - expected: FAIL - - [max(1svw, 1svh) computes to 2px] - expected: FAIL - - [min(1lvw, 1lvh) computes to 1px] - expected: FAIL - - [calc(1dvw + 10%) computes to 12px] - expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini deleted file mode 100644 index 804ae41be0b..00000000000 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-keyframes.html.ini +++ /dev/null @@ -1,60 +0,0 @@ -[viewport-units-keyframes.html] - [Interpolation from 0px to 100vi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svw is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svmax is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvw is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvmax is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvw is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvi is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvmax is 100px at 50%] - expected: FAIL - - [Interpolation from 0px to 100vb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svh is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100svmin is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvh is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100lvmin is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvh is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvb is 50px at 50%] - expected: FAIL - - [Interpolation from 0px to 100dvmin is 50px at 50%] - expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini index 3ab74d58d8e..f35893c7154 100644 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini +++ b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-media-queries.html.ini @@ -1,34 +1,4 @@ [viewport-units-media-queries.html] - [@media(width:100vi) applies] - expected: FAIL - - [@media(width:100svw) applies] - expected: FAIL - - [@media(width:100svi) applies] - expected: FAIL - - [@media(width:100svmax) applies] - expected: FAIL - - [@media(width:100lvw) applies] - expected: FAIL - - [@media(width:100lvi) applies] - expected: FAIL - - [@media(width:100lvmax) applies] - expected: FAIL - - [@media(width:100dvw) applies] - expected: FAIL - - [@media(width:100dvi) applies] - expected: FAIL - - [@media(width:100dvmax) applies] - expected: FAIL - [@media(height:100vh) applies] expected: FAIL diff --git a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini b/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini deleted file mode 100644 index cefd79faca5..00000000000 --- a/tests/wpt/meta-legacy-layout/css/css-values/viewport-units-parsing.html.ini +++ /dev/null @@ -1,60 +0,0 @@ -[viewport-units-parsing.html] - [e.style['width'\] = "1vi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1vb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmax" should set the property value] - expected: FAIL diff --git a/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini b/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini index adcbd9435b3..9fbce555a33 100644 --- a/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini +++ b/tests/wpt/meta/css/css-values/viewport-units-media-queries.html.ini @@ -1,34 +1,4 @@ [viewport-units-media-queries.html] - [@media(width:100vi) applies] - expected: FAIL - - [@media(width:100svw) applies] - expected: FAIL - - [@media(width:100svi) applies] - expected: FAIL - - [@media(width:100svmax) applies] - expected: FAIL - - [@media(width:100lvw) applies] - expected: FAIL - - [@media(width:100lvi) applies] - expected: FAIL - - [@media(width:100lvmax) applies] - expected: FAIL - - [@media(width:100dvw) applies] - expected: FAIL - - [@media(width:100dvi) applies] - expected: FAIL - - [@media(width:100dvmax) applies] - expected: FAIL - [@media(height:100vh) applies] expected: FAIL diff --git a/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini b/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini deleted file mode 100644 index cefd79faca5..00000000000 --- a/tests/wpt/meta/css/css-values/viewport-units-parsing.html.ini +++ /dev/null @@ -1,60 +0,0 @@ -[viewport-units-parsing.html] - [e.style['width'\] = "1vi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1vb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1svmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1lvmax" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvw" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvh" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvi" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvb" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmin" should set the property value] - expected: FAIL - - [e.style['width'\] = "1dvmax" should set the property value] - expected: FAIL